第四章:订单管理系统
订单管理系统,说白了就是回测引擎的「心脏」。你想想看,策略产生的买卖信号,最终都要转化成实实在在的订单,然后被撮合成交。这个环节要是出了问题,回测结果基本就是废纸一张。
我个人习惯把订单系统拆成三个核心模块:订单类型、生命周期管理、撮合逻辑。咱们一个一个来啃。
4.1 订单类型:不只是「买」和「卖」
很多新手以为订单就两种:买入和卖出。其实在真实交易中,订单类型丰富得很。我当年第一次做回测框架时,只实现了市价单,结果回测出来的收益曲线漂亮得不像话——后来才发现,那是因为我忽略了滑点和限价单未成交的情况。
咱们先实现最核心的三种订单类型:
4.1.1 市价单(Market Order)
市价单就是「不管什么价格,立刻成交」。它的特点是执行速度快,但成交价格不确定。在回测中,我们通常用下一笔的行情价格来模拟。
4.1.2 限价单(Limit Order)
限价单指定了「我愿意接受的最高/最低价格」。买入限价单只会在价格≤指定价时成交,卖出限价单只会在价格≥指定价时成交。
这里有个坑:限价单可能永远无法成交。我在项目中遇到过,某个策略在震荡市里挂了一堆限价单,结果行情突然单边上涨,所有买单都没吃到,直接踏空。
4.1.3 止损单(Stop Order)
止损单是「价格到了某个触发点,就变成市价单」。它用于控制风险,防止亏损扩大。注意,止损单的触发价和成交价是两码事。
嗯,这里要注意:止损单触发后变成市价单,所以成交价可能比触发价更差。这在回测中很容易被忽略。
4.2 订单数据结构
咱们用Python定义一个通用的订单对象。我个人习惯用dataclass,简洁又清晰。
from dataclasses import dataclass
from enum import Enum
from datetime import datetime
class OrderType(Enum):
MARKET = "market" # 市价单
LIMIT = "limit" # 限价单
STOP = "stop" # 止损单
class OrderSide(Enum):
BUY = "buy"
SELL = "sell"
class OrderStatus(Enum):
PENDING = "pending" # 待成交
PARTIAL = "partial" # 部分成交
FILLED = "filled" # 全部成交
CANCELLED = "cancelled" # 已撤销
REJECTED = "rejected" # 已拒绝
@dataclass
class Order:
order_id: str
symbol: str
side: OrderSide
order_type: OrderType
quantity: float
price: float = None # 限价单/止损单的价格
stop_price: float = None # 止损单的触发价
status: OrderStatus = OrderStatus.PENDING
filled_quantity: float = 0.0
avg_fill_price: float = 0.0
created_at: datetime = None
updated_at: datetime = None
你看,一个订单对象包含了所有关键信息。为什么要有 filled_quantity 和 avg_fill_price?因为订单可能被部分成交,我们需要记录实际成交了多少。
4.3 订单生命周期管理
一个订单从出生到死亡,会经历几个状态。我画了一张图,帮你直观理解:
这张图展示了订单的完整生命周期。核心流程是:
- 创建订单 → 状态为 PENDING
- 尝试撮合 → 可能变成 PARTIAL 或直接 FILLED
- 持续撮合 → PARTIAL 状态继续等待,直到全部成交或撤销
- 撤销/拒绝 → 进入终态 CANCELLED 或 REJECTED
4.4 撮合逻辑实现
撮合逻辑是订单系统的核心。说白了,就是判断「当前行情下,这个订单能不能成交」。咱们分订单类型来写。
4.4.1 市价单撮合
市价单最简单:只要有对手盘,就能成交。在回测中,我们通常用下一根K线的开盘价或当前价来模拟。
def match_market_order(order, current_price):
"""市价单撮合:直接用当前价格成交"""
if order.side == OrderSide.BUY:
fill_price = current_price # 买入用卖一价
else:
fill_price = current_price # 卖出用买一价
order.filled_quantity = order.quantity
order.avg_fill_price = fill_price
order.status = OrderStatus.FILLED
return True
4.4.2 限价单撮合
限价单需要比较订单价格和当前行情价格:
def match_limit_order(order, current_high, current_low):
"""限价单撮合:检查价格是否满足条件"""
if order.side == OrderSide.BUY:
# 买入限价:当前最低价 <= 订单价格
if current_low <= order.price:
fill_price = min(order.price, current_low)
order.filled_quantity = order.quantity
order.avg_fill_price = fill_price
order.status = OrderStatus.FILLED
return True
else:
# 卖出限价:当前最高价 >= 订单价格
if current_high >= order.price:
fill_price = max(order.price, current_high)
order.filled_quantity = order.quantity
order.avg_fill_price = fill_price
order.status = OrderStatus.FILLED
return True
return False
4.4.3 止损单撮合
止损单稍微复杂一点:先判断是否触发,触发后按市价单处理。
def match_stop_order(order, current_price, current_high, current_low):
"""止损单撮合:先判断触发,再按市价成交"""
triggered = False
if order.side == OrderSide.BUY:
# 买入止损:当前最高价 >= 触发价
if current_high >= order.stop_price:
triggered = True
else:
# 卖出止损:当前最低价 <= 触发价
if current_low <= order.stop_price:
triggered = True
if triggered:
# 触发后按市价单处理
fill_price = current_price
order.filled_quantity = order.quantity
order.avg_fill_price = fill_price
order.status = OrderStatus.FILLED
return True
return False
我曾经犯过一个错误:把止损单的触发价和成交价混为一谈。结果回测时,止损单总是以完美的触发价成交,实盘却滑了好几个点。后来我加了滑点模型,才让回测更贴近真实。
4.5 订单管理器
有了订单对象和撮合逻辑,咱们需要一个管理器来统筹全局:
class OrderManager:
def __init__(self):
self.active_orders = [] # 活跃订单(待成交)
self.filled_orders = [] # 已成交订单
self.cancelled_orders = [] # 已撤销订单
def submit_order(self, order):
"""提交新订单"""
self.active_orders.append(order)
def cancel_order(self, order_id):
"""撤销订单"""
for order in self.active_orders:
if order.order_id == order_id:
if order.status == OrderStatus.PARTIAL:
# 部分成交的订单,撤销剩余部分
order.status = OrderStatus.CANCELLED
else:
order.status = OrderStatus.CANCELLED
self.active_orders.remove(order)
self.cancelled_orders.append(order)
return True
return False
def process_orders(self, bar_data):
"""处理所有活跃订单"""
for order in self.active_orders[:]: # 用切片复制,避免修改迭代器
matched = False
if order.order_type == OrderType.MARKET:
matched = match_market_order(order, bar_data['close'])
elif order.order_type == OrderType.LIMIT:
matched = match_limit_order(order, bar_data['high'], bar_data['low'])
elif order.order_type == OrderType.STOP:
matched = match_stop_order(order, bar_data['close'],
bar_data['high'], bar_data['low'])
if matched:
self.active_orders.remove(order)
self.filled_orders.append(order)
4.6 避坑指南
做订单系统这些年,我踩过不少坑。分享几个最痛的:
- 订单ID重复:我曾经用时间戳当ID,结果同一毫秒生成两个订单,直接崩了。后来改用UUID。
- 部分成交的累计误差:浮点数计算会累积误差,导致最终成交数量对不上。建议用Decimal或整数(最小单位)来算。
- 止损单的触发顺序:同一根K线内,多个止损单同时触发,谁先谁后?我建议按价格优先、时间优先的原则排序。
- 滑点处理:市价单和止损单一定要加滑点模型,否则回测结果会过于乐观。
嗯,订单系统这块内容不少,但核心就三点:订单类型要全、生命周期要准、撮合逻辑要细。你把这些搞明白了,回测框架的骨架就算搭起来了。
公众号:蓝海资料掘金营,微信deep3321