Kaynağa Gözat

rejecting switch because degraded - fixed

Lukas Goldschmidt 2 hafta önce
ebeveyn
işleme
cc12abf5a3
2 değiştirilmiş dosya ile 52 ekleme ve 3 silme
  1. 9 3
      src/trader_mcp/server.py
  2. 43 0
      tests/test_engine.py

+ 9 - 3
src/trader_mcp/server.py

@@ -310,8 +310,8 @@ def switch_strategy(*, account_id: str, market_symbol: str | None, target_strate
         return {"ok": False, "status": "rejected", "error": "expected active strategy mismatch", "from_strategy_id": current_id}
     if current_id == target_strategy_id:
         return {"ok": True, "status": "noop", "from_strategy_id": current_id, "to_strategy_id": target_strategy_id, "reconciled": False}
-    if current_id and _is_degraded(current_id) and not override:
-        return {"ok": False, "status": "rejected", "error": "current strategy is execution-degraded", "from_strategy_id": current_id}
+
+    degraded_current = bool(current_id and _is_degraded(current_id))
 
     if current_id:
         update_strategy_mode(current_id, "off")
@@ -321,7 +321,11 @@ def switch_strategy(*, account_id: str, market_symbol: str | None, target_strate
     if not ok:
         return {"ok": False, "status": "failed", "error": "failed to activate target strategy", "from_strategy_id": current_id, "to_strategy_id": target_strategy_id}
     reconcile_instance(target_strategy_id)
-    return {"ok": True, "status": "applied", "from_strategy_id": current_id, "to_strategy_id": target_strategy_id, "reconciled": True}
+    result = {"ok": True, "status": "applied", "from_strategy_id": current_id, "to_strategy_id": target_strategy_id, "reconciled": True}
+    if degraded_current:
+        result["warning"] = "previous active strategy was execution-degraded"
+        result["current_degraded"] = True
+    return result
 
 
 def apply_control_decision(payload: dict) -> dict:
@@ -357,6 +361,7 @@ def apply_control_decision(payload: dict) -> dict:
         "target_exists": None,
         "target_scope_match": None,
         "expected_active_match": None,
+        "current_degraded": None,
     }
     errors: list[str] = []
 
@@ -396,6 +401,7 @@ def apply_control_decision(payload: dict) -> dict:
     current = _active_strategy_for_scope(account_id, market_symbol)
     current_id = getattr(current, "id", None)
     validation["expected_active_match"] = (current_id == expected_active_strategy_id) if expected_active_strategy_id else None
+    validation["current_degraded"] = bool(current_id and _is_degraded(current_id))
     if expected_active_strategy_id and current_id != expected_active_strategy_id and not override:
         errors.append("expected active strategy mismatch")
 

+ 43 - 0
tests/test_engine.py

@@ -6,6 +6,7 @@ from tempfile import TemporaryDirectory
 from fastapi.testclient import TestClient
 
 from src.trader_mcp import strategy_registry, strategy_store, strategy_engine
+from src.trader_mcp import server as trader_server
 from src.trader_mcp.server import app, apply_control_decision
 
 
@@ -362,3 +363,45 @@ def test_apply_control_decision_rejects_expected_active_mismatch(tmp_path):
         strategy_store.DB_PATH = original_db
         strategy_registry.STRATEGIES_DIR = original_dir
         strategy_engine._running.clear()
+
+
+def test_apply_control_decision_switches_even_when_current_is_degraded(tmp_path, monkeypatch):
+    original_db = strategy_store.DB_PATH
+    original_dir = strategy_registry.STRATEGIES_DIR
+    try:
+        strategy_store.DB_PATH = tmp_path / "trader_mcp.sqlite3"
+        strategy_registry.STRATEGIES_DIR = tmp_path / "strategies"
+        strategy_registry.STRATEGIES_DIR.mkdir()
+        (strategy_registry.STRATEGIES_DIR / "hello_world.py").write_text(STRATEGY_CODE)
+        (strategy_registry.STRATEGIES_DIR / "demo.py").write_text(STRATEGY_CODE)
+
+        strategy_store.add_strategy_instance(id="old", strategy_type="hello_world", account_id="acct-1", client_id="cid-old", mode="active", market_symbol="xrpusd", base_currency="XRP", counter_currency="USD", config={})
+        strategy_store.add_strategy_instance(id="new", strategy_type="demo", account_id="acct-1", client_id="cid-new", mode="off", market_symbol="xrpusd", base_currency="XRP", counter_currency="USD", config={})
+        strategy_engine.reconcile_all()
+
+        monkeypatch.setattr(trader_server, "_is_degraded", lambda instance_id: True)
+
+        result = apply_control_decision(
+            {
+                "decision_id": "dec-switch-degraded-1",
+                "concern_id": "acct-1:xrpusd",
+                "account_id": "acct-1",
+                "market_symbol": "xrpusd",
+                "action": "switch",
+                "target_strategy_id": "new",
+                "expected_active_strategy_id": "old",
+                "reason": "trend fits better",
+                "confidence": 0.9,
+            }
+        )
+        assert result["ok"] is True
+        assert result["status"] == "applied"
+        assert result["validation"]["current_degraded"] is True
+        assert result["result"].get("current_degraded") is True
+        assert result["result"].get("warning")
+        assert strategy_store.get_strategy_instance("old").mode == "off"
+        assert strategy_store.get_strategy_instance("new").mode == "active"
+    finally:
+        strategy_store.DB_PATH = original_db
+        strategy_registry.STRATEGIES_DIR = original_dir
+        strategy_engine._running.clear()