2. 核心数据模型设计:订单、成交、行情、账户、风控事件的数据结构设计

做交易系统这么多年,我最大的体会就是:数据模型是系统的骨架。骨架歪了,后面再怎么填肉都别扭。今天咱们就来聊聊这五个核心模型——订单、成交、行情、账户、风控事件。每个模型我都踩过坑,有些坑现在想起来还肉疼。

2.1 订单模型:最基础也最容易翻车

订单模型是所有交易系统的起点。你想想看,一笔订单从创建到最终完成,要经过多少状态转换?我见过不少新手直接用一个字段存状态,结果后面查历史记录时一脸懵。

核心原则:订单模型必须支持全生命周期追溯,不能只存当前状态。

我个人习惯把订单模型拆成两部分:订单头订单明细。订单头存公共信息,明细存每个标的的具体数据。这样设计,一个订单可以同时买多只股票,也能方便地做撤单、改单操作。

// 订单头结构(简化版)
{
  "order_id": "202503210001",      // 订单编号,全局唯一
  "account_id": "ACC001",          // 账户编号
  "client_id": "CLT001",           // 客户编号
  "order_type": "LIMIT",           // 订单类型:LIMIT/MARKET/STOP
  "direction": "BUY",              // 买卖方向
  "status": "NEW",                 // 当前状态
  "create_time": 1742524800000,    // 创建时间戳
  "update_time": 1742524805000,    // 最后更新时间
  "total_quantity": 10000,         // 总数量
  "total_amount": 500000.00,       // 总金额
  "status_history": [              // 状态变更历史
    {"status": "NEW", "time": 1742524800000},
    {"status": "PARTIAL_FILLED", "time": 1742524803000}
  ]
}

// 订单明细结构
{
  "order_id": "202503210001",
  "symbol": "600519.SH",           // 股票代码
  "price": 50.00,                  // 委托价格
  "quantity": 10000,               // 委托数量
  "filled_quantity": 5000,         // 已成交数量
  "filled_amount": 250000.00,      // 已成交金额
  "commission": 250.00,            // 手续费
  "status": "PARTIAL_FILLED"
}
避坑指南:我曾经在状态字段上吃过亏——直接用字符串存状态,结果查询性能惨不忍睹。后来改用整型枚举,速度提升了一个数量级。建议用 int 类型,0=NEW, 1=PARTIAL_FILLED, 2=FILLED, 3=CANCELLED, 4=REJECTED。

2.2 成交模型:别小看这个"小兄弟"

成交模型看着简单,其实门道不少。一笔订单可能拆成多笔成交,每笔成交又可能来自不同的交易所。我见过最离谱的情况是:成交记录和订单记录对不上账,最后查了三天才发现是时间戳精度不一致导致的。

// 成交记录结构
{
  "trade_id": "TRADE202503210001",  // 成交编号
  "order_id": "202503210001",      // 关联订单编号
  "symbol": "600519.SH",
  "direction": "BUY",
  "price": 50.00,                  // 成交价格
  "quantity": 5000,                // 成交数量
  "amount": 250000.00,             // 成交金额
  "trade_time": 1742524803000,     // 成交时间(交易所时间)
  "system_time": 1742524803005,    // 系统接收时间
  "exchange_id": "SSE",            // 交易所代码
  "commission": 250.00,            // 手续费
  "tax": 0.00,                     // 印花税
  "settlement_date": "20250322"    // 交收日期
}

这里有个关键点:trade_time 和 system_time 必须分开存。为什么?因为交易所返回的时间和系统收到的时间可能有毫秒级差异。做回测时,这个差异会直接影响策略评估的准确性。我记得有一次做高频策略回测,就因为没区分这两个时间,回测结果和实盘差了 30%。

2.3 行情模型:数据量大到让你怀疑人生

行情数据是交易系统里数据量最大的部分。以 A 股为例,一天 4 小时交易时间,每只股票每秒至少推送 2-3 次快照。全市场 5000 多只股票,一天下来就是几亿条数据。如果不设计好模型,存储和查询都会爆炸。

注意:行情数据不建议用关系型数据库存。我见过有人用 MySQL 存 tick 数据,结果一张表一个月就 10 亿条,查询慢到怀疑人生。建议用时序数据库(如 InfluxDB、ClickHouse)或列式存储。
// 行情快照结构(Level-1)
{
  "symbol": "600519.SH",
  "timestamp": 1742524800000,      // 行情时间
  "open": 49.50,                   // 开盘价
  "high": 51.00,                   // 最高价
  "low": 49.00,                    // 最低价
  "close": 50.50,                  // 收盘价(当前最新价)
  "volume": 5000000,               // 成交量
  "amount": 250000000.00,          // 成交额
  "bid_price": [50.00, 49.99, ...], // 买一到买五价格
  "bid_volume": [1000, 2000, ...],  // 买一到买五数量
  "ask_price": [50.01, 50.02, ...], // 卖一到卖五价格
  "ask_volume": [1500, 1800, ...]   // 卖一到卖五数量
}

// 逐笔成交结构(Level-2)
{
  "symbol": "600519.SH",
  "trade_id": "T2025032100001",
  "timestamp": 1742524800000,
  "price": 50.00,
  "volume": 1000,
  "amount": 50000.00,
  "trade_type": "BUYER_INITIATED",  // 买方主动/卖方主动
  "buy_order_id": "B202503210001",
  "sell_order_id": "S202503210001"
}
我的经验:行情数据一定要做分区。按天分区是最基本的,如果数据量特别大,可以按小时分区。另外,历史行情数据建议做降采样——比如把 tick 数据聚合成 1 分钟 K 线,这样查询历史走势时不用扫全量数据。

2.4 账户模型:钱的事不能马虎

账户模型直接关系到资金安全,设计时必须严谨。我见过最坑的设计是把账户余额和持仓放在同一张表里,结果并发更新时经常出现数据不一致。

// 账户资金结构
{
  "account_id": "ACC001",
  "client_id": "CLT001",
  "currency": "CNY",
  "total_assets": 1000000.00,      // 总资产
  "available_balance": 500000.00,  // 可用余额
  "frozen_balance": 200000.00,     // 冻结余额
  "position_value": 300000.00,     // 持仓市值
  "margin_used": 0.00,             // 已用保证金
  "profit_loss": 5000.00,          // 浮动盈亏
  "update_time": 1742524800000
}

// 持仓结构
{
  "account_id": "ACC001",
  "symbol": "600519.SH",
  "direction": "LONG",             // 多/空
  "quantity": 10000,               // 持仓数量
  "available_quantity": 8000,      // 可用数量
  "frozen_quantity": 2000,         // 冻结数量
  "cost_price": 48.50,             // 成本价
  "current_price": 50.00,          // 当前价
  "market_value": 500000.00,       // 市值
  "profit_loss": 15000.00,         // 浮动盈亏
  "profit_loss_ratio": 0.0309      // 盈亏比例
}

账户模型设计时,有几点要特别注意:

  • 资金和持仓必须分开表,避免并发更新时的锁竞争
  • 每次资金变动都要记录流水,方便对账和审计
  • 冻结余额和冻结数量要单独字段,不能靠计算得出
血的教训:我曾经在账户模型里用 decimal(18,2) 存金额,结果遇到分红送股时,小数点后两位根本不够用。后来统一改成 decimal(18,8),虽然存储空间大了点,但再也没出过精度问题。

2.5 风控事件模型:救命用的数据结构

风控事件模型可能是最容易被忽视的。很多人觉得风控就是几个阈值判断,随便存一下就行。但真正出事的时候,你会发现风控数据是唯一能帮你还原现场的证据。

// 风控事件结构
{
  "event_id": "RISK202503210001",
  "event_type": "POSITION_LIMIT",   // 事件类型
  "severity": "WARNING",            // 严重级别:INFO/WARNING/CRITICAL
  "account_id": "ACC001",
  "symbol": "600519.SH",
  "trigger_time": 1742524800000,    // 触发时间
  "trigger_value": 1000000.00,      // 触发时的值
  "threshold_value": 800000.00,     // 阈值
  "rule_id": "RULE001",             // 触发的规则编号
  "rule_name": "单品种持仓上限",     // 规则名称
  "action_taken": "BLOCK_ORDER",    // 采取的动作
  "status": "PROCESSED",            // 处理状态
  "handler": "SYSTEM",              // 处理人/系统
  "handle_time": 1742524801000,     // 处理时间
  "details": {                      // 扩展信息
    "current_position": 2000000,
    "max_position": 800000,
    "exceed_amount": 200000
  }
}

风控事件模型设计时,我建议重点关注以下几点:

  • 事件类型要可扩展,用枚举或字典表,不要硬编码
  • 严重级别要明确,INFO 只是记录,WARNING 需要关注,CRITICAL 必须立即处理
  • 触发值和阈值都要存,方便事后分析为什么触发了风控
  • 采取的动作要记录,比如是阻止订单、强制平仓还是只告警
我的习惯:风控事件一定要做持久化,不能只存在内存里。我曾经遇到过一次系统崩溃,内存里的风控事件全丢了,结果复盘时根本不知道当时发生了什么。从那以后,所有风控事件都同步写数据库,宁可慢一点也不能丢数据。

2.6 数据模型之间的关系

这五个模型不是孤立的,它们之间有紧密的关联。我画了一张图,帮你理清它们之间的关系:

核心数据模型关系图 订单模型 Order 成交模型 Trade 行情模型 Market Data 账户模型 Account 风控事件模型 Risk Event 1:N 参考 归属 更新 阻断 监控 输入 图例: 关联关系 风控阻断 1:N 表示一对多关系

从这张图可以看出:

  • 订单和成交是一对多关系,一笔订单可能产生多笔成交
  • 成交参考行情数据,成交价格必须在行情范围内
  • 订单和成交都归属于账户,账户模型是资金维度的核心
  • 风控事件监控账户和订单,一旦触发就阻断后续操作
  • 行情数据是风控的输入,比如价格异常波动会触发风控
总结一下:这五个模型是交易系统的基石。订单模型管流程,成交模型管结果,行情模型管数据,账户模型管资金,风控模型管安全。设计时一定要考虑扩展性、性能和可追溯性。别问我为什么知道——当年我设计的第一个版本,三个月后就重构了,就是因为没考虑全。

好了,核心数据模型就聊到这儿。记住一句话:好的数据模型能让你少写一半的 bug。下一章咱们聊聊数据模型在数据库中的落地实现,包括表结构设计、索引优化和分库分表策略。


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