跳到主要内容

策略对比与基准

直觉

讲了趋势(双均线)、回归(布林带)、截面动量、配对——到底哪个「好」?没有绝对的好坏,只有「在同一把尺子下」的横向比较。这一节把三个单资产策略跑在同一份数据上,用全站统一的 quant 指标做成对比表。

为什么要比基准

任何策略都要先问一个问题:它跑得过「买入持有」吗? 买入持有是最朴素也最顽强的基准(benchmark)。一个复杂策略如果连买入持有都比不过,再精巧也没意义。

:::note 统一的尺子 三个策略都用同一个 quant.vector_backtest(同样防前视、同样 cost_bps、同样年化口径),所以指标可直接横比。这正是模块 8「统一回测引擎」的价值。 :::

可运行案例:买入持有 vs 双均线 vs 布林带

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']
cost = 2.0

# —— 构造三种信号 ——
signals = {}
signals['买入持有'] = pd.Series(1.0, index=close.index)

raw = (close.rolling(20).mean() > close.rolling(60).mean()).astype(float)
signals['双均线20/60'] = raw.replace(0, np.nan).ffill().fillna(0)

mid, sd = close.rolling(20).mean(), close.rolling(20).std()
bb = pd.Series(np.nan, index=close.index)
bb[close < mid - 2*sd] = 1.0
bb[close > mid + 2*sd] = -1.0
signals['布林带20/2'] = bb.ffill().fillna(0)

# —— 同一引擎回测, 汇总指标 ——
rows, eqs = [], {}
for name, sig in signals.items():
  r = quant.vector_backtest(close, sig, cost_bps=cost, freq=252)
  s = quant.performance_summary(r['equity'], r['returns'], freq=252)
  eqs[name] = r['equity']
  rows.append({'策略': name, '年化收益': s['年化收益'], '夏普': s['夏普'],
               '最大回撤': s['最大回撤'], '卡玛': s['卡玛'], '换手': r['turnover']})

print(pd.DataFrame(rows).set_index('策略').round(3))

plt.figure(figsize=(9, 3.8))
for name, eq in eqs.items():
  plt.plot(eq, label=name, lw=1.4)
plt.title(f'三种策略资金曲线对比(成本 {cost} bps)'); plt.ylabel('净值'); plt.legend()
plt.tight_layout(); plt.show()

动手改一改:拖动成本即时横比

拖动统一成本——换手越高的策略(双均线、布林带)净值塌得越狠,而买入持有几乎不动。「在零成本假象下赚钱的策略,加上真实成本可能亏钱」。

# ParamSlider(SSR 预览)
参数: 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']
signals = {}
signals['买入持有'] = pd.Series(1.0, index=close.index)
raw = (close.rolling(20).mean() > close.rolling(60).mean()).astype(float)
signals['双均线20/60'] = raw.replace(0, np.nan).ffill().fillna(0)
mid, sd = close.rolling(20).mean(), close.rolling(20).std()
bb = pd.Series(np.nan, index=close.index)
bb[close < mid - 2*sd] = 1.0
bb[close > mid + 2*sd] = -1.0
signals['布林带20/2'] = bb.ffill().fillna(0)

rows, eqs = [], {}
for name, sig in signals.items():
  r = quant.vector_backtest(close, sig, cost_bps=COST_BPS, freq=252)
  s = quant.performance_summary(r['equity'], r['returns'], freq=252)
  eqs[name] = r['equity']
  rows.append({'策略': name, '夏普': s['夏普'], '最大回撤': s['最大回撤'], '换手': round(r['turnover'], 0)})
print(pd.DataFrame(rows).set_index('策略').round(3))

plt.figure(figsize=(9, 3.6))
for name, eq in eqs.items():
  plt.plot(eq, label=name, lw=1.3)
plt.title(f'三策略对比(成本 {COST_BPS} bps)'); plt.ylabel('净值'); plt.legend(fontsize=9)
plt.tight_layout(); plt.show()

小结

  • 永远先和买入持有基准比,跑不赢就没意义;
  • 同一引擎、同一成本、同一年化口径,指标才可横比;
  • 零成本是回测的最大陷阱——参数越敏感的策略越经不起成本考验。

到此,模块 0–4 全部完成:从「什么是量化」到「能跑、能比的经典策略」。下一批(模块 5–8)将进入因子、时序、组合优化与回测工程