from __future__ import annotations from pathlib import Path from tempfile import TemporaryDirectory from fastapi.testclient import TestClient from src.trader_mcp import strategy_registry, strategy_store from src.trader_mcp.server import app from src.trader_mcp.strategy_context import StrategyContext STRATEGY_CODE = ''' from src.trader_mcp.strategy_sdk import Strategy class Strategy(Strategy): def init(self): return {"started": True, "config_copy": dict(self.config)} ''' def test_strategies_endpoints_roundtrip(): with TemporaryDirectory() as tmpdir: strategy_store.DB_PATH = Path(tmpdir) / "trader_mcp.sqlite3" from src.trader_mcp import strategy_registry strategy_registry.STRATEGIES_DIR = Path(tmpdir) / "strategies" strategy_registry.STRATEGIES_DIR.mkdir() (strategy_registry.STRATEGIES_DIR / "demo.py").write_text(STRATEGY_CODE) client = TestClient(app) r = client.get("/strategies") assert r.status_code == 200 body = r.json() assert "available" in body assert "configured" in body r = client.post( "/strategies", json={ "id": "demo-1", "strategy_type": "demo", "account_id": "acct-1", "client_id": "strategy:test", "mode": "observe", "config": {"risk": 0.01}, }, ) assert r.status_code == 200 assert r.json()["id"] == "demo-1" r = client.get("/strategies") assert any(item["id"] == "demo-1" for item in r.json()["configured"]) r = client.delete("/strategies/demo-1") assert r.status_code == 200 assert r.json()["ok"] is True def test_strategy_context_binds_identity(monkeypatch): calls = {} def fake_place_order(arguments): calls["place_order"] = arguments return {"ok": True} def fake_open_orders(account_id, client_id=None): calls["open_orders"] = {"account_id": account_id, "client_id": client_id} return {"ok": True} def fake_cancel_all(account_id, client_id=None): calls["cancel_all"] = {"account_id": account_id, "client_id": client_id} return {"ok": True} monkeypatch.setattr("src.trader_mcp.strategy_context.place_order", fake_place_order) monkeypatch.setattr("src.trader_mcp.strategy_context.list_open_orders", fake_open_orders) monkeypatch.setattr("src.trader_mcp.strategy_context.cancel_all_orders", fake_cancel_all) ctx = StrategyContext(id="inst-1", account_id="acct-1", client_id="client-1", mode="active") ctx.place_order(side="sell", market="xrpusd", order_type="limit", amount="10", price="2") ctx.get_open_orders() ctx.cancel_all_orders() assert calls["place_order"]["account_id"] == "acct-1" assert calls["place_order"]["client_id"] == "client-1" assert calls["open_orders"] == {"account_id": "acct-1", "client_id": "client-1"} assert calls["cancel_all"] == {"account_id": "acct-1", "client_id": "client-1"} def test_stop_loss_strategy_loads_with_aligned_regime_config(tmp_path): 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 / "grid_trader.py").write_text((Path(__file__).resolve().parents[1] / "strategies" / "grid_trader.py").read_text()) (strategy_registry.STRATEGIES_DIR / "stop_loss_trader.py").write_text((Path(__file__).resolve().parents[1] / "strategies" / "stop_loss_trader.py").read_text()) grid_defaults = strategy_registry.get_strategy_default_config("grid_trader") stop_defaults = strategy_registry.get_strategy_default_config("stop_loss_trader") assert grid_defaults["trade_sides"] == "both" assert grid_defaults["trend_guard_reversal_max"] == 0.25 assert stop_defaults["regime_timeframes"] == ["1d", "4h", "1h", "15m"] assert stop_defaults["trend_enter_threshold"] == 0.7 assert stop_defaults["trend_exit_threshold"] == 0.45 finally: strategy_store.DB_PATH = original_db strategy_registry.STRATEGIES_DIR = original_dir