216 lines
9.1 KiB
Python
216 lines
9.1 KiB
Python
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)) |