Update README.md

This commit is contained in:
Mohammad Taha Ranjbar Shirazi 2026-05-23 16:42:16 +03:30
parent 72fdba121c
commit fde0beb3eb

519
README.md
View File

@ -1,166 +1,176 @@
# WebSocket API Documentation | IME Live Market Data # IME Live Data WebSocket Documentation
## Real-Time Market Streams (IME) ## Introduction
### WebSocket • High-Frequency • Low-Latency This WebSocket service provides real-time market data for Iran Mercantile Exchange (IME) contracts.
The service streams live contract updates from Redis Pub/Sub channels and delivers them to connected WebSocket clients.
Two WebSocket endpoints are available:
1. WebSocket By Contract Name
2. WebSocket By Contract ID
Both endpoints provide the exact same payload structure.
The only difference is how contracts are subscribed.
--- ---
## 1. Overview # WebSocket Endpoints
This documentation describes the **HT Data Engine WebSocket API** for subscribing to real-time market data for **IME (Iran Mercantile Exchange)** contracts. # 1. WebSocket By Contract Name
The API provides **live market overview streams** including: ```text
- Best limit order book (top 3 levels) wss://YOUR_DOMAIN/live/data/websocket/contract/name
- 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. ## Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| contract_names | list[string] | Yes | List of contract names |
--- ---
## 2. Authentication ## Example Connection
To access protected WebSocket endpoints, a valid **JWT token** is required. ```text
wss://YOUR_DOMAIN/live/data/websocket/contract/name?contract_names=ContractA&contract_names=ContractB
```
### How to get the token ---
Send a `POST` request to: ## Python Example
https://core.hedgetech.ir/auth/user/token/issue
text ```python
import asyncio
import websockets
import json
**Headers:** async def main():
Content-Type: application/x-www-form-urlencoded
text uri = (
"ws://127.0.0.1:8000/live/data/websocket/contract/name"
"?contract_names=ContractA"
"&contract_names=ContractB"
)
**Body:** async with websockets.connect(uri) as websocket:
username=your_username&password=your_password
text while True:
### Response Example message = await websocket.recv()
data = json.loads(message)
print(json.dumps(data, indent=4))
asyncio.run(main())
```
---
# 2. WebSocket By Contract ID
```text
wss://YOUR_DOMAIN/live/data/websocket/contract/id
```
## Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| contract_id | list[string] | Yes | List of contract IDs |
---
## Example Connection
```text
wss://YOUR_DOMAIN/live/data/websocket/contract/id?contract_id=12345&contract_id=67890
```
---
## Python Example
```python
import asyncio
import websockets
import json
async def main():
uri = (
"ws://127.0.0.1:8000/live/data/websocket/contract/id"
"?contract_id=12345"
"&contract_id=67890"
)
async with websockets.connect(uri) as websocket:
while True:
message = await websocket.recv()
data = json.loads(message)
print(json.dumps(data, indent=4))
asyncio.run(main())
```
---
# Authentication
All WebSocket endpoints require authentication.
If authentication fails, the connection will be closed with:
```text
1008 POLICY VIOLATION
```
---
# Message Structure
## Response Structure (Contract Name)
```json ```json
{ {
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." "channel": "IME Stream",
"contractName": "ContractA",
"timestamp": "2026-05-23T15:10:00.000000",
"data": {}
} }
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. ## Response Structure (Contract ID)
Ensure your account is registered and approved by an admin. ```json
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", "channel": "IME Stream",
"contractName": "IMEFutures_Sample", "ContractId": "12345",
"timestamp": "2025-11-14T12:00:00.000000", "timestamp": "2026-05-23T15:10:00.000000",
"data": { ... } "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: # Data Payload Structure
Consumers might assume that subscribing to the contract-name endpoint returns a different schema — it does not. The `data` field contains the complete live contract market data.
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). ## Full Payload Example
4. Connection Flow ```json
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": { "BestLimit": {
"1": { "1": {
@ -204,136 +214,159 @@ json
"open_interest_changes": 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 # Payload Fields Description
sell_quantity int Total sell quantity at this level
sell_price int Sell price at this level # BestLimit
Aggregate (Trading Statistics)
Field Type Description Represents the top buy and sell order book levels.
date str Trading date (YYYY-MM-DD)
time str Time of last trade update (HH:MM:SS) ## Levels
trade_count int Number of trades
total_volume int Total volume traded | Level | Description |
total_value int Total value of trades |---|---|
closing_price float Closing price of the contract | 1 | Best market level |
last_price float Last traded price | 2 | Second order book level |
low_price float Lowest traded price | 3 | Third order book level |
high_price float Highest traded price
open_price float Opening price ## Fields
previous_close float Previous day's closing price
AllowedPriceRange (Price Limits) | Field | Description |
Field Type Description |---|---|
minAllowedPrice float Minimum allowed price for the contract | buy_quantity | Buy order quantity |
maxAllowedPrice float Maximum allowed price for the contract | buy_price | Buy order price |
ContractInfo (Position Information) | sell_quantity | Sell order quantity |
Field Type Description | sell_price | Sell order price |
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 # Aggregate
1008 Policy violation (invalid JWT, invalid contract names/IDs)
Connection closed Occurs if Redis stream fails or server error Aggregated market statistics for the contract.
8. Examples
8.1 Python (WebSocket Client) | Field | Description |
python |---|---|
| date | Trading date |
| time | Last update time |
| trade_count | Number of trades |
| total_volume | Total traded volume |
| total_value | Total traded value |
| closing_price | Closing price |
| last_price | Last traded price |
| low_price | Lowest traded price |
| high_price | Highest traded price |
| open_price | Opening price |
| previous_close | Previous closing price |
---
# AllowedPriceRange
Allowed trading price range.
| Field | Description |
|---|---|
| minAllowedPrice | Minimum allowed price |
| maxAllowedPrice | Maximum allowed price |
---
# ContractInfo
Additional contract information.
| Field | Description |
|---|---|
| open_interest | Open interest |
| open_interest_changes | Open interest changes |
---
# Error Handling
## Invalid Authentication
```text
WebSocket closed with code 1008
```
---
## Invalid Contract
If an invalid contract name or contract ID is provided, the connection will be closed.
```text
WebSocket closed with code 1008
```
---
# Notes
- The WebSocket streams real-time market data updates.
- Each contract is subscribed through a dedicated Redis Pub/Sub stream.
- Updates are only sent when market data changes.
- The WebSocket connection remains active until disconnected by the client.
- Multiple contracts can be subscribed simultaneously.
- Timestamps are provided in ISO 8601 format.
---
# Recommended Client Strategy
For production usage, it is recommended to:
- Implement automatic reconnect logic
- Enable heartbeat / ping interval
- Process messages asynchronously
- Use internal message queues
- Configure WebSocket timeout handling
---
# Recommended Python Reconnect Example
```python
import asyncio import asyncio
import websockets
import json import json
import websockets
async def subscribe_ime(url: str, token: str): URI = (
headers = {"Authorization": token} "ws://127.0.0.1:8000/live/data/websocket/contract/id"
async with websockets.connect(url, extra_headers=headers) as ws: "?contract_id=12345"
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() { async def connect():
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) while True:
if err != nil {
log.Fatal(err)
}
defer c.Close()
for { try:
_, message, _ := c.ReadMessage()
var msg map[string]interface{}
json.Unmarshal(message, &msg)
identifier := msg["contractName"] async with websockets.connect(
if identifier == nil { URI,
identifier = msg["ContractId"] ping_interval=20,
} ping_timeout=20
) as websocket:
data := msg["data"].(map[string]interface{}) print("Connected")
agg := data["Aggregate"].(map[string]interface{})
fmt.Printf("%s | %v | Last Price: %v\n", while True:
msg["timestamp"], identifier, agg["last_price"])
}
}
9. Best Practices
Reconnect with exponential backoff in case of disconnects.
Validate your JWT before subscribing. message = await websocket.recv()
Subscribe only to the contracts you need to reduce bandwidth. data = json.loads(message)
Handle both contractName and ContractId in your message parsing. print(data)
The data payload is the same for both endpoints; reuse your parsing logic. except Exception as e:
Appendix: Quick Developer Checklist print("Disconnected:", e)
✅ Use correct endpoint (/contract/name vs /contract/id)
✅ Provide Authorization header with valid token await asyncio.sleep(5)
✅ Include contract_names or contract_id as repeated query params asyncio.run(connect())
```
✅ 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")