Skip to content
EliteChart

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

code
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:

code
'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:

  1. A backend service (Python / C# / Go) connects to MT5 via the official RPC.
  2. The backend exposes a REST + WS surface that matches the Broker interface byte-for-byte.
  3. 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.