第26章 做市商模拟环境:仿真市场生成、对手方行为建模、压力测试场景设计

做市商策略上线前,最怕什么?

怕实盘直接亏钱。

我见过太多团队,策略回测跑得漂亮,一上实盘就崩。原因很简单——回测环境太「干净」了。真实市场有滑点、有对手方博弈、有极端行情,这些在普通回测里根本模拟不出来。

所以这一章,我们来聊聊怎么搭建一个靠谱的做市商模拟环境。说白了,就是给你的策略造一个「虚拟战场」。

26.1 仿真市场生成:从零造一个交易所

仿真市场的核心,是生成可信的订单簿和价格序列。我个人习惯用以下三种方法:

26.1.1 基于历史数据重放

最简单,也最真实。直接把历史Level2数据喂进去,逐笔回放。优点是市场微观结构完全真实,缺点是数据量有限,没法测试「没发生过」的场景。

class HistoricalReplayer:
    def __init__(self, data_path):
        self.data = pd.read_parquet(data_path)
        self.index = 0
    
    def next_tick(self):
        """返回下一个tick的订单簿快照"""
        if self.index >= len(self.data):
            return None
        tick = self.data.iloc[self.index]
        self.index += 1
        return {
            'bids': tick['bid_prices'],  # 数组
            'asks': tick['ask_prices'],
            'bid_sizes': tick['bid_sizes'],
            'ask_sizes': tick['ask_sizes'],
            'timestamp': tick['timestamp']
        }

26.1.2 随机过程生成

当你想测试「如果波动率突然翻倍会怎样」时,历史数据就不够用了。这时候需要用随机过程来生成价格路径。

我常用的是Heston模型,因为它能模拟波动率聚集效应——就是那种「跌的时候波动更大」的现象。

def generate_price_path(S0, T, N, kappa, theta, sigma_v, rho, v0):
    """
    Heston模型生成价格路径
    S0: 初始价格
    T: 时间长度(秒)
    N: 步数
    """
    dt = T / N
    prices = np.zeros(N)
    vols = np.zeros(N)
    prices[0] = S0
    vols[0] = v0
    
    for i in range(1, N):
        z1 = np.random.normal()
        z2 = np.random.normal()
        # 相关性处理
        dw1 = z1 * np.sqrt(dt)
        dw2 = (rho * z1 + np.sqrt(1 - rho**2) * z2) * np.sqrt(dt)
        
        # 方差过程(保证非负)
        vols[i] = max(vols[i-1] + kappa * (theta - vols[i-1]) * dt 
                     + sigma_v * np.sqrt(max(vols[i-1], 0)) * dw2, 0)
        
        # 价格过程
        prices[i] = prices[i-1] * np.exp(
            (0 - 0.5 * max(vols[i-1], 0)) * dt 
            + np.sqrt(max(vols[i-1], 0)) * dw1
        )
    
    return prices, vols

26.1.3 订单簿生成器

有了价格路径,还得有订单簿。我一般用「泊松到达 + 价格偏移」的方式来模拟限价单的到达和撤销。

class OrderBookSimulator:
    def __init__(self, mid_price, spread, depth=10):
        self.mid = mid_price
        self.spread = spread
        self.depth = depth
        self.bids = {}  # 价格 -> 数量
        self.asks = {}
        self._init_book()
    
    def _init_book(self):
        """初始化一个均匀分布的订单簿"""
        for i in range(1, self.depth + 1):
            bid_price = self.mid - self.spread/2 - i * 0.01
            ask_price = self.mid + self.spread/2 + i * 0.01
            self.bids[bid_price] = np.random.exponential(10)
            self.asks[ask_price] = np.random.exponential(10)
    
    def step(self, new_mid, dt):
        """模拟一个时间步的订单簿变化"""
        # 价格漂移
        shift = new_mid - self.mid
        self.mid = new_mid
        
        # 限价单到达(泊松过程)
        lambda_arrival = 5  # 每秒到达5单
        n_arrivals = np.random.poisson(lambda_arrival * dt)
        for _ in range(n_arrivals):
            side = 'buy' if np.random.random() < 0.5 else 'sell'
            price_offset = np.random.exponential(0.5)  # 价格偏移
            size = np.random.exponential(2)
            
            if side == 'buy':
                price = self.mid - self.spread/2 - price_offset * 0.01
                self.bids[price] = self.bids.get(price, 0) + size
            else:
                price = self.mid + self.spread/2 + price_offset * 0.01
                self.asks[price] = self.asks.get(price, 0) + size
        
        # 限价单撤销(每个价格以概率撤销)
        for book in [self.bids, self.asks]:
            for price in list(book.keys()):
                if np.random.random() < 0.1 * dt:  # 10%/秒的撤销率
                    del book[price]
        
        return self.get_snapshot()

我的经验:刚开始做仿真时,我总想把订单簿做得越精细越好。后来发现,对于做市商策略测试,关键是要模拟出「流动性分布」和「订单到达的随机性」,而不是追求每个tick都跟真实市场一模一样。够用就好。

26.2 对手方行为建模:你的敌人是谁?

做市商赚的是谁的钱?对手方的钱。所以你得知道对手方长什么样。

26.2.1 对手方类型

我一般把对手方分成三类:

类型 行为特征 对做市商的影响
知情交易者 有信息优势,只在有利可图时交易 造成逆向选择,做市商需要扩大价差
流动性交易者 随机买卖,无信息优势 提供利润来源,做市商赚取价差
算法交易者 高频、小单、策略化 增加市场噪音,可能被「钓鱼」

26.2.2 知情交易者建模

这个最难,也最关键。我参考了Kyle模型的思想:知情交易者知道资产的「真实价值」,当市场价格偏离真实价值时,他们会出手。

class InformedTrader:
    def __init__(self, true_value, sigma_info=0.01):
        self.true_value = true_value  # 真实价值(只有他知道)
        self.sigma_info = sigma_info  # 信息精度
    
    def decide(self, orderbook):
        """根据订单簿决定是否交易"""
        best_bid = max(orderbook.bids.keys())
        best_ask = min(orderbook.asks.keys())
        mid = (best_bid + best_ask) / 2
        
        # 如果市场价格偏离真实价值超过阈值
        threshold = self.sigma_info * 2
        
        if self.true_value - best_ask > threshold:
            # 真实价值远高于卖价,买入
            return ('buy', best_ask, orderbook.asks[best_ask])
        elif best_bid - self.true_value > threshold:
            # 真实价值远低于买价,卖出
            return ('sell', best_bid, orderbook.bids[best_bid])
        else:
            return None  # 不交易

26.2.3 流动性交易者建模

这个简单,用泊松过程加随机方向就行。但要注意,真实市场中流动性交易者的订单大小往往有「肥尾」特征——就是偶尔会出现大单。

class LiquidityTrader:
    def __init__(self, intensity=2.0):
        self.intensity = intensity  # 每秒交易次数
        self.size_dist = lambda: np.random.pareto(1.5) * 0.5  # 肥尾分布
    
    def generate_orders(self, dt):
        """生成一个时间步内的流动性订单"""
        n_orders = np.random.poisson(self.intensity * dt)
        orders = []
        for _ in range(n_orders):
            side = 'buy' if np.random.random() < 0.5 else 'sell'
            size = self.size_dist()
            orders.append((side, size))
        return orders

注意:我曾经在模拟中只用了正态分布来生成订单大小,结果策略表现特别好。一上实盘,被几个大单直接打穿。后来才意识到,真实市场的订单大小分布是肥尾的。所以模拟时一定要用Pareto分布或者混合分布。

26.3 压力测试场景设计:把策略往死里整

策略在正常市场里赚钱不算本事。能在极端行情下活下来,才是真功夫。

26.3.1 经典压力场景

我一般会测试以下场景:

  1. 闪电崩盘:价格在几秒内暴跌10%,然后快速反弹
  2. 流动性枯竭:订单簿深度骤降,价差扩大10倍
  3. 波动率爆发:波动率从10%瞬间跳到100%
  4. 对手方狙击:大量知情交易者同时出现,专门吃你的单
  5. 网络延迟:你的订单比别人晚到100ms

26.3.2 闪电崩盘模拟

def flash_crash_scenario(simulator, crash_depth=0.1, recovery_time=5):
    """
    模拟闪电崩盘
    crash_depth: 跌幅比例
    recovery_time: 恢复时间(秒)
    """
    original_mid = simulator.mid
    
    # 第一阶段:暴跌
    for t in range(10):  # 10个tick内完成暴跌
        new_mid = original_mid * (1 - crash_depth * (t+1)/10)
        simulator.step(new_mid, dt=0.1)
        yield simulator.get_snapshot()
    
    # 第二阶段:底部震荡
    for t in range(int(recovery_time * 10)):
        noise = np.random.normal(0, 0.005)
        new_mid = original_mid * (1 - crash_depth) * (1 + noise)
        simulator.step(new_mid, dt=0.1)
        yield simulator.get_snapshot()
    
    # 第三阶段:反弹
    for t in range(10):
        new_mid = original_mid * (1 - crash_depth * (1 - (t+1)/10))
        simulator.step(new_mid, dt=0.1)
        yield simulator.get_snapshot()

26.3.3 流动性枯竭模拟

def liquidity_drought(simulator, duration=30):
    """
    流动性枯竭:订单到达率降到正常值的1/10
    """
    original_intensity = simulator.liquidity_intensity
    simulator.liquidity_intensity = original_intensity * 0.1
    
    for _ in range(int(duration / 0.1)):
        # 同时扩大价差
        simulator.spread *= 1.01  # 每次扩大1%
        simulator.step(simulator.mid, dt=0.1)
        yield simulator.get_snapshot()
    
    # 恢复
    simulator.liquidity_intensity = original_intensity

26.4 完整模拟框架

把这些组件拼起来,就是一个完整的模拟环境了。我习惯用事件驱动的方式组织:

class MarketSimulator:
    def __init__(self, config):
        self.orderbook = OrderBookSimulator(
            config['initial_price'],
            config['initial_spread']
        )
        self.informed = InformedTrader(config['true_value'])
        self.liquidity = LiquidityTrader(config['liquidity_intensity'])
        self.history = []
    
    def run(self, n_steps, dt=0.1, stress_scenario=None):
        """运行模拟"""
        for step in range(n_steps):
            # 1. 更新价格(如果有压力场景)
            if stress_scenario:
                new_mid = next(stress_scenario, None)
                if new_mid is None:
                    break
            else:
                # 正常随机游走
                new_mid = self.orderbook.mid * (1 + np.random.normal(0, 0.001))
            
            # 2. 更新订单簿
            snapshot = self.orderbook.step(new_mid, dt)
            
            # 3. 生成对手方订单
            informed_order = self.informed.decide(self.orderbook)
            liquidity_orders = self.liquidity.generate_orders(dt)
            
            # 4. 执行交易(这里可以接入你的做市策略)
            all_orders = []
            if informed_order:
                all_orders.append(informed_order)
            all_orders.extend(liquidity_orders)
            
            # 5. 记录状态
            self.history.append({
                'step': step,
                'mid': new_mid,
                'spread': self.orderbook.spread,
                'n_orders': len(all_orders),
                'snapshot': snapshot
            })
        
        return self.history

核心要点:

  • 仿真市场要能同时模拟「正常波动」和「极端事件」
  • 对手方建模要区分知情交易者和流动性交易者
  • 压力测试不是走过场,要真的把策略逼到极限
  • 模拟环境要可复现(设置随机种子),方便对比不同策略

嗯,这套模拟环境我用了快三年。每次开发新策略,我都会先在这个环境里跑至少1000次模拟,覆盖各种极端场景。虽然不能保证实盘100%没问题,但至少能筛掉80%的坑。

记住一句话:模拟环境越残酷,实盘越安全。

做市商模拟环境架构 仿真市场生成 历史数据重放 随机过程生成 订单簿模拟器 价格路径生成 对手方行为建模 知情交易者 流动性交易者 算法交易者 订单到达与撤销 压力测试场景 闪电崩盘 流动性枯竭 波动率爆发 对手方狙击 做市商策略验证与评估 三大模块协同工作,构建完整的做市商模拟环境 仿真市场提供基础数据 → 对手方行为增加博弈 → 压力测试检验极限 关键指标:最大回撤 | 夏普比率 | 胜率 | 平均持仓时间 | 库存周转率

无相订单流研究社 微信Lucian808555