Update README.md
This commit is contained in:
parent
72fdba121c
commit
fde0beb3eb
519
README.md
519
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:
|
||||
- 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
|
||||
```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": {}
|
||||
}
|
||||
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
|
||||
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
|
||||
```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())
|
||||
```
|
||||
|
||||
Loading…
Reference in New Issue
Block a user