一、核心数据结构设计:库存台账、订单簿、成交记录与资金流水
做市系统的核心,说白了就是管好两样东西:钱和货。
钱是资金,货是库存。中间穿插着订单、成交、流水这些记录。我做了这么多年做市系统,发现很多新手一上来就写业务逻辑,结果数据结构没设计好,后面改得想哭。
今天我们就来聊聊,这四张核心表该怎么设计。
1. 库存台账设计
库存台账,就是记录你手里有多少货。听起来简单,但坑不少。
举个例子:你在币安上有10个BTC,在OKX上也有5个BTC。总库存是15个。但如果你只记一个总数,哪天币安上的BTC被借出去了,你都不知道。
所以我个人习惯,库存台账要分三层:
- 总库存:所有交易所加起来的总量
- 分交易所库存:每个交易所的独立库存
- 可用/冻结库存:哪些能卖,哪些被订单锁住了
来看一个实际的数据结构:
// 库存台账核心字段
struct InventoryLedger {
string symbol; // 交易对,比如 BTC/USDT
string exchange; // 交易所标识
double total_qty; // 总数量
double available_qty; // 可用数量
double frozen_qty; // 冻结数量(挂单占用)
double locked_qty; // 锁仓数量(风控锁定)
int64_t update_time; // 更新时间戳
};
这里有个关键点:可用 + 冻结 + 锁仓 = 总数量。这个等式必须时刻成立。我在项目中遇到过有人忘了更新冻结数量,结果系统以为还有货,继续卖空,最后爆仓了。
CAS (Compare And Swap) 方式更新。
2. 订单簿数据结构
订单簿就是买卖双方的排队列表。做市策略的核心就是盯着订单簿,找机会吃单或挂单。
订单簿的数据结构,我推荐用跳表(Skip List)或者红黑树。为什么?因为订单簿需要频繁的插入、删除、查询操作,而且价格是有序的。
来看一个简化版的设计:
// 订单簿中的单个档位
struct OrderBookLevel {
double price; // 价格
double qty; // 数量
int order_count; // 该价格上的订单数量
};
// 订单簿整体结构
struct OrderBook {
string symbol;
vector<OrderBookLevel> bids; // 买盘,按价格降序
vector<OrderBookLevel> asks; // 卖盘,按价格升序
int64_t timestamp;
};
你可能会问:为什么不用数组?
嗯,这里要注意。订单簿的深度是动态变化的。用数组的话,中间插入一个档位,后面的全要移动,性能扛不住。我见过有人用Python的list硬扛,结果一到行情剧烈波动,CPU直接飙到100%。
std::map(红黑树)来存储订单簿。价格作为key,数量作为value。插入、删除、查询都是O(log n),够用了。
3. 成交记录数据结构
成交记录,就是每一笔买卖的明细。这东西看似简单,但设计不好,复盘的时候会疯掉。
我见过最糟糕的设计:只记录了成交价格和数量,没记录订单ID。结果对账的时候,根本不知道这笔成交对应的是哪个挂单。
来看一个靠谱的设计:
struct TradeRecord {
string trade_id; // 成交ID,全局唯一
string order_id; // 关联的订单ID
string symbol;
string side; // BUY 或 SELL
double price;
double qty;
double fee; // 手续费
string fee_currency; // 手续费币种
int64_t trade_time; // 成交时间戳
string exchange; // 交易所
};
这里有个细节:手续费一定要单独记录。很多交易所的手续费是用平台币抵扣的,如果你不记清楚,算利润的时候会差一大截。
我记得有一次,一个同事没记录手续费币种,结果月底算账,发现利润少了2%。查了半天,原来是手续费用BNB抵扣了,但系统默认按USDT算的。
4. 资金流水数据结构
资金流水,就是记录你的钱怎么进怎么出的。做市系统里,资金流水比成交记录更重要。为什么?因为成交记录只反映买卖,而资金流水能反映所有资金变动,包括充提、转账、利息、手续费返还等。
我设计的资金流水表,核心字段如下:
struct FundFlow {
string flow_id; // 流水ID
string account_id; // 账户ID
string currency; // 币种
double amount; // 变动金额,正数表示增加,负数表示减少
double balance_before; // 变动前余额
double balance_after; // 变动后余额
string flow_type; // 流水类型:TRADE, DEPOSIT, WITHDRAW, FEE, INTEREST
string ref_id; // 关联ID,比如订单ID或充值ID
int64_t create_time; // 创建时间
};
这里有个原则:每一笔资金变动,都必须有对应的流水记录。而且流水记录一旦生成,就不能修改,只能追加。
5. 四张表的关系
这四张表不是孤立的。它们之间的关系,我用一张图来说明:
从图上可以看得很清楚:
- 订单簿是输入,它决定了你能以什么价格买卖
- 成交记录是结果,每一笔成交都来自订单簿的撮合
- 库存台账和资金流水是状态,成交后要同时更新这两者
这里有个容易忽略的点:库存和资金必须同时更新。不能先更新库存再更新资金,万一中间系统挂了,数据就不一致了。我建议用数据库事务,或者用分布式事务框架。
std::atomic 做库存的并发控制。如果是Python,用 threading.Lock 或者 asyncio.Lock。但说实话,高频场景下,我建议用C++,Python的GIL在极端行情下会拖后腿。
6. 总结一下
这四张表,是做市系统的地基。地基没打好,上面的策略再牛也白搭。
我个人习惯,在项目一开始就把这些数据结构定义好,用单元测试把增删改查都测一遍。别等到上线了才发现字段不够用,那时候改起来成本就高了。
嗯,今天就聊到这里。记住一句话:数据结构决定上层建筑。设计好了,后面写策略就像搭积木一样轻松。