> For the complete documentation index, see [llms.txt](https://docs.hydromancer.xyz/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.hydromancer.xyz/readme/websocket/candles/candles.md).

# candle

Stream real-time OHLCV candle updates for a specific coin and interval.

Every live data message includes both routing identifiers with the same value:

```json
{"type": "candle", "channel": "candle"}
```

Other payload fields are omitted above. Existing clients may continue routing on either field.

{% hint style="info" %}

### Hydromancer offers 1s, 15s, and 30s candlesticks that are unavailable on Hyperliquid natively. The Hydromancer candlesticks endpoint also returns more than 5,000 candles, compared with the native Hyperliquid API.

{% endhint %}

Streams real-time candle updates as trades happen. Each message contains the latest state of the current candle for the subscribed coin and interval. When a candle closes and a new one opens, both events are sent.

### Subscribe

```json
{
    "method": "subscribe",
    "subscription": {
        "type": "candle",
        "coin": "BTC",
        "interval": "1m"
    }
}
```

| Field      | Type   | Required | Description                                                                                                                |
| ---------- | ------ | -------- | -------------------------------------------------------------------------------------------------------------------------- |
| `coin`     | string | Yes      | Coin name (e.g. `"BTC"`, `"ETH"`, `"hyna:ETH"`)                                                                            |
| `interval` | string | Yes      | Candle interval: `1s`, `15s`, `30s`, `1m`, `3m`, `5m`, `15m`, `30m`, `1h`, `2h`, `4h`, `8h`, `12h`, `1d`, `3d`, `1w`, `1M` |

### Unsubscribe

```json
{
    "method": "unsubscribe",
    "subscription": {
        "type": "candle",
        "coin": "BTC",
        "interval": "1m"
    }
}
```

### Update data format

Each message contains a full candle snapshot. The `x` field indicates whether the candle is finalized (`true`) or still accumulating trades (`false`).

```json
{
    "type": "candle",
    "channel": "candle",
    "seq": 42,
    "cursor": "1700000040000",
    "data": {
        "s": "BTC",
        "i": "1m",
        "t": 1700000040000,
        "T": 1700000099999,
        "o": "45000.00",
        "h": "45100.50",
        "l": "44980.00",
        "c": "45050.25",
        "v": "123.45",
        "q": "5560125.00",
        "n": 312,
        "x": false
    }
}
```

### Field definitions

**Envelope fields:**

| Field    | Type   | Description                                                            |
| -------- | ------ | ---------------------------------------------------------------------- |
| `type`   | string | Always `"candle"`                                                      |
| `seq`    | number | Per-subscription sequence number (starts at 1, increments per message) |
| `cursor` | string | Candle open time as millisecond timestamp string (for session replay)  |

**Candle fields (`data`):**

| Field | Type    | Description                                                     |
| ----- | ------- | --------------------------------------------------------------- |
| `s`   | string  | Coin name                                                       |
| `i`   | string  | Interval string                                                 |
| `t`   | number  | Candle open time (milliseconds, interval start)                 |
| `T`   | number  | Candle close time (milliseconds, interval end)                  |
| `o`   | string  | Open price                                                      |
| `h`   | string  | High price                                                      |
| `l`   | string  | Low price                                                       |
| `c`   | string  | Close (latest) price                                            |
| `v`   | string  | Volume in base asset                                            |
| `q`   | string  | Quote volume (notional value)                                   |
| `n`   | number  | Number of trades                                                |
| `x`   | boolean | `true` if the candle is closed/finalized, `false` if still open |

### Examples

{% tabs %}
{% tab title="JavaScript" %}

```javascript
const WebSocket = require('ws');

const ws = new WebSocket(`wss://api.hydromancer.xyz/ws?token=${process.env.HYDROMANCER_API_KEY}`);

ws.on('message', (data) => {
    const msg = JSON.parse(data);

    if (msg.type === 'connected') {
        ws.send(JSON.stringify({
            method: 'subscribe',
            subscription: {
                type: 'candle',
                coin: 'BTC',
                interval: '1m'
            }
        }));
    } else if (msg.type === 'ping') {
        ws.send(JSON.stringify({ type: 'pong' }));
    } else if (msg.type === 'candle') {
        const c = msg.data;
        const status = c.x ? 'CLOSED' : 'open';
        console.log(`[${status}] ${c.s} ${c.i}  O=${c.o} H=${c.h} L=${c.l} C=${c.c} V=${c.v} trades=${c.n}`);
    }
});
```

{% endtab %}

{% tab title="Python" %}

```python
import websocket
import json
import os

def on_message(ws, message):
    msg = json.loads(message)

    if msg['type'] == 'connected':
        ws.send(json.dumps({
            "method": "subscribe",
            "subscription": {
                "type": "candle",
                "coin": "BTC",
                "interval": "1m"
            }
        }))
    elif msg['type'] == 'ping':
        ws.send(json.dumps({'type': 'pong'}))
    elif msg['type'] == 'candle':
        c = msg['data']
        status = 'CLOSED' if c['x'] else 'open'
        print(f"[{status}] {c['s']} {c['i']}  O={c['o']} H={c['h']} L={c['l']} C={c['c']} V={c['v']} trades={c['n']}")

ws = websocket.WebSocketApp(
    f"wss://api.hydromancer.xyz/ws?token={os.environ.get('HYDROMANCER_API_KEY')}",
    on_message=on_message
)
ws.run_forever()
```

{% endtab %}
{% endtabs %}

### Common errors

| Error                | Cause                                                           |
| -------------------- | --------------------------------------------------------------- |
| `Invalid interval`   | Interval string not in the valid list                           |
| `Invalid coin`       | Coin contains invalid characters or exceeds max length          |
| `Connection timeout` | Not responding to ping messages — reply with `{"type": "pong"}` |
