Connecting a broker
Wire a real broker into the EliteChart Trading Panel — order placement, position sync, MT5 bridge, error flow.
This guide builds a complete Broker against a generic REST + WS
backend, wires it into EliteChart, and shows the MT5-bridge
adaptation pattern.
Quick example
import type { Broker, BrokerEvent, OrderRequest, OrderResult } from '@elitechart/elitechart';
export class HttpBroker implements Broker {
private listeners: Array<(e: BrokerEvent) => void> = [];
private ws: WebSocket | null = null;
constructor(private readonly base: string, private readonly token: string) {}
async getAccount() {
const r = await fetch(`${this.base}/account`, this.headers());
return r.json();
}
async getPositions() {
const r = await fetch(`${this.base}/positions`, this.headers());
return r.json();
}
async getOrders() {
const r = await fetch(`${this.base}/orders`, this.headers());
return r.json();
}
async placeOrder(req: OrderRequest): Promise<OrderResult> {
const r = await fetch(`${this.base}/orders`, {
method: 'POST',
...this.headers(),
body: JSON.stringify(req),
});
if (!r.ok) throw new Error(`order failed: ${r.status}`);
return r.json();
}
async modifyOrder(id: string, patch: object) {
await fetch(`${this.base}/orders/${id}`, { method: 'PATCH', ...this.headers(), body: JSON.stringify(patch) });
}
async cancelOrder(id: string) {
await fetch(`${this.base}/orders/${id}`, { method: 'DELETE', ...this.headers() });
}
async closePosition(id: string) {
await fetch(`${this.base}/positions/${id}/close`, { method: 'POST', ...this.headers() });
}
subscribe(onEvent: (e: BrokerEvent) => void) {
this.listeners.push(onEvent);
if (this.ws === null) {
this.ws = new WebSocket(`${this.base.replace(/^http/, 'ws')}/stream?token=${this.token}`);
this.ws.onmessage = (e) => {
const ev = JSON.parse(e.data) as BrokerEvent;
for (const l of this.listeners) l(ev);
};
}
return () => {
this.listeners = this.listeners.filter((l) => l !== onEvent);
};
}
private headers() {
return { headers: { Authorization: `Bearer ${this.token}`, 'Content-Type': 'application/json' } };
}
}
Wire into the chart:
'use client';
import { EliteChart } from '@elitechart/elitechart';
import { HttpBroker } from '@/lib/broker';
const broker = new HttpBroker('https://api.example.com', process.env.NEXT_PUBLIC_API_TOKEN!);
export default function Page() {
return <EliteChart broker={broker} symbol="BTCUSD" timeframe="1h" />;
}
How it works
The Trading Panel in the bottom bar reads
positions and orders from your broker on mount, then re-renders on
every event from subscribe. placeOrder is called by the order
ticket; modifyOrder is called when the user drags a SL or TP line.
Errors should be thrown from placeOrder, modifyOrder,
cancelOrder, closePosition. The Trading Panel catches and
surfaces them as toast notifications. From subscribe, emit
{ kind: 'error', message } instead.
MT5 bridge
MetaTrader 5 isn't browser-callable. The standard pattern is a
small server-side bridge:
- A backend service (Python / C# / Go) connects to MT5 via the
official RPC.
- The backend exposes a REST + WS surface that matches the
Broker interface byte-for-byte.
- The browser talks to that surface; the bridge proxies to MT5.
The bridge service is yours to write — ChartForge doesn't ship one.
The 1:1 mapping makes it straightforward; the open-source MT5 API
clients on GitHub can be wrapped in a few hours.
Variations
Paper trading instead
See recipes/paper-trading for an
in-memory Broker — no backend required.
Read-only mode
Don't pass a broker prop. Trading Panel degrades to "trading not
available" gracefully.
API
See concepts/broker-contract for the
full interface and event union.