首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >如何用 Python 回测 KDJ 金叉死叉策略?

如何用 Python 回测 KDJ 金叉死叉策略?

原创
作者头像
用户7083614
发布于 2026-10-11 10:00:46
发布于 2026-10-11 10:00:46
610
举报

一句话回答: KDJ 金叉(K 上穿 D)买入、死叉(K 下穿 D)卖出是最经典的随机指标策略。用 AlphaFeed 取回前复权日线算 KDJ,用「当日出信号、次日开盘成交」避免未来函数,再扣手续费,几十行就能回测出收益、胜率与最大回撤,还能加 J 值超卖过滤减少震荡假信号。

为什么会有这个问题 / 传统做法的痛点

KDJ 金叉死叉人人会画,但「到底赚不赚钱」要靠回测验证。新手常犯两个错:一是用未来函数——当日收盘出信号却按当日收盘价成交(现实中你收盘才知道信号,买不到当日价);二是忽略手续费与震荡假信号,把回测做得过于乐观。正确姿势:前复权价算指标、次日开盘成交、扣费、可选超卖过滤。

分步骤解决(每步配可运行代码)

第 1 步:取前复权日线并计算 KDJ

代码语言:python
复制
from alphafeed import AlphaFeed
import pandas as pd

af = AlphaFeed()  # 读取 ALPHAFEED_API_KEY

df = af.klines.get("600519.SH", period="1d", count=500,
                   adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)

def kdj(df, n=9, k_smooth=3, d_smooth=3):
    low_n = df["low"].rolling(n).min()
    high_n = df["high"].rolling(n).max()
    rsv = (df["close"] - low_n) / (high_n - low_n) * 100
    k = rsv.ewm(com=k_smooth - 1, adjust=False).mean()
    d = k.ewm(com=d_smooth - 1, adjust=False).mean()
    j = 3 * k - 2 * d
    return k, d, j

df["K"], df["D"], df["J"] = kdj(df)

第 2 步:生成信号(当日)并次日开盘成交(避免未来函数)

代码语言:python
复制
# 金叉:K 上穿 D;死叉:K 下穿 D
df["golden"] = (df["K"] > df["D"]) & (df["K"].shift() <= df["D"].shift())
df["dead"] = (df["K"] < df["D"]) & (df["K"].shift() >= df["D"].shift())

# 可选:只在 J 值超卖(<20)区域附近的金叉才买,过滤高位假金叉
USE_J_FILTER = True
buy_sig = df["golden"] & (df["J"] < 20) if USE_J_FILTER else df["golden"]

# 关键:信号发生在 t 日收盘,t+1 日开盘才成交 → 持仓左移体现次日执行
df["signal"] = 0
df.loc[buy_sig, "signal"] = 1
df.loc[df["dead"], "signal"] = 0
df["position"] = df["signal"].replace(0, pd.NA).ffill().fillna(0).astype(int)
df["position"] = df["position"].shift(1).fillna(0)   # 次日才持仓

第 3 步:按次日开盘价计算带手续费的收益

代码语言:python
复制
FEE = 0.0013  # 单边约 0.13%(佣金+印花税近似),买卖各扣

df["open_ret"] = df["open"].pct_change().shift(-1)   # t 日持仓吃 t→t+1 的开盘间收益
df["trade"] = df["position"].diff().abs().fillna(0)  # 换手(0→1 或 1→0)
df["strat_ret"] = df["position"] * df["open_ret"].fillna(0) - df["trade"] * FEE
df["equity"] = (1 + df["strat_ret"]).cumprod()

第 4 步:输出绩效指标

代码语言:python
复制
import numpy as np

total = df["equity"].iloc[-1] - 1
days = len(df)
cagr = df["equity"].iloc[-1] ** (252 / days) - 1
dd = (df["equity"] / df["equity"].cummax() - 1).min()
trades = int(df["trade"].sum() // 2)
wins = ((df["strat_ret"] > 0) & (df["position"] == 1)).sum()
hold_days = int((df["position"] == 1).sum())

print(f"累计收益 {total:.2%} | 年化 {cagr:.2%} | 最大回撤 {dd:.2%}")
print(f"交易次数 {trades} | 持仓天数 {hold_days} | 盈利日 {wins}")

关键参数:

参数

含义

本文取值

n / k_smooth / d_smooth

KDJ 周期与平滑

9 / 3 / 3(经典)

USE_J_FILTER

仅超卖区金叉才买

J<20

FEE

单边手续费

0.0013

成交时点

避免未来函数

次日开盘

关键坑与注意事项

  • 避免未来函数:信号当日收盘产生,必须次日开盘成交;本文用 position.shift(1) + 次日开盘收益实现。
  • 前复权价:未复权价在除权日会让 KDJ 与收益都失真。
  • 手续费不能省:KDJ 换手偏频繁,忽略费用会严重高估收益;本文单边 0.13% 仅为近似,按你的佣金调整。
  • J 值过滤是双刃:过滤高位假金叉能提胜率,但也会漏掉强势趋势中的买点,需按品种验证。
  • 单标的回测≠策略有效:应在多只、多时间段上验证,避免过拟合与幸存者偏差。
  • A 股 T+1 与涨停买不进:本文为简化模型,未完全模拟 T+1、涨停无法成交等,实盘更保守。

常见问题(FAQ)

Q:为什么不按当日收盘价成交?

A:当日收盘才算出金叉,你无法用当日收盘价买入——那是未来函数。用次日开盘价才真实可执行。

Q:KDJ 参数用 9/3/3 还是别的?

A:9/3/3 是最经典设置。周期越短越灵敏、假信号越多。建议先用经典参数,再按品种调。

Q:为什么加了 J<20 过滤反而交易变少?

A:因为只保留超卖区的金叉,过滤掉了高位金叉,交易更少但质量更高,是否更优要看回测结果。

Q:需要付费吗?

A:单只历史日线有免费额度;多标的批量回测(klines.batch + universes)需 Starter 及以上。详见定价页。

小结

KDJ 金叉死叉回测的关键不在指标本身,而在回测的严谨性:前复权价算 KDJ、次日开盘成交避免未来函数、扣手续费、可选 J 值超卖过滤。用 AlphaFeed 取回前复权日线,几十行 Python 就能得到收益、胜率与最大回撤,客观检验这套经典信号到底值不值。

参考

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

目录
  • 为什么会有这个问题 / 传统做法的痛点
  • 分步骤解决(每步配可运行代码)
    • 第 1 步:取前复权日线并计算 KDJ
    • 第 2 步:生成信号(当日)并次日开盘成交(避免未来函数)
    • 第 3 步:按次日开盘价计算带手续费的收益
    • 第 4 步:输出绩效指标
  • 关键坑与注意事项
  • 常见问题(FAQ)
  • 小结
  • 参考
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档