[[asof || '—']]
[[liveCard.title]]
[[liveCard.description]]
RSRS 标准分
[[fx(S.curSig,2)]]
[[liveCard.signalLabel]]
原版净值 [[fx(S.nav1,2)]]×
优化净值 [[fx(S.nav2,2)]]×
买入持有 [[fx(S.navb,2)]]×
量化方法论 · 教学复现
在噪声中寻找信号
市场每天抛出成千上万个数字,其中绝大多数是噪声。量化研究的全部手艺,是用一套可证伪、可复算的数理统计方法 ,从噪声里分离出一丝真实的异象(anomaly) ,再把它转化成可以下注的 alpha 。
本文以光大证券提出的 RSRS(阻力支撑相对强度,Resistance-Support Relative Strength) 择时指标为标本,完整走一遍这条流水线:从一个关于「支撑与阻力」的朴素直觉出发,写成数学,交给 QuantBuddy 公式引擎 实算二十一年的沪深300,再用《信号与噪声》(Nate Silver)的方法论逐项拷问——这究竟是信号,还是我们骗了自己?
标的 沪深300
区间 2005-04 → [[asof||'…']]
交易日 [[ndays||'…']]
实算引擎 QuantBuddy DSL
数据 实时取数 · 自动重算
实时取数失败:[[err]] (请稍后刷新;公式包凭证或网络可能临时不可用)
正在向 QuantBuddy 生产引擎实时取数并重算 …
⚠ 实时引擎此刻在你的网络下不可达([[snapMsg]] ),下文分析已自动改用页面内嵌的数据快照 (截至 [[asof]])渲染——内容与口径完全一致,只是不含快照之后的新交易日。待网络/防火墙恢复后刷新本页,即可切回实时取数。
原版 RSRS 择时 · 累计净值
[[fx(S.nav1,2)]]×
年化 [[fx(S.c1,1)]]% · 最大回撤 [[fx(S.dd1,1)]]%
优化版(+趋势确认)· 累计净值
[[fx(S.nav2,2)]]×
年化 [[fx(S.c2,1)]]% · 最大回撤 [[fx(S.dd2,1)]]%
买入持有沪深300 · 累计净值
[[fx(S.navb,2)]]×
年化 [[fx(S.cb,1)]]% · 最大回撤 [[fx(S.ddb,1)]]%
当前 RSRS 标准分信号
[[fx(S.curSig,2)]]
[[S.curSig>0.7?'≥0.7 → 偏多':(S.curSig<-0.7?'≤-0.7 → 偏空':'中性区 → 持仓不变')]]
上述数字由页面在你打开的此刻向生产引擎实时取回净值序列、在浏览器内重算得到;底层行情更新后会自动反映。下文逐段拆解它们从何而来、可信到什么程度。
1 异象:把「支撑与阻力」翻译成一个数字
技术分析里最古老的两个词是支撑 与阻力 :价格跌到某处总有人接、涨到某处总有人抛。它们通常只是图上随手画的线,主观、不可复算。RSRS 的聪明之处,是把这对概念压缩成一个可计算的数 。
做法极简:取最近 N=18 个交易日,每天有一个最高价(当日阻力被触及的高度)和最低价(当日支撑被触及的低度)。把这 18 组「最低价 → 最高价」点画在坐标系里,做一条最小二乘回归直线 ,它的斜率 β 就是当下「阻力相对支撑的强度」。
β 大(陡) :支撑略抬,阻力就大幅抬升——买盘强势、上行弹性足;
β 小(平)甚至下行 :支撑在动而阻力顶不上去——上方抛压沉重、动能枯竭。
把 β 读成一句话会更顺:「支撑每抬高一点,阻力会跟着抬高多少?」 ——它度量的是阻力对支撑的响应弹性 。想象每根日 K 线都是一段从最低价(地板)到最高价(天花板)的竖条;18 天看下来,地板与天花板各自上下浮动。回归就在问:当买盘把地板一寸寸顶高时,天花板是顺势让开(β 大、阻力松动),还是被卖盘死死压住(β 小、抛压顽固)。
β 大 · 阻力松动(强势)
支撑抬高 1 元,阻力跟涨约 1.4 元——天花板比地板涨得还快
阻力 最高价 ↑
支撑 最低价 →
支撑 +1
阻力 +1.4
β ≈ 1.4 → 偏多
β 小 · 抛压封顶(弱势)
支撑抬高 1 元,阻力只抬约 0.6 元——每次反弹都被卖盘按住
阻力 最高价 ↑
支撑 最低价 →
支撑 +1
阻力 +0.6
β ≈ 0.6 → 偏空
同一种「最低价 → 最高价」散点,斜率却天差地别。 β 把「天花板让不让路」这件事压缩成一个无量纲、可比较、可下注的数——这就是把支撑阻力 这句技术分析黑话,翻译成统计量的全部秘密。
下图是活的 :拖动滑块选任意一天,左侧实时对该天往前 18 日的最高/最低价做回归,右图显示这条 β 曲线在历史中的位置。这里画出的 β 与后文策略所用的,是同一个引擎算的同一个数(已逐点核对一致)。
交互 · 01 18 日 阻力—支撑 回归,斜率即 β
拖动滑块沿时间轴移动回归窗口;或点「播放」让它自己走。
[[playing?'⏸ 暂停':'▶ 播放']]
[[fmtD(reg.date)]]
读图: 左图每个点是某一天的(最低价, 最高价),斜直线是 18 日 OLS 拟合;标题给出当日 β 与拟合优度 R²。R² 越高,这条「支撑—阻力」关系越紧密、信号越可信——这正是后文「修正标准分」要利用的东西。
为什么是高—低回归,而非别的 用「最高价对最低价」回归,刻意避开了收盘价的噪声与隔夜跳空;斜率天然无量纲、可跨时间比较。这是把一个语义 (支撑阻力强弱)化为统计量 的典型手法——异象研究的第一步,永远是先找到一个能被算出来、且有经济学故事支撑的数。
2 数学:从斜率到一个可下注的信号
原始 β 有两个毛病:它有量纲、随行情中枢漂移,无法直接定阈值。RSRS 用四步把它驯化成一个均值约 0、可比较的信号。
第一步 · 斜率 β(OLS)
对窗口内 $\{(L_i,H_i)\}_{i=1}^{N}$($L$ 最低价、$H$ 最高价),最小二乘斜率:
$$\beta_t=\frac{\sum_{i}(L_i-\bar L)(H_i-\bar H)}{\sum_{i}(L_i-\bar L)^2},\qquad R^2_t=\big[\mathrm{corr}(L,H)\big]^2$$
[[ exp.lsq?'▾':'▸' ]] 讲透这条公式:β 到底在量什么?R² 又是什么?(两层讲解 · 配图 · 自测)
第一层 · 先看懂 β 和 R² 在量什么高中的回归直线就够用
直觉 1 β 就是那条「回归直线」的斜率
先别碰任何高深的东西。把这 18 天里、每一天的(最低价,最高价) 当成一个点,描在坐标纸上:横轴是当天最低价 L、纵轴是当天最高价 H。你会看到一串大致向右上方走的点。RSRS 第一步做的,就是你高中学过的那件事 ——拿尺子穿过这堆点,找一条最贴合的直线 H ≈ α + βL(课本叫「回归直线」「最小二乘」)。
这条线的斜率 β 回答一个特别朴素的问题:「今天最低价要是比平时高 1 块,最高价大约会跟着高几块?」 β 越陡(大),说明低点稍微一抬、高点就冲很远 → 买盘强、上方阻力松动;β 越平(小),说明低点抬了、高点却纹丝不动 → 上方抛压沉重。一个斜率,就把「多空力量」量化成了一个数。
当天最低价 L →
当天最高价 H ↑
回归直线 · 斜率 = β
最低价 +Δ
高价跟涨 ≈ β·Δ
把 18 天的(最低价, 最高价)描成散点,穿过去的那条直线就是回归直线;它的斜率 β 就是「最低价每抬高 1 块,最高价平均跟涨几块」。这正是高中统计里的最小二乘直线。
直觉 2 斜率公式 = 「一起动的劲头 ÷ 自己晃的幅度」
课本里直线斜率是 Δy/Δx,可点云不是一条完美直线,怎么办?办法是对一堆点取平均 。先在图上画一个十字:竖线穿过最低价的平均 L̄、横线穿过最高价的平均 H̄,把图切成四块。看每个点落在哪:
落在右上 / 左下 (都高 或 都低)→ L 与 H 同向 偏离,记一笔 正账 ;
落在左上 / 右下 (一个高一个低)→ 反向 ,记一笔负账 。
把所有点的账加总,就是公式分子 Σ(Lᵢ−L̄)(Hᵢ−H̄)——衡量 L、H 一起涨跌的劲头 (统计里叫协方差 )。分母 Σ(Lᵢ−L̄)² 则是 L 自己晃的幅度 (方差 )。一除,就得到「L 每变 1 单位、H 平均变多少」= β。所以这条吓人的公式,本质就一句话:β = 一起动的劲头 ÷ 自己晃的幅度 。
L̄(最低价均值)
H̄
+ 同高
+ 同低
- 反向
- 反向
蓝点几乎都落在「同高同低」的绿区(记正账),只有少数橙点落在红区(记负账)。绿区压倒红区 → 协方差为正 → β 为正。β 的公式,就是这场记账的总和除以「L 自己的晃动」。
直觉 3 R² 就是「点贴不贴这条线」
光有斜率 β 还不够,得问一句:这条线靠不靠谱? 同样一个 β,点紧贴着线,和点散成一团云,可信度天差地别。R²(取值 0~1)量的就是这个贴合度——其实它正是你高中学过的相关系数 r 的平方 :
R² → 1 :点几乎全压在线上,「低→高」关系铁瓷,这个 β 可信 ;
R² → 0 :点乱成一团云,看不出什么关系,这个 β 基本是蒙的 。
这也解释了 RSRS 第三步为什么要乘 R² :给那些「蒙出来的 β」自动打个折,让看不清的窗口少说话。
点贴线 → R²≈0.9 可信
点散成云 → R²≈0.2 蒙的
两边斜率 β 一样,但左边点紧贴线(关系铁)、右边散成一团云(基本没关系)。R² 就量这个贴合度:1=全在线上,0=纯噪声。
第二层 · 再看深 用线性代数的眼睛重看一遍选读 · 为做量化打底
进阶 1 先把一串数字「打包成一个向量」
上面三步用高中知识已经够用了。但要真正做量化、读懂论文,值得再换一副「线性代数的眼睛」看一遍——你会发现 β 和 R² 其实是同一张几何图 上的两个量。第一步是换个角度看数据:把这 18 天的最高价按顺序排成一列 ,打包成一个向量 h =(H₁, H₂, …, H₁₈)。别被「18 维」吓到——二维向量是平面上一支箭头,三维是空间里一支箭头,18 维只是同一件事、分量多几个而已 ,加法、长度、夹角的规则全照搬。同理把 18 个最低价打包成 ℓ ,再把一列全是 1 的清单打包成 1 (负责式子里的「截距」那一项)。
于是「用最低价去解释最高价」就变成一道向量问题:能不能用 ℓ 和 1 这两支箭头,拼出最接近 h 的那支箭头?
h(18 个最高价)
H₁ H₂ ⋮ H₁₇ H₁₈
打包
2维:平面上一支箭头
3维:空间里一支箭头
18维:同一件事,只是 18 个分量
「加法 / 长度 / 夹角」规则全照搬
把一串有序的数当成一支「高维箭头」——这一步什么都没改变,只是换个角度,让下面的「投影」「夹角」能登场。
进阶 2 拟合 = 在「够得到的平面」里找最近的影子
把所有可能的组合 a·1 + β·ℓ 收集起来,它们铺成一张平面 (数学上叫 1 与 ℓ 张成的子空间)——这就是「只靠 ℓ 和 1,我们能拼出的全部箭头」的范围。真实的 h 通常不在 这张平面上(数据带噪声,最高价不可能被最低价完美解释)。那么「最好的拟合」是谁?就是平面上离 h 最近 的那一点 ĥ ,也就是 h 投在平面上的影子(正交投影) 。从 h 到它影子的那截差距 e = h − ĥ ,就是残差 。
平面:所有 a·1 + β·ℓ 能拼出的箭头
O
h 真实最高价向量
e 残差(最短)
ĥ 影子=最佳拟合
核心图。 蓝箭头 h 是真实数据;绿箭头 ĥ 是它落在平面上的影子(最佳拟合);金色虚线 e 是两者之差,垂直 于平面、长度最短。最小二乘做的,就是找这个影子。
进阶 3 为什么「最近」一定意味着「垂直」
正午把光从头顶打下来,你的影子落在脚边,你和影子的连线垂直于地面 ——这不是巧合,而是「最近」的必然结果。几何事实是:点到平面的最短距离,就是那条垂线段 。任何别的拟合点 h′,它到 h 的连线都是一个直角三角形的斜边 (一条直角边是垂线 e,另一条躺在平面里),而斜边永远比直角边长 。所以「残差最短」⇔「残差垂直于平面」——这就是 Least Squares(最小二乘)里那个「Least」的几何含义。
拟合平面(侧视)
h
ĥ
最短残差(垂线)
h′
换个拟合点 → 斜边,更长
侧视图:垂线(金)比任何斜线(红)都短。所以最佳影子 ĥ 一定落在 h 正下方、残差垂直于平面。
进阶 4 把「垂直」解出来,斜率就是 Cov / Var
「残差垂直于平面」翻成式子,就是 e 同时垂直于 ℓ 和垂直于 1 (与它们的内积都为 0)。其中「垂直于 1」恰好等价于残差的平均值为 0 ;利用这一条,可以先把 h、ℓ 各自减去自己的均值 (中心化)得到 $\tilde h,\tilde\ell$,把碍事的截距 a 一笔消掉。剩下「$e\perp\tilde\ell$」这一个条件解出来,就是那条课本公式:
$$\beta=\frac{\langle\tilde h,\tilde\ell\rangle}{\langle\tilde\ell,\tilde\ell\rangle}=\frac{\operatorname{Cov}(H,L)}{\operatorname{Var}(L)}$$
它一点都不神秘:分子 $\langle\tilde h,\tilde\ell\rangle=\sum_i\tilde H_i\tilde L_i$ 正是协方差 (最高价与最低价「一起波动」的程度),分母 $\langle\tilde\ell,\tilde\ell\rangle=\sum_i\tilde L_i^2$ 正是方差 (最低价自己波动的程度)。所以「协方差 ÷ 方差」这句口诀,背后就是上面那张投影图的代数答案——β 衡量的是:最低价每动一个单位,最高价平均跟动几个单位 。
进阶 5 R² 就是两支箭头夹角的「余弦平方」
同一张图还能直接读出拟合优度 R²。中心化后的 $\tilde h$ 和 $\tilde\ell$ 是两支从原点出发的箭头,它们之间有一个夹角 θ。把 $\tilde h$ 投影到 $\tilde\ell$ 方向上,投影长度占 $\tilde h$ 全长的比例,正好是 cos θ 。再平方,就是大名鼎鼎的:
$$R^2=\cos^2\theta=\frac{\langle\tilde h,\tilde\ell\rangle^2}{\lVert\tilde h\rVert^2\,\lVert\tilde\ell\rVert^2}$$
ℓ̃
h̃
θ
投影长度 = |h̃|·cos θ(沿 ℓ̃ 方向的影子)
cos θ = 影子长度 ÷ 箭头全长。θ 越小,两支箭头越同向,cos θ 越接近 1。
θ 很小(几乎同向) :cos θ → 1,R² → 1。最低价一动,最高价几乎同步跟动,「支撑—阻力」关系紧、这条 β 可信 。
θ ≈ 90°(几乎正交) :cos θ → 0,R² → 0。最高价对最低价基本没有线性响应,这条 β 是噪声 拼出来的、不可信。
这下就懂了第三步为什么要乘 R² :R² 是这条「支撑—阻力」关系方向有多一致 的可信度分;用它给 β 打折,等于让「看不清的窗口」自动少说话。
想把「投影即拟合」这套直觉吃到骨子里,强烈推荐周志成的《線代啟示錄》——〈从线性变换解释最小平方近似〉与〈正交投影:威力强大的线代工具〉两篇,把上面每一步的几何来龙去脉讲得极透。
ccjou.wordpress.com ↗
随堂自测 · 点选项即判分
[[ 'Q'+(qi+1)+'. '+qz.q ]]
[[ String.fromCharCode(65+oi)+'. '+o ]]
[[ picked('lsq',qi)===qz.a?'✓ 答对了。':'✗ 再想想。' ]] [[ qz.why ]]
第二步 · 标准分(滚动 z 标准化)
用过去 $M=600$ 天的 β 均值与标准差,把当前 β 转成 z 分数,消除量纲与漂移:
$$z_t=\frac{\beta_t-\mu_{t}(\beta,M)}{\sigma_{t}(\beta,M)},\qquad M=600$$
[[ exp.zsc?'▾':'▸' ]] 讲透这条公式:z 标准分到底在干什么?为什么是 0.7?(配图 + 自测)
直觉 1 β 的「零刻度」会偷偷移动,没法直接画线
想象你用一把尺子量身高,但这把尺子的零刻度每年都悄悄往上挪一点 。那「175」今年和十年前就不是一回事了。原始 β 正有这个毛病:它带着价格的量纲,而且「常态水平」会随行情中枢慢慢漂移 。如果直接拿一个固定的 0.7 当买卖线,就等于用一把会移动的尺子去量东西 ——同一个 β=1.2,在这个年代可能很高、在那个年代却很平常。
① 原始 β:常态基线在漂移(虚线)
基线一路抬高 → 阈值没法固定
② 标准分 z:钉在 0 附近、刻度统一
+0.7
−0.7
0
同一段 β,标准化前基线一路漂移(上),标准化后被「钉」回 0 附近、幅度也归一(下)——这才敢用一条固定的 ±0.7 去切。
直觉 2 两步校准尺子:减均值、除标准差
公式 z=(β − μ)/σ 看着抽象,其实就是两个动作:
减去最近 600 天的均值 μ :把「当前的常态」对齐到 0,漂移被自动抵消(μ 自己也在逐日滚动更新,永远代表「最近一年多的常态」)。
除以最近 600 天的标准差 σ :把波动幅度换算成「几个标准差 」。从此不同年代、不同波动环境,全部换成同一把无量纲的尺子。
于是 z 有了一个非常好读的含义:「现在的 β,比最近 600 天的常态高(或低)了几个标准差」 。z=+2 就是「比常态高 2 个标准差,相当罕见的强」;z=0 就是「正处在常态当中」。
μ(z=0 常态)
z < −0.7
强空
z > +0.7
强多
常态区
把 β 摊在「标准差」这把尺子上:中间是常态,越往两侧越罕见。±0.7 就是落在两侧、值得动作的偏离。
直觉 3 为什么偏偏是 0.7
在标准化后的刻度上,0.7 大约就是「比常态偏离 0.7 个标准差 」。它不大不小 ,是一个工程折中:阈值太小(比如 0.2),信号会频繁触发、被日常噪声反复打脸;太大(比如 2),信号又太稀、白白错过大段行情。0.7 是原研报在历史数据上权衡「灵敏 vs 稳健 」挑的折中点。
但请记住:0.7 是一个可以被质疑、被回测检验的选择,不是天降神谕。 到了第 7、8 节,我们就会把阈值和窗口长度铺成一张网格,亲眼看看这个选择到底有多稳、换个数会怎样——这正是《信号与噪声》教我们的态度:对每一个参数都保持怀疑,让数据说话。
阈值越大 →(越严格)
← 越小(越灵敏)
太小(0.2)
信号乱跳、被噪声
反复打脸、来回交易
≈0.7 折中
灵敏与稳健的
平衡点
太大(2.0)
信号太稀、迟钝,
白白错过大段行情
谷底≈最划算
把阈值从小往大调:太小被噪声打脸、太大错过行情,「代价」曲线呈 U 形。0.7 落在谷底附近——一个被历史数据选出、且应当被回测检验的折中,不是魔法数字。
随堂自测 · 点选项即判分
[[ 'Q'+(qi+1)+'. '+qz.q ]]
[[ String.fromCharCode(65+oi)+'. '+o ]]
[[ picked('zsc',qi)===qz.a?'✓ 答对了。':'✗ 再想想。' ]] [[ qz.why ]]
这就是 RSRS 标准分 。它在 0 附近波动,大于 +0.7 视为强多、小于 −0.7 视为强空 ——阈值 0.7 是原研报的设定。
第三步 · 修正标准分(用 R² 加权)
低 R² 的窗口意味着「支撑—阻力」关系松散、这个 β 不可信。乘上 R² 给低质量信号自动降权 :
$$z^{\mathrm{rev}}_t = z_t \cdot R^2_t$$
第四步 · 右偏标准分(再乘 β)
再乘原始 β,让指标在趋势方向上更「敢于」表态(右偏):
$$z^{\mathrm{right}}_t = z^{\mathrm{rev}}_t \cdot \beta_t$$
[[ exp.sig?'▾':'▸' ]] 讲透全局视角:这四步合起来,其实是一条「信号处理流水线」(配流程图 + 自测)
直觉 1 退一步:从脏信号里把「异常」滤出来
把镜头拉远,前面四步连起来其实就是工程师天天在做的事:从一段又脏又吵的信号里,把真正有用的那一点点「异常」提炼出来。 原始价格是混着趋势、噪声、隔夜跳空的脏信号;β 像一个特征提取器 ,把每 18 天的形态压成一个标量;后面几步则一站一站地把它「洗干净、调好刻度、按可信度加权、最后设闸」。
脏信号 · 价格(高 / 低 / 收)
混着趋势、噪声与隔夜跳空
β · 特征提取
18 天高~低回归,把一段形态压成一个斜率标量
z · 去直流 + 白化
减 μ(高通,去慢漂移)÷ σ(归一),留下「偏离常态」
×R² · 信噪比加权
估计越干净(R² 越大)权重越大,纯噪声自动压低
±0.7 · 门限检测 + 迟滞
穿过才动作、闸内维持现状 → 输出仓位 0 / 1
同一段价格,依次经过「特征提取 → 去直流白化 → 信噪比加权 → 门限检测」四级处理,最后落成一个干脆的多/空仓位。
直觉 2 逐站对照:每一步对应一个经典器件
z 标准化=去直流(高通)+ 白化 :减掉缓慢漂移的均值 μ,相当于一道高通滤波,滤去随行情中枢的慢漂移、只留真正有信息的偏离;除以 σ 是一维白化(whitening),让不同年代、不同波动环境的幅度可比——这才敢用一条固定 ±0.7 去切。
×R²=信噪比加权 :R²=cos²θ 衡量这次回归有多干净,乘上它就是按信噪比加权,可信的偏离被放大、噪声被抑制。
×β=方向增益 :在趋势方向上放大响应,让指标更「敢表态」(右偏)。
±0.7=带迟滞的门限检测器 :在白化后的信号上设一道闸,穿过才动作、闸内保持,避免在阈值附近来回抖动打脸。
直觉 3 这正是《信号与噪声》的方法论
一句话——RSRS 做的,正是「用一个科学的、数理统计的方法,来捕捉市场的某种『异常』,然后转化成 alpha」。 这也是本页(与一切量化策略)贯穿始终的方法论:先有可证伪的假设,再用数据审判它。 Nate Silver 在《信号与噪声》里反复强调的,正是把信号从噪声里分离出来,并对每一个判断保持谦逊与怀疑。
标准化/白化、内积与夹角这些信号与线代的共同语言,《線代啟示錄》(周志成)有极清楚的几何讲法;配合 Nate Silver《信号与噪声》一起读,能同时长出「数学的手」与「怀疑的脑」。
ccjou.wordpress.com ↗
随堂自测 · 点选项即判分
[[ 'Q'+(qi+1)+'. '+qz.q ]]
[[ String.fromCharCode(65+oi)+'. '+o ]]
[[ picked('sig',qi)===qz.a?'✓ 答对了。':'✗ 再想想。' ]] [[ qz.why ]]
下图把三条信号叠在一起。注意它们的形状差异:标准分最「跳」,修正分在震荡市被 R² 压扁,右偏分在大趋势里被放大。哪一个更好?——先别急着回答,这正是第 7 节要用数据审判的问题。
交互 · 02 RSRS 信号阶梯
切换查看三种信号,虚线是 ±0.7 阈值。
读图: 信号穿越 +0.7(绿虚线)触发满仓、跌破 −0.7(红虚线)触发空仓;位于阈值之间则维持现状(迟滞,减少来回打脸)。纵轴固定在 ±4:极端行情下信号偶尔会冲到 ±5~6(如 2019 年 7 月一度达 −6.2),这类离群点会被裁到边缘,以免日常区间被压扁看不清。
3 QuantBuddy 公式引擎:把数学写成会自己重算的代码
上面的公式若用 Python+pandas 手写,要操心数据对齐、滚动窗口、缺失值、复权、回测状态机……几百行,且每次数据更新都得重跑。QuantBuddy 的思路不同:你声明式地写公式 ,引擎负责取数、对齐、滚动与重算。整套 RSRS 策略就是下面这二十几行——没有循环、没有 for ,每一行是一个具名序列。
# —— 原始量:沪深300 的日内高/低/收 ——
HI = 最高价 (沪深300)
LO = 最低价 (沪深300)
PRICE = 收盘价 (沪深300)
# —— 斜率 β、拟合优度 R² ——
BETA = 回归系数 ("HI","LO",18 )
R2 = 次方 (相关系数 ("HI","LO",18 ),2 )
# —— 标准分 → 修正 → 右偏 ——
RSRS_STD = z分数 ("BETA",600 )
RSRS_REVISED = "RSRS_STD" * "R2"
RSRS_RIGHT = "RSRS_REVISED" * "BETA"
# —— 择时:>0.7 满仓 / <-0.7 空仓,进出场区间是个持仓状态机 ——
POS = 进出场区间 (("RSRS_STD" > 0.7 ), ("RSRS_STD" < - 0.7 ))
DAILY_RET = 缺失填零 (涨跌幅 ("PRICE",1 ))
STRAT_NAV = 累乘 (1 + 昨天 ("POS") * "DAILY_RET") # 昨天持仓×今日收益→净值
BENCH_NAV = 累乘 (1 + "DAILY_RET")
# —— 优化版:叠加 250 日趋势确认(见 §8)——
MA250 = 平均 ("BENCH_NAV",250 )
POS_OPT = "POS" * ("BENCH_NAV" > "MA250")
OPT_NAV = 累乘 (1 + 昨天 ("POS_OPT") * "DAILY_RET")
关键函数各司其职:回归系数 / 相关系数 是滚动 OLS 与相关,z分数 是滚动标准化,进出场区间 是「入场置 1、出场置 0、其余保持」的持仓状态机,累乘 把日收益复利成净值,昨天 实现 T+1 防未来函数。
声明式 = 可信 注册成「公式包」后,得到一对 package_id + signature 凭证;本页就是凭它在你眼前实时取数。底层行情每更新一天,引擎自动增量重算,无需改一行代码——这让「复现」不再是一次性的截图,而是一个始终鲜活、可被任何人重新核验 的对象。
4 从信号到策略:一条信号怎样变成净值
先把两件最容易含糊的事说死:交易什么、怎么交易。
标的=沪深300 指数本身 。复现时直接采用指数的日收益;现实里它对应「买一只跟踪沪深300 的 ETF / 指数基金 」。本策略只做择时、不选股 ——买卖的永远是这一篮子,信号也只来自指数自己的日内最高价 / 最低价。
方向只有「持有」或「空仓」两种 (不做空、不加杠杆)。所谓持仓,就是「这一篮子,今天拿着还是不拿」。
先说总账:策略收益 = 「持仓」与「收益」的内积
回测看似是一台复杂机器,可它的总账 能用第 2 节那把尺子——内积 ——一句话写完。把每个交易日的指数涨跌幅 $r_t$ 排成一支收益向量 $\mathbf r$,把每天的仓位(昨天 定下、今天生效)排成一支只含 0/1 的持仓向量 $\mathbf w$,那么整条策略的累计(对数)收益,就是这两支向量的内积:
$$\mathbf r=(r_1,\dots,r_T),\quad w_t=\mathrm{POS}_{t-1}\in\{0,1\};\qquad \ln\mathrm{NAV}_T=\sum_{t}\ln(1+w_t r_t)\;\approx\;\langle\,\mathbf w,\,\mathbf r\,\rangle=\sum_{t} w_t\,r_t$$
因为 $\mathbf w$ 每个分量非 0 即 1,这里的内积退化成最朴素的一件事——挑日子求和 :$\mathbf w$ 像一张 0/1 掩码 ,把收益向量里持有的那些天 留下相加,空仓日乘 0、自动丢弃。买入持有则是掩码全 1 、一天不落。两者一减,「择时」就被单独逼了出来:
$$R_{\text{买入持有}}=\langle\,\mathbf 1,\,\mathbf r\,\rangle=\sum_t r_t,\qquad \underbrace{\langle\mathbf w,\mathbf r\rangle-\langle\mathbf 1,\mathbf r\rangle}_{\text{择时超额}}=-\,\langle\,\mathbf 1-\mathbf w,\,\mathbf r\,\rangle=-\!\!\!\sum_{t:\,w_t=0}\!\!\! r_t$$
这条等式把择时的全部 价值压成一句话:超额 = 减去「空仓那些天的收益之和」 。所以一个择时策略能不能赚钱,只 取决于一件事——它躲开的日子,整体是不是在跌 :躲对下跌日,超额为正;误躲上涨日,超额为负。下面这张小图,就是这支内积的「逐项明细」。
每天:指数收益 r × 持仓掩码 w(深=留下 / 浅=丢弃)
r
w
1
1
1
0
0
1
1
1
1
0
1
1
⟨ w , r ⟩
= 深色条之代数和
= 策略相对买入持有,
唯一多做的「挑日子」
深色条($w=1$)当天收益被留下计入 策略;浅色条($w=0$)被乘 0、丢弃 。内积 $\langle\mathbf w,\mathbf r\rangle$ 就是这些深色条的代数和。本例特意空仓了三根最深的红条(下跌日),于是「被丢弃项之和」为负,按上面的等式,超额便为正——这正是 RSRS 在真实历史里赚到钱的全部秘密:躲对了大跌 。
再拆机械步骤:每天其实只重复四步
「总账」是一支内积;落到每天的算法,不过是同一件事的四个小步骤:
1
RSRS 标准分 → 持仓指令
每天看连续信号:穿上 +0.7 满仓(w=1 )、跌破 −0.7 空仓(w=0 )、落在 ±0.7 之间维持昨天 (迟滞,不来回折腾)。代码即 进出场区间(…) ,把连续信号翻译成一串 0/1 掩码 $\mathbf w$。
2
用「昨天」的仓位(T+1 防未来)
今天能动用的,是昨天收盘 才算得出的仓位 $w_t=\mathrm{POS}_{t-1}$。信号要等收盘才成形,不能拿今天的信号去赚今天的钱——这正是 昨天("POS") 的职责。
3
当日盈亏 = 仓位 × 当日涨跌
单日贡献 $w_t\,r_t$:持有日如实承接指数涨跌,空仓日乘 0、贡献为零,自动躲过当天行情。把这些日贡献沿时间加总,正是上面那支内积 $\langle\mathbf w,\mathbf r\rangle$。
4
复利累乘 → 净值曲线
把每天 $(1+w_t r_t)$ 连乘 (即 累乘(1 + 昨天(POS)·收益) )长成净值曲线;对照组「买入持有」永远满仓(掩码全 1),用来量出择时究竟有没有加分(见 §5)。
下图三条线:原版 RSRS 优化版 买入持有 。底部阴影标出策略实际持仓 的时段——空白处即空仓避险。
交互 · 03 策略净值(默认线性轴;切「对数轴」更宜看长期复利)
线性轴 对数轴
[[navHold?'✓ ':'']]持仓阴影
[[navJump?'✓ ':'']]标注跳变日
读图: 默认线性轴直观看绝对增长;切到对数轴时,等斜率=等复合增速。原版与优化版长期增速高于买入持有,且在 2008、2015 两次熊市里走平(空仓)而非俯冲。
把上面的内积摊开看,更清楚:下图把每天的指数对数收益 $\ln(1+r_t)$ 按当天是否持仓,分别累加成两条线——持有日累计 (计入策略的项,即 $\langle\mathbf w,\mathbf r\rangle$,因仓位无杠杆,它恰等于 $\ln$ 策略净值)与 空仓日累计 (被掩码丢弃的项,即 $\langle\mathbf 1-\mathbf w,\mathbf r\rangle$)。二者之和恰是买入持有;而 超额 = −(空仓日累计) ——空仓日那条线压得越深,说明躲开的越是下跌,超额越厚。
交互 · 04 内积分解:超额究竟来自哪些天(累计对数收益)
读图: 持有日累计 一路上行,终值 = $\ln$ 策略净值,是策略真正吃到的部分;空仓日累计 长期向下、终值约 [[fx(S.ipCash,2)]] (对数收益)——这正是被躲开的下跌,取相反数即择时超额 ≈ +[[fx(-S.ipCash,2)]] 。换算成倍数:你躲过的那些日子合计把指数乘了约 [[fx(S.ipCashX,2)]]× (<1 即净在跌)。两条相加恰为买入持有 ;它们的差,就是 §5 要单独放大的 alpha。
体检:那些「净值突变」是 bug 还是真事?
肉眼扫净值曲线,会看到一些天净值陡跳 (比如 2024 年 9~10 月那一段 )。量化里这往往是数据脏点的警报,必须查。我把开关打开、逐日核对了日间净值变动:全样本单日波动 ≥6% 的共 20 天 (图中已用绿 /红 圆点标出,悬停可看日期与幅度),每一天都能对应一段真实历史 ——股灾、涨停式反弹、政策驱动的暴涨暴跌——没有一个是脏数据。下表是其中最具代表性的几天:
日期 当日指数涨跌 策略当时持仓? 真实事件
2005-06-08 +8.2% 满仓 股改启动+「千点」政策底,报复性反弹
2007-02-27 −9.2% 满仓 「2·27」全球股灾,沪深300 单日重挫
2008-09-19 +9.3% 满仓 印花税单边征收+汇金增持,全线涨停式反弹
2015-01-19 −7.7% 满仓 券商两融检查,蓝筹集体跳水
2024-09-30 +8.5% 满仓 「9·24」一揽子货币+稳增长政策,节前最后一日暴涨
2024-10-09 −7.0% 满仓 节后预期落差(发布会不及预期),高位回吐
其余十余天集中在 2008 金融海啸 前后那串「涨跌停式」交易日。结论:它们不是脏数据,而是一段段真实历史 。指数那几天本身就是这个幅度,策略恰好持仓,于是如实承接(含 2024 年 9 月的政策驱动暴涨——RSRS 当时早已满仓,完整吃到)。这条曲线通过了体检:它没有偷偷平滑掉风险,也没有虚构收益。能区分「真实的极端」与「错误的极端」,是回测可信度的底线。
方法论提醒 若曾看到「滚动夏普是一条水平直线」之类的图,那才是真 bug——某些库的 夏普 函数会返回全样本单值再广播到每一天。本页的滚动夏普(见 §6)是在浏览器内按真窗口逐日重算的,会随市况起伏。先怀疑工具,再怀疑市场。
5 超额收益:把择时单独拎出来看
净值好看,可能只是因为指数本身涨了。要看择时本身有没有创造价值,得看相对净值 (策略净值 ÷ 基准净值):曲线向上,就是这段时间跑赢了基准;走平或向下,则是在白费力气甚至拖后腿。
交互 · 05 相对基准的超额净值(>1 即跑赢)
读图: 超额几乎全部来自 2008 与 2015 两次空仓避险——台阶式跃升。其余漫长岁月里超额曲线接近走平:这个策略的本事是「躲大跌」,不是「天天比指数聪明」。 认清 alpha 的来源,比知道它有多大更重要。
6 风险:回撤与滚动夏普
真正稳健的择时,价值往往不在更高的收益,而在更浅的回撤 。下图是水下回撤曲线(离前高的距离)。买入持有在 2008 一度回撤逾 −70% ;两个策略把最深伤口收窄到约 −50%/−30%。
图 · 06 水下回撤曲线
读图: 越接近 0 越好。优化版(绿)几乎从不深潜,代价是牛市偶尔踏空——风险与收益的老实交换。
再看滚动一年夏普比率 (过去 244 个交易日的风险调整后收益,逐日重算并年化)。这条线会随市况上下起伏——若它是一条水平直线,那一定是算错了。
图 · 07 滚动 1 年 夏普比率(已修正为真·滚动窗口)
读图: 三条线都在 −2 至 +3.5 间起伏,牛市高、熊市负。全样本夏普:原版≈[[fx(S.sh1,2)]]、优化版≈[[fx(S.sh2,2)]]、买入持有≈[[fx(S.shb,2)]]。
7 信号,还是噪声?——一次过拟合审判
「最危险的不是我们不知道的事,而是我们自以为知道、其实并不成立的事。」— Nate Silver,《信号与噪声》(释义)
Silver 反复强调:一个模型在历史上拟合得越完美,越要警惕它只是记住了噪声 。回测里最常见的自欺,是把参数(窗口 M、阈值 S、信号变体)调到历史最优,然后惊叹于那条完美的净值。
所以我做了两件事来拷问 RSRS。其一 ,把参数铺成一张网格全部跑一遍,看好成绩是一整片高原 还是一根孤立的尖峰 。下面这张三维曲面,是「标准分」策略在不同 M (标准化窗口)× S (阈值)下的全样本夏普——你可以旋转它。
交互 · 08 参数稳健性曲面:夏普 = f(窗口 M, 阈值 S)
[[ varName(v) ]]
3D 曲面 2D 热图
3D:拖动旋转 · 滚轮缩放
⚠ 当前浏览器未启用 WebGL,已自动用 2D 热图(数据完全相同,仅少了立体视角)
读图: 曲面平缓、连成一片高原 ——在很宽的参数范围内夏普都稳定在 0.5–0.7,没有「只有某一组参数才灵」的孤峰。这是稳健 的标志:说明 β 标准化择时是个真实存在的弱信号,而非拟合出来的幻象。
其二 ,更严格的检验——样本外 。把历史切成两半:用 2005–2015 当「样本内」(你能看到的过去),2016 至今 当「样本外」(假装的未来)。如果一组参数只是过拟合,它的样本内夏普会很漂亮、样本外却会坍塌。下图每个点是一组参数:横轴样本内夏普、纵轴样本外夏普。
交互 · 09 样本内 vs 样本外:过拟合无所遁形
读图: 点若落在对角线附近,说明样本内外一致、可信;越靠右下,越是「样本内华丽、样本外打回原形」的过拟合陷阱 。全网格样本外夏普普遍从约 0.8 回落到 0.2–0.4——这才是这个信号在「未来」里的真实成色。买入持有的样本外夏普仅 [[robust.bench.test_sharpe]]。
诚实的结论:开头那串「八九倍 vs 五倍」大部分是样本内的运气 (恰好躲过 2008、2015)。样本外,RSRS 仍能跑赢买入持有、并显著降低回撤,但 alpha 是温和而非惊艳 的。承认这一点,正是把研究和营销区分开的地方。
8 稳健地优化:一个先验的、而非挖来的改进
既然要优化,就得避免掉进第 7 节的陷阱。规矩有三:(1) 不动 RSRS 本身的参数 (M=600、阈值 0.7 照旧);(2) 新增的部件必须有独立的经济学逻辑 ,而不是为了好看挑出来的;(3) 参数取行业公认的先验值,绝不在网格里挑极值。
改进只有一处:趋势确认 。RSRS 擅长抓拐点,但在结构性熊市里它也会频繁给出「抄底」信号——结果接到下落的飞刀。于是加一道闸:只有当 RSRS 看多、且 指数本身站上其 250 日(约一年)均线时,才允许持仓 。两个相互独立的确认同时点头,才下注。250 日是「一年趋势」的标准取值,不是调出来的。
POS_OPT = "POS" × ( 指数净值 > 平均 (指数净值 , 250 ) ) # RSRS看多 且 站上年线
效果(全样本,实时计算):夏普 [[fx(S.sh1,2)]] → [[fx(S.sh2,2)]] ,最大回撤 [[fx(S.dd1,1)]]% → [[fx(S.dd2,1)]]% ,净值 [[fx(S.nav1,2)]]× → [[fx(S.nav2,2)]]×,而持仓时间反而从 [[fx(S.hold1,0)]]% 降到 [[fx(S.hold2,0)]]% ——用更少的暴露拿到更稳的收益。下面这张「参数阶梯」是关键的反过拟合证据 (同样在浏览器内实算):
图 · 10 趋势确认:均线窗口的稳健性阶梯
读图: 横轴是均线窗口(120/200/250 日),柱为样本内夏普、点为样本外夏普。越短的窗口越容易样本内偏高、样本外回落(过拟合气味);MA250 在样本内外都站得住 ——所以选 250,是因为它落在稳健的一侧,而非因为它某个指标最高。
这才叫「不偏离原方法的优化」 没有动 RSRS 的任何核心参数,只叠加了一个有独立逻辑、用公认窗口的趋势闸。它带来的主要是风险的下降 (而非收益的暴涨),且样本外不退化——这是稳健改进的指纹。任何「样本内收益翻倍、样本外查无此事」的优化,都该被扔掉。
把方法装进口袋
RSRS 这趟流水线,浓缩了量化研究的全部要义:
异象要有故事 ——支撑/阻力强弱,先有经济学直觉,再有统计量;
把直觉写成可复算的数学 ,交给会自动重算的引擎,让结论始终鲜活、可被他人核验;
用样本外和参数高原拷问自己 ,把运气和信号分开;
优化要先验、要有逻辑、要看样本外 ——稳健的改进通常体现为风险下降,而非收益神话。
最终我们得到的不是一台印钞机,而是一个诚实的弱信号 :它真实存在、能降低回撤、样本外仍站得住。在充满噪声的市场里,能稳稳抓住一丝真信号,已经是 alpha 的全部含义。