Broker Adapter: Transactional Live Order Execution
Implement a Broker adapter to execute live orders with transactional safety — exchange failures roll back internal state automatically and retry next tick.
Use this file to discover all available pages before exploring further.
The broker adapter is Backtest Kit’s integration point between the framework’s internal position state machine and a real exchange. Every trade mutation — opening a position, closing at TP/SL, adjusting a trailing stop, adding a DCA entry — fires a corresponding hook on the adapter before the framework mutates its internal state. If the adapter throws (exchange rejection, network timeout, rate limit), the mutation is skipped and the internal state is left exactly as it was. The framework retries automatically on the next tick. This transactional guarantee means a failing exchange can never desync the engine’s ledger from what actually happened on the exchange.
The broker adapter implements eight methods corresponding to the eight position events the framework can emit. All methods are optional (Partial<IBroker>) — unimplemented hooks are no-ops.
Method
Fired when
onSignalOpenCommit
A new position is being activated (entry order)
onSignalCloseCommit
Position is being closed (SL/TP hit or manual close)
onPartialProfitCommit
A partial profit close is being executed
onPartialLossCommit
A partial loss close is being executed
onTrailingStopCommit
The stop-loss price is being adjusted upward (trailing)
onTrailingTakeCommit
The take-profit price is being adjusted
onBreakevenCommit
The stop-loss is being moved to the entry price
onAverageBuyCommit
A DCA entry is being added to the position
An optional waitForInit() method can be used to warm up exchange connections before the first tick fires.
The minimal adapter handles the two most common operations — opening and closing positions:
import { Broker, IBroker, BrokerSignalOpenPayload, BrokerSignalClosePayload,} from 'backtest-kit';class MyBroker implements Partial<IBroker> { async waitForInit(): Promise<void> { // Pre-warm exchange connection, load markets, etc. // Called once on startup before any hook fires. } async onSignalOpenCommit(payload: BrokerSignalOpenPayload): Promise<void> { const { symbol, cost, priceOpen, priceTakeProfit, priceStopLoss, position } = payload; // Place entry order on exchange. // Throw to prevent the open from being recorded internally. } async onSignalCloseCommit(payload: BrokerSignalClosePayload): Promise<void> { const { symbol, currentPrice } = payload; // Place close order on exchange. // Throw to prevent the close from being recorded internally. }}Broker.useBrokerAdapter(MyBroker);Broker.enable();
Pass the class constructor to useBrokerAdapter, not an instance. The
framework instantiates it internally and manages the lifecycle.
async onSignalOpenCommit(payload: BrokerSignalOpenPayload): Promise<void> { const { symbol, // trading pair, e.g. 'BTCUSDT' cost, // position size in quote currency (e.g. $100 USDT) priceOpen, // target entry price (VWAP-aligned) priceTakeProfit, // TP level priceStopLoss, // SL level position, // 'long' | 'short' } = payload; // Place limit or market entry order. // Place TP and SL orders after fill. // If entry times out, throw — position state is not mutated.}
onSignalCloseCommit — SL/TP/manual close
async onSignalCloseCommit(payload: BrokerSignalClosePayload): Promise<void> { const { symbol, position, // 'long' | 'short' currentPrice, // target close price priceTakeProfit, // current TP (for restore if close times out) priceStopLoss, // current SL (for restore if close times out) } = payload; // Cancel existing SL/TP orders, then place close order. // If position was already closed by exchange-side SL/TP, throw gracefully.}
onPartialProfitCommit — partial profit close
async onPartialProfitCommit(payload: BrokerPartialProfitPayload): Promise<void> { const { symbol, percentToClose, // percentage of current position to close (0–100) currentPrice, // target close price for the partial position, priceTakeProfit, priceStopLoss, } = payload; // Cancel existing SL/TP, sell `percentToClose`% of position. // Restore SL/TP on the remaining qty after fill.}
onPartialLossCommit — partial loss close
async onPartialLossCommit(payload: BrokerPartialLossPayload): Promise<void> { const { symbol, percentToClose, currentPrice, position, priceTakeProfit, priceStopLoss, } = payload; // Same pattern as partial profit but triggered at a loss level.}
onTrailingStopCommit — stop-loss adjustment
async onTrailingStopCommit(payload: BrokerTrailingStopPayload): Promise<void> { const { symbol, newStopLossPrice, // new SL price (higher than original for long) position, } = payload; // Cancel the existing SL order and place a new one at newStopLossPrice.}
onTrailingTakeCommit — take-profit adjustment
async onTrailingTakeCommit(payload: BrokerTrailingTakePayload): Promise<void> { const { symbol, newTakeProfitPrice, position, } = payload; // Cancel the existing TP order and place a new one at newTakeProfitPrice.}
onBreakevenCommit — SL moved to entry price
async onBreakevenCommit(payload: BrokerBreakevenPayload): Promise<void> { const { symbol, newStopLossPrice, // equal to original entry price position, } = payload; // Cancel existing SL, place new SL at the breakeven (entry) price.}
onAverageBuyCommit — DCA entry
async onAverageBuyCommit(payload: BrokerAverageBuyPayload): Promise<void> { const { symbol, currentPrice, // DCA entry price cost, // additional cost in quote currency position, priceTakeProfit, // updated TP for the new combined position priceStopLoss, // updated SL for the new combined position } = payload; // Cancel existing SL/TP, place DCA entry, restore SL/TP on total position.}
The cleanest way to register a broker adapter with @backtest-kit/cli is through a mode-specific module file. The CLI loads ./modules/<mode>.module.ts as a side-effect import before the run starts.Create modules/live.module.ts:
The transactional guarantee is what makes the broker adapter production-safe:
1
Adapter hook fires before state mutation
The framework calls e.g. onSignalOpenCommit with the full payload.
2
Exchange rejects the order (or network times out)
The adapter throws an Error. The exchange has not filled the order.
3
Framework catches the error and rolls back
The internal position state is left exactly as it was before the hook was called. No opened event is emitted. The signal remains in its previous state (e.g. idle).
4
Automatic retry on the next tick
On the next strategy interval, the framework evaluates the signal again. If getSignal returns the same entry parameters, onSignalOpenCommit fires again. A transient exchange error heals itself without any manual intervention.
You can deliberately test this rollback path by registering a broker that always throws:
import { Broker, IBroker, BrokerSignalOpenPayload } from 'backtest-kit';Broker.useBrokerAdapter( class implements Partial<IBroker> { async onSignalOpenCommit(payload: BrokerSignalOpenPayload): Promise<void> { console.log('SIGNAL_OPEN', payload); throw new Error('Exchange not available — rollback test'); } });Broker.enable();
The UI will surface the thrown message back to the operator. The engine’s position state will remain unchanged, confirming the transactional guarantee.
The broker adapter is automatically bypassed in backtest mode. All eight
hooks are no-ops during historical replay — no exchange API calls are made,
no orders are placed, and no real capital is at risk. Registering a broker
adapter in a backtest module file has no effect. This is intentional: the
framework isolates exchange code from historical simulations to prevent
accidental order placement during development.