Skip to content

Node.js Bindings

Event-driven live trading and backtesting from Node.js using @flox-foundation/flox.

Install / Build

npm install @flox-foundation/flox
# or build from source:
cd node && npm install && npm run 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);