
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
你是否曾经好奇,那些华尔街的量化交易员是如何验证他们的交易策略的?今天我们来拆解一个完整的量化交易案例——基于 Keltner 通道和 SuperTrend 指标的交易系统,看看如何用 Python 对策略进行压力测试和前向优化(Walk-Forward Optimization)。
这篇文章基于美国银行(BAC)从 2000 年到 2026 年的历史数据,涵盖了互联网泡沫、2008 年金融危机、以及多次市场震荡,是一个非常严苛的测试环境。
这个交易系统的逻辑并不复杂:
关键在于,作者没有只测试一组参数,而是进行了数百种参数组合的压力测试,并采用前向优化来验证策略的稳健性。
import pandas as pd
import numpy as np
import yfinance as yf
# 设置股票代码和时间范围
symbol = "BAC" # 美国银行
start_date = "2000-01-01"
end_date = "2026-01-01"
interval = "1d" # 日线数据
# 使用 yfinance 下载数据
df_raw = yf.download(
symbol,
start=start_date,
end=end_date,
interval=interval,
multi_level_index=False
)
print(f"数据量:{len(df_raw)} 条")
print(df_raw.head())Keltner 通道是一种基于波动率的指标,由中轨(EMA)和上下轨(ATR 的倍数)组成。
def calculate_keltner_channel(df, period, multiplier):
"""
计算 Keltner 通道
参数:
df: 包含 OHLC 数据的 DataFrame
period: 计算周期
multiplier: ATR 倍数
返回:
添加了 KC_Lower 列的 DataFrame
"""
df = df.copy()
# 计算典型价格(Typical Price)
tp = (df['High'] + df['Low'] + df['Close']) / 3
# 计算 EMA
ema = tp.ewm(span=period, adjust=False).mean()
# 计算真实波幅(True Range)
tr = pd.concat([
df['High'] - df['Low'],
(df['High'] - df['Close'].shift()).abs(),
(df['Low'] - df['Close'].shift()).abs()
], axis=1).max(axis=1)
# 计算 ATR(平均真实波幅)
atr = tr.rolling(period).mean()
# 计算下轨
df['KC_Lower'] = ema - multiplier * atr
return dfSuperTrend 是一种趋势跟踪指标,能够帮助识别趋势方向。
def calculate_supertrend(df, period, multiplier):
"""
计算 SuperTrend 指标
参数:
df: 包含 OHLC 数据的 DataFrame
period: 计算周期
multiplier: ATR 倍数
返回:
添加了 Upper_Band 和 Lower_Band 列的 DataFrame
"""
df = df.copy()
# 计算中间价
hl2 = (df['High'] + df['Low']) / 2
# 计算真实波幅
tr = pd.concat([
df['High'] - df['Low'],
(df['High'] - df['Close'].shift()).abs(),
(df['Low'] - df['Close'].shift()).abs()
], axis=1).max(axis=1)
# 计算 ATR
atr = tr.rolling(period).mean()
# 计算基础上下轨
upper_basic = hl2 + multiplier * atr
lower_basic = hl2 - multiplier * atr
upper_band = upper_basic.copy()
lower_band = lower_basic.copy()
# 动态调整上下轨
for i in range(1, len(df)):
# 如果前一日收盘价低于上轨,取较小值
if df['Close'].iloc[i-1] <= upper_band.iloc[i-1]:
upper_band.iloc[i] = min(upper_basic.iloc[i], upper_band.iloc[i-1])
# 如果前一日收盘价高于下轨,取较大值
if df['Close'].iloc[i-1] >= lower_band.iloc[i-1]:
lower_band.iloc[i] = max(lower_basic.iloc[i], lower_band.iloc[i-1])
df['Upper_Band'] = upper_band
df['Lower_Band'] = lower_band
return df作者的核心思想是:一个只在完美参数下才能工作的策略,本质上是脆弱的。
因此,他测试了数百种参数组合:
import itertools
import vectorbt as vbt
import matplotlib.pyplot as plt
# 定义参数范围
KC_MULTIPLIER = [2, 3, 4] # Keltner 通道倍数
KC_PERIOD = list(range(16, 25)) # Keltner 通道周期:16-24
SUPERTREND_MULTIPLIER = [2, 3, 4, 5] # SuperTrend 倍数
SUPERTREND_PERIOD = list(range(10, 21)) # SuperTrend 周期:10-20
# 生成所有参数组合
param_sets = list(itertools.product(
KC_MULTIPLIER,
KC_PERIOD,
SUPERTREND_MULTIPLIER,
SUPERTREND_PERIOD
))
print(f"总共 {len(param_sets)} 种参数组合")
# 存储每种参数组合的资金曲线
equity_curves = {}
for kc_mult, kc_period, st_mult, st_period in param_sets:
df = df_raw.copy()
# 计算指标
df = calculate_keltner_channel(df, kc_period, kc_mult)
df = calculate_supertrend(df, st_period, st_mult)
# 定义入场和出场信号
df['entry_signal'] = df['Close'] > df['KC_Lower'] # 价格突破下轨
df['exit_signal'] = (
(df['Close'] <= df['Upper_Band']) &
(df['Close'] >= df['Lower_Band'])
) # 价格回到通道内
# 信号延迟一天执行(模拟真实交易)
entries = df['entry_signal'].shift(1).astype(bool).fillna(False).to_numpy()
exits = df['exit_signal'].shift(1).astype(bool).fillna(False).to_numpy()
# 使用 vectorbt 进行回测
pf = vbt.Portfolio.from_signals(
close=df['Open'], # 以开盘价成交
entries=entries,
exits=exits,
init_cash=100, # 初始资金
fees=0.001, # 手续费 0.1%
slippage=0.002, # 滑点 0.2%
freq='1d'
)
# 保存资金曲线
label = f"KC({kc_period},{kc_mult}) ST({st_period},{st_mult})"
equity_curves[label] = pf.value()任何策略都需要一个基准,最简单的基准就是买入持有(Buy & Hold)。
# 计算买入持有的收益
pf_holding = vbt.Portfolio.from_holding(
df_raw['Open'],
init_cash=100,
freq='1d'
)
buy_hold_curve = pf_holding.value()
# 计算所有策略的平均资金曲线
equity_df = pd.DataFrame(equity_curves)
average_equity = equity_df.mean(axis=1)
# 绘制对比图
plt.figure(figsize=(14, 7))
# 绘制所有策略曲线(淡色)
for curve in equity_curves.values():
plt.plot(curve.index, curve.values, alpha=0.25, linewidth=1, color='gray')
# 绘制平均策略曲线(蓝色粗线)
plt.plot(average_equity.index, average_equity.values,
color='blue', linewidth=2, label='策略平均')
# 绘制买入持有曲线(红色虚线)
plt.plot(buy_hold_curve.index, buy_hold_curve.values,
color='red', linewidth=2, linestyle='--', label='买入持有')
plt.title("压力测试:策略 vs 买入持有")
plt.xlabel("日期")
plt.ylabel("资金曲线")
plt.legend()
plt.grid(True, alpha=0.3)
plt.show()测试结果显示,蓝色的策略平均曲线在 25 年间跑赢了红色的买入持有曲线。
压力测试回答了"策略能否经受参数变化",而前向优化回答了一个更难的问题:
"策略能否在不知道未来的情况下自适应?"
前向优化的框架:
from itertools import product
def run_walk_forward(df, train_years=4, test_years=1):
"""
执行前向优化
参数:
df: 完整的历史数据
train_years: 训练窗口年数
test_years: 测试窗口年数
返回:
每个测试期的结果列表
"""
results = []
max_lb = max(max(KC_PERIOD), max(SUPERTREND_PERIOD)) + 5 # 预留指标计算所需数据
start_year = df.index[0].year
end_year = df.index[-1].year
# 滚动窗口遍历
for train_start in range(start_year, end_year - train_years - test_years + 1):
train_end = train_start + train_years - 1
test_start = train_end + 1
test_end = test_start + test_years - 1
# 获取训练和测试数据的索引
train_data = df[(df.index.year >= train_start) & (df.index.year <= train_end)]
test_data = df[(df.index.year >= test_start) & (df.index.year <= test_end)]
best_perf = -np.inf
best_params = None
# 在训练集上寻找最优参数
for kc_p, kc_m, st_p, st_m in product(KC_PERIOD, KC_MULTIPLIER,
SUPERTREND_PERIOD, SUPERTREND_MULTIPLIER):
# 计算信号并回测
# ...(回测逻辑)
# 记录最佳参数
if performance > best_perf:
best_perf = performance
best_params = (kc_p, kc_m, st_p, st_m)
results.append({
"train_period": f"{train_start}-{train_end}",
"test_period": f"{test_start}-{test_end}",
"params": best_params,
"test_data": test_data
})
print(f"训练期:{train_start}-{train_end} | 测试期:{test_start}-{test_end}")
print(f"最优参数:KC({best_params[0]},{best_params[1]}) ST({best_params[2]},{best_params[3]})")
return results经过前向优化测试,从 2004 年到 2025 年的样本外结果如下:
乍一看,34% 的胜率和 79% 的最大回撤似乎令人担忧。但需要理解这是一个趋势跟踪系统:
这篇文章展示了一个完整的量化策略验证流程:
作者最后总结道:
压力测试证明了稳健性,前向优化证明了真实性。这个系统并不完美,但它是诚实的。
对于学习量化交易的同学来说,这个案例提供了一个很好的模板。记住,一个好的策略不是因为它被优化得很好,而是因为它的结构是合理的。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用
9. Python 量化投资利器:Ridge、Lasso 和 Elastic Net 回归详解
好书推荐