新增实盘策略: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))
|
||||
@@ -0,0 +1,224 @@
|
||||
import numpy as np
|
||||
import math
|
||||
import talib
|
||||
from collections import deque
|
||||
from typing import Optional, Any, List
|
||||
|
||||
from src.core_data import Bar, Order
|
||||
from src.indicators.base_indicators import Indicator
|
||||
from src.strategies.base_strategy import Strategy
|
||||
|
||||
|
||||
class PragmaticCyberneticStrategy(Strategy):
|
||||
"""
|
||||
【务实回归版·控制论策略】(TA-Lib 提速 + 换月支持)
|
||||
|
||||
哲学坚守:
|
||||
1. 捍卫原版出场逻辑:坚决使用基于 entry_price 的固定硬止损。脱离成本区后,给予价格无限宽容度,实现长线死拿。
|
||||
2. 动能极值止盈:恢复 FB > 1.6 且拐头时的精准落袋为安。
|
||||
3. 拒绝画蛇添足:没有任何 _target_state,不堆砌 Trick,底层数据即真理。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
min_tick: float = 1.0,
|
||||
|
||||
# --- 核心周期 ---
|
||||
trend_period: int = 26,
|
||||
fisher_period: int = 20,
|
||||
atr_period: int = 23,
|
||||
|
||||
# --- 经过验证的优秀参数 ---
|
||||
fisher_exit_level: float = 1.6, # 配合 1.98 修复后的 FB,1.6 是一个非常好的极值止盈点
|
||||
fb_entry_threshold: float = 0.1, # 0.1 确保在顺大势的前提下,动能稍有配合即可入场
|
||||
|
||||
# --- 风险控制 ---
|
||||
stop_mult: float = 2.0, # 原始的硬止损乘数
|
||||
limit_offset_mult: float = 0.,
|
||||
|
||||
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
|
||||
|
||||
# 为 talib.ATR 预留充足的数据预热空间
|
||||
self._buf_len = max(self.t_len, self.f_len, self.atr_len * 2) + 100
|
||||
self._highs = deque(maxlen=self._buf_len)
|
||||
self._lows = deque(maxlen=self._buf_len)
|
||||
self._closes = deque(maxlen=self._buf_len)
|
||||
|
||||
# 使用 deque 记录 FB,相比单独的 _prev_fb 变量更安全,不会因为初始 0 导致误判
|
||||
self._fbs = deque(maxlen=2)
|
||||
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):
|
||||
"""完全无状态的纯数学指标计算 (基于 TA-Lib)"""
|
||||
if len(self._closes) < self._buf_len:
|
||||
return None, None, None, None
|
||||
|
||||
np_highs = np.array(self._highs, dtype=np.float64)
|
||||
np_lows = np.array(self._lows, dtype=np.float64)
|
||||
np_closes = np.array(self._closes, dtype=np.float64)
|
||||
|
||||
# 1. T轴 (大势过滤)
|
||||
t_max_h = talib.MAX(np_highs, timeperiod=self.t_len)[-1]
|
||||
t_min_l = talib.MIN(np_lows, timeperiod=self.t_len)[-1]
|
||||
T = (t_max_h + t_min_l) / 2.0
|
||||
|
||||
# 2. FV (公允价值挂单基准)
|
||||
FV = (np_closes[-1] + 2 * np_closes[-2] + 2 * np_closes[-3] + np_closes[-4]) / 6.0
|
||||
|
||||
# 3. Fisher Transform (1.98 数学修复版)
|
||||
f_max_h = talib.MAX(np_highs, timeperiod=self.f_len)[-1]
|
||||
f_min_l = talib.MIN(np_lows, timeperiod=self.f_len)[-1]
|
||||
denom = f_max_h - f_min_l if f_max_h != f_min_l else self.min_tick
|
||||
|
||||
stoc = (np_closes[-1] - f_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
|
||||
atr_array = talib.ATR(np_highs, np_lows, np_closes, timeperiod=self.atr_len)
|
||||
ATR = atr_array[-1]
|
||||
|
||||
return T, FV, FB, ATR
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
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 or math.isnan(ATR): return
|
||||
|
||||
self._fbs.append(FB)
|
||||
# 必须凑齐两根 FB 才能判断拐点,否则跳过
|
||||
if len(self._fbs) < 2: return
|
||||
|
||||
current_fb = self._fbs[-1]
|
||||
prev_fb = self._fbs[-2]
|
||||
|
||||
# 绝对无状态:从账户物理数据获取真实持仓和开仓均价
|
||||
pos = self.get_current_positions().get(self.symbol, 0)
|
||||
entry_price = self.get_average_position_price(self.symbol)
|
||||
|
||||
is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())
|
||||
|
||||
# ==========================================
|
||||
# 核心一:经典出场逻辑 (你的原版设计)
|
||||
# ==========================================
|
||||
if pos != 0:
|
||||
# 最小 20 tick 保护,防止 ATR 极度萎缩时止损过窄
|
||||
stop_dist = max(self.stop_mult * ATR, self.min_tick * 20)
|
||||
|
||||
if pos > 0:
|
||||
# A. 动能极值止盈:到达 1.6 且刚刚发生向下拐头
|
||||
if current_fb > self.fisher_exit_level and current_fb < prev_fb:
|
||||
self.log(f"TAKE PROFIT (Long): FB {current_fb:.2f} Peak Reached")
|
||||
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# B. 原版硬止损:只看 entry_price,给趋势震荡留出无限空间
|
||||
if prev_bar.close < entry_price - stop_dist:
|
||||
self.log(f"HARD STOP (Long): Close {prev_bar.close} < Limit {entry_price - stop_dist}")
|
||||
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
elif pos < 0:
|
||||
# A. 动能极值止盈
|
||||
if current_fb < -self.fisher_exit_level and current_fb > prev_fb:
|
||||
self.log(f"TAKE PROFIT (Short): FB {current_fb:.2f} Bottom Reached")
|
||||
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# B. 原版硬止损
|
||||
if prev_bar.close > entry_price + stop_dist:
|
||||
self.log(f"HARD STOP (Short): Close {prev_bar.close} > Limit {entry_price + stop_dist}")
|
||||
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# ==========================================
|
||||
# 核心二:经典入场逻辑
|
||||
# ==========================================
|
||||
elif pos == 0 and is_met:
|
||||
# 趋势向上,且 FB < -0.1 (符合你的优异参数)
|
||||
if prev_bar.close > T and current_fb < -self.fb_entry_threshold:
|
||||
if "BUY" in self.order_direction:
|
||||
long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR))
|
||||
self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN")
|
||||
self.log(f"ENTRY LONG SIGNAL: T={T:.2f}, FB={current_fb:.2f}")
|
||||
|
||||
# 趋势向下,且 FB > 0.1
|
||||
elif prev_bar.close < T and current_fb > self.fb_entry_threshold:
|
||||
if "SELL" in self.order_direction:
|
||||
short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR))
|
||||
self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN")
|
||||
self.log(f"ENTRY SHORT SIGNAL: T={T:.2f}, FB={current_fb:.2f}")
|
||||
|
||||
# ==========================================
|
||||
# 彻底隔离历史噪音:原生换月机制
|
||||
# ==========================================
|
||||
def on_contract_rollover(self, old_symbol: str, new_symbol: str):
|
||||
self.log(f"ROLLOVER TRIGGERED: {old_symbol} -> {new_symbol}")
|
||||
|
||||
# 撤销老合约全部残留动作
|
||||
self.cancel_all_pending_orders(old_symbol)
|
||||
|
||||
# 市价清空老合约,因为换月会导致 entry_price 和盘面断层,强制物理归零最安全
|
||||
pos = self.get_current_positions().get(old_symbol, 0)
|
||||
if pos > 0:
|
||||
self.send_order(Order(id=f"ROLLOVER_CLOSE_L_{old_symbol}", symbol=old_symbol, direction="CLOSE_LONG",
|
||||
volume=abs(pos), price_type="MARKET", submitted_time=self.get_current_time(),
|
||||
offset="CLOSE"))
|
||||
elif pos < 0:
|
||||
self.send_order(Order(id=f"ROLLOVER_CLOSE_S_{old_symbol}", symbol=old_symbol, direction="CLOSE_SHORT",
|
||||
volume=abs(pos), price_type="MARKET", submitted_time=self.get_current_time(),
|
||||
offset="CLOSE"))
|
||||
|
||||
# 切换主交易标的
|
||||
self.main_symbol = new_symbol
|
||||
self.symbol = new_symbol
|
||||
|
||||
# --- 下单系统 ---
|
||||
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))
|
||||
1253
futures_trading_strategies/FG/FisherTrendStrategy/Strategy.ipynb
Normal file
1253
futures_trading_strategies/FG/FisherTrendStrategy/Strategy.ipynb
Normal file
File diff suppressed because one or more lines are too long
1393
futures_trading_strategies/FG/FisherTrendStrategy/Strategy2.ipynb
Normal file
1393
futures_trading_strategies/FG/FisherTrendStrategy/Strategy2.ipynb
Normal file
File diff suppressed because one or more lines are too long
1536
futures_trading_strategies/FG/FisherTrendStrategy/Strategy3.ipynb
Normal file
1536
futures_trading_strategies/FG/FisherTrendStrategy/Strategy3.ipynb
Normal file
File diff suppressed because one or more lines are too long
239
futures_trading_strategies/FG/FisherTrendStrategy/Strategy3.py
Normal file
239
futures_trading_strategies/FG/FisherTrendStrategy/Strategy3.py
Normal file
@@ -0,0 +1,239 @@
|
||||
import numpy as np
|
||||
import math
|
||||
import talib
|
||||
from collections import deque
|
||||
from typing import Optional, Any, List
|
||||
|
||||
from src.core_data import Bar, Order
|
||||
from src.indicators.base_indicators import Indicator
|
||||
from src.strategies.base_strategy import Strategy
|
||||
|
||||
|
||||
class PragmaticCyberneticStrategy(Strategy):
|
||||
"""
|
||||
【务实回归版·控制论策略】(TA-Lib 提速 + 换月支持)
|
||||
|
||||
哲学坚守:
|
||||
1. 捍卫原版出场逻辑:坚决使用基于 entry_price 的固定硬止损。脱离成本区后,给予价格无限宽容度,实现长线死拿。
|
||||
2. 动能极值止盈:恢复 FB > 1.6 且拐头时的精准落袋为安。
|
||||
3. 拒绝画蛇添足:没有任何 _target_state,不堆砌 Trick,底层数据即真理。
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
context: Any,
|
||||
main_symbol: str,
|
||||
enable_log: bool,
|
||||
trade_volume: int,
|
||||
min_tick: float = 1.0,
|
||||
|
||||
# --- 核心周期 ---
|
||||
trend_period: int = 26,
|
||||
fisher_period: int = 20,
|
||||
atr_period: int = 23,
|
||||
|
||||
# --- 经过验证的优秀参数 ---
|
||||
fisher_exit_level: float = 1.6, # 配合 1.98 修复后的 FB,1.6 是一个非常好的极值止盈点
|
||||
fb_entry_threshold: float = 0.1, # 0.1 确保在顺大势的前提下,动能稍有配合即可入场
|
||||
|
||||
# --- 风险控制 ---
|
||||
stop_mult: float = 2.0, # 原始的硬止损乘数
|
||||
limit_offset_mult: float = 0.,
|
||||
|
||||
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
|
||||
|
||||
# 为 talib.ATR 预留充足的数据预热空间
|
||||
self._buf_len = max(self.t_len, self.f_len, self.atr_len * 2) + 100
|
||||
self._highs = deque(maxlen=self._buf_len)
|
||||
self._lows = deque(maxlen=self._buf_len)
|
||||
self._closes = deque(maxlen=self._buf_len)
|
||||
|
||||
# 使用 deque 记录 FB,相比单独的 _prev_fb 变量更安全,不会因为初始 0 导致误判
|
||||
self._fbs = deque(maxlen=2)
|
||||
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):
|
||||
"""完全无状态的纯数学指标计算 (基于 TA-Lib)"""
|
||||
if len(self._closes) < self._buf_len:
|
||||
return None, None, None, None
|
||||
|
||||
np_highs = np.array(self._highs, dtype=np.float64)
|
||||
np_lows = np.array(self._lows, dtype=np.float64)
|
||||
np_closes = np.array(self._closes, dtype=np.float64)
|
||||
|
||||
# 1. T轴 (大势过滤)
|
||||
t_max_h = talib.MAX(np_highs, timeperiod=self.t_len)[-1]
|
||||
t_min_l = talib.MIN(np_lows, timeperiod=self.t_len)[-1]
|
||||
T = (t_max_h + t_min_l) / 2.0
|
||||
|
||||
# 2. FV (公允价值挂单基准)
|
||||
FV = (np_closes[-1] + 2 * np_closes[-2] + 2 * np_closes[-3] + np_closes[-4]) / 6.0
|
||||
|
||||
# 3. Fisher Transform (1.98 数学修复版)
|
||||
f_max_h = talib.MAX(np_highs, timeperiod=self.f_len)[-1]
|
||||
f_min_l = talib.MIN(np_lows, timeperiod=self.f_len)[-1]
|
||||
denom = f_max_h - f_min_l if f_max_h != f_min_l else self.min_tick
|
||||
|
||||
stoc = (np_closes[-1] - f_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
|
||||
atr_array = talib.ATR(np_highs, np_lows, np_closes, timeperiod=self.atr_len)
|
||||
ATR = atr_array[-1]
|
||||
|
||||
return T, FV, FB, ATR
|
||||
|
||||
def on_open_bar(self, open_price: float, symbol: str):
|
||||
self.symbol = symbol
|
||||
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 or math.isnan(ATR): return
|
||||
|
||||
self._fbs.append(FB)
|
||||
# 必须凑齐两根 FB 才能判断拐点,否则跳过
|
||||
if len(self._fbs) < 2: return
|
||||
|
||||
current_fb = self._fbs[-1]
|
||||
prev_fb = self._fbs[-2]
|
||||
|
||||
# 绝对无状态:从账户物理数据获取真实持仓和开仓均价
|
||||
pos = self.get_current_positions().get(self.symbol, 0)
|
||||
entry_price = self.get_average_position_price(self.symbol)
|
||||
|
||||
is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())
|
||||
|
||||
# ==========================================
|
||||
# 核心一:经典出场逻辑 (你的原版设计)
|
||||
# ==========================================
|
||||
if pos != 0:
|
||||
stop_dist = max(self.stop_mult * ATR, self.min_tick * 20)
|
||||
|
||||
# --- 预判:是否存在反向入场信号? ---
|
||||
# 反向做多信号:大势已多 (C > T),且动能回调 (FB < -0.1)
|
||||
is_long_signal = (prev_bar.close > T) and (current_fb < -self.fb_entry_threshold)
|
||||
# 反向做空信号:大势已空 (C < T),且动能反弹 (FB > 0.1)
|
||||
is_short_signal = (prev_bar.close < T) and (current_fb > self.fb_entry_threshold)
|
||||
|
||||
if pos > 0:
|
||||
# A. 动能极值止盈
|
||||
if current_fb > self.fisher_exit_level and current_fb < prev_fb:
|
||||
self.log(f"TAKE PROFIT (Long): FB {current_fb:.2f} Peak Reached")
|
||||
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# B. 原版硬止损 (最后的底线)
|
||||
if prev_bar.close < entry_price - stop_dist:
|
||||
self.log(f"HARD STOP (Long): Close {prev_bar.close} < Limit {entry_price - stop_dist}")
|
||||
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# C. 解决冲突:反向信号强制平仓
|
||||
# 如果我们持有多单,但盘面竟然发出了“做空”信号,说明大势已去,逻辑矛盾,必须平仓!
|
||||
if is_short_signal and is_met:
|
||||
self.log(f"REVERSAL SIGNAL EXIT (Long): Trend Down & Short Signal Triggered.")
|
||||
self.send_market_order("CLOSE_LONG", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
elif pos < 0:
|
||||
# A. 动能极值止盈
|
||||
if current_fb < -self.fisher_exit_level and current_fb > prev_fb:
|
||||
self.log(f"TAKE PROFIT (Short): FB {current_fb:.2f} Bottom Reached")
|
||||
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# B. 原版硬止损
|
||||
if prev_bar.close > entry_price + stop_dist:
|
||||
self.log(f"HARD STOP (Short): Close {prev_bar.close} > Limit {entry_price + stop_dist}")
|
||||
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# C. 解决冲突:反向信号强制平仓
|
||||
# 如果我们持有空单,但盘面发出了“做多”信号,平空!
|
||||
if is_long_signal and is_met:
|
||||
self.log(f"REVERSAL SIGNAL EXIT (Short): Trend Up & Long Signal Triggered.")
|
||||
self.send_market_order("CLOSE_SHORT", abs(pos), "CLOSE")
|
||||
return
|
||||
|
||||
# ==========================================
|
||||
# 核心二:经典入场逻辑 (完全不变)
|
||||
# ==========================================
|
||||
elif pos == 0 and is_met:
|
||||
if prev_bar.close > T and current_fb < -self.fb_entry_threshold:
|
||||
if "BUY" in self.order_direction:
|
||||
long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR))
|
||||
self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN")
|
||||
|
||||
elif prev_bar.close < T and current_fb > self.fb_entry_threshold:
|
||||
if "SELL" in self.order_direction:
|
||||
short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR))
|
||||
self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN")
|
||||
|
||||
# ==========================================
|
||||
# 彻底隔离历史噪音:原生换月机制
|
||||
# ==========================================
|
||||
def on_contract_rollover(self, old_symbol: str, new_symbol: str):
|
||||
self.log(f"ROLLOVER TRIGGERED: {old_symbol} -> {new_symbol}")
|
||||
|
||||
# 撤销老合约全部残留动作
|
||||
self.cancel_all_pending_orders(old_symbol)
|
||||
|
||||
# 市价清空老合约,因为换月会导致 entry_price 和盘面断层,强制物理归零最安全
|
||||
pos = self.get_current_positions().get(old_symbol, 0)
|
||||
if pos > 0:
|
||||
self.send_order(Order(id=f"ROLLOVER_CLOSE_L_{old_symbol}", symbol=old_symbol, direction="CLOSE_LONG",
|
||||
volume=abs(pos), price_type="MARKET", submitted_time=self.get_current_time(),
|
||||
offset="CLOSE"))
|
||||
elif pos < 0:
|
||||
self.send_order(Order(id=f"ROLLOVER_CLOSE_S_{old_symbol}", symbol=old_symbol, direction="CLOSE_SHORT",
|
||||
volume=abs(pos), price_type="MARKET", submitted_time=self.get_current_time(),
|
||||
offset="CLOSE"))
|
||||
|
||||
# 切换主交易标的
|
||||
self.main_symbol = new_symbol
|
||||
self.symbol = new_symbol
|
||||
|
||||
# --- 下单系统 ---
|
||||
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))
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user