Custom Brokers
Adding a new broker to OpenAlice requires three things: implement the IBroker interface, declare config schema and UI field descriptors, and register in the broker registry. Zero changes to the framework needed.
IBroker Interface
Every broker implements this interface:
interface IBroker {
readonly id: string // Unique account ID
readonly label: string // Display name
// Lifecycle
init(): Promise<void>
close(): Promise<void>
// Contract search
searchContracts(pattern: string): Promise<ContractDescription[]>
getContractDetails(query: Contract): Promise<ContractDetails | null>
// Account data
getAccount(): Promise<AccountInfo>
getPositions(): Promise<Position[]>
getOrders(pendingIds: string[]): Promise<OpenOrder[]>
getOrder(orderId: string): Promise<OpenOrder | null>
// Trading
placeOrder(contract: Contract, order: Order, tpsl?: TpSlParams): Promise<PlaceOrderResult>
modifyOrder(orderId: string, changes: Partial<Order>): Promise<PlaceOrderResult>
closePosition(contract: Contract, qty?: Decimal): Promise<PlaceOrderResult>
cancelOrder(orderId: string, orderCancel?: OrderCancel): Promise<PlaceOrderResult>
// Market data
getQuote(contract: Contract): Promise<Quote>
getMarketClock(): Promise<MarketClock>
// Identity
getNativeKey(contract: Contract): string
resolveNativeKey(nativeKey: string): Contract
getCapabilities(): AccountCapabilities
getHealth(): Promise<BrokerHealthInfo>
}
All types (Contract, Order, Execution, OrderState) come from @traderalice/ibkr — this is the single source of truth for the type system.
Config Schema & UI Fields
Each broker declares its config shape and how the Web UI should render the config form:
class MyBroker implements IBroker {
// Zod schema for validating brokerConfig from accounts.json
static configSchema = z.object({
apiKey: z.string(),
apiSecret: z.string(),
sandbox: z.boolean().default(false),
})
// UI field descriptors for dynamic form rendering
static configFields: BrokerConfigField[] = [
{ name: 'apiKey', type: 'password', label: 'API Key', required: true, sensitive: true },
{ name: 'apiSecret', type: 'password', label: 'API Secret', required: true, sensitive: true },
{ name: 'sandbox', type: 'boolean', label: 'Sandbox Mode', default: false },
]
// Factory: construct from AccountConfig
static fromConfig(config: AccountConfig): IBroker {
const parsed = MyBroker.configSchema.parse(config.brokerConfig)
return new MyBroker(config.id, config.label ?? config.id, parsed)
}
}
Register in the Broker Registry
Add one entry to src/domain/trading/brokers/registry.ts:
import { MyBroker } from './my-broker/MyBroker.js'
export const BROKER_REGISTRY: Record<string, BrokerRegistryEntry> = {
// ... existing brokers
'my-broker': {
configSchema: MyBroker.configSchema,
configFields: MyBroker.configFields,
fromConfig: MyBroker.fromConfig,
name: 'My Broker',
description: 'Description for the account management UI.',
badge: 'MB',
badgeColor: 'text-blue-400',
subtitleFields: [{ field: 'sandbox', label: 'Sandbox' }],
guardCategory: 'securities', // or 'crypto'
},
}
The BrokerRegistryEntry includes:
| Field | Description |
|---|---|
configSchema | Zod schema for validation |
configFields | UI field descriptors for dynamic form rendering |
fromConfig | Factory function from AccountConfig |
name | Display name |
description | Short description |
badge / badgeColor | 2-3 char badge for the UI |
subtitleFields | Fields shown in the account card subtitle |
guardCategory | "crypto" or "securities" — determines available guard types |
Financial Precision
Use decimal.js for all quantity calculations. The Order.totalQuantity field is a Decimal, and Position.quantity must also be a Decimal. Never use JavaScript floating-point for financial math.
Error Handling
Throw BrokerError with appropriate codes:
| Code | Permanent? | Description |
|---|---|---|
CONFIG | Yes | Invalid configuration |
AUTH | Yes | Authentication failure |
NETWORK | No | Network/timeout issues |
EXCHANGE | No | Exchange-level rejections |
MARKET_CLOSED | No | Market is closed |
UNKNOWN | No | Unclassified errors |
Permanent errors disable the account. Transient errors trigger auto-recovery with exponential backoff.
Reference
Study the existing implementations for patterns:
- AlpacaBroker — Simple REST API broker, good starting point
- CcxtBroker — Adapter pattern over the CCXT library
- IbkrBroker — Complex callback-based SDK bridged to Promise-based interface