Update README.md
This commit is contained in:
parent
72fdba121c
commit
fde0beb3eb
531
README.md
531
README.md
@ -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",
|
|
||||||
"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",
|
"channel": "IME Stream",
|
||||||
"contractName": "IMEFutures_Sample",
|
"ContractId": "12345",
|
||||||
"timestamp": "2025-11-14T12:00:00.000000",
|
"timestamp": "2026-05-23T15:10:00.000000",
|
||||||
"data": { ... }
|
"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": {
|
"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)
|
|
||||||
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.
|
while True:
|
||||||
|
|
||||||
Subscribe only to the contracts you need to reduce bandwidth.
|
try:
|
||||||
|
|
||||||
Handle both contractName and ContractId in your message parsing.
|
async with websockets.connect(
|
||||||
|
URI,
|
||||||
|
ping_interval=20,
|
||||||
|
ping_timeout=20
|
||||||
|
) as websocket:
|
||||||
|
|
||||||
The data payload is the same for both endpoints; reuse your parsing logic.
|
print("Connected")
|
||||||
|
|
||||||
Appendix: Quick Developer Checklist
|
while True:
|
||||||
✅ Use correct endpoint (/contract/name vs /contract/id)
|
|
||||||
|
|
||||||
✅ Provide Authorization header with valid token
|
message = await websocket.recv()
|
||||||
|
|
||||||
✅ Include contract_names or contract_id as repeated query params
|
data = json.loads(message)
|
||||||
|
|
||||||
✅ Handle both contractName and ContractId in message parsing
|
print(data)
|
||||||
|
|
||||||
✅ Monitor for WS close code 1008 for authorization errors
|
except Exception as e:
|
||||||
|
|
||||||
✅ BestLimit contains only top 3 levels (keys: "1", "2", "3")
|
print("Disconnected:", e)
|
||||||
|
|
||||||
|
await asyncio.sleep(5)
|
||||||
|
|
||||||
|
asyncio.run(connect())
|
||||||
|
```
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user