Python 量化交易入门:用 200 行代码实现一个回测引擎

7 阅读3分钟

量化交易听起来高大上,其实核心就三件事:拿数据、生信号、模拟跑

本文用 Python 从零实现一个简单但完整的回测引擎,包括:策略信号生成、资金管理、交易模拟、绩效评估。代码可以直接跑。

整体架构

市场数据(yfinance) → 信号生成(策略) → 回测引擎(模拟交易) → 绩效报告

我们要实现的东西:

  1. 数据获取:用 yfinance 拉免费行情
  2. 策略信号:均线交叉(最经典的入门策略)
  3. 回测引擎:模拟交易,算收益
  4. 绩效评估:Sharpe、最大回撤、胜率

Step 1:获取市场数据

import yfinance as yf
import pandas as pd

def fetch_data(ticker: str, period: str = "2y") -> pd.DataFrame:
    """获取股票历史数据"""
    df = yf.download(ticker, period=period, auto_adjust=True)
    df.columns = [c.lower() for c in df.columns]
    return df[['open', 'high', 'low', 'close', 'volume']]

df = fetch_data("AAPL")
print(f"数据量: {len(df)} 天")
print(df.tail())

yfinance 是免费的,不需要任何 API key。

Step 2:均线交叉策略

最经典的趋势跟踪策略——快线上穿慢线买入,下穿卖出:

import numpy as np

def moving_average_crossover(df, fast=10, slow=30):
    df = df.copy()
    df['ma_fast'] = df['close'].rolling(fast).mean()
    df['ma_slow'] = df['close'].rolling(slow).mean()
    df['signal'] = np.where(df['ma_fast'] > df['ma_slow'], 1, 0)
    df['position'] = df['signal'].diff()
    return df.dropna()

df = moving_average_crossover(df)
buy_count = (df['position'] == 1).sum()
sell_count = (df['position'] == -1).sum()
print(f"买入: {buy_count} 次, 卖出: {sell_count} 次")

为什么从均线交叉开始? 因为它足够简单,逻辑清晰,而且是很多复杂策略的基础。

Step 3:回测引擎

核心部分——模拟真实交易:

class Backtest:
    def __init__(self, df, initial_capital=100000, commission=0.001):
        self.df = df.copy()
        self.initial_capital = initial_capital
        self.commission = commission
    
    def run(self):
        capital = self.initial_capital
        shares = 0
        trades = []
        equity_curve = []
        
        for i, row in self.df.iterrows():
            if row['position'] == 1 and shares == 0:
                shares = int(capital * 0.95 / row['close'])
                cost = shares * row['close'] * (1 + self.commission)
                capital -= cost
                trades.append({'date': i, 'action': 'BUY', 'price': row['close'], 'shares': shares})
            elif row['position'] == -1 and shares > 0:
                revenue = shares * row['close'] * (1 - self.commission)
                capital += revenue
                trades.append({'date': i, 'action': 'SELL', 'price': row['close'], 'shares': shares})
                shares = 0
            total = capital + shares * row['close']
            equity_curve.append({'date': i, 'equity': total})
        
        self.trades = pd.DataFrame(trades)
        self.equity = pd.DataFrame(equity_curve).set_index('date')
        return self

bt = Backtest(df).run()
print(f"交易次数: {len(bt.trades)}")
print(f"最终资金: ${bt.equity['equity'].iloc[-1]:,.0f}")
print(f"收益率: {(bt.equity['equity'].iloc[-1] / 100000 - 1) * 100:.1f}%")

几个关键细节:

  • 手续费:不算手续费的回测都是自欺欺人
  • 95% 仓位:留现金避免买入时资金不够
  • 记录净值曲线:后面算回撤要用

Step 4:绩效评估

def calculate_metrics(equity_df):
    returns = equity_df['equity'].pct_change().dropna()
    total_return = equity_df['equity'].iloc[-1] / equity_df['equity'].iloc[0] - 1
    days = len(equity_df)
    annual_return = (1 + total_return) ** (252 / days) - 1
    excess_returns = returns - 0.04 / 252
    sharpe = np.sqrt(252) * excess_returns.mean() / returns.std()
    peak = equity_df['equity'].expanding().max()
    drawdown = (equity_df['equity'] - peak) / peak
    max_drawdown = drawdown.min()
    return {
        'total_return': f"{total_return:.1%}",
        'annual_return': f"{annual_return:.1%}",
        'sharpe_ratio': f"{sharpe:.2f}",
        'max_drawdown': f"{max_drawdown:.1%}",
        'trading_days': days
    }

metrics = calculate_metrics(bt.equity)
for k, v in metrics.items():
    print(f"{k}: {v}")

Sharpe > 1 还不错,> 2 很好,< 0 亏钱。最大回撤反映你要承受的最大痛苦。

接下来可以做什么?

这个 200 行引擎是最小可用版本。要做得更专业:

  1. 更多策略:RSI 动量、布林带均值回归、多因子组合
  2. 止损模块:固定止损、移动止损、ATR 波动率止损
  3. 仓位管理:Kelly 公式、波动率目标法
  4. 多标的:同时回测多只股票

如果你不想从零写这些,我整理了一个完整的 Python Quant Starter Kit,包含 3 个策略 + 风控模块 + 详细教程:

hilenislen.gumroad.com/l/quant-sta…


Q: yfinance 数据准确吗? 个人学习够用,生产环境用付费数据源。

Q: 这个策略能赚钱吗? 均线交叉在趋势市好,震荡市差。没有圣杯,关键是风控。

Q: 能交易加密货币吗? 可以,yfinance 支持 BTC-USD


觉得有帮助点个赞。量化问题欢迎评论区交流。