Make risk guard symbol aware

This commit is contained in:
Codex
2026-06-24 05:30:12 +03:00
parent 4112a17bc5
commit a6c94b1555
4 changed files with 168 additions and 12 deletions
+53 -9
View File
@@ -114,30 +114,39 @@ def risk_guard_snapshot(
"position_size_multiplier": 1.0,
"reasons": [],
}
active_trades = _active_universe_trades(settings, closed_trades)
reasons: list[str] = []
consecutive_losses = _consecutive_losses(closed_trades)
degraded_reasons: list[str] = []
consecutive_losses = _consecutive_losses(active_trades)
if consecutive_losses >= settings.risk_max_consecutive_losses:
reasons.append("consecutive_losses")
today_pnl = _today_pnl(closed_trades)
degraded_reasons.append("consecutive_losses")
today_pnl = _today_pnl(active_trades)
if today_pnl <= -abs(settings.max_daily_drawdown_usdt):
reasons.append("daily_loss_limit")
window = max(4, settings.risk_recent_trade_window)
recent_stats = _trade_stats(closed_trades[:window])
recent_stats = _trade_stats(active_trades[:window])
if recent_stats["trades"] >= max(4, min(window, 8)):
if recent_stats["profit_factor"] < settings.risk_min_recent_profit_factor:
reasons.append("recent_profit_factor_below_min")
degraded_reasons.append("recent_profit_factor_below_min")
if recent_stats["avg_net_percent"] <= 0:
reasons.append("recent_expectancy_non_positive")
degraded_reasons.append("recent_expectancy_non_positive")
latest_drawdown = float((latest_equity or {}).get("drawdown", 0.0) or 0.0)
if latest_drawdown >= abs(settings.max_daily_drawdown_usdt):
reasons.append("equity_drawdown_limit")
block = bool({"consecutive_losses", "daily_loss_limit", "equity_drawdown_limit"} & set(reasons))
multiplier = 0.0 if block else (settings.risk_reduce_multiplier if reasons else 1.0)
symbol_stats = _symbol_guard_stats(settings, active_trades)
blocked_symbols = sorted(row["symbol"] for row in symbol_stats if row["block_new_entries"])
block = bool(reasons)
all_reasons = reasons + degraded_reasons
multiplier = 0.0 if block else (settings.risk_reduce_multiplier if degraded_reasons else 1.0)
return {
"enabled": True,
"block_new_entries": block,
"position_size_multiplier": round(max(0.0, min(1.0, multiplier)), 4),
"reasons": reasons,
"reasons": all_reasons,
"global_reasons": reasons,
"degraded_reasons": degraded_reasons,
"blocked_symbols": blocked_symbols,
"symbols": symbol_stats,
"consecutive_losses": consecutive_losses,
"today_pnl": round(today_pnl, 6),
"recent": recent_stats,
@@ -183,6 +192,41 @@ def _group_stats(trades: list[dict[str, Any]], key_fn) -> list[dict[str, Any]]:
return sorted(rows, key=lambda row: (row["net_pnl"], row["trades"]), reverse=True)
def _active_universe_trades(settings: Settings, trades: list[dict[str, Any]]) -> list[dict[str, Any]]:
symbols = {symbol.upper() for symbol in settings.symbols}
if not symbols:
return trades
return [trade for trade in trades if str(trade.get("symbol", "")).upper() in symbols]
def _symbol_guard_stats(settings: Settings, trades: list[dict[str, Any]]) -> list[dict[str, Any]]:
expectancy_min_samples = max(6, min(settings.risk_recent_trade_window, 10))
loss_streak_min_samples = max(3, settings.risk_max_consecutive_losses)
rows: list[dict[str, Any]] = []
for symbol in settings.symbols:
symbol_trades = [trade for trade in trades if str(trade.get("symbol", "")).upper() == symbol.upper()]
recent = symbol_trades[: settings.risk_recent_trade_window]
stats = _trade_stats(recent)
losses = _consecutive_losses(recent)
reasons: list[str] = []
if stats["trades"] >= expectancy_min_samples:
if stats["profit_factor"] < settings.risk_min_recent_profit_factor and stats["avg_net_percent"] <= 0:
reasons.append("symbol_expectancy_negative")
if stats["trades"] >= loss_streak_min_samples:
if losses >= settings.risk_max_consecutive_losses:
reasons.append("symbol_consecutive_losses")
rows.append(
{
"symbol": symbol.upper(),
"block_new_entries": bool(reasons),
"reasons": reasons,
"consecutive_losses": losses,
**stats,
}
)
return rows
def _trade_summary(trade: dict[str, Any]) -> dict[str, Any]:
diagnostics = trade.get("entry_diagnostics") if isinstance(trade.get("entry_diagnostics"), dict) else {}
forecast = diagnostics.get("forecast") if isinstance(diagnostics.get("forecast"), dict) else {}