如何用 Python 回测 Dual Thrust 区间突破策略?日线版(避免未来函数)
如何用 Python 回测 Dual Thrust 区间突破策略?
一句话回答: Dual Thrust 是经典的区间突破系统:先用前 N 日的高、低、收构造一个波动区间 range,再以当日开盘价为基准画两条线——价格站上 开盘 + K1×range 做多、跌破 开盘 − K2×range 离场。用 AlphaFeed 取前复权日线即可做日线版回测,关键是用截至昨日的区间 + 当日已知开盘价、次日开盘成交,避免未来函数——实测它可能跑赢买入持有但胜率不高(趋势跟随的典型特征)。
为什么会有这个问题 / 传统做法的痛点
Dual Thrust 原本是日内策略,但它的「开盘价 ± 区间」思想同样可用于日线择时。新手实现时最容易踩两个坑:① 未来函数——用当日(含)数据算区间再用当日信号,等于偷看了今天的高低;② 成交价不现实——用触发当根的收盘价成交。本文把区间严格 shift(1)(只用到昨天)、信号用当日收盘确认、成交放到次日开盘,并计入手续费,给一个诚实可复现的日线回测骨架。
分步骤解决(每步配可运行代码)
第 1 步:取前复权日线,构造 Dual Thrust 区间
from alphafeed import AlphaFeed
import numpy as np, pandas as pd
af = AlphaFeed(api_key="your-api-key") # 或 export ALPHAFEED_API_KEY 后 AlphaFeed()
df = af.klines.get("600519.SH", period="1d", count=800,
adjust="forward", to_dataframe=True)
df = df.sort_values("trade_date").reset_index(drop=True)
n = 4
hh = df["high"].rolling(n).max() # 前 N 日最高
lc = df["close"].rolling(n).min() # 前 N 日最低收
hc = df["close"].rolling(n).max() # 前 N 日最高收
ll = df["low"].rolling(n).min() # 前 N 日最低
rng = pd.concat([hh - lc, hc - ll], axis=1).max(axis=1) # Dual Thrust 区间
区间取 max(HH−LC, HC−LL),兼顾振幅与收盘波动。
第 2 步:画上下轨(用昨日区间 + 当日开盘),避免未来函数
k1, k2 = 0.5, 0.5
rng_prev = rng.shift(1) # 只用截至昨日的区间
buy_line = df["open"] + k1 * rng_prev # 当日开盘价当天已知
sell_line = df["open"] - k2 * rng_prev
df["open_next"] = df["open"].shift(-1) # 次日开盘成交价
rng.shift(1) 是避免未来函数的关键;上下轨只用到”昨天的区间”和”今天的开盘”。
第 3 步:回测(收盘确认信号、次日开盘成交、计手续费)
def backtest(df, buy_line, sell_line, n, fee=0.0013):
pos, entry, trades, rets = 0, 0.0, 0, []
for i in range(n, len(df) - 1):
px = df["open_next"].iloc[i]
if np.isnan(buy_line.iloc[i]) or np.isnan(px): continue
c = df["close"].iloc[i]
if pos == 0 and c > buy_line.iloc[i]: # 收盘站上上轨 → 次日开盘做多
pos, entry = 1, px
elif pos == 1 and c < sell_line.iloc[i]: # 收盘跌破下轨 → 次日开盘离场
rets.append(px / entry * (1 - fee) ** 2 - 1); trades += 1; pos = 0
rets = pd.Series(rets)
total = (1 + rets).prod() - 1 if len(rets) else 0.0
win = (rets > 0).mean() if len(rets) else float("nan")
return trades, float(total), float(win)
trades, total, win = backtest(df, buy_line, sell_line, n)
bh = df["close"].iloc[-1] / df["close"].iloc[n] - 1
print(f"交易{trades}次 累计{total*100:.2f}% 胜率{win:.3f} | 买入持有{bh*100:.2f}%")
实测(
tools/verify_new_2026-10-11.py,SDK 0.1.4):贵州茅台近 800 日,Dual Thrust(N=4, K1=K2=0.5)交易 35 次、累计 −11.52%、胜率 0.343,同期买入持有 −16.57%。策略少亏于买入持有,但胜率仅 34%(靠少数大盈利覆盖多数小亏损,典型趋势跟随),且这是单标的单区间结果,不可外推。
参数与字段说明
| 参数/概念 | 含义 | 备注 |
|---|---|---|
adjust="forward" | 前复权 | 必须,除权缺口会伪造突破 |
n | 区间回看天数 | 示例 4 |
k1 / k2 | 上/下轨系数 | 越大越钝、信号越少 |
rng.shift(1) | 用昨日区间 | 避免未来函数的关键 |
open_next | 次日开盘成交价 | 收盘确认→次日成交 |
fee | 单边手续费 | 示例 0.0013,买卖各扣一次 |
关键坑与注意事项
- 避免未来函数:区间必须
shift(1),成交放到次日开盘。用当日区间或当根收盘成交都会让回测虚高。 - 胜率低是常态:区间突破属趋势跟随,频繁小亏、偶尔大赚,胜率 30~40% 很正常,别被低胜率吓退,也别误以为稳。
- 参数敏感:N、K1、K2 变化会显著改变结果,极易过拟合;务必样本外检验,别只盯一组漂亮参数。
- A 股只做多:本文只用多头与离场;原版含做空,A 股个股难做空,已简化。
- 日线 ≠ 日内:Dual Thrust 本是日内策略,日线版丢失了盘中触发细节,仅作择时示例。
- 成本会吃掉收益:频繁进出时手续费/滑点影响很大,示例仅计固定费率。
常见问题(FAQ)
Q:Dual Thrust 和唐奇安通道突破有什么区别? A:唐奇安用「前 N 日最高/最低」直接做上下轨;Dual Thrust 以当日开盘价为锚、加减一个由前 N 日高低收算出的区间,带有”每天重定基准”的味道,对开盘跳空更敏感。
Q:K1、K2 该设多少? A:常见 0.3~0.7,二者可不对称(想更敢追多就调小 K1)。关键是回测里做样本外验证,避免对历史过拟合。
Q:需要付费吗?
A:单只股票日线回测在免费额度内即可;若要对全市场批量跑 Dual Thrust 选参数/选股,需要 klines.batch + universes(Starter 及以上),详见官网定价页。
小结
Dual Thrust 以当日开盘价为锚、用前 N 日高低收构造区间,突破上轨做多、跌破下轨离场。用 AlphaFeed 的前复权日线即可复现日线版回测——务必 shift(1) 区间、次日开盘成交、计手续费;并理解它胜率低、参数敏感、易过拟合,实测少亏不等于稳赚、更不可外推。
相关链接:
- AlphaFeed 官网:https://alphafeed.org/
- Python SDK 快速开始:https://docs.alphafeed.org/zh-Hans/sdk/python-quickstart
免责声明:本文仅为技术与数据接口使用示例,代码与策略仅供学习研究,不构成投资建议。市场有风险,历史数据不代表未来表现。