从"固定参数"到"尺度感知",把交易系统的阈值从死的变成活的
一、开场白:为什么你的拐点系统总在震荡市被来回打脸?
如果你已经搭过一个基于二阶差分(Δ²P)的拐点检测框架,大概率遇到过这个问题:
震荡市里信号满天飞,趋势市里信号迟迟不来。
原因很简单------你在用一个固定阈值去判断"加速度是否显著"。但金融市场的噪声强度是时变的:平静期Δ²P的自然抖动很小,高波动期Δ²P被波动率拖着跑。用一个全局常数去卡,等价于假设"背景噪声恒定"------这在分形世界里是错的。
本文把两个方向串起来给你:
| 方向 | 解决什么问题 | 输出 |
|---|---|---|
| MF-DFA(诊断层) | 市场现在是"均匀粗糙"还是"碎裂模式"? | 谱宽Δα、机制标签 |
| 分形动态阈值(执行层) | 此时此刻,Δ²P的"合理抖动边界"是多少? | θ(t)逐根K线自适应 |
两者关系很简单:MF-DFA是显微镜(看结构有多不均匀),动态阈值是恒温器(让阈值跟着粗糙度呼吸)。
二、方向一:MF-DFA------检测市场是否进入"碎裂模式"
2.1 为什么普通Hurst不够?
Hurst指数H的本质是问:R/S∼τHR/S \sim \tau^HR/S∼τH。但它有个致命弱点------把所有波动当成同一类。
现实中的股价是这样的:
- 有些天走势温和,H≈0.6(持久性强)
- 有些天闪崩/暴涨,局部H可能瞬间塌到0.3以下
普通H把这两种混在一起平均,给你一个看似合理的H≈0.58------但这恰恰掩盖了最有交易价值的信息:粗糙度本身在随时间跳变。MF-DFA要解决的,就是这个。
2.2 从DFA起步:先扒掉趋势再量波动
股价里混着低频趋势(宏观漂移、结构性牛熊),直接算方差会把"趋势"误认为"波动"。DFA的核心思想:先去掉局部趋势,再看残差有多跳。
Step 1:累积和
给定对数价格序列 y(i)=lnPiy(i)=\ln P_iy(i)=lnPi,做累积偏差:
Y(k)=∑i=1k(y(i)−yˉ)Y(k)=\sum_{i=1}^{k}(y(i)-\bar{y})Y(k)=i=1∑k(y(i)−yˉ)
物理直觉:把局部收益累加,让低频趋势变成Y(k)Y(k)Y(k)里的多项式成分,噪声变成"绕趋势线抖的部分"。
Step 2:分段 + 多项式去趋势
把Y(k)Y(k)Y(k)切成长度为τ\tauτ的窗口(通常取τ=2n\tau=2^nτ=2n,如16、32、64......直到N/4N/4N/4):
Yv(j)=Y((v−1)τ+j),j=1,...,τY_v(j)=Y((v-1)\tau+j),\quad j=1,\ldots,\tauYv(j)=Y((v−1)τ+j),j=1,...,τ
对每个窗口做最小二乘多项式拟合(阶数m=1m=1m=1或2):
Yfit,v(j)=a0+a1j+a2j2+⋯Y_{fit,v}(j)=a_0+a_1j+a_2j^2+\cdotsYfit,v(j)=a0+a1j+a2j2+⋯
残差:
Rv(j)=Yv(j)−Yfit,v(j)R_v(j)=Y_v(j)-Y_{fit,v}(j)Rv(j)=Yv(j)−Yfit,v(j)
Step 3:波动函数
每个窗口的均方残差:
Fv(τ)=1τ∑j=1τRv(j)2F_v(\tau)=\sqrt{\frac{1}{\tau}\sum_{j=1}^{\tau}R_v(j)^2}Fv(τ)=τ1j=1∑τRv(j)2
全局波动(q=2q=2q=2时的特例):
FDFA(τ)=1Nseg∑v=1NsegFv(τ)2F_{DFA}(\tau)=\sqrt{\frac{1}{N_{seg}}\sum_{v=1}^{N_{seg}}F_v(\tau)^2}FDFA(τ)=Nseg1v=1∑NsegFv(τ)2
然后看标度:
FDFA(τ)∼τHDFAF_{DFA}(\tau)\sim\tau^{H_{DFA}}FDFA(τ)∼τHDFA
- HDFA≈0.5H_{DFA}\approx0.5HDFA≈0.5 → 基本随机
- >0.55>0.55>0.55 → 趋势持久
- <0.45<0.45<0.45 → 均值回归倾向
到这里就是普通DFA。它已经比裸方差靠谱了------先扒掉局部趋势再量粗糙度。
2.3 进入MF-DFA:从"只看方差"到"看各阶矩"
普通DFA只看L2L_2L2范数(平方和→标准差)。但如果你的粗糙度是多重分形的,不同幅度的波动缩放方式不同 ------小幅波动和大幅波动的τ\tauτ指数不一样。
解法:引入qqq阶矩:
Fq(τ)=1Nseg∑v=1Nseg(Fv(τ))q1/q,q≠0F_q(\tau)=\left\\frac{1}{N_{seg}}\\sum_{v=1}\^{N_{seg}}(F_v(\\tau))\^q\\right^{1/q},\quad q\neq0Fq(τ)= Nseg1v=1∑Nseg(Fv(τ))q 1/q,q=0
q=0q=0q=0时用对数平均的极限形式:
F0(τ)=exp1Nseg∑vlnFv(τ)F_0(\tau)=\exp\left\\frac{1}{N_{seg}}\\sum_v\\ln F_v(\\tau)\\rightF0(τ)=expNseg1v∑lnFv(τ)
然后对每个qqq拟合:
Fq(τ)∼τh(q)F_q(\tau)\sim\tau^{h(q)}Fq(τ)∼τh(q)
这个h(q)h(q)h(q)叫广义Hurst指数。
关键直觉:
| q值 | 在加权什么 | 单分形市场 | 多重分形市场 |
|---|---|---|---|
| q<0q<0q<0(如-2、-5) | 小幅波动被放大 | h(q)h(q)h(q)常数 | h(q)h(q)h(q)上升 |
| q=2q=2q=2 | 普通波动(方差) | h(2)=Hh(2)=Hh(2)=H | 只是其中一个截面 |
| q>0q>0q>0(如+3、+4) | 大幅波动/尖峰被放大 | h(q)h(q)h(q)常数 | h(q)h(q)h(q)下降 |
如果h(q)h(q)h(q)随qqq变化→多重分形;如果h(q)h(q)h(q)是一条水平线→单分形。
2.4 多重分形谱f(α)f(\alpha)f(α)------把"不均匀"画成一张图
把h(q)h(q)h(q)转换成更直观的奇异性谱。做Legendre变换:
τ(q)=qh(q)−1\tau(q)=qh(q)-1τ(q)=qh(q)−1
α=h(q)+qh′(q)\alpha=h(q)+qh'(q)α=h(q)+qh′(q)
f(α)=q(α−h(q))+1f(\alpha)=q(\alpha-h(q))+1f(α)=q(α−h(q))+1
你不需要记住这些公式。你需要抓住的是:
| 谱的性质 | 含义 |
|---|---|
| 谱宽Δα=αmax−αmin\Delta\alpha=\alpha_{max}-\alpha_{min}Δα=αmax−αmin | 越大=越"多重分形"=越不均匀 |
| 谱左偏(f(α)f(\alpha)f(α)左侧更高) | 大幅波动主导(危机/暴涨期常见) |
| 谱右偏 | 小幅波动主导(平静期) |
交易翻译:
- 当Δα\Delta\alphaΔα突然扩张→市场从"均匀粗糙"跳到"碎玻璃模式"→紧止损、降杠杆
- 当Δα\Delta\alphaΔα收窄回稳态→可以重新开趋势策略
- 它不是价格方向信号,它是机制切换的早期温度计
2.5 最小实现的numpy骨架
python
import numpy as np
def poly_detrend(x, order=1):
"""局部多项式去趋势(返回残差)"""
t = np.arange(len(x))
coeffs = np.polyfit(t, x, order)
trend = np.polyval(coeffs, t)
return x - trend
def mfdfa_profile(y, tau_list, q_list, deg=1):
"""
y: log-price 或累积和(通常先用累积和)
tau_list: 窗口长度列表 e.g. [16, 24, 32, 48, 64, ...]
q_list: e.g. [-5, -3, -2, -1, 0, 1, 2, 3, 5]
"""
Y = y
N = len(Y)
Fq = {q: [] for q in q_list}
for tau in tau_list:
if tau >= N:
break
n_seg = N // tau
if n_seg < 2:
continue
Yc = Y[:n_seg * tau]
segs = Yc.reshape(n_seg, tau)
Fvs = []
for seg in segs:
res = poly_detrend(seg, order=deg)
Fv = np.sqrt(np.mean(res**2))
Fvs.append(Fv)
Fvs = np.array(Fvs)
Fvs = Fvs[Fvs > 0]
for q in q_list:
if q == 0:
F_val = np.exp(np.mean(np.log(Fvs)))
else:
F_val = (np.mean(Fvs**q))**(1.0/q)
Fq[q].append(F_val)
return Fq, tau_list[:len(Fq[q_list[0]])]
# ---- 用法示例 ----
P = np.array([100, 102, 105, 109, 108, 106, 107, 108, 111, 115,
114, 112, 110, 113, 117, 120, 118, 116, 114, 115,
119, 122, 121])
y = np.log(P)
Y = np.cumsum(y - y.mean()) # 累积和
tau_list = [8, 12, 16, 20, 24, 28, 32]
q_list = [-5, -3, -2, -1, 0, 1, 2, 3, 5]
Fq, taus = mfdfa_profile(Y, tau_list, q_list, deg=1)
# 粗略看 h(q):对每个q拟合 log(tau)-log(Fq)
for q in [-5, 0, 5]:
log_t = np.log(taus)
log_F = np.log(Fq[q])
h = np.polyfit(log_t, log_F, 1)[0]
print(f"h({q}) ≈ {h:.3f}")
⚠️ 实操提醒 :上面7个点只是演示骨架,真玩最少要几百根K线,τ\tauτ范围也得覆盖一个合理的倍频程。
2.6 MF-DFA对交易框架的意义(落地)
它不是用来生成"买/卖"的,它是用来回答:"此刻我的Δ²P系统,运行在一个单分形环境还是多重分形环境?"
具体用法:
python
if Δα_this_window >> Δα_baseline: # e.g. > 1.5×baseline
regime = "FRACTURED" # → 降杠杆,放宽Δ²P阈值或直接关信号
else:
regime = "UNIFORM" # → Δ²P系统正常运作
这比静态ADX聪明得多------ADX看的是"有没有趋势",MF-DFA看的是 "粗糙度本身是否正在裂变" ,前者是表层,后者是结构层。
三、方向二:把Δ²P阈值从固定值升级为分形动态阈值
这是更直接能喂进交易框架的东西。
3.1 固定阈值到底错在哪?
你的框架里有一句:|Δ²P| > threshold(全局固定值)。
假设市场价格服从分形噪声而非i.i.d.:
- 低波动时段:Δ²P的"自然抖动"很小→固定阈值相对太大→信号迟钝(拐点确认太晚)
- 高波动时段(波动率聚集) :Δ²P被波动率拖着走→固定阈值相对太小→被噪声频繁击穿,假信号炸裂
本质上:固定阈值暗示"Δ²P的背景噪声水平恒定",但分形世界里背景噪声是时变的且尺度耦合的。
3.2 分形缩放告诉我们应该用什么形式
回忆分形标度:σ(τ)∼τH\sigma(\tau)\sim\tau^Hσ(τ)∼τH。Δ²P(差分两次)=相邻ΔP之差,它的"合理抖动幅度"应该正比于局部σ,而不是一个全局常数。
正确的动态阈值长这样:
θ(t)=κ⋅σlocal(t)⋅τscaleHlocal(t)\theta(t)=\kappa\cdot\sigma_{local}(t)\cdot\tau_{scale}^{H_{local}(t)}θ(t)=κ⋅σlocal(t)⋅τscaleHlocal(t)
| 因子 | 是什么 | 怎么估 |
|---|---|---|
| σlocal(t)\sigma_{local}(t)σlocal(t) | 当前局部波动率("现在市场多吵") | rolling std of returns,或ATR/N |
| τscale\tau_{scale}τscale | 你操作的时间尺度 | 常数,取决于你用的K线(日线=1) |
| Hlocal(t)H_{local}(t)Hlocal(t) | 局部持久性 | 滑动窗估H,或用MF-DFA的h(q=2)h(q=2)h(q=2)近似 |
| κ\kappaκ | 置信系数(≈1.5~2.5) | 回测校准,或取历史分位数 |
3.3 工程简化版(不需要MF-DFA也能跑)
严格求Hlocal(t)H_{local}(t)Hlocal(t)每根K线很贵。实战上用两层局部估计替代,效果够用。
第一层:σlocal\sigma_{local}σlocal ------用滚动std或ATR
python
# σ_local: 过去L根K线return的滚动std
sigma_local = pd.Series(r).rolling(L).std()
# 或者用ATR归一化(更稳健对抗跳空)
atr = ATR(high, low, close, L)
sigma_local = atr / close # 相对形式
第二层:HHH代理 ------用ΔP的自相关区分"趋势态"和"碎裂态"
python
# ΔP = diff(close)
rho = roll_autocorr(delta_P, lag=1, window=20)
H_proxy = np.where(np.abs(rho) > 0.15, 0.62, 0.42)
当∣ρΔP∣>ρthr|\rho_{\Delta P}| > \rho_{thr}∣ρΔP∣>ρthr(ΔP自相关显著)→Htrend≈0.62H_{trend}\approx0.62Htrend≈0.62;否则→Hfractured≈0.42H_{fractured}\approx0.42Hfractured≈0.42
组装动态阈值:
python
kappa = 2.0
# 基础版(推荐从这里开始)
threshold_t = kappa * sigma_local
# 精细版:加一个碎裂惩罚
fracture_penalty = 1.0 + 0.8 * (0.5 - H_proxy).clip(0) # H低→阈值放大
threshold_t = kappa * sigma_local * fracture_penalty
3.4 塞回买卖决策树(升级版)
旧版:
if sign(Δ²P) flips AND |Δ²P| > FIXED_THEN → 候选信号
新版:
σ_t = rolling_std(r, L)
H_t = rolling_hurst_proxy() # 或 h(q=2) from mini-DFA
θ_t = κ · σ_t · φ(H_t)
if sign(Δ²P) flips AND |Δ²P| > θ_t:
→ 候选信号
→ 等下一根K线确认(不变)
→ 额外检查:if Δα_t expanded → 否决(碎裂期不追)
效果:
- 震荡市:σ_t小但H_t也降→θ_t自适应收紧/放松,假翻号被σ_t自然压下
- 趋势爆发行情:σ_t飙升→θ_t同步抬高,避免被"正常的剧烈加速度变化"误杀
- 闪崩碎裂:Δα扩→额外否决层兜底
这才是"分形思维"喂进交易系统的正确姿势:不是预测方向,是让阈值呼吸。
四、一张表总结:两个方向的关系
| 维度 | MF-DFA路线 | 动态阈值路线 |
|---|---|---|
| 在测什么 | 波动的多重性(谱宽、机制裂变) | 当前尺度的"合理抖动边界" |
| 输出 | Δα、h(q)、机制标签 | θ(t)逐根K线自适应 |
| 喂给交易 | 开关层:什么时候信信号/什么时候关系统 | 过滤层:单个信号是否超噪声 |
| 计算成本 | 高(多尺度+多项式拟合+q阶矩) | 低(滚动std+自相关+乘因子) |
| 先搞哪个 | 研究/周级别监控 | ✅ 先上这个,明天就能跑 |
五、完整可运行信号模块
下面是一个可直接接入pandas DataFrame(OHLCV)的信号模块:
python
import numpy as np
import pandas as pd
def compute_dynamic_threshold(close, high=None, low=None,
L=20, kappa=2.0, method='std'):
"""
计算分形动态阈值 θ(t)
Parameters
----------
close : pd.Series
收盘价序列
high, low : pd.Series, optional
用于ATR计算
L : int
滚动窗口长度
kappa : float
置信系数
method : str
'std' 或 'atr'
Returns
-------
theta : pd.Series
动态阈值序列
H_proxy : pd.Series
Hurst代理序列
"""
r = np.log(close).diff()
# 第一层:局部波动率
if method == 'std':
sigma_local = r.rolling(L).std()
else: # 'atr'
if high is None or low is None:
raise ValueError("ATR method requires high and low")
tr1 = high - low
tr2 = (high - close.shift()).abs()
tr3 = (low - close.shift()).abs()
tr = pd.concat([tr1, tr2, tr3], axis=1).max(axis=1)
atr = tr.rolling(L).mean()
sigma_local = atr / close
# 第二层:H代理(基于ΔP自相关)
delta_P = close.diff()
rho = delta_P.rolling(L).apply(lambda x: x.autocorr(lag=1), raw=False)
H_proxy = np.where(np.abs(rho) > 0.15, 0.62, 0.42)
H_proxy = pd.Series(H_proxy, index=close.index)
# 组装动态阈值
fracture_penalty = 1.0 + 0.8 * (0.5 - H_proxy).clip(0)
theta = kappa * sigma_local * fracture_penalty
return theta, H_proxy
def compute_signals(df, L=20, kappa=2.0, method='std',
min_confirm_bars=1):
"""
完整的信号生成函数
Parameters
----------
df : pd.DataFrame
必须包含 'close',可选 'high', 'low'
L : int
滚动窗口长度
kappa : float
置信系数
method : str
'std' 或 'atr'
min_confirm_bars : int
确认所需K线数
Returns
-------
df : pd.DataFrame
添加了 'signal' 和 'regime' 列
"""
df = df.copy()
close = df['close']
# 1. 计算Δ²P(加速度)
logP = np.log(close)
delta_P = logP.diff()
delta2_P = delta_P.diff()
# 2. 计算动态阈值
theta, H_proxy = compute_dynamic_threshold(
close,
high=df.get('high'),
low=df.get('low'),
L=L,
kappa=kappa,
method=method
)
# 3. 计算Δα(MF-DFA谱宽)------简化版用H_proxy的波动替代
# 严格MF-DFA需要多尺度计算,这里用H_proxy的rolling std作为代理
delta_alpha = H_proxy.rolling(L).std() * 10 # 经验缩放
# 4. 机制判断
baseline_alpha = delta_alpha.rolling(L*3).mean()
regime = np.where(
delta_alpha > 1.5 * baseline_alpha,
'FRACTURED',
'UNIFORM'
)
# 5. 信号生成
sign_flip = np.sign(delta2_P) != np.sign(delta2_P.shift())
threshold_breach = np.abs(delta2_P) > theta
raw_signal = sign_flip & threshold_breach
# 6. 碎裂期否决
final_signal = raw_signal & (regime != 'FRACTURED')
# 7. 确认机制(等N根K线)
if min_confirm_bars > 1:
confirm = final_signal.rolling(min_confirm_bars).sum() >= min_confirm_bars
final_signal = confirm
df['delta2_P'] = delta2_P
df['theta'] = theta
df['H_proxy'] = H_proxy
df['regime'] = regime
df['signal'] = final_signal.astype(int)
return df
# ---- 使用示例 ----
# df = pd.read_csv('your_data.csv')
# df['date'] = pd.to_datetime(df['date'])
# df = df.set_index('date')
#
# result = compute_signals(df, L=20, kappa=2.0, method='std')
#
# # 查看信号
# buy_signals = result[result['signal'] == 1]
# print(f"共产生 {len(buy_signals)} 个买入信号")
#
# # 查看机制分布
# print(result['regime'].value_counts())
六、收束
分形的真正用处不是让你"看见图案→押注",而是把交易里三个最古老的陷阱------固定止损、固定参数、假设平稳------一个个撬开,换成尺度感知的版本。
- MF-DFA是显微镜:看结构有多不均匀
- 动态阈值是恒温器:让系统跟着结构的粗糙度呼吸
两个一起上,你的Δ²P拐点框架就从"聪明的TA"变成 "尺度感知的滤波器" 。
📌 下一步建议 :先把动态阈值版(
compute_signals)接上真实数据跑一遍,观察θ(t)在不同市场环境下的行为。跑通之后,再把MF-DFA的Δα计算从简化版升级为完整的多尺度版本,作为周级别的机制监控仪表盘。