第四章:做市策略系统架构

做市系统,说白了就是一个精密的「印钞机」——前提是你得把它搭对。

我见过太多团队,策略逻辑写得天花乱坠,结果一上线就崩。为什么?因为架构没想清楚。今天我就把做市系统的五层架构掰开揉碎了讲给你听。

4.1 整体架构概览

做市系统不是单打独斗,它需要五个层次协同工作:

  • 数据层:喂给系统「眼睛」
  • 策略层:负责「大脑」决策
  • 执行层:充当「手脚」干活
  • 风控层:扮演「安全带」
  • 监控层:做好「仪表盘」

这五层缺一不可。我早期做的一个项目,就是忽略了风控层,结果一次极端行情直接爆仓。嗯,从那以后我再也不敢偷懒了。

核心原则:每一层都要独立部署、独立升级。千万别搞成「大泥球」架构。

做市系统五层架构 数据层 行情数据 · 订单簿 · 历史数据 · 实时推送 策略层 定价模型 · 库存管理 · 报价生成 · 信号计算 执行层 订单管理 · 撤单重发 · 交易所接口 · 延迟优化 风控层 仓位限制 · 最大亏损 · 异常检测 · 熔断机制 监控层 实时看板 · 日志审计 · 告警通知 · 性能指标 数据流方向

4.2 数据层:系统的「眼睛」

数据层是所有决策的基础。你想想看,如果行情数据都拿不准,策略再牛也是白搭。

4.2.1 核心数据源

数据类型 来源 更新频率 用途
Level 2 行情 交易所API 毫秒级 实时定价
成交数据 WebSocket 实时 成交确认
历史数据 数据库 离线 回测分析
账户信息 REST API 秒级 仓位管理

我的经验:数据层一定要做「双路备份」。主链路用WebSocket,备用链路用REST轮询。我曾经因为WebSocket断连,整整5分钟没有收到行情更新,差点酿成大祸。

4.2.2 数据清洗与对齐

原始数据不能直接用。你需要做三件事:

  1. 去重:同一个tick重复推送,只保留最新
  2. 排序:按时间戳严格排序,乱序数据要丢弃
  3. 对齐:不同交易所的数据要统一时间基准
// 数据清洗示例(伪代码)
function cleanTick(rawTick) {
  // 去重:检查sequence是否已处理
  if (processedSequences.has(rawTick.sequence)) {
    return null;  // 已处理过,丢弃
  }
  
  // 排序:确保时间戳递增
  if (rawTick.timestamp < lastTimestamp) {
    log.warn("乱序数据,丢弃");
    return null;
  }
  
  // 对齐:统一到毫秒级时间戳
  rawTick.timestamp = normalizeTimestamp(rawTick.timestamp);
  
  processedSequences.add(rawTick.sequence);
  lastTimestamp = rawTick.timestamp;
  
  return rawTick;
}

4.3 策略层:系统的「大脑」

策略层是核心中的核心。这里我建议你保持「策略引擎」和「策略逻辑」分离。

4.3.1 策略引擎架构

策略引擎负责:

  • 加载策略配置
  • 管理策略生命周期(启动/暂停/停止)
  • 分发行情数据到各个策略实例
  • 收集策略输出的报价信号

关键设计:策略引擎要支持「热加载」。也就是说,修改策略参数后不用重启系统。我见过太多团队,改个参数要停掉整个系统,这在做市场景下是致命的。

4.3.2 核心策略模块

一个典型的做市策略包含:

  1. 定价模型:计算合理价格区间
  2. 库存管理器:控制持仓风险
  3. 报价生成器:生成买卖报价
  4. 信号过滤器:过滤无效信号
// 策略层核心逻辑示例
class MarketMakingStrategy {
  constructor(config) {
    this.pricingModel = new PricingModel(config.pricing);
    this.inventoryManager = new InventoryManager(config.inventory);
    this.quoteGenerator = new QuoteGenerator(config.quote);
  }
  
  onTick(tick) {
    // 1. 计算合理价格
    const fairPrice = this.pricingModel.calculate(tick);
    
    // 2. 检查库存状态
    const inventorySignal = this.inventoryManager.evaluate();
    
    // 3. 生成报价
    const quotes = this.quoteGenerator.generate(fairPrice, inventorySignal);
    
    // 4. 过滤无效报价
    return this.filterQuotes(quotes);
  }
}

4.4 执行层:系统的「手脚」

执行层负责把策略的报价信号变成真实的订单。这里有个坑——交易所API不是100%可靠的。

4.4.1 订单管理

执行层需要处理:

  • 订单生命周期:新单→已提交→部分成交→完全成交→已撤销
  • 撤单重发:超时未成交的订单要撤销并重新报价
  • 订单簿同步:确保本地订单簿和交易所一致

避坑指南:我曾经遇到过一个bug——撤单请求发送成功了,但交易所返回了超时错误。结果系统以为撤单失败,又发了一次撤单。最后同一个订单被撤了两次,导致报价出现空窗期。解决方案是:给每个订单一个唯一ID,并维护一个「订单状态机」。

4.4.2 延迟优化

做市是毫秒级的竞争。执行层必须做到:

优化项 方法 预期效果
网络延迟 托管服务器、专线连接 降低50-80%
序列化延迟 使用Protobuf替代JSON 降低60-70%
业务逻辑延迟 预计算、缓存热点数据 降低30-40%

4.5 风控层:系统的「安全带」

风控层是最后一道防线。我见过太多人,策略赚钱的时候觉得风控是累赘,爆仓了才后悔没装。

4.5.1 核心风控规则

  • 仓位限制:单边持仓不超过总资金的20%
  • 最大亏损:单日亏损超过5%自动暂停
  • 报价偏离:报价偏离市场中间价超过阈值则拦截
  • 频率限制:每秒报价次数不超过10次
// 风控检查示例
function riskCheck(order) {
  // 仓位检查
  if (currentPosition + order.quantity > positionLimit) {
    return { passed: false, reason: "仓位超限" };
  }
  
  // 报价偏离检查
  if (Math.abs(order.price - midPrice) > maxSpread) {
    return { passed: false, reason: "报价偏离过大" };
  }
  
  // 频率检查
  if (orderCountInSecond >= maxOrderPerSecond) {
    return { passed: false, reason: "报价频率超限" };
  }
  
  return { passed: true };
}

我的建议:风控规则要分「硬风控」和「软风控」。硬风控直接拦截订单,软风控只发告警。刚开始做的时候,建议全部用硬风控,等系统稳定了再逐步放开。

4.6 监控层:系统的「仪表盘」

监控层让你知道系统「活得好不好」。没有监控,你就像在开一辆没有仪表盘的车。

4.6.1 核心监控指标

指标类别 具体指标 告警阈值
性能指标 订单处理延迟、行情延迟 >100ms告警
业务指标 成交率、库存周转率 成交率<30%告警
风控指标 当前仓位、当日盈亏 仓位>80%告警
系统指标 CPU、内存、网络 CPU>80%告警

4.6.2 告警分级

我习惯把告警分成三级:

  • P0(致命):系统宕机、资金异常 → 电话通知
  • P1(严重):策略暂停、风控触发 → 即时消息
  • P2(警告):延迟升高、成交率下降 → 邮件通知

核心观点:监控不是为了看数据,而是为了「发现问题」。所以告警一定要 actionable——收到告警后,你知道该做什么。

4.7 五层协作流程

最后,我们看看这五层是怎么配合的:

  1. 数据层收到行情tick,推送给策略层
  2. 策略层计算报价,发送给执行层
  3. 执行层在发单前,先经过风控层检查
  4. 风控层通过后,执行层向交易所发单
  5. 所有环节的数据都上报给监控层

这个流程看起来简单,但每个环节都可能出问题。我建议你从第一天就把监控做起来,别等到出事了再补。

最后说一句:架构设计没有银弹。这五层架构是我在多个项目中打磨出来的,但它不一定适合所有场景。关键是要理解每一层「为什么存在」,然后根据你的实际情况做调整。


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