# Streaming prices

_Source: https://docs.neurotrading.io/en/api/price-stream_

Live prices come over a WebSocket, the same feed that moves the dashboard's charts. One connection can follow as many tokens as you like, on Solana and EVM networks together.

The URL is `wss://socket.neurotrading.io`.

The price stream needs no token. You can connect from a script or straight from a browser.

## Subscribing

Every message, in both directions, is a JSON object with a `type`. After connecting, send the token addresses you want to follow:

```json
{ "type": "subscribe", "contractAddresses": ["Bp8m...pump", "0x6e2c...a41f"] }
```

The server confirms with the same list:

```json
{ "type": "subscribed", "contractAddresses": ["Bp8m...pump", "0x6e2c...a41f"] }
```

From then on you receive a message every time one of those tokens trades. Stop following a token with:

```json
{ "type": "unsubscribe", "contractAddresses": ["Bp8m...pump"] }
```

Subscriptions belong to the connection. If it drops, reconnect and subscribe again.

> **Warning**
>
> **Send EVM addresses in lowercase.** Addresses are matched exactly, and the feed publishes EVM tokens in lowercase, so a checksummed address (`0x6E2C…A41F`) subscribes successfully but never receives a tick. Solana addresses are case-sensitive — send them exactly as they are.

## Solana ticks — `price_update`

```json
{
  "type": "price_update",
  "data": {
    "contractAddress": "Bp8m...pump",
    "name": "Example",
    "symbol": "EXM",
    "priceNative": "0.0000000412",
    "priceInUsd": "0.00000736",
    "marketCapInSol": "41.2",
    "marketCapInUsd": "7360.11",
    "supply": "1000000000",
    "dexId": "pumpfun",
    "quoteMint": "So11111111111111111111111111111111111111112",
    "lastUpdatedAt": "2026-10-22T08:15:03.412Z"
  }
}
```

| Field | Meaning |
| --- | --- |
| `priceNative` | Price of one token **in SOL**, whatever the pool is quoted in. A USDC pool's price is converted to SOL. |
| `priceInUsd` | Price of one token in USD. |
| `marketCapInSol`, `marketCapInUsd` | Market cap in SOL and USD. |
| `dexId` | Where the swap happened (Pump.fun, PumpSwap, Raydium, …). |
| `quoteMint` | The token the pool is actually quoted in (SOL, USDC, USD1, …). |
| `lastUpdatedAt` | When the swap happened. |

Numbers arrive as **strings** so no precision is lost — parse them with a decimal library if you do arithmetic on them.

Some `price_update` messages carry trading-activity figures instead of a fresh swap price. You can tell them apart because they include a `metricsUpdatedAt` field (a millisecond timestamp). If you only want prices, skip messages that have it.

## EVM ticks — `evm_price_update`

Tokens on EVM networks, such as Robinhood Chain, arrive with their own message type on the same connection:

```json
{
  "type": "evm_price_update",
  "data": {
    "chainId": 4663,
    "tokenAddress": "0x6e2c...a41f",
    "tokenSymbol": "EXM",
    "priceInQuote": "0.00000231",
    "quoteAsset": { "address": "0x...", "symbol": "ETH", "decimals": 18, "isNative": true },
    "source": "pons-curve",
    "venue": { "kind": "curve" },
    "graduationProgress": 0.42,
    "sellsClosed": false,
    "betweenVenues": false,
    "blockNumber": 1234567,
    "txHash": "0x...",
    "sourceEventId": "4663:0x...:7",
    "publishedAt": "2026-10-22T08:15:03.880Z"
  }
}
```

| Field | Meaning |
| --- | --- |
| `priceInQuote` | Price of one whole token in whole units of `quoteAsset`. **Not USD.** A token can be priced in ETH, in USDG, or in a tokenised stock — always read `quoteAsset.symbol`. |
| `graduationProgress` | For a token still on its launch curve, how close it is to moving to a pool (0 to 1). |
| `sellsClosed` | `true` while the curve is full and about to graduate: buys still fill, **sells fail**. Do not send a sell while this is `true`. |
| `betweenVenues` | `true` for the moment between the curve closing and the pool opening. The price has not crashed — the token is moving. |
| `sourceEventId` | Unique per event. Use it to ignore a tick you have already seen. |

## Keeping the connection alive

The server sends a WebSocket ping every 30 seconds and closes connections that do not answer. Standard WebSocket clients (browsers, the Node `ws` package, Python `websockets`) answer pings automatically.

You can also check the connection yourself: send `{"type": "ping"}` and the server answers `{"type": "pong"}`. A message the server cannot read gets `{"type": "error", "message": "..."}` back, and the connection stays open.

## Getting history

The stream only sends new trades. For candles — to seed an indicator, or to get a price for a token that has not traded since you subscribed — use the REST API (this one needs your session token):

```bash
curl -s "$NEURO_API/tokens//ohlc?type=1m&from=1761120000&to=1761206400" \
  -H "Authorization: Bearer $NEURO_TOKEN"
```

| Parameter | Notes |
| --- | --- |
| `type` | Candle size: `1s`, `30s`, `1m`, `5m`, `15m`, `30m`, `1h`, `4h`, `1d`. Default `1m`. |
| `from`, `to` | Unix time in **seconds**. Default: the last 24 hours. |
| `currency` | `usd` (default) or `native` — priced in the pool's own quote token. |
| `quote_mint` | With `native`: which pool's quote to chart, if the token trades in more than one. |

Each candle in `data.items` has `o`, `h`, `l`, `c` (open, high, low, close), `v` and `v_usd` (volume), `unix_time` (the candle's start, in seconds), and market-cap equivalents `o_mc` … `c_mc` where available.

Candle history is available for Solana tokens.

## Example: follow a token in Node.js

```js
// npm install ws

const TOKENS = ["Bp8m...pump"]; // EVM addresses: lowercase

function connect() {
  const ws = new WebSocket("wss://socket.neurotrading.io");

  ws.on("open", () => ws.send(JSON.stringify({ type: "subscribe", contractAddresses: TOKENS })));

  ws.on("message", (raw) => {
    const msg = JSON.parse(raw.toString());
    if (msg.type === "price_update" && msg.data.metricsUpdatedAt === undefined) {
      console.log(msg.data.symbol, "SOL", msg.data.priceNative, "USD", msg.data.priceInUsd);
    } else if (msg.type === "evm_price_update") {
      console.log(msg.data.tokenSymbol, msg.data.priceInQuote, msg.data.quoteAsset.symbol);
    } else if (msg.type === "error") {
      console.error("stream error:", msg.message);
    }
  });

  // Reconnect (and resubscribe) after a short pause whenever the connection drops.
  ws.on("close", () => setTimeout(connect, 2000));
  ws.on("error", () => ws.close());
}

connect();
```
