221 lines
9.0 KiB
Python
221 lines
9.0 KiB
Python
import numpy as np
|
||
import math
|
||
from collections import deque
|
||
from typing import Optional, Any, List, Dict
|
||
|
||
from src.core_data import Bar, Order
|
||
from src.indicators.base_indicators import Indicator
|
||
from src.indicators.indicators import Empty
|
||
from src.strategies.base_strategy import Strategy
|
||
|
||
|
||
class ITrendStrategy(Strategy):
|
||
"""
|
||
【Ehlers 瞬时趋势线策略 (期货实战版)】
|
||
|
||
针对期货日内交易优化:
|
||
1. 【跳数止损】:使用固定的 min_tick 跳数作为止损,替代百分比。
|
||
2. 【价格对齐】:所有计算出的挂单价格,强制对齐到 min_tick 的整数倍。
|
||
3. 【无反手】:止损或信号反转仅平仓。
|
||
|
||
参数含义变更:
|
||
- min_tick: 合约最小变动价位 (如 IF为0.2, RB为1)
|
||
- stop_loss_ticks: 止损跳数 (如 50跳)
|
||
"""
|
||
|
||
def __init__(
|
||
self,
|
||
context: Any,
|
||
main_symbol: str,
|
||
enable_log: bool,
|
||
trade_volume: int,
|
||
# --- 【合约规格参数】 ---
|
||
min_tick: float = 1.0, # 核心:必须根据品种设置 (例如 0.2, 1.0, 5.0)
|
||
|
||
# --- 【策略参数】 ---
|
||
length: int = 20, # 趋势线周期
|
||
range_fraction: float = 0.35, # 入场回调系数 (Range的比例)
|
||
stop_loss_ticks: int = 30, # 【新】硬止损跳数 (替代百分比)
|
||
|
||
# --- 【其他】 ---
|
||
order_direction: Optional[List[str]] = None,
|
||
indicator: Indicator = None,
|
||
):
|
||
super().__init__(context, main_symbol, enable_log)
|
||
if order_direction is None: order_direction = ['BUY', 'SELL']
|
||
|
||
self.trade_volume = trade_volume
|
||
self.min_tick = min_tick
|
||
self.rng_frac = range_fraction
|
||
self.stop_ticks = stop_loss_ticks # 止损跳数
|
||
self.order_direction = order_direction
|
||
self.alpha = 2.0 / (length + 1.0)
|
||
|
||
self.indicator = indicator
|
||
|
||
# 历史数据缓存
|
||
self._mid_price_history = deque(maxlen=50)
|
||
self._itrend_history = deque(maxlen=50)
|
||
self._trigger_history = deque(maxlen=50)
|
||
|
||
self.bar_count = 0
|
||
self.order_id_counter = 0
|
||
|
||
def round_to_tick(self, price: float) -> float:
|
||
"""辅助函数:将价格对齐到最小变动价位"""
|
||
if self.min_tick <= 0: return price
|
||
return round(price / self.min_tick) * self.min_tick
|
||
|
||
def on_init(self):
|
||
super().on_init()
|
||
|
||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||
self.log(f"合约换月: {old_symbol} -> {new_symbol},重置状态。")
|
||
self._mid_price_history.clear()
|
||
self._itrend_history.clear()
|
||
self._trigger_history.clear()
|
||
self.bar_count = 0
|
||
|
||
self.symbol = new_symbol
|
||
self.cancel_all_pending_orders(old_symbol)
|
||
|
||
def on_open_bar(self, open_price: float, symbol: str):
|
||
self.symbol = symbol
|
||
bars = self.get_bar_history()
|
||
if len(bars) < 2: return
|
||
self.cancel_all_pending_orders(symbol)
|
||
|
||
prev_bar = bars[-1]
|
||
|
||
# --- 1. 指标计算 ---
|
||
mid_price = (prev_bar.high + prev_bar.low) / 2.0
|
||
self._mid_price_history.append(mid_price)
|
||
self.bar_count += 1
|
||
|
||
if len(self._mid_price_history) < 3:
|
||
self._itrend_history.append(mid_price)
|
||
self._trigger_history.append(mid_price)
|
||
return
|
||
|
||
price = list(self._mid_price_history)
|
||
itrend_prev = list(self._itrend_history)
|
||
|
||
current_itrend = 0.0
|
||
if self.bar_count < 7:
|
||
current_itrend = (price[-1] + 2 * price[-2] + price[-3]) / 4.0
|
||
else:
|
||
alpha = self.alpha
|
||
a2 = alpha * alpha
|
||
current_itrend = (alpha - a2 / 4) * price[-1] + (a2 / 2) * price[-2] - (alpha - 0.75 * a2) * price[-3] + \
|
||
2 * (1 - alpha) * itrend_prev[-1] - (1 - alpha) ** 2 * itrend_prev[-2]
|
||
|
||
self._itrend_history.append(current_itrend)
|
||
|
||
if len(self._itrend_history) < 3:
|
||
current_trigger = current_itrend
|
||
else:
|
||
current_trigger = 2.0 * current_itrend - self._itrend_history[-3]
|
||
self._trigger_history.append(current_trigger)
|
||
|
||
# --- 2. 交易决策 ---
|
||
if len(self._trigger_history) < 2: return
|
||
|
||
curr_trig = self._trigger_history[-1]
|
||
prev_trig = self._trigger_history[-2]
|
||
curr_itrend = self._itrend_history[-1]
|
||
prev_itrend = self._itrend_history[-2]
|
||
|
||
is_bullish = (prev_trig <= prev_itrend) and (curr_trig > curr_itrend)
|
||
is_bearish = (prev_trig >= prev_itrend) and (curr_trig < curr_itrend)
|
||
|
||
position_volume = self.get_current_positions().get(self.symbol, 0)
|
||
|
||
prev_range = prev_bar.high - prev_bar.low
|
||
|
||
# === 分支 A: 持仓管理 (止损/平仓) ===
|
||
if position_volume != 0:
|
||
|
||
entry_price = self.get_average_position_price(self.symbol)
|
||
|
||
# --- A1. 强制跳数止损 (Tick Stop) ---
|
||
# 计算止损价差绝对值
|
||
stop_diff = self.stop_ticks * self.min_tick
|
||
|
||
self.log(f'Holding {position_volume} position volume'
|
||
f'is_bullish={is_bullish}, is_bearish={is_bearish}'
|
||
f'entry_price={entry_price}, stop_diff={stop_diff}')
|
||
|
||
if position_volume > 0: # 多头
|
||
# 止损价 = 开仓价 - 止损点数
|
||
stop_price = entry_price - stop_diff
|
||
# 对齐一下(虽然entry_price理论上已对齐,但为了保险)
|
||
stop_price = self.round_to_tick(stop_price)
|
||
|
||
if prev_bar.close < stop_price:
|
||
self.log(
|
||
f"LONG STOP TRIGGERED. Close:{prev_bar.close} < Stop:{stop_price} (-{self.stop_ticks} ticks)")
|
||
self.send_market_order("CLOSE_LONG", abs(position_volume), "CLOSE")
|
||
return
|
||
|
||
elif position_volume < 0: # 空头
|
||
# 止损价 = 开仓价 + 止损点数
|
||
stop_price = entry_price + stop_diff
|
||
stop_price = self.round_to_tick(stop_price)
|
||
|
||
if prev_bar.close > stop_price:
|
||
self.log(
|
||
f"SHORT STOP TRIGGERED. Close:{prev_bar.close} > Stop:{stop_price} (+{self.stop_ticks} ticks)")
|
||
self.send_market_order("CLOSE_SHORT", abs(position_volume), "CLOSE")
|
||
return
|
||
|
||
# --- A2. 信号平仓 ---
|
||
if position_volume > 0 and is_bearish:
|
||
self.send_market_order("CLOSE_LONG", abs(position_volume), "CLOSE")
|
||
return
|
||
|
||
if position_volume < 0 and is_bullish:
|
||
self.send_market_order("CLOSE_SHORT", abs(position_volume), "CLOSE")
|
||
return
|
||
|
||
# === 分支 B: 开仓管理 ===
|
||
else: # position_volume == 0
|
||
|
||
# 计算回调距离 (Range Fraction)
|
||
# 例如 Range=10, Frac=0.35 -> 3.5 -> 对齐到tick
|
||
raw_offset = self.rng_frac
|
||
offset_price = self.round_to_tick(raw_offset)
|
||
|
||
self.log(f'RANDOM OFFSET: {offset_price}, is_bullish={is_bullish}, is_bearish={is_bearish}')
|
||
|
||
# 开多
|
||
if is_bullish and "BUY" in self.order_direction and (self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())):
|
||
# 挂单价 = Open - 回调点数
|
||
limit_price = prev_bar.close - offset_price
|
||
limit_price = self.round_to_tick(limit_price) # 再次确保对齐
|
||
|
||
self.send_limit_order(limit_price, "BUY", self.trade_volume, "OPEN")
|
||
self.log(f"Signal BUY. Open:{open_price} - Offset:{offset_price} = Limit:{limit_price}")
|
||
|
||
# 开空
|
||
elif is_bearish and "SELL" in self.order_direction and (self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())):
|
||
# 挂单价 = Open + 回调点数
|
||
limit_price = prev_bar.close + offset_price
|
||
limit_price = self.round_to_tick(limit_price)
|
||
|
||
self.send_limit_order(limit_price, "SELL", self.trade_volume, "OPEN")
|
||
self.log(f"Signal SELL. Open:{open_price} + Offset:{offset_price} = Limit:{limit_price}")
|
||
|
||
# --- 交易辅助函数 ---
|
||
def send_market_order(self, direction: str, volume: int, offset: str):
|
||
order_id = f"{self.symbol}_{direction}_MKT_{self.order_id_counter}"
|
||
self.order_id_counter += 1
|
||
order = Order(id=order_id, symbol=self.symbol, direction=direction, volume=volume, price_type="MARKET",
|
||
submitted_time=self.get_current_time(), offset=offset)
|
||
self.send_order(order)
|
||
|
||
def send_limit_order(self, limit_price: float, direction: str, volume: int, offset: str):
|
||
order_id = f"{self.symbol}_{direction}_LMT_{self.order_id_counter}"
|
||
self.order_id_counter += 1
|
||
order = Order(id=order_id, symbol=self.symbol, direction=direction, volume=volume, price_type="LIMIT",
|
||
submitted_time=self.get_current_time(), offset=offset, limit_price=limit_price)
|
||
self.send_order(order) |