2. 策略回测与参数优化
任务目标
基于历史K线数据,实现双均线策略的回测引擎,并通过网格搜索寻找最优的短期与长期均线参数组合。回测结果包含总收益率、最大回撤与夏普比率,用于量化评估策略表现。
环境与依赖
需要 pandas、numpy。数据使用本地CSV文件(包含 date, close 列),可通过 yfinance 下载或使用任意历史行情数据。
关键完整代码
import pandas as pd
import numpy as np
def load_data(csv_path):
"""加载CSV,按日期升序排列"""
df = pd.read_csv(csv_path, parse_dates=['date'])
df = df.sort_values('date').reset_index(drop=True)
return df
def backtest_ma(df, short_win, long_win, commission=0.0005):
"""
双均线策略回测
规则:短期均线上穿长期均线时全仓买入,下穿时清仓
返回:总收益率、最大回撤、夏普比率(年化)
"""
df = df.copy()
df['short_ma'] = df['close'].rolling(short_win).mean()
df['long_ma'] = df['close'].rolling(long_win).mean()
# 生成信号:1持仓,0空仓
df['signal'] = 0
df.loc[df['short_ma'] > df['long_ma'], 'signal'] = 1
df['position'] = df['signal'].diff().fillna(0)
# 计算每日收益率(含手续费)
df['daily_ret'] = df['close'].pct_change().fillna(0)
df['strategy_ret'] = df['signal'].shift(1) * df['daily_ret'] - commission * df['position'].abs()
# 累计净值
df['equity'] = (1 + df['strategy_ret']).cumprod()
# 总收益率
total_return = df['equity'].iloc[-1] - 1
# 最大回撤
peak = df['equity'].cummax()
drawdown = (df['equity'] - peak) / peak
max_drawdown = drawdown.min()
# 夏普比率(年化,假设252个交易日)
std = df['strategy_ret'].std()
sharpe = 0 if std == 0 else (df['strategy_ret'].mean() / std) * np.sqrt(252)
return total_return, max_drawdown, sharpe
def grid_search(df, short_range, long_range):
"""网格搜索最优参数(按夏普比率排序)"""
results = []
for s in short_range:
for l in long_range:
if s >= l: # 短期必须小于长期
continue
tr, mdd, sharpe = backtest_ma(df, s, l)
results.append({
'short_win': s,
'long_win': l,
'total_return': tr,
'max_drawdown': mdd,
'sharpe': sharpe
})
return pd.DataFrame(results).sort_values('sharpe', ascending=False)
# ========== 最小执行入口 ==========
if __name__ == '__main__':
# 使用示例数据(实际使用时替换为真实CSV路径)
dates = pd.date_range('2020-01-01', periods=500, freq='D')
np.random.seed(42)
close = 100 + np.cumsum(np.random.randn(500) * 0.5)
demo_df = pd.DataFrame({'date': dates, 'close': close})
demo_df.to_csv('demo_data.csv', index=False)
# 执行网格搜索
df = load_data('demo_data.csv')
short_wins = range(5, 21, 2) # 5,7,...,19
long_wins = range(20, 61, 5) # 20,25,...,60
result_df = grid_search(df, short_wins, long_wins)
# 输出最优参数
best = result_df.iloc[0]
print(f"最优参数: short={best['short_win']}, long={best['long_win']}")
print(f"夏普比率: {best['sharpe']:.3f}")
print(f"总收益率: {best['total_return']:.2%}")
print(f"最大回撤: {best['max_drawdown']:.2%}")
print(f"\n参数组合数: {len(result_df)}")
print("Top 5 结果:")
print(result_df.head().to_string(index=False))
运行与验证
运行方式:
python ma_backtest.py
验证方法:
- 程序正常结束并输出最优参数及对应指标。
- 检查输出中
short_win < long_win,且所有指标数值在合理范围(总收益率通常在 -1~5 之间,夏普比率通常 -2~5)。 - 将
demo_data.csv替换为真实历史数据(如沪深300日线),重新运行,观察结果是否合理变化。
通过条件:程序无报错,输出包含5列完整指标的最优参数行,且参数组合数等于 len(short_wins) * len(long_wins) 减去无效组合数(即 s >= l 的组合)。
核心逻辑说明
回测引擎在每日收盘后计算双均线,信号在次日开盘生效(通过 shift(1) 实现),避免未来函数。手续费按每次仓位变化的成交金额双边收取。网格搜索遍历所有短长均线组合,以夏普比率作为参数择优标准,兼顾收益与波动风险。