1. 基于均线交叉的简单调仓策略
任务目标
实现一个基于双均线交叉的调仓策略:当短期均线(如20日)上穿长期均线(如60日)时产生买入信号(金叉),下穿时产生卖出信号(死叉)。策略根据信号在满仓(100%)与空仓(0%)之间切换,并在同一数据集上完成回测,输出策略净值与基准(买入持有)净值的对比结果。
环境与依赖
本案例使用 Python 3.8+,依赖 pandas、numpy。无需安装额外回测框架,全部逻辑在单个脚本中实现。
完整实现代码
import pandas as pd
import numpy as np
# ---------- 1. 生成模拟价格数据(确定性,可复现) ----------
def generate_price_data(n=500, seed=42):
np.random.seed(seed)
returns = np.random.normal(0.0002, 0.015, n)
price = 100 * np.exp(np.cumsum(returns))
dates = pd.date_range('2022-01-01', periods=n, freq='B')
return pd.Series(price, index=dates, name='close')
# ---------- 2. 均线交叉调仓策略 ----------
def moving_average_crossover_strategy(price, short_win=20, long_win=60):
df = pd.DataFrame({'close': price})
df['short_ma'] = df['close'].rolling(short_win).mean()
df['long_ma'] = df['close'].rolling(long_win).mean()
# 信号:1=金叉买入,-1=死叉卖出,0=无操作
df['signal'] = 0
df.loc[df['short_ma'] > df['long_ma'], 'signal'] = 1
df.loc[df['short_ma'] < df['long_ma'], 'signal'] = -1
# 仓位:金叉后满仓,死叉后空仓(使用shift避免未来函数)
df['position'] = df['signal'].replace(0, np.nan).ffill().fillna(0).shift(1).fillna(0)
df['position'] = df['position'].clip(lower=0, upper=1)
return df
# ---------- 3. 回测 ----------
def backtest(df, initial_capital=10000):
df = df.copy()
df['daily_return'] = df['close'].pct_change().fillna(0)
df['strategy_return'] = df['position'] * df['daily_return']
df['strategy_net'] = initial_capital * (1 + df['strategy_return']).cumprod()
df['benchmark_net'] = initial_capital * (1 + df['daily_return']).cumprod()
return df
# ---------- 4. 运行与输出 ----------
if __name__ == '__main__':
price = generate_price_data()
df = moving_average_crossover_strategy(price)
result = backtest(df)
# 输出关键指标
total_return_strategy = result['strategy_net'].iloc[-1] / 10000 - 1
total_return_benchmark = result['benchmark_net'].iloc[-1] / 10000 - 1
print(f"策略累计收益率: {total_return_strategy:.2%}")
print(f"基准累计收益率: {total_return_benchmark:.2%}")
print(f"最终策略净值: {result['strategy_net'].iloc[-1]:.2f}")
print(f"最终基准净值: {result['benchmark_net'].iloc[-1]:.2f}")
print(f"调仓次数: {int((df['signal'].diff() != 0).sum() - 1)}")
# 保存净值序列(可选)
result[['strategy_net', 'benchmark_net']].to_csv('backtest_result.csv')
print("净值数据已保存至 backtest_result.csv")
运行方式
python ma_crossover_backtest.py
验证方法与判定标准
运行脚本后,控制台将输出策略与基准的累计收益率、最终净值及调仓次数。判定通过条件:
- 程序无报错正常退出;
- 输出结果中策略净值曲线与基准净值曲线均从10000起算;
- 调仓次数大于0(说明均线交叉信号确实触发了仓位变化);
- 生成的
backtest_result.csv包含两列净值数据,行数与输入价格数据一致。
由于使用固定随机种子(seed=42),每次运行结果完全一致,便于验证。策略累计收益率与基准的差异取决于均线参数及市场数据特征,本案例中金叉/死叉信号在震荡行情下可能产生连续亏损,这是策略本身的特性而非代码错误。
关键实现说明
- 避免未来函数:仓位信号通过
shift(1)延迟一天生效,即当日收盘确认金叉后,次日开盘才调整仓位,符合真实交易逻辑。 - 信号处理:使用
ffill()将离散信号转换为连续持仓状态,直到下一次反向信号出现才改变仓位。 - 边界处理:前60个交易日因长期均线未形成,仓位默认为0(空仓),不参与交易。
常见问题
- 为什么策略收益可能低于基准?:均线交叉在趋势行情中表现较好,但在震荡市中会频繁产生错误信号,导致高买低卖。这是策略本身的局限,可通过增加过滤条件(如价格高于长期均线才做多)优化。
- 如何修改均线参数?:直接修改
moving_average_crossover_strategy函数中的short_win和long_win参数即可。 - 能否替换为真实行情数据?:将
generate_price_data()替换为从CSV或API读取的收盘价序列即可,后续逻辑无需改动。