A Broker implementation that lives entirely in memory, simulates
fills at the latest tick, and prints the resulting equity curve.
Useful for demos, training, and integration tests.
Quick example
import type { Broker, BrokerEvent, OrderRequest, OrderResult } from '@elitechart/elitechart';
export function createPaperBroker(): Broker {
let cash = 10_000;
let positions = new Map<string, { qty: number; entry: number }>();
let listeners: Array<(e: BrokerEvent) => void> = [];
const emit = (e: BrokerEvent) => listeners.forEach((l) => l(e));
const account = () => ({
id: 'paper', currency: 'USD',
balance: cash, equity: cash, margin: 0, freeMargin: cash, marginLevel: null,
});
return {
async getAccount() { return account(); },
async getPositions() {
return [...positions.entries()].map(([symbol, p]) => ({
id: symbol, symbol, side: p.qty > 0 ? 'long' : 'short',
quantity: Math.abs(p.qty), entryPrice: p.entry,
stopLoss: null, takeProfit: null,
unrealizedPnl: 0, openedAt: Date.now(),
} as const));
},
async getOrders() { return []; },
async placeOrder(req: OrderRequest): Promise<OrderResult> {
const fill = req.limit ?? req.market ?? 0;
const cost = fill * req.quantity;
cash -= cost;
const existing = positions.get(req.symbol) ?? { qty: 0, entry: 0 };
const newQty = existing.qty + (req.side === 'long' ? req.quantity : -req.quantity);
positions.set(req.symbol, { qty: newQty, entry: fill });
emit({ kind: 'order:fill', order: { id: `o-${Date.now()}`, symbol: req.symbol, side: req.side, quantity: req.quantity, price: fill, status: 'filled' } as never });
emit({ kind: 'account', account: account() });
return { id: `o-${Date.now()}` };
},
async modifyOrder() { /* no-op */ },
async cancelOrder() { /* no-op */ },
async closePosition(id: string) { positions.delete(id); emit({ kind: 'position:close', id }); },
subscribe(onEvent) {
listeners.push(onEvent);
return () => { listeners = listeners.filter((l) => l !== onEvent); };
},
};
}
How it works
The broker keeps two state slabs — cash and positions — and
emits the same BrokerEvent union your real broker would. Every
placeOrder debits cash, mutates the position, and fires a
order:fill followed by account. The chart's Trading Panel
reflects the change immediately.
For more realistic fills, plug in the latest mid price from your
Datafeed.subscribe callback and use it as the simulated fill
price.
Variations
Add slippage
const fill = (req.market ?? 0) * (1 + (req.side === 'long' ? 0.0005 : -0.0005));
Persist between reloads
Wrap cash and positions in useChartStore.notes JSON or
your own localStorage key.
API
The full Broker interface is in
concepts/broker-contract.