快速傅里叶变换 (FFT) 分析股价波动 Python 示例
核心思路:股价原始序列是时域信号,通过 FFT 转换到频域,分离长期趋势、中期波动、短期噪声,再逆变换还原平滑后的股价,实现降噪、周期提取。下面示例包含:数据模拟 / 真实股价获取、FFT 频域分析、滤波降噪、绘图可视化。
下面给出滚动窗口 FFT + 周期分析 + 自动生成买卖信号完整版,直接用 A 股真实行情,可直接跑。
特点:
- 滑动窗口滚动傅里叶变换,捕捉实时变化的周期
- 提取主周期,计算相位,给出买入/卖出信号
- 可视化:股价、平滑线、买卖点、周期强度
1. 先安装依赖
pip install numpy pandas matplotlib tushare
2. 完整可运行代码
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tushare as ts
# ---------------------- 配置Tushare ----------------------
ts.set_token('你的Tushare Token')
pro = ts.pro_api()
# ---------------------- 参数设置 ----------------------
STOCK_CODE = "300502.SZ" # 股票代码
START_DATE = "20240101"
END_DATE = "20260531"
WINDOW = 120 # 滚动窗口大小(交易日)
KEEP_NUM = 20 # FFT保留低频分量
TRADE_SIGNAL_THRESHOLD = 0.2 # 相位阈值
# ---------------------- 下载数据 ----------------------
df = pro.daily(ts_code=STOCK_CODE, start_date=START_DATE, end_date=END_DATE)
df = df.sort_values("trade_date").reset_index(drop=True)
df["trade_date"] = pd.to_datetime(df["trade_date"])
price = df["close"].values
dates = df["trade_date"].values
n = len(price)
print(df.head())
# 用于保存结果
smooth_list = np.full(n, np.nan)
signal_buy = np.full(n, np.nan)
signal_sell = np.full(n, np.nan)
period_list = np.full(n, np.nan)
# ---------------------- 滚动窗口FFT ----------------------
for i in range(WINDOW, n):
win_data = price[i-WINDOW:i]
fft_vals = np.fft.fft(win_data)
fft_freq = np.fft.fftfreq(WINDOW, d=1)
amp = np.abs(fft_vals)
# 滤波
fft_filtered = fft_vals.copy()
fft_filtered[KEEP_NUM : WINDOW-KEEP_NUM] = 0
smooth = np.fft.ifft(fft_filtered).real[-1]
smooth_list[i] = smooth
# 找主周期(去掉0频)
amp[0] = 0
main_idx = np.argmax(amp)
main_freq = fft_freq[main_idx]
if main_freq > 0:
main_period = 1 / main_freq
period_list[i] = main_period
else:
main_period = None
# 相位判断买卖:正弦波 0附近买入,π附近卖出
phase = np.angle(fft_vals[main_idx])
if main_period and abs(phase) < TRADE_SIGNAL_THRESHOLD:
signal_buy[i] = price[i]
elif main_period and abs(abs(phase)-np.pi) < TRADE_SIGNAL_THRESHOLD:
signal_sell[i] = price[i]
# ---------------------- 绘图 ----------------------
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(3,1, figsize=(16,12))
# 图1:股价+FFT平滑+买卖点
axes[0].plot(dates, price, label="收盘价", alpha=0.7)
axes[0].plot(dates, smooth_list, label="滚动FFT平滑线", c="red", lw=2)
axes[0].scatter(dates, signal_buy, marker="^", c="green", s=80, label="买入")
axes[0].scatter(dates, signal_sell, marker="v", c="red", s=80, label="卖出")
axes[0].set_title(f"{STOCK_CODE} 滚动FFT周期交易信号")
axes[0].legend()
axes[0].grid(True)
# 图2:实时主周期变化
axes[1].plot(dates, period_list, c="blue")
axes[1].set_title("滚动窗口识别的主波动周期(交易日)")
axes[1].set_ylabel("周期(天)")
axes[1].grid(True)
# 图3:FFT振幅示例(最后一个窗口)
win_data = price[-WINDOW:]
amp = np.abs(np.fft.fft(win_data))
freq = np.fft.fftfreq(WINDOW,1)
mask = freq>=0
axes[2].plot(freq[mask], amp[mask])
axes[2].set_title("最新窗口频域振幅谱")
axes[2].set_xlabel("频率(1/天)")
axes[2].grid(True)
plt.tight_layout()
plt.show()
# ---------------------- 输出统计 ----------------------
valid_periods = period_list[~np.isnan(period_list)]
print("="*50)
print(f"股票 {STOCK_CODE} 分析结果")
print(f"平均主波动周期:{np.mean(valid_periods):.1f} 天")
print(f"最大周期:{np.max(valid_periods):.1f} 天")
print(f"最小周期:{np.min(valid_periods):.1f} 天")
print("="*50)
3. 关键参数解释(直接调参即可实战)
WINDOW=120:滚动窗口,越大看大周期,越小抓短线周期KEEP_NUM=20:FFT保留低频分量,越大波动越多,越小越平滑TRADE_SIGNAL_THRESHOLD=0.2:相位阈值,越小买卖信号越严格
4. 实战用法
- 替换
你的Tushare Token - 更换
STOCK_CODE即可分析任意A股 - 输出:
- 实时平滑股价
- 自动绿三角买入、红倒三角卖出
- 实时变化的主周期(比如20/30/60天)
下面直接给出滚动FFT周期策略 + 完整回测完整版,包含:
买卖信号、持仓、净值曲线、胜率、盈亏比、最大回撤、年化收益,一键出结果。
安装依赖
pip install numpy pandas matplotlib tushare
完整可运行代码(FFT周期策略 + 回测)
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import tushare as ts
# ---------------------- 配置 ----------------------
ts.set_token('你的Tushare Token')
pro = ts.pro_api()
# 策略参数
STOCK_CODE = "300308.SZ" # 可替换
START_DATE = "20240101"
END_DATE = "20260531"
WINDOW = 120 # FFT滚动窗口
KEEP_NUM = 20 # 保留低频分量
PHASE_THRESH = 0.2 # 相位阈值,越小信号越少越稳
INIT_CAPITAL = 100000 # 初始资金
FEE_RATE = 0.0003 # 交易手续费+滑点
# ---------------------- 下载数据 ----------------------
df = pro.daily(ts_code=STOCK_CODE, start_date=START_DATE, end_date=END_DATE)
df = df.sort_values("trade_date").reset_index(drop=True)
df["trade_date"] = pd.to_datetime(df["trade_date"])
price = df["close"].values
dates = df["trade_date"].values
n = len(price)
print(df.tail())
# 信号存储
smooth_list = np.full(n, np.nan)
buy_signal = np.full(n, False)
sell_signal = np.full(n, False)
period_list = np.full(n, np.nan)
# ---------------------- 滚动FFT生成买卖信号 ----------------------
for i in range(WINDOW, n):
win_data = price[i-WINDOW:i]
fft_vals = np.fft.fft(win_data)
fft_freq = np.fft.fftfreq(WINDOW, d=1)
amp = np.abs(fft_vals)
amp[0] = 0 # 去除直流趋势
# 滤波平滑
fft_filtered = fft_vals.copy()
fft_filtered[KEEP_NUM : WINDOW-KEEP_NUM] = 0
smooth = np.fft.ifft(fft_filtered).real[-1]
smooth_list[i] = smooth
# 主周期
main_idx = np.argmax(amp)
main_freq = fft_freq[main_idx]
if main_freq > 0:
period_list[i] = 1 / main_freq
else:
continue
# 相位判断:接近0买入,接近π卖出
phase = np.angle(fft_vals[main_idx])
if abs(phase) < PHASE_THRESH:
buy_signal[i] = True
elif abs(abs(phase) - np.pi) < PHASE_THRESH:
sell_signal[i] = True
# ---------------------- 回测计算 ----------------------
cash = INIT_CAPITAL
shares = 0
net_value = []
position = []
trade_records = []
for i in range(n):
p = price[i]
# 买入
if buy_signal[i] and shares == 0:
shares = cash / p * (1 - FEE_RATE)
cash = 0
trade_records.append({"date": dates[i], "type":"buy", "price":p})
# 卖出
elif sell_signal[i] and shares > 0:
cash = shares * p * (1 - FEE_RATE)
shares = 0
trade_records.append({"date": dates[i], "type":"sell", "price":p})
nv = cash + shares * p
net_value.append(nv)
position.append(1 if shares>0 else 0)
net_value = np.array(net_value)
position = np.array(position)
# 回测指标
final_cap = net_value[-1]
total_return = (final_cap / INIT_CAPITAL) - 1
days = (dates[-1] - dates[0]).astype(int)
annual_return = (1 + total_return) ** (365/days) - 1
# 最大回撤
peak = np.maximum.accumulate(net_value)
drawdown = (net_value - peak) / peak
max_dd = drawdown.min()
# 胜率
trades = pd.DataFrame(trade_records)
win_count = 0
profit_sum = 0
loss_sum = 0
for i in range(0, len(trades)-1, 2):
if i+1 >= len(trades): break
buy_p = trades.loc[i, "price"]
sell_p = trades.loc[i+1, "price"]
ret = (sell_p / buy_p) - 1
if ret > 0:
win_count += 1
profit_sum += ret
else:
loss_sum += ret
total_trades = win_count + (len(trades)//2 - win_count)
win_rate = win_count / total_trades if total_trades>0 else 0
profit_loss_ratio = abs(profit_sum / loss_sum) if loss_sum !=0 else np.inf
# ---------------------- 输出回测结果 ----------------------
print("="*60)
print(f"【FFT周期策略回测结果】{STOCK_CODE}")
print(f"初始资金: {INIT_CAPITAL:.0f}")
print(f"期末资金: {final_cap:.2f}")
print(f"总收益率: {total_return:.2%}")
print(f"年化收益率: {annual_return:.2%}")
print(f"最大回撤: {max_dd:.2%}")
print(f"交易次数: {total_trades}")
print(f"胜率: {win_rate:.2%}")
print(f"盈亏比: {profit_loss_ratio:.2f}")
print("="*60)
# ---------------------- 绘图 ----------------------
plt.rcParams["font.sans-serif"] = ["SimHei"]
plt.rcParams["axes.unicode_minus"] = False
fig, axes = plt.subplots(3,1, figsize=(16,12))
# 图1:股价+平滑线+买卖点
axes[0].plot(dates, price, label="收盘价", alpha=0.7)
axes[0].plot(dates, smooth_list, label="FFT平滑线", c="red", lw=2)
axes[0].scatter(dates[buy_signal], price[buy_signal], marker="^", c="g", s=80, label="买入")
axes[0].scatter(dates[sell_signal], price[sell_signal], marker="v", c="r", s=80, label="卖出")
axes[0].set_title(f"{STOCK_CODE} FFT周期交易信号")
axes[0].legend()
axes[0].grid(True)
# 图2:净值曲线
axes[1].plot(dates, net_value, c="purple", label="策略净值")
axes[1].axhline(y=INIT_CAPITAL, c="gray", ls="--", label="初始资金")
axes[1].set_title("策略净值曲线")
axes[1].legend()
axes[1].grid(True)
# 图3:主周期变化
axes[2].plot(dates, period_list, c="blue")
axes[2].set_title("滚动窗口识别的主波动周期(交易日)")
axes[2].set_ylabel("周期(天)")
axes[2].grid(True)
plt.tight_layout()
plt.show()
输出指标说明
- 总收益率 / 年化收益率:策略整体收益
- 最大回撤:账户最大亏损幅度,越小越稳
- 胜率:盈利交易占比
- 盈亏比:平均盈利 / 平均亏损,>1.5 算优秀
调参实战建议(直接改这4个)
WINDOW=120:窗口越大,抓大周期;窗口60做短线KEEP_NUM=20:越大保留更多波动,信号变多;越小越平滑PHASE_THRESH=0.2:阈值越小,买卖点越少,过滤假信号STOCK_CODE:换股票测试

260

被折叠的 条评论
为什么被折叠?



