4. 金融行业(三)

金融行业这一块,咱们今天聊点硬核的——量化交易平台。说白了,就是用程序代替人脑做决策,机器下单。我这些年接触过不少量化团队,从几个人小作坊到几十人的自营部门,架构上踩过的坑真不少。今天重点拆解三个核心模块:低延迟行情接入、策略回测框架、订单管理系统。

嗯,先看一张整体架构图,心里有个谱。

量化交易平台核心架构图 量化交易平台核心架构 行情接入层 UDP组播 · 行情网关 · 解码 策略回测框架 Tick级回测 · 参数优化 · 绩效 订单管理系统 路由 · 风控 · 生命周期 高性能消息总线 / 内存网格 持久化层:时序数据库 · 关系库 · 对象存储 实时监控 · 延迟追踪 · 告警 风控引擎 · 资金校验 · 限仓 核心设计目标:微秒级端到端延迟 · 零数据丢失 · 确定性回测 行情驱动策略 → 策略驱动订单 → 订单驱动成交 → 成交反馈行情

一、低延迟行情接入:与时间赛跑的第一公里

量化交易里,行情就是生命线。你比别人晚1毫秒看到价格,可能就错过了一笔交易。我个人习惯把行情接入拆成三层:网络层、解码层、分发层。

1.1 网络层:放弃TCP,拥抱UDP组播

为什么?TCP有重传机制,丢包了要等,延迟不可控。交易所的行情基本都是UDP组播,一发多收。我在项目中遇到过一个问题:组播数据流太大,网卡中断处理不过来,导致丢包。后来怎么解决的?

  • 网卡调优:开启RSS(接收端缩放),多队列分散中断到不同CPU核心。
  • 内核旁路:用DPDK或者Solarflare的OpenOnload,跳过内核协议栈,用户态直接收包。
  • 绑定核心:收行情线程固定在一个物理核心上,避免上下文切换。
我的经验:别迷信“万兆网卡就够”。行情峰值时,一个交易所的Level 2数据就能吃掉几百Mbps带宽。如果接多个交易所,建议用25G网卡起步。

1.2 解码层:快,还要准

交易所的行情格式五花八门:STEP、Binary、FIX/FAST……解码速度直接影响延迟。我见过有人用Python写解码器,延迟直接飙到几十微秒。说实话,这不行。

正确的做法:

  • 用C++或Rust写解码器,内存直接映射,零拷贝。
  • 预分配对象池,避免运行时动态分配内存。
  • 用SIMD指令加速字段解析,比如一次处理16个字节。
// 简化的行情解码示例(C++风格伪代码)
struct MarketDataPacket {
    uint64_t timestamp;   // 纳秒时间戳
    uint32_t price;       // 价格,放大1万倍存储
    uint32_t volume;      // 成交量
    char     symbol[8];   // 合约代码
};

// 直接从内存映射区域读取,无拷贝
const MarketDataPacket* packet = 
    reinterpret_cast<const MarketDataPacket*>(mmap_addr + offset);
注意:解码时一定要做边界检查。我曾经见过一个团队,因为一个异常包导致解码器崩溃,整个交易系统停了3分钟。嗯,那天的损失……不提了。

1.3 分发层:谁需要谁拿走

解码后的行情要分发给多个策略实例。这里我推荐用无锁环形缓冲区(Lock-Free Ring Buffer)。为什么?因为互斥锁在争抢时,延迟会急剧恶化。

分发方式 延迟(P50) 延迟(P99) 适用场景
互斥锁 + 条件变量 ~500 ns ~5 μs 低频策略,非核心
无锁环形缓冲区 ~50 ns ~200 ns 高频策略,推荐
共享内存 + 信号量 ~100 ns ~1 μs 跨进程分发

二、策略回测框架:用历史数据检验你的想法

回测是量化策略的试金石。但很多人把回测做成了“过拟合大赛”。我见过一个策略,回测年化收益200%,实盘一个月亏了30%。为什么会这样?

说白了,回测框架的设计决定了结果的可靠性。我建议关注三个要点:

2.1 数据精度:用Tick级,别用分钟级

分钟级回测会忽略很多细节:比如开盘瞬间的冲击成本、大单扫货的滑点。我个人习惯用Tick级数据回测,虽然数据量大(一天可能几GB),但结果更真实。

  • 数据清洗:剔除异常Tick(比如价格超过涨跌停板)。
  • 复权处理:分红、送股、拆合都要精确到Tick时间点。
  • 对齐时间戳:不同交易所的时间基准可能不同,需要统一到纳秒级。

2.2 撮合引擎:模拟真实交易所

回测的核心是撮合逻辑。不能简单用“最新价成交”,要考虑盘口深度。我建议实现一个订单簿回测引擎:

// 订单簿回测核心逻辑(伪代码)
class OrderBook {
    // 买盘,价格从高到低
    std::map<double, double, std::greater<double>> bids;
    // 卖盘,价格从低到高
    std::map<double, double> asks;

    // 限价单撮合
    MatchResult matchLimitOrder(Order order) {
        if (order.side == BUY) {
            // 从最低卖价开始吃
            for (auto it = asks.begin(); it != asks.end(); ) {
                if (it->first > order.price) break;
                double traded = std::min(order.volume, it->second);
                // 记录成交
                it->second -= traded;
                if (it->second == 0) it = asks.erase(it);
                else ++it;
                order.volume -= traded;
                if (order.volume == 0) break;
            }
        }
        // 类似逻辑处理SELL
    }
};
关键点:回测时一定要考虑滑点模型和手续费。我见过太多策略,回测时忽略滑点,实盘被市场摩擦成本吃光利润。建议至少加一个固定滑点(比如1跳)和一个比例滑点(比如成交量的0.1%)。

2.3 绩效评估:别只看夏普比率

夏普比率是经典指标,但不够。我建议至少看这几个:

指标 含义 我的经验阈值
年化收益率 复利年化收益 > 20% 才值得实盘
最大回撤 从峰值到谷底的最大跌幅 < 15% 比较安全
收益回撤比 年化收益 / 最大回撤 > 2.0 算合格
胜率 盈利交易占比 40%-60% 正常
盈亏比 平均盈利 / 平均亏损 > 1.5 较好
避坑指南:我曾经犯过一个错误——用全部历史数据做回测,然后直接实盘。结果市场风格一变,策略立刻失效。正确的做法是:留出至少20%的数据做样本外测试,再跑一段模拟盘(Paper Trading)验证。

三、订单管理系统:从决策到成交的最后一公里

订单管理系统(OMS)是策略和交易所之间的桥梁。它不复杂,但必须零差错。因为一个错误的订单可能造成巨大损失。

3.1 订单生命周期管理

一个订单从创建到成交或撤销,状态变化必须严格记录。我习惯用状态机来管理:

// 订单状态枚举
enum OrderStatus {
    PENDING_NEW,    // 待发送
    NEW,            // 已发送,未成交
    PARTIALLY_FILLED, // 部分成交
    FILLED,         // 全部成交
    CANCELLED,      // 已撤销
    REJECTED,       // 被交易所拒绝
    EXPIRED         // 过期
};

每个状态变更都要记录时间戳和原因。为什么?因为事后分析时,你需要知道“订单为什么被拒”或者“为什么成交慢了”。

3.2 订单路由:智能选择交易场所

现在很多品种在多个交易所上市。比如股指期货,中金所、新加坡A50都有。订单路由要解决一个问题:去哪下单最划算?

  • 价格优先:选最优价格的市场。
  • 流动性优先:选盘口深度最好的市场。
  • 延迟优先:选网络延迟最低的市场。
  • 成本优先:考虑手续费和过户费差异。
我的做法:给每个市场打一个综合分,加权计算。比如价格权重0.5,流动性0.3,延迟0.2。然后选分数最高的。这个权重可以动态调整,比如市场波动大时,提高延迟的权重。

3.3 风控:OMS的最后一道防线

OMS里必须内嵌风控模块。我见过一个团队,因为OMS没有做资金校验,策略下了一个超过账户余额10倍的订单,结果被交易所罚款。嗯,这种错误一次都不能犯。

必备的风控检查:

  1. 资金校验:下单金额不能超过可用资金。
  2. 持仓校验:卖空数量不能超过持仓(如果是股票)。
  3. 频率限制:每秒下单次数不能超过阈值(比如100笔/秒)。
  4. 价格限制:订单价格不能偏离最新价太远(比如±5%)。
  5. 自成交防护:避免自己的买单和卖单互相成交。
// 风控检查伪代码
bool RiskCheck(const Order& order) {
    // 1. 资金校验
    if (order.volume * order.price > account.available_balance) {
        log("资金不足");
        return false;
    }
    // 2. 频率限制
    if (order_counter.increment_and_check() > MAX_ORDER_PER_SEC) {
        log("下单频率超限");
        return false;
    }
    // 3. 价格限制
    double last_price = market_data.get_last_price(order.symbol);
    if (std::abs(order.price - last_price) / last_price > 0.05) {
        log("价格偏离过大");
        return false;
    }
    return true;
}
核心原则:风控检查必须在订单发送前执行,而不是发送后。一旦订单到了交易所,再想撤回就来不及了。我建议把风控逻辑放在OMS的入口处,作为第一道关卡。

四、总结:架构设计的三个核心原则

聊了这么多,其实就三个原则:

  • 低延迟:从行情到订单,每一微秒都要争取。用C++、用DPDK、用无锁结构。
  • 高可靠:数据不丢、订单不错、状态可追溯。用状态机、用日志、用风控。
  • 可回测:回测越真实,实盘越放心。用Tick数据、用订单簿撮合、用样本外测试。

量化交易平台的设计,说白了就是一场与时间、与风险、与不确定性的博弈。架构师的任务,就是让这场博弈的胜率,尽可能高一点。