Base class for custom broker adapter implementations.

Provides default no-op implementations for all IBroker methods that log events. Extend this class to implement a real exchange adapter for:

  • Placing and canceling limit/market orders
  • Updating stop-loss and take-profit levels on exchange
  • Tracking position state in an external system
  • Sending trade notifications (Telegram, Discord, Email)
  • Recording trades to a database or analytics service

Key features:

  • All methods have default implementations (no need to override unused methods)
  • Automatic logging of all events via bt.loggerService
  • Implements the full IBroker interface
  • makeExtendable applied for correct subclass instantiation

Lifecycle:

  1. Constructor called (no arguments)
  2. waitForInit() called once for async initialization (e.g. exchange login)
  3. Event methods called as strategy executes
  4. No explicit dispose — clean up in waitForInit teardown or externally

Event flow (called only in live mode, skipped in backtest):

  • onSignalOpenCommit — new position opened
  • onSignalCloseCommit — position closed (SL/TP hit or manual close)
  • onPartialProfitCommit — partial close at profit executed
  • onPartialLossCommit — partial close at loss executed
  • onTrailingStopCommit — trailing stop-loss updated
  • onTrailingTakeCommit — trailing take-profit updated
  • onBreakevenCommit — stop-loss moved to entry price
  • onAverageBuyCommit — new DCA entry added to position
import { BrokerBase, Broker } from "backtest-kit";

// Extend BrokerBase and override only needed methods
class BinanceBroker extends BrokerBase {
private client: BinanceClient | null = null;

async waitForInit() {
super.waitForInit(); // Call parent for logging
this.client = new BinanceClient(process.env.API_KEY, process.env.SECRET);
await this.client.connect();
}

async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
super.onSignalOpenCommit(payload); // Call parent for logging
await this.client!.placeOrder({
symbol: payload.symbol,
side: payload.position === "long" ? "BUY" : "SELL",
quantity: payload.cost / payload.priceOpen,
});
}

async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
super.onSignalCloseCommit(payload); // Call parent for logging
await this.client!.closePosition(payload.symbol);
}
}

// Register the adapter
Broker.useBrokerAdapter(BinanceBroker);
Broker.enable();
// Minimal implementation — only handle opens and closes
class NotifyBroker extends BrokerBase {
async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
await sendTelegram(`Opened ${payload.position} on ${payload.symbol}`);
}

async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
const pnl = payload.pnl.profit - payload.pnl.loss;
await sendTelegram(`Closed ${payload.symbol}: PnL $${pnl.toFixed(2)}`);
}
}

Implements

Constructors

Methods

  • Called when a new DCA entry is added to the active position.

    Triggered explicitly after all validations pass, before strategyCoreService.averageBuy(). currentPrice is the market price at which the new averaging entry is placed. cost is the dollar amount of the new DCA entry.

    Default implementation: Logs average buy event.

    Parameters

    Returns Promise<void>

    async onAverageBuyCommit(payload: BrokerAverageBuyPayload) {
    super.onAverageBuyCommit(payload); // Keep parent logging
    await this.exchange.placeMarketOrder({
    symbol: payload.symbol,
    side: "BUY",
    quantity: payload.cost / payload.currentPrice,
    });
    }
  • Called when the stop-loss is moved to breakeven (entry price).

    Triggered explicitly after all validations pass, before strategyCoreService.breakeven(). newStopLossPrice equals effectivePriceOpen — the position's effective entry price. newTakeProfitPrice is unchanged by breakeven.

    Default implementation: Logs breakeven event.

    Parameters

    • payload: BrokerBreakevenPayload

      Breakeven details: symbol, currentPrice, newStopLossPrice, newTakeProfitPrice, context, backtest

    Returns Promise<void>

    async onBreakevenCommit(payload: BrokerBreakevenPayload) {
    super.onBreakevenCommit(payload); // Keep parent logging
    await this.exchange.updateStopLoss({
    symbol: payload.symbol,
    price: payload.newStopLossPrice, // = entry price
    });
    }
  • Called when a partial close at loss is executed.

    Triggered explicitly from strategy.ts / Live.ts / Backtest.ts after all validations pass, before strategyCoreService.partialLoss(). If this method throws, the DI mutation is skipped. Use to partially close the position on the exchange at the loss level.

    Default implementation: Logs partial loss event.

    Parameters

    • payload: BrokerPartialLossPayload

      Partial loss details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest

    Returns Promise<void>

    async onPartialLossCommit(payload: BrokerPartialLossPayload) {
    super.onPartialLossCommit(payload); // Keep parent logging
    await this.exchange.reducePosition({
    symbol: payload.symbol,
    dollarAmount: payload.cost,
    price: payload.currentPrice,
    });
    }
  • Called when a partial close at profit is executed.

    Triggered explicitly from strategy.ts / Live.ts / Backtest.ts after all validations pass, before strategyCoreService.partialProfit(). If this method throws, the DI mutation is skipped. Use to partially close the position on the exchange at the profit level.

    Default implementation: Logs partial profit event.

    Parameters

    • payload: BrokerPartialProfitPayload

      Partial profit details: symbol, percentToClose, cost (dollar value), currentPrice, context, backtest

    Returns Promise<void>

    async onPartialProfitCommit(payload: BrokerPartialProfitPayload) {
    super.onPartialProfitCommit(payload); // Keep parent logging
    await this.exchange.reducePosition({
    symbol: payload.symbol,
    dollarAmount: payload.cost,
    price: payload.currentPrice,
    });
    }
  • Called when a position is fully closed (SL/TP hit or manual close).

    Triggered automatically via syncSubject when a pending signal is closed. Use to place the exit order and record final PnL.

    Default implementation: Logs signal-close event.

    Parameters

    • payload: BrokerSignalClosePayload

      Signal close details: symbol, cost, position, currentPrice, pnl, totalEntries, totalPartials, context, backtest

    Returns Promise<void>

    async onSignalCloseCommit(payload: BrokerSignalClosePayload) {
    super.onSignalCloseCommit(payload); // Keep parent logging
    await this.exchange.closePosition(payload.symbol);
    await this.db.recordTrade({ symbol: payload.symbol, pnl: payload.pnl });
    }
  • Called when a new position is opened (signal activated).

    Triggered automatically via syncSubject when a scheduled signal's priceOpen is hit. Use to place the actual entry order on the exchange.

    Default implementation: Logs signal-open event.

    Parameters

    • payload: BrokerSignalOpenPayload

      Signal open details: symbol, cost, position, priceOpen, priceTakeProfit, priceStopLoss, context, backtest

    Returns Promise<void>

    async onSignalOpenCommit(payload: BrokerSignalOpenPayload) {
    super.onSignalOpenCommit(payload); // Keep parent logging
    await this.exchange.placeMarketOrder({
    symbol: payload.symbol,
    side: payload.position === "long" ? "BUY" : "SELL",
    quantity: payload.cost / payload.priceOpen,
    });
    }
  • Called when the trailing stop-loss level is updated.

    Triggered explicitly after all validations pass, before strategyCoreService.trailingStop(). newStopLossPrice is the absolute SL price — use it to update the exchange order directly.

    Default implementation: Logs trailing stop event.

    Parameters

    • payload: BrokerTrailingStopPayload

      Trailing stop details: symbol, percentShift, currentPrice, newStopLossPrice, context, backtest

    Returns Promise<void>

    async onTrailingStopCommit(payload: BrokerTrailingStopPayload) {
    super.onTrailingStopCommit(payload); // Keep parent logging
    await this.exchange.updateStopLoss({
    symbol: payload.symbol,
    price: payload.newStopLossPrice,
    });
    }
  • Called when the trailing take-profit level is updated.

    Triggered explicitly after all validations pass, before strategyCoreService.trailingTake(). newTakeProfitPrice is the absolute TP price — use it to update the exchange order directly.

    Default implementation: Logs trailing take event.

    Parameters

    • payload: BrokerTrailingTakePayload

      Trailing take details: symbol, percentShift, currentPrice, newTakeProfitPrice, context, backtest

    Returns Promise<void>

    async onTrailingTakeCommit(payload: BrokerTrailingTakePayload) {
    super.onTrailingTakeCommit(payload); // Keep parent logging
    await this.exchange.updateTakeProfit({
    symbol: payload.symbol,
    price: payload.newTakeProfitPrice,
    });
    }
  • Performs async initialization before the broker starts receiving events.

    Called once by BrokerProxy via waitForInit() (singleshot) before the first event. Override to establish exchange connections, authenticate API clients, load configuration.

    Default implementation: Logs initialization event.

    Returns Promise<void>

    async waitForInit() {
    super.waitForInit(); // Keep parent logging
    this.exchange = new ExchangeClient(process.env.API_KEY);
    await this.exchange.authenticate();
    }