从一张差点爆仓的网格,到一次与 AI 共同完成的策略重写
一张跑在韩国股指上的网格,在真实行情里露出四根刺。追着这四根刺往下挖,最后挖到的不是参数,而是一整套推翻重来的数学。而全程真正稀缺的,不是"会解题",是"提对问题"。

图 1:左侧的固定网格在趋势中不断积累风险,右侧则是由递归估计与风控闸门支撑的自适应结构。
故事从一个主观判断开始。我看好韩国市场的一段趋势,于是把一张通用网格挂上了 EWY——区间内低买高卖,价格突破就往有利方向平移跟随。逻辑很干净,回测也好看。
但真金白银跑起来,四个问题一根根扎出来。下面这四段,是我在盘面前实实在在的感受:
刺一 · 低效。 格距和每格下单金额都是写死的固定值。可股票不是 7×24 均匀波动的——亚盘收盘到美盘开盘那一段,价格几乎不动,波动率低得可怜。这时候网格还用着和剧烈时段一样的大区间、一样的下单额,格子稀稀拉拉挂在那儿半天不成交,钱就那么躺着。当然,一旦有突发事件,它又会突然大幅波动。问题就在于:低波动的时候,还在用固定的大区间下单,效率低得刺眼。
刺二 · 爆仓。 这是最要命的。一旦行情进入持续下跌,做多的网格会不停地在更低的格子接多单——越跌越买、越买越多,而且没有任何止损。10 倍杠杆之下,这就是典型的"接飞刀",仓位越堆越重,迟早爆仓。策略里虽然能显示清算价,但它只是显示,从不干预。
刺三 · 收益回吐,还干等浪费时间。 网格在震荡里一点点攒出来的收益,看着挺美。可只要来一波像样的下跌,账户收益几乎全部吐回去。更难受的是接下来——我抱着一堆套牢的多单,只能死等价格回归,才能把它们各自止盈。这一等就是大段时间,资金卡在里面动弹不得,机会成本白白流失。
刺四 · 高位死库存。 那些在高位开的单子,其实是靠底部反复的震荡收益,一点一点把它们的亏损"磨平"的。这个过程又慢又被动。我想要的是:网格的最高点能够灵活下调,别一直死扛着那堆高位的货。
这四根刺,看起来是四个毛病。但往下挖,它们是同一根。

图 2:震荡阶段的小额收益不断积累;一旦进入单边下跌,逆势库存的风险会以更快速度放大。
用希腊字母说最清楚。网格的本质是做空波动率(short gamma):它靠价格来回震荡吃钱,赌的是均值回归。而做多网格在下跌里不断接飞刀,净持仓(delta)越堆越大——它对趋势毫无防御。
把盈亏拆开看,结构是这样的:
网格盈亏 ≈ ( 震荡收割 ∝ σ²/d ) − ( 库存 × 趋势漂移 μ )
└── 永远为正,吃波动 ──┘ └── 下跌时对多头是纯亏损 ──┘
d 越频繁、波动 σ 越大,收割越多,永远为正。库存 × μ。在下跌里 μ 长期为负,而库存又在不断变大,这一项会发散,最终吞掉一切。真正的病根:这是一个"做空波动"的策略,却没有任何风险控制。
更糟的是,传统网格"只往有利方向移动"的设计,其实和网格初衷是相反的:价格涨破上沿时它上移、在更高的价位挂新多单(追高买入);价格跌破下沿时它不动、不撤单、无止损(死扛接飞刀)。这等于在放大那个趋势项。
回头看那四根刺——低效、爆仓、回吐、死库存——全是同一个 库存 × μ 在不同阶段的表情。承认这一点,方向就清楚了:不能只调参数,得给这张裸奔的短 gamma 网格,补上一套会思考的骨架。
先解最直观的刺一。区间宽度不该是价格的固定百分比,而应该跟着波动结构走:低波动时自动收窄、密集收割小波动;剧烈时自动张开、不被一根长针扫穿。像肺一样一呼一吸。

图 3:左侧波动较低,格距与区间同步收紧;右侧波动放大,网格随均衡幅度向外扩张。
刺二、刺三的核心,是方向判断。
资本市场毫秒级的跳动看着吓人,但那恰恰是网格要收割的噪声——你不需要预测它,也预测不了。真正要估的,是那个藏在噪声底下的慢变量:漂移 μ。它的时间尺度是分钟到小时,不是毫秒。
把高频跳动当成方向信号去频繁止损,是所有网格翻车的认知根源——用噪声做决策,必然一边浪费收益(频繁止损的手续费和错杀),一边还是躲不过真正的趋势。
于是问题变成:怎么在不频繁止损、也不硬扛的前提下,连续地感知 μ?这需要一只"第二只眼睛"。
EWY 是韩国股指,它有成分股——三星、SK 海力士。关键的转念是:不去赌"谁领先谁"(那种领先关系并不稳定,会被地缘、美联储、消息面不断打乱),而是用成分股来确认一个下跌是不是"真的"。这只需要同步性,不需要领先性。
数学上,把 EWY 的每一步收益,拆成两块:
r_EWY = β · r_参考 + ε
└─系统性─┘ └特质性┘
广泛的/基本面的 EWY 自己的
→ 真趋势 → 会回归
判断规则一下子清晰了:

价格自己看不出这个区别,一根阴线长得都一样。加上成分股这只眼睛,就能把趋势和噪声分开——这正是我一直想要的"短期偏离 vs 长期转换"的分水岭。

图 4:左侧三条价格流同步走弱,系统判为广泛趋势并关闭逆势开仓;右侧只有目标短期偏离,均值回归通道仍然保留。
还有一个更深的不满:区间该多宽、该等多久回归——凭什么由我拍脑袋定?
如果价差是均值回归的,它就服从 OU 过程,那么它的时间尺度是可以从数据里估出来的,不是我输入的参数。核心是回归半衰期:
H = ln2 / θ (θ 是回归速度)
k·H 还不回归 = 回归关系变质 = 趋势转换确认 → 退出,而不是死等。"等多久算太久",从一种主观焦虑,变成了一个数字。刺三里那个"干等回归浪费时间",第一次有了精确的闸门。
所有诉求,最后收敛成一个原则——递归、在线、无固定窗口。每来一根 K 线更新一次,记忆随信噪比与回归速度自适应,而不是回看固定的 N 根。三个估计器扛起全部判断。

图 5:Kalman 跟踪局部水平与斜率,RLS-OU 估计回归尺度,跨资产 β 分解系统性漂移;三路结果最后共同决定区间与风控闸门。
用一个二维卡尔曼滤波(状态 = 水平 + 斜率)在线跟踪价格。它没有回看窗口:卡尔曼增益随信噪比自适应,市场稳则记忆长、突变则快速遗忘。观测噪声 R(也就是波动率)用 Robbins-Monro 递归在线估计——软记忆,不是固定窗口。
def kf_step(st, y):
"""Kalman 局部水平+趋势,逐点更新一个状态字典。观测噪声 R 在线自适应,无窗口。"""
st["price"] = y
if not st["init"]:
st["l"] = y
st["s"] = 0.0
st["R"] = max((y * 0.0005) ** 2, 1e-12) # 初始波动先验≈5bp
st["init"] = True
return
R = st["R"] if st["R"] > 0 else 1e-12
q_s = KF_Q_RATIO * R # 斜率过程噪声(响应性,唯一软先验)
q_l = KF_Q_RATIO * R * 0.25
# 预测: F=[[1,1],[0,1]]
l_pred = st["l"] + st["s"]
s_pred = st["s"]
pp00 = st["p00"] + st["p01"] + st["p10"] + st["p11"] + q_l
pp01 = st["p01"] + st["p11"]
pp10 = st["p10"] + st["p11"]
pp11 = st["p11"] + q_s
# 更新: H=[1,0]
e = y - l_pred # 创新(本次预测误差)
S = pp00 + R
k0 = pp00 / S
k1 = pp10 / S
st["l"] = l_pred + k0 * e
st["s"] = s_pred + k1 * e
st["p00"] = (1 - k0) * pp00
st["p01"] = (1 - k0) * pp01
st["p10"] = pp10 - k1 * pp00
st["p11"] = pp11 - k1 * pp01
# 观测噪声自适应(Robbins-Monro,软记忆,非固定窗口)
st["R"] = st["R"] + KF_ADAPT * (e * e - st["R"])
st["sigma"] = math.sqrt(st["R"]) # 瞬时波动
st["z"] = st["s"] / (st["sigma"] + 1e-12) # 趋势强度=斜率/波动,带符号
输出的 st["z"](斜率/波动,一个带符号的"每根信噪比")就是我们的趋势强度信号。注意这个状态是一个字典 st——同一套数学,目标和每个参考标的复用。
用带遗忘因子的递归最小二乘,在线拟合 price_t = a + b·price_{t-1}。由回归系数 b 反推 OU 半衰期 H 和均衡幅度 σ_eq。
精髓在于遗忘因子由估出的半衰期反过来决定(记忆≈OU_MEMORY 个半衰期)——记忆长度是数据自己收敛出来的,不是我拍的窗口。
def _rls_update(y_prev, y):
"""RLS-AR(1) 估计均值回归。遗忘因子由估出的半衰期自洽给出,无固定窗口。"""
global _rls_a, _rls_b, _rls_p00, _rls_p01, _rls_p10, _rls_p11
global _rls_resid_var, _rls_init, _ou_half_life, _ou_sigma_eq
# 遗忘因子:记忆 ≈ OU_MEMORY 个半衰期;半衰期未知时用温和默认
if _ou_half_life > 0:
mem = max(OU_MEMORY * _ou_half_life, 5.0)
ff = math.exp(-1.0 / mem)
else:
ff = 0.99
ff = min(max(ff, 0.90), 0.9995)
phi0, phi1 = 1.0, y_prev
pph0 = _rls_p00 * phi0 + _rls_p01 * phi1
pph1 = _rls_p10 * phi0 + _rls_p11 * phi1
denom = ff + phi0 * pph0 + phi1 * pph1
k0 = pph0 / denom
k1 = pph1 / denom
e = y - (_rls_a * phi0 + _rls_b * phi1)
_rls_a += k0 * e
_rls_b += k1 * e
_rls_p00 = (_rls_p00 - k0 * pph0) / ff
_rls_p01 = (_rls_p01 - k0 * pph1) / ff
_rls_p10 = (_rls_p10 - k1 * pph0) / ff
_rls_p11 = (_rls_p11 - k1 * pph1) / ff
_rls_resid_var = _rls_resid_var + KF_ADAPT * (e * e - _rls_resid_var)
b = _rls_b
if 0.0 < b < 1.0:
theta = -math.log(b)
_ou_half_life = math.log(2.0) / theta # 半衰期(根)
if (1 - b * b) > 1e-9 and _rls_resid_var > 0:
_ou_sigma_eq = math.sqrt(_rls_resid_var / (1 - b * b)) # 均衡幅度
else:
_ou_half_life = 0.0 # b≥1:无回归(单位根/趋势),交给趋势闸门处理
区间宽度直接由 σ_eq 决定,随波动/回归速度自适应,带上下限保护——没有任何固定窗口:
def effective_width_abs(ref_price):
"""区间宽度由 OU 均衡幅度 σ_eq 决定,随波动结构自适应。未热身时回退固定百分比。"""
if estimators_ready() and _ou_sigma_eq > 0:
w = WIDTH_SIGMA_MULT * _ou_sigma_eq
floor_w = pct_to_abs(GRID_WIDTH_PCT, ref_price) * WIDTH_FLOOR_FRAC
cap_w = pct_to_abs(GRID_WIDTH_PCT, ref_price) * WIDTH_CAP_MULT
return min(max(w, floor_w), cap_w)
return pct_to_abs(GRID_WIDTH_PCT, ref_price)
这是把系统性趋势判断的"确认"真正嵌进去。每根新 K 线,在收益空间用 EWMA 递归估计目标对各参考的回归系数 β_j,再把目标漂移拆成系统性与特质性两部分:
# —— update_estimators 内部,逐根更新 β 与系统性趋势 ——
# 递归回归系数 β_j = cov / var (无窗口软记忆)
r_t = (c - prev_close) / prev_close # 目标本根收益
r_j = (rc - _ref_prevc[j]) / _ref_prevc[j] # 参考本根收益
_ref_cov[j] += KF_ADAPT * (r_t * r_j - _ref_cov[j])
_ref_var[j] += KF_ADAPT * (r_j * r_j - _ref_var[j])
_ref_beta[j] = _ref_cov[j] / _ref_var[j]
# 系统性(被参考确认的)漂移: ĝ_sys = Σ β_j · 参考分数漂移
g_sys = 0.0
for j in range(nref):
g_sys += _ref_beta[j] * (_kf_refs[j]["s"] / _kf_refs[j]["price"])
sig_frac = _vol_sigma / pt
_trend_z_sys = g_sys / (sig_frac + 1e-12) # 系统性趋势强度
_sys_share = abs(g_sys) / (abs(g_t) + 1e-12) # 系统性占比
于是趋势闸门只认被参考确认的那部分。启用参考时用系统性 z_sys,否则退回目标自身 z:
def gate_trend_z():
"""
启用参考且数据可用 → 用【系统性】z_sys(被三星/海力士等确认的部分);否则退回目标自身 z。
目标跌但参考没跟(特质噪声) → z_sys≈0 → 不拦,照收割;
目标跌且参考同步(系统真趋势) → z_sys 显著 → 拦住逆势单。
"""
if ref_n() > 0 and _refs_ok and estimators_ready():
return _trend_z_sys
return _trend_z
而"最高点灵活下调"(刺四),用库存保留价偏移(Avellaneda-Stoikov 保留价的离散版)实现:多头库存越大,有效上沿越往下压,高位自动停开——天花板自己会降。
def effective_ceiling():
"""多头库存越大,有效上沿越往下压——高位不再加仓,天花板自动下调。"""
long_amt = assets[base_currency].get("long_amount", 0)
short_amt = assets[base_currency].get("short_amount", 0)
net_usdt = (long_amt - short_amt) * assets[base_currency]["ctVal"] * get_price()
inv_ratio = min(max(0.0, net_usdt / (Funding * MAX_NET_EXPOSURE_MULT)), 1.0)
drop = INVENTORY_SKEW * inv_ratio * (range_high - range_low)
return range_high - drop
这些闸门统一收束在开仓判断里——趋势、净敞口硬上界、库存天花板、回撤熔断,任何一道不通过就不开新仓,但绝不强平已有的止盈单(既不逆势加仓、也不频繁止损):
def can_open(side, grid_price):
"""开新仓的多道数学闸门:趋势 / 净敞口 / 库存天花板 / 回撤熔断。"""
if estimators_ready():
gz = gate_trend_z()
if side == "long" and gz <= -TREND_Z_THRESHOLD: return False, "trend"
if side == "short" and gz >= TREND_Z_THRESHOLD: return False, "trend"
# 净敞口硬上界(防无限接飞刀爆仓)
net_usdt = (long_amt - short_amt) * ct_val * price
if side == "long" and net_usdt >= Funding * MAX_NET_EXPOSURE_MULT: return False, "risk"
if side == "short" and -net_usdt >= Funding * MAX_NET_EXPOSURE_MULT: return False, "risk"
# 库存天花板:高位停开
if side == "long" and grid_price > effective_ceiling(): return False, "skew"
if check_drawdown_breaker(): return False, "dd"
return True, ""
重构成 v4.0 后,四根刺各自对上了一个数学机制——不再是打补丁,而是同一套骨架的不同投影:

参考合约只要在参数里填一行 SAMSUNG_USDT, SKHYNIX_USDT(用 GetRecords 直接取数,无需配置附加交易所),其余全自适应。
用合成序列做数值验证。同样是 EWY 在跌,就看成分股确认与否,闸门给出完全相反的动作——这正是"确认而非预测"该有的样子:

目标自己看都是下跌(z ≈ −0.3),但有了第二只眼睛:一个判为真跌该躲,一个判为噪声该赚。价格永远分不出的区别,数学分出来了。
这次优化真正的收获,不是那份代码。是一条路径:
从实盘里那根具体的刺出发,拒绝用过时的、无法自证有效的套路去糊,一路逼问到数学的第一性,让参数自己收敛出来。
四根刺,最后收敛成两个可调旋钮(趋势灵敏度、区间松紧)和一个风险偏好。剩下的,交给数据自己说话——以及,交给回测去检验这套地基到底赚不赚钱。那是下一段故事。
老实说,这篇文章里的数学,我并不都懂。卡尔曼滤波怎么推导、RLS 的遗忘因子为什么要那样自洽、OU 半衰期背后的随机微分方程——真要我从头讲一遍,我讲不清楚。
但这恰恰是这个时代最好的地方。
想做一桌好菜,酱油和醋不需要我们亲手去酿——我们照样能做出美味。在大模型的时代,能力的分工已经悄悄换了位置:重要的,不再是"解决问题",而是"从实践中发现问题"。
求解的那一段路,工具可以替我们走完——推导、建模、写代码、做验证。但"哪里痛、为什么痛、真正的问题到底是什么",这一段,只能来自真实的实践。是我在盘面前一天天盯着那四根刺,才知道该往哪问;是我坚持"固定窗口的指标无效",才逼出了那套递归无窗口的骨架。工具能回答问题,但提不出问题;能走完路,但选不了方向。
知识的阶梯正在越变越低,几乎人人都能借助工具,一步就站到答案跟前。可也正因为如此,真正稀缺的东西变了:是发现真正问题的眼力,是在无数表象里迈向正确那条路的判断力。方向对了,答案自会浮现;方向错了,再强的算力也只是把你更快地带向错误。
所以这篇文章记录的,与其说是一套量化策略,不如说是一次示范——
一个并不精通全部数学的人,只要能从实盘里精准地指出"这四根刺"、并且不肯用似是而非的套路敷衍过去,就能借助这个时代的工具,把问题一根根拔干净。
在人人都能拿到答案的时代,先学会提对的问题。