Crypto Trader
A small, hackable crypto trading bot with a web dashboard. Describe a strategy, drop in one Python file, run it, and watch positions and P&L live.
- Backend: FastAPI, SQLAlchemy, SQLite (Postgres optional), pure-Python
indicators,
httpxfor market data. - Frontend: Vue 3 + Vite + Tailwind dashboard with a live WebSocket feed.
- Brokers:
paper(simulated fills, default) andbinance(live spot). - Market data: Binance public REST (no API key needed).
- Backtesting: replay historical candles through the same strategy interface, with return/drawdown/win-rate/Sharpe metrics — so what you test is what runs.
Paper trading is the default. Live mode places real orders and is opt-in. This is experimental software. Never trade more than you can afford to lose.
How it works
Binance REST ──▶ MarketData ──▶ TradingEngine ──▶ Broker (paper | binance)
│
Strategy.on_candle(ctx)
│
REST /api + WebSocket /api/ws ──▶ Vue dashboard
The engine runs one strategy at a time. Each tick it fetches candles, updates the mark price, enforces stop-loss/take-profit, calls the strategy, executes the resulting intents, and persists orders, equity snapshots and events.
Quickstart (local)
# 1. Config
cp .env.example .env
# 2. Backend
python3 -m venv .venv
.venv/bin/pip install -r backend/requirements-dev.txt
cd backend && ../.venv/bin/python -m uvicorn app.main:app --reload --port 8000
# 3. Frontend (new terminal)
cd frontend && npm install && npm run dev
Open http://localhost:5173. API docs are at http://localhost:8000/docs.
Quickstart (Docker, local)
cp .env.example .env
docker compose -f docker-compose.local.yml up --build
# dashboard: http://localhost:8080 API docs: http://localhost:8000/docs
The default docker-compose.yml is the deployment stack (Traefik +
docker_default); use docker-compose.local.yml for a self-contained local run.
Deployment
Merging to main triggers .gitea/workflows/deploy.yml, which tests, builds
both images, and rolls the stack out on the shared Traefik host:
- The frontend joins the external
docker_defaultnetwork and is published atcrypto.${BASE_DOMAIN}(defaultcrypto.matitos.org) via Traefik, with a Let's Encrypt cert (certresolver=myresolvercd). - The backend stays on the internal network only; the frontend nginx proxies
/api(including the/api/wsWebSocket) to it, so API keys are never directly exposed. - The WebUI is protected by fail-closed Basic auth: the deploy aborts unless
CRYPTO_FRONTEND_AUTH_USERSis set (falling back to the sharedSCA_FRONTEND_AUTH_USERS). Generate a value withhtpasswd -nbB user 'password'. - A new release is rolled back automatically if healthchecks fail.
Repo/org variables and secrets used by the deploy:
| Name | Kind | Purpose |
|---|---|---|
CRYPTO_FRONTEND_AUTH_USERS |
var/secret | htpasswd string for the WebUI (required) |
BASE_DOMAIN |
secret | Base domain (default matitos.org) |
CRYPTO_MODE |
var | paper (default) or live |
CRYPTO_EXCHANGE / CRYPTO_SYMBOL / CRYPTO_TIMEFRAME |
var | Trading defaults |
CRYPTO_STARTING_BALANCE / CRYPTO_POLL_INTERVAL |
var | Paper account + tick rate |
CRYPTO_EXCHANGE_API_KEY / CRYPTO_EXCHANGE_API_SECRET |
secret | Required only for live |
CRYPTO_EXCHANGE_SANDBOX |
var | Use the Binance testnet |
CRYPTO_MAX_DAILY_LOSS / CRYPTO_MAX_DRAWDOWN_PCT / CRYPTO_MAX_POSITION_PCT / CRYPTO_MAX_TRADES_PER_DAY |
var | Risk limits |
CRYPTO_TELEGRAM_BOT_TOKEN |
secret | Telegram alerts |
CRYPTO_TELEGRAM_CHAT_ID |
var/secret | Telegram chat id |
CRYPTO_NOTIFY_WEBHOOK_URL |
secret | Generic webhook alerts |
CRYPTO_CORS_ORIGINS |
var | Defaults to https://crypto.<BASE_DOMAIN> |
CRYPTO_DATA_DIR / PERSISTENT_MOUNT |
var | Host path for the SQLite DB |
DOCKERHUB_USERNAME / DOCKERHUB_TOKEN |
secret | Optional; for authenticated pulls |
Configuration
All settings are environment variables (see .env.example):
| Variable | Default | Purpose |
|---|---|---|
MODE |
paper |
paper or live |
EXCHANGE |
binance |
Market data / execution venue |
SYMBOL |
BTC/USDT |
Default trading pair |
TIMEFRAME |
5m |
Candle interval |
POLL_INTERVAL |
5 |
Seconds between ticks |
STARTING_BALANCE |
10000 |
Paper account size |
FEE_BPS / SLIPPAGE_BPS |
10 / 5 |
Paper fill model |
DATABASE_URL |
sqlite:///./data/trader.db |
Postgres: postgresql+psycopg://... |
EXCHANGE_API_KEY / EXCHANGE_API_SECRET |
— | Required only for live |
EXCHANGE_SANDBOX |
false |
Use Binance testnet |
MAX_DAILY_LOSS |
0 |
Halt+flatten after this daily loss (quote ccy); 0 disables |
MAX_DRAWDOWN_PCT |
0 |
Halt+flatten after this % drop from peak; 0 disables |
MAX_POSITION_PCT |
1.0 |
Cap a single position at this fraction of equity |
MAX_TRADES_PER_DAY |
0 |
Max new entries per UTC day; 0 disables |
TELEGRAM_BOT_TOKEN / TELEGRAM_CHAT_ID |
— | Telegram alerts (both required) |
NOTIFY_WEBHOOK_URL |
— | Generic webhook alerts |
API_TOKEN / API_TOKEN_HEADER |
— | Bearer token required on /api when set |
Exchanges
Market data works out of the box on Binance, Kraken and Coinbase
(public REST, no API key). Set EXCHANGE to binance, kraken or coinbase.
Live execution (MODE=live) is implemented for Binance (spot, optional
testnet via EXCHANGE_SANDBOX) and Coinbase (Advanced Trade; set
EXCHANGE_API_SECRET to the CDP EC private key in PEM). Kraken is
market-data-only for now — MODE=live on Kraken raises a clear error.
API token (defense-in-depth)
Set API_TOKEN to require a bearer token on all /api routes (health stays
open). The frontend nginx injects it server-side, so the browser never sees it
and the dashboard keeps working unchanged. This sits behind the Traefik Basic
auth, not instead of it.
/api/ws is special: browsers do not reliably replay Basic-auth credentials on
a WebSocket upgrade (which left the live feed stuck on "reconnecting"), so
Traefik serves that one path on a separate higher-priority router without
Basic auth. It is still gated by API_TOKEN, which nginx injects on the
handshake. Because of this the deploy requires CRYPTO_API_TOKEN to be set —
without it the WebSocket would be unauthenticated.
Risk controls
The engine enforces limits on every tick and halts + flattens when one trips:
MAX_DAILY_LOSS— absolute loss since the start of the UTC day.MAX_DRAWDOWN_PCT— percentage fall from peak equity.MAX_POSITION_PCT— position size cap as a fraction of equity.MAX_TRADES_PER_DAY— new-entry budget per UTC day.
A halt is sticky: the position is closed, the bot stops, a red banner appears in
the dashboard, and a notification is sent. Restarting the bot resets the limits.
The dashboard also has a manual Kill switch button (POST /api/bot/kill)
that flattens and halts immediately.
Notifications
Set TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID and/or NOTIFY_WEBHOOK_URL to
receive alerts on trades, stop-outs, errors and halts. Notifications are
best-effort: a failing channel never affects trading. For Telegram, create a bot
with @BotFather, message it once, then read the chat id
from https://api.telegram.org/bot<token>/getUpdates.
Adding a strategy
Drop a file in backend/app/engine/strategies/ — it is auto-discovered and
appears in the UI immediately. See AGENTS.md for the full
template and the StrategyContext API.
Bundled strategies: sma_cross, ema_cross, macd_trend,
bollinger_reversion, donchian_breakout, momentum, rsi_reversion.
API
| Method | Path | Description |
|---|---|---|
GET |
/api/status |
Bot status, equity, P&L |
GET |
/api/strategies |
Available strategies + parameter specs |
POST |
/api/bot/start |
Start a strategy |
POST |
/api/bot/stop |
Stop the bot |
POST |
/api/bot/kill |
Kill switch: flatten + halt |
POST |
/api/backtest |
Backtest a strategy on historical candles |
GET |
/api/positions |
Positions (with unrealized P&L when open) |
GET |
/api/orders |
Order history |
GET |
/api/events |
Activity log |
GET |
/api/equity |
Equity curve points |
POST |
/api/orders |
Manual market order |
POST |
/api/positions/{id}/close |
Close the open position |
GET/POST |
/api/presets |
List / save strategy presets |
DELETE |
/api/presets/{id} |
Delete a preset |
WS |
/api/ws |
Live status + event stream |
Tests & lint
make test # backend pytest + frontend vitest
make lint # ruff
make build # frontend production build
Project layout
backend/
app/
engine/ trading loop, indicators, strategies/
broker/ paper + binance execution
market/ binance + fake data feeds
api/routes.py REST + WebSocket
models.py positions, orders, equity, events
store.py persistence
tests/
frontend/
src/ Vue dashboard