新增实盘策略:FisherTrendStrategy(FG)

This commit is contained in:
2026-02-25 23:54:24 +08:00
parent c0d996f39b
commit 05adafeb4e
28 changed files with 16221 additions and 1982 deletions

View File

@@ -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))

View File

@@ -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 修复后的 FB1.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))

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View 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 修复后的 FB1.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