傅里叶变换(特别是快速傅里叶变换 FFT)在量化投资策略开发中可能的应用场景

好的,我们来探讨傅里叶变换(特别是快速傅里叶变换 FFT)在量化投资策略开发中可能的应用场景,并提供相应的 Python 代码示例。

核心思想:

金融时间序列(如股价、交易量)通常包含复杂的模式和噪声。傅里叶变换能将时间序列从时域转换到频域,帮助我们识别数据中隐藏的周期性成分。量化策略可以利用这些周期性信息来做出交易决策、进行风险管理或构建更优的信号。

傅里叶变换在量化投资中的主要用途:

  1. 周期识别: 识别价格波动中的主导周期(如季节性效应、某些经济循环的映射)。
  2. 噪声过滤/趋势平滑: 去除高频噪声,提取更平滑的趋势信号。
  3. 特征工程: 提取频域特征(如主导频率的振幅、相位、能量分布)作为机器学习模型的输入。
  4. 市场状态识别: 市场在不同状态下(如趋势、震荡)可能表现出不同的频率特性。
  5. 配对交易信号增强: 分析两个相关资产价格差的频域特性,寻找均值回归的周期。

以下是几个扩展的场景及 Python 示例:


场景一:识别股价中的主导周期并尝试构建简单交易信号

  • 目标: 找出股价波动中最显著的周期,并基于该周期的相位生成买卖信号(简化示例)。
  • 逻辑:
    1. 获取历史股价数据。
    2. 对价格(或价格变化率)进行 FFT。
    3. 找到(排除直流分量后)振幅最大的频率,认为这是主导周期。
    4. 基于这个周期的相位信息(例如,预测下一个波峰/波谷)生成交易信号。
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.fft import fft, fftfreq

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei'] # 指定默认字体
plt.rcParams['axes.unicode_minus'] = False # 解决保存图像是负号'-'显示为方块的问题

# 1. 获取数据
ticker = 'AAPL' # 以苹果公司为例
data = yf.download(ticker, start='2020-01-01', end='2023-12-31')['Adj Close']

# 2. 计算FFT
N = len(data)
yf_data = data.values # 转换为numpy数组
yf_fft = fft(yf_data)
xf = fftfreq(N, 1) # 假设采样间隔为1天

# 过滤掉直流分量(索引0)和负频率部分
positive_freq_indices = np.where(xf > 0)
xf_pos = xf[positive_freq_indices]
yf_fft_pos = yf_fft[positive_freq_indices]
amplitudes = np.abs(yf_fft_pos)

# 3. 找到主导频率(排除直流分量)
peak_freq_index = np.argmax(amplitudes)
dominant_freq = xf_pos[peak_freq_index]
dominant_period = 1 / dominant_freq if dominant_freq != 0 else np.inf
dominant_amplitude = amplitudes[peak_freq_index]
dominant_phase = np.angle(yf_fft_pos[peak_freq_index]) # 获取相位

print(f"股票 [{ticker}] 数据长度: {N} 天")
print(f"主导频率: {dominant_freq:.4f} (1/天)")
print(f"对应的主导周期: {dominant_period:.2f} 天")
print(f"主导周期的振幅: {dominant_amplitude:.2f}")
print(f"主导周期的相位: {dominant_phase:.2f} 弧度")

# 4. 尝试构建信号 (非常简化的示例)
# 基于主导周期重构一个简化的正弦波,并观察其相位
# 注意:这只是为了演示思路,实际交易信号需要更复杂的逻辑和验证
reconstructed_cycle = dominant_amplitude * np.cos(2 * np.pi * dominant_freq * np.arange(N) + dominant_phase)

# 简单信号逻辑:当重构周期的导数(近似为差分)由负转正时买入,由正转负时卖出
cycle_diff = np.diff(reconstructed_cycle, prepend=0)
signals = pd.Series(index=data.index, dtype=int)
signals[cycle_diff > 0] = 1  # 上升趋势 -> 买入信号
signals[cycle_diff < 0] = -1 # 下降趋势 -> 卖出信号
signals = signals.ffill().fillna(0) # 填充缺失值

# 5. 可视化
plt.figure(figsize=(14, 10))

plt.subplot(3, 1, 1)
plt.plot(data.index, data.values)
plt.title(f'{ticker} 调整后收盘价 (时域)')
plt.ylabel('价格')

plt.subplot(3, 1, 2)
# 只绘制部分频率以突出主导频率
plot_range = int(N * 0.1) # 只看前10%的频率
plt.plot(xf_pos[:plot_range], amplitudes[:plot_range])
plt.scatter(dominant_freq, dominant_amplitude, color='red', s=100, label=f'主导频率 ({dominant_freq:.4f})')
plt.title('快速傅里叶变换 (FFT) 振幅谱 (正频率部分)')
plt.xlabel('频率 (1/天)')
plt.ylabel('振幅')
plt.legend()
plt.grid(True)

plt.subplot(3, 1, 3)
plt.plot(data.index, reconstructed_cycle, label='基于主导频率重构的周期信号 (简化)', color='orange', linestyle='--')
# 在价格图上叠加信号点(可选)
buy_points = data[signals == 1]
sell_points = data[signals == -1]
# plt.scatter(buy_points.index, buy_points.values, marker='^', color='g', label='买入信号点', s=50)
# plt.scatter(sell_points.index, sell_points.values, marker='v', color='r', label='卖出信号点', s=50)
plt.title('重构的主导周期信号 & 简单交易信号演示')
plt.xlabel('日期')
plt.ylabel('重构信号值')
plt.legend()
plt.grid(True)


plt.tight_layout()
plt.show()

# 注意:这只是一个非常基础的演示,实际策略需要考虑:
# 1. 多周期叠加:真实市场通常是多个周期的混合。
# 2. 周期稳定性:金融市场的周期性会随时间变化(非平稳性)。需要使用滚动窗口或短时傅里叶变换(STFT)。
# 3. 信号延迟:基于历史数据计算的周期对未来的预测能力有限。
# 4. 过拟合风险:找到的周期可能是历史数据的巧合。
# 5. 交易成本和滑点。

场景二:使用 FFT 进行噪声过滤,提取价格趋势

  • 目标: 去除股价中的高频噪声,得到一个更平滑的价格曲线,可能用于判断长期趋势。
  • 逻辑:
    1. 对股价序列进行 FFT。
    2. 在频域中,将高频部分的系数置零(或衰减)。高频通常对应快速、短暂的波动(噪声)。
    3. 进行逆傅里叶变换 (IFFT),得到一个只包含低频成分(趋势和主要周期)的平滑序列。
import numpy as np
import pandas as pd
import yfinance as yf
import matplotlib.pyplot as plt
from scipy.fft import fft, ifft, fftfreq

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 1. 获取数据
ticker = 'MSFT' # 以微软为例
data = yf.download(ticker, start='2021-01-01', end='2023-12-31')['Adj Close']
price = data.values
N = len(price)

# 2. 计算FFT
yf_fft = fft(price)
xf = fftfreq(N, 1)

# 3. 噪声过滤:设定一个频率阈值,将高于此阈值的频率系数置零
cutoff_freq_ratio = 0.05 # 保留最低频率的5% (可调整)
cutoff_index = int(N * cutoff_freq_ratio)

# 复制频谱并进行过滤 (注意要同时处理正负频率对称的部分)
yf_fft_filtered = yf_fft.copy()
yf_fft_filtered[cutoff_index:-cutoff_index+1] = 0 # 将高频系数置零

# 4. 计算逆傅里叶变换 (IFFT)
price_filtered = ifft(yf_fft_filtered)
# IFFT 的结果可能是复数,取其实部
price_filtered = np.real(price_filtered)

# 5. 可视化
plt.figure(figsize=(12, 6))
plt.plot(data.index, price, label='原始股价', alpha=0.7)
plt.plot(data.index, price_filtered, label=f'FFT 滤波后 (保留最低 {cutoff_freq_ratio*100:.1f}% 频率)', color='red', linewidth=2)
plt.title(f'{ticker} 股价及 FFT 滤波后的趋势')
plt.xlabel('日期')
plt.ylabel('价格')
plt.legend()
plt.grid(True)
plt.show()

# 思考:
# - cutoff_freq_ratio 如何选择?这是一个关键参数,需要根据具体目标和数据特性调整。
# - 这种滤波方法类似于移动平均,但它是在频域操作的。
# - 可能引入滞后性,特别是 cutoff 设置得较低时。

场景三:提取频域特征用于机器学习模型

  • 目标: 不直接用傅里叶变换做信号,而是提取频域特征,喂给机器学习模型(如分类器预测涨跌,或回归模型预测收益率)。
  • 逻辑:
    1. 在滚动窗口上计算股价(或收益率)序列的 FFT。
    2. 对每个窗口的 FFT 结果,提取有意义的特征:
      • 主导频率的振幅/能量。
      • 某个频率范围内的总能量(例如,低频能量 vs 高频能量)。
      • 谱熵(衡量频谱的复杂度或随机性)。
      • 谱峭度、谱偏度等统计量。
    3. 将这些频域特征与时域特征(如移动平均、波动率)结合,作为机器学习模型的输入。
import numpy as np
import pandas as pd
import yfinance as yf
from scipy.fft import fft, fftfreq
from scipy.stats import skew, kurtosis

# 设置中文显示
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False

# 1. 获取数据
ticker = 'GOOGL' # 以谷歌为例
data = yf.download(ticker, start='2019-01-01', end='2023-12-31')
returns = data['Adj Close'].pct_change().dropna() # 使用日收益率

# 2. 定义特征提取函数 (在一个窗口上)
def extract_fft_features(series):
    N = len(series)
    if N < 2:
        return {'dom_freq_amp': 0, 'low_freq_energy_ratio': 0, 'spectral_entropy': 0}

    yf_fft = fft(series.values)
    xf = fftfreq(N, 1)

    # 只考虑正频率部分 (排除直流分量)
    pos_freq_indices = np.where(xf > 0)[0]
    if len(pos_freq_indices) == 0:
         return {'dom_freq_amp': 0, 'low_freq_energy_ratio': 0, 'spectral_entropy': 0}

    xf_pos = xf[pos_freq_indices]
    amplitudes = np.abs(yf_fft[pos_freq_indices])
    power_spectrum = amplitudes**2

    # 特征1: 主导频率的振幅
    dom_freq_amp = np.max(amplitudes) if len(amplitudes) > 0 else 0

    # 特征2: 低频能量占比 (例如,最低的20%频率)
    low_freq_cutoff_index = int(len(xf_pos) * 0.2)
    total_energy = np.sum(power_spectrum)
    if total_energy > 1e-9: # 避免除以零
        low_freq_energy = np.sum(power_spectrum[:low_freq_cutoff_index])
        low_freq_energy_ratio = low_freq_energy / total_energy
    else:
        low_freq_energy_ratio = 0

    # 特征3: 谱熵 (衡量频谱的平坦度/复杂度)
    # 对功率谱进行归一化,使其和为1,模拟概率分布
    if total_energy > 1e-9:
        normalized_power = power_spectrum / total_energy
        # 防止 log(0)
        non_zero_power = normalized_power[normalized_power > 1e-12]
        spectral_entropy = -np.sum(non_zero_power * np.log2(non_zero_power))
    else:
        spectral_entropy = 0 # 如果没有能量,熵为0

    # 可以添加更多特征,如谱峭度、谱偏度等
    # spectral_skewness = skew(amplitudes)
    # spectral_kurtosis = kurtosis(amplitudes)

    return {
        'dom_freq_amp': dom_freq_amp,
        'low_freq_energy_ratio': low_freq_energy_ratio,
        'spectral_entropy': spectral_entropy
    }

# 3. 在滚动窗口上应用特征提取
window_size = 60 # 例如,使用过去60天的数据计算特征
features = returns.rolling(window=window_size).apply(extract_fft_features, raw=False)

# 将字典列表转换为DataFrame
fft_features_df = pd.DataFrame(features.tolist(), index=returns.index).dropna()

print("计算得到的频域特征 (部分展示):")
print(fft_features_df.tail())

# 4. 可视化某个特征随时间的变化
plt.figure(figsize=(12, 8))

plt.subplot(3, 1, 1)
plt.plot(data['Adj Close'].loc[fft_features_df.index], label=f'{ticker} 收盘价')
plt.title('股价')
plt.legend()
plt.grid(True)


plt.subplot(3, 1, 2)
plt.plot(fft_features_df.index, fft_features_df['dom_freq_amp'], label='主导频率振幅 (滚动窗口)')
plt.title('频域特征: 主导频率振幅')
plt.legend()
plt.grid(True)

plt.subplot(3, 1, 3)
plt.plot(fft_features_df.index, fft_features_df['low_freq_energy_ratio'], label='低频能量占比 (滚动窗口)', color='green')
plt.title('频域特征: 低频能量占比')
plt.legend()
plt.grid(True)

plt.tight_layout()
plt.show()

# 下一步:
# - 将这些 fft_features_df 与其他时域特征合并。
# - 定义目标变量(如未来N天的收益率或涨跌方向)。
# - 使用这些特征训练机器学习模型 (如 RandomForest, XGBoost, LSTM 等)。
# - 进行严格的回测和验证。

通用注意事项和局限性:

  1. 非平稳性: 金融时间序列的统计特性(包括周期性)会随时间改变。直接对整个长序列做 FFT 可能效果不佳。使用滚动窗口 (STFT - Short-Time Fourier Transform) 或更高级的小波变换 (Wavelet Transform) 通常更合适。
  2. 数据预处理: 对原始价格还是收益率进行 FFT?是否需要去趋势?这些选择会影响结果。通常对近似平稳的序列(如收益率)进行分析效果更好。
  3. 频率分辨率 vs 时间分辨率: FFT 窗口越长,频率分辨率越高(能区分更接近的频率),但时间定位越差(无法知道某个频率何时出现/消失)。窗口越短则相反。这是经典的时频不确定性原理。
  4. 边界效应: FFT 假设信号是无限周期的,对于有限窗口的数据,两端会产生跳变,引入虚假频率。可以使用加窗函数(如 Hanning, Hamming 窗)来缓解。
  5. 过拟合: 很容易在历史数据上找到看似显著的周期,但在未来失效。必须结合经济逻辑、其他指标,并进行严格的样本外测试。
  6. 解释性: 单纯的频率特征有时缺乏直观的经济或市场行为解释。
  7. 集成: 傅里叶分析得出的信号或特征,很少单独构成一个完整的策略,通常需要与其他因子、风控模型结合使用。

结论:

傅里叶变换及其变种(如 STFT、小波变换)为量化投资者提供了一套强大的工具,用于从频域视角分析金融时间序列。它可以帮助发现隐藏的周期性、过滤噪声、提取有用的特征。然而,成功应用的关键在于理解其假设和局限性,谨慎选择参数,进行严格的测试,并将其整合到更广泛的量化框架中。纯粹基于历史周期拟合的策略风险很高,将频域分析作为特征工程或信号增强的一部分,通常是更稳健的做法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值