Update README.md

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

527
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:
- Best limit order book (top 3 levels)
- Aggregate trade data (OHLC, volume, trade count)
- Allowed price range (min/max)
- Contract information (open interest & changes)
```text
wss://YOUR_DOMAIN/live/data/websocket/contract/name
```
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:
https://core.hedgetech.ir/auth/user/token/issue
## Python Example
text
```python
import asyncio
import websockets
import json
**Headers:**
Content-Type: application/x-www-form-urlencoded
async def main():
text
uri = (
"ws://127.0.0.1:8000/live/data/websocket/contract/name"
"?contract_names=ContractA"
"&contract_names=ContractB"
)
**Body:**
username=your_username&password=your_password
async with websockets.connect(uri) as websocket:
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
{
"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.
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
```json
{
"channel": "IME Stream",
"contractName": "IMEFutures_Sample",
"timestamp": "2025-11-14T12:00:00.000000",
"data": { ... }
"ContractId": "12345",
"timestamp": "2026-05-23T15:10: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
# Data Payload Structure
The `data` field contains the complete live contract market data.
---
## Full Payload Example
```json
{
"BestLimit": {
"1": {
@ -204,136 +214,159 @@ json
"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
```
---
# Payload Fields Description
# BestLimit
Represents the top buy and sell order book levels.
## Levels
| Level | Description |
|---|---|
| 1 | Best market level |
| 2 | Second order book level |
| 3 | Third order book level |
## Fields
| Field | Description |
|---|---|
| buy_quantity | Buy order quantity |
| buy_price | Buy order price |
| sell_quantity | Sell order quantity |
| sell_price | Sell order price |
---
# Aggregate
Aggregated market statistics for the contract.
| Field | Description |
|---|---|
| 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 websockets
import json
import websockets
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"
URI = (
"ws://127.0.0.1:8000/live/data/websocket/contract/id"
"?contract_id=12345"
)
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>"}}
async def connect():
c, _, err := websocket.DefaultDialer.Dial(url, header)
if err != nil {
log.Fatal(err)
}
defer c.Close()
while True:
for {
_, message, _ := c.ReadMessage()
var msg map[string]interface{}
json.Unmarshal(message, &msg)
try:
identifier := msg["contractName"]
if identifier == nil {
identifier = msg["ContractId"]
}
async with websockets.connect(
URI,
ping_interval=20,
ping_timeout=20
) as websocket:
data := msg["data"].(map[string]interface{})
agg := data["Aggregate"].(map[string]interface{})
print("Connected")
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.
while True:
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
✅ Use correct endpoint (/contract/name vs /contract/id)
print("Disconnected:", e)
✅ Provide Authorization header with valid token
await asyncio.sleep(5)
✅ 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")
asyncio.run(connect())
```