339 lines
10 KiB
Markdown
339 lines
10 KiB
Markdown
# WebSocket API Documentation | IME Live Market Data
|
|
|
|
## Real-Time Market Streams (IME)
|
|
|
|
### WebSocket • High-Frequency • Low-Latency
|
|
|
|
---
|
|
|
|
## 1. Overview
|
|
|
|
This documentation describes the **HT Data Engine WebSocket API** for subscribing to real-time market data for **IME (Iran Mercantile Exchange)** contracts.
|
|
|
|
The API provides **live market overview streams** including:
|
|
- Best limit order book (top 3 levels)
|
|
- Aggregate trade data (OHLC, volume, trade count)
|
|
- Allowed price range (min/max)
|
|
- Contract information (open interest & changes)
|
|
|
|
All WebSocket endpoints require **JWT token-based authentication** and support multiple contract names or IDs in a single connection.
|
|
|
|
---
|
|
|
|
## 2. Authentication
|
|
|
|
To access protected WebSocket endpoints, a valid **JWT token** is required.
|
|
|
|
### How to get the token
|
|
|
|
Send a `POST` request to:
|
|
https://core.hedgetech.ir/auth/user/token/issue
|
|
|
|
text
|
|
|
|
**Headers:**
|
|
Content-Type: application/x-www-form-urlencoded
|
|
|
|
text
|
|
|
|
**Body:**
|
|
username=your_username&password=your_password
|
|
|
|
text
|
|
|
|
### Response Example
|
|
|
|
```json
|
|
{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
}
|
|
Usage
|
|
Include the token in the WebSocket connection headers:
|
|
|
|
text
|
|
Authorization: <your_token>
|
|
Important Notes:
|
|
|
|
Tokens are bound to your IP and browser fingerprint. A change invalidates the token.
|
|
|
|
Ensure your account is registered and approved by an admin.
|
|
|
|
Unauthorized connections are closed with WS code 1008.
|
|
|
|
3. WebSocket Endpoints
|
|
Endpoint Description
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name Subscribe using contract names
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id Subscribe using contract IDs
|
|
Note: The output payload structure is identical for both endpoints, except the identifier field:
|
|
|
|
contractName for /contract/name
|
|
|
|
ContractId for /contract/id
|
|
|
|
🔶 Important Clarification: Contract Name vs Contract ID WebSocket Endpoints
|
|
The data engine provides two separate WebSocket endpoints:
|
|
|
|
Subscribe using Contract Names
|
|
|
|
text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name
|
|
Subscribe using Contract IDs
|
|
|
|
text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id
|
|
Both endpoints deliver identical payload structures, including the same channel and the same data schema. The only difference is the identifier field inside each message:
|
|
|
|
Endpoint Identifier Field in Payload
|
|
/contract/name "contractName": "<Contract Name>"
|
|
/contract/id "ContractId": "<Contract ID>"
|
|
Example for Contract Name endpoint:
|
|
|
|
json
|
|
{
|
|
"channel": "IME Stream",
|
|
"contractName": "IMEFutures_Sample",
|
|
"timestamp": "2025-11-14T12:00:00.000000",
|
|
"data": { ... }
|
|
}
|
|
Example for Contract ID endpoint:
|
|
|
|
json
|
|
{
|
|
"channel": "IME Stream",
|
|
"ContractId": "IME123456789",
|
|
"timestamp": "2025-11-14T12:00:00.000000",
|
|
"data": { ... }
|
|
}
|
|
No other structural difference exists between these two WebSocket services.
|
|
|
|
Why this clarification matters:
|
|
|
|
Consumers might assume that subscribing to the contract-name endpoint returns a different schema — it does not.
|
|
|
|
Client implementations should be prepared to handle either identifier field (contractName or ContractId) depending on which endpoint they connect to.
|
|
|
|
This avoids confusing bugs (for example: looking for ContractId in messages coming from the /contract/name endpoint).
|
|
|
|
4. Connection Flow
|
|
Establish WebSocket connection with the proper Authorization header.
|
|
|
|
Include query parameters in the URL. Each contract name/ID is repeated as a separate query parameter:
|
|
|
|
For Contract Name endpoint:
|
|
|
|
text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_names=<name1>&contract_names=<name2>
|
|
For Contract ID endpoint:
|
|
|
|
text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id?contract_id=<id1>&contract_id=<id2>
|
|
If verification passes, the WebSocket connection is accepted.
|
|
|
|
Real-time messages are streamed continuously until the connection is closed.
|
|
|
|
Important: Unauthorized connections are closed immediately with code 1008.
|
|
|
|
5. Query Parameters
|
|
Parameter Type Description
|
|
contract_names list of strings List of contract names to subscribe (for /contract/name)
|
|
contract_id list of strings List of contract IDs to subscribe (for /contract/id)
|
|
Example URL (Contract Name):
|
|
|
|
text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_names=IMEFutures_Sample&contract_names=IMEOption_Sample
|
|
Example URL (Contract ID):
|
|
|
|
text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id?contract_id=IME123456789&contract_id=IME987654321
|
|
6. Channel & Payload Schema
|
|
All messages are delivered in the following JSON structure:
|
|
|
|
json
|
|
{
|
|
"channel": "IME Stream",
|
|
"contractName": "IMEFutures_Sample",
|
|
"timestamp": "2025-11-14T12:00:00.000000",
|
|
"data": { ... }
|
|
}
|
|
NOTE: For the /contract/id endpoint, the contractName field above is replaced by ContractId. Everything else remains the same.
|
|
|
|
Complete Data Payload Structure
|
|
The data field contains a comprehensive market overview with four subsections:
|
|
|
|
json
|
|
{
|
|
"BestLimit": {
|
|
"1": {
|
|
"buy_quantity": 0,
|
|
"buy_price": 0,
|
|
"sell_quantity": 0,
|
|
"sell_price": 0
|
|
},
|
|
"2": {
|
|
"buy_quantity": 0,
|
|
"buy_price": 0,
|
|
"sell_quantity": 0,
|
|
"sell_price": 0
|
|
},
|
|
"3": {
|
|
"buy_quantity": 0,
|
|
"buy_price": 0,
|
|
"sell_quantity": 0,
|
|
"sell_price": 0
|
|
}
|
|
},
|
|
"Aggregate": {
|
|
"date": "",
|
|
"time": "",
|
|
"trade_count": 0,
|
|
"total_volume": 0,
|
|
"total_value": 0,
|
|
"closing_price": 0,
|
|
"last_price": 0,
|
|
"low_price": 0,
|
|
"high_price": 0,
|
|
"open_price": 0,
|
|
"previous_close": 0
|
|
},
|
|
"AllowedPriceRange": {
|
|
"minAllowedPrice": 1,
|
|
"maxAllowedPrice": 9999999999
|
|
},
|
|
"ContractInfo": {
|
|
"open_interest": 0,
|
|
"open_interest_changes": 0
|
|
}
|
|
}
|
|
6.1 Field Descriptions
|
|
BestLimit (Top 3 Order Book Levels)
|
|
Field Type Description
|
|
buy_quantity int Total buy quantity at this level
|
|
buy_price int Buy price at this level
|
|
sell_quantity int Total sell quantity at this level
|
|
sell_price int Sell price at this level
|
|
Aggregate (Trading Statistics)
|
|
Field Type Description
|
|
date str Trading date (YYYY-MM-DD)
|
|
time str Time of last trade update (HH:MM:SS)
|
|
trade_count int Number of trades
|
|
total_volume int Total volume traded
|
|
total_value int Total value of trades
|
|
closing_price float Closing price of the contract
|
|
last_price float Last traded price
|
|
low_price float Lowest traded price
|
|
high_price float Highest traded price
|
|
open_price float Opening price
|
|
previous_close float Previous day's closing price
|
|
AllowedPriceRange (Price Limits)
|
|
Field Type Description
|
|
minAllowedPrice float Minimum allowed price for the contract
|
|
maxAllowedPrice float Maximum allowed price for the contract
|
|
ContractInfo (Position Information)
|
|
Field Type Description
|
|
open_interest int Open interest for the contract
|
|
open_interest_changes int Change in open interest compared to previous period
|
|
7. Error Handling
|
|
Code Description
|
|
1008 Policy violation (invalid JWT, invalid contract names/IDs)
|
|
Connection closed Occurs if Redis stream fails or server error
|
|
8. Examples
|
|
8.1 Python (WebSocket Client)
|
|
python
|
|
import asyncio
|
|
import websockets
|
|
import json
|
|
|
|
async def subscribe_ime(url: str, token: str):
|
|
headers = {"Authorization": token}
|
|
async with websockets.connect(url, extra_headers=headers) as ws:
|
|
async for message in ws:
|
|
data = json.loads(message)
|
|
identifier = data.get("contractName") or data.get("ContractId")
|
|
payload = data.get("data")
|
|
|
|
print(f"{data['timestamp']} | {identifier}")
|
|
print(f" Last Price: {payload.get('Aggregate', {}).get('last_price')}")
|
|
print(f" Best Buy: {payload.get('BestLimit', {}).get('1', {}).get('buy_price')}")
|
|
|
|
# Usage
|
|
url = "wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_names=IMEFutures_Sample"
|
|
token = "<your_token>"
|
|
asyncio.run(subscribe_ime(url, token))
|
|
8.2 JavaScript (WebSocket Client)
|
|
javascript
|
|
const WebSocket = require('ws');
|
|
|
|
const url = 'wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_names=IMEFutures_Sample';
|
|
const token = '<your_token>';
|
|
|
|
const ws = new WebSocket(url, { headers: { Authorization: token } });
|
|
|
|
ws.on('open', () => console.log('Connected'));
|
|
ws.on('message', (data) => {
|
|
const msg = JSON.parse(data);
|
|
const identifier = msg.contractName || msg.ContractId;
|
|
console.log(`${msg.timestamp} | ${identifier}`);
|
|
console.log(' Last Price:', msg.data.Aggregate.last_price);
|
|
});
|
|
ws.on('close', () => console.log('Disconnected'));
|
|
8.3 Go (WebSocket Client)
|
|
go
|
|
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/gorilla/websocket"
|
|
"log"
|
|
)
|
|
|
|
func main() {
|
|
url := "wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_names=IMEFutures_Sample"
|
|
header := map[string][]string{"Authorization": {"<your_token>"}}
|
|
|
|
c, _, err := websocket.DefaultDialer.Dial(url, header)
|
|
if err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
defer c.Close()
|
|
|
|
for {
|
|
_, message, _ := c.ReadMessage()
|
|
var msg map[string]interface{}
|
|
json.Unmarshal(message, &msg)
|
|
|
|
identifier := msg["contractName"]
|
|
if identifier == nil {
|
|
identifier = msg["ContractId"]
|
|
}
|
|
|
|
data := msg["data"].(map[string]interface{})
|
|
agg := data["Aggregate"].(map[string]interface{})
|
|
|
|
fmt.Printf("%s | %v | Last Price: %v\n",
|
|
msg["timestamp"], identifier, agg["last_price"])
|
|
}
|
|
}
|
|
9. Best Practices
|
|
Reconnect with exponential backoff in case of disconnects.
|
|
|
|
Validate your JWT before subscribing.
|
|
|
|
Subscribe only to the contracts you need to reduce bandwidth.
|
|
|
|
Handle both contractName and ContractId in your message parsing.
|
|
|
|
The data payload is the same for both endpoints; reuse your parsing logic.
|
|
|
|
Appendix: Quick Developer Checklist
|
|
✅ Use correct endpoint (/contract/name vs /contract/id)
|
|
|
|
✅ Provide Authorization header with valid token
|
|
|
|
✅ Include contract_names or contract_id as repeated query params
|
|
|
|
✅ Handle both contractName and ContractId in message parsing
|
|
|
|
✅ Monitor for WS close code 1008 for authorization errors
|
|
|
|
✅ BestLimit contains only top 3 levels (keys: "1", "2", "3") |