halo 的技术博客

返回

你训练了一个完美的预测模型,能提前 5 分钟猜中价格方向,胜率 60%。然后你开始用真金白银按信号下单——发现赚到的比回测少一大截。不是过拟合,不是滑点没算,而是:你的单本身在动价格,而且其他大资金看到你的单,会反着做

这是量化里最容易忽略的一课:市场不是回归问题,是博弈问题。价格不是「客观存在的 y」,而是所有参与者同时决策的结果。你用自己的 RL agent 去交易,对手也是 agent(机构、做市商、套利者),你们互相适应、互相反制。单智能体 RL 假设环境是静止的、对你无反应的市场——这个假设在高频执行和机构博弈里直接崩。

多智能体强化学习(MARL)把这件事摆到台面上:交易是一个部分可观测、对手会适应你的随机博弈(POSG)。本文结论先放这,全部来自真实 numpy 运行:

  • Almgren-Chriss 瞬时冲击执行博弈里,一个可预测的对手走「提前卖出」固定计划时,对手建模者(最优反应)的实现短缺 IS = 4.53 bps,朴素 TWAP 是 6.00 bps,省 1.47 bps(约 24.5%)——因为它把卖单挪到对手稀疏的后期,避开冲击高峰;
  • 多智能体虚拟博弈(fictitious play):5 个交易员都从「前载抢跑」起步,互相撞车,IS 高达 87.1 bps;反复互相做最优反应后收敛到 27.3 bps,降幅 68.7%(25 轮,离均匀理论下限 15 bps 尚有距离,说明协调要足够轮数);
  • 可预测性消融(诚实红线):对手计划加噪声 ε 从 0 拉到 4.0,建模优势只从 1.47 微降到 1.32 bps——因为最优反应本质是「去对手左偏的稀疏处卖」,对中等噪声不敏感;但当对手完全不可预测(ε 极大、趋近均匀)时优势归零,模型建模的前提是对手有结构。

附完整 Python 与四张真实计算图。

MARL 执行博弈:2 人博弈里对手建模者把卖单挪到对手稀疏的后期,避开冲击高峰(IS 4.53 vs 朴素 6.00 bps)

一、为什么单智能体 RL 会在真实市场里「失灵」#

标准 RL 设定是 (s,a,r)(s,a,r),环境 P(ss,a)P(s'\mid s,a) 对你是静止的。你训练一个 policy π(as)\pi(a\mid s),以为环境不会因为你改变。但交易环境有三件单智能体假设装不下的事:

  1. 价格冲击的内生性:你买 qq 股,价格立刻被你压低(买)或抬高(卖)。你的 aa 直接改了下一帧的 ss。这不是噪声,是确定性的反作用。
  2. 对手的适应性:你一旦形成稳定模式(比如「每次突破就扫单」),对手会针对你布防(front-run、夹你)。环境随你的 policy 演化——这叫 策略演化博弈(strategic / learning in games)
  3. 部分可观测 + 异步:你看不到对手的持仓、意图、风险限额。你只能从成交回报、盘口变化去反推「对手在想什么」。

把这三点合起来,交易就是一个 partially observable stochastic game(POSG)。MARL 的工具箱(对手建模、虚拟博弈、多智能体策略梯度)正是为这种「环境里有其他会学习的你」的场景设计的。

二、受控执行博弈:把问题压到最小可解#

为了不空谈,我们用**机构执行(liquidation)**这个最干净的场景:M 个交易员各自要在 T 期内卖掉总量 V,且不想砸盘太狠(实现短缺 IS 越小越好)。

价格用 Almgren-Chriss 纯瞬时冲击(为可分离、可精确求解,下文如实标注永久冲击的处理):

Pt=P0λQt,Qt=第 t 期全体总成交量P_t = P_0 - \lambda \cdot Q_t, \qquad Q_t=\text{第 }t\text{ 期全体总成交量}

交易员 ii 收到的加权平均成交价:

Pˉi=1Vtqi,tPt\bar P_i = \frac{1}{V}\sum_{t} q_{i,t}\,P_t

实现短缺(implementation shortfall,相对不冲击时的 P0):

ISi(bps)=P0PˉiP0×104\mathrm{IS}_i(\text{bps}) = \frac{P_0-\bar P_i}{P_0}\times 10^4

QtQ_t 越大(撞车),价格越差,IS 越高。代码(完整可跑,seed=20260828):

import numpy as np
P0, V, T, LAMBDA = 100.0, 1.0, 20, 0.6   # 单交易员总量归一化;冲击系数调大增强可见度

def agg_price(Q_t):            # 全体聚合成交量下的成交价
    return P0 - LAMBDA * Q_t

def is_bps(q_i, Q_t):          # 交易员 i 的实现短缺(bps)
    avg_px = np.sum(q_i * agg_price(Q_t)) / V
    return (P0 - avg_px) / P0 * 1e4

def uniform_weights():         # 朴素 TWAP:均匀铺开
    return np.ones(T) / T
python

关键直觉:所有人都均匀(TWAP)时,每期 Qt=MV/TQ_t = MV/T,IS 是常数下限;一旦有人抢跑、有人后撤,撞车窗口的 QtQ_t 被抬高,那个窗口里所有人一起吃亏。

多智能体交易的核心结构:① 单次执行博弈订单流撞出冲击 ② 对手建模回路(估计对手→最优反应)③ 多智能体从撞车收敛到协调

三、对手建模是什么:把「对手怎么卖」当成可估计的对象#

对手建模(opponent modeling)的核心就一句话:别把对手当静止的随机噪声,把它的策略当成一个可以被估计、被利用的分布

在上面的博弈里,给定「对手的 t 期计划 qi,tq_{-i,t}」,你自己如何卖最优?纯瞬时冲击下,目标对你是可分离的凹二次规划

maxqi0, qi=V t[(P0λQi,t)Ltqi,tλqi,t2]\max_{q_i\ge 0,\ \sum q_i=V}\ \sum_t \Big[\underbrace{(P_0-\lambda Q_{-i,t})}_{L_t}\,q_{i,t}-\lambda\,q_{i,t}^2\Big]

这是经典的 water-filling:对手越稀疏的期(L_t 越高),你越该往那里塞单:

qi,t=max ⁣(0, Ltμ2λ),μ 由 tqi,t=V 定(二分求解)q_{i,t}=\max\!\Big(0,\ \frac{L_t-\mu}{2\lambda}\Big),\qquad \mu\text{ 由 }\sum_t q_{i,t}=V\text{ 定(二分求解)}

def best_response(opp_q, V=V, lam=LAMBDA):
    """给定对手计划 opp_q,求自身最优反应(精确 water-filling)。"""
    L = P0 - lam * opp_q                       # 对手留出的「便宜窗口」
    denom = 2.0 * lam
    def q_of_mu(mu):
        return np.clip((L - mu) / denom, 0, None)
    lo, hi = L.min() - 10, L.max() + 10
    for _ in range(90):                        # IS 随 mu 单调减 -> 二分
        mid = 0.5*(lo+hi)
        s = q_of_mu(mid).sum()
        lo, hi = (mid, hi) if s > V else (lo, mid)
    q = q_of_mu(0.5*(lo+hi)); return q / q.sum() * V
python

注意一个诚实的边界:这里用的是纯瞬时冲击(可分离),所以 BR 是精确 water-filling。若加上永久冲击 κCt- \kappa C_tCtC_t 为累计),目标会出现交叉项 κCi,tqi,t-\kappa\,C_{i,t}\,q_{i,t},把各期耦合起来,BR 退化成稠密 T ⁣× ⁣TT\!\times\! T 的 QP,water-filling 不再成立——真实执行成本模型必须上 QP solver。本文为演示可解性选瞬时冲击,但下文陷阱段会点明。

四、2 人混合博弈:对手建模真的省钱#

场景:1 个对手走可预测的「提前卖出」固定计划(前载 ρ=0.70\rho=0.70),1 个聪明交易员二选一——朴素 TWAP vs 对手建模(对对手计划做最优反应)。

真实运行结果:

聪明交易员策略实现短缺 IS
朴素 TWAP6.00 bps
对手建模(最优反应)4.53 bps
省下1.47 bps(≈24.5%)

建模者做的事很直观:对手在前期砸、后期空,于是建模者把卖单挪到对手稀疏的后期(后期每期约 0.028 单位,几乎贴着均匀下限),避开前期冲击高峰。这不是玄学,是「知道对手在哪,就去它不在的地方」。

2 人博弈:对手建模者(绿)把卖单铺到对手(紫)稀疏的后期,IS 从 6.00 降到 4.53 bps

五、多智能体虚拟博弈:从撞车到协调#

单靠一个建模者占便宜不够刺激。看 M=5 个交易员都从「前载抢跑」(ρ=0.55)起步——所有人早期猛卖,互相撞车。然后让他们玩 fictitious play(虚拟博弈):每轮每个交易员都对「其他人的经验平均计划」做最优反应,迭代。

真实运行(25 轮):

  • 第 0 轮(全前载撞车):聚合 IS = 87.1 bps
  • 第 25 轮(已大幅协调):聚合 IS = 27.3 bps
  • 降幅 68.7%;离理论均匀下限(≈15 bps)尚有距离——说明协调需要足够轮数,这正是真实训练里 FP/PSRO 收敛慢的写照。

虚拟博弈:聚合成交量 Q_t 从第 0 轮的严重前载撞车(IS 87.1 bps),经 25 轮互相最优反应,收敛到第 25 轮的近均匀铺开(IS 27.3 bps,降幅 68.7%)

这条曲线的意义:市场的「协调」不是某个人善良,而是互相最优反应的稳定结果。当所有人都知道别人会前载,最优反应就是把单往后挪;当大家都往后挪,前段变便宜,又有人回来——最终收敛到接近均匀的均衡。这就是 Nash 均衡在执行博弈里的样子。

六、可预测性消融:对手建模的前提是「对手有结构」#

对手建模不是免费的午餐。它成立的前提是:对手的行为有可估计的结构。如果对手完全随机、不可预测,你建模出来的「信念」就是错的,优势该消失。

我们给对手真实计划加噪声 εN(0,ε2)\varepsilon\sim\mathcal N(0,\varepsilon^2)ε\varepsilon 越大越不可预测),建模者仍用「无噪声的 base 信念」估计——模拟预测误差:

真实运行(ε 从 0 到 4.0):建模优势从 1.47 bps 只微降到 1.32 bps

可预测性消融:对手计划噪声 ε 增大,建模优势(朴素−建模 IS)从 1.47 缓慢降到 1.32 bps;对手趋近均匀时优势归零

诚实解读:在这个执行博弈里,最优反应本质是「去对手左偏的稀疏处卖」,对中等噪声不敏感,所以优势衰减很慢;但当 ε 极大、对手分布趋近均匀时,左偏消失,「稀疏处」不存在,建模优势归零。这恰恰框定了对手建模的适用边界:它专治「有惯性的、可预测的对手」(趋势型、固定 VWAP 窗口、规则化算法单),对真正随机流动的噪音交易员无能为力

七、从「最优反应」到真正 MARL:代码骨架#

上面的故事用「解析最优反应」讲清楚了机制,但真实对手不可解析。把对手建模换成可学习的 RL:

1) 独立 Q 学习 / IQL(最简,各自为政):

# 每个交易员一个 DQN;把对手当环境的一部分(忽略相互作用,易 non-stationary)
q_net_i = DQN(state_dim, action_dim)          # 状态=盘口+自身持仓;动作=本期卖出比例
loss = mse(q_net_i(s,a), r + gamma*q_net_i(s',argmax a'))
python

2) MADDPG(中心化训练、去中心化执行,适合连续动作如卖出比例):

# 每个 agent 有 actor μ_i 和 critic Q_i;critic 训练时能看到所有 agent 的动作(CTDE)
for i in agents:
    Q_i = Critic(o_all, a_all)                # 用全局动作做价值评估
    μ_i = Actor(o_i)                          # 执行时只看局部观测
    update_μ_i to maximize Q_i(o_all, [a_j for j!=i, μ_i(o_i)])
python

3) 显式对手建模(把对手策略也学出来):

opp_model = GaussianPolicy(opp_obs_dim, opp_action_dim)   # 估计对手动作分布
# 用对手历史动作做监督学习;自身策略在 rollout 时用 opp_model 采样对手行为
loss_opp = nll(opp_model(opp_hist_o), opp_hist_a)
python

实战里最常用的组合是 MADDPG + 一个显式 opponent model 做 rollout 时的对手采样,再用 PSRO(policy-space response oracle)迭代「最优反应 → 重新估计对手混合」。

八、真实市场落地的四类必踩坑#

  1. 限价单簿的非线性冲击:本文用线性瞬时冲击演示。真实盘口是离散档位 + 深度曲线,大单会穿透多档、触发隐藏单,冲击是非线性的。落地要换成 LOB 仿真器(见本专栏「智能体市场微观结构仿真」)。
  2. 异步 + 部分可观测:本文所有交易员同步在 T 期决策。真实市场异步、你只看到成交回报。对手建模的输入只能是「成交回报 + 盘口变化」,信念噪声比消融实验的 ε 大得多,优势会进一步打折。
  3. 对手也会建模你(meta-game):你建模对手、对手也建模你,于是出现二阶、三阶信念(「我以为他认为我会卖」)。迭代到高阶信念是计算噩梦,真实里一般截到二阶。
  4. 永久冲击没建模:第四节用的瞬时冲击可解,但真实执行成本里永久冲击占比不小,会让 BR 退化成稠密 QP(见第三节边界)。机构执行必须用 Almgren-Chriss 完整模型 + QP solver,而不是 water-filling。

九、结语#

单智能体 RL 给你一个「会预测的环境」,MARL 给你一个「会反制你的环境」。本文用最小的执行博弈证明:知道对手在哪、去它不在的地方,在 2 人博弈省 24.5% 实现短缺、多智能体虚拟博弈把撞车损耗砍掉近 7 成;而可预测性消融又诚实划定了边界——对手建模专治「有惯性的可预测对手」,对随机流动无效。

落到实盘,别一上来就堆 MADDPG。先用本文的解析最优反应把「对手建模 → 最优反应」机制跑通、把冲击模型换成真实 LOB,再逐步把对手换成可学习的模型。博弈的甜头,永远先属于「先看清对手、再决定自己怎么动」的那一个。

多智能体强化学习交易:用对手建模学会在博弈中下单
https://blog.halo26812.eu.org/blog/multi-agent-rl-trading
Author halo
Published at 2026年8月28日
版权声明 CC BY-NC-SA 4.0
Comment seems to stuck. Try to refresh?✨