Skip to content

Backtest components

const { BacktestRunner, SimulatedExecutor, BacktestResult, Engine, SignalBuilder } = require('@flox-foundation/flox');

BacktestRunner

Replays OHLCV data through a strategy. Emitted orders go to a SimulatedExecutor; statistics are returned at the end.

const bt = new flox.BacktestRunner(registry, feeRate, initialCapital);
bt.setStrategy(strategy);

const stats = bt.runCsv('/path/to/data.csv', 'BTCUSDT');
const stats = bt.runOhlcv(timestampsNs, closePrices, 'BTCUSDT');
Method Returns Description
setStrategy(strategy) void Attach a strategy
runCsv(path, symbol) stats object Replay a CSV file (timestamp, open, high, low, close, volume)
runOhlcv(timestamps, closes, symbol) stats object Replay raw arrays (timestamps as Float64Array, closes as Float64Array)
runTape(path) stats object Replay a .floxlog tape directory — the canonical recording written by flox tape record or scripts/backfill_to_tape.py
runTapes(paths) stats object Merge N .floxlog tape directories on read and replay the merged stream. Symbols are rekeyed by (metadata.exchange, name) so two venues stay distinct. runTapes([t]) equals runTape(t); throws on bad paths or overlapping book streams
setExecutor(executor) void Replace the built-in SimulatedExecutor. null detaches
addExecutionListener(listener) void Attach an order-lifecycle listener. Multiple listeners may be attached; each fires for every order event
equityCurve() EquityCurve Equity curve from the most recent run
trades() BacktestTrades Closed trades from the most recent run

equityCurve() and trades() throw FloxError(E_RUN_002) if no run has completed.

Stats object

Key Type Description
totalTrades number Round-trip trade count
winningTrades number Winning trades
losingTrades number Losing trades
initialCapital number Starting capital
finalCapital number Ending capital
netPnl number Net P&L after fees
totalPnl number Gross P&L
totalFees number Total fees paid
grossProfit number Sum of winning trades
grossLoss number Sum of losing trades
maxDrawdown number Max drawdown (absolute)
maxDrawdownPct number Max drawdown (%)
winRate number Winning trade ratio
profitFactor number Gross profit / gross loss
avgWin number Average winning trade
avgLoss number Average losing trade
sharpeRatio number Annualized Sharpe ratio
sortinoRatio number Sortino ratio
calmarRatio number Calmar ratio
returnPct number Net return (%)

SimulatedExecutor

Lower-level backtest executor. Use directly when you need more control over fill logic than BacktestRunner provides.

const exec = new flox.SimulatedExecutor();
exec.setDefaultSlippage('fixed_bps', 0, 0, 2.0, 0);
exec.setQueueModel('tob', 1);
Method / Property Description
submitOrder(id, side, price, qty, type, symbol, opts?) Submit an order (side: "buy"/"sell")
cancelOrder(orderId) Cancel an order
cancelAll(symbol) Cancel all orders for a symbol
onBar(symbol, closePrice) Feed a bar close
onTrade(symbol, price, isBuy) Feed a trade
advanceClock(timestampNs) Advance simulated time
setDefaultSlippage(model, ticks, tickSize, bps, impactCoeff) Configure slippage. model is one of "none", "fixed_ticks", "fixed_bps", "volume_impact"
setQueueModel(model, depth) Configure limit order queue. model is one of "none", "tob", "full", "pro_rata", "pro_rata_with_fifo", "top_pro_lmm", "pro_rata_with_priority"
fillCount Number of fills (property)

submitOrder's type is one of "market", "limit", "stop_market", "stop_limit", "trailing_stop". The optional seventh argument carries { tif?: 'gtc' | 'ioc' | 'fok' | 'gtd' | 'post_only', reduceOnly?: boolean, expiresAtNs?: number }.


BacktestResult

Computes statistics and equity curve from a SimulatedExecutor's fills.

const result = new flox.BacktestResult(initialCapital, feeRate);
result.ingestExecutor(exec);
const stats = result.stats();
Method Returns Description
recordFill(orderId, symbol, side, price, qty, timestampNs) void Record a single fill
ingestExecutor(exec) void Drain all fills from a SimulatedExecutor
stats() stats object Same fields as BacktestRunner stats

Engine

Bulk backtesting engine. Loads OHLCV data once, then runs strategies against it.

The constructor takes capital and fee rate, not a registry.

const engine = new flox.Engine(100_000, 0.0001);
engine.loadCsv('/path/to/btcusdt_1m.csv');   // symbol inferred from the filename
const signals = new flox.SignalBuilder();
signals.buy(tsNs, 1.0);
const stats = engine.run(signals);
Method / Property Returns Description
new Engine(initialCapital?, feeRate?) Defaults 100000 and 0.0001 (fee is a fraction)
loadCsv(path, symbol?) void Load OHLCV CSV. Without symbol the name is inferred from the file stem, upper-cased, with a _1m / _5m / _15m / _1h / _4h / _1d suffix stripped
loadOhlcv(bars, symbol?) void bars is one object {ts, open, high, low, close, volume} of Float64Arrays, not positional arrays. Timestamps in s / ms / us are auto-scaled to ns
resample(src, dst, interval) void Three strings, e.g. ('BTCUSDT', 'BTCUSDT_1h', '1h'). Interval is <count><s\|m\|h\|d>. Throws E_SYM_001 if src is not loaded
run(signals) BacktestStats Takes a SignalBuilder only. Passing a Strategy aborts
barCount(symbol?) number Bars loaded; defaults to the first loaded symbol
ts(symbol?) Float64Array Timestamps
open(symbol?) Float64Array Open prices
high(symbol?) Float64Array High prices
low(symbol?) Float64Array Low prices
close(symbol?) Float64Array Close prices
volume(symbol?) Float64Array Volumes
symbols string[] Registered symbol names (property)

run() returns the full BacktestStats shape, not the narrower runner shape — see Stats object. The key names are the same in both; only the field set differs.


SignalBuilder

Builds a signal array to pass to engine.run().

Signals are keyed by timestamp and carry a quantity — not a bar index.

const signals = new flox.SignalBuilder();
signals.buy(tsNs, 1.0)              // methods return `this`, so they chain
       .limitSell(tsNs, 101.0, 1.0);
const stats = engine.run(signals);
Method / Property Description
buy(tsNs, qty, symbol?) Long entry at tsNs. Returns this
sell(tsNs, qty, symbol?) Short entry. Returns this
limitBuy(tsNs, price, qty, symbol?) Limit long entry. Returns this
limitSell(tsNs, price, qty, symbol?) Limit short entry. Returns this
clear() Clear all signals
length Signal count (read-only property)

Timestamps are read as a JS number, not a bigint; values in s / ms / us are auto-scaled to ns. | length | Number of signals (property) |