> ## Documentation Index
> Fetch the complete documentation index at: https://docs.easierprop.com/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket Guide

> Real-time quote, order, profit, heartbeat, OHLC, order-book, tick-value, and alert streaming via WebSocket

## Connection

```
wss://api.easierprop.com/ws?apiKey=sk_your_key
```

## Subscribe to a Channel

Send a JSON message to start streaming:

```json theme={null}
{
  "type": "subscribe",
  "channel": "quotes",
  "account_id": "70f60784-20f1-45ba-9a04-8e01c0b810c3",
  "symbols": ["EURUSD", "GBPUSD"],
  "interval_ms": 500
}
```

**Server confirms:**

```json theme={null}
{
  "type": "subscribed",
  "channel": "quotes",
  "account_id": "70f60784-20f1-45ba-9a04-8e01c0b810c3",
  "symbols": ["EURUSD", "GBPUSD"]
}
```

## Receive Events

### Quote Ticks

Quote data is wrapped in a `data` field containing the upstream quote object:

```json theme={null}
{
  "type": "quote",
  "account_id": "70f60784-...",
  "data": {
    "ask": 1.08544,
    "bid": 1.08542,
    "last": 0.0,
    "symbol": "EURUSD",
    "time": "2026-04-01T12:30:00",
    "volume": 0.0
  }
}
```

### Order Updates

Sent when an order is opened, modified, or closed. Order data is wrapped in a `data` field:

```json theme={null}
{
  "type": "order_update",
  "account_id": "70f60784-...",
  "data": {
    "ticket": 12345678,
    "symbol": "EURUSD",
    "orderType": "Buy",
    "lots": 0.01,
    "openPrice": 1.08542,
    "profit": 3.20,
    "swap": 0.0,
    "commission": -0.07
  }
}
```

### Profit Updates

Live equity and P\&L, wrapped in a `data` field:

```json theme={null}
{
  "type": "profit",
  "account_id": "70f60784-...",
  "data": {
    "balance": 10000.00,
    "equity": 10142.50,
    "profit": 142.50,
    "margin": 500.00,
    "freeMargin": 9642.50
  }
}
```

### Heartbeat

Periodic account health snapshot with balance, equity, margin, and position count:

```json theme={null}
{
  "type": "heartbeat",
  "account_id": "70f60784-...",
  "balance": 10000.00,
  "equity": 10142.50,
  "free_margin": 9642.50,
  "margin": 500.00,
  "position_count": 3,
  "profit": 142.50
}
```

Subscribe to heartbeat:

```json theme={null}
{
  "type": "subscribe",
  "channel": "heartbeat",
  "account_id": "70f60784-20f1-45ba-9a04-8e01c0b810c3"
}
```

### New Position

Fires when a new position is opened on the account. The full order object is delivered in the `data` field:

```json theme={null}
{
  "type": "new_position",
  "account_id": "70f60784-...",
  "data": {
    "ticket": 12345680,
    "symbol": "GBPUSD",
    "orderType": "Buy",
    "lots": 0.02,
    "openPrice": 1.26110,
    "profit": 0.0
  }
}
```

Subscribe to new positions:

```json theme={null}
{
  "type": "subscribe",
  "channel": "new_position",
  "account_id": "70f60784-20f1-45ba-9a04-8e01c0b810c3"
}
```

## Alerts

Register price, spread, or P\&L alerts to be notified when conditions are met. Alerts are one-shot: they fire once and are automatically removed.

### Price Alert

Register a price alert to trigger when a symbol's price crosses a threshold:

```json theme={null}
{
  "type": "price_alert",
  "account_id": "70f60784-...",
  "symbol": "EURUSD",
  "direction": "above",
  "price": 1.09000
}
```

**Server confirms registration:**

```json theme={null}
{
  "type": "alert_registered",
  "alert_id": "a1b2c3d4-...",
  "alert_type": "price_alert"
}
```

**When triggered:**

```json theme={null}
{
  "type": "price_alert_triggered",
  "account_id": "70f60784-...",
  "alert_id": "a1b2c3d4-...",
  "current_ask": 1.09010,
  "current_bid": 1.09008,
  "direction": "above",
  "symbol": "EURUSD",
  "target_price": 1.09000
}
```

### Spread Alert

Register a spread alert to trigger when the spread exceeds a threshold in pips:

```json theme={null}
{
  "type": "spread_alert",
  "account_id": "70f60784-...",
  "symbol": "EURUSD",
  "max_pips": 2.0
}
```

**When triggered:**

```json theme={null}
{
  "type": "spread_alert_triggered",
  "account_id": "70f60784-...",
  "alert_id": "e5f6g7h8-...",
  "ask": 1.08560,
  "bid": 1.08530,
  "max_pips": 2.0,
  "spread_pips": 3.0,
  "symbol": "EURUSD"
}
```

### P\&L Alert

Register a P\&L alert to trigger when account profit crosses a threshold:

```json theme={null}
{
  "type": "pl_alert",
  "account_id": "70f60784-...",
  "condition": "profit_below",
  "value": -500.00
}
```

**When triggered:**

```json theme={null}
{
  "type": "pl_alert_triggered",
  "account_id": "70f60784-...",
  "alert_id": "i9j0k1l2-...",
  "condition": "profit_below",
  "current_value": -520.00,
  "threshold": -500.00
}
```

### Cancel Alert

Cancel a previously registered alert before it triggers:

```json theme={null}
{
  "type": "cancel_alert",
  "alert_id": "a1b2c3d4-..."
}
```

## Aggregate Feed

Use `subscribe_aggregate` to receive aggregate events for selected stream types across every account owned by the API key. This is separate from the normal per-account `subscribe` message and does not include an `account_id` field.

```json theme={null}
{
  "type": "subscribe_aggregate",
  "channels": ["orders", "profit"],
  "interval_ms": 1000
}
```

Aggregate events are delivered as:

```json theme={null}
{
  "type": "aggregate",
  "account_id": "70f60784-...",
  "channel": "profit",
  "data": {
    "equity": 10142.50,
    "profit": 142.50
  }
}
```

To stop aggregate delivery:

```json theme={null}
{
  "type": "unsubscribe_aggregate"
}
```

## Unsubscribe

```json theme={null}
{
  "type": "unsubscribe",
  "channel": "quotes",
  "account_id": "70f60784-...",
  "symbols": ["EURUSD"]
}
```

Unsubscribe from everything on an account:

```json theme={null}
{
  "type": "unsubscribe_all",
  "account_id": "70f60784-..."
}
```

## Available Channels

| Stream         | Description                    | `symbols` required | `interval_ms` default           |
| -------------- | ------------------------------ | ------------------ | ------------------------------- |
| `quotes`       | Real-time bid/ask ticks        | yes                | 500ms                           |
| `orders`       | Order open/modify/close events | no                 | --                              |
| `profit`       | Live equity and P\&L           | no                 | 1000ms                          |
| `heartbeat`    | Account health snapshot        | no                 | 5000ms                          |
| `new_position` | New position opened            | no                 | --                              |
| `ohlc`         | Live OHLC candle updates       | yes                | Uses `interval_ms` as timeframe |
| `order_book`   | Market depth / order book      | yes                | 500ms                           |
| `tick_value`   | Tick value updates             | yes                | 500ms                           |

Aggregate delivery uses `subscribe_aggregate` with a `channels` array instead of a normal `channel` value.

## Multi-Account Streaming

Subscribe to multiple accounts on the same WebSocket:

```javascript theme={null}
// Stream quotes from account A
ws.send(JSON.stringify({
  type: "subscribe",
  channel: "quotes",
  account_id: "account-a-uuid",
  symbols: ["EURUSD"]
}));

// Stream profit from account B
ws.send(JSON.stringify({
  type: "subscribe",
  channel: "profit",
  account_id: "account-b-uuid",
  interval_ms: 1000
}));

// Stream order fills from both
ws.send(JSON.stringify({
  type: "subscribe",
  channel: "orders",
  account_id: "account-a-uuid"
}));

ws.send(JSON.stringify({
  type: "subscribe",
  channel: "orders",
  account_id: "account-b-uuid"
}));
```

Every message includes `account_id` so you can route it to the correct handler.

## Error Events

```json theme={null}
{
  "type": "error",
  "code": "SESSION_ERROR",
  "message": "No active session for this account"
}
```

Error codes: `SESSION_ERROR`, `INVALID_MESSAGE`

## Keepalive

```json theme={null}
{ "type": "ping" }
```

```json theme={null}
{ "type": "pong" }
```

## Full JavaScript Example

```javascript theme={null}
const ws = new WebSocket("wss://api.easierprop.com/ws?apiKey=sk_your_key");

ws.onopen = () => {
  console.log("Connected to Easier Prop WebSocket");

  // Stream live quotes
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "quotes",
    account_id: "70f60784-20f1-45ba-9a04-8e01c0b810c3",
    symbols: ["EURUSD", "GBPUSD", "USDJPY"],
    interval_ms: 500
  }));

  // Monitor order fills
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "orders",
    account_id: "70f60784-20f1-45ba-9a04-8e01c0b810c3"
  }));

  // Track live P&L
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "profit",
    account_id: "70f60784-20f1-45ba-9a04-8e01c0b810c3",
    interval_ms: 1000
  }));

  // Heartbeat for account health
  ws.send(JSON.stringify({
    type: "subscribe",
    channel: "heartbeat",
    account_id: "70f60784-20f1-45ba-9a04-8e01c0b810c3"
  }));

  // Price alert
  ws.send(JSON.stringify({
    type: "price_alert",
    account_id: "70f60784-20f1-45ba-9a04-8e01c0b810c3",
    symbol: "EURUSD",
    direction: "above",
    price: 1.09000
  }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);

  switch (msg.type) {
    case "subscribed":
      console.log(`Subscribed to ${msg.channel}`);
      break;

    case "quote":
      console.log(`${msg.data.symbol}: ${msg.data.bid} / ${msg.data.ask}`);
      break;

    case "order_update":
      console.log(`Order ${msg.data.ticket}: ${msg.data.orderType} ${msg.data.symbol} P&L=${msg.data.profit}`);
      break;

    case "profit":
      console.log(`Equity: ${msg.data.equity} | Profit: ${msg.data.profit}`);
      break;

    case "heartbeat":
      console.log(`Heartbeat: balance=${msg.balance} equity=${msg.equity} positions=${msg.position_count}`);
      break;

    case "new_position":
      console.log(`New position: ${msg.data.ticket} ${msg.data.orderType} ${msg.data.symbol}`);
      break;

    case "alert_registered":
      console.log(`Alert registered: ${msg.alert_id} (${msg.alert_type})`);
      break;

    case "price_alert_triggered":
      console.log(`Price alert! ${msg.symbol} crossed ${msg.target_price} (${msg.direction})`);
      break;

    case "spread_alert_triggered":
      console.log(`Spread alert! ${msg.symbol} spread=${msg.spread_pips} pips`);
      break;

    case "pl_alert_triggered":
      console.log(`P&L alert! ${msg.condition} ${msg.threshold}, current=${msg.current_value}`);
      break;

    case "error":
      console.error(`Error: ${msg.message}`);
      break;
  }
};

ws.onclose = () => console.log("Disconnected");
```

## Full Python Example

```python theme={null}
import asyncio
import json
import websockets

API_KEY = "sk_your_key"
ACCOUNT_ID = "70f60784-20f1-45ba-9a04-8e01c0b810c3"

async def main():
    uri = f"wss://api.easierprop.com/ws?apiKey={API_KEY}"

    async with websockets.connect(uri) as ws:
        # Subscribe to quotes
        await ws.send(json.dumps({
            "type": "subscribe",
            "channel": "quotes",
            "account_id": ACCOUNT_ID,
            "symbols": ["EURUSD"],
            "interval_ms": 500
        }))

        # Subscribe to P&L
        await ws.send(json.dumps({
            "type": "subscribe",
            "channel": "profit",
            "account_id": ACCOUNT_ID,
            "interval_ms": 1000
        }))

        # Subscribe to heartbeat
        await ws.send(json.dumps({
            "type": "subscribe",
            "channel": "heartbeat",
            "account_id": ACCOUNT_ID
        }))

        # Register a price alert
        await ws.send(json.dumps({
            "type": "price_alert",
            "account_id": ACCOUNT_ID,
            "symbol": "EURUSD",
            "direction": "above",
            "price": 1.09000
        }))

        async for message in ws:
            msg = json.loads(message)

            if msg["type"] == "quote":
                d = msg["data"]
                print(f"{d['symbol']}: {d['bid']} / {d['ask']}")
            elif msg["type"] == "profit":
                d = msg["data"]
                print(f"Equity: {d['equity']} | P&L: {d['profit']}")
            elif msg["type"] == "heartbeat":
                print(f"Heartbeat: balance={msg['balance']} positions={msg['position_count']}")
            elif msg["type"] == "price_alert_triggered":
                print(f"Price alert! {msg['symbol']} crossed {msg['target_price']}")
            elif msg["type"] == "error":
                print(f"Error: {msg['message']}")

asyncio.run(main())
```

## Session Behavior

* Sessions auto-connect when you subscribe. No need to call `/connect` first.
* If the MT5 session drops, the gateway reconnects automatically and resumes streaming.
* On WebSocket disconnect, all subscriptions for that client are cleaned up.
* Upstream quote subscriptions are ref-counted. Multiple clients on the same symbol share one upstream feed.
