Crypto trading bot: engine, brokers, backtester, dashboard, deploy
FastAPI backend with a single-strategy async engine, paper and Binance brokers, pure-python indicators, a backtester that shares the engine's portfolio math, and a Vue dashboard with a live WebSocket feed. Adds the Gitea Actions CI/CD workflow that deploys to Traefik via docker_default.
This commit is contained in:
1 parent
98028248c2
commit
9c3bee87ac
73 files changed
+9759
-1
No files matched your search
@@ -0,0 +1,84 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi.testclient import TestClient
|
||||
|
||||
from app.main import create_app
|
||||
from app.market.fake import FakeMarketData
|
||||
from app.runtime import Runtime
|
||||
|
||||
|
||||
def test_health_and_strategies(runtime: Runtime) -> None:
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
assert client.get("/api/health").json() == {"status": "ok"}
|
||||
keys = {s["key"] for s in client.get("/api/strategies").json()}
|
||||
assert {"sma_cross", "rsi_reversion"} <= keys
|
||||
|
||||
|
||||
def test_start_status_stop_flow(runtime: Runtime) -> None:
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
status = client.get("/api/status").json()
|
||||
assert status["running"] is False
|
||||
|
||||
started = client.post(
|
||||
"/api/bot/start",
|
||||
json={"strategy_key": "sma_cross", "params": {"fast": 2, "slow": 4}},
|
||||
)
|
||||
assert started.status_code == 200
|
||||
assert started.json()["running"] is True
|
||||
|
||||
assert client.get("/api/positions").status_code == 200
|
||||
assert client.get("/api/orders").status_code == 200
|
||||
assert client.get("/api/events").status_code == 200
|
||||
|
||||
stopped = client.post("/api/bot/stop").json()
|
||||
assert stopped["running"] is False
|
||||
|
||||
|
||||
def test_unknown_strategy_returns_404(runtime: Runtime) -> None:
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
response = client.post("/api/bot/start", json={"strategy_key": "does_not_exist"})
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_backtest_endpoint(runtime: Runtime, market: FakeMarketData) -> None:
|
||||
market.candles = FakeMarketData.sine_wave(200).candles
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
response = client.post(
|
||||
"/api/backtest",
|
||||
json={"strategy_key": "sma_cross", "params": {"fast": 5, "slow": 20}, "limit": 200},
|
||||
)
|
||||
assert response.status_code == 200
|
||||
data = response.json()
|
||||
assert data["strategy_key"] == "sma_cross"
|
||||
assert data["error"] is None
|
||||
assert len(data["equity_curve"]) == 200
|
||||
assert "total_return_pct" in data["metrics"]
|
||||
assert data["metrics"]["num_trades"] >= 1
|
||||
|
||||
|
||||
def test_backtest_unknown_strategy_404(runtime: Runtime) -> None:
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
response = client.post("/api/backtest", json={"strategy_key": "nope"})
|
||||
assert response.status_code == 404
|
||||
|
||||
|
||||
def test_backtest_limit_validation(runtime: Runtime) -> None:
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client:
|
||||
assert (
|
||||
client.post("/api/backtest", json={"strategy_key": "sma_cross", "limit": 5}).status_code
|
||||
== 422
|
||||
)
|
||||
|
||||
|
||||
def test_websocket_sends_initial_status(runtime: Runtime) -> None:
|
||||
app = create_app(runtime.settings, runtime)
|
||||
with TestClient(app) as client, client.websocket_connect("/api/ws") as websocket:
|
||||
message = websocket.receive_json()
|
||||
assert message["type"] == "status"
|
||||
assert message["data"]["running"] is False
|
||||
Reference in new issue
Block a user