1. 搭建最小可运行的回测框架
任务说明
本案例实现一个最小可用的单资产回测框架:给定历史价格序列和一个简单的均线交叉策略,框架按日逐笔模拟交易,输出每日账户权益曲线,并计算累计收益率、年化收益率、最大回撤、夏普比率和胜率五项基础统计指标。代码不涉及滑点、手续费、复利以外的资金管理细节,以清晰展示回测主循环为核心。
核心实现
import numpy as np
import pandas as pd
def generate_sample_data(n=500, seed=42):
"""生成模拟价格序列(几何布朗运动)"""
np.random.seed(seed)
returns = np.random.normal(0.0005, 0.01, n)
price = 100 * np.exp(np.cumsum(returns))
return pd.Series(price, index=pd.date_range('2022-01-01', periods=n, freq='D'))
def moving_average_crossover(price, short=5, long=20):
"""均线交叉策略:短期上穿长期产生买入信号,下穿产生卖出信号"""
signal = pd.Series(0, index=price.index)
ma_short = price.rolling(short).mean()
ma_long = price.rolling(long).mean()
# 1=持有,0=空仓
position = (ma_short > ma_long).astype(int)
signal = position.diff().fillna(0)
return signal, position
def run_backtest(price, signal, initial_cash=10000):
"""最小回测循环:逐日更新持仓与现金"""
cash = initial_cash
shares = 0
equity_curve = []
trades = []
for i in range(len(price)):
today_price = price.iloc[i]
# 先处理信号(用当日收盘价成交)
if signal.iloc[i] == 1 and shares == 0:
shares = cash // today_price
cash -= shares * today_price
trades.append(('BUY', price.index[i], today_price, shares))
elif signal.iloc[i] == -1 and shares > 0:
cash += shares * today_price
trades.append(('SELL', price.index[i], today_price, shares))
shares = 0
# 记录每日总资产
equity_curve.append(cash + shares * today_price)
equity = pd.Series(equity_curve, index=price.index)
return equity, trades
def compute_stats(equity, risk_free_rate=0.02):
"""计算基础统计指标"""
total_return = equity.iloc[-1] / equity.iloc[0] - 1
years = len(equity) / 252
annual_return = (1 + total_return) ** (1 / years) - 1
daily_returns = equity.pct_change().dropna()
sharpe = np.sqrt(252) * (daily_returns.mean() - risk_free_rate / 252) / daily_returns.std()
# 最大回撤
cummax = equity.cummax()
drawdown = (equity - cummax) / cummax
max_drawdown = drawdown.min()
# 胜率(按交易对统计)
win_count = 0
total_trades = 0
buy_prices = []
for t in trades:
if t[0] == 'BUY':
buy_prices.append(t[2])
else:
if buy_prices:
total_trades += 1
if t[2] > buy_prices.pop(0):
win_count += 1
win_rate = win_count / total_trades if total_trades > 0 else 0
return {
'累计收益率': total_return,
'年化收益率': annual_return,
'最大回撤': max_drawdown,
'夏普比率': sharpe,
'胜率': win_rate,
'交易次数': total_trades
}
# 主流程
if __name__ == '__main__':
price = generate_sample_data()
signal, position = moving_average_crossover(price)
equity, trades = run_backtest(price, signal)
stats = compute_stats(equity, trades)
print('=== 回测结果 ===')
for k, v in stats.items():
print(f'{k}: {v:.4f}' if isinstance(v, float) else f'{k}: {v}')
关键设计说明
回测主循环位于run_backtest函数中,采用逐日迭代方式:每日先检查信号,若产生买入信号且当前空仓则全仓买入;若产生卖出信号且当前持仓则清仓。成交价使用当日收盘价,这是最小框架的常见简化。权益曲线每日记录现金与持仓市值之和,完整反映策略净值变化。
均线交叉策略中,position表示每日应持有的状态(1或0),signal是状态变化点(1表示买入日,-1表示卖出日)。由于均线需要历史数据,前20个交易日无信号,属于正常预热期。
验证方法
直接运行脚本,观察控制台输出。判定标准:
- 程序无异常退出,输出五项统计指标及交易次数;
- 累计收益率与
equity.iloc[-1]/equity.iloc[0]-1手算结果一致; - 最大回撤为负数或零,且值不超过1;
- 夏普比率为有限数值(非NaN),胜率在0到1之间。
如需验证逻辑正确性,可将generate_sample_data的随机种子固定后,用相同数据手动计算前几笔交易,与trades列表对比。
运行与验证
# 安装依赖(如未安装)
pip install numpy pandas
# 运行回测
python backtest_minimal.py
预期输出格式如下(数值随随机种子变化,但结构一致):
=== 回测结果 ===
累计收益率: 0.xxxx
年化收益率: 0.xxxx
最大回撤: -0.xxxx
夏普比率: x.xxxx
胜率: 0.xxxx
交易次数: N
将代码保存为backtest_minimal.py后执行即可。若需更换策略,只需修改moving_average_crossover返回的signal序列,其余框架代码无需改动。