希尔伯特变换是信号处理中的经典工具,近年来被一些量化研究者尝试用于金融市场分析,特别是判断市场处于“趋势”还是“震荡”状态。然而,金融时间序列数据具有其独特的复杂性,直接套用传统方法往往会得到失真的结果。本文将探讨其基本思路、传统方法在A股应用中的问题,并重点介绍一种更有效的改进方案——Hilbert-Huang Transform (HHT)。
一、希尔伯特变换基本概念
希尔伯特变换是一种数学变换,它将一个实值信号 x(t) 转换为一个复解析信号 z(t) = x(t) + i * H[x(t)],其中 H[x(t)] 是 x(t) 的希尔伯特变换。从这个解析信号中,我们可以提取出信号的瞬时幅度、瞬时相位和瞬时频率:
- 瞬时相位
φ(t) = arctan(H[x(t)] / x(t)),反映了信号在时刻 t 的相位角。
- 瞬时频率
f(t) = (1/(2π)) * dφ(t)/dt,表示信号在局部时刻的振荡频率。
在理想情况下,如果一个信号是窄带(频率成分集中在单一频率附近)且稳态(统计特性不随时间变化)的,那么它的瞬时频率具有明确的物理意义,能够准确反映信号的局部振荡特性。这一特性使得希尔伯特变换被广泛应用于机械振动、生物医学信号处理等领域。
二、在A股使用希尔伯特变换进行市场状态分析的思路
将希尔伯特变换应用于A股市场,核心思路是通过价格序列的瞬时频率来判断市场状态,进而指导交易策略的选择。具体逻辑如下:
- 计算瞬时频率:对价格序列(或经过预处理的价格序列)进行希尔伯特变换,得到每个时刻的瞬时频率。
- 市场状态分类:
- 当瞬时频率较低且变化缓慢时,表明价格呈现单向运动(趋势行情)。
- 当瞬时频率较高且稳定波动时,表明价格呈现周期性来回摆动(震荡行情)。
- 策略切换:
- 在趋势行情中,采用突破策略(如通道突破、均线跟随)。
- 在震荡行情中,采用均值回归策略(如布林带反转、RSI高抛低吸)。
一些金融技术指标如TA-Lib中的 HT_TRENDMODE 正是基于这一思想:它通过希尔伯特变换计算瞬时周期,比较当前价格与一个周期前的价格,若价格变化显著则判定为趋势,否则为震荡。
三、使用传统希尔伯特变换在A股市场中的问题
尽管上述思路直观,但直接对A股价格序列应用传统希尔伯特变换会面临严重的数学和实际障碍,导致结果失去意义:
- 非窄带信号:股票价格通常包含多种频率成分——长期趋势、中期波动、短期噪音等,频谱较宽,不满足窄带条件。直接进行希尔伯特变换得到的瞬时频率会混杂不同频率的贡献,无法区分市场的主导周期。
- 非稳态特性:价格序列具有趋势、异方差性(波动率聚集)等非稳态特征。例如,牛市中价格持续上升,均值随时间变化,这会导致希尔伯特变换的解析信号无法准确提取局部振荡信息。
- 瞬时频率无物理意义:在上述条件下,计算出的瞬时相位会发生不规则跳跃,瞬时频率可能出现负值或剧烈振荡,无法反映市场的真实周期状态。实践中常表现为相位轨迹杂乱无章,而不是围绕原点规则旋转。
- 结果噪声大:为了强行使用,一些研究者会对数据进行多次平滑滤波,但这会引入滞后并损失关键信息,且无法从根本上解决多频率叠加的问题。
针对传统方法的局限性,学术界提出了Hilbert-Huang Transform (HHT)。它由经验模态分解(EMD) 和希尔伯特变换两部分组成,能够自适应地处理非稳态、多频率信号,在A股市场分析中更具实用价值。
1. 经验模态分解(EMD)
EMD是一种数据驱动的分解方法,它不需要预设基函数,而是通过“筛分”过程将原始信号 X(t) 分解为若干个本征模态函数(IMF) 和一个残余项:X(t) = Σ IMF_i(t) + r_n(t)。每个IMF分量必须满足两个条件:
- 在整个数据段内,极值点个数与过零点个数相等或最多相差一个。
- 在任意点,由局部极大值和极小值定义的包络均值为零。
这些IMF分量代表信号中不同时间尺度的振荡模式,且每个IMF都是窄带和近似稳态的。对于A股价格序列,分解结果通常包括:
- 高频IMF:市场噪音或日内波动(可忽略)。
- 中频IMF:短期市场波动(如周度周期),这正是短线择时所关心的。
- 低频IMF:长期趋势(如月度以上方向)。
2. 对目标IMF进行希尔伯特变换
选取代表短期市场波动的IMF(例如IMF2或IMF3),对该分量进行希尔伯特变换,就可以得到有意义的瞬时频率和相位。因为该IMF已满足窄带稳态条件,其瞬时频率能够准确反映当前市场的主要振荡节奏,相位轨迹也会围绕原点规则旋转。
3. 改进后的优势
- 自适应性:EMD完全根据数据自身的时间尺度分解,无需人工设定滤波器参数,更贴合A股市场的非线性、时变特性。
- 物理意义明确:每个IMF对应一种市场周期成分,便于针对性地构建交易策略。
- 瞬时参数可靠:基于IMF得到的瞬时频率可用于定量划分趋势/震荡状态,例如设置阈值,当瞬时频率低于某值时为趋势,反之为震荡。
- 避免未来数据:尽管经典EMD需要整段数据,但在实际应用中可采用滑动窗口或实时分解算法,满足在线交易需求。
4. 在A股中的应用示例与Python实现
下面通过一个完整的Python示例,演示如何利用HHT(EMD+希尔伯特变换)构建一个简单的市场状态识别与策略切换系统。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import yfinance as yf
from PyEMD import EMD
from scipy.signal import hilbert
import warnings
warnings.filterwarnings('ignore')
# ==================== 1. 数据获取 ====================
# 使用沪深300 ETF (510300.SS) 作为沪深300指数替代,获取最近3年日线数据
ticker = '510300.SS'
data = yf.download(ticker, period='3y', interval='1d', progress=False)
price = data['Close'].dropna()
if len(price) < 200:
raise ValueError("数据量不足,请检查网络或更换数据源")
# ==================== 2. EMD分解 ====================
# 对整个序列进行EMD分解(注意:实际回测需滚动处理,此处仅为演示)
emd = EMD()
IMFs = emd(price.values) # shape: (n_imfs, n_points)
# 选择短期IMF(通常第一个或第二个IMF代表短期波动)
short_term_imf = IMFs[0, :] # 可根据需要改为 IMFs[1, :]
# ==================== 3. 计算瞬时频率 ====================
analytic_signal = hilbert(short_term_imf)
instantaneous_phase = np.unwrap(np.angle(analytic_signal))
# 瞬时频率 = 相位差分 / (2π * Δt),Δt=1天
instantaneous_frequency = np.diff(instantaneous_phase) / (2.0 * np.pi)
freq = pd.Series(instantaneous_frequency, index=price.index[1:])
# 平滑频率(5日移动平均)
freq_smooth = freq.rolling(window=5, min_periods=1).mean()
# ==================== 4. 设定阈值 ====================
# 使用平滑频率的中位数作为阈值(可调)
threshold = freq_smooth.median()
print(f"瞬时频率阈值: {threshold:.4f}")
# ==================== 5. 构建交易信号 ====================
signal = pd.Series(0, index=price.index) # 1:做多, -1:做空, 0:空仓
lookback = 20 # 突破策略的回顾窗口
bb_period = 20 # 布林带周期
bb_std = 2 # 标准差倍数
# 逐日判断(从足够长的起始点开始,避免指标计算缺失)
start_idx = max(lookback, bb_period) + 1
for i in range(start_idx, len(price)):
# 使用前一天的平滑频率判断市场状态(避免未来数据)
current_freq = freq_smooth.iloc[i-1] if i-1 < len(freq_smooth) else threshold
is_trend = current_freq < threshold # 趋势行情
is_range = not is_trend # 震荡行情
# 获取历史数据(截至前一日)
hist_price = price.iloc[:i] # 包括当天之前的所有数据
hist_close = hist_price
hist_high = hist_price
hist_low = hist_price
if is_trend:
# 突破策略:价格突破前N日高点/低点时开仓
recent_high = hist_high.iloc[-lookback-1:-1].max() # 前一天的过去N日高点
recent_low = hist_low.iloc[-lookback-1:-1].min()
if price.iloc[i] > recent_high:
signal.iloc[i] = 1
elif price.iloc[i] < recent_low:
signal.iloc[i] = -1
else:
signal.iloc[i] = 0 # 无信号则空仓
else:
# 均值回归策略:布林带上下轨
sma = hist_close.iloc[-bb_period:].mean()
std = hist_close.iloc[-bb_period:].std()
upper = sma + bb_std * std
lower = sma - bb_std * std
if price.iloc[i] < lower:
signal.iloc[i] = 1
elif price.iloc[i] > upper:
signal.iloc[i] = -1
else:
signal.iloc[i] = 0
# ==================== 6. 回测 ====================
tcost = 0.001 # 单边交易成本0.1%
returns = price.pct_change().fillna(0)
position = signal.shift(1).fillna(0) # 次日开盘以收盘价成交(简化)
strat_returns = position * returns
# 交易成本:每次持仓变动时扣除
trade = position.diff().abs().fillna(0)
strat_returns_net = strat_returns - trade * tcost
cumulative = (1 + strat_returns_net).cumprod()
benchmark = (1 + returns).cumprod()
# 绩效指标
total_ret = cumulative.iloc[-1] - 1
bench_ret = benchmark.iloc[-1] - 1
sharpe = np.sqrt(252) * strat_returns_net.mean() / strat_returns_net.std()
print(f"策略总收益: {total_ret:.2%}")
print(f"基准总收益: {bench_ret:.2%}")
print(f"策略夏普比率: {sharpe:.2f}")
# ==================== 7. 可视化 ====================
fig, axes = plt.subplots(3, 1, figsize=(12, 10))
# 价格与信号
axes[0].plot(price.index, price, label='Price', linewidth=1)
axes[0].plot(price.index[signal==1], price[signal==1], '^', markersize=4, color='g', label='Long')
axes[0].plot(price.index[signal==-1], price[signal==-1], 'v', markersize=4, color='r', label='Short')
axes[0].set_title('Price and Trading Signals')
axes[0].legend()
# 瞬时频率与阈值
axes[1].plot(freq_smooth.index, freq_smooth, label='Smoothed Instantaneous Frequency', linewidth=1)
axes[1].axhline(y=threshold, color='r', linestyle='--', label='Threshold')
axes[1].set_title('Instantaneous Frequency (5-day MA)')
axes[1].legend()
# 净值曲线
axes[2].plot(cumulative.index, cumulative, label='Strategy', linewidth=1)
axes[2].plot(benchmark.index, benchmark, label='Buy & Hold', linewidth=1)
axes[2].set_title('Cumulative Returns')
axes[2].legend()
plt.tight_layout()
plt.show()
流程解析:
- 数据获取与EMD分解:获取沪深300ETF价格数据,并使用EMD分解出本征模态函数。
- 计算瞬时频率:选取代表短期波动的IMF分量,对其进行希尔伯特变换并计算瞬时频率,再进行平滑处理。
- 状态判断与策略生成:基于平滑后的瞬时频率与预设阈值判断市场状态(趋势/震荡),并相应地触发突破策略或均值回归策略,生成交易信号。
- 回测与评估:对生成的信号进行简单的回测,计算策略收益、夏普比率等关键指标,并可视化价格、频率信号及净值曲线。
总结
希尔伯特变换为识别市场状态提供了理论可能,但在A股这样复杂的金融市场中,传统方法因信号非稳态、多频率而失效。通过引入 Hilbert-Huang Transform (HHT),利用经验模态分解先行提取窄带稳态分量,再应用希尔伯特变换,能够有效克服原有限制,为量化交易提供更可靠的市场状态识别工具。这一改进不仅保留了希尔伯特变换的瞬时分析能力,还增强了其对金融时间序列的适应性。当然,实际应用中还需考虑过拟合、参数优化、实时计算效率等问题。希望本文的探讨和代码示例能为你的量化策略研究带来新的启发。欢迎在云栈社区交流更多关于信号处理与量化交易的实战心得。