问题描述
解决方案
这是一个基于 Dual Thrust (双重推力) 算法的日内交易策略实现。
策略原理
Dual Thrust 是一种经典的日内突破策略,由 Michael Chalek 开发。它的核心逻辑是根据前 N 天的最高价(High)、最低价(Low)和收盘价(Close)来确定一个震荡区间(Range),结合当天的开盘价(Open)计算出买入和卖出的触发轨。
计算公式:
- Range (震荡区间) = $Max(HH - LC, HC - LL)$
- $HH$: 过去 N 日的最高价
- $LC$: 过去 N 日的最低收盘价
- $HC$: 过去 N 日的最高收盘价
- $LL$: 过去 N 日的最低价
- 上轨 (Buy Line) = 当日开盘价 + $K1 \times Range$
- 下轨 (Sell Line) = 当日开盘价 - $K2 \times Range$
交易逻辑:
- 当价格突破上轨时,做多(买入)。
- 当价格跌破下轨时,平仓或做空(本代码示例为股票策略,故触及下轨时执行平仓操作)。
- 日内策略:每日收盘前(如 14:55)强制平仓,不持仓过夜。
Python 策略代码 (PTrade)
# Dual Thrust 日内交易策略
# 适用频率:分钟级别 (建议 1m 或 5m)
import numpy as np
def initialize(context):
"""
初始化函数,设置策略参数和全局变量
"""
# 1. 设置标的股票 (例如:贵州茅台)
g.security = '600519.SS'
set_universe(g.security)
# 2. 策略参数设置
g.N = 5 # 回溯天数,用于计算 Range
g.K1 = 0.2 # 上轨系数 (Buy Trigger)
g.K2 = 0.2 # 下轨系数 (Sell Trigger)
# 3. 全局变量初始化
g.range = 0.0 # 震荡区间
g.buy_line = 0.0 # 上轨
g.sell_line = 0.0 # 下轨
g.day_open = None # 当日开盘价
# 4. 设置定时任务:收盘前强制平仓 (日内策略)
# 时间设置为 14:55,预留 5 分钟进行清仓
run_daily(context, close_all_positions, time='14:55')
def before_trading_start(context, data):
"""
盘前处理:计算前 N 天的 Range
"""
# 重置当日开盘价状态
g.day_open = None
# 获取过去 N 天的历史数据 (不包含今天)
# 字段:最高价 high, 最低价 low, 收盘价 close
hist = get_history(g.N, '1d', ['high', 'low', 'close'], g.security, fq=None, include=False)
# 如果数据不足 N 天,则不进行计算
if len(hist) < g.N:
log.info("历史数据不足,跳过 Range 计算")
g.range = 0.0
return
# 计算 Dual Thrust 的核心参数
# HH: N日最高价的最高值
hh = hist['high'].max()
# HC: N日收盘价的最高值
hc = hist['close'].max()
# LC: N日收盘价的最低值
lc = hist['close'].min()
# LL: N日最低价的最低值
ll = hist['low'].min()
# Range = Max(HH - LC, HC - LL)
g.range = max(hh - lc, hc - ll)
log.info("今日 Range 计算完成: %.2f" % g.range)
def handle_data(context, data):
"""
盘中运行:每分钟执行一次,判断突破逻辑
"""
# 如果 Range 未计算或为 0,直接返回
if g.range == 0.0:
return
# 1. 获取当日开盘价
# 注意:在分钟回测中,第一根 K 线的 open 即视为当日开盘价
if g.day_open is None:
current_open = data[g.security]['open']
# 简单的过滤:确保开盘价有效
if current_open > 0:
g.day_open = current_open
# 计算上下轨
g.buy_line = g.day_open + g.K1 * g.range
g.sell_line = g.day_open - g.K2 * g.range
log.info("今日开盘价: %.2f, 上轨: %.2f, 下轨: %.2f" % (g.day_open, g.buy_line, g.sell_line))
else:
return # 等待有效开盘价
# 2. 获取当前最新价格
current_price = data[g.security]['close']
# 获取当前持仓数量
position = get_position(g.security).amount
# 获取当前可用资金
cash = context.portfolio.cash
# 3. 交易逻辑
# 情况 A: 价格突破上轨 -> 做多 (买入)
if current_price > g.buy_line:
# 如果当前空仓,则全仓买入
if position == 0:
log.info("价格突破上轨 (%.2f > %.2f),触发买入" % (current_price, g.buy_line))
# 使用 order_value 按金额下单,这里示例为使用所有现金买入
order_value(g.security, cash)
# 情况 B: 价格跌破下轨 -> 平仓 (卖出)
# 注意:如果是股票策略,通常只能平仓;如果是期货,这里可以反手做空
elif current_price < g.sell_line:
# 如果当前持有股票,则清仓卖出
if position > 0:
log.info("价格跌破下轨 (%.2f < %.2f),触发止损/平仓" % (current_price, g.sell_line))
order_target(g.security, 0)
def close_all_positions(context):
"""
收盘前强制平仓函数
"""
position = get_position(g.security).amount
if position > 0:
log.info("日内策略收盘强制平仓")
order_target(g.security, 0)
代码说明与 SEO Q&A
1. 核心函数解析
before_trading_start: 利用get_history获取过去 N 天的日线数据。这是 Dual Thrust 策略的关键,因为它依赖历史波动率来定义当天的震荡区间。handle_data: 这是策略的执行引擎。- 它首先捕获当天的 Open (开盘价)。在 PTrade 分钟级别回测中,我们通常取当天的第一分钟 Open 作为当天的开盘价。
- 计算出
buy_line(上轨) 和sell_line(下轨)。 - 实时监控价格,一旦突破上轨就买入,跌破下轨就卖出。
close_all_positions: 通过run_daily在 14:55 触发。这是日内策略的风控核心,确保不持仓过夜,规避隔夜跳空风险。
2. 参数调整建议
g.N(回溯天数): 默认为 5 天。如果希望策略对短期波动更敏感,可以减小 N;如果希望捕捉更长期的趋势,可以增大 N。g.K1和g.K2: 这是触发系数。- 如果 K 值较小,区间变窄,交易会更频繁,容易出现假突破。
- 如果 K 值较大,区间变宽,信号更稳健,但可能错过部分行情。
- 非对称设置: 在多头市场中,可以设置 $K1 < K2$ (更容易买入);在空头市场中,设置 $K1 > K2$ (更容易卖出)。
Q&A (常见问题)
Q1: 这个策略可以直接用于期货交易吗?
A: 代码逻辑通用,但需要微调。目前的 handle_data 逻辑是“跌破下轨平仓”。如果是期货交易,你可能希望在跌破下轨时开空单 (Short)。你需要将 order_target(g.security, 0) 修改为 order_target_value(g.security, -cash) 或使用 sell_open 等期货专用接口。
Q2: 为什么在 handle_data 里判断 g.day_open?
A: PTrade 的 get_price 接口在回测模式下,获取当日数据有时会有延迟或不包含当日数据。最稳健的方法是在 handle_data 运行的第一时间(即开盘时)直接读取行情的 open 字段并存储在全局变量中,确保全天使用的基准开盘价一致。
Q3: 如何回测这个策略?
A: 在 PTrade 客户端中,新建一个策略,将上述代码粘贴进去。设置回测频率为 “分钟” (这一点很重要,因为是日内策略),选择一个起止时间,设置初始资金即可开始回测。