☰
几何折叠:用价格路径弯曲度量化行情信号与噪音
2026/10/19 14:29:31 网站建设 项目流程

盘面又开始做布朗运动了。前几天价格在一个窄箱体里来回折返,你盯着MACD金叉追进去,结果刚买完就给你一根阴线。均线、MACD、KDJ这类指标,在单边行情里确实好用,但一旦转入震荡,它们就成了噪音的传声筒。我今天想分享一种我从几何里借来的视角——把价格波动当成一条路径,用路径的弯曲程度去量化市场到底是在“走直线”还是在“原地绕圈”。我把它叫几何折叠,并会把核心计算逻辑和完整源码写出来。这套方法更适合已经被“假突破”折磨过几次、想换个思路做复盘和过滤交易的人,也适合对量化指标设计有兴趣的朋友。它不预测下一根K线,只回答一个问题:当前这段行情,是噪音主导,还是信号主导。

1. 为什么看了一百个指标,还是分不清噪音和信号

1.1 噪音与信号的本质差异

市场数据里其实藏了两个完全不同的成分。一种是带方向性的驱动,比如资金持续流入、基本面预期变化、关键价位突破后的连锁反应,这种成分会有惯性,表现出来就是价格沿着某个方向持续移动。另一种成分则是随机扰动,来自短线交易者的博弈、做市商的库存调整、算法单的噪声冲击,它会推动价格来回抖动,并不具备持续性。

用物理的话说,信号是外力,噪音是分子热运动。没有外力的时候,价格像悬浮在水里的花粉一样做随机的布朗运动;一旦有持续的力作用,它才会沿着受力方向发生漂移。大多数技术指标默认在“有外力”的场景下设计,所以单边行情里表现很好,一旦外力消失、市场回归布朗运动,它们就会反复制造假信号。

我前几年为了过滤噪音试过很多办法。先是用均线多头排列,震荡市里金叉死叉来回打脸;后来用布林带收口判断趋势来临,结果收口之后只是继续横盘,并没有如期变盘。问题不在于指标本身,而在于我没有先回答一个问题:“当前市场是否处在信号主导的状态?”如果连这个问题都没回答,任何入场规则都像是在雾天开车。

1.2 “几何折叠”的独特视角

后来我换了一个思路:不预测方向,只看价格路径本身的几何特征。把一段时间的收盘价序列画成折线,想象你手里拿着一条绳子,把它按价格走势的起伏放在桌面上。这条绳子是被拉成一条直线,还是被反复折叠成一团乱麻,是两种完全不同的状态。

这个观察角度很有用。直线意味着价格行为有“目的性”,每一段移动都在为最终结果贡献净位移;乱麻意味着价格行为更多是“随机性”,总路径很长,但折腾了半天没有离开原点。

我管这种方法叫几何折叠。它不是去猜“接下来涨还是跌”,而是去量化“当前价格轨迹折叠得有多厉害”。折叠得越厉害,噪音占比越高;折叠程度明显下降,信号开始占据主导。这个视角算是绕过了一层屏幕:K线还是那根K线,但你看的不再是涨跌,而是涨跌背后路径的几何复杂度。

2. 几何折叠的核心模型与计算逻辑

2.1 把价格序列变成一条可测量的路径

要把折叠程度量化,第一步是给价格序列建立一个可测量的坐标系统。横轴是时间间隔,相邻两个样本之间距离为1;纵轴是价格,但不能直接用原生价格,必须先做标准化。

原因很直观:如果价格是3000点,日内波动30点和时间前进1天在坐标上的“距离”完全不是一个量级。路径总长会被纵向波动主导,横向时间被压缩到几乎看不到,折叠程度就失真了。我试过直接用裸价格计算,结果折叠指数几乎永远偏高,因为随便一个小波动产生的纵向跳变,都相当于横轴几十个单位的长度。

标准化我推荐滚动z-score:

y = (close - close.rolling(window).mean()) / close.rolling(window).std(ddof=0)

这样处理之后,价格波动一个标准差,在坐标上大约等于一个单位的距离,横向时间与纵向价格就具备了可比性。这种做法还有一个好处:不同的品种、不同的价格区间都会自适应到同一套尺度上,方便横向比较。部分品种价格是几百,部分是几万,如果直接用原生值,折叠指数根本没法跨品种比较。

2.2 折叠指数的构成与计算方式

坐标系建立之后,一段窗口内的价格序列就成了一条由连续线段组成的折线。我用了三个量来刻画这条折线的几何特征。

第一个量是路径总长度,把窗口内所有相邻点之间的欧氏距离累加起来。这个量描述的是价格实际走过的总路程。

L = sum(sqrt(dx * dx + dy * dy))

第二个量是端点直线距离,也就是窗口内第一个点和最后一个点之间的欧氏距离。它描述的是这段行情最终的净位移。

D = sqrt((x_end - x_start)**2 + (y_end - y_start)**2)

第三个量是方向反转次数,记录窗口内价格变动方向符号变化的频率。路径长度能反映整体弯曲幅度,但遇到那种频繁小幅震荡时,它的反应可能不够灵敏,反转次数是从频率维度补充。

把这三个量组合成折叠指数(Fold Index, FI):

CR = L / D FI = (CR - 1) / (CR + 1) RevRate = Rev / (window - 1)

CR是路径-直线比,当价格走成一条直线时CR等于1,折线越弯CR越大。FI是在CR基础上再做了归一化,把取值范围压到0到1之间。0表示一条完美直线,1表示完全随机、净位移接近零的极端折叠状态。RevRate则单独作为一个辅助观察量,用来衡量方向反转的频繁程度。

为什么不直接用CR?因为CR从1到无穷大,画图时不方便设定固定的阈值,而且个别极端窗口会把纵轴拉得特别开,其它窗口的细节全被压扁。归一化到0~1之后,任何品种、任何时间框架下都可以直接看图比较。

2.3 为什么几何指标能分离噪音与信号

把FI和RevRate联合起来看,噪音与信号的特征差异非常明显。

信号主导的行情,价格路径接近一条带斜率的直线,路径长度和端点距离相差不大,FI通常处于0.15以下,反转次数也少。噪音主导的行情,价格来回折叠,路径长度远大于端点位移,FI通常会到0.5以上,反转次数也非常频繁。

还有个更值得关注的地方:行情状态的切换过程。随机序列产生的路径在标准化坐标下有个特点——折叠程度趋向极大化,接近于空间填充曲线。但当趋势信号出现时,路径会产生方向偏好,折叠程度会从高位向低位快速回落。也就是说,FI的“高位转降”往往比“绝对低位”更有意义,它标志着噪音的衰减正在发生。

我一开始也怀疑这会不会只是“均线斜率”换了层皮。后来回看几个案例发现,均线斜率关注的是方向,而且天然滞后;折叠指数关注的是路径效率,它不关心方向朝哪边,只关心路径是不是变得越来越“直”。一个在高位箱体里反复震荡的行情,均线斜率接近零,折叠指数很高;突破后均线斜率开始转正,折叠指数才同步下降。两者有相关性,但折叠指数对状态切换的反应速度明显更直接。

3. Python实现:从原始数据到信号识别

3.1 准备工作:工具与数据

完整的代码我放在下面,环境只需要常用的数据科学生态,pandas、numpy、matplotlib。如果机器上还没有这些库,先执行:

pip install pandas numpy matplotlib

数据方面只需要两列:日期与收盘价。任何自己导出的本地K线CSV都可以,只要字段名统一好就行。我习惯先把数据读进来,转成标准列名,再跑折叠指数计算。

import pandas as pd import numpy as np import matplotlib.pyplot as plt # 读取数据,假设CSV里有date和close两列 df = pd.read_csv("your_data.csv", parse_dates=["date"]) df = df[["date", "close"]].rename(columns={"close": "close"}).reset_index(drop=True)

如果没有现成数据,建议先用下面的模拟数据验证整个流程,等思路通了再换真实行情。

3.2 用Python计算折叠指数

核心函数不复杂,但有两个实现细节需要注意。一是滚动窗口要做到无前视偏差,窗口内只用当前位置及之前的数据;二是前面的窗口不完整时取空值,不能拿一个残缺窗口硬算,否则会污染信号。

def fold_index(close, window=20): close = close.astype(float) # 滚动z-score标准化,保证纵轴与横轴尺度可比 mean = close.rolling(window, min_periods=window).mean() std = close.rolling(window, min_periods=window).std(ddof=0) y = (close - mean) / std n = len(y) fi_series = np.full(n, np.nan) rev_rate_series = np.full(n, np.nan) cr_series = np.full(n, np.nan) for i in range(window - 1, n): seg = y.iloc[i - window + 1 : i + 1].values # 相邻点距离累加,得到路径总长度 dy = np.diff(seg) dx = 1.0 path = np.sum(np.sqrt(dx**2 + dy**2)) # 端点直线距离 end_dist = np.sqrt((seg[-1] - seg[0])**2 + (window - 1)**2) # 方向反转次数 sign_changes = np.sign(dy[1:]) * np.sign(dy[:-1]) rev_count = np.sum(sign_changes < 0) cr = path / end_dist fi = (cr - 1) / (cr + 1) rev_rate = rev_count / (window - 1) fi_series[i] = fi cr_series[i] = cr rev_rate_series[i] = rev_rate return pd.Series(fi_series, index=close.index), pd.Series(rev_rate_series, index=close.index)

这里有个容易被忽略的细节:端点距离的分母我并没有弃掉横轴距离。有的实现为了省事,直接用价格差代替端点距离,这在横轴长度很长的窗口里会明显高估折叠程度。窗口为20时,横轴跨度是19,一个没有净涨跌但波动巨大的窗口,如果只看价格差会陷入除零,而完整欧氏距离能稳定描述“净位移相对于横向时间”到底有多少。

3.3 加一点模拟数据验证直觉

先用模拟数据看看折叠指数是否真的能区分状态。我构造了一段混着“趋势段”和“横盘段”的价格序列,趋势段斜率较大,横盘段斜率很小只靠噪声驱动。

np.random.seed(42) n = 1500 t = np.arange(n) regime = np.where((t // 300) % 2 == 0, 0.08, 0.01) noise = np.random.normal(0, 0.25, n) price = 100 + np.cumsum(regime + noise) close = pd.Series(price) fi, rev_rate = fold_index(close, window=30)

画图时会发现几个有意思的现象。趋势段里FI贴着低位,偶尔有几个尖峰,但整体维持在很低范围;横盘段里FI冲到0.6甚至更高,而且持续不回落。最关键的是状态切换点附近,FI往往会在横盘段末期达到一个高点,然后随着价格开始出现线性移动而快速俯冲,这个俯冲点大约领先于肉眼可见的“突破”出现。

我在实际验证时还叠了一张反转率的图。FI下降但RevRate还在高位的情况,说明路径虽然整体“变直”了一些,但内部仍然充满来回拉锯,这时候的信号还不够稳定。更好的组合是FI和RevRate同步走低,那才是真正干净的趋势阶段。

3.4 把折叠指数变成可交易的信号

只看FI还不够方便,需要把它改造成一个直观的决策信号。最简单的办法是把FI做滚动标准化,得到类似Z-Score的值,然后设定一个阈值。低于负阈值代表折叠程度显著低于近期正常水平,市场处于信号主导状态;高于正阈值代表折叠程度显著偏高,噪音主导,不建议做突破交易。

z_fi = (fi - fi.rolling(50).mean()) / fi.rolling(50).std(ddof=0) # 趋势环境:折叠程度显著偏低 trend_signal = (z_fi < -1.0).astype(int) # 噪音环境:折叠程度显著偏高 noise_signal = (z_fi > 1.0).astype(int)

以这个信号作为过滤条件,再配合一个最简单的方向判断,比如窗口内收盘价相对起始位置的涨跌方向,就构成了一个可回测的规则:

direction = np.where(close.shift(1) < close, 1, -1) # 只有处于趋势环境时才允许开仓 final_signal = trend_signal * direction

注意这是概念验证级别,不是在推荐某个可以直接跑实盘的策略。但用来复盘干净趋势段和无效震荡段的分布,已经足够了。我建议把FI和价格画在同一张图上,用阴影标出噪音环境段,再回看那些阴影段里的K线形态和指标信号,会看到重叠度非常高。

4. 参数调优、常见问题与避坑指南

4.1 窗口大小的选择策略

折叠指数最核心的参数是窗口长度。它相当于你“看多长的一段路”来判断弯曲程度。窗口太小,比如5,路径里只有四段线段,偶然一根大K线就能破坏整个几何结构,导致FI噪音极大,趋势段也会时不时蹦出高值;窗口太大,比如100,确实能把折叠程度磨得很平滑,但状态切换的响应会非常迟钝,等到FI降下来时行情已经走完大半。

我在日线上一般从20起步,最小不低于15,最大不超过60。20的好处是一个月交易日的长度,能够覆盖大多数中线趋势的完整启动段,同时又不至于把转折点磨平。分钟级别我试过5分钟K线,窗口反而会放到50以上,因为分钟级别的噪声密度更高,需要更多样本才能稳定描述路径结构。

调参的时候不要只盯着收益曲线,要把FI图像和K线图叠在一起人工检查。如果趋势段的FI仍然偶尔冲到0.5以上,说明窗口太短或者标准化窗口太短;如果横盘段的FI降到了极低水平,说明窗口太长把噪声给平滑成了“伪趋势”。

4.2 坐标缩放与数据标准化

这个部分是整个方法里最容易被忽略的坑。直接拿原始价格做路径距离计算,在高价品种上会产生一个致命问题:纵向价格波动可以轻易达到横向时间的几十倍,于是FI几乎完全由纵向单点变化驱动,横向时间被边缘化。结果是所有行情都显得“折叠剧烈”,信号和噪音完全无法区分。

滚动z-score是一个相对稳妥的通用解。但z-score里的窗口选择也很要紧,如果z-score窗口与折叠窗口完全一致,有时候会互相纠缠:折叠窗口内的大幅趋势会让标准差变大,进而压低后续标准化后的波动幅度,等于自己打自己。我习惯让标准化窗口大于等于折叠窗口的两倍,比如折叠窗口30,标准化窗口至少60,这样尺度估计更稳定。

如果是做固定品种的长期跟踪,也可以用ATR做标准化,把价格变动除以ATR,效果上也等价于让涨跌单位变成“多少个平均波动幅度”。只不过ATR本身也是滚动计算,同样存在双窗口联动问题,处理起来不如z-score简洁。

4.3 常见问题速查

我在测试过程中踩过几个坑,整理成表格,方便直接对照。

现象可能原因解决办法
折叠指数几乎恒定为高值未做价格标准化,纵轴尺度太大改用滚动z-score或ATR归一化
趋势段仍频繁出现高折叠信号折叠窗口过短适当增大窗口至20以上
信号严重滞后,行情走完才触发折叠窗口太长缩短窗口,或降低滚动Z-Score的均值窗口
不同品种之间阈值不通用品种波动结构差异大用滚动Z-Score替代固定阈值
FI与反转率同步性差窗口内噪声分布不均匀引入3~5根K线的平滑处理

还有一个细节:滚动Z-Score计算时,如果均值窗口和标准差窗口太短,前面一段天然会有很大的不稳定性,信号会来回抖。我在实盘复盘时给Z-Score加了3~5根K线的简单移动平均,虽然会多一点点滞后,但稳定性能接受。

4.4 和其他技术指标搭配

折叠指数最适合的角色是“过滤器”,而不是“触发器”。它的优势在于刻画状态,并不擅长给出精确到某个价的买卖点。所以我通常把折叠指数作为第一层判定,当FI处于趋势环境时才打开交易权限,然后在打开权限的前提下用自己熟悉的突破、均线或者动量规则做具体入场决策。

一个值得注意的组合是折叠指数加成交量。FI从高位下降的时候,只能说明路径开始变直,还不能确认变直背后有真实的资金参与。如果同时看到成交量放大,信号可信度会大幅提升。我复盘过几个原本FI信号有分歧的行情,加了成交量确认之后,假信号明显变少。

反过来我要提醒一点:不要和另一套滞后的过滤指标叠加使用。折叠指数本身已经有窗口滞后,再叠一个更长周期的均线做过滤,整个系统会变得过于迟钝,等它给出同时满足的条件时,行情早就走完一半了。要叠加就叠加响应速度快的信息,比如成交量变化率、盘口主动买卖比例。

5. 写在最后:我实际使用中的几点体会

这几个月我一直在用折叠指数做复盘,最大的变化不是多赚了多少钱,而是心态稳了不少。以前看到一根大阳线突破就冲动入场,现在会先问一句:当前折叠度降下来了吗?如果FI还在0.6以上,突破再漂亮我也不会碰,那大概率只是噪音里的一次随机抖动。

对于刚开始接触这个方法的人,我的建议是先不要急着拿真金白银做交易,把它当作“行情状态指示器”,挂在图表上观察一两周。结合自己熟悉的品种,看看哪些横盘阶段FI持续偏高,哪些趋势启动前FI先出现了俯冲。等你能一眼在图上脑补出FI的形态时,再考虑把它加入实际的决策流程。

我另外做了一个小扩展:不只对价格序列做折叠,也对成交量和波动率序列分别计算折叠指数,然后取三个指数的一致性作为更严格的过滤条件。当价格、量能、波动的折叠度同时走低时,那一段行情往往是最干净的趋势段。代码实现上其实就是对三列数据各跑一遍fold_index,再加一个与运算,但效果比单独看价格FI要好很多,后续有心得再专门写一篇分享。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询