第12章:订单管理模块:订单生命周期管理、订单取消与重发策略、订单路由逻辑、FIFO与Pro-Rata撮合规则适配
做市商系统里,订单管理模块就像人的心脏。它跳得好,整个策略就活着;它出问题,再好的定价模型也是白搭。我这些年踩过的坑,有一半都跟订单管理有关。今天咱们就把这块掰开揉碎了讲清楚。
12.1 订单生命周期管理
一个订单从出生到死亡,经历哪些状态?说白了就这几个阶段:
- 创建(Created):订单刚生成,还没发出去
- 已发送(Sent):发到交易所了,等确认
- 已确认(Acknowledged):交易所收下了,挂单成功
- 部分成交(Partially Filled):吃了一半,另一半还在
- 完全成交(Filled):全吃完了,订单结束
- 已取消(Cancelled):主动撤单
- 已拒绝(Rejected):交易所不给过
嗯,这里要注意。很多新手只盯着"成交"和"未成交"两个状态。我建议你至少维护上面这7个状态。为什么?
我在项目中遇到过这么个事:某次行情剧烈波动,我们发了一堆限价单。结果交易所返回了部分成交,但我们的系统还傻傻地以为订单全活着,继续发新的。最后仓位直接爆了。从那以后,我强制团队必须把每个订单的状态机画清楚。
核心原则:订单状态机必须是确定性的。每个状态只能有唯一的入口和出口。别搞什么"既可以是A又可以是B"的模糊状态。
来看一个简单的状态机实现:
class OrderStateMachine:
def __init__(self):
self.state = 'CREATED'
self.valid_transitions = {
'CREATED': ['SENT'],
'SENT': ['ACKNOWLEDGED', 'REJECTED'],
'ACKNOWLEDGED': ['PARTIALLY_FILLED', 'FILLED', 'CANCELLED'],
'PARTIALLY_FILLED': ['FILLED', 'CANCELLED'],
'FILLED': [],
'CANCELLED': [],
'REJECTED': []
}
def transition(self, new_state):
if new_state in self.valid_transitions[self.state]:
self.state = new_state
return True
return False
12.2 订单取消与重发策略
做市商最怕什么?挂单挂在那里,行情跑了,你还傻等着。这时候就需要取消重发策略。
我个人习惯用这么几种策略:
| 策略名称 | 触发条件 | 适用场景 |
|---|---|---|
| 价格偏移取消 | 最新成交价偏离挂单价超过阈值 | 快速行情 |
| 超时取消 | 订单挂单超过设定时间(如500ms) | 流动性差的市场 |
| 队列位置取消 | 订单在队列中排名靠后 | FIFO撮合市场 |
| 批量取消 | 策略信号变化,需要整体调整 | 多腿策略 |
我曾经犯过一个低级错误:取消订单后立刻重发,结果交易所还没处理完取消,新订单就被拒绝了。后来我加了一个取消确认等待机制:
class CancelReplaceManager:
def __init__(self, exchange_client):
self.client = exchange_client
self.pending_cancels = {}
async def cancel_and_replace(self, old_order_id, new_order):
# 先发取消
cancel_req = await self.client.cancel_order(old_order_id)
self.pending_cancels[old_order_id] = {
'new_order': new_order,
'timestamp': time.time()
}
# 等待确认,超时则放弃
while time.time() - self.pending_cancels[old_order_id]['timestamp'] < 0.1:
status = await self.client.get_order_status(old_order_id)
if status == 'CANCELLED':
# 确认取消成功,再发新单
return await self.client.place_order(new_order)
await asyncio.sleep(0.001)
# 超时了,记录告警
logger.warning(f"取消订单 {old_order_id} 超时")
return None
避坑指南:我曾经在某个交易所遇到取消请求返回成功,但订单实际还在挂单的情况。原因是交易所的取消确认是异步的。解决方案:不要相信一次返回,要轮询确认状态。
12.3 订单路由逻辑
如果你只在一个交易所做市,路由逻辑很简单。但现实是,我们往往要同时对接多个交易所。这时候路由就变得关键了。
订单路由的核心问题就一个:这个单子该发到哪去?
我总结了几种常见的路由策略:
- 最优价格路由:哪个交易所价格好,发哪个。简单粗暴,但容易忽略流动性深度。
- 流动性加权路由:根据各交易所的挂单深度,按比例分配订单。适合大单。
- 延迟优先路由:哪个交易所延迟低,优先发。适合高频做市。
- 智能路由:综合价格、深度、延迟、手续费等因素,动态决策。
你想想看,如果只按价格路由,你可能会遇到这种情况:A交易所价格好但只有1手深度,B交易所价格差一点但有100手深度。你把大单全发到A,结果只成交了一点点,剩下的全被市场吃掉了。亏不亏?
我个人比较喜欢用智能路由,但实现起来确实复杂。这里给一个简化版的权重计算:
def calculate_exchange_score(exchange_data):
"""
计算交易所的综合评分
exchange_data: {
'price': 100.5,
'depth': 10000,
'latency': 0.005,
'fee': 0.0001
}
"""
price_score = 1 / (1 + abs(exchange_data['price'] - best_price))
depth_score = min(exchange_data['depth'] / target_size, 1.0)
latency_score = 1 / (1 + exchange_data['latency'] * 100)
fee_score = 1 / (1 + exchange_data['fee'] * 1000)
# 权重可以动态调整
weights = {'price': 0.4, 'depth': 0.3, 'latency': 0.2, 'fee': 0.1}
return (price_score * weights['price'] +
depth_score * weights['depth'] +
latency_score * weights['latency'] +
fee_score * weights['fee'])
12.4 FIFO与Pro-Rata撮合规则适配
这是很多做市商容易忽略的点。不同交易所的撮合规则不一样,你的订单管理策略必须适配。
FIFO(先进先出):谁先挂单,谁先成交。说白了就是排队。在这种规则下,队列位置就是一切。
Pro-Rata(按比例分配):按挂单量比例分配成交。你挂得多,成交就多。在这种规则下,挂单量才是关键。
我画了一张图,帮你理解这两种规则的区别:
那么,做市商该怎么适配?
实战建议:
- 在FIFO市场,你的订单管理要关注队列位置监控。如果发现订单排在后面,果断取消重发,往前挤。
- 在Pro-Rata市场,你的订单管理要关注挂单量调整。如果发现成交比例太低,适当增加挂单量。
- 混合市场(比如某些交易所同时用两种规则),那就更复杂了。我建议你根据历史数据,统计出哪种规则占主导,再针对性优化。
我曾经在某个FIFO市场做市,一开始没注意队列位置,结果挂单经常排到第100位以后,一天下来成交率不到5%。后来我加了一个队列位置监控,一旦发现排名掉出前10,立刻取消重发。成交率直接提升到40%。
适配代码示例:
class MatchingRuleAdapter:
def __init__(self, exchange_type):
self.exchange_type = exchange_type
def should_cancel_and_replace(self, order):
if self.exchange_type == 'FIFO':
# FIFO市场:关注队列位置
if order.queue_position > 10:
return True
elif self.exchange_type == 'PRO_RATA':
# Pro-Rata市场:关注成交比例
if order.fill_ratio < 0.3:
return True
return False
def calculate_optimal_order_size(self, base_size, current_depth):
if self.exchange_type == 'FIFO':
# FIFO市场:小单更容易排到前面
return min(base_size, current_depth * 0.01)
elif self.exchange_type == 'PRO_RATA':
# Pro-Rata市场:大单才能分到更多
return max(base_size, current_depth * 0.05)
return base_size
嗯,最后说一句。订单管理模块是做市系统的最后一道防线。定价模型可以错,信号可以延迟,但订单管理绝对不能出bug。我建议你在上线前,至少做三轮压力测试:
- 模拟极端行情,看订单取消重发是否正常
- 模拟网络延迟,看超时处理是否健壮
- 模拟交易所异常返回,看状态机是否兜得住
这三轮测试跑下来,你的订单管理模块基本就稳了。