Python 量化入门
第一个策略:双均线
策略逻辑、信号生成、仓位管理、回测对比
终于到写策略的环节了。这一篇我们从零实现一个完整的双均线策略,包括信号生成、仓位管理、收益计算和绩效评估。这是你的第一个真正可执行的量化策略。
双均线策略原理
策略思想极其简单:
- MA5 上穿 MA20(金叉)→ 买入
- MA5 下穿 MA20(死叉)→ 卖出
背后的逻辑:短期均线代表近期趋势,长期均线代表中期趋势。当短期向上突破长期,说明上涨动能增强;反之亦然。
它的优点是简单、易理解、能在趋势行情中赚钱;缺点是震荡行情中频繁打脸。
第一版:信号生成
pythonimport akshare as ak import pandas as pd import numpy as np df = ak.stock_us_daily(symbol="AAPL", adjust="qfq") df['date'] = pd.to_datetime(df['date']) df = df[df['date'] >= '2022-01-01'].set_index('date') # 计算均线 df['MA5'] = df['close'].rolling(5).mean() df['MA20'] = df['close'].rolling(20).mean() # 生成持仓信号:MA5 > MA20 时持有,否则空仓 df['position'] = (df['MA5'] > df['MA20']).astype(int)
position 列的含义:1 表示满仓持有,0 表示空仓。
第二版:避免未来函数
新手最常犯的错误是用当天的均线判断当天的开仓,但实际交易中你今天收盘后才知道均线值,最早只能明天开盘买。
python# 信号 shift(1):今天的持仓基于昨天的信号 df['position'] = (df['MA5'] > df['MA20']).astype(int).shift(1)
这一行 shift(1) 是回测真实性的关键。
第三版:计算策略收益
pythondf['return'] = df['close'].pct_change() df['strategy_return'] = df['position'] * df['return'] # 累计净值 df['cum_market'] = (1 + df['return']).cumprod() df['cum_strategy'] = (1 + df['strategy_return']).cumprod() print(df[['cum_market', 'cum_strategy']].tail())
第四版:考虑交易成本
实盘有手续费、印花税、滑点。简单起见,我们假设单边手续费 0.03%(万三)。
pythonCOMMISSION = 0.0003 # 持仓变化才扣手续费 df['trade'] = df['position'].diff().abs() df['commission'] = df['trade'] * COMMISSION df['strategy_return_net'] = df['strategy_return'] - df['commission'] df['cum_strategy_net'] = (1 + df['strategy_return_net']).cumprod()
第五版:绩效指标
pythondef performance_metrics(returns, periods_per_year=252): """计算关键绩效指标""" total_return = (1 + returns).prod() - 1 n_years = len(returns) / periods_per_year annual_return = (1 + total_return) ** (1 / n_years) - 1 annual_vol = returns.std() * np.sqrt(periods_per_year) sharpe = annual_return / annual_vol if annual_vol > 0 else 0 cum = (1 + returns).cumprod() drawdown = (cum / cum.cummax() - 1).min() win_rate = (returns > 0).sum() / (returns != 0).sum() return { '总收益': f'{total_return:.2%}', '年化收益': f'{annual_return:.2%}', '年化波动': f'{annual_vol:.2%}', '夏普比率': f'{sharpe:.2f}', '最大回撤': f'{drawdown:.2%}', '日胜率': f'{win_rate:.2%}', } # 对比基准和策略 print('基准(Buy & Hold):') print(performance_metrics(df['return'].dropna())) print('\n策略(含手续费):') print(performance_metrics(df['strategy_return_net'].dropna()))
第六版:可视化
pythonimport matplotlib.pyplot as plt plt.rcParams['font.sans-serif'] = ['Arial Unicode MS'] # macOS plt.rcParams['axes.unicode_minus'] = False fig, axes = plt.subplots(2, 1, figsize=(12, 8), sharex=True, gridspec_kw={'height_ratios': [3, 1]}) # 净值曲线 axes[0].plot(df['cum_market'], label='Buy & Hold', color='#94a3b8', linewidth=1.5) axes[0].plot(df['cum_strategy_net'], label='双均线策略', color='#3b82f6', linewidth=2) axes[0].set_title('双均线策略 vs Buy & Hold', fontsize=14, fontweight='bold') axes[0].set_ylabel('净值') axes[0].legend() axes[0].grid(True, alpha=0.3) axes[0].axhline(y=1, color='black', linestyle='--', alpha=0.3) # 回撤 def drawdown(returns): cum = (1 + returns).cumprod() return (cum / cum.cummax() - 1) dd = drawdown(df['strategy_return_net']) axes[1].fill_between(df.index, dd * 100, 0, color='#ef4444', alpha=0.4) axes[1].set_ylabel('回撤 (%)') axes[1].set_xlabel('日期') axes[1].grid(True, alpha=0.3) plt.tight_layout() plt.show()
完整代码:封装成函数
pythondef backtest_dual_ma(df, fast=5, slow=20, commission=0.0003): """ 双均线策略回测 df: 包含 close 列的 DataFrame,索引是日期 fast / slow: 快慢线周期 commission: 单边手续费 """ df = df.copy() df[f'MA{fast}'] = df['close'].rolling(fast).mean() df[f'MA{slow}'] = df['close'].rolling(slow).mean() # 生成持仓信号(避免未来函数) df['position'] = (df[f'MA{fast}'] > df[f'MA{slow}']).astype(int).shift(1) # 收益计算 df['return'] = df['close'].pct_change() df['trade'] = df['position'].diff().abs() df['commission'] = df['trade'] * commission df['strategy_return'] = df['position'] * df['return'] - df['commission'] # 净值 df['cum_market'] = (1 + df['return']).cumprod() df['cum_strategy'] = (1 + df['strategy_return']).cumprod() return df # 使用 result = backtest_dual_ma(df, fast=5, slow=20) print(performance_metrics(result['strategy_return'].dropna()))
参数优化
不同的均线周期效果差异很大。可以做个简单的参数扫描:
pythonresults = [] for fast in [5, 10, 15]: for slow in [20, 30, 60]: if fast >= slow: continue result = backtest_dual_ma(df, fast=fast, slow=slow) ret = result['strategy_return'].dropna() total = (1 + ret).prod() - 1 sharpe = ret.mean() / ret.std() * np.sqrt(252) results.append({ 'fast': fast, 'slow': slow, 'total_return': total, 'sharpe': sharpe }) opt_df = pd.DataFrame(results).sort_values('sharpe', ascending=False) print(opt_df)
⚠️ 小心过拟合 不要光盯着夏普最高的参数。如果一组参数明显优于其他组合,很可能是过拟合到这段历史数据上。一个好的策略应该对参数变化不敏感。
双均线策略的局限
实际跑一下你会发现,这个策略在很多股票上跑不赢 Buy & Hold。原因:
- 震荡行情打脸严重 — 频繁金叉死叉,不停被止损
- 滞后性 — 等到信号出现,行情已经走了一半
- 没有止损止盈 — 趋势反转时可能交回所有利润
改进方向(后续可以试):
- 加入 ATR 动态止损
- 用 EMA 替代 SMA 减少滞后
- 加入波动率过滤(震荡时不交易)
- 多周期组合(日线 + 周线)
小结
你已经完整走过了一个量化策略的开发流程:
- ✅ 信号生成
- ✅ 避免未来函数
- ✅ 考虑交易成本
- ✅ 计算绩效指标
- ✅ 可视化
- ✅ 参数优化
- ✅ 反思局限
但我们目前是手撸回测,代码很啰嗦。下一篇 回测框架入门,我们用专业的回测框架 backtrader 让代码更规范。
📌 最重要的一课 不要拿着这个策略直接去实盘。它的目的是让你理解策略开发的流程,不是让你赚钱。真正能赚钱的策略需要更多的细节打磨和真实的市场理解。
本页导读