Explorar el Código

Refine grid strategy trend guard and layout

Lukas Goldschmidt hace 1 mes
padre
commit
4362387f14
Se han modificado 1 ficheros con 39 adiciones y 0 borrados
  1. 39 0
      strategies/grid_trader.py

+ 39 - 0
strategies/grid_trader.py

@@ -28,6 +28,8 @@ class Strategy(Strategy):
         "trade_sides": {"type": "string", "default": "both"},
         "max_notional_per_order": {"type": "float", "default": 0.0, "min": 0.0},
         "order_call_delay_ms": {"type": "int", "default": 250, "min": 0, "max": 10000},
+        "enable_trend_guard": {"type": "bool", "default": True},
+        "trend_guard_reversal_max": {"type": "float", "default": 0.25, "min": 0.0, "max": 1.0},
         "debug_orders": {"type": "bool", "default": True},
         "use_all_available": {"type": "bool", "default": True},
     }
@@ -42,6 +44,7 @@ class Strategy(Strategy):
         "debug_log": {"type": "list", "default": []},
         "base_available": {"type": "float", "default": 0.0},
         "counter_available": {"type": "float", "default": 0.0},
+        "trend_guard_active": {"type": "bool", "default": False},
     }
 
     def init(self):
@@ -56,6 +59,7 @@ class Strategy(Strategy):
             "debug_log": ["init cancel all orders"],
             "base_available": 0.0,
             "counter_available": 0.0,
+            "trend_guard_active": False,
         }
 
     def _log(self, message: str) -> None:
@@ -88,6 +92,25 @@ class Strategy(Strategy):
                 snapshot[tf] = {"error": str(exc)}
         return snapshot
 
+    def _trend_guard_status(self) -> tuple[bool, str]:
+        if not bool(self.config.get("enable_trend_guard", True)):
+            return False, "disabled"
+
+        reversal_max = float(self.config.get("trend_guard_reversal_max", 0.25) or 0.0)
+        regimes = self.state.get("regimes") or self._regime_snapshot()
+        d1 = (regimes.get("1d") or {}) if isinstance(regimes, dict) else {}
+        h4 = (regimes.get("4h") or {}) if isinstance(regimes, dict) else {}
+        d1_trend = str((d1.get("trend") or {}).get("state") or "unknown")
+        h4_trend = str((h4.get("trend") or {}).get("state") or "unknown")
+        d1_rev = float((d1.get("reversal") or {}).get("score") or 0.0)
+        h4_rev = float((h4.get("reversal") or {}).get("score") or 0.0)
+
+        strong_trend = d1_trend in {"bull", "bear"} and d1_trend == h4_trend
+        weak_reversal = max(d1_rev, h4_rev) <= reversal_max
+        active = bool(strong_trend and weak_reversal)
+        reason = f"1d={d1_trend} 4h={h4_trend} rev={max(d1_rev, h4_rev):.3f}"
+        return active, reason
+
     def _grid_step_pct(self) -> float:
         base_step = float(self.config.get("grid_step_pct", 0.012) or 0.012)
         tf = str(self.config.get("volatility_timeframe", "1h") or "1h")
@@ -292,6 +315,18 @@ class Strategy(Strategy):
         self.state["open_order_count"] = open_order_count
 
         mode = self._mode()
+        guard_active, guard_reason = self._trend_guard_status()
+        self.state["trend_guard_active"] = guard_active
+
+        if mode == "active" and guard_active:
+            self._log(f"trend guard active: {guard_reason}")
+            try:
+                self.context.cancel_all_orders()
+            except Exception as exc:
+                self.state["last_error"] = str(exc)
+                self._log(f"trend guard cancel failed: {exc}")
+            self.state["last_action"] = "trend_guard"
+            return {"action": "guard", "price": price, "reason": guard_reason}
 
         if mode != "active":
             if not self.state.get("seeded") or not self.state.get("center_price"):
@@ -377,6 +412,10 @@ class Strategy(Strategy):
                 {"type": "metric", "label": "15m", "value": ((self.state.get('regimes') or {}).get('15m') or {}).get('trend', {}).get('state', 'n/a')},
                 {"type": "metric", "label": f"{self._base_symbol()} avail", "value": round(float(self.state.get("base_available") or 0.0), 8)},
                 {"type": "metric", "label": f"{self.context.counter_currency or 'USD'} avail", "value": round(float(self.state.get("counter_available") or 0.0), 8)},
+                *([
+                    {"type": "metric", "label": "trend guard active", "value": "on"},
+                    {"type": "text", "label": "trend guard reason", "value": "higher-timeframe trend conflict"},
+                ] if self.state.get("trend_guard_active") else []),
                 {"type": "text", "label": "error", "value": self.state.get("last_error", "") or "none"},
                 {"type": "log", "label": "debug log", "lines": self.state.get("debug_log") or []},
             ]