From 72fdba121c4946c07094b92df7d9f8a9d435c24b Mon Sep 17 00:00:00 2001 From: mtrshirazi21 Date: Sat, 23 May 2026 16:26:12 +0330 Subject: [PATCH] Update README.md --- README.md | 339 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 339 insertions(+) diff --git a/README.md b/README.md index e69de29..4c90b60 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,339 @@ +# WebSocket API Documentation | IME Live Market Data + +## Real-Time Market Streams (IME) + +### WebSocket • High-Frequency • Low-Latency + +--- + +## 1. Overview + +This documentation describes the **HT Data Engine WebSocket API** for subscribing to real-time market data for **IME (Iran Mercantile Exchange)** contracts. + +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) + +All WebSocket endpoints require **JWT token-based authentication** and support multiple contract names or IDs in a single connection. + +--- + +## 2. Authentication + +To access protected WebSocket endpoints, a valid **JWT token** is required. + +### How to get the token + +Send a `POST` request to: +https://core.hedgetech.ir/auth/user/token/issue + +text + +**Headers:** +Content-Type: application/x-www-form-urlencoded + +text + +**Body:** +username=your_username&password=your_password + +text + +### Response Example + +```json +{ + "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +Usage +Include the token in the WebSocket connection headers: + +text +Authorization: +Important Notes: + +Tokens are bound to your IP and browser fingerprint. A change invalidates the token. + +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/id "ContractId": "" +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=&contract_names= +For Contract ID endpoint: + +text +wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/id?contract_id=&contract_id= +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 +{ + "BestLimit": { + "1": { + "buy_quantity": 0, + "buy_price": 0, + "sell_quantity": 0, + "sell_price": 0 + }, + "2": { + "buy_quantity": 0, + "buy_price": 0, + "sell_quantity": 0, + "sell_price": 0 + }, + "3": { + "buy_quantity": 0, + "buy_price": 0, + "sell_quantity": 0, + "sell_price": 0 + } + }, + "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 + } +} +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 +import asyncio +import websockets +import json + +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 = "" +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 = ''; + +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() { + url := "wss://core.hedgetech.ir/data-engine/ime/live/data/websocket/contract/name?contract_names=IMEFutures_Sample" + header := map[string][]string{"Authorization": {""}} + + 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. + +Subscribe only to the contracts you need to reduce bandwidth. + +Handle both contractName and ContractId in your message parsing. + +The data payload is the same for both endpoints; reuse your parsing logic. + +Appendix: Quick Developer Checklist +✅ Use correct endpoint (/contract/name vs /contract/id) + +✅ Provide Authorization header with valid token + +✅ 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") \ No newline at end of file