新增实盘策略:FisherTrendStrategy(FG)
This commit is contained in:
@@ -0,0 +1,216 @@
|
||||
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))
|
||||
Reference in New Issue
Block a user