diff --git a/README.md b/README.md index 9dcf1f6..b86c34b 100644 --- a/README.md +++ b/README.md @@ -1,465 +1,593 @@ -# HedgeTech IME Live Data WebSocket API - -Professional real-time WebSocket streaming service for Iran Mercantile Exchange (IME) market data. +# HT Data Engine | IME | WebSocket +## Real-Time Mercantile Exchange Streams (IME) +### WebSocket • Real-Time • Low-Latency --- -# Overview +# 1. Overview -The HedgeTech IME Live Data WebSocket service provides ultra low-latency real-time streaming market data for IME contracts. +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 is designed for: +The service provides low-latency real-time updates for IME contracts including: -- Trading Platforms -- HFT Systems -- Algorithmic Trading -- Quantitative Infrastructure -- Market Monitoring Systems -- Real-Time Dashboards -- Risk Engines +- Best bid/ask limits +- Aggregate trading statistics +- Allowed price ranges +- Contract open interest information -All streams are powered by Redis Pub/Sub infrastructure and asynchronously distributed through FastAPI WebSocket endpoints. +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. --- -# Base URL +# 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/data-engine/ime +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 ``` --- -# Available WebSocket Endpoints +## Response Example -| Endpoint | Description | -|---|---| -| `/live/data/websocket/contract/name` | Subscribe using contract names | -| `/live/data/websocket/contract/id` | Subscribe using contract IDs | - ---- - -# Authentication - -All WebSocket endpoints require authentication. - -Authentication is performed during the WebSocket handshake phase using Authorization headers. - ---- - -## Authorization Header - -```text -Authorization: Bearer YOUR_ACCESS_TOKEN -``` - ---- - -## Example Authentication Flow - -```python -extra_headers = { - "Authorization": "Bearer YOUR_ACCESS_TOKEN" +```json +{ + "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." } ``` --- -# WebSocket: Subscribe By Contract Name +## WebSocket Authorization Header -## Endpoint +```text +Authorization: +``` + +--- + +## 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: -## Query Parameters - -| Parameter | Type | Required | Description | -|---|---|---|---| -| contract_name | list[string] | Yes | List of contract names | - ---- - -## Example Connection - -```text -wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_name=ContractA&contract_name=ContractB +```json +"contractName": "" ``` --- -## Python Example - -```python -import asyncio -import json -import websockets - -async def main(): - - uri = ( - "wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name" - "?contract_name=ContractA" - "&contract_name=ContractB" - ) - - headers = { - "Authorization": "Bearer YOUR_ACCESS_TOKEN" - } - - async with websockets.connect( - uri, - additional_headers=headers, - ping_interval=20, - ping_timeout=20, - ) as websocket: - - while True: - - message = await websocket.recv() - - data = json.loads(message) - - print(json.dumps(data, indent=4)) - -asyncio.run(main()) -``` - ---- - -# WebSocket: Subscribe By Contract ID - -## Endpoint +## Contract ID Endpoint ```text wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id ``` ---- +Messages contain: -## Query Parameters - -| Parameter | Type | Required | Description | -|---|---|---|---| -| contract_id | list[string] | Yes | List of contract IDs | - ---- - -## Example Connection - -```text -wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id?contract_id=12345&contract_id=67890 +```json +"contractId": "" ``` --- -## Python Example +## 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 -import asyncio -import json -import websockets +identifier = message.get("contractId") or message.get("contractName") +``` -async def main(): +This prevents parsing issues when switching between endpoints. - uri = ( - "wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id" - "?contract_id=12345" - "&contract_id=67890" - ) +--- - headers = { - "Authorization": "Bearer YOUR_ACCESS_TOKEN" +# 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": { + "...": "..." } - - async with websockets.connect( - uri, - additional_headers=headers, - ping_interval=20, - ping_timeout=20, - ) as websocket: - - while True: - - message = await websocket.recv() - - data = json.loads(message) - - print(json.dumps(data, indent=4)) - -asyncio.run(main()) -``` - ---- - -# WebSocket Response Structure - -Both endpoints stream identical payload structures. - -The only difference is: - -- `contractName` exists in Name endpoint -- `contractId` exists in ID endpoint - ---- - -## Response Example (Contract Name) - -```json -{ - "channel": "IME Stream", - "contractName": "ContractA", - "timestamp": "2026-05-23T15:10:00.000000", - "data": {} } ``` --- -## Response Example (Contract ID) +## Contract ID Endpoint Example ```json { "channel": "IME Stream", - "contractId": "12345", - "timestamp": "2026-05-23T15:10:00.000000", - "data": {} + "contractId": "1001", + "timestamp": "2026-05-29T12:00:00.000000Z", + "data": { + "...": "..." + } } ``` --- -# Data Payload Structure +# 8. Data Payload Schema -The `data` field contains the complete real-time contract market state. +The `data` field contains multiple market data sections grouped into a single payload. --- -# Full Payload Example +# 8.1 BestLimit + +Top bid/ask levels for the contract. + +## Example ```json { "BestLimit": { "1": { - "buy_quantity": 0, - "buy_price": 0, - "sell_quantity": 0, - "sell_price": 0 + "buy_quantity": 150, + "buy_price": 245000, + "sell_quantity": 100, + "sell_price": 246000 }, "2": { - "buy_quantity": 0, - "buy_price": 0, - "sell_quantity": 0, - "sell_price": 0 + "buy_quantity": 120, + "buy_price": 244500, + "sell_quantity": 90, + "sell_price": 246500 }, "3": { - "buy_quantity": 0, - "buy_price": 0, - "sell_quantity": 0, - "sell_price": 0 + "buy_quantity": 80, + "buy_price": 244000, + "sell_quantity": 70, + "sell_price": 247000 } - }, - - "Aggregate": { - "date": "", - "time": "", - "trade_count": 0, - "total_volume": 0, - "total_value": 0, - "closing_price": 0, - "last_price": 0, - "low_price": 0, - "high_price": 0, - "open_price": 0, - "previous_close": 0 - }, - - "AllowedPriceRange": { - "minAllowedPrice": 1, - "maxAllowedPrice": 9999999999 - }, - - "ContractInfo": { - "open_interest": 0, - "open_interest_changes": 0 } } ``` --- -# Payload Field Documentation - -# BestLimit - -Represents top order book levels. - ---- - -## Order Book Levels - -| Level | Description | -|---|---| -| 1 | Best market level | -| 2 | Second order book level | -| 3 | Third order book level | - ---- - -## BestLimit Fields +## Fields | Field | Description | |---|---| -| buy_quantity | Buy order quantity | -| buy_price | Buy order price | -| sell_quantity | Sell order quantity | -| sell_price | Sell order price | +| `buy_quantity` | Total bid quantity | +| `buy_price` | Bid price | +| `sell_quantity` | Total ask quantity | +| `sell_price` | Ask price | --- -# Aggregate +# 8.2 Aggregate -Aggregated trading statistics. +Aggregate trading statistics for the contract. -| Field | Description | -|---|---| -| date | Trading date | -| time | Last update time | -| trade_count | Total 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 | +## Example ---- - -# AllowedPriceRange - -Allowed trading range. - -| Field | Description | -|---|---| -| minAllowedPrice | Minimum allowed price | -| maxAllowedPrice | Maximum allowed price | - ---- - -# ContractInfo - -Contract-level metadata. - -| Field | Description | -|---|---| -| open_interest | Open interest | -| open_interest_changes | Open interest changes | - ---- - -# Error Handling - ---- - -## Authentication Failure - -If the access token is invalid or missing: - -```text -WebSocket closed with code 1008 POLICY VIOLATION +```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 + } +} ``` --- -## Invalid Contract +## Fields -If invalid contract names or IDs are provided: +| 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 | -```text -WebSocket closed with code 1008 POLICY VIOLATION +--- + +# 8.3 AllowedPriceRange + +Allowed trading price boundaries for the contract. + +## Example + +```json +{ + "AllowedPriceRange": { + "minAllowedPrice": 220000, + "maxAllowedPrice": 270000 + } +} ``` --- -# Architecture Notes +## Fields -- All streams are asynchronous -- Data is pushed only on updates -- Multiple contracts can be subscribed simultaneously -- Connections remain active until disconnected -- ISO 8601 timestamps are used -- Built for high-throughput market infrastructure +| Field | Description | +|---|---| +| `minAllowedPrice` | Minimum allowed trading price | +| `maxAllowedPrice` | Maximum allowed trading price | --- -# Production Recommendations +# 8.4 ContractInfo -For production-grade integrations: +Contract open interest statistics. -- Implement automatic reconnect logic -- Enable WebSocket heartbeat -- Configure proper timeout handling -- Monitor connection lifecycle -- Handle transient disconnects gracefully +## Example + +```json +{ + "ContractInfo": { + "open_interest": 5400, + "open_interest_changes": 120 + } +} +``` --- -# Production Reconnect Example +## 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 -URI = ( - "wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id" - "?contract_id=12345" +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" ) -HEADERS = { - "Authorization": "Bearer YOUR_ACCESS_TOKEN" -} +token = "" -async def connect(): - - while True: - - try: - - async with websockets.connect( - URI, - additional_headers=HEADERS, - ping_interval=20, - ping_timeout=20, - ) as websocket: - - print("Connected") - - while True: - - message = await websocket.recv() - - data = json.loads(message) - - print(data) - - except Exception as e: - - print("Disconnected:", e) - - await asyncio.sleep(5) - -asyncio.run(connect()) +asyncio.run(subscribe(url, token)) ``` --- +# 12. JavaScript Example -# HedgeTech +```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 = ''; + +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. -Developed and maintained by HedgeTech Infrastructure Team. \ No newline at end of file