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.
5.9 KiB
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_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 |
1m |
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 |
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. Two examples ship in the repo:
sma_cross and 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/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 |
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