594 lines
11 KiB
Markdown
594 lines
11 KiB
Markdown
# HT Data Engine | IME | WebSocket
|
|
## Real-Time Mercantile Exchange Streams (IME)
|
|
### WebSocket • Real-Time • Low-Latency
|
|
|
|
---
|
|
|
|
# 1. Overview
|
|
|
|
This documentation describes the **HT Data Engine IME WebSocket API** for subscribing to real-time market data streams from the **Iran Mercantile Exchange (IME)**.
|
|
|
|
The service provides low-latency real-time updates for IME contracts including:
|
|
|
|
- Best bid/ask limits
|
|
- Aggregate trading statistics
|
|
- Allowed price ranges
|
|
- Contract open interest information
|
|
|
|
The API supports two independent subscription endpoints:
|
|
|
|
- Subscription using **Contract Names**
|
|
- Subscription using **Contract IDs**
|
|
|
|
All WebSocket connections require valid authentication and support subscribing to multiple contracts in a single connection.
|
|
|
|
---
|
|
|
|
# 2. Authentication
|
|
|
|
All IME WebSocket endpoints require authentication.
|
|
|
|
Clients must provide a valid JWT token during the WebSocket handshake.
|
|
|
|
## Token Endpoint
|
|
|
|
```text
|
|
https://core.hedgetech.ir/auth/user/token/issue
|
|
```
|
|
|
|
## Request
|
|
|
|
### Headers
|
|
|
|
```text
|
|
Content-Type: application/x-www-form-urlencoded
|
|
```
|
|
|
|
### Body
|
|
|
|
```text
|
|
username=your_username&password=your_password
|
|
```
|
|
|
|
---
|
|
|
|
## Response Example
|
|
|
|
```json
|
|
{
|
|
"Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## WebSocket Authorization Header
|
|
|
|
```text
|
|
Authorization: <your_token>
|
|
```
|
|
|
|
---
|
|
|
|
## Important Notes
|
|
|
|
- Unauthorized connections are rejected with WebSocket close code `1008`
|
|
- Tokens may be invalidated if account policies or security rules change
|
|
- Ensure your account has access to IME live market data services
|
|
|
|
---
|
|
|
|
# 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 |
|
|
|
|
---
|
|
|
|
# 4. Important Clarification: Contract Name vs Contract ID Endpoints
|
|
|
|
The IME data engine exposes two separate WebSocket endpoints.
|
|
|
|
## Contract Name Endpoint
|
|
|
|
```text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name
|
|
```
|
|
|
|
Messages contain:
|
|
|
|
```json
|
|
"contractName": "<CONTRACT_NAME>"
|
|
```
|
|
|
|
---
|
|
|
|
## Contract ID Endpoint
|
|
|
|
```text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id
|
|
```
|
|
|
|
Messages contain:
|
|
|
|
```json
|
|
"contractId": "<CONTRACT_ID>"
|
|
```
|
|
|
|
---
|
|
|
|
## Important
|
|
|
|
Both endpoints return:
|
|
|
|
- The exact same market data
|
|
- The same payload structure
|
|
- The same channel schema
|
|
|
|
The only difference is the identifier field.
|
|
|
|
| Endpoint | Identifier Field |
|
|
|---|---|
|
|
| `/contract/name` | `contractName` |
|
|
| `/contract/id` | `contractId` |
|
|
|
|
---
|
|
|
|
## Why This Matters
|
|
|
|
Client applications must correctly detect which identifier field exists in incoming messages.
|
|
|
|
Recommended approach:
|
|
|
|
```python
|
|
identifier = message.get("contractId") or message.get("contractName")
|
|
```
|
|
|
|
This prevents parsing issues when switching between endpoints.
|
|
|
|
---
|
|
|
|
# 5. Connection Flow
|
|
|
|
1. Establish WebSocket connection
|
|
|
|
2. Provide the Authorization header
|
|
|
|
3. Pass query parameters in the URL
|
|
|
|
4. Server validates:
|
|
- JWT token
|
|
- Contract identifiers
|
|
- Subscription permissions
|
|
|
|
5. If validation succeeds:
|
|
- WebSocket connection is accepted
|
|
- Real-time streams begin immediately
|
|
|
|
6. If validation fails:
|
|
- Connection closes with code `1008`
|
|
|
|
---
|
|
|
|
# 6. Query Parameters
|
|
|
|
## Contract Name Endpoint
|
|
|
|
| Parameter | Type | Description |
|
|
|---|---|---|
|
|
| `contract_name` | repeated string | IME contract names |
|
|
|
|
### Example
|
|
|
|
```text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_name=GOLD-APR&contract_name=CEMENT-MAY
|
|
```
|
|
|
|
---
|
|
|
|
## Contract ID Endpoint
|
|
|
|
| Parameter | Type | Description |
|
|
|---|---|---|
|
|
| `contract_id` | repeated string | IME contract IDs |
|
|
|
|
### Example
|
|
|
|
```text
|
|
wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id?contract_id=1001&contract_id=1002
|
|
```
|
|
|
|
---
|
|
|
|
# 7. Message Structure
|
|
|
|
All WebSocket messages follow the same envelope structure.
|
|
|
|
---
|
|
|
|
## Contract Name Endpoint Example
|
|
|
|
```json
|
|
{
|
|
"channel": "IME Stream",
|
|
"contractName": "GOLD-APR",
|
|
"timestamp": "2026-05-29T12:00:00.000000Z",
|
|
"data": {
|
|
"...": "..."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Contract ID Endpoint Example
|
|
|
|
```json
|
|
{
|
|
"channel": "IME Stream",
|
|
"contractId": "1001",
|
|
"timestamp": "2026-05-29T12:00:00.000000Z",
|
|
"data": {
|
|
"...": "..."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# 8. Data Payload Schema
|
|
|
|
The `data` field contains multiple market data sections grouped into a single payload.
|
|
|
|
---
|
|
|
|
# 8.1 BestLimit
|
|
|
|
Top bid/ask levels for the contract.
|
|
|
|
## Example
|
|
|
|
```json
|
|
{
|
|
"BestLimit": {
|
|
"1": {
|
|
"buy_quantity": 150,
|
|
"buy_price": 245000,
|
|
"sell_quantity": 100,
|
|
"sell_price": 246000
|
|
},
|
|
"2": {
|
|
"buy_quantity": 120,
|
|
"buy_price": 244500,
|
|
"sell_quantity": 90,
|
|
"sell_price": 246500
|
|
},
|
|
"3": {
|
|
"buy_quantity": 80,
|
|
"buy_price": 244000,
|
|
"sell_quantity": 70,
|
|
"sell_price": 247000
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
| Field | Description |
|
|
|---|---|
|
|
| `buy_quantity` | Total bid quantity |
|
|
| `buy_price` | Bid price |
|
|
| `sell_quantity` | Total ask quantity |
|
|
| `sell_price` | Ask price |
|
|
|
|
---
|
|
|
|
# 8.2 Aggregate
|
|
|
|
Aggregate trading statistics for the contract.
|
|
|
|
## Example
|
|
|
|
```json
|
|
{
|
|
"Aggregate": {
|
|
"date": "2026-05-29",
|
|
"time": "12:00:00",
|
|
"trade_count": 120,
|
|
"total_volume": 4500,
|
|
"total_value": 1102500000,
|
|
"closing_price": 245500,
|
|
"last_price": 245700,
|
|
"low_price": 244000,
|
|
"high_price": 247000,
|
|
"open_price": 244500,
|
|
"previous_close": 243000
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
| Field | Description |
|
|
|---|---|
|
|
| `trade_count` | Number of executed trades |
|
|
| `total_volume` | Total traded volume |
|
|
| `total_value` | Total traded value |
|
|
| `closing_price` | Current settlement/closing price |
|
|
| `last_price` | Last traded price |
|
|
| `low_price` | Session low |
|
|
| `high_price` | Session high |
|
|
| `open_price` | Session open |
|
|
| `previous_close` | Previous settlement/close price |
|
|
|
|
---
|
|
|
|
# 8.3 AllowedPriceRange
|
|
|
|
Allowed trading price boundaries for the contract.
|
|
|
|
## Example
|
|
|
|
```json
|
|
{
|
|
"AllowedPriceRange": {
|
|
"minAllowedPrice": 220000,
|
|
"maxAllowedPrice": 270000
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
| Field | Description |
|
|
|---|---|
|
|
| `minAllowedPrice` | Minimum allowed trading price |
|
|
| `maxAllowedPrice` | Maximum allowed trading price |
|
|
|
|
---
|
|
|
|
# 8.4 ContractInfo
|
|
|
|
Contract open interest statistics.
|
|
|
|
## Example
|
|
|
|
```json
|
|
{
|
|
"ContractInfo": {
|
|
"open_interest": 5400,
|
|
"open_interest_changes": 120
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Fields
|
|
|
|
| Field | Description |
|
|
|---|---|
|
|
| `open_interest` | Current open interest |
|
|
| `open_interest_changes` | Change in open interest |
|
|
|
|
---
|
|
|
|
# 9. Complete Example Payload
|
|
|
|
```json
|
|
{
|
|
"channel": "IME Stream",
|
|
"contractId": "1001",
|
|
"timestamp": "2026-05-29T12:00:00.000000Z",
|
|
"data": {
|
|
"BestLimit": {
|
|
"1": {
|
|
"buy_quantity": 150,
|
|
"buy_price": 245000,
|
|
"sell_quantity": 100,
|
|
"sell_price": 246000
|
|
},
|
|
"2": {
|
|
"buy_quantity": 120,
|
|
"buy_price": 244500,
|
|
"sell_quantity": 90,
|
|
"sell_price": 246500
|
|
},
|
|
"3": {
|
|
"buy_quantity": 80,
|
|
"buy_price": 244000,
|
|
"sell_quantity": 70,
|
|
"sell_price": 247000
|
|
}
|
|
},
|
|
"Aggregate": {
|
|
"date": "2026-05-29",
|
|
"time": "12:00:00",
|
|
"trade_count": 120,
|
|
"total_volume": 4500,
|
|
"total_value": 1102500000,
|
|
"closing_price": 245500,
|
|
"last_price": 245700,
|
|
"low_price": 244000,
|
|
"high_price": 247000,
|
|
"open_price": 244500,
|
|
"previous_close": 243000
|
|
},
|
|
"AllowedPriceRange": {
|
|
"minAllowedPrice": 220000,
|
|
"maxAllowedPrice": 270000
|
|
},
|
|
"ContractInfo": {
|
|
"open_interest": 5400,
|
|
"open_interest_changes": 120
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# 10. Error Handling
|
|
|
|
| Code | Description |
|
|
|---|---|
|
|
| `1008` | Invalid token, invalid contract, or unauthorized access |
|
|
| Connection Closed | Internal server error or Redis stream interruption |
|
|
|
|
---
|
|
|
|
# 11. Python Example
|
|
|
|
```python
|
|
import asyncio
|
|
import json
|
|
import websockets
|
|
|
|
async def subscribe(url: str, token: str):
|
|
|
|
headers = {
|
|
"Authorization": token
|
|
}
|
|
|
|
async with websockets.connect(
|
|
url,
|
|
additional_headers=headers
|
|
) as ws:
|
|
|
|
async for message in ws:
|
|
|
|
payload = json.loads(message)
|
|
|
|
identifier = (
|
|
payload.get("contractId")
|
|
or payload.get("contractName")
|
|
)
|
|
|
|
print(
|
|
payload["timestamp"],
|
|
identifier,
|
|
payload["channel"]
|
|
)
|
|
|
|
print(payload["data"])
|
|
|
|
url = (
|
|
"wss://core.hedgetech.ir/"
|
|
"data-engine/ime/live/data/websocket/contract/id"
|
|
"?contract_id=1001"
|
|
"&contract_id=1002"
|
|
)
|
|
|
|
token = "<your_token>"
|
|
|
|
asyncio.run(subscribe(url, token))
|
|
```
|
|
|
|
---
|
|
|
|
# 12. JavaScript Example
|
|
|
|
```javascript
|
|
const WebSocket = require('ws');
|
|
|
|
const url =
|
|
'wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id?contract_id=1001&contract_id=1002';
|
|
|
|
const token = '<your_token>';
|
|
|
|
const ws = new WebSocket(
|
|
url,
|
|
{
|
|
headers: {
|
|
Authorization: token
|
|
}
|
|
}
|
|
);
|
|
|
|
ws.on('open', () => {
|
|
console.log('Connected');
|
|
});
|
|
|
|
ws.on('message', (message) => {
|
|
|
|
const data = JSON.parse(message);
|
|
|
|
const identifier =
|
|
data.contractId ||
|
|
data.contractName;
|
|
|
|
console.log(
|
|
data.timestamp,
|
|
identifier,
|
|
data.channel
|
|
);
|
|
|
|
console.log(data.data);
|
|
});
|
|
|
|
ws.on('close', () => {
|
|
console.log('Disconnected');
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
# 13. Subscription Notes
|
|
|
|
- Multiple contracts can be subscribed in a single connection
|
|
- Query parameters must be repeated
|
|
- Streams are pushed continuously in real time
|
|
- Connections should be handled asynchronously
|
|
- Invalid contracts cause immediate connection rejection
|
|
|
|
---
|
|
|
|
# 14. Best Practices
|
|
|
|
- Reconnect using exponential backoff
|
|
- Subscribe only to required contracts
|
|
- Handle disconnects gracefully
|
|
- Normalize `contractId` and `contractName`
|
|
- Monitor WebSocket close code `1008`
|
|
- Use asynchronous processing pipelines for high-frequency streams
|
|
|
|
---
|
|
|
|
# 15. Developer Checklist
|
|
|
|
- Use the correct endpoint
|
|
- Provide valid Authorization header
|
|
- Pass repeated query parameters
|
|
- Handle both identifier field types
|
|
- Parse the nested `data` payload correctly
|
|
- Monitor connection lifecycle events
|
|
- Handle reconnect scenarios
|
|
|
|
---
|
|
|
|
# 16. Future Compatibility Notes
|
|
|
|
The IME streaming architecture is designed to remain schema-compatible across both endpoint types.
|
|
|
|
Future extensions may include:
|
|
|
|
- Additional market channels
|
|
- Incremental order-book streams
|
|
- Snapshot recovery
|
|
- Binary transport protocols
|
|
- Sequence numbers
|
|
- Compression support
|
|
- Dynamic subscribe/unsubscribe actions
|
|
|
|
Clients are encouraged to write flexible parsers to remain forward-compatible.
|
|
|