Node.js Bindings¶
Event-driven live trading and backtesting from Node.js using @flox-foundation/flox.
Install / Build¶
The addon is built out of tree by npm, not by CMake. FLOX_BUILD_NODE exists for parity with the other binding flags but does not invoke npm. The compiled addon lands at node/build/Release/flox_node.node.
const flox = require('@flox-foundation/flox');
// or, if building from source:
const flox = require('../node');
SymbolRegistry and Symbol¶
const registry = new flox.SymbolRegistry();
const btc = registry.addSymbol('binance', 'BTCUSDT', 0.01);
btc.id // 1
btc.name // "BTCUSDT"
btc.exchange // "binance"
btc.tickSize // 0.01
Number(btc) // 1
btc.valueOf() // 1
btc + 0 // 1
btc.toString() // "Symbol(binance:BTCUSDT, id=1)"
// btc works as a number wherever a symbol ID is expected
Strategy¶
A strategy is a plain JavaScript object with callback properties.
const strategy = {
symbols: [btc],
onStart() {},
onStop() {},
onTrade(ctx, trade, emit) {
// ctx.position — current position quantity
// ctx.symbolId — symbol ID (int)
// ctx.lastTradePrice
// ctx.bestBid
// ctx.bestAsk
// ctx.midPrice
// trade.price
// trade.qty
// trade.isBuy — boolean
// trade.side — "buy" | "sell"
// trade.timestampNs — BigInt nanoseconds
if (ctx.position === 0) {
emit.marketBuy(0.01);
}
},
onBookUpdate(ctx, emit) {},
};
emit methods¶
| Method | Description |
|---|---|
emit.marketBuy(qty) |
Market buy |
emit.marketSell(qty) |
Market sell |
emit.limitBuy(price, qty) |
Limit buy |
emit.limitSell(price, qty) |
Limit sell |
emit.provideLiquidity(priceLower, priceUpper, liquidity) |
Add AMM liquidity in a tick range |
emit.withdrawLiquidity(liquidity) |
Remove AMM liquidity |
emit.cancel(orderId) |
Cancel order |
emit.closePosition() |
Close position (reduce-only) |
Runner¶
function onSignal(sig) {
// sig.side — "buy" | "sell"
// sig.quantity
// sig.price — 0 for market orders
// sig.orderType — "market" | "limit" | ...
// sig.orderId
}
const runner = new flox.Runner(registry, onSignal); // synchronous
const runner = new flox.Runner(registry, onSignal, true); // Disruptor background thread
runner.addStrategy(strategy);
runner.start();
// Inject market data from your feed:
runner.onTrade(btc, price, qty, isBuy, tsNs);
runner.onBookSnapshot(btc, bidPrices, bidQtys, askPrices, askQtys, tsNs);
runner.stop();
btc in feed methods accepts a Symbol object or a raw number.
Backtest¶
const bt = new flox.BacktestRunner(registry, 0.0004, 10_000);
bt.setStrategy(strategy);
const stats = bt.runCsv('/path/to/btcusdt_1m.csv', 'BTCUSDT');
The constructor takes (registry, feeRate, initialCapital) — nothing else. Flat fee rate, no funding, no liquidation, no rate limits, no queue position. Good for an indicator sanity check; not enough before live.
runCsv reads OHLCV bars: one header line, then timestamp,open,high,low,close,volume. Only the timestamp and close columns are used, and each bar is replayed as one trade. Other entry points: runOhlcv(timestamps, closes, symbol), runTape(path), runTapes(paths).
Hooks attach after construction: setExecutor(executor) (an object implementing the Executor interface, or null), addExecutionListener(listener). Run artifacts: equityCurve(), trades().
Stats object¶
| Key | Description |
|---|---|
returnPct |
Net return percentage |
netPnl |
Net P&L after fees |
totalTrades |
Round-trip trade count |
winRate |
Winning trade fraction |
sharpeRatio |
Annualized Sharpe ratio |
maxDrawdownPct |
Peak-to-trough drawdown (%) |
Also present: winningTrades, losingTrades, initialCapital, finalCapital, totalFees, maxDrawdown, profitFactor, avgWin, avgLoss.
Every producer uses the same names: sharpeRatio, sortinoRatio and calmarRatio. What differs is the field set, and index.d.ts has one interface per shape — BacktestStats for BacktestResult.stats(), RunnerStats for BacktestRunner.runCsv / runOhlcv / runBars, and FoldStats for GridSearch and WalkForwardRunner.
Venue physics¶
VenueStack is a separate venue simulation with its own flat proxy surface. It is not an argument to BacktestRunner and does not attach to one.
const stack = flox.VenueStack.binanceUmFutures(42, 10_000);
stack.accountOpenPosition(btc, 5.0, 50_000);
stack.accountSetMark(btc, 47_000);
const liquidated = stack.liquidationOnMark(btc, 47_000);
stack.feesRecordFill(tsNs, 20_000);
One call wires the cross-margin account, MM tiers and ADL, the VIP fee schedule, funding settlement, rate limits, and the venue-availability hook. Other factories: bybitLinear, okxSwap, deribit, plus VenueStack.fromVenue(name, accountId, equity).
Full pattern and pieces: Realistic backtest in one call, Cross-margin accounts, Liquidation and ADL.
Full Example — SMA Crossover¶
const flox = require('@flox-foundation/flox');
const registry = new flox.SymbolRegistry();
const btc = registry.addSymbol('binance', 'BTCUSDT', 0.01);
// Simple SMA helper
function makeSMA(period) {
const buf = [];
return {
update(price) {
buf.push(price);
if (buf.length > period) buf.shift();
if (buf.length < period) return null;
return buf.reduce((a, b) => a + b, 0) / period;
},
};
}
const fast = makeSMA(10);
const slow = makeSMA(30);
const strategy = {
symbols: [btc],
onTrade(ctx, trade, emit) {
const f = fast.update(trade.price);
const s = slow.update(trade.price);
if (f === null || s === null) return;
if (f > s && ctx.position === 0) {
emit.marketBuy(0.01);
} else if (f < s && ctx.position > 0) {
emit.closePosition();
}
},
};
// --- Live ---
function onSignal(sig) {
console.log(sig.side, sig.orderType, sig.quantity);
// forward to exchange
}
const runner = new flox.Runner(registry, onSignal);
runner.addStrategy(strategy);
runner.start();
// runner.onTrade(btc, price, qty, isBuy, tsNs) ← from your market data feed
// runner.stop()
// --- Backtest ---
const bt = new flox.BacktestRunner(registry, 0.0004, 10_000);
bt.setStrategy(strategy);
const stats = bt.runCsv('./data/btcusdt_1m.csv', 'BTCUSDT');
console.log(stats);