- GET /api/metrics renders Prometheus text (equity, P&L, per-strategy
equity/pnl/allocation/halted, info) with no client library.
- In-memory ring log handler (app/logs.py) exposed at GET /api/logs and a
dashboard "Server logs" panel; Runtime mirrors bot events into the logger.
- docs/runbook.md covers deploys, the kill switch, resetting paper state and
going live. Adds tests for metrics rendering and the log ring.
Setting MODE=live alone no longer starts trading. check_live_ready requires an
active risk limit, MAX_POSITION_PCT < 1.0, LIVE_CONFIRMATION exactly
"I UNDERSTAND THE RISK", a minimum balance, and exchange credentials. Enforced
in Runtime.start_many (both start paths) and surfaced at GET /api/live-check;
the dashboard shows a banner and blocks the start buttons. Adds tests.
Adds a "Strategies (multi)" panel: add/remove strategy rows (strategy, symbol,
timeframe, per-strategy params and weight), start all, stop all and kill all,
plus a per-strategy status table (allocation, equity, P&L, halted). Polls
/api/bot/status alongside the existing status. Includes component tests.
While the strategy list is being fetched the dropdown now shows a disabled
"Loading strategies…" option instead of an empty select, and "No strategies
available" once loading finishes with no results. Applies to the strategy,
backtest and sweep panels via a loading prop. Adds tests.
- The strategy/preset lists were loaded once with failures swallowed, so a
single failed first request left the strategy dropdown empty forever. They
are now retried every 3s until populated.
- Clearer collapse affordance: hover highlight, a hide/show hint, and a
tooltip; Backtest and Parameter sweep start collapsed so the strategy
selector and status are immediately visible.
- Adds an integration test that the strategy dropdown is populated and the
panel actually collapses.
Adds a reusable CollapsiblePanel (chevron header, optional summary shown when
collapsed, state persisted in localStorage) and wraps the Strategy, Backtest
and Parameter sweep panels with it, so any single part of the dashboard can be
focused on quickly. Panels stay mounted (v-show) so their form/results state is
preserved across collapse. Includes component tests.
onMounted awaited the initial REST calls before setting up the socket and the
6s polling fallback, so any rejected request aborted the handler and left the
badge stuck on "connecting". Set up the socket and fallback first, make the
initial loads best-effort, and log [ws] connect/open/close for debugging.
Adds a footer with the client build SHA and the server /api/version value, a
mismatch warning, and the socket error text, so it is unambiguous whether the
browser is running a stale bundle and why the live feed fell back to polling.
The frontend container was serving the very first build (498-byte index.html)
through every later deploy: `docker compose up -d` did not replace a
config-identical service. Deploy now uses `build --pull` and
`up --force-recreate`, the frontend image writes a .build-version marker, and
the workflow fails the deploy if the served marker != the commit SHA.
Phones kept running old JS because their cached index.html pointed at an old
hashed asset. Both images now bake the commit SHA as BUILD_VERSION; the backend
exposes it at GET /api/version and the client reloads once with a cache-busting
query when its own baked version differs. Guarded by sessionStorage so it cannot
loop. /api/version is unauthenticated like /api/health.
iOS Safari can leave a blocked wss:// upgrade hanging without firing
onclose/onerror, so the badge sat on "connecting". The socket now times out a
slow handshake after 5s, reports at most one failure per attempt, and the app
commits to polling after a 6s grace period if the socket never opens.
Some browsers (notably iOS Safari on an auth-gated page) abort the wss://
upgrade even though the server accepts it. Rather than showing a perpetual
"reconnecting", stop retrying after three failures, poll every 3s, and label
the badge "live (polling)" in green. The socket error is kept as a tooltip.
On some mobile networks/browsers the wss:// upgrade is blocked even though the
page loads (the server side is verified working: a browser-style handshake gets
101). After three socket failures the header now shows "polling (5s)" instead
of a misleading perpetual "reconnecting", since the 5s REST poll keeps all data
correct. The socket error handler also surfaces the browser's message.
Phones offer no easy hard-refresh, so a cached index.html kept serving an old
bundle after a deploy (the live feed showed "reconnecting"). index.html and the
SPA catch-all now send Cache-Control: no-store, while hashed /assets stay
immutable. Also retry the first socket reconnect after 1s (was 2s) and surface
the close code in the header so a failure is actionable on a phone.
Trailing stops: strategies can pass trailing_stop_pct to enter_long/enter_short.
advance_trailing_stop in app/engine/portfolio.py ratchets the stop with the price
(up for longs, down for shorts) and never loosens; the engine persists the
ratcheted level and the backtester applies the same logic. Position gains a
trailing_stop_pct column (additive migration). momentum exposes it as a param.
Parameter sweeps: app/backtest/sweep.py runs a cartesian grid through the same
backtester and ranks by return minus max drawdown. POST /api/sweep plus a
dashboard Parameter sweep panel.
The deploy job hard-failed without CRYPTO_API_TOKEN, and /api/ws (which skips
Basic auth) relied on that token. Drop the requirement: the app now works with
no token at all, matching the backend guard that was always a no-op when unset.
When a token is configured it still protects /api and /api/ws; when it is not,
/api/ws is as open as the rest of the API, which the Traefik Basic auth still
guards for everything else. Adds tests for the tokenless API and WebSocket.
Browsers don't reliably replay Basic-auth credentials on a WebSocket upgrade,
so the dashboard's live feed was stuck on "reconnecting" (only the 5s REST
poll worked). Give /api/ws a higher-priority Traefik router that skips Basic
auth; the backend still requires API_TOKEN, which nginx injects on the
handshake. The deploy now fails closed unless CRYPTO_API_TOKEN is set.
Also: split the nginx /api/ws block so upgrade headers are only set there, and
add reconnect backoff (2s -> 30s) so a failing socket stops hammering.
- Add an SVG Bitcoin favicon and link it in index.html.
- New indicators: macd() and roc().
- Five new strategies: macd_trend, bollinger_reversion, donchian_breakout,
ema_cross, momentum (plus the existing sma_cross, rsi_reversion).
- Default TIMEFRAME is now 5m (config, .env.example, compose, workflow, UI).
- RiskManager halts + flattens on max daily loss / drawdown, caps position
size and trades per day; adds a manual kill switch (POST /api/bot/kill).
- Telegram and webhook notifications on trades, errors and halts.
- Optional API bearer token on /api, injected server-side by the frontend
nginx so the browser never sees it.
- Kraken and Coinbase market data (public); Coinbase live broker. Live on an
unsupported exchange now errors instead of silently falling back to paper.
- Strategy presets backed by the StrategyConfig table (CRUD API + UI).
- Idempotent additive-column migration so existing SQLite databases upgrade in
place (fixes the new strategy_configs.name column).
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.