欢迎加入专注于财经数据与量化投研的【数据科学实战】知识星球!在这里,您将获取持续更新的《财经数据宝典》和《量化投研宝典》,这两部宝典相辅相成,为您在量化投研道路上提供明确指引。 《量化投研宝典》精选了业内持续维护且实用性强的开源工具(Backtrader、Qlib、VeighNa等),配合详细教程与代码示例,帮助您快速构建量化策略;《财经数据宝典》则汇集了多年财经数据维护经验,全面介绍从 AKShare、Tushare 到 Wind、iFind 等国内外数据源,并附有丰富的使用技巧。 无论您是量化投资新手还是经验丰富的研究者,星球社区都能帮您少走弯路,事半功倍,共同探索数据驱动的投资世界!
在量化交易的世界里,一个看似优秀的策略在实盘中却可能表现平平。如何验证策略的稳健性?如何避免过度拟合?今天我们将通过一个完整的案例,展示如何使用 Python 构建一个基于 EMA 和 Stochastic 指标的动量策略,并通过前向验证(Walk-Forward Optimization)和蒙特卡洛模拟等方法,全面评估策略的真实表现。
策略核心思想
本文介绍的交易策略结合了两个经典的技术指标:
- Stochastic(随机指标):用于识别超买超卖区域
入场信号
出场信号
完整代码实现
让我们看看完整的策略实现代码:
import numpy as np
import pandas as pd
import yfinance as yf
import vectorbt as vbt
import itertools
# --- Stochastic 和 EMA 计算 ---
def calculate_ema_sto(df, ema_period=20, k_period=14, d_period=3, k_smooth=3):
"""
计算 EMA 和 Stochastic 指标
参数:
- df: 包含 OHLC 数据的 DataFrame
- ema_period: EMA 周期
- k_period: Stochastic K 线周期
- d_period: Stochastic D 线周期
- k_smooth: K 线平滑周期
"""
df = df.copy()
# 计算 EMA
df['EMA'] = df['Close'].ewm(span=ema_period, adjust=False).mean()
# 计算 Stochastic
low_k = df['Low'].rolling(window=k_period).min()
high_k = df['High'].rolling(window=k_period).max()
df['%K_raw'] = 100 * (df['Close'] - low_k) / (high_k - low_k)
df['%K_smooth'] = df['%K_raw'].rolling(window=k_smooth).mean()
df['%D'] = df['%K_smooth'].rolling(window=d_period).mean()
return df
# --- 信号生成器 ---
def generate_signals_ema_sto(df):
"""
根据 EMA 和 Stochastic 生成交易信号
"""
# 入场信号
entry_signal = (
((df['%K_smooth'] > df['%D'])) & # K 线上穿 D 线
(df['%K_smooth'] 30) & # 超卖区
(df['Close'] > df['EMA']) # 价格在 EMA 上方
)
# 出场信号
exit_signal = (
((df[
'%K_smooth'] < df['%D'])) & # K 线下穿 D 线
(df['%K_smooth'] > 70) & # 超买区
(df['Close'] < df['EMA']) # 价格在 EMA 下方
)
return entry_signal, exit_signal
前向验证(Walk-Forward Optimization)
前向验证是避免过度拟合的关键技术。它通过滚动的训练窗口来优化参数,确保策略在未来数据上的表现。
def walk_forward_optimization(df, start_year, end_year):
"""
执行前向验证优化
使用 4 年训练窗口,1 年测试窗口的滚动方式
"""
results = []
param_combinations = generate_parameter_combinations()
for test_year in range(start_year + 4, end_year + 1):
train_start = test_year - 4
train_end = test_year - 1
# 获取训练数据
train_data = df[(df.index.year >= train_start) &
(df.index.year <= train_end)]
best_params = None
best_performance = -np.inf
# 遍历所有参数组合
for params in param_combinations:
ema_p, k_p, d_p, ks_p = params
# 计算指标并生成信号
data = calculate_ema_sto(train_data, ema_p, k_p, d_p, ks_p)
entries, exits = generate_signals_ema_sto(data)
# 回测
pf = vbt.Portfolio.from_signals(
close=data['Open'],
entries=entries.shift(1), # 延迟一根 K 线执行
exits=exits.shift(1),
init_cash=100_000,
fees=0.001, # 0.1% 手续费
slippage=0.002, # 0.2% 滑点
freq='D'
)
performance = pf.total_return()
if performance > best_performance:
best_performance = performance
best_params = params
results.append({
'Year': test_year,
'Best_Params': best_params
})
return pd.DataFrame(results)
策略评估指标
除了基本的收益率,我们还需要更全面的评估指标:
# === 计算风险调整后的收益指标 ===
risk_free_rate = 0.02# 2% 无风险利率
# 获取核心指标
car = portfolio.annualized_return() # 年化收益率
vol = portfolio.annualized_volatility() # 年化波动率
max_dd = abs(portfolio.max_drawdown()) # 最大回撤
# Sharpe 风格的调整收益率(基于波动率)
adj_car_sharpe = (car - risk_free_rate) / vol if vol != 0else np.nan
# Calmar 风格的调整收益率(基于回撤)
adj_car_calmar = car / max_dd if max_dd != 0else np.nan
# 计算市场暴露时间
exposure_percentage = calculate_exposure(df, combined_entries, combined_exits)
蒙特卡洛模拟验证
为了更全面地评估策略的稳健性,我们使用区块自助法(Block Bootstrap)进行蒙特卡洛模拟:
# === 区块自助法蒙特卡洛模拟 ===
n_simulations = 1000
block_size = 5# 每个区块包含 5 天
# 存储模拟结果
equity_paths = []
drawdowns = []
final_returns = []
# 基准收益和回撤
benchmark_return = -0.0919 # 买入持有策略收益
benchmark_drawdown = 0.4545# 买入持有策略最大回撤
# 模拟完整路径
for _ in range(n_simulations):
# 随机选择区块索引
block_indices = np.random.randint(0, len(returns) - block_size + 1,
size=n_blocks)
# 连接选中的区块
sampled_returns = np.concatenate([returns[i:i+block_size]
for i in block_indices])[:n_days]
# 计算权益曲线
equity = init_value * np.cumprod(1 + sampled_returns)
equity_paths.append(equity)
# 计算性能指标
max_dd = np.max(1 - equity / np.maximum.accumulate(equity))
total_return = (equity[-1] / init_value) - 1
drawdowns.append(max_dd)
final_returns.append(total_return)
# 统计超越基准的概率
prob_beat_return = sum(r > benchmark_return for r in final_returns) / n_simulations
prob_beat_dd = sum(d < benchmark_drawdown for d in drawdowns) / n_simulations
print(f"超越基准收益的概率:{prob_beat_return:.1%}")
print(f"超越基准回撤的概率:{prob_beat_dd:.1%}")
实战案例:AMT 股票交易
让我们以美国电塔公司(AMT)为例,展示策略的实际表现:
前向验证结果
| 最优参数 (EMA, K, D, K_smooth) |
---|
| |
| |
| |
|
|
| |
| |
策略表现对比
交易策略:
买入持有策略:
策略相对基准的表现
-
Alpha: 0.0002(日均超额收益 0.02%)
- Beta: 0.3449(策略波动率约为基准的 34%)
蒙特卡洛模拟结果
经过 1000 次模拟:
关键启示
- 参数自适应:不同市场阶段需要不同的参数设置,前向验证帮助我们动态调整
- 风险控制:虽然策略收益提升,但最大回撤改善有限,需要额外的风险管理措施
-
稳健性验证:蒙特卡洛模拟显示策略有较高概率超越基准,证明策略具有统计优势
总结
本文展示了如何构建一个完整的量化交易系统,从策略设计到稳健性验证。通过前向验证避免过度拟合,通过蒙特卡洛模拟评估策略的统计特性。虽然策略在 AMT 股票上取得了正收益,但投资者仍需注意:
- 实盘交易还需考虑更多因素(如资金管理、执行延迟等)
记住,量化交易的核心不是找到"圣杯",而是通过科学的方法管理风险、获取稳定的超额收益。
参考文章
加入专注于财经数据与量化投研的知识星球【数据科学实战】,获取完整研究解析、详细回测框架代码实现和完整策略逻辑实操指南。财经数据与量化投研知识社区
核心权益如下:
- 赠送《财经数据宝典》完整文档,汇集多年财经数据维护经验
- 赠送《量化投研宝典》完整文档,汇集多年量化投研领域经验
- 赠送《PyBroker-入门及实战》视频课程,手把手学习量化策略开发
星球已有丰富内容积累,包括量化投研论文、财经高频数据、 PyBroker 视频教程、定期直播、数据分享和答疑解难。适合对量化投研和财经数据分析有兴趣的学习者及从业者。欢迎加入我们!