Exchange Schema Demo

Link to the source code

Reference implementation of a full addExchangeSchema setup using CCXT and Binance spot.

Demonstrates how to wire a real exchange into backtest-kit for:

  • OHLCV candle fetching with proper timestamp handling
  • Price and quantity formatting via market tick/step size
  • Order book fetching (live only)
  • Aggregated trade history fetching
  • Candle Fetching: fetchOHLCV mapped to backtest-kit candle format
  • Price Formatting: Tick-size-aware rounding via roundTicks
  • Quantity Formatting: Step-size-aware rounding via roundTicks
  • Order Book: Live order book via fetchOrderBook (backtest mode throws)
  • Aggregated Trades: Historical trade data via Binance publicGetAggTrades
  • Singleton Exchange: One CCXT instance reused across all calls via singleshot
  • Runtime: Node.js (ES Modules)
  • Framework: backtest-kit 6.5.0
  • Utilities: functools-kit 1.0.95
  • Data Source: ccxt 4.5.24 (Binance spot)
demo/exchange/
├── src/
│ └── index.mjs # Exchange schema registration and smoke test
├── package.json # Dependencies and scripts
└── README.md # This file
# Navigate to project directory
cd demo/exchange

# Install dependencies
npm install

# Run the demo
npm start

Registers the exchange schema and fetches 5 candles for BTCUSDT/1m:

npm start

Output:

[
{ timestamp: ..., open: ..., high: ..., low: ..., close: ..., volume: ... },
...
]

Copy addExchangeSchema block from src/index.mjs into your own strategy file and reference it by name:

import { Exchange } from "backtest-kit";

const candles = await Exchange.getCandles("ETHUSDT", "15m", 100, {
exchangeName: "ccxt-exchange",
});

Replace ccxt.binance with any other CCXT-supported exchange:

const exchange = new ccxt.bybit({
enableRateLimit: true,
});

A single Binance spot instance is created lazily via singleshot and reused across all calls:

const getExchange = singleshot(async () => {
const exchange = new ccxt.binance({ ... });
await exchange.loadMarkets();
return exchange;
});

Uses market limits (tick size / step size) with roundTicks fallback to CCXT precision methods:

const tickSize = market.limits?.price?.min || market.precision?.price;
return tickSize !== undefined
? roundTicks(price, tickSize)
: exchange.priceToPrecision(symbol, price);

Returns normalized { asks, bids } arrays with string price/quantity. Throws in backtest mode — implement your own snapshot replay if needed.

Uses Binance-specific publicGetAggTrades endpoint with startTime/endTime window.

The aggregated trades and candles fetched via this exchange schema can be fed directly into two companion analysis libraries.

Forecasts the expected price range for the next candle(s) using realized GARCH-family models. Auto-selects the best model (GARCH, EGARCH, GJR-GARCH, HAR-RV, NoVaS) by QLIKE error comparison.

import { predict } from 'garch';

const candles = await Exchange.getCandles("BTCUSDT", "4h", 200, { exchangeName: "ccxt-exchange" });
const result = predict(candles, '4h');
// {
// currentPrice: 97500,
// sigma: 0.012, // 1.2% per-period volatility
// upperPrice: 98677, // P·exp(+σ) — ceiling
// lowerPrice: 96337, // P·exp(-σ) — floor
// modelType: 'egarch',
// reliable: true
// }

Use predictRange(candles, interval, steps) for multi-candle swing trade corridors. Use backtest(candles, interval) for walk-forward validation of model accuracy.

Confidence bands:

confidence z Typical use
0.6827 (default) 1.00 Expected move, SL/TP targets
0.95 1.96 Risk management, position sizing
0.99 2.58 Stress testing, margin calculations

See the garch npm page for the full API reference.


Detects abnormal surges in trade flow from a raw stream of aggregated trades. Three independent detectors run in parallel (Hawkes Process, CUSUM, Bayesian Online Changepoint Detection) and combine into a single confidence score.

import { predict } from 'volume-anomaly';

// Fetch trades using getAggregatedTrades from the exchange schema
const all = await Exchange.getAggregatedTrades("BTCUSDT", from, to, { exchangeName: "ccxt-exchange" });
const historical = all.slice(0, 1200); // calibration baseline
const recent = all.slice(1200); // window to evaluate — no overlap

const result = predict(historical, recent, 0.75);
// {
// anomaly: true,
// confidence: 0.83,
// direction: 'long', // 'long' | 'short' | 'neutral'
// imbalance: 0.61,
// }

Never overlap historical and recent — training absorbs any anomaly in the baseline and the detector learns to treat it as normal.

Use the stateful VolumeAnomalyDetector class for continuous monitoring (re-use fitted models across multiple detect() calls without re-training).

See the volume-anomaly npm page for the full API reference.


MIT © tripolskypetr