
2026年重磅升级已全面落地!欢迎加入专注财经数据与量化投研的【数据科学实战】知识星球!您将获取持续更新的《财经数据宝典》与《量化投研宝典》,双典协同提供系统化指引;星球内含300篇以上独有高质量文章,深度覆盖策略开发、因子分析、风险管理等核心领域,内容基本每日更新;同步推出的「量化因子专题教程」系列(含完整可运行代码与实战案例),系统详解因子构建、回测与优化全流程,并实现日更迭代。我们持续扩充独家内容资源,全方位赋能您的投研效率与专业成长。无论您是量化新手还是资深研究者,这里都是助您少走弯路、事半功倍的理想伙伴,携手共探数据驱动的投资未来!
在量化交易的世界里,价格图表乍看像是无规律的噪音——K 线不停地上下跳动。但隐藏在这些噪音背后的,是交易者们研究了数十年的模式和结构。
今天这篇文章,将带你拆解一个有趣的 Python 量化交易策略:将经典的日本 K 线反转形态(看跌吞没)与一目均衡图(Ichimoku Cloud)相结合,构建一套规则驱动的交易系统,并在长达 25 年的历史数据上进行回测。
这套策略的核心思想非常优雅——用短期恐慌信号入场,用长期趋势结构出场。让我们一步步来看它是如何实现的。
策略回测的第一步是拿到足够长的历史数据。我们使用 yfinance 下载 CNP(CenterPoint Energy)从 2000 年到 2025 年底的日线数据。
import pandas as pd
import numpy as np
import yfinance as yf
import vectorbt as vbt
# -------------------------
# 下载数据
# -------------------------
symbol = "CNP" # 股票代码
start_date = "2000-01-01" # 起始日期
end_date = "2026-01-01" # 结束日期
interval = "1d" # 日线级别
# 通过 yfinance 下载历史数据
df = yf.download(symbol, start=start_date, end=end_date, interval=interval)
# 保存为 CSV 文件备用
df.to_csv("CNP_clean.csv", index=False)
df为什么要用 25 年的数据? 短期回测可能会"骗人"。只有跨越多个市场周期(泡沫、崩盘、横盘、复苏),才能真正检验一个策略的生存能力。
这套系统建立在两个截然不同的市场信号之间的"对话"上。
看跌吞没是经典的 K 线反转形态。它的出现条件是:
简单来说,这是多头力量被空头彻底压制的信号——买方先推高价格,但卖方随后完全接管了控制权。
def bearish_engulfing(df):
"""
检测看跌吞没形态
返回布尔型 Series,True 表示当天出现了看跌吞没
"""
prev_close = df['Close'].shift(1) # 前一天收盘价
prev_open = df['Open'].shift(1) # 前一天开盘价
return (
(prev_close > prev_open) & # 前一天是阳线
(df['Close'] < df['Open']) & # 今天是阴线
(df['Open'] > prev_close) & # 今天开盘高于昨天收盘
(df['Close'] < prev_open) # 今天收盘低于昨天开盘
)注意:原文 PDF 中的代码对"前一天阳线"的判断写成了
prev_close < prev_open(即前一天是阴线),这与看跌吞没的标准定义不符。标准定义要求前一天是阳线(prev_close > prev_open),上面的代码已做修正。
如果看跌吞没是"警报",那一目均衡图就是"天气系统"。
一目均衡图是一套综合性的技术分析工具,包含以下几条线:
# 一目均衡图参数
ICHIMOKU_DISPLACEMENT = 26 # 位移周期
ICHIMOKU_KIJUN_PERIOD = 26 # 基准线周期
ICHIMOKU_SENKOU_B_PERIOD = 52 # 先行带 B 周期
ICHIMOKU_TENKAN_PERIOD = 9 # 转换线周期
def calculate_ichimoku(df,
tenkan_period=ICHIMOKU_TENKAN_PERIOD,
kijun_period=ICHIMOKU_KIJUN_PERIOD,
senkou_b_period=ICHIMOKU_SENKOU_B_PERIOD,
displacement=ICHIMOKU_DISPLACEMENT):
"""
计算一目均衡图的各条线
"""
df = df.copy()
# 转换线:9 日最高价与最低价的均值
df['tenkan_sen'] = (
df['High'].rolling(tenkan_period).max() +
df['Low'].rolling(tenkan_period).min()
) / 2
# 基准线:26 日最高价与最低价的均值
df['kijun_sen'] = (
df['High'].rolling(kijun_period).max() +
df['Low'].rolling(kijun_period).min()
) / 2
# 先行带 A:转换线与基准线的均值,向前位移 26 日
df['senkou_span_a'] = (
(df['tenkan_sen'] + df['kijun_sen']) / 2
).shift(displacement)
# 先行带 B:52 日最高价与最低价的均值,向前位移 26 日
df['senkou_span_b'] = (
(df['High'].rolling(senkou_b_period).max() +
df['Low'].rolling(senkou_b_period).min()) / 2
).shift(displacement)
# 迟行带:收盘价向后位移 26 日
df['chikou_span'] = df['Close'].shift(-displacement)
return df这套策略并不使用一目均衡图的全部信号,而是专注于一个特定事件:先行带 A 从下方向上穿越先行带 B,即看涨交叉。这意味着前方的趋势结构正在转向看涨——此时选择出场。
def senkou_span_cross_bullish(df):
"""
检测先行带 A 向上穿越先行带 B 的信号(看涨交叉)
返回布尔型 Series,True 表示当天发生了看涨交叉
"""
df = calculate_ichimoku(df)
return (
(df['senkou_span_a'].shift(1) < df['senkou_span_b'].shift(1)) &
(df['senkou_span_a'] >= df['senkou_span_b'])
)策略的逻辑非常简洁且具有不对称美感:
# -------------------------
# 入场条件
# -------------------------
df["Bearish_Engulfing"] = bearish_engulfing(df)
# -------------------------
# 出场条件
# -------------------------
df["Ichimoku_Senkou_Span_Cross_Bullish"] = senkou_span_cross_bullish(df)
# -------------------------
# 生成交易信号
# -------------------------
entry_conditions = ['Bearish_Engulfing'] # 入场条件列表
exit_conditions = ['Ichimoku_Senkou_Span_Cross_Bullish'] # 出场条件列表
# 当所有入场条件同时满足时,生成入场信号
df['entry_signal'] = df[entry_conditions].all(axis=1)
# 当所有出场条件同时满足时,生成出场信号
df['exit_signal'] = df[exit_conditions].all(axis=1)使用 vectorbt 进行回测时,策略遵循以下规则以确保公平性:
# -------------------------
# 回测执行
# -------------------------
# 将信号向后移动一天,避免使用未来数据
shift_entries = df['entry_signal'].shift(1).astype(bool).fillna(False).to_numpy()
shift_exits = df['exit_signal'].shift(1).astype(bool).fillna(False).to_numpy()
# 使用 vectorbt 构建投资组合并回测
pf = vbt.Portfolio.from_signals(
close=df['Open'], # 以开盘价成交
entries=shift_entries, # 入场信号
exits=shift_exits, # 出场信号
init_cash=100_000, # 初始资金 10 万美元
fees=0.001, # 手续费 0.1%
slippage=0.002, # 滑点 0.2%
freq='1d' # 日线频率
)
# 输出回测统计信息
print(pf.stats())
# 绘制权益曲线
pf.plot().show()同时,我们也可以计算"买入并持有"策略作为基准对比:
# -------------------------
# 买入持有基准
# -------------------------
df_holding = df['Open']
pf_holding = vbt.Portfolio.from_holding(
df_holding,
init_cash=100_000, # 初始资金 10 万美元
fees=0.001 # 手续费 0.1%
)
print(pf_holding.stats())以下是该策略在 2000 年至 2025 年间的核心回测指标:
这套策略在 25 年的回测中跑赢了买入持有策略。盈亏比达到 2.25,说明虽然胜率不到一半,但每次盈利的幅度远大于亏损。68 笔交易意味着平均每年不到 3 笔,避免了过度交易。更重要的是,它将微观层面的 K 线形态与宏观层面的趋势结构巧妙结合。
88% 的最大回撤极为惊人,在实盘中几乎不可承受。49% 的胜率意味着超过一半的交易是亏损的,对交易者心理是巨大考验。此外,入场信号基于下跌形态而非确认信号,时机偏早。实际部署时必须叠加风控手段,如止损、仓位管理等。
这套策略真正有趣的地方不仅是回测数字,而是它背后的设计哲学:
它证明了一件事:不是所有的交易优势都来自复杂模型。有时候,优势恰恰来自"对比"——短期恐慌与长期结构之间的反差。
当然,原文作者也坦诚指出:仅仅在历史数据上回测 25 年是不够的。真正投入实盘之前,还需要进行更多的鲁棒性测试,比如滚动窗口优化(Walk-Forward Optimization)、跨标的验证等。
本文通过拆解一篇量化交易策略文章,带大家用 Python 实现了一套结合"看跌吞没形态"和"一目均衡图云层交叉"的交易系统。
这套策略的核心要点包括:
yfinance 获取长周期历史数据vectorbt 进行信号驱动的回测,包含手续费和滑点最重要的启示:量化交易不是追求完美胜率,而是在概率和赔率之间寻找平衡。一个胜率不到 50% 的策略,只要盈亏比足够高,依然可以长期盈利。但高回撤提醒我们,任何策略都需要配合严格的风险管理。
本文仅供学习交流,不构成任何投资建议。量化交易涉及风险,请务必在充分研究后再做决策。
2026年全面升级已落地!【数据科学实战】知识星球核心权益如下:
星球已沉淀丰富内容生态——涵盖量化文章专题教程库、因子日更系列、高频数据集、PyBroker实战课程、专家深度分享与实时答疑服务。无论您是初探量化的学习者,还是深耕领域的从业者,这里都是助您少走弯路、高效成长的理想平台。诚邀加入,共探数据驱动的投资未来!
好文推荐
1. 用 Python 打造股票预测系统:Transformer 模型教程(一)
2. 用 Python 打造股票预测系统:Transformer 模型教程(二)
3. 用 Python 打造股票预测系统:Transformer 模型教程(三)
4. 用 Python 打造股票预测系统:Transformer 模型教程(完结)
6. YOLO 也能预测股市涨跌?计算机视觉在股票市场预测中的应用