SIO Nexus is a Bun-based market data backend for crypto trading pairs. It watches live exchange trade streams, aggregates them into unified klines, persists closed candles to SQLite, and exposes both real-time and historical data over Unix sockets.
The current runtime is centered around:
- live trade and liquidation ingestion from multiple exchanges through
ccxt - normalized pair aggregation across spot and derivatives markets
- batched SQLite persistence for closed klines
- a query socket for historical reads
- an aggregation socket for streaming live bucket updates
- a market scanner socket for real-time exchange-wide stats
- optional terminal dashboards for operators
- Aggregates multiple exchange symbols into a single logical pair such as
SOLUSDT - Produces unified OHLCV-style candles with buy/sell cost, volume, and liquidation metrics
- Persists closed candles for
1m,5m,15m,1h,4h, and1d - Streams in-progress (
delta) and closed (full) buckets over a Unix socket - Scans Binance market data for pairs that are tradeable on Phemex and streams rolling stats
- Serves historical and metadata queries from SQLite over a separate Unix socket
- Imports historical Binance
1mdata and rolls it up into higher timeframes - Supports a terminal dashboard for monitoring exchange workers and prices
At a high level, the runtime flow is:
src/main.tsloadsnexus-config.jsonNexusServerstarts Unix socket servers:- aggregation socket: real-time candle updates
- query socket: historical SQLite queries
- market scanner socket: exchange-wide rolling stats
- One worker per exchange is spawned through
ExchangeWorker.ts - Workers watch trades, and where supported, liquidation feeds
TickAggregatormaps exchange symbols into logical pairs and updates timeframe buckets- Closed buckets are buffered by
KlinesBufferand flushed into SQLite on an interval
src/
main.ts CLI entrypoint
config.ts Environment-driven runtime config
domain/ Candle and timeframe types
nexus/
NexusServer.ts Main runtime coordinator
ExchangeWorker.ts Per-exchange watcher worker
AggrSocketServer.ts Real-time aggregation socket
QuerySocketServer.ts Historical query socket
aggregator/ Bucket logic and buffering
utils/
db/ SQLite helpers
klinesDownloader/ Historical download/normalize/import pipeline
data/ SQLite DB and downloaded historical files
scripts/ Deploy script and systemd service unit
- Bun installed
unzipavailable inPATHfor the historical download workflow- A writable
data/directory - A valid
nexus-config.jsonfile at the project root, or a custom path provided through env
bun install- Create or edit
nexus-config.json - Start the server:
bun run devThat runs:
bun run --watch src/main.ts runTo run without watch mode:
bun run src/main.ts runTo enable the terminal dashboard:
bun run dashboardThere are two configuration layers:
- environment variables for runtime paths and logging
nexus-config.jsonfor aggregation pairs, DB path, and flush interval
| Variable | Default | Description |
|---|---|---|
SIO_NEXUS_ENV |
prod |
Runtime environment name |
SIO_NEXUS_IS_BINARY |
false |
Set to true when running the compiled binary |
SIO_NEXUS_SHOW_INIT_BANNER |
false |
Show startup banner |
SIO_NEXUS_CONFIG_FILE_PATH |
./nexus-config.json |
Path to the Nexus config file |
SIO_NEXUS_LOG_ENABLED |
false |
Enable logging |
SIO_NEXUS_LOG_LEVEL |
warn |
Log level |
SIO_NEXUS_LOG_DIR |
./logs |
Log directory |
SIO_NEXUS_WRITE_LOG_TO_FILES |
false |
Write logs to files |
SIO_NEXUS_AGGR_SOCKET_PATH |
/tmp/sio-nexus-aggr.sock |
Real-time aggregation socket path |
SIO_NEXUS_QUERY_SOCKET_PATH |
/tmp/sio-nexus-query.sock |
Historical query socket path |
SIO_NEXUS_SCANNER_SOCKET_PATH |
/tmp/sio-nexus-scanner.sock |
Market scanner socket path |
The project loads JSON5, so comments are allowed. A minimal example:
{
secondsBufferFlushToDB: 10,
sqliteKlinesDBPath: './data/klines.db',
marketScanner: {
enabled: true,
sourceExchangeId: 'binance',
tradableExchangeId: 'phemex',
statsWindowMs: 60000,
publishIntervalMs: 1000,
subscribeDelayMs: 25,
},
aggregatedPairs: {
SOLUSDT: [
{ exchangeId: 'binance', pairs: ['SOL/USDT', 'SOL/USDC'] },
{
exchangeId: 'binanceusdm',
pairs: ['SOL/USDT:USDT', 'SOL/USDC:USDC'],
mainPriceTracker: true,
},
],
},
}Each key in aggregatedPairs is the logical pair exposed by Nexus. Each entry under that key declares which exchange markets should contribute to the aggregate.
Fields:
exchangeId: exchange name used byccxtpairs: list of market symbols to watch on that exchangemainPriceTracker: whentrue, those symbols are allowed to drive OHLC prices for the aggregate
This lets you combine liquidity from several markets while keeping one preferred price source for candle OHLC values.
marketScanner runs alongside the normal aggregation pipeline. It discovers markets with ccxt, builds the intersection between markets tradeable on Phemex and markets available on Binance, watches Binance trades for that intersection, computes rolling per-symbol stats, and broadcasts snapshots through /tmp/sio-nexus-scanner.sock.
Available fields:
enabled: start the scanner withrunsourceExchangeId: market-data source exchange; defaultbinancetradableExchangeId: exchange used to decide which pairs are tradeable; defaultphemexmarketTypes: optional list such as['spot', 'swap']; applied to the tradeable exchangequoteCurrencies: optional list such as['USDT']; applied to the tradeable exchangeincludeSymbols/excludeSymbols: exact symbols,*wildcards, or/regex/; applied to the tradeable exchangemaxSymbols: optional cap for development testsstatsWindowMs: rolling stats windowpublishIntervalMs: socket snapshot intervalsubscribeDelayMs: throttle between source subscriptions; Binance useswatchTradesForSymbolsin chunks of up to 200 symbols
| Command | Description |
|---|---|
bun run dev |
Start in watch mode |
bun run dashboard |
Start the server with the terminal dashboard enabled |
bun run market-scanner |
Start the server with the market scanner enabled |
bun run market-scanner-dashboard |
Open the market scanner terminal dashboard |
bun run test |
Run Bun tests |
bun run typecheck |
Run TypeScript checks |
bun run lint |
Run ESLint |
bun run build |
Build the Linux x64 binary |
bun run clean |
Remove build/ and logs/ |
bun run download-historical-data <start> <end> |
Download, normalize, and import Binance 1m historical data |
bun run import-historical-klines <PAIR> <CSV_PATH> |
Import normalized 1m historical candles into SQLite |
bun run src/main.ts normalize-historical-data <CSV_PATH> |
Normalize a raw historical CSV into Nexus format |
When running, Nexus creates these Unix sockets:
- aggregation socket: streams live bucket updates
- query socket: accepts request/response JSON lines for SQLite reads
- market scanner socket: streams exchange-wide rolling stat snapshots
By default:
/tmp/sio-nexus-aggr.sock/tmp/sio-nexus-query.sock/tmp/sio-nexus-scanner.sock
It also writes SQLite data to the path declared in nexus-config.json, for example:
./data/klines.db
The aggregation socket is a newline-delimited JSON stream. Every message includes:
pairtimeframetype:deltafor in-progress bucket updates,fullfor a closed bucket- candle fields such as
timestamp,open,high,low,close - cost, volume, trade count, and liquidation metrics
Example message:
{
"pair": "SOLUSDT",
"timeframe": "1m",
"type": "delta",
"timestamp": 1773725340000,
"open": 141.2,
"high": 141.5,
"low": 141.1,
"close": 141.4,
"buyCost": 12850.55,
"sellCost": 9733.21,
"buyVolume": 91.4,
"sellVolume": 69.2,
"liqBuyCost": 0,
"liqSellCost": 0,
"liqBuyVolume": 0,
"liqSellVolume": 0,
"tradeCount": 184,
"liqCount": 0
}The query socket also uses newline-delimited JSON.
Supported queryType values:
info: list available klines tables, ranges, and timeframeslast: fetch the lastNcandles per timeframerange: fetch candles inside a timestamp window
{ "queryType": "info" }{
"queryType": "last",
"pair": "SOLUSDT",
"timeframes": ["1m", "5m", "1h"],
"limit": 50
}{
"queryType": "range",
"pair": "SOLUSDT",
"timeframes": ["1m", "5m"],
"startDate": 1773638400000,
"endDate": 1773724800000
}The server responds with JSON lines of these forms:
{"type":"info","info":...}{"type":"kline","kline":...}{"type":"end","count":123}{"type":"error","message":"..."}
For multi-timeframe reads, range results are emitted in timestamp order across the selected timeframes.
The market scanner socket is a newline-delimited JSON stream. The main message is market-scanner-snapshot:
{
"type": "market-scanner-snapshot",
"exchangeId": "binance",
"timestamp": 1773725340000,
"windowMs": 60000,
"symbolsCount": 250,
"activeSymbolsCount": 120,
"totals": {
"tradeCount": 4200,
"tradesPerSecond": 70,
"buyCost": 1800000,
"sellCost": 1700000,
"totalCost": 3500000,
"totalVolume": 42000,
"netCost": 100000
},
"symbols": [
{
"exchangeId": "binance",
"symbol": "SOL/USDT",
"lastPrice": 141.4,
"tradeCount": 184,
"tradesPerSecond": 3.07,
"totalCost": 22583.76,
"netCost": 3117.34,
"priceChangePct": 0.42,
"priceChange5mPct": 1.85,
"priceChange15mPct": -0.33,
"priceChange1hPct": 4.91
}
]
}priceChangePct is the realtime scanner-window change. The scanner also emits 5-minute, 15-minute, and 1-hour change fields, plus market-scanner-started and market-scanner-error events.
Klines are stored in per-pair, per-timeframe tables:
klines_<PAIR>_<TIMEFRAME>
Example:
klines_SOLUSDT_1m
Each table contains:
timestampopenhighlowclosebuyCostsellCostbuyVolumesellVolumeliqBuyCostliqSellCostliqBuyVolumeliqSellVolumetradeCountliqCount
Nexus includes a Binance historical pipeline for bootstrapping the SQLite database.
bun run download-historical-data 2026-01-01 2026-01-31What it does:
- Downloads Binance USD-M
SOLUSDT1mklines - Unzips and merges the downloaded CSV parts
- Normalizes the CSV into the Nexus schema
- Imports
1mdata into SQLite - Aggregates and imports
5m,15m,1h,4h, and1d
Generated output:
data/historical/SOLUSDT-1m/SOLUSDT.csv
bun run import-historical-klines SOLUSDT data/historical/SOLUSDT-1m/SOLUSDT.csvImported CSV files must use this header:
timestamp,open,high,low,close,buyCost,sellCost,buyVolume,sellVolume,liqBuyCost,liqSellCost,liqBuyVolume,liqSellVolume,tradeCount,liqCountNotes:
- the importer expects sorted ascending timestamps
- the importer is designed for normalized
1msource data - liquidation columns may be zero for exchange exports that do not include them
Run the main validation commands before opening a PR:
bun run test
bun run typecheck
bun run lintCreate the Linux x64 binary:
bun run buildOutput:
build/x64/sio-nexus
The deployment script:
bun run deployCurrent behavior:
- runs typecheck and build
- copies
build/x64/sio-nexusto the remote host withrsync - restarts
sio-nexusthroughsystemctl
The script expects SSH settings in .env:
SIO_NEXUS_SSH_HOSTSIO_NEXUS_SSH_USERNAMESIO_NEXUS_SSH_PORT
- The built-in historical downloader is currently hardcoded to Binance USD-M
SOLUSDT1m - Live aggregation timeframes currently persist
1m,5m,15m,1h,4h, and1d - Socket APIs are Unix-socket based, so clients must run on the same machine or through their own forwarding layer
This project is licensed under the GNU General Public License v3.0. See LICENSE.
