第四章:订单管理系统

订单管理系统,说白了就是回测引擎的「心脏」。你想想看,策略产生的买卖信号,最终都要转化成实实在在的订单,然后被撮合成交。这个环节要是出了问题,回测结果基本就是废纸一张。

我个人习惯把订单系统拆成三个核心模块:订单类型生命周期管理撮合逻辑。咱们一个一个来啃。

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_quantityavg_fill_price?因为订单可能被部分成交,我们需要记录实际成交了多少。

4.3 订单生命周期管理

一个订单从出生到死亡,会经历几个状态。我画了一张图,帮你直观理解:

订单生命周期状态机 PENDING PARTIAL FILLED CANCELLED REJECTED 部分成交 全部成交 剩余成交 继续部分成交 撤销 撤销剩余 风控拒绝

这张图展示了订单的完整生命周期。核心流程是:

  1. 创建订单 → 状态为 PENDING
  2. 尝试撮合 → 可能变成 PARTIAL 或直接 FILLED
  3. 持续撮合 → PARTIAL 状态继续等待,直到全部成交或撤销
  4. 撤销/拒绝 → 进入终态 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
注意:限价单的成交价不一定等于你挂的价格。比如你挂100元买入,但当前最低价是99.5元,那么成交价可能是99.5元。这在回测中要如实模拟。

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)
核心思想:每次来一根新K线,就遍历所有活跃订单,尝试撮合。撮合成功的移出活跃列表,失败的继续等待。

4.6 避坑指南

做订单系统这些年,我踩过不少坑。分享几个最痛的:

  • 订单ID重复:我曾经用时间戳当ID,结果同一毫秒生成两个订单,直接崩了。后来改用UUID。
  • 部分成交的累计误差:浮点数计算会累积误差,导致最终成交数量对不上。建议用Decimal或整数(最小单位)来算。
  • 止损单的触发顺序:同一根K线内,多个止损单同时触发,谁先谁后?我建议按价格优先、时间优先的原则排序。
  • 滑点处理:市价单和止损单一定要加滑点模型,否则回测结果会过于乐观。

嗯,订单系统这块内容不少,但核心就三点:订单类型要全、生命周期要准、撮合逻辑要细。你把这些搞明白了,回测框架的骨架就算搭起来了。

我的建议:先实现最基础的市价单和限价单,跑通后再加止损单。别一上来就想搞大而全,容易把自己绕进去。

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