All endpoints are served by the FastAPI backend under the version prefix
/api/v1. Interactive docs are always available while the backend runs:
- Swagger UI: http://localhost:8000/docs
- ReDoc: http://localhost:8000/redoc
Base URL (local): http://localhost:8000/api/v1
Routes are thin — they validate input, call a service, and return a Pydantic schema. This reference is hand-maintained; the live
/docsis the source of truth for exact field types.
- All request/response bodies are JSON.
- Timestamps are ISO-8601 UTC strings; some market fields use Unix ms.
- Money is USDT; percentages are decimals (
0.015= 1.5%) unless a field name ends in_pctand is documented as already-multiplied. - Errors return
{ "detail": "<message>" }with a 4xx/5xx status.
Liveness check (note: not under /api/v1).
{ "status": "ok", "version": "0.1.0", "env": "development", "testnet": true }Binance API key management. Keys are validated against Binance before saving; the secret is never returned.
// request
{ "api_key": "…", "api_secret": "…", "testnet": true }
// response 200
{ "configured": true, "testnet": true, "key_preview": "abcd1234…" }
// 400 if Binance rejects the keys{ "configured": true, "testnet": true, "key_preview": "abcd1234…" }Lifecycle control. State machine lives in services/bot_engine.py.
{
"state": "running", // running | stopped | paused | error
"uptime_seconds": 3600,
"current_cycle": 12,
"circuit_breaker_active": false,
"last_error": null,
"last_cycle_time": "2026-06-18T00:30:00+00:00",
"open_position": {
"symbol": "BTCUSDT", "side": "BUY", "entry_price": 65000.0,
"quantity": 0.001, "entry_time": "…", "order_id": "123"
}
}// request
{ "command": "start" } // start | stop | pause | resume
// response: a BotStatus object (as above)startfetches the opening balance (circuit-breaker baseline) and runs the strategy loop immediately, then every 15 min.resumelifts a manual pause but not a circuit-breaker pause.
Cancels all open Binance orders, clears the position, stops the bot. Returns a BotStatus.
Clears the circuit-breaker flag and resumes. Investigate the root cause first; if the account is still down ≥ 2% it re-trips. Returns a BotStatus.
{
"symbol": "BTCUSDT", "timeframe": "15m",
"bb_period": 20, "bb_std_dev": 2.0,
"volume_ma_period": 30, "stop_loss_pct": 0.015
}Body = a full StrategyConfig (same shape as above). Takes effect on the next cycle; the live strategy singleton is rebuilt immediately. Returns the saved config.
Runs the strategy against current Binance data without placing an order.
{
"direction": "long", // long | short | neutral
"reason": "Price 64000 crossed below lower band 64200 …",
"confidence": 0.42,
"metadata": { "upper_band": 65800.0, "middle_band": 65000.0, "lower_band": 64200.0,
"std_dev": 312.8, "percent_b": -0.05, "volume_filter_ok": true }
}Read-only. Writes happen via the bot engine → repository, never here.
Query: page (≥1), page_size (1–200), symbol (optional), status
(open|closed, optional).
{
"total": 42, "page": 1, "page_size": 50,
"trades": [{
"id": 1, "symbol": "BTCUSDT", "side": "BUY",
"entry_price": 65000.0, "exit_price": 65800.0, "quantity": 0.001,
"entry_time": "…", "exit_time": "…",
"pnl_usdt": 0.8, "pnl_pct": 0.0123,
"exit_reason": "mean_revert", "status": "closed"
}]
}[{
"symbol": "BTCUSDT", "side": "BUY", "entry_price": 65000.0, "quantity": 0.001,
"entry_time": "…", "current_price": 65500.0,
"unrealised_pnl_usdt": 0.5, "unrealised_pnl_pct": 0.0077
}]Falls back to the stored snapshot price if the live lookup fails.
Computed from closed trades via services/performance_service.py +
backtest/metrics.py. Requires the database.
Query: from_date, to_date (optional ISO dates; all-time by default).
{
"total_pnl_usdt": 25.0, "total_pnl_pct": 0.025,
"win_rate": 0.66, "total_trades": 9, "winning_trades": 6, "losing_trades": 3,
"avg_win_usdt": 8.0, "avg_loss_usdt": -4.0,
"profit_factor": 3.0, "sharpe_ratio": 1.85, "max_drawdown_pct": 0.04,
"best_trade_pnl_usdt": 17.5, "worst_trade_pnl_usdt": -9.0,
"daily_pnl": [{ "date": "2026-06-16", "pnl_usdt": 5.0, "pnl_pct": 0.0, "trade_count": 2 }]
}profit_factor and sharpe_ratio are null when undefined (no losses / <2
data points).
{ "date": "2026-06-18", "pnl_usdt": 3.0, "pnl_pct": 0.003, "trade_count": 1 }Telegram notification toggles. Bot token + chat id come from .env, not these
endpoints.
{ "enabled": true, "trade_notifications": true, "daily_summary": true,
"circuit_breaker_alerts": true, "error_alerts": true }Body = an AlertConfig (same shape). Returns the updated config.
{ "success": true, "message": "Test message sent — check Telegram." }
// success:false with the reason if creds are missing or Telegram rejects itRuns as a background job; poll for the result.
// request
{
"symbol": "BTCUSDT", "timeframe": "15m",
"from_date": "2026-05-18", "to_date": "2026-06-17",
"bb_period": 20, "bb_std_dev": 2.0, "volume_ma_period": 30,
"stop_loss_pct": 0.015, "initial_capital": 10000, "fee_pct": 0.001
}
// response
{ "job_id": "0455dc63a333", "status": "pending", "trades": [] }{
"job_id": "0455dc63a333", "status": "complete", // pending|running|complete|failed
"symbol": "BTCUSDT", "timeframe": "15m",
"initial_capital": 10000, "final_capital": 10061.21, "candles_processed": 1356,
"total_pnl_usdt": 61.21, "total_pnl_pct": 0.0061,
"win_rate": 0.72, "total_trades": 25,
"profit_factor": 2.09, "sharpe_ratio": 5.45, "max_drawdown_pct": 0.0019,
"trades": [{ "entry_time": "…", "exit_time": "…", "side": "BUY",
"entry_price": 63387.96, "exit_price": 63500.0,
"quantity": 0.015, "pnl_usdt": 1.5, "pnl_pct": 0.0017,
"exit_reason": "mean_revert" }]
}
// on failure: { "job_id": "…", "status": "failed", "error": "…", "trades": [] }Jobs are held in a process-local dict (fine for single-user; move to Redis/DB for multi-worker).
Live Binance data. Public endpoints (ticker/price/candles) work without API
keys; account/balance requires them.
{ "symbol": "BTCUSDT", "price": 65802.3, "price_change_24h_pct": -0.03,
"high_24h": 66984.4, "low_24h": 54616.9, "volume_24h": 747.99 }{ "symbol": "BTCUSDT", "price": 65802.3 }Query: timeframe (default 15m), limit (1–1000). Returns closed candles
(the open candle is dropped).
[{ "open_time": 1781541584284, "open": 65000.0, "high": 65100.0, "low": 64900.0,
"close": 65050.0, "volume": 12.3, "close_time": 1781542484283 }]{ "upper_band": 65800.0, "middle_band": 65000.0, "lower_band": 64200.0,
"current_price": 65050.0, "std_dev": 312.8, "signal": "neutral" }{ "free_usdt": 9000.0, "locked_usdt": 0.0, "total_usdt": 9000.0,
"free_btc": 0.015, "locked_btc": 0.0, "total_btc": 0.015,
"btc_price": 65000.0, "portfolio_value_usdt": 9975.0 }{ "server_time_ms": 1781541584284, "testnet": true }| Status | Meaning |
|---|---|
| 400 | Bad request (e.g. Binance rejected API keys) |
| 404 | Resource not found (unknown trade id / backtest job) |
| 422 | Validation error (bad query/body params) |
| 502 | Upstream Binance call failed |
| 500 | Server/DB error (e.g. Postgres not running for /trades, /performance) |