matitos 43831d31f6
CI/CD / backend (push) Successful in 6m1s
CI/CD / frontend (push) Successful in 5m18s
CI/CD / build (push) Successful in 16s
CI/CD / cleanup (push) Successful in 5s
CI/CD / deploy (push) Successful in 17s
Merge pull request: Fall back to REST polling when the WebSocket cannot connect
2026-09-20 13:00:52 +00:00
2026-09-19 14:31:42 +00:00

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, httpx for market data.
  • Frontend: Vue 3 + Vite + Tailwind dashboard with a live WebSocket feed.
  • Brokers: paper (simulated fills, default) and binance (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.
  • Parameter sweeps: grid-search a strategy's params and rank the results.
  • Trailing stops: strategies can trail a stop with the price (never loosening), enforced identically in backtest, paper and live.

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_default network and is published at crypto.${BASE_DOMAIN} (default crypto.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/ws WebSocket) 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_USERS is set (falling back to the shared SCA_FRONTEND_AUTH_USERS). Generate a value with htpasswd -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. If API_TOKEN is set, the WebSocket still requires it (nginx injects it on the handshake). If API_TOKEN is unset, /api/ws is as open as the rest of /api, which is fine for a trusted/private deployment but is the reason to set a token if the host is exposed.

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
POST /api/sweep Grid-sweep strategy params, ranked
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
S
Description
No description provided
Readme
579 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 74.7%
Vue 17.4%
JavaScript 6.7%
Dockerfile 0.4%
Makefile 0.3%
Other 0.5%