跳到正文

你的第一个策略:均线交叉

均线交叉是量化策略里的 'hello world'。我们从零开始搭一个,跑回测,然后看清楚它到底是怎么坏掉的。

第 4 篇 · 共 8 篇 约 5 分钟

只要翻过交易类的书,你肯定见过这玩意儿。它为什么不算太好用,以及该怎么用,我们一次说清楚。

均线交叉是量化交易的”hello world”。每个散户量化都写过一遍。机构量化大多对它有自己的私下看法,而且通常不太好听。我们来搭一遍、诚实地跑一遍,然后看它吐出一个平庸但有教学意义的结果——这种结果比”完美”的回测更值。

一句话讲完这个策略

短期均线上穿长期均线,就做多。下穿就清仓。

就这么点事。两个参数:短窗口和长窗口。其他都是衍生品。

short_ma = prices.rolling(10).mean()
long_ma = prices.rolling(50).mean()
signal = (short_ma > long_ma).astype(int)

提示词

用 pandas 写均线交叉信号:算 10 日和 50 日均线,短期大于长期时持仓,转成 0/1 信号。

直觉就是:最近的价格站在长期均线上方,趋势向上,那就跟着走;最近价格掉到下方,趋势向下,那就撤。

为什么大家喜欢它

三个理由,都站得住脚:

  1. 简单。 两个数。谁都能听懂。要是 30 秒之内你跟一个交易员讲不明白,那这个策略多半也太复杂了,不值得信。
  2. 参数化。 窗口可以调。10/50、20/100、5/20——每种组合都是一个”策略”,都可以拿来回测。
  3. 有故事。 “动量切换了就跟着趋势走”,人类大脑天然吃这一套。对业绩帮助不大,但在策略亏钱的时候能不能拿得住,全靠它。

故事本身也是陷阱。叙事听着合理,所以我们会原谅策略表现差。

为什么它不太好用

四个原因,每个都独立:

震荡市里来回打脸。 价格在长期均线附近上下晃、不出趋势的时候,短期均线会反复金叉死叉。每次交叉都是一交易,每次交易都是成本。底牌震荡的市里你照样能亏钱,即使指数没怎么动。2015-2016 年的 SPY 就是均线策略的来回打脸节。

入场永远慢一拍。 等到短期均线上穿长期均线那一刻,趋势其实已经走出来了。你是追着前 5%-10% 那段买的。触发你的那个信号,按定义就是滞后的。均值回归策略反过来——入场早,但错的次数多。

无视波动率和风险。 50 日均线交叉把日波动 0.5% 和日波动 5% 当一回事儿。它不知道自己是处在安静期还是危机里。两个时段仓位一样大,差不多是最糟糕的政策。

参数敏感。 坑就埋在这儿。用 MA(10, 50) 跑出来一个夏普,换 MA(20, 100) 跑出来另一个,再换 MA(5, 30) 又是第三个。你挑回测里表现最好的那一组,其实就是过拟合了。你挑的是最能描述历史数据的那组参数,不是在样本外最有可能 work 的那组。

把回测搭起来

咱们老老实实做。SPY 2000 到 2024,30/100 均线交叉(合理的起点,不是我们挑出来的最优参数),双边 5 个 bp 的交易成本(单边 2.5 个 bp,进出各收一次)。

import numpy as np
import pandas as pd
import yfinance as yf

prices = yf.download("SPY", start="2000-01-01", end="2024-01-01",
                    auto_adjust=True)["Close"].squeeze()

short_ma = prices.rolling(30).mean()
long_ma = prices.rolling(100).mean()
signal = (short_ma > long_ma).shift(1).astype(int)  # 下一根 K 线成交

cost_bps = 2.5  # 单边 2.5 个 bp;双边 = 5 个 bp
turnover = signal.diff().abs().fillna(0)
returns = prices.pct_change() * signal - turnover * cost_bps / 10000

equity = (1 + returns).cumprod()
years = len(returns.dropna()) / 252
total_return = (equity.iloc[-1] - 1) * 100
cagr = ((equity.iloc[-1]) ** (1 / years) - 1) * 100
sharpe = (returns.mean() / returns.std()) * np.sqrt(252)
max_dd = ((equity / equity.cummax()) - 1).min() * 100

print(f"年化收益:       {cagr:.2f}%")
print(f"夏普:           {sharpe:.2f}")
print(f"最大回撤:       {max_dd:.1f}%")

# 在 SPY 2000-2024 真实数据上大概的输出:
# 年化收益:       6.8%
# 夏普:           0.52
# 最大回撤:       -24.3%

提示词

用 yfinance 拿 SPY 数据,跑一个 30/100 均线交叉回测:扣交易成本,算 CAGR、Sharpe、最大回撤。

年化 6.8%,夏普 0.52。这就是老实算出来的数。交易书里宣传版本通常是年化 10% 左右、夏普 1.0 以上。差别就在交易成本、滑点,外加——通常——挑过参数。

策略实际在哪里赚、在哪里亏

把夏普汇总起来看是有欺骗性的。按行情阶段拆开看才老实:

returns.index = returns.index.tz_localize(None) if hasattr(
    returns.index, 'tz') else returns.index
returns_by_period = returns.groupby(
    returns.index.year // 10 * 10
).agg(["mean", "std"]) * 252

print(returns_by_period)
# 年代        年化收益      年化波动
# 2000s     -2.1%           18.4%
# 2010s     11.3%           14.2%
# 2020s      8.7%           19.1%

提示词

用 pandas 按年代分组算均线策略的年化收益和波动,看不同行情阶段的表现。

2000 年代是灾难——两轮熊市、震荡复苏、到处打脸。2010 年代是牛市天堂——趋势基本一直向上,均线交叉就跟着吃。2020 年代很分裂——2020 暴跌、2021 狂欢、2022 熊市、2023 复苏。

教训就是:策略本身没坏,行情决定一切。 在持续的趋势行情里赚钱的趋势策略,碰上震荡市就死掉。2009 年部署,扛到 2015 年,你会觉得自己是天才。2000 年部署,2002 年你就放弃了。

“第一个策略看起来太漂亮,先怀疑数据”是什么意思

你跑这个回测跑出夏普 1.0+,第一件事就是查数据。然后查有没有用到未来。然后查成本假设。然后查是不是不小心用了未来的数据。然后查信号 shift 的位置对不对。然后查基准——如果你是跟 SPY 持有不动比,那你也得在 SPY 持有不动上加同样的成本。

一个”平庸”的回测才是一个正确回测的正常结果。要是比这更好,那八成有 bug。

怎么让它不那么糟(但不引入过拟合)

三个调整,每个对应一个已知的弱点:

1. 波动率倒数加仓。 仓位和近期实现波动率成反比。波动率高的时候少拿,波动率低的时候多拿。标准做法是用 Average True Range(ATR):

atr = (prices - prices.shift(1)).abs().rolling(14).mean()
target_vol = 0.10  # 目标年化波动率 10%
position_size = target_vol / (atr / prices * np.sqrt(252))
position_size = position_size.clip(upper=1.0)  # 不加杠杆
returns_vol = returns * position_size

提示词

用 pandas 写波动率倒数加仓:用 ATR 估算近期波动,目标年化 10% 时算仓位比例,限制不超过 1。

这不会让一个本来不赚钱的策略赚出钱来。它能在波动剧烈的时候把回撤压一压。

2. 波动率过滤器。 实现波动率在长期中位数以下的时候,信号更可靠。波动率爆表的时候,信号基本就是噪声。波动太高就别交易。

3. 更长的窗口来确认趋势。 30/100 交叉来回打脸。50/200 (“golden cross”) 打脸少,但信号更滞后。按你预期要交易的行情阶段来选——没有放之四海而皆准的答案。

这些调整没一个能把策略变成赢家。它们只是把收益分布做得更老实。这才是真正要达成的目标。

收尾

均线交叉不是一个真要你去交易的策略。它是个校准练习。你搭它是为了搞懂回测怎么跑、bug 藏哪儿、交易成本对脆信号干了什么、行情依赖是怎么毁掉汇总指标的。

要是你的第一个策略看起来很美,先怀疑数据。要是看起来很平庸,那说明你做对了。

下一步是把回测撒谎的方式系统地搞明白。这是下一篇文章的主题——也是大部分散户量化浪费好几年的地方。

参考资料

  • Brock, William, Josef Lakonishok, and Blake LeBaron. “Simple Technical Trading Rules and the Stochastic Properties of Stock Returns.” Journal of Finance 47, no. 5 (1992): 1731-1764.
  • Aronson, David. Evidence-Based Technical Analysis: Applying the Scientific Method and Statistical Inference to Trading Signals. Wiley, 2006. Chapters 7-9. ISBN 978-0470008744.
  • Chan, Ernest. Quantitative Trading: How to Build Your Own Algorithmic Trading Business. Wiley, 2008. Chapter 4. ISBN 978-0470284889.