第二章:核心业务逻辑——订单生命周期、做市策略、套利策略、信号生成与执行
各位同学,欢迎来到第二章。上一章我们聊了系统架构的骨架,这一章咱们来填血肉——核心业务逻辑。
说白了,高频交易系统再花哨,最终落地就四件事:订单怎么跑、做市怎么报、套利怎么抓、信号怎么发。我做了这么多年,见过太多团队架构画得漂亮,一到这四块就露怯。今天咱们一个一个拆开讲。
2.1 订单生命周期:从诞生到消亡
订单生命周期,是交易系统的“心跳”。你想想看,一笔订单从客户端敲下去,到交易所确认成交,中间经历了什么?
我个人习惯把订单生命周期拆成六个阶段:
- 创建:策略模块生成订单指令,包含价格、数量、方向、有效期等。
- 校验:风控模块检查资金、持仓、价格限制等。嗯,这里要注意,校验不过的订单千万别硬发,我见过有人跳过校验直接发单,结果爆仓了。
- 路由:根据交易所、品种、流动性,选择最优路径。
- 发送:通过网关发往交易所。
- 确认:收到交易所回执,更新订单状态(已报、部分成交、全部成交、已撤单等)。
- 结算:成交后更新持仓、资金、统计信息。
核心原则:订单状态机必须严格无环。我曾经在一个项目里发现状态机有回路,导致一笔订单被重复结算了两次,亏了十几万。从那以后,我要求所有订单状态转换必须画成有向无环图(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毫秒才发出去,价格早就变了。这种信号再准也没用。
信号生成的核心流程:
- 数据预处理:去噪、对齐、填充缺失值。嗯,这里要注意,不同交易所的数据时间戳可能不同步,必须做对齐。
- 特征计算:价格、成交量、波动率、订单簿不平衡等。我习惯用滑动窗口计算,窗口大小根据品种调整。
- 信号判定:根据特征和规则,生成买卖信号。可以是阈值触发,也可以是机器学习模型。
- 信号过滤:防止假信号。我常用的方法是“确认后再发”——信号出现后,等一个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