策略对比与基准
直觉
讲了趋势(双均线)、回归(布林带)、截面动量、配对——到底哪个「好」?没有绝对的好坏,只有「在同一把尺子下」的横向比较。这一节把三个单资产策略跑在同一份数据上,用全站统一的 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)将进入因子、时序、组合优化与回测工程。