一、策略介绍
1. 多周期趋势确认+短周期择时
利用了1小时周期均线差与 ATR 的比例判断大趋势方向(trend_direction)。
在趋势方向明确后,才允许在5分钟周期用“分型”做入场判断,有效过滤震荡噪声。
2. 使用分型形态作为入场条件
顶/底分型是经典的技术分析形态,结合趋势方向,有助于提高入场信号的胜率。
进一步要求分型后一根K线的收盘价确认,增强信号可靠性。
3. 每日只开一次仓,避免过度交易
使用 last_trade_date 限制每天只开一次仓,特别适用于日内稳健交易。
二、回测结果
爆仓了。
三、代码
from vnpy_ctastrategy import (
CtaTemplate,
BarData,
OrderData,
TradeData,
ArrayManager,
BarGenerator,
)
from vnpy.trader.constant import Interval, Direction, Offset, Status
from datetime import time, datetime
class SimpleFenxingStrategy(CtaTemplate):
author = "ChatGPT"
fast_ma_window = 10
slow_ma_window = 40
atr_length = 14
pricetick = 1
fixed_size = 1
parameters = [
"fast_ma_window",
"slow_ma_window",
"atr_length",
"pricetick",
"fixed_size",
]
variables = [
"trend_direction", # 1: 上升趋势,-1: 下降趋势,0: 无趋势
"last_trade_date", # 记录上次开仓日期,控制每天只开一次仓
]
def __init__(self, cta_engine, strategy_name, vt_symbol, setting):
super().__init__(cta_engine, strategy_name, vt_symbol, setting)
self.bg_1h = BarGenerator(self.on_bar, window=12, on_window_bar=self.on_1h_bar, interval=Interval.MINUTE)
self.am_1h = ArrayManager(100)
self.bg_5m = BarGenerator(self.on_bar, window=5, on_window_bar=self.on_5m_bar, interval=Interval.MINUTE)
self.am_5m = ArrayManager(150)
self.bars_5m = [] # 缓存5分钟bar用于分型判断
self.trend_direction = 0
self.last_trade_date = None
def on_init(self):
self.load_bar(50)
def on_start(self):
self.write_log("策略启动")
def on_stop(self):
self.write_log("策略停止")
def on_bar(self, bar: BarData):
# 过滤非交易时间(根据实际合约调整)
bar_time = bar.datetime.time()
# 示例:过滤早盘9:00-9:30和下午13:30-14:45的时间段(如果需要)
if (time(9, 0) <= bar_time <= time(9, 30)) or (time(13, 30) <= bar_time <= time(14, 45)):
return
self.bg_1h.update_bar(bar)
self.bg_5m.update_bar(bar)
def on_1h_bar(self, bar: BarData):
self.am_1h.update_bar(bar)
if not self.am_1h.inited:
return
fast_ma = self.am_1h.sma(self.fast_ma_window)
slow_ma = self.am_1h.sma(self.slow_ma_window)
atr = self.am_1h.atr(self.atr_length)
if atr == 0:
self.trend_direction = 0
return
diff = fast_ma - slow_ma
if abs(diff) > 0.35 * atr:
self.trend_direction = 1 if diff > 0 else -1
else:
self.trend_direction = 0
def on_5m_bar(self, bar: BarData):
self.am_5m.update_bar(bar)
if not self.am_5m.inited:
return
self.bars_5m.append(bar)
if len(self.bars_5m) > 10:
self.bars_5m.pop(0)
if len(self.bars_5m) < 7:
return
if self.trend_direction == 0:
# 无趋势,不交易
return
today = bar.datetime.date()
# 每天最多开一次仓,且目前无仓位才允许开仓
if self.pos == 0 and self.last_trade_date == today:
return # 当天已开过仓,跳过
def is_top_fenxing(idx):
if idx < 2 or idx > len(self.bars_5m) - 3:
return False
return (
self.bars_5m[idx].high_price > self.bars_5m[idx - 1].high_price and
self.bars_5m[idx].high_price > self.bars_5m[idx - 2].high_price and
self.bars_5m[idx].high_price > self.bars_5m[idx + 1].high_price and
self.bars_5m[idx].high_price > self.bars_5m[idx + 2].high_price
)
def is_bottom_fenxing(idx):
if idx < 2 or idx > len(self.bars_5m) - 3:
return False
return (
self.bars_5m[idx].low_price < self.bars_5m[idx - 1].low_price and
self.bars_5m[idx].low_price < self.bars_5m[idx - 2].low_price and
self.bars_5m[idx].low_price < self.bars_5m[idx + 1].low_price and
self.bars_5m[idx].low_price < self.bars_5m[idx + 2].low_price
)
idx = len(self.bars_5m) - 4
if idx < 2:
return
# 多仓逻辑(趋势向上)
if self.trend_direction == 1:
# 有仓位,遇顶分型,平仓
if self.pos > 0 and is_top_fenxing(idx):
self.sell(bar.close_price - self.pricetick, abs(self.pos))
self.write_log(f"趋势向上,形成顶分型,平多仓,价格:{bar.close_price}")
return
# 无仓且满足底分型,且分型后一根K线收盘价大于分型收盘价,开多仓
if (
self.pos == 0
and is_bottom_fenxing(idx)
and self.bars_5m[idx + 1].close_price > self.bars_5m[idx].close_price
):
self.buy(bar.close_price + self.pricetick, self.fixed_size)
self.last_trade_date = today
self.write_log(f"趋势向上,底分型开多仓,价格:{bar.close_price}")
return
# 空仓逻辑(趋势向下)
elif self.trend_direction == -1:
# 有空仓位,遇底分型,平仓
if self.pos < 0 and is_bottom_fenxing(idx):
self.cover(bar.close_price + self.pricetick, abs(self.pos))
self.write_log(f"趋势向下,形成底分型,平空仓,价格:{bar.close_price}")
return
# 无仓且满足顶分型,且分型后一根K线收盘价小于分型收盘价,开空仓
if (
self.pos == 0
and is_top_fenxing(idx)
and self.bars_5m[idx + 1].close_price < self.bars_5m[idx].close_price
):
self.short(bar.close_price - self.pricetick, self.fixed_size)
self.last_trade_date = today
self.write_log(f"趋势向下,顶分型开空仓,价格:{bar.close_price}")
return
def on_order(self, order: OrderData):
if order.status == Status.ALLTRADED:
direction = "买入" if order.direction == Direction.LONG else "卖出"
self.write_log(f"{direction}订单成交 {order.volume}手 @{order.price}")
def on_trade(self, trade: TradeData):
if trade.offset == Offset.OPEN:
self.write_log(f"开仓成交 {trade.direction.value} {trade.volume}手 @{trade.price}")
elif trade.offset == Offset.CLOSE:
if self.pos == 0:
self.write_log("平仓完成,仓位清零")