第二十九讲:案例实战——搭建简易做市商系统、常见问题排查、性能调优实例

好,终于到了实战环节。

前面我们聊了那么多理论、架构、数据结构,说实话,都是纸上谈兵。今天咱们就真刀真枪地干一把——搭一个简易的做市商系统出来。

别紧张,这个系统不会太复杂。我的目标是让你亲手跑起来,然后亲眼看到它出问题,再亲手把它修好。这个过程,比你看十遍文档都管用。

一、系统核心逻辑:一个极简的报价引擎

做市商系统,说白了就三件事:接收行情 → 计算报价 → 发送订单

我们今天只聚焦中间那一步——报价引擎。它负责根据外部价格,加上我们自己的点差,生成买卖报价。

先看核心代码。我习惯用Python来快速原型,生产环境当然会用C++或Java,但教学嘛,Python最直观。

class SimpleMarketMaker:
    def __init__(self, spread_bps=5):
        self.spread_bps = spread_bps  # 点差,单位是基点
        self.mid_price = 100.0
        self.bid_price = 0.0
        self.ask_price = 0.0

    def on_market_data(self, mid_price):
        """收到行情后更新报价"""
        self.mid_price = mid_price
        half_spread = self.mid_price * (self.spread_bps / 10000) / 2
        self.bid_price = round(self.mid_price - half_spread, 2)
        self.ask_price = round(self.mid_price + half_spread, 2)

    def get_quote(self):
        return {'bid': self.bid_price, 'ask': self.ask_price, 'mid': self.mid_price}

# 模拟运行
mm = SimpleMarketMaker(spread_bps=10)
prices = [100.0, 100.05, 100.10, 99.95, 100.00]
for p in prices:
    mm.on_market_data(p)
    print(mm.get_quote())

这段代码,你一看就懂。但我要说的是——千万别在生产环境这么写

为什么?因为它是单线程的,而且没有处理并发。真实行情是源源不断涌进来的,你这边还在算,那边新数据又来了,很容易就乱套了。

核心要点:报价引擎必须是无状态的,或者状态管理极其严格。每次报价计算,只依赖当前输入,不依赖历史状态。

二、常见问题排查:我踩过的三个坑

好,系统搭起来了。跑一下,嗯,能出报价。但别高兴太早,问题马上就来。

坑1:报价延迟——为什么我的报价总是慢半拍?

我记得第一次做回测时,发现我的报价总是比市场慢200毫秒。一开始我以为是网络问题,查了半天,最后发现是垃圾回收(GC)在作祟。

Python的GC会在不确定的时间点触发,导致计算线程被暂停。你想想看,做市商拼的就是毫秒级响应,你这边GC一下,那边行情已经变了三波了。

我的建议:在Python里做原型可以,但上生产一定要用Java或C++,并且要配置低延迟GC(如ZGC或Shenandoah)。或者,干脆用无GC的语言,比如Rust。

坑2:报价抖动——价格明明没变,报价却在跳

这个坑更隐蔽。有一次我发现,行情价格明明是100.00,我的bid却在99.99和100.00之间来回跳。查了半天,发现是浮点数精度问题

你看这句代码:

half_spread = self.mid_price * (self.spread_bps / 10000) / 2

如果mid_price是100.00,spread_bps是5,算出来half_spread是0.025。但浮点数表示0.025时,其实是个近似值。再经过round函数一折腾,有时候就变成了0.02,有时候是0.03。

避坑指南:金融计算,永远不要用float。用Decimal,或者直接用整数(比如把价格乘以10000,用整数表示基点)。

坑3:订单重复——同一个报价被发了两次

这个我印象太深了。有一次系统上线后,发现交易所那边收到了重复订单。排查下来,原因是报价更新和订单发送之间没有加锁

场景是这样的:行情更新触发了报价计算,然后异步发送订单。但如果在发送过程中,又来了一笔行情,报价引擎又算了一次,又把新报价发出去了。结果就是,同一个价格被发了两次。

解决方案很简单:加一个版本号。每次报价更新时,版本号+1。发送订单时,带上版本号。如果发现版本号已经变了,就丢弃这次发送。

class SafeMarketMaker:
    def __init__(self):
        self.version = 0
        self.last_sent_version = -1

    def on_market_data(self, mid_price):
        self.version += 1
        # 计算报价...
        self._send_order_if_needed()

    def _send_order_if_needed(self):
        if self.version > self.last_sent_version:
            # 发送订单
            self.last_sent_version = self.version

三、性能调优实例:从100ms到5ms

好,问题排查完了,咱们来调优。

我拿一个真实案例来说。当时我们的系统,从收到行情到发出订单,平均延迟是100ms。这个速度,在股票市场还能凑合,但在期货市场,根本没法玩。

我们做了三件事,把延迟降到了5ms以下。

1. 去掉不必要的对象创建

原来的代码里,每次报价计算都会new一个字典:

def get_quote(self):
    return {'bid': self.bid_price, 'ask': self.ask_price}

这个操作看似无害,但每次new对象都会触发内存分配,而内存分配是有成本的。我们改成了对象池模式:

class Quote:
    __slots__ = ('bid', 'ask')
    def __init__(self):
        self.bid = 0.0
        self.ask = 0.0

# 预分配对象池
quote_pool = [Quote() for _ in range(1000)]
pool_index = 0

def get_quote():
    global pool_index
    q = quote_pool[pool_index]
    pool_index = (pool_index + 1) % 1000
    return q

就这么一个小改动,延迟降了30%。

2. 用数组代替哈希表

我们的报价数据原来存在dict里,key是字符串。字符串查找是有哈希开销的。我们改成了固定长度的数组,用枚举值做索引。

数据结构 查找时间(纳秒)
dict(字符串key) ~80 ns
list(整数索引) ~10 ns

别小看这70纳秒。在每秒处理10万笔行情的场景下,这就是7毫秒的差距。

3. 锁分离——读写锁的妙用

原来的系统,所有操作共用一把大锁。读行情要锁,写报价要锁,发订单也要锁。这导致严重的锁竞争。

我们改成了读写锁:行情写入用写锁,报价读取用读锁。读锁之间不互斥,只有写锁会阻塞读锁。

from threading import Lock

class ReadWriteLock:
    def __init__(self):
        self.read_lock = Lock()
        self.write_lock = Lock()
        self.readers = 0

    def acquire_read(self):
        with self.read_lock:
            self.readers += 1
            if self.readers == 1:
                self.write_lock.acquire()

    def release_read(self):
        with self.read_lock:
            self.readers -= 1
            if self.readers == 0:
                self.write_lock.release()

    def acquire_write(self):
        self.write_lock.acquire()

    def release_write(self):
        self.write_lock.release()

这个改动,让系统的吞吐量提升了3倍。

四、知识体系总览

说了这么多,咱们用一张图把整个做市商系统的核心逻辑串起来。

简易做市商系统核心流程 外部行情输入 报价引擎 计算点差 → 生成买卖报价 风险检查 库存限制 | 最大订单量 | 价格偏离 订单发送 去重检查 → 发送到交易所 成交反馈 → 更新库存 ⚠ 常见瓶颈 1. GC暂停 2. 浮点精度 3. 锁竞争 4. 对象创建 ✅ 优化方案 1. 对象池 2. 数组代替哈希表 3. 读写锁分离 4. 版本号去重

这张图把整个流程串起来了。你注意看,报价引擎是核心,但它也是最容易出问题的地方。我们刚才排查的三个坑,全都在这个环节。

五、写在最后

今天这个实战案例,说实话,只是冰山一角。真正的生产级做市商系统,比这复杂十倍不止。但核心思想是一样的:快、准、稳

快——延迟要低;准——报价要精确;稳——系统不能崩溃。

我建议你,看完这篇文章后,自己动手把代码敲一遍。然后故意制造一些延迟、抖动、重复的问题,再尝试用我们讲的方法去修复。这个过程,比任何教程都管用。

一句话总结:做市商系统,拼的不是算法有多复杂,而是细节处理有多到位。每一个纳秒、每一个字节、每一次锁的获取,都可能决定你的盈亏。


交易系统化学习资料 微信Strategy888888