20. 程序化交易系统架构:分层架构设计

做基差交易这几年,我踩过最大的坑,就是系统架构没想清楚就上手写代码。

一开始觉得,不就是拿个价差数据,算个阈值,然后下单嘛。结果呢?数据源断了不知道,策略跑飞了没察觉,交易所一卡顿,仓位全乱套。

后来我学乖了。一个靠谱的基差交易系统,必须分层。每一层各司其职,出了问题也能快速定位。说白了,就是「高内聚,低耦合」那套老话,但在实盘里,这六个字值真金白银。

核心观点: 分层架构不是炫技,是保命。数据层管「看」,策略层管「想」,执行层管「做」。三层之间用标准接口通信,互不干扰。

20.1 数据层:地基要稳

数据层是整个系统的眼睛。基差交易对数据质量要求极高——你想想看,价差差一个tick,可能就是一笔亏损。

我个人习惯把数据层拆成三个子模块:

  • 行情接入模块:对接交易所、数据商。支持多路冗余,比如同时接CTP和易盛,主路断了自动切备用。
  • 数据清洗模块:去重、补全、时间戳对齐。我遇到过某数据商偶尔发重复tick,不清洗的话策略会反复开仓。
  • 数据存储模块:实时数据写内存数据库(比如Redis),历史数据落盘到时序数据库(比如InfluxDB)。

这里有个避坑指南:时间戳一定要用交易所时间,别用本地时间。我曾经因为本地时钟漂移,导致回测和实盘对不上,查了三天才找到原因。

小技巧: 数据层建议单独部署一台机器,或者至少用独立进程。别让策略计算拖慢数据接收,反过来也一样。

20.2 策略层:核心大脑

策略层是真正干活的地方。它从数据层拿清洗好的行情,算出基差,判断套利机会,然后生成信号。

我习惯把策略层设计成「策略引擎 + 策略实例」的模式:

  • 策略引擎:负责调度、生命周期管理、风险控制。比如统一检查账户权益、持仓限制。
  • 策略实例:每个具体的基差策略(比如跨期套利、期现套利)都是一个独立实例。互不干扰,可以单独启停。

举个例子,一个简单的跨期基差策略核心逻辑:

class BasisStrategy:
    def __init__(self, symbol_pair, threshold):
        self.symbol1 = symbol_pair[0]  # 近月合约
        self.symbol2 = symbol_pair[1]  # 远月合约
        self.threshold = threshold     # 开仓阈值
        
    def on_tick(self, tick1, tick2):
        # 计算基差
        basis = tick1.last_price - tick2.last_price
        
        # 判断套利机会
        if basis > self.threshold:
            # 基差过大,做空基差:卖近买远
            return Signal('SELL', self.symbol1, 'BUY', self.symbol2)
        elif basis < -self.threshold:
            # 基差过小,做多基差:买近卖远
            return Signal('BUY', self.symbol1, 'SELL', self.symbol2)
        else:
            return None

嗯,这里要注意:阈值不能是固定值。市场波动率在变,基差的统计分布也在变。我一般用滚动窗口的动态阈值,比如过去20个tick的均值加减两倍标准差。

警告: 策略层千万别直接连交易所API。信号生成和下单执行必须分离。否则策略一卡,下单也跟着卡,容易出事故。

20.3 执行层:最后一公里

执行层负责把策略信号变成真实订单。这一层最容易被忽视,但恰恰是亏损的重灾区。

执行层要处理几个关键问题:

  1. 订单管理:维护订单状态机(已发送、部分成交、全部成交、已撤销)。
  2. 成交反馈:监听交易所的成交回报,更新持仓。
  3. 异常处理:撤单失败、网络断开、交易所拒绝等。
  4. 滑点控制:基差交易的滑点往往比单腿交易更致命。我习惯用「被动挂单 + 主动吃单」混合策略。

举个例子,执行层收到一个「卖近买远」的信号后,流程是这样的:

def execute_signal(signal):
    # 1. 先挂被动单
    order1 = place_limit_order(signal.symbol1, signal.side1, price=best_ask)
    order2 = place_limit_order(signal.symbol2, signal.side2, price=best_bid)
    
    # 2. 等待成交,设置超时
    wait_for_fill(order1, order2, timeout=500)  # 500ms
    
    # 3. 如果有一腿没成交,撤单重发
    if not order1.filled:
        cancel_order(order1)
        order1 = place_market_order(signal.symbol1, signal.side1)
    
    if not order2.filled:
        cancel_order(order2)
        order2 = place_market_order(signal.symbol2, signal.side2)

我曾经因为没处理「一腿成交、一腿没成交」的情况,导致单边持仓过夜,第二天跳空亏了十几个点。从那以后,我强制要求执行层必须做「腿平衡」检查。

20.4 低延迟通信:别让速度拖后腿

基差交易对延迟敏感。价差机会可能只存在几十毫秒,晚一步就没了。

我常用的低延迟手段:

  • 进程间通信用共享内存:数据层到策略层,策略层到执行层,都用mmap或共享内存队列。别用TCP socket,太慢。
  • 避免锁竞争:用无锁队列(比如Disruptor模式)。Python里可以用multiprocessing.Queue的底层换成共享内存。
  • 网络层面:和交易所同机房托管,用FPGA网卡加速。如果条件有限,至少保证服务器和交易所之间的物理距离最短。
一个实测数据: 从共享内存读一个tick大约耗时0.1微秒,而走TCP loopback要10微秒。差了100倍。在基差交易里,这100倍可能就是盈利和亏损的分界线。

20.5 系统稳定性保障:不出事比赚钱更重要

做量化交易,活得久比赚得快重要。我见过太多系统跑着跑着就崩了,然后一夜回到解放前。

稳定性保障的几个要点:

保障措施 具体做法 我踩过的坑
冗余设计 数据源双路、网络双线、电源双路 主数据商宕机,备用没配好,裸奔了半小时
监控告警 延迟监控、持仓监控、资金监控 策略停了三天没发现,因为监控只报了warning没报error
熔断机制 单日亏损超阈值自动平仓 有一次没设熔断,一天亏了两个月利润
日志审计 所有操作写日志,支持事后回放 出问题查不到原因,因为没有日志

我个人习惯在系统里加一个「心跳线程」,每秒检查各层是否正常。如果连续三次心跳丢失,自动发短信报警,同时启动应急平仓流程。

一句话总结: 分层架构的核心是「各司其职,互不干扰」。数据层只管数据,策略层只管算,执行层只管下单。出了问题,哪一层的事,一目了然。

下面这张图是我自己画的分层架构示意图,你可以参考一下整体结构:

基差交易系统分层架构 数据层 行情接入(CTP/易盛) 数据清洗(去重/对齐) 数据存储(Redis/InfluxDB) 策略层 策略引擎(调度/风控) 策略实例(基差计算) 信号生成(阈值判断) 执行层 订单管理(状态机) 成交反馈(持仓更新) 异常处理(撤单/重发) 通信方式:共享内存(mmap) | 监控:心跳检测 + 告警

这张图里,三层之间用共享内存通信,每层内部又细分了子模块。你实际搭建的时候,可以根据自己的交易品种和频率做调整。但记住一点:层与层之间的接口要稳定。接口一变,上下游全得改,那代价就大了。

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