优化前改动
This commit is contained in:
@@ -149,6 +149,35 @@ class BitmartFuturesTransactionConservative:
|
||||
self.auto_optimize_script_path = Path(__file__).with_name("optimize_params.py")
|
||||
self.auto_optimized_this_run = False
|
||||
|
||||
# 方案B:智能动态模式
|
||||
self.enable_smart_mode = os.getenv("BITMART_SMART_MODE_ENABLED", "1").strip().lower() not in {"0", "false", "off", "no"}
|
||||
# 波动率分档阈值(单位:百分比)
|
||||
self.vol_regime_low_threshold_pct = float(os.getenv("BITMART_VOL_REGIME_LOW_PCT", "0.22"))
|
||||
self.vol_regime_high_threshold_pct = float(os.getenv("BITMART_VOL_REGIME_HIGH_PCT", "0.45"))
|
||||
self.mid_vol_order_size_multiplier = float(os.getenv("BITMART_MID_VOL_SIZE_MULT", "0.7"))
|
||||
self.high_vol_order_size_multiplier = float(os.getenv("BITMART_HIGH_VOL_SIZE_MULT", "0.3"))
|
||||
self.high_vol_pause_trading = os.getenv("BITMART_HIGH_VOL_PAUSE", "1").strip().lower() not in {"0", "false", "off", "no"}
|
||||
self.mid_vol_stop_loss_multiplier = float(os.getenv("BITMART_MID_VOL_SL_MULT", "0.85"))
|
||||
self.high_vol_stop_loss_multiplier = float(os.getenv("BITMART_HIGH_VOL_SL_MULT", "0.70"))
|
||||
# 时间过滤:亚洲时段 + 跳过换线小时
|
||||
self.trade_asia_session_only = os.getenv("BITMART_ASIA_SESSION_ONLY", "1").strip().lower() not in {"0", "false", "off", "no"}
|
||||
self.asia_session_start_hour_utc = int(os.getenv("BITMART_ASIA_START_HOUR_UTC", "1"))
|
||||
self.asia_session_end_hour_utc = int(os.getenv("BITMART_ASIA_END_HOUR_UTC", "10"))
|
||||
self.blocked_utc_hours_utc = self._parse_int_set_csv(os.getenv("BITMART_BLOCKED_UTC_HOURS", "0,8,16"), {0, 8, 16})
|
||||
# 日内盈亏管理
|
||||
self.daily_profit_reduce_size_usd = float(os.getenv("BITMART_DAILY_PROFIT_REDUCE_SIZE_USD", "8"))
|
||||
self.daily_reduced_size_multiplier = float(os.getenv("BITMART_DAILY_REDUCED_SIZE_MULT", "0.5"))
|
||||
self.daily_max_loss_usd = float(os.getenv("BITMART_DAILY_MAX_LOSS_USD", "-10"))
|
||||
self.daily_halt_on_loss = os.getenv("BITMART_DAILY_HALT_ON_LOSS", "1").strip().lower() not in {"0", "false", "off", "no"}
|
||||
self.daily_balance_refresh_seconds = float(os.getenv("BITMART_DAILY_BALANCE_REFRESH_SECONDS", "15"))
|
||||
self.daily_stat_day = None
|
||||
self.daily_start_balance = None
|
||||
self.daily_current_balance = None
|
||||
self.daily_pnl_usd = 0.0
|
||||
self.daily_halt = False
|
||||
self.last_daily_balance_refresh_time = 0.0
|
||||
self.last_smart_status_log_time = 0.0
|
||||
|
||||
# 启动时尝试读取动态参数(可由优化脚本自动生成)
|
||||
self.load_runtime_params()
|
||||
self.capture_base_risk_params()
|
||||
@@ -203,6 +232,22 @@ class BitmartFuturesTransactionConservative:
|
||||
"dynamic_vol_target_pct",
|
||||
"dynamic_scale_min",
|
||||
"dynamic_scale_max",
|
||||
"enable_smart_mode",
|
||||
"vol_regime_low_threshold_pct",
|
||||
"vol_regime_high_threshold_pct",
|
||||
"mid_vol_order_size_multiplier",
|
||||
"high_vol_order_size_multiplier",
|
||||
"high_vol_pause_trading",
|
||||
"mid_vol_stop_loss_multiplier",
|
||||
"high_vol_stop_loss_multiplier",
|
||||
"trade_asia_session_only",
|
||||
"asia_session_start_hour_utc",
|
||||
"asia_session_end_hour_utc",
|
||||
"blocked_utc_hours_utc",
|
||||
"daily_profit_reduce_size_usd",
|
||||
"daily_reduced_size_multiplier",
|
||||
"daily_max_loss_usd",
|
||||
"daily_halt_on_loss",
|
||||
}
|
||||
|
||||
try:
|
||||
@@ -218,8 +263,23 @@ class BitmartFuturesTransactionConservative:
|
||||
continue
|
||||
if key == "leverage":
|
||||
setattr(self, key, str(value))
|
||||
elif key in {"dynamic_risk_enabled", "enable_shadow_reverse", "enable_trend_filter", "enable_ai_filter"}:
|
||||
elif key in {
|
||||
"dynamic_risk_enabled",
|
||||
"enable_shadow_reverse",
|
||||
"enable_trend_filter",
|
||||
"enable_ai_filter",
|
||||
"enable_smart_mode",
|
||||
"high_vol_pause_trading",
|
||||
"trade_asia_session_only",
|
||||
"daily_halt_on_loss",
|
||||
}:
|
||||
setattr(self, key, self._to_bool(value))
|
||||
elif key == "blocked_utc_hours_utc":
|
||||
if isinstance(value, (list, tuple, set)):
|
||||
parsed = {int(v) for v in value if str(v).strip().isdigit()}
|
||||
setattr(self, key, {h for h in parsed if 0 <= h <= 23} or {0, 8, 16})
|
||||
else:
|
||||
setattr(self, key, self._parse_int_set_csv(str(value), {0, 8, 16}))
|
||||
else:
|
||||
setattr(self, key, value)
|
||||
|
||||
@@ -232,7 +292,8 @@ class BitmartFuturesTransactionConservative:
|
||||
f"BreakoutBuf={self.open_breakout_buffer_pct}%, "
|
||||
f"OpenCD={self.open_cooldown_seconds}s, ReverseCD={self.reverse_cooldown_seconds}s, "
|
||||
f"ReverseMove={self.reverse_min_move_pct}%, "
|
||||
f"TrendFilter={self.enable_trend_filter}, AIFilter={self.enable_ai_filter}"
|
||||
f"TrendFilter={self.enable_trend_filter}, AIFilter={self.enable_ai_filter}, "
|
||||
f"SmartMode={self.enable_smart_mode}"
|
||||
)
|
||||
except Exception as e:
|
||||
logger.error(f"加载动态参数文件失败: {e} | path={params_path}")
|
||||
@@ -470,9 +531,155 @@ class BitmartFuturesTransactionConservative:
|
||||
return value.strip().lower() in {"1", "true", "yes", "y", "on"}
|
||||
return False
|
||||
|
||||
def _parse_int_set_csv(self, text, default):
|
||||
if not text:
|
||||
return set(default)
|
||||
result = set()
|
||||
for item in str(text).split(","):
|
||||
token = item.strip()
|
||||
if not token:
|
||||
continue
|
||||
try:
|
||||
value = int(token)
|
||||
except Exception:
|
||||
continue
|
||||
if 0 <= value <= 23:
|
||||
result.add(value)
|
||||
return result or set(default)
|
||||
|
||||
def _clip(self, value, low, high):
|
||||
return max(low, min(high, value))
|
||||
|
||||
def _normalize_hour(self, hour):
|
||||
return int(hour) % 24
|
||||
|
||||
def _utc_hour(self, ts=None):
|
||||
return int(time.strftime("%H", time.gmtime(ts if ts is not None else time.time())))
|
||||
|
||||
def _utc_day(self, ts=None):
|
||||
return time.strftime("%Y-%m-%d", time.gmtime(ts if ts is not None else time.time()))
|
||||
|
||||
def _is_hour_in_range(self, hour, start, end):
|
||||
hour = self._normalize_hour(hour)
|
||||
start = self._normalize_hour(start)
|
||||
end = self._normalize_hour(end)
|
||||
if start <= end:
|
||||
return start <= hour <= end
|
||||
return hour >= start or hour <= end
|
||||
|
||||
def infer_volatility_regime(self):
|
||||
vol_pct = self.get_recent_volatility_pct_from_cache(20)
|
||||
if vol_pct is None:
|
||||
return "unknown", None
|
||||
|
||||
low_th = max(0.0, float(self.vol_regime_low_threshold_pct))
|
||||
high_th = max(low_th, float(self.vol_regime_high_threshold_pct))
|
||||
if vol_pct >= high_th:
|
||||
return "high", vol_pct
|
||||
if vol_pct <= low_th:
|
||||
return "low", vol_pct
|
||||
return "mid", vol_pct
|
||||
|
||||
def refresh_daily_pnl_state(self, force=False):
|
||||
now = time.time()
|
||||
day = self._utc_day(now)
|
||||
|
||||
if self.daily_stat_day != day:
|
||||
self.daily_stat_day = day
|
||||
self.daily_start_balance = None
|
||||
self.daily_current_balance = None
|
||||
self.daily_pnl_usd = 0.0
|
||||
self.daily_halt = False
|
||||
self.last_daily_balance_refresh_time = 0.0
|
||||
logger.info(f"日内风控重置: day={day}")
|
||||
|
||||
if not force and (now - self.last_daily_balance_refresh_time) < self.daily_balance_refresh_seconds:
|
||||
return
|
||||
|
||||
balance = self.get_available_balance()
|
||||
self.last_daily_balance_refresh_time = now
|
||||
if balance is None:
|
||||
return
|
||||
|
||||
self.daily_current_balance = float(balance)
|
||||
if self.daily_start_balance is None:
|
||||
self.daily_start_balance = float(balance)
|
||||
logger.info(f"日内风控基准余额: {self.daily_start_balance:.4f} USDT")
|
||||
|
||||
self.daily_pnl_usd = float(self.daily_current_balance - self.daily_start_balance)
|
||||
if self.daily_halt_on_loss and self.daily_pnl_usd <= float(self.daily_max_loss_usd):
|
||||
if not self.daily_halt:
|
||||
logger.warning(
|
||||
f"触发日内亏损熔断: DailyPnL={self.daily_pnl_usd:.2f} <= {self.daily_max_loss_usd:.2f}"
|
||||
)
|
||||
self.daily_halt = True
|
||||
|
||||
def get_smart_trade_controls(self):
|
||||
controls = {
|
||||
"vol_regime": "unknown",
|
||||
"vol_pct": None,
|
||||
"size_multiplier": 1.0,
|
||||
"stop_loss_multiplier": 1.0,
|
||||
"allow_new_trade": True,
|
||||
"block_reason": "",
|
||||
"daily_pnl_usd": self.daily_pnl_usd,
|
||||
"daily_halt": self.daily_halt,
|
||||
}
|
||||
if not self.enable_smart_mode:
|
||||
return controls
|
||||
|
||||
# 1) 波动率分档
|
||||
vol_regime, vol_pct = self.infer_volatility_regime()
|
||||
controls["vol_regime"] = vol_regime
|
||||
controls["vol_pct"] = vol_pct
|
||||
if vol_regime == "mid":
|
||||
controls["size_multiplier"] *= max(0.05, float(self.mid_vol_order_size_multiplier))
|
||||
controls["stop_loss_multiplier"] *= max(0.05, float(self.mid_vol_stop_loss_multiplier))
|
||||
elif vol_regime == "high":
|
||||
controls["size_multiplier"] *= max(0.05, float(self.high_vol_order_size_multiplier))
|
||||
controls["stop_loss_multiplier"] *= max(0.05, float(self.high_vol_stop_loss_multiplier))
|
||||
if self.high_vol_pause_trading:
|
||||
controls["allow_new_trade"] = False
|
||||
controls["block_reason"] = "高波动暂停开新仓"
|
||||
|
||||
# 2) 时间段过滤
|
||||
hour = self._utc_hour()
|
||||
if hour in self.blocked_utc_hours_utc:
|
||||
controls["allow_new_trade"] = False
|
||||
controls["block_reason"] = f"UTC {hour}:00 属于换线禁交易时段"
|
||||
elif self.trade_asia_session_only:
|
||||
if not self._is_hour_in_range(hour, self.asia_session_start_hour_utc, self.asia_session_end_hour_utc):
|
||||
controls["allow_new_trade"] = False
|
||||
controls["block_reason"] = (
|
||||
f"非亚洲交易时段(UTC {hour}:00,不在 {self.asia_session_start_hour_utc}-{self.asia_session_end_hour_utc})"
|
||||
)
|
||||
|
||||
# 3) 日内盈亏管理
|
||||
self.refresh_daily_pnl_state(force=False)
|
||||
controls["daily_pnl_usd"] = self.daily_pnl_usd
|
||||
controls["daily_halt"] = self.daily_halt
|
||||
if self.daily_pnl_usd >= float(self.daily_profit_reduce_size_usd):
|
||||
controls["size_multiplier"] *= max(0.05, float(self.daily_reduced_size_multiplier))
|
||||
if self.daily_halt:
|
||||
controls["allow_new_trade"] = False
|
||||
controls["block_reason"] = f"日内亏损达到阈值 {self.daily_max_loss_usd:.2f},暂停开新仓"
|
||||
|
||||
return controls
|
||||
|
||||
def log_smart_trade_controls(self, controls):
|
||||
now = time.time()
|
||||
if now - self.last_smart_status_log_time < 30:
|
||||
return
|
||||
self.last_smart_status_log_time = now
|
||||
vol_text = "n/a" if controls["vol_pct"] is None else f"{controls['vol_pct']:.4f}%"
|
||||
logger.info(
|
||||
"智能模式状态: "
|
||||
f"regime={controls['vol_regime']} vol={vol_text} "
|
||||
f"sizeMult={controls['size_multiplier']:.3f} slMult={controls['stop_loss_multiplier']:.3f} "
|
||||
f"dailyPnL={controls['daily_pnl_usd']:.2f} allowNew={controls['allow_new_trade']} "
|
||||
f"reason={controls['block_reason'] or '-'}"
|
||||
)
|
||||
|
||||
def _ema_series(self, closes, period):
|
||||
period = max(1, int(period))
|
||||
if not closes:
|
||||
@@ -1339,6 +1546,9 @@ class BitmartFuturesTransactionConservative:
|
||||
logger.error("杠杆设置失败,程序继续运行但可能下单失败")
|
||||
return
|
||||
|
||||
# 初始化日内风控状态(方案B)
|
||||
self.refresh_daily_pnl_state(force=True)
|
||||
|
||||
if self.start_price_stream():
|
||||
logger.success("实时价格流已启动(WebSocket 优先)")
|
||||
else:
|
||||
@@ -1395,14 +1605,20 @@ class BitmartFuturesTransactionConservative:
|
||||
continue
|
||||
|
||||
logger.debug(f"当前持仓状态: {self.start} (0=无, 1=多, -1=空)")
|
||||
smart_controls = self.get_smart_trade_controls()
|
||||
self.log_smart_trade_controls(smart_controls)
|
||||
effective_stop_loss = self.stop_loss_usd
|
||||
sl_mult = max(0.05, float(smart_controls.get("stop_loss_multiplier", 1.0)))
|
||||
if effective_stop_loss < 0:
|
||||
effective_stop_loss = -abs(float(effective_stop_loss)) * sl_mult
|
||||
|
||||
# 3.5 止损/止盈/保本锁盈/移动止损
|
||||
if self.start != 0:
|
||||
pnl_usd = self.get_unrealized_pnl_usd()
|
||||
if pnl_usd is not None:
|
||||
# 固定止损:亏损达到 stop_loss_usd 平仓
|
||||
if pnl_usd <= self.stop_loss_usd:
|
||||
logger.info(f"仓位亏损 {pnl_usd:.2f} 美元 <= 止损 {self.stop_loss_usd} 美元,执行止损平仓")
|
||||
if pnl_usd <= effective_stop_loss:
|
||||
logger.info(f"仓位亏损 {pnl_usd:.2f} 美元 <= 止损 {effective_stop_loss:.2f} 美元,执行止损平仓")
|
||||
self.平仓()
|
||||
self.max_unrealized_pnl_seen = None
|
||||
time.sleep(3)
|
||||
@@ -1441,6 +1657,11 @@ class BitmartFuturesTransactionConservative:
|
||||
# 4. 检查信号
|
||||
signal = self.check_signal(current_price, prev_kline, current_kline)
|
||||
|
||||
# 4.5 智能模式拦截:高波动暂停/时间过滤/日内亏损熔断
|
||||
if signal and not smart_controls["allow_new_trade"]:
|
||||
logger.info(f"智能模式阻止交易: {signal[0]} | {smart_controls['block_reason']}")
|
||||
signal = None
|
||||
|
||||
# 5. 反手过滤:冷却时间 + 最小价差
|
||||
if signal and signal[0].startswith('reverse_'):
|
||||
if not self.can_reverse(current_price, signal[1]):
|
||||
@@ -1455,9 +1676,13 @@ class BitmartFuturesTransactionConservative:
|
||||
|
||||
# 6. 有信号则执行交易
|
||||
if signal:
|
||||
trade_success = self.execute_trade(signal)
|
||||
dynamic_size = max(1.0, float(self.default_order_size) * float(smart_controls["size_multiplier"]))
|
||||
dynamic_size = round(dynamic_size, 4)
|
||||
trade_success = self.execute_trade(signal, size=dynamic_size)
|
||||
if trade_success:
|
||||
logger.success(f"交易执行完成: {signal[0]}, 当前持仓状态: {self.start}")
|
||||
logger.success(
|
||||
f"交易执行完成: {signal[0]}, 下单数量={dynamic_size}, 当前持仓状态: {self.start}"
|
||||
)
|
||||
page_start = True
|
||||
else:
|
||||
logger.warning(f"交易执行失败或被阻止: {signal[0]}")
|
||||
|
||||
Reference in New Issue
Block a user