GitHubBlog

Search Documentation

Search for a page in the docs

Custom Guards

Guards are pre-execution safety checks that run before orders reach the broker. You can create custom guards to enforce any risk rule you need.

OperationGuard Interface

interface OperationGuard {
  readonly name: string
  check(ctx: GuardContext): Promise<string | null> | string | null
}

Return null to allow the operation, or a string to reject it with a reason.

GuardContext

The context provides everything you need to evaluate an operation:

interface GuardContext {
  readonly operation: Operation    // The operation being checked
  readonly positions: Position[]   // Current portfolio positions
  readonly account: AccountInfo    // Account info (equity, cash, etc.)
}

The guard pipeline fetches positions and account info from the broker before running guards, so your guard always has fresh data.

Creating a Custom Guard

1. Implement the Guard

import type { OperationGuard, GuardContext } from './types.js'

export class MaxDailyTradesGuard implements OperationGuard {
  readonly name = 'max-daily-trades'
  private maxTrades: number
  private todayCount = 0
  private lastResetDate = ''

  constructor(options: Record<string, unknown>) {
    this.maxTrades = Number(options.maxTrades ?? 10)
  }

  check(ctx: GuardContext): string | null {
    if (ctx.operation.action !== 'placeOrder') return null

    const today = new Date().toISOString().slice(0, 10)
    if (today !== this.lastResetDate) {
      this.todayCount = 0
      this.lastResetDate = today
    }

    this.todayCount++
    if (this.todayCount > this.maxTrades) {
      return `Daily trade limit reached (${this.maxTrades} trades per day)`
    }

    return null
  }
}

2. Register in the Guard Registry

import { registerGuard } from './registry.js'
import { MaxDailyTradesGuard } from './max-daily-trades.js'

registerGuard({
  type: 'max-daily-trades',
  create: (opts) => new MaxDailyTradesGuard(opts),
})

3. Configure per Account

{
  "id": "alpaca-paper",
  "type": "alpaca",
  "guards": [
    { "type": "max-daily-trades", "options": { "maxTrades": 5 } }
  ]
}

Guard Pipeline Flow

Guards run sequentially during the push step:

  1. Pipeline fetches positions + account info from broker
  2. For each staged operation, builds a GuardContext
  3. Runs each guard in order — first rejection wins
  4. If all guards pass, the operation is dispatched to the broker
  5. If any guard rejects, the operation fails and the rejection is recorded

Built-in Guards for Reference

GuardWhat it checks
max-position-sizeProjected position value vs equity percentage
cooldownMinimum time between trades on the same symbol
symbol-whitelistWhether the symbol is in an allowed list

These are defined in src/domain/trading/guards/ and serve as reference implementations.