1. 策略为什么能赚钱?——三大来源¶
市场不是无缘无故给你钱的。策略能赚钱,必须依赖某种可持续的优势。这些优势大致来自三个地方:
来源一:行为偏差(Behavioral Edge)¶
人类不是理性的。我们有很多可预测的、系统性的错误:
- 羊群效应:大家都买,我也买(→ 形成趋势)
- 过度反应:坏消息一出,大家恐慌抛售,把价格砸过头(→ 均值回归机会)
- 锚定效应:「这股票以前涨到 200,现在 150,感觉便宜」(→ 错误定价)
- 损失厌恶:亏损的痛苦是盈利快乐的 2 倍,所以大家更倾向于「早止盈、晚止损」(→ 动量效应)
量化策略的本质之一:系统性地捕捉人类的系统性错误。
来源二:风险溢价(Risk Premium)¶
有些收益是对「承担风险」的补偿,不是「免费的午餐」:
- 股票长期跑赢国债,因为你承担了公司倒闭的风险
- 小盘股长期跑赢大盘股,因为你承担了流动性风险
- 高波动率资产有更高收益,因为大多数人不喜欢波动
这类策略不是「天才」,而是愿意承担别人不愿承担的风险,换取更高回报。
来源三:结构性优势(Structural Edge)¶
市场结构本身造成的定价偏差:
- 指数调仓效应:指数成分股调整时,被动基金必须买/卖,造成短期量价异常
- 流动性提供:做市商通过买卖价差赚钱,而非预测涨跌
- 季节性效应:「一月效应」「卖掉五月」等统计规律
💡 关键判断问题: 每次你想到一个策略,都要问自己:
「我的优势来自哪里?为什么别人没有发现这个,或者发现了为什么不愿意去做它?」
如果答不上来,这个策略很可能是随机噪声,或者已经被套利掉了。
2. 四大策略类型——核心逻辑与直觉¶
策略类型一:趋势跟随(Trend Following)¶
核心信念:「涨的会继续涨,跌的会继续跌。」
这听起来像是废话,但有非常扎实的学术依据——「动量效应」(Momentum Effect)在几乎所有资产类别和时间段都被验证过。
为什么趋势会持续?
- 好消息往往分批传播,不是一次性被市场消化
- 机构投资者仓位调整需要时间(不是一天能买完)
- 趋势吸引更多跟随者,形成自我强化
典型策略:
- 均线金叉:SMA20 上穿 SMA60 → 买入
- 突破策略:价格突破 52 周新高 → 买入
- MACD 信号:MACD 线上穿信号线 → 买入
适合的市场: 单边趋势行情(牛市/熊市)
不适合的市场: 震荡盘整市(会被反复打止损、假信号多)
策略类型二:均值回归(Mean Reversion)¶
核心信念:「价格偏离正常水平太远了,总会回来的。」
就像橡皮筋——拉得越远,弹回来的力量越大。
为什么会有均值回归?
- 市场的过度反应:坏消息出来,恐慌性抛售把价格砸过头了
- 统计均衡:历史上价格总是在一个中枢附近波动
- 套利者的存在:偏离太远时,「聪明钱」会入场修正
典型策略:
- RSI < 30 时买入(超卖,可能反弹)
- 价格触碰布林带下轨时买入
- 配对交易:两个高度相关的股票价差扩大时套利
适合的市场: 震荡区间市
不适合的市场: 单边趋势(「越涨越买」会追到顶,「越跌越买」会越套越深)
策略类型三:跨截面动量(Cross-Sectional Momentum)¶
核心信念:「同一时期表现最好的资产,短期内往往继续表现好。」
不是预测某一只股票绝对涨跌,而是预测相对排名。
做法:
- 每月末计算所有股票过去 12 个月(除最近 1 个月)的收益
- 买入排名前 20% 的「赢家」
- 卖出(做空)排名后 20% 的「输家」
这是诺贝尔奖级别的策略,由 Jegadeesh & Titman 在 1993 年首次系统记录。
策略类型四:统计套利(Statistical Arbitrage)¶
核心信念:「两个高相关的资产,价差应该稳定,偏离时可以套利。」
经典例子:可口可乐(KO)和百事可乐(PEP)。都是碳酸饮料公司,股价长期高度相关。如果某天 KO 跌、PEP 不跌,这可能是短暂偏离,买 KO + 卖空 PEP,等价差回归时获利。
关键特点: 策略收益与市场涨跌无关(市场中性),靠的是价差的均值回归。
策略类型对比总结¶
| 策略类型 | 核心信念 | 挣钱来源 | 适合市场 | 风险点 |
|---|---|---|---|---|
| 趋势跟随 | 动量持续 | 行为偏差(羊群效应) | 单边行情 | 震荡市假信号多 |
| 均值回归 | 偏离会修正 | 行为偏差(过度反应) | 区间震荡 | 趋势市扛不住 |
| 跨截面动量 | 相对强弱持续 | 行为偏差 + 机构滞后 | 股票多空 | 动量崩溃风险 |
| 统计套利 | 价差均值回归 | 结构性 + 统计规律 | 任意市场 | 相关性破裂 |
3. 从想法到策略:完整开发流程¶
灵感/假设
↓
逻辑自洽性检验(问自己:为什么这能赚钱?)
↓
初步代码实现
↓
样本内回测(In-Sample)——用历史数据验证
↓
样本外测试(Out-of-Sample)——用没见过的数据验证
↓
敏感性分析(参数稍微变化,策略是否还稳定?)
↓
加入真实交易成本(手续费、滑点)
↓
风险控制设计(最大仓位、止损规则)
↓
小资金实盘测试
每一步都有可能淘汰掉你的策略——这是正常的。 大多数策略死在「样本外测试」这一关。原因通常是:
- 过拟合(Overfitting):参数太多,历史数据「背」得太熟,但遇到新数据就失效
- Look-ahead Bias:不小心用了未来数据(下一节详细讲)
- 幸存者偏差(Survivorship Bias):只用了还在市场上的股票数据,忽略了已退市、破产的股票
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
import numpy as np
import pandas as pd
# 演示:过拟合的可视化
# 用随机收益率数据,展示「样本内完美,样本外失灵」的现象
np.random.seed(42)
n = 300
returns = pd.Series(np.random.normal(0.0003, 0.01, n)) # 纯随机价格变动
# 把数据切成两半
in_sample = returns[:150] # 样本内:用来开发策略
out_sample = returns[150:] # 样本外:用来验证策略
# 在样本内穷举 SMA 参数,找「最优」参数
best_return = -np.inf
best_params = None
all_in_returns = []
for fast in range(3, 20):
for slow in range(fast + 3, 40):
price = (1 + in_sample).cumprod()
sma_f = price.rolling(fast).mean()
sma_s = price.rolling(slow).mean()
pos = (sma_f > sma_s).shift(1).fillna(False).astype(int)
r = (pos * in_sample).sum()
all_in_returns.append(r)
if r > best_return:
best_return = r
best_params = (fast, slow)
# 用样本内「最优」参数跑样本外
f, s = best_params
price_full = (1 + returns).cumprod()
sma_f_full = price_full.rolling(f).mean()
sma_s_full = price_full.rolling(s).mean()
pos_full = (sma_f_full > sma_s_full).shift(1).fillna(False).astype(int)
strat_ret = pos_full * returns
cum_strat_in = (1 + strat_ret[:150]).cumprod()
cum_strat_out = (1 + strat_ret[150:]).cumprod() * cum_strat_in.iloc[-1]
cum_market = (1 + returns).cumprod()
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(13, 4))
ax1.hist(all_in_returns, bins=40, color='steelblue', alpha=0.7)
ax1.axvline(best_return, color='red', linestyle='--', label=f'「最优」参数 SMA({f},{s})')
ax1.set_title('样本内穷举:找到了「最优」参数', fontsize=12)
ax1.set_xlabel('策略累积收益')
ax1.legend()
ax1.grid(alpha=0.3)
ax2.plot(range(150), cum_strat_in.values, color='green', linewidth=1.5, label='样本内(用于寻参)')
ax2.plot(range(150, 300), cum_strat_out.values, color='red', linewidth=1.5, label='样本外(真实表现)')
ax2.plot(range(300), cum_market.values, color='gray', linewidth=1, linestyle='--', label='买入持有')
ax2.axvline(150, color='black', linestyle=':', linewidth=1.5, label='样本内/外分割点')
ax2.set_title(f'SMA({f},{s}) 过拟合演示\n样本内看起来很好,样本外回到起点', fontsize=11)
ax2.legend(fontsize=9)
ax2.grid(alpha=0.3)
plt.tight_layout()
plt.show()
print('⚠️ 注意:这是纯随机数据,不存在任何规律。')
print(' 但只要参数够多、调得够细,「历史回测」可以很漂亮。这就是过拟合。')
⚠️ 注意:这是纯随机数据,不存在任何规律。 但只要参数够多、调得够细,「历史回测」可以很漂亮。这就是过拟合。
import matplotlib.pyplot as plt
# 1. 设置系统自带的中文字体(这里使用黑体 SimHei)
plt.rcParams['font.sans-serif'] = ['SimHei'] # 如果你想用微软雅黑,可以改成 ['Microsoft YaHei']
# 2. 解决更换字体后,负号(-)显示为方块的问题
plt.rcParams['axes.unicode_minus'] = False
陷阱二:Look-ahead Bias(未来函数偏差)¶
这是回测中最常见、也最隐蔽的错误。
定义: 在生成交易信号时,不小心用了还没发生的数据。
举个栗子:
# ❌ 错误写法(Look-ahead Bias)
# 今天的信号用了今天的收盘价,但你在今天收盘后才知道收盘价
# 真实交易中,你在今天开盘时就要决定买不买
signal_today = rsi_today < 30 # 今天收盘计算的 RSI
position_today = signal_today # ← 今天就执行,但你当时不知道今天收盘价!
# ✅ 正确写法(用 shift(1) 延迟一天)
signal_today = rsi.shift(1) < 30 # 昨天收盘计算的 RSI
position_today = signal_today # 今天开盘执行昨天的信号,这才合理
为什么很隐蔽? 代码看起来没有任何问题,回测结果漂亮得令人难以置信……这恰恰是警报!如果一个策略的夏普比率超过 3,大概率是有 Look-ahead Bias。
检查清单:
- 信号生成时用的数据,是否都是截止到昨天收盘的?
- 执行交易时,是否用了
shift(1)延迟信号? - 止损止盈是否用了当天的收盘价(而不能用高低价的中间某个价格)?
import yfinance as yf
# 演示 Look-ahead Bias 对回测结果的影响
data = yf.download('SPY', start='2020-01-01', end='2024-01-01', progress=False)
close = data['Close'].squeeze()
ret = close.pct_change()
# 用 RSI 为例
def rsi(price, period=14):
delta = price.diff()
gain = delta.clip(lower=0)
loss = -delta.clip(upper=0)
avg_g = gain.ewm(com=period-1, min_periods=period).mean()
avg_l = loss.ewm(com=period-1, min_periods=period).mean()
return 100 - 100/(1 + avg_g/avg_l)
rsi_val = rsi(close)
# ❌ 有 Look-ahead Bias 的版本(信号当天执行)
pos_biased = pd.Series(0.0, index=close.index)
pos_biased[rsi_val < 35] = 1.0 # 今天 RSI<35 → 今天就买入(用了今天才知道的数据)
pos_biased[rsi_val > 65] = -1.0
ret_biased = (pos_biased * ret).dropna()
# ✅ 正确版本(信号延迟一天执行)
pos_correct = pos_biased.shift(1).fillna(0) # 昨天的信号,今天才执行
ret_correct = (pos_correct * ret).dropna()
cum_b = (1 + ret_biased).cumprod()
cum_c = (1 + ret_correct).cumprod()
cum_m = (1 + ret.dropna()).cumprod()
fig, ax = plt.subplots(figsize=(13, 5))
ax.plot(cum_b.index, cum_b.values, label=f'有 Look-ahead Bias(夏普={ret_biased.mean()/ret_biased.std()*252**0.5:.2f})',
color='red', linewidth=2)
ax.plot(cum_c.index, cum_c.values, label=f'正确实现(夏普={ret_correct.mean()/ret_correct.std()*252**0.5:.2f})',
color='green', linewidth=2)
ax.plot(cum_m.index, cum_m.values, label='买入持有', color='gray', linewidth=1.5, linestyle='--')
ax.set_title('Look-ahead Bias 的影响:「错误」代码让策略看起来无敌好', fontsize=13)
ax.legend()
ax.grid(alpha=0.3)
plt.tight_layout()
plt.show()
print('⚠️ 两条线的差异,完全来自一个 shift(1)。')
print(' 没有 shift(1) 的版本用了未来数据——在现实中根本无法执行!')
⚠️ 两条线的差异,完全来自一个 shift(1)。 没有 shift(1) 的版本用了未来数据——在现实中根本无法执行!
陷阱三:幸存者偏差(Survivorship Bias)¶
类比: 你只采访了高考考上清华的学生,问他们的学习方法,然后得出结论「只要这样学就能上清华」——你完全忽略了用同样方法但没考上的人。
在量化研究里:
- 如果你只分析当前还在标普500里的503只股票,你忽略了过去30年被踢出指数、甚至退市破产的数百只股票
- 这让历史数据天然「偏好」了最终成功的公司
- 用这种数据开发的策略,回测收益会被系统性高估
如何避免:
- 使用包含历史成分股(包括退市股票)的数据库
- 或者限定回测范围(只做指数成分股的策略,并用当时的成分股名单)
对于本教程的练习,我们用 ETF(如 SPY)或单只股票回测,基本不涉及幸存者偏差。但当你做股票选择策略(选一篮子股票)时,一定要注意这个问题。
5. 你的第一个「策略假设」应该长什么样?¶
好的策略假设要回答这三个问题:
当 [某个条件成立] 时,
买入 [某个资产],
因为 [某个行为/结构原因] 会导致 [预期的价格走向]。
例子:
| 假设 | 逻辑依据 |
|---|---|
| 「当 RSI < 30 时买入」 | 超卖说明市场过度恐慌,行为偏差→均值回归 |
| 「当 SMA20 上穿 SMA60 时买入」 | 短期动量超过长期趋势,趋势跟随 |
| 「当价格突破 52 周新高时买入」 | 新高被心理阻力压制,突破后解放筹码→趋势延续 |
| 「买最近 12 月涨幅最大的股票」 | 动量效应,机构资金流入有惯性 |
练习: 在下方 Markdown 单元格里,写下你自己的一个策略假设:
我的策略假设(在这里填写):
当 ________ 时, 买入/卖出 ________, 因为 ________ 会导致 ________。
🎯 小结¶
- 策略赚钱靠的是可持续的优势,不是运气
- 四种主要策略类型:趋势跟随、均值回归、跨截面动量、统计套利
- 每种策略都有适合和不适合的市场——没有万能策略
- 开发策略时,永远先问「我的优势是什么」
- 三大陷阱:过拟合、Look-ahead Bias、幸存者偏差
下一节 → 01_simple_backtest.ipynb(把「双均线趋势策略」完整实现一遍)
🎯 练习¶
- 回顾经典的「一月效应」,用代码验证 SPY 历史上 1 月份是否真的更容易上涨。
- 列举一个可能遭受「幸存者偏差」的研究场景,并说明如何避免它。
- 比较趋势跟随策略和均值回归策略在 2008 年、2020 年、2022 年的历史表现差异。
下一节 → 01_simple_backtest.ipynb