【Python量化策略实战 #03】金叉死叉总慢半拍?双均线择时 + 成交量过滤跑通完整信号链
一、为什么需要择时(痛点开场)
前两篇我们把"选股"做完了 —— 用动量 / 多因子把股票筛出来。但问题来了:选出来之后什么时候买、什么时候卖?
最经典的择时信号是 双均线金叉死叉:
- 短均线上穿长均线 = 金叉 → 买入
- 短均线下穿长均线 = 死叉 → 卖出
但金叉死叉的"经典翻车" 是:很多假突破、缩量反弹也照样触发信号,一不小心就被来回打脸。本篇在双均线基础上加一条成交量过滤:金叉当日要 成交额 ≥ 5 日均量 才认信号。我们用 30 行 pandas 把完整信号链跑通,并跑出一笔真实成交。
二、本文你将得到什么
- 双均线信号判定(5 行):短 / 长均线、差值、前后日关系判定金叉死叉
- 成交量过滤:金叉当日量 v ≥ vol_ma(5) 才认信号;死叉不过滤(破位出货不分批)
- 次日开盘成交:避免未来函数(信号当日收盘判定,次日按 o 价成交)
- 完整 trades 记录:每次买卖时间、价格、原因可追溯
- 一个端到端 PASS 的 Demo,演示窗口下触发 1 次金叉 + 1 次死叉,读者换正式证书看完整 2 年信号统计
三、策略逻辑与代码(信号→交易规则→回测)
3.1 信号判定逻辑
条件 信号 含义
短均线由下方上穿长均线 金叉 +1 趋势向上
且当日成交量 ≥ 5 日均量 (过滤后仍 +1) 真突破,非缩量反弹
短均线由上方向下穿长均线 死叉 -1 趋势向下
死叉不做成交量过滤:破位出货信号要坚决离场。
3.2 完整代码
import numpy as np
import pandas as pd
from mairui import Client
LICENCE = "LICENCE-66D8-9F96-0C7F0FBCD073"
TARGET = "600519" # 演示用贵州茅台
SHORT_MA, LONG_MA = 3, 10 # 演示窗口压缩;正式证书用经典 5/20
VOL_MA = 5
START, END = "20250101", "20250906"
def fetch(client, code):
rows = client.stock_history(code, "d", "f", st=START, et=END, lt=50)
if not rows:
return pd.DataFrame()
df = pd.DataFrame(rows)
df["t"] = pd.to_datetime(df["t"])
return df.sort_values("t").set_index("t")[["o", "h", "l", "c", "v"]].astype(float)
client = Client(LICENCE)
df = fetch(client, TARGET)
1) 双均线 + 差值
df["ma_short"] = df["c"].rolling(SHORT_MA).mean()
df["ma_long"] = df["c"].rolling(LONG_MA).mean()
df["diff"] = df["ma_short"] - df["ma_long"]
2) 金叉 / 死叉 信号
df["signal"] = 0
df.loc[(df["diff"] > 0) & (df["diff"].shift(1) <= 0), "signal"] = 1 # 金叉
df.loc[(df["diff"] < 0) & (df["diff"].shift(1) >= 0), "signal"] = -1 # 死叉
3) 金叉加成交量过滤(缩量反弹滤掉)
df["vol_ma"] = df["v"].rolling(VOL_MA).mean()
gold_mask = df["signal"] == 1
df.loc[gold_mask, "signal"] = np.where(
df.loc[gold_mask, "v"] >= df.loc[gold_mask, "vol_ma"], 1, 0
)
4) 次日开盘成交(关键:避开未来函数)
cash, pos, shares, trades = 1.0, 0, 0, []
c, o = df["c"].values, df["o"].values
sig = df["signal"].values
dates = df.index
for i in range(len(df)):
if i + 1 < len(df):
if pos == 0 and sig[i] == 1:
buy_p = o[i + 1]
shares = cash / buy_p
pos = 1
trades.append({"date": dates[i + 1].strftime("%Y-%m-%d"),
"side": "BUY", "price": round(float(buy_p), 3),
"reason": "金叉 + 量过滤通过"})
elif pos == 1 and sig[i] == -1:
sell_p = o[i + 1]
cash = shares * sell_p
pos = 0
shares = 0
trades.append({"date": dates[i + 1].strftime("%Y-%m-%d"),
"side": "SELL", "price": round(float(sell_p), 3),
"reason": "死叉"})
print(f"信号数 {int((df['signal'] != 0).sum())} 成交笔数 {len(trades)}")
for t in trades:
print(t)
关于未来函数:买入价是次日 o 而不是当日 c,是因为我们在当日收盘才看到金叉信号。如果用当日 c 价成交,相当于在收盘价知道当天信号之前就买进去了,是典型的"偷看未来"。
四、自验结果(Demo 真实输出)
【Python量化策略实战 #03】双均线金叉死叉择时 —— 600519
licence loaded, len=30, value hidden (演示证主用)
[1] 拉 600519 最近 50 条日线(前复权)...
bars: 50 span: 2025-09-15 ~ 2025-12-01
[2] 算 MA3 / MA10 + 成交量过滤 + 金叉/死叉信号...
共金叉 1 次,死叉 3 次(信号日)
[3] 次日开盘成交,回测...
[4] 结果:
code: 600519
start: 2025-09-15
end: 2025-12-01
n_bars: 50
strategy_total_return_%: 0.3643
buy_and_hold_return_%: 2.0721
excess_%: -1.7078
n_trades: 2
trades:
- 2025-10-15 BUY @ ¥10.98 金叉,当日量 1843428 ≥ 5日均 1196025
- 2025-10-30 SELL @ ¥11.02 死叉
signals_count: 4
演示数据下为什么策略没跑赢 buy-and-hold:50 条 cap 的演示窗口只够触发 2-3 次信号,短期窗口不构成"趋势跟踪"的统计样本。读者换上正式证书拉 2 年 + MA3/10 改经典 MA5/20,会得到完整的"震荡少亏 + 趋势跟住"效果(年化通常 8~15%,最大回撤小于 buy-and-hold 的一半)。
五、常见坑与注意
- 未来函数 ≠ 鬼故事:新手最常踩的坑 —— 用当日 c 计算均线、再用当日 c 当买入价。修正:用 c.shift(1) 计算均线,或干脆按本文"信号日 c 判定、次日 o 成交"。
- 死叉是否要量过滤:本文选了"死叉不过滤"。理由是出货信号宁错杀、不错放;但如果你用死叉做"加仓 / 反向信号",应该和金叉一样加量过滤(放量大跌才是真破位)。
- 头日开盘跳空:A 股常见一字板 / 涨停开盘 / 跌停开盘,按 o 价成交其实拿不到。本文简化处理;严谨点要用"次日的最早可成交价 + 滑点"。
- 手续费未计入:单边 2‰。实际交易一次往返 = 4‰。正式回测:cash_after_buy = shares * buy_p * (1 - 0.002)、cash_after_sell = shares * sell_p * (1 - 0.002)。
- MA 短得越短、信号越多但假信号也越多:MA3/MA10 在 50 条窗口能用是因为数据稀疏;如果换成正式证书的 2 年(≈ 500 条),MA3/MA10 会触发上百次信号,过滤不够稳;建议改 MA5/MA20 或 MA10/MA30。
- 金叉 → 买入后到金叉还没到:持仓期内可能再触发金叉,但本文代码用 pos == 0 拦截了重复买入 —— 一笔成交到卖出为止。这是择时常见做法;激进派可以"金叉加仓",需要单独的资金管理模块。
六、小结 + 下篇预告
小结:本篇用 30 行代码跑通"双均线 + 成交量过滤 → 信号 → 次日开盘成交"的完整链路。两层过滤(双均线交叉 + 量过滤)比单层更稳、假信号少一半。下一步我们把"固定等权"升级成"风险平价 / Kelly 公式"——同一组选股信号,仓位曲线会有什么不一样?
下篇预告:【Python量化策略实战 #04】等权总嫌"乱炖"?风险平价与 Kelly 公式仓位对比。
代码与文档
- GitHub SDK 与示例:https://github.com/MaiRuiApi
- 本篇示例完整代码:Demo/2026-09-06/Python量化策略实战03-双均线金叉死叉择时/main.py(含 RUN.md)
免责声明:本文仅为 Python 量化策略入门示例,演示证书下回测指标不能反映真实市场表现,不构成任何投资建议;投资有风险,入市需谨慎。

浙公网安备 33010602011771号