Prechádzať zdrojové kódy

grid trader skew bug fixed

Lukas Goldschmidt 1 týždeň pred
rodič
commit
0f55fa0feb
2 zmenil súbory, kde vykonal 47 pridanie a 8 odobranie
  1. 22 8
      strategies/grid_trader.py
  2. 25 0
      tests/test_strategies.py

+ 22 - 8
strategies/grid_trader.py

@@ -361,14 +361,28 @@ class Strategy(Strategy):
         quote_total: float | None = None,
     ) -> dict[str, float | str]:
         ratio_price = price if price > 0 else float(self.state.get("last_price") or self.state.get("center_price") or 1.0)
-        if base_total is not None or quote_total is not None:
-            # Shape planning should see the whole wallet, not only the free slice.
-            base_value = max(float(base_total if base_total is not None else self.state.get("base_available") or 0.0), 0.0) * ratio_price
-            counter_value = max(float(quote_total if quote_total is not None else self.state.get("counter_available") or 0.0), 0.0)
-            total = base_value + counter_value
-            ratio = base_value / total if total > 0 else 0.5
-        else:
-            ratio = self._inventory_ratio(ratio_price if ratio_price > 0 else 1.0)
+        if base_total is None or quote_total is None:
+            live_orders = list(self.state.get("orders") or [])
+            reserved_quote = sum(
+                float(order.get("price") or 0.0) * float(order.get("amount") or 0.0)
+                for order in live_orders
+                if isinstance(order, dict) and str(order.get("side") or "").lower() == "buy"
+            )
+            reserved_base = sum(
+                float(order.get("amount") or 0.0)
+                for order in live_orders
+                if isinstance(order, dict) and str(order.get("side") or "").lower() == "sell"
+            )
+            if base_total is None:
+                base_total = max(float(self.state.get("base_available") or 0.0), 0.0) + reserved_base
+            if quote_total is None:
+                quote_total = max(float(self.state.get("counter_available") or 0.0), 0.0) + reserved_quote
+
+        # Rebalance bias must see the whole wallet, not only the free slice.
+        base_value = max(float(base_total or 0.0), 0.0) * ratio_price
+        counter_value = max(float(quote_total or 0.0), 0.0)
+        total = base_value + counter_value
+        ratio = base_value / total if total > 0 else 0.5
         imbalance = min(abs(ratio - 0.5) * 2.0, 1.0)
         factor = float(self.config.get("inventory_rebalance_step_factor", 0.15) or 0.0)
         factor = min(max(factor, 0.0), 0.9)

+ 25 - 0
tests/test_strategies.py

@@ -255,6 +255,31 @@ def test_grid_supervision_exposes_adverse_side_open_orders():
     assert "sell ladder exposed" in " ".join(supervision["concerns"])
 
 
+def test_grid_rebalance_skew_uses_total_wallet_not_free_balance():
+    class FakeContext:
+        account_id = "acct-1"
+        market_symbol = "xrpusd"
+        base_currency = "XRP"
+        counter_currency = "USD"
+        mode = "active"
+
+    strategy = GridStrategy(FakeContext(), {"grid_step_pct": 0.01, "grid_step_min_pct": 0.001, "inventory_rebalance_step_factor": 0.15})
+    strategy.state.update(
+        {
+            "base_available": 1.0,
+            "counter_available": 100.0,
+            "orders": [
+                {"side": "sell", "price": 10.0, "amount": 10.0, "id": "locked-sell"},
+            ],
+        }
+    )
+
+    steps = strategy._effective_grid_steps(10.0)
+
+    assert steps["favored_side"] == "sell"
+    assert steps["sell"] < steps["buy"]
+
+
 def test_grid_on_tick_honors_refresh_pause_before_shape_rebuild(monkeypatch):
     class FakeContext:
         account_id = "acct-1"