Files

221 lines
9.0 KiB
Python
Raw Permalink Normal View History

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)