跳到主要内容

趋势跟踪 · 双均线交叉

直觉

趋势跟踪的核心信念是:「趋势一旦形成,往往会延续一段时间」。最朴素的捕捉方法就是双均线交叉:快线(短期均线)上穿慢线(长期均线)→ 趋势向上 → 做多;下穿 → 离场或做空。它逻辑简单、全市场通用,是每个量化的第一课。

策略规则

  • 快线 SMAf\text{SMA}_f、慢线 SMAs\text{SMA}_sf<sf<s,如 20/60);
  • 金叉(快线上穿慢线)→ 持多头;
  • 死叉(快线下穿慢线)→ 空仓(或多空则转空头)。

把「目标仓位」用 ffill 延续成持续序列,再交给引擎(内部 shift(1) 防前视、按换手扣成本)。

可运行案例:双均线策略 + 参数扫描

先跑一组 (20,60) 看完整资金曲线;再扫描多组快/慢参数,比较夏普——体会「参数越敏感、换手越高、越被成本吃掉」。

import quant
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
close = df['adj_close']

def ma_signal(close, f, s):
  raw = (close.rolling(f).mean() > close.rolling(s).mean()).astype(float)
  return raw.replace(0, np.nan).ffill().fillna(0)

# —— 详细跑一组 ——
sig = ma_signal(close, 20, 60)
res = quant.vector_backtest(close, sig, cost_bps=2.0, freq=252)
bh = close / close.iloc[0]
dd = res['equity'] / res['equity'].cummax() - 1

fig, ax = plt.subplots(2, 1, figsize=(9, 5.2), sharex=True,
                     gridspec_kw={'height_ratios':[2,1]})
ax[0].plot(res['equity'], label='MA(20,60)', lw=1.5)
ax[0].plot(bh, label='买入持有', alpha=0.7); ax[0].legend(); ax[0].set_title('资金曲线')
ax[1].fill_between(dd.index, dd, 0, color='tab:red', alpha=0.4); ax[1].set_title('回撤')
plt.tight_layout(); plt.show()
print(quant.performance_summary(res['equity'], res['returns'], freq=252).round(3))

# —— 参数扫描 ——
print("\n快\慢参数 → 夏普 / 换手:")
rows = []
for f in [5, 10, 20]:
  for s in [20, 60, 120]:
      if f >= s: continue
      r = quant.vector_backtest(close, ma_signal(close, f, s), cost_bps=2.0, freq=252)
      sr = quant.sharpe(r['returns'], freq=252)
      rows.append(f"({f:>2}/{s:>3})  夏普={sr:5.2f}  换手={r['turnover']:6.0f}")
print('\n'.join(rows))

动手改一改:拖动参数即时看回测

拖动快/慢均线窗口与成本,体会「参数越敏感、换手越高、越被成本吃掉」——慢参数(如 40/120)抗成本能力强得多。

# ParamSlider(SSR 预览)
参数: FAST, SLOW, COST_BPS

import quant
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

df = pd.read_csv('/data/spy_daily.csv', parse_dates=['date']).set_index('date')
close = df['adj_close']
raw = (close.rolling(FAST).mean() > close.rolling(SLOW).mean()).astype(float)
sig = raw.replace(0, np.nan).ffill().fillna(0)
r = quant.vector_backtest(close, sig, cost_bps=COST_BPS, freq=252)
bh = close / close.iloc[0]

plt.figure(figsize=(9, 3.6))
plt.plot(r['equity'], label=f'MA({FAST}/{SLOW})', lw=1.5)
plt.plot(bh, label='买入持有', alpha=0.7)
plt.title(f'成本 {COST_BPS} bps'); plt.ylabel('净值'); plt.legend(fontsize=9)
plt.tight_layout(); plt.show()
print(quant.performance_summary(r['equity'], r['returns'], freq=252).round(3))

小结

  • 趋势策略靠「快线上穿慢线」捕捉趋势,简单稳健;
  • 信号要 ffill 成持续仓位,再交引擎(防前视、扣成本);
  • 参数越敏感 → 换手越高 → 越容易被交易成本吞噬利润。

小测验

1. 快线上穿慢线(金叉)通常对应?

2. 信号生成后为何要做 ffill?

3. 为什么「参数越敏感的策略越经不起成本」?