Chart UI
Use this guide when you want to run the React/Vite chart workspace and inspect
candles loaded into the local DuckDB database. The UI talks to the FastAPI
backend under /api/v1.
The top bar includes a workspace selector and a create-workspace field. Live paper trade plans and category backtests use the selected workspace. See multiuser.md for the workspace API guide and current limitations.
Quick Start
- Download and insert candles into
file.db. - Start the backend with
uv run uvicorn main:app --host 127.0.0.1 --port 8000. - Start the frontend with
npm run dev. - Open the local Vite URL and inspect candles, ratios, categories, or scanner results.
Use the Downloader guide first if file.db does not yet
contain candles.
Requirements
- Python dependencies installed with
uv. - Node dependencies installed with
npm install. - A DuckDB database containing
binance_candles, usuallyfile.db. - Backend running on
127.0.0.1:8000. - Frontend dev server running on
127.0.0.1:5173.
Install dependencies:
uv sync --dev
npm install
Start The Backend
Run:
uv run uvicorn main:app --host 127.0.0.1 --port 8000
Keep this terminal open while using the UI. Stop the backend with Ctrl-C.
Check backend health:
curl http://127.0.0.1:8000/api/v1/health
Expected healthy response shape:
{
"status": "ok",
"database": {
"path": "file.db",
"connected": true,
"readonly": true
}
}
If the backend cannot read file.db, the UI will not have market data.
Start The Frontend
In another terminal, run:
npm run dev
Keep this terminal open while using the UI. Stop the frontend with Ctrl-C.
Open:
http://127.0.0.1:5173/
The Vite dev server proxies /api requests to the backend at
http://127.0.0.1:8000 by default.
If 5173 is already in use, Vite automatically chooses the next available
port, such as 5174. Use the URL printed by npm run dev.
Use A Different Backend
Use this when 8000 is already occupied or you intentionally start the backend
on another port.
Start the backend on another port:
uv run uvicorn main:app --host 127.0.0.1 --port 8001
Set VITE_PROXY_TARGET when starting the frontend:
VITE_PROXY_TARGET=http://127.0.0.1:8001 npm run dev
You can also bypass the proxy by setting VITE_API_BASE at build/runtime:
VITE_API_BASE=http://127.0.0.1:8000/api/v1 npm run dev
Verify the scanner through the frontend proxy:
curl 'http://127.0.0.1:5173/api/v1/scanner/uptrends?limit=1'
If the backend is on 8001 and the UI was started with
VITE_PROXY_TARGET=http://127.0.0.1:8001, the command above should still work
through the frontend server.
Mock Server Mode
The repo includes a mock-server path for the OpenAPI spec. Start the mock server:
npm run mock
Then start the frontend against the mock server:
VITE_PROXY_TARGET=http://127.0.0.1:4010 npm run dev
When VITE_PROXY_TARGET includes 4010, the Vite proxy rewrites /api/v1 to
the mock server root.
Main Screen
The UI opens directly to the chart workspace.
Top area:
- Current symbol title, for example
BTCUSDT. - Status indicator:
- green dot: last request succeeded,
- red dot: request failed.
- Status text such as
Loading chart,Market data ready,Ratio ready, or an HTTP error.
Chart Modes
Use the mode switch above the controls:
Candles: shows OHLC candles, volume bars, and strategy signal markers.Ratio: shows the ratio candle series for a numerator/denominator pair.Category: shows an equal-weighted category index built from the local symbol category file.
Market Scanner
The side scanner panel has two modes:
Single: ranks symbols that are clearly trending upward according to moving-average stack, moving-average slope, recent returns, and extension from the medium moving average.Ratio: ranks long-short pairs where the numerator is outperforming the denominator.
On desktop-width screens, the scanner stays beside the chart and scrolls inside its own panel. On smaller screens, it moves below the chart.
See uptrend-scanner.md for the full scanner user guide, including score rules, API examples, and troubleshooting.
Controls:
Single/Ratio: switches between single-symbol and pair scanning.Quote: filters symbols by quote asset, for exampleUSDT.Bench: inRatiomode, selects denominator symbols such asBTC,ETH, orBTC+ETH.Min Score: only shows symbols whose trend score is at least this value.Limit: controls the maximum number of rows returned.Refresh: reruns the scanner request.
The single-symbol scanner table shows:
Symbol: candidate symbol and trend state.Score: explainable trend score.30D: recent return over the scanner’s recent-return window.90D: longer return over the scanner’s long-return window.Ext: distance above the medium moving average.Last: latest candle date used by the scan.
Click a scanner row to open that symbol in candle mode.
The ratio scanner table shows:
Pair: numerator/denominator pair and relative trend state.Score: explainable relative trend score.30D: recent ratio return.90D: longer ratio return.Ext: distance above the ratio medium moving average.
Click a pair row to open ratio mode with the numerator and denominator filled.
The scanner endpoints are:
GET /api/v1/scanner/uptrends
GET /api/v1/scanner/ratio-uptrends
Candle Mode
In Candles mode:
Symbol: selected market symbol.Interval: one of1d,4h,1h, or15m.Strategy: selected strategy marker source.- Range buttons:
1M: last 30 days,3M: last 90 days,1Y: last 365 days,All: up to 5000 candles.
The chart displays:
- candlesticks,
- volume bars,
- moving-average indicator overlays,
- strategy markers.
Indicators
In Candles mode, the Indicators bar appears above the chart.
Available defaults:
SMA 20: enabled by default.SMA 50: enabled by default.SMA 200: disabled by default.EMA 20: disabled by default.
Toggle an indicator checkbox to add or remove its line overlay from the candle chart. Indicators reload when you change symbol, interval, or chart range.
Indicator requests use:
POST /api/v1/indicators/preview
Indicator presets for the current chart come from:
GET /api/v1/symbols/{symbol}/intervals/{interval}/indicators
The metrics row shows:
Last: latest close,Session: latest candle open-to-close percentage,High: latest candle high,Low: latest candle low,Volume: latest candle volume.
The lower Signals panel lists strategy markers with side, label, and date.
Ratio Mode
In Ratio mode:
Numerator: long-leg symbol.Denominator: short-leg symbol.Interval: shared interval.Strategy: still visible, but ratio mode displays ratio candles rather than signal markers.- Range buttons work the same as candle mode.
The ratio endpoint joins candles by timestamp and computes ratio OHLC values.
The metrics row shows:
Ratio: latest ratio close,Range: ratio change across the selected range,Base Close: latest numerator close,Quote Close: latest denominator close,Pair: selected numerator/denominator pair.
The lower Pair panel labels the numerator as the long leg and denominator as
the short leg.
Category Mode
In Category mode:
View: chooseIndexfor one category orRatiofor category-vs-category relative performance.Category: selected sector or theme fromconfigs/symbol-categories.yaml.Numerator: in ratio view, the category being compared.Denominator: in ratio view, the benchmark category.Max Borrow APR: in ratio view, optionally excludes denominator components whose Binance margin borrow APR is above the selected limit.Interval: shared candle interval.- Range buttons work the same as candle mode.
The category index is equal-weighted. Each component symbol is normalized to
100 at its first visible candle, then the backend averages all available
component values at each timestamp. This makes the line read like category
performance rather than absolute price.
The category index uses the same Quote setting as the scanner panel. For
example, with Quote = USDT, Layer 1 only includes category members whose
symbol quote asset is USDT.
When Category mode is active, the right side panel changes from the market
scanner to the selected category’s component list. Use the panel’s Quote
control to change the component universe. Click a component symbol to open that
symbol in candle mode. In ratio view, the panel shows separate numerator and
denominator component groups.
The metrics row shows:
Index: latest normalized category index value,Range: index change across the selected range,Components: number of component symbols present at the latest timestamp,Universe: number of symbols selected from the category file,Quote: quote-asset filter used for the index.
The category endpoints are:
GET /api/v1/categories
GET /api/v1/categories/{category}/index
GET /api/v1/category-ratios
The category ratio endpoint divides the numerator category index by the denominator category index on matching timestamps. A rising ratio means the numerator category is outperforming the denominator category over the selected range.
Borrow filtering applies only to the denominator category because that side is
the synthetic short leg. When Max Borrow APR is Off, category ratios are
computed entirely from local DuckDB data. When it is set to a percentage, the
backend reads Binance margin next-hourly borrow rates and excludes denominator
symbols above the limit. This requires the backend environment variables:
BINANCE_API_KEY
BINANCE_API_SECRET
API Request Panel
The lower-right API Request panel shows the exact endpoint used for the
current chart.
Examples:
/api/v1/candles?symbol=BTCUSDT&interval=1d&from=...&to=...&limit=1000&order=asc
/api/v1/ratios?base_symbol=BTCUSDT"e_symbol=ETHUSDT&interval=1d&from=...&to=...&limit=1000&order=asc
Use this panel when debugging backend responses or reproducing a request with
curl.
Data Requirements
The symbol dropdown comes from:
GET /api/v1/symbols
Only symbols already present in DuckDB appear in the UI.
The interval selector is currently fixed in the frontend:
1d, 4h, 1h, 15m
If you select an interval that is not present for the symbol in DuckDB, the
backend returns an error and the UI shows Request failed.
Verify Data Before Opening UI
Check available symbols and intervals:
uv run python -c "import duckdb; con=duckdb.connect('file.db'); print(con.execute(\"select symbol, list(distinct interval order by interval) from binance_candles group by symbol order by symbol limit 20\").fetchall())"
Check one candle series:
curl 'http://127.0.0.1:8000/api/v1/candles?symbol=BTCUSDT&interval=1d&limit=5&order=desc'
Check the symbol catalog:
curl http://127.0.0.1:8000/api/v1/symbols
Troubleshooting
Catalog load failed
The frontend could not load /api/v1/symbols or /api/v1/strategies.
Check:
curl http://127.0.0.1:8000/api/v1/health
curl http://127.0.0.1:8000/api/v1/symbols
Request failed
The selected symbol, interval, range, or ratio request failed.
Common causes:
- backend is not running,
file.dbis missing,binance_candlestable is empty,- selected interval has no rows,
- ratio symbols do not overlap in time.
Symbol dropdown only shows BTCUSDT
That is the frontend fallback while the catalog is loading or after catalog loading fails. Check the backend and DuckDB data.
The chart is empty
Check that the selected symbol and interval have rows:
uv run python -c "import duckdb; con=duckdb.connect('file.db'); print(con.execute(\"select count(*), min(open_time), max(open_time) from binance_candles where symbol='BTCUSDT' and interval='1d'\").fetchone())"
Ratio mode fails
Both symbols must have candles for the same interval and overlapping timestamps. Check:
uv run python -c "import duckdb; con=duckdb.connect('file.db'); print(con.execute(\"select symbol, count(*), min(open_time), max(open_time) from binance_candles where symbol in ('BTCUSDT','ETHUSDT') and interval='1d' group by symbol\").fetchall())"
Frontend shows stale data
Refresh the browser after inserting new data. The UI fetches data when controls change or the page loads; it does not currently stream live updates.