Multi-strategy engine with per-strategy capital allocation

Adds MultiStrategyManager: it runs several strategies concurrently, each with
its own TradingEngine seeded with starting_balance * allocation, so one
strategy's losses cannot eat into another's capital. Weights are normalised
across the run ([1,3] -> 25%/75%). Risk limits stay per strategy (each engine
owns a RiskManager).

New API: POST /api/bot/start-many and GET /api/bot/status. The existing
single-strategy endpoints delegate to the manager, so their behaviour is
unchanged. Adds manager tests (allocation, isolation, duplicate keys) and API
tests.
This commit is contained in:
matitos committed 2026-09-21 20:10:28 +00:00
1 parent e07536542f
commit 8b6cf8130c
6 files changed
+469 -28

No files matched your search

+36
View File
@@ -111,6 +111,42 @@ def test_backtest_limit_validation(runtime: Runtime) -> None:
)
def test_start_many_and_multi_status(runtime: Runtime, market: FakeMarketData) -> None:
app = create_app(runtime.settings, runtime)
with TestClient(app) as client:
started = client.post(
"/api/bot/start-many",
json={
"strategies": [
{"strategy_key": "sma_cross", "params": {"fast": 2, "slow": 4}, "weight": 1},
{"strategy_key": "ema_cross", "params": {"fast": 2, "slow": 4}, "weight": 3},
],
"starting_balance": 10000,
},
)
assert started.status_code == 200
body = started.json()
assert body["running"] is True
assert len(body["strategies"]) == 2
assert [s["allocation"] for s in body["strategies"]] == [0.25, 0.75]
status = client.get("/api/bot/status").json()
assert status["running"] is True
assert len(status["strategies"]) == 2
assert status["equity"] == 10000
client.post("/api/bot/stop")
def test_start_many_rejects_unknown_strategy(runtime: Runtime) -> None:
app = create_app(runtime.settings, runtime)
with TestClient(app) as client:
response = client.post(
"/api/bot/start-many", json={"strategies": [{"strategy_key": "nope"}]}
)
assert response.status_code == 404
def test_kill_endpoint(runtime: Runtime) -> None:
app = create_app(runtime.settings, runtime)
with TestClient(app) as client: