Link to the source code
Reference implementation of a full addExchangeSchema setup using CCXT and Binance spot, with integrated volatility forecasting, volume anomaly, pump detection
Demonstrates how to wire a real exchange into backtest-kit for:
garchvolume-anomalypump-anomalyfetchOHLCV mapped to backtest-kit candle formatroundTicksroundTicksfetchOrderBook (backtest mode throws)publicGetAggTradessingleshotgetCandles adapter over the same exchange schemademo/ccxt/
├── src/
│ └── index.mjs # Exchange schema, volatility forecast, volume anomaly, pump detection, smoke tests
├── assets/
│ ├── parser-items.json # Crypto channel signal history (ParserItem[]) — pump-anomaly input
│ └── model-weights.json # Trained PumpMatrix weights (model.save() output, params v3)
├── package.json # Dependencies and scripts
└── README.md # This file
# Navigate to project directory
cd demo/ccxt
# Install dependencies
npm install
# Run the demo
npm start
Registers the exchange schema, then runs four smoke tests:
npm start
Output:
// 1. Last 5 candles (1m) — BTCUSDT
[
{ timestamp: ..., open: ..., high: ..., low: ..., close: ..., volume: ... },
...
]
// 2. Volume anomaly skew from aggregated trades — BTCUSDT
{ anomaly: true, confidence: 0.83, direction: 'long', imbalance: 0.61 }
// 3. Multi-timeframe volatility forecast — BTCUSDT
{
volatility_1m: { sigma_1m: 0.0004, reliable_1m: true },
volatility_5m: { sigma_5m: 0.0009, reliable_5m: true },
...
volatility_8h: { sigma_8h: 0.031, reliable_8h: false }
}
// 4. Pump backtest over the parser-items history (BacktestSignal[])
[
{
symbol: 'SOLUSDT', direction: 'long', action: 'enter', ts: ...,
exit: { trailingTake: 0.5, hardStop: 2, impactHorizonMinutes: 60, ... },
origin: { detector: 'single', exitSource: 'cell', volRegime: 'calm', ... },
result: { entered: true, pnl: 0.037, peak: 0.052, reason: 'trailing-take', heldMinutes: 42, ... }
},
...
]
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.
getExecutedTradesSkew)Fetches N_TRAIN + N_DETECT (1 400) aggregated trades and splits them into a calibration baseline and a detection window with no overlap:
const all = await Exchange.getAggregatedTrades(symbol, { exchangeName: "ccxt-exchange" }, 1400);
return anomaly.predict(all.slice(0, 1200), all.slice(1200), 0.75);
Returns { anomaly, confidence, direction, imbalance }.
getVolatilityForecast)Fetches candles across 8 timeframes (1m, 5m, 15m, 30m, 1h, 4h, 6h, 8h) and runs garch.predict() on each. Returns per-timeframe { sigma, reliable } pairs grouped by interval:
const { sigma: sigma_1m, reliable: reliable_1m } = await volatility.predict(candles_1m, "1m");
// ...
return { volatility_1m, volatility_5m, ..., volatility_8h };
getPumpWeights / getPumpLive / getPumpBacktest)All three phases share one getCandles adapter that forwards straight to Exchange.getRawCandles — the argument order (symbol, interval, limit, sDate, eDate) matches pump-anomaly's GetCandles contract one-to-one, so no reshaping is needed:
const getCandles = (symbol, interval, limit, sDate, eDate) =>
Exchange.getRawCandles(symbol, interval, { exchangeName: "ccxt-exchange" }, limit, sDate, eDate);
getPumpWeights — PumpMatrix.fit(signals, getCandles) trains once on parser-items.json (labels replay 1m candles forward, so this is the slow phase) and returns model.save() — the JSON written to assets/model-weights.json.getPumpLive — PumpMatrix.load(weights) then model.plan(signals, getCandles): look-ahead-free live decisions (cascade measured from candles before each signal). Returns ready-to-execute TradeSignal[].getPumpBacktest — same loaded weights, model.backtest(signals, getCandles): replays each exit plan forward over closed history. Returns BacktestSignal[], each carrying a result with realized pnl/reason. This is the function wired into the fourth smoke test.signals (the JSON) is the channel history; weights is a previously-trained model — getPumpLive/getPumpBacktest never retrain, they only load.
The aggregated trades and candles fetched via this exchange schema feed three independent libraries. garch and volume-anomaly are one-shot predictors over a candle/trade window; pump-anomaly is a trained model (train → save → load → plan/backtest) that answers which post to trade and how to exit it. All three plug into the same Exchange schema without touching each other.
garch — Volatility ForecastingForecasts 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.
volume-anomaly — Trade Flow Anomaly DetectionDetects 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
historicalandrecent— 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.
pump-anomaly — Pump Signal Detection & Exit PlanningDetects synchronized pump signals in a stream of Telegram channel recommendations (ParserItem[]) and turns each into a ready-to-execute trade plan. It solves three problems the other two libraries do not touch:
replayExit), so a wick into a liquidation-cascade trap is labeled negative even if close-to-close looks positive.[mode][channel][symbol][direction][volRegime] cell of an exit tensor.import * as pump from "pump-anomaly";
// getCandles is the same Exchange.getRawCandles adapter used above.
// 1) TRAIN once → serialize weights (slow: labels replay 1m candles forward)
const model = await pump.PumpMatrix.fit(signals, getCandles);
const weights = model.save(); // → assets/model-weights.json
// 2) LIVE — load weights, no retraining; cascade measured BEFORE the signal (no look-ahead)
const live = await pump.PumpMatrix.load(weights).plan(signals, getCandles);
// live: TradeSignal[] — direction already inverted if needed, entry zone + exit ready
// 3) BACKTEST — replay each exit plan forward, realized pnl in result
const bt = await pump.PumpMatrix.load(weights).backtest(signals, getCandles);
// bt: BacktestSignal[] — each carries result.pnl / result.reason
Three execution methods, distinguished by which candles they are allowed to see:
| method | candles | use |
|---|---|---|
signals(items, policy?) |
none | fast path; cascade not evaluated → every outcome is enter |
plan(items, source, policy?) |
before the signal | live decision, no look-ahead (squeezePressureBefore) |
backtest(items, source, policy?) |
after the signal | forward replay over closed history (realized pnl/cascade) |
signals/plan already pick the mode, compute volRegime, evaluate the cascade, filter veto, and apply inversion — execution just runs s.direction with s.exit, no if (veto) branching.
Statistical gates (why a trained model may refuse to trade). A grid search is argmax over thousands of CV scores, and the max of N noisy estimates is biased upward even when the true edge is zero. pump-anomaly defends against this in layers: the one-standard-error rule picks the most conservative config within 1 SE of the best (not the raw max); model.reliable/model.confidence report whether training had enough stable data; and model.certification is an independent five-barrier judge (Deflated Sharpe, PBO, SPA/Reality-Check, minTRL, nested OOS) — certified: false is the honest refusal that the surviving edge is a brute-force artifact.
if (!model.certification.certified) {
console.warn("do NOT trade this model:", model.certification.reasons);
}
Per-asset training grids. The default grid is deliberately small and asset-agnostic. The library ships tuned TrainGrids per asset (fastest → slowest: HYPE, SOL, TRX, TON, DOGE, BNB, ETH, XRP, LTC, ZEC, XLM, LINK, DOT, BTC), set from how each coin actually pumps — the unifying axis is pump speed → everything else (faster ⇒ shorter staleMinutes, tighter hardStop, shorter cascade window, looser matrix thresholds, more aggressive squeeze handling). A grid only steers where the search looks; the 1-SE rule and certification still decide what is tradeable.
See the pump-anomaly npm page for the full API reference, the TradeSignal/BacktestResult contracts, the exit tensor, the liquidation-cascade detector, and the meta-overfitting ledger.
MIT © tripolskypetr