1. 基于移动平均线的趋势跟踪策略

任务目标

实现一个双均线(快线MA_short与慢线MA_long)趋势跟踪策略:当快线上穿慢线时产生买入信号,下穿时产生卖出信号。在历史日线数据上完成回测,输出策略净值曲线、交易次数、胜率、最大回撤和年化收益率,用于评估策略表现。

环境与依赖

本案例使用 pandas、numpy 和 matplotlib(仅用于可视化净值曲线)。数据通过 yfinance 获取,若网络受限,可替换为本地CSV数据文件(代码中已注释对应读取方式)。

完整实现代码

import pandas as pd
import numpy as np
import yfinance as yf

# ---------- 1. 获取数据 ----------
ticker = "AAPL"
df = yf.download(ticker, start="2020-01-01", end="2024-01-01", progress=False)
# 若使用本地CSV,替换为: df = pd.read_csv("your_data.csv", index_col=0, parse_dates=True)
df = df[["Close"]].copy()
df.columns = ["price"]

# ---------- 2. 计算双均线信号 ----------
ma_short = 20
ma_long = 50
df["ma_short"] = df["price"].rolling(ma_short).mean()
df["ma_long"] = df["price"].rolling(ma_long).mean()

# 生成信号: 1=持有/买入, 0=空仓/卖出
df["signal"] = np.where(df["ma_short"] > df["ma_long"], 1.0, 0.0)
df["position"] = df["signal"].diff().fillna(0)  # 记录信号变化点

# ---------- 3. 回测引擎 ----------
initial_capital = 100000.0
df["returns"] = df["price"].pct_change().fillna(0)
df["strategy_returns"] = df["signal"].shift(1) * df["returns"]  # 次日执行信号
df["equity"] = initial_capital * (1 + df["strategy_returns"]).cumprod()

# ---------- 4. 绩效指标 ----------
total_return = df["equity"].iloc[-1] / initial_capital - 1
years = (df.index[-1] - df.index[0]).days / 365.25
annual_return = (1 + total_return) ** (1 / years) - 1

# 交易次数与胜率
trades = df[df["position"] != 0]
buy_dates = trades[trades["position"] == 1].index
sell_dates = trades[trades["position"] == -1].index
if len(buy_dates) > len(sell_dates):
    sell_dates = sell_dates.append(pd.DatetimeIndex([df.index[-1]]))  # 强制平仓
trade_returns = []
for b, s in zip(buy_dates, sell_dates):
    if b < s:
        trade_returns.append(df.loc[s, "price"] / df.loc[b, "price"] - 1)
win_rate = np.mean([r > 0 for r in trade_returns]) if trade_returns else 0

# 最大回撤
cummax = df["equity"].cummax()
drawdown = df["equity"] / cummax - 1
max_drawdown = drawdown.min()

# ---------- 5. 输出结果 ----------
print(f"=== {ticker} 双均线策略回测结果 ===")
print(f"回测区间: {df.index[0].date()} 至 {df.index[-1].date()}")
print(f"总收益率: {total_return:.2%}")
print(f"年化收益率: {annual_return:.2%}")
print(f"最大回撤: {max_drawdown:.2%}")
print(f"交易次数: {len(trade_returns)}")
print(f"胜率: {win_rate:.2%}")

# ---------- 6. 净值曲线可视化 ----------
import matplotlib.pyplot as plt
plt.figure(figsize=(10, 5))
plt.plot(df.index, df["equity"], label="Strategy Equity", linewidth=1.5)
plt.plot(df.index, initial_capital * (1 + df["returns"]).cumprod(), label="Buy & Hold", alpha=0.6)
plt.title(f"{ticker} - Dual Moving Average Strategy")
plt.xlabel("Date")
plt.ylabel("Portfolio Value")
plt.legend()
plt.grid(alpha=0.3)
plt.show()

运行方式

  1. 安装依赖:pip install pandas numpy matplotlib yfinance
  2. 将上述代码保存为 dual_ma_backtest.py,在终端执行 python dual_ma_backtest.py

验证方法与通过条件

程序正常运行后,控制台会输出回测区间、总收益率、年化收益率、最大回撤、交易次数和胜率六项指标,同时弹出净值曲线图。通过条件:

  • 输出指标均为合理数值(收益率在-100%以上,回撤在0%以下,胜率在0~100%之间);
  • 净值曲线图包含策略净值(实线)与买入持有基准(半透明线)两条曲线;
  • 策略净值曲线在均线金叉后上升、死叉后回落,与双均线逻辑一致。

若需验证不同参数或标的,修改 ticker、ma_short、ma_long 或日期范围后重新运行即可。

常见问题

  • 数据获取失败:检查网络或改用本地CSV(代码中已注释替换方式),CSV需包含日期索引和Close列。
  • 信号延迟:策略使用次日开盘执行(shift(1)),避免未来函数,这是回测的基本要求。
  • 强制平仓:若最后仍持有仓位,代码自动以末日价格平仓,确保交易次数统计完整。