第二章:核心业务逻辑——订单生命周期、做市策略、套利策略、信号生成与执行

各位同学,欢迎来到第二章。上一章我们聊了系统架构的骨架,这一章咱们来填血肉——核心业务逻辑。

说白了,高频交易系统再花哨,最终落地就四件事:订单怎么跑、做市怎么报、套利怎么抓、信号怎么发。我做了这么多年,见过太多团队架构画得漂亮,一到这四块就露怯。今天咱们一个一个拆开讲。

2.1 订单生命周期:从诞生到消亡

订单生命周期,是交易系统的“心跳”。你想想看,一笔订单从客户端敲下去,到交易所确认成交,中间经历了什么?

我个人习惯把订单生命周期拆成六个阶段:

  1. 创建:策略模块生成订单指令,包含价格、数量、方向、有效期等。
  2. 校验:风控模块检查资金、持仓、价格限制等。嗯,这里要注意,校验不过的订单千万别硬发,我见过有人跳过校验直接发单,结果爆仓了。
  3. 路由:根据交易所、品种、流动性,选择最优路径。
  4. 发送:通过网关发往交易所。
  5. 确认:收到交易所回执,更新订单状态(已报、部分成交、全部成交、已撤单等)。
  6. 结算:成交后更新持仓、资金、统计信息。

核心原则:订单状态机必须严格无环。我曾经在一个项目里发现状态机有回路,导致一笔订单被重复结算了两次,亏了十几万。从那以后,我要求所有订单状态转换必须画成有向无环图(DAG)。

下面这张图是我常用的订单状态机,你们可以照着画:

创建 校验 路由 发送 确认 结算 拒绝 通过 校验失败 回执 成交

避坑指南:订单ID生成一定要全局唯一、单调递增。我曾经用UUID做订单ID,结果排查问题时根本没法排序,后来改成“时间戳+自增序列”才搞定。

2.2 做市策略:赚的是“流动性”的钱

做市策略,说白了就是两边挂单,赚差价。但实际做起来,远没这么简单。

我见过很多新手做市商,上来就挂对称价差,结果被高频收割者打得鼻青脸肿。为什么?因为市场不是对称的。

一个成熟的做市策略,至少要考虑以下因素:

  • 库存风险:你手里有多少货?库存多了,方向性风险就大。我习惯用“库存偏移量”来动态调整报价。
  • 波动率调整:波动率越高,价差应该越大。否则你就是在给市场送钱。
  • 订单簿不平衡:买盘厚还是卖盘厚?这直接影响你的报价偏向。
  • 对手方识别:是不是有“毒流”在吃你的单?我曾经发现某个账户专门吃我的被动单,后来一查,是另一家做市商在套我的价。

核心公式:最优报价 = 中间价 ± (基础价差 × 波动率系数 + 库存调整项)

其中库存调整项 = 库存偏移量 × 库存惩罚因子。这个因子我一般设成0.1到0.3之间,具体看品种。

代码实现上,我建议用事件驱动的方式。每次行情更新,重新计算报价,然后批量撤单、重新挂单。注意,撤单和挂单之间要尽量无缝衔接,否则会被别人抢了位置。

// 伪代码:做市报价更新
void onMarketUpdate(const MarketData& md) {
    double midPrice = (md.bidPrice + md.askPrice) / 2.0;
    double spread = baseSpread * volatilityFactor(md);
    double inventoryAdj = inventory * inventoryPenalty;
    
    double bidPrice = midPrice - spread/2 - inventoryAdj;
    double askPrice = midPrice + spread/2 - inventoryAdj;
    
    cancelAllOrders();
    sendOrder(Side::BUY, bidPrice, baseQty);
    sendOrder(Side::SELL, askPrice, baseQty);
}

警告:做市策略最怕“闪崩”。市场瞬间暴跌时,你的买单会被大量吃掉,库存瞬间爆仓。我建议设置硬性止损线,一旦库存超过阈值,立即停止做市并反向对冲。

2.3 套利策略:赚的是“定价错误”的钱

套利,说白了就是同一个东西在不同地方卖不同价,你低价买、高价卖。但高频套利拼的是速度,不是智商。

常见的套利类型有三种:

套利类型 原理 延迟要求 我踩过的坑
跨市场套利 同一品种在不同交易所价差 < 1ms 网络抖动导致套利变亏损
跨品种套利 相关品种价差偏离(如沪深300与IF期货) < 10ms 相关性突变没及时调整参数
期现套利 期货与现货价差 < 100ms 交割日效应没考虑进去

我个人最常用的是跨市场套利。为什么?因为确定性最高。你想想看,同一只股票在A交易所和B交易所,价格理论上应该一样。如果不一样,就是机会。

但这里有个关键点:套利窗口的持续时间极短。我实测过,在流动性好的品种上,套利窗口通常只有几十微秒到几毫秒。所以你的检测逻辑必须放在FPGA或者内核态,不能在用户态做。

经验之谈:套利策略的“入场”和“出场”要分开考虑。入场要快,出场要稳。我曾经为了抢入场,忽略了出场流动性,结果套利变成了单边持仓,亏了不少。

2.4 信号生成与执行:从想法到订单

信号生成,是策略的大脑。执行,是策略的手脚。两者缺一不可。

我见过太多团队,信号做得花里胡哨,执行却一塌糊涂。信号说“买”,结果执行花了50毫秒才发出去,价格早就变了。这种信号再准也没用。

信号生成的核心流程:

  1. 数据预处理:去噪、对齐、填充缺失值。嗯,这里要注意,不同交易所的数据时间戳可能不同步,必须做对齐。
  2. 特征计算:价格、成交量、波动率、订单簿不平衡等。我习惯用滑动窗口计算,窗口大小根据品种调整。
  3. 信号判定:根据特征和规则,生成买卖信号。可以是阈值触发,也可以是机器学习模型。
  4. 信号过滤:防止假信号。我常用的方法是“确认后再发”——信号出现后,等一个tick确认,再执行。

执行的关键指标

  • 延迟:从信号生成到订单发出的时间。目标:< 10微秒。
  • 滑点:实际成交价与信号触发价的差异。目标:< 0.5个tick。
  • 成交率:发出的订单最终成交的比例。目标:> 90%。

执行层面,我建议采用“分层执行”架构:

  • 信号层:生成信号,不关心具体怎么发单。
  • 执行层:接收信号,拆分成子订单,管理订单生命周期。
  • 网关层:负责与交易所通信,处理协议转换。

这种分层的好处是,信号层可以独立优化,执行层可以复用。我曾经在一个项目里,把执行层抽出来做成通用组件,后来三个策略团队都在用,省了不少开发时间。

// 伪代码:信号生成与执行流程
void onTick(const Tick& tick) {
    // 1. 计算特征
    double imbalance = (tick.bidVol - tick.askVol) / (tick.bidVol + tick.askVol);
    
    // 2. 信号判定
    if (imbalance > 0.7) {
        Signal sig = {Side::BUY, tick.askPrice, 100};
        
        // 3. 信号过滤:确认一次
        if (lastImbalance > 0.6) {
            executionLayer.execute(sig);
        }
    }
    
    lastImbalance = imbalance;
}

重要提醒:信号生成和执行之间,绝对不能有阻塞操作。我曾经见过有人在信号生成里写日志,结果磁盘I/O把整个流程卡住了。记住,高频交易里,延迟就是生命。

好了,这一章的内容就到这里。核心业务逻辑这块,说白了就是“快、准、稳”。订单生命周期要稳,做市策略要准,套利策略要快,信号执行要三者兼备。你们在实际开发中,一定要多从这几个角度去思考。

公众号:蓝海资料掘金营,微信deep3321