Files
NewQuant/futures_trading_strategies/FG/FisherTrendStrategy/FisherTrendStrategy.py

216 lines
9.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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.strategies.base_strategy import Strategy
class PragmaticCyberneticStrategy(Strategy):
"""
【务实优化版·控制论策略】
优化核心:
1. 保持“钝感”:拒绝灵敏的趋势反转平仓,保留“死拿趋势”的盈利特性。
2. 修复系数:将 Stoch 映射系数从 0.66 提升至 1.98,使 Fisher 值域恢复到 [-3, 3] 可调范围。
- 现在你可以设置 exit_level = 2.0 来捕捉极端利润,而不是被迫等到换月。
3. 纯粹逻辑:开仓是开仓,平仓是平仓。互不干扰,消除中间地带的内耗。
"""
def __init__(
self,
context: Any,
main_symbol: str,
enable_log: bool,
trade_volume: int,
min_tick: float = 1.0,
# --- 核心参数 (保持原策略风格) ---
trend_period: int = 26, # T: 趋势中轴
fisher_period: int = 20, # FB: 动能周期 (原策略 46 可能太慢,建议 20-30)
atr_period: int = 23,
# --- 阈值参数 (关键调整) ---
# 1. 止盈阈值:因为系数修复了,现在 FB 能跑到 2.5 以上。
# 建议设为 2.0 - 2.5。如果设得很高(如 5.0),效果就等于你原来的“死拿”。
fisher_exit_level: float = 2.2,
# 2. 入场阈值:保持在 0.5 左右,只接深回调
fb_entry_threshold: float = 0.,
stop_mult: float = 2, # 稍微放宽止损,适应趋势震荡
limit_offset_mult: float = 0.2, # FV 挂单偏移
order_direction: Optional[List[str]] = None,
indicator: Indicator = None,
):
super().__init__(context, main_symbol, enable_log)
self.trade_volume = trade_volume
self.min_tick = min_tick
self.t_len = trend_period
self.f_len = fisher_period
self.atr_len = atr_period
self.fisher_exit_level = fisher_exit_level
self.fb_entry_threshold = fb_entry_threshold
self.stop_mult = stop_mult
self.limit_offset_mult = limit_offset_mult
self.order_direction = order_direction or ['BUY', 'SELL']
self.indicator = indicator
# 缓存
self._buf_len = max(self.t_len, self.f_len, self.atr_len) + 5
self._highs = deque(maxlen=self._buf_len)
self._lows = deque(maxlen=self._buf_len)
self._closes = deque(maxlen=self._buf_len)
# 记录上一根 Bar 的 FB仅用于判断拐点
self._prev_fb = 0.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 _calculate_indicators(self):
"""
计算逻辑:保留原策略的简洁性,仅修复 Scaling 系数
"""
if len(self._closes) < self._buf_len:
return None, None, None, None
# 1. 趋势中轴 T (物理中轴)
h_trend = list(self._highs)[-self.t_len:]
l_trend = list(self._lows)[-self.t_len:]
T = (max(h_trend) + min(l_trend)) / 2.0
# 2. 公平价 FV (FIR 滤波)
FV = (self._closes[-1] + 2 * self._closes[-2] + 2 * self._closes[-3] + self._closes[-4]) / 6.0
# 3. Fisher Transform (非递归版,响应更快)
h_fisher = list(self._highs)[-self.f_len:]
l_fisher = list(self._lows)[-self.f_len:]
max_h, min_l = max(h_fisher), min(l_fisher)
denom = max_h - min_l if max_h != min_l else self.min_tick
# --- 关键修正点 ---
# 你的原代码是 0.66 * (stoc - 0.5),这导致最大值被锁死。
# 改为 1.98,使得输入范围从 [-0.33, 0.33] 扩大到 [-0.99, 0.99]。
# 这样 math.log 就能计算出 -3 到 +3 的值,让止盈逻辑“复活”且可控。
stoc = (self._closes[-1] - min_l) / denom
value = max(-0.999, min(0.999, 1.98 * (stoc - 0.5)))
FB = 0.5 * math.log((1.0 + value) / (1.0 - value))
# 4. ATR
tr_list = []
for i in range(1, self.atr_len + 1):
h, l, pc = self._highs[-i], self._lows[-i], self._closes[-i - 1]
tr = max(h - l, abs(h - pc), abs(l - pc))
tr_list.append(tr)
ATR = sum(tr_list) / self.atr_len
return T, FV, FB, ATR
def on_open_bar(self, open_price: float, symbol: str):
self.symbol = symbol
# 每次 Bar 重置挂单,防止挂单“长在”图表上
self.cancel_all_pending_orders(self.symbol)
bars = self.get_bar_history()
if len(bars) < 1: return
prev_bar = bars[-1]
self._highs.append(prev_bar.high)
self._lows.append(prev_bar.low)
self._closes.append(prev_bar.close)
T, FV, FB, ATR = self._calculate_indicators()
if T is None: return
pos = self.get_current_positions().get(self.symbol, 0)
entry_price = self.get_average_position_price(self.symbol)
# 状态定义
# 这里我们不去定义 "short_signal" 这种会诱发反向平仓的变量
# 而是只关注眼下的:大势(T) 和 动能(FB)
trend_up = prev_bar.close > T
trend_down = prev_bar.close < T
# ==========================================
# 1. 持仓逻辑:简单、迟钝、粘性强
# ==========================================
if pos != 0:
stop_dist = max(self.stop_mult * ATR, self.min_tick * 20)
if pos > 0:
# A. 动能止盈 (Mean Reversion Exit)
# 只有当行情极其疯狂(FB > 2.2) 且开始回头时才止盈。
# 正常波动绝不下车。
if FB > self.fisher_exit_level and FB < self._prev_fb:
self.log(f"TAKE PROFIT (Long): FB {FB:.2f} Peak Reached")
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
return
# B. 硬止损 (ATR Stop) - 最后的防线
if prev_bar.close < entry_price - stop_dist:
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
return
# C. (可选) 极端趋势反转保护
# 如果你希望策略更像原来的“死拿”,这部分可以注释掉,或者把判定条件设严
# if prev_bar.close < T - ATR: ...
elif pos < 0:
# A. 动能止盈
if FB < -self.fisher_exit_level and FB > self._prev_fb:
self.log(f"TAKE PROFIT (Short): FB {FB:.2f} Bottom Reached")
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
return
# B. 硬止损
if prev_bar.close > entry_price + stop_dist:
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
return
# ==========================================
# 2. 开仓逻辑:严苛、左侧、顺大势
# ==========================================
if pos == 0:
is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())
# 挂单价格优化:
# 在 FV 基础上再便宜一点点,增加胜率
long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR))
short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR))
# 开多:趋势向上 + 动能深跌 (FB < -0.5)
if trend_up and FB < -self.fb_entry_threshold and is_met:
if "BUY" in self.order_direction:
self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN")
# 开空:趋势向下 + 动能冲高 (FB > 0.5)
elif trend_down and FB > self.fb_entry_threshold and is_met:
if "SELL" in self.order_direction:
self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN")
# 记录上一根FB仅用于止盈时的拐点比较
self._prev_fb = FB
# --- 辅助 ---
def send_market_order(self, direction, volume, offset):
ts = self.get_current_time().strftime('%H%M%S')
oid = f"{self.symbol}_{direction}_MKT_{ts}_{self.order_id_counter}"
self.order_id_counter += 1
self.send_order(Order(id=oid, symbol=self.symbol, direction=direction, volume=volume, price_type="MARKET",
submitted_time=self.get_current_time(), offset=offset))
def send_limit_order(self, price, direction, volume, offset):
ts = self.get_current_time().strftime('%H%M%S')
oid = f"{self.symbol}_{direction}_LMT_{ts}_{self.order_id_counter}"
self.order_id_counter += 1
self.send_order(Order(id=oid, symbol=self.symbol, direction=direction, volume=volume, price_type="LIMIT",
submitted_time=self.get_current_time(), offset=offset, limit_price=price))