diff --git a/README.md b/README.md index 61c3421..c1fd24c 100644 --- a/README.md +++ b/README.md @@ -1,16 +1,13 @@ # HT Data Engine | TSE & IFB | WebSocket - -## Real-Time Market Streams (TSE & IFB) - +## Real-Time Market Streams (TSE & IFB) ### WebSocket • High-Frequency • Low-Latency --- ## 1. Overview -This documentation describes the **HT Data Engine WebSocket API** for subscribing to real-time market data for **TSE & IFB** symbols. - -The API provides multiple channels delivering structured financial data such as order books, aggregate trades, OHLCV, and fund/contract information. +This documentation describes the **HT Data Engine WebSocket API** for subscribing to real-time market data for **TSE & IFB** symbols. +The API provides multiple channels delivering structured financial data such as order books, aggregate trades, OHLCV, and fund/contract information. All WebSocket endpoints require **JWT token-based authentication** and support multiple symbols or ISINs in a single connection. @@ -28,13 +25,13 @@ Send a `POST` request to: https://core.hedgetech.ir/auth/user/token/issue ``` -**Headers:** +Headers: ``` Content-Type: application/x-www-form-urlencoded ``` -**Body:** +Body: ``` username=your_username&password=your_password @@ -72,7 +69,6 @@ Authorization: | `wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name` | Subscribe using **symbol names** | **Note:** The output payload structure is identical for both endpoints, except the symbol identifier field: - - `symbolIsin` for `/symbol/isin` - `symbolName` for `/symbol/name` @@ -92,16 +88,15 @@ The data engine provides **two separate WebSocket endpoints**: wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name ``` -Both endpoints deliver **identical payload structures**, including the same channels and the same `data` schema. - +Both endpoints deliver **identical payload structures**, including the same channels and the same `data` schema. The **only difference** is the identifier field inside each message: | Endpoint | Identifier Field in Payload | -|----------|----------------------------| +|----------|------------------------------| | `/symbol/isin` | `"symbolIsin": ""` | | `/symbol/name` | `"symbolName": ""` | -**Example for ISIN endpoint:** +Example for ISIN endpoint: ```json { @@ -112,7 +107,7 @@ The **only difference** is the identifier field inside each message: } ``` -**Example for Symbol-Name endpoint:** +Example for Symbol-Name endpoint: ```json { @@ -136,23 +131,25 @@ No other structural difference exists between these two WebSocket services. ## 4. Connection Flow (Updated) 1. Establish WebSocket connection with the proper `Authorization` header. + 2. Include query parameters in the URL. **Each symbol/ISIN and channel is repeated as a separate query parameter**: - For ISIN endpoint: - ``` - wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin? - channels=&channels=& - symbol_isins=&symbol_isins= - ``` +``` +wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin? +channels=&channels=& +symbol_isins=&symbol_isins= +``` - For symbol names endpoint: - ``` - wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name? - channels=&channels=& - symbol_names=&symbol_names= - ``` +or for symbol names: + +``` +wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/name? +channels=&channels=& +symbol_names=&symbol_names= +``` 3. If verification passes, the WebSocket connection is accepted. + 4. Real-time messages are streamed continuously until the connection is closed. **Important:** Unauthorized connections are closed immediately with **code 1008**. @@ -230,6 +227,8 @@ All messages are delivered in the following **JSON structure**: } ``` +--- + ### 6.2 Example: `order-book` ```json @@ -238,24 +237,26 @@ All messages are delivered in the following **JSON structure**: "symbolIsin": "IRO1XYZ1234", "timestamp": "2025-11-14T12:00:00.000000", "data": { - "Buy": [ - { - "price": 0, - "quantity": 0, - "count": 0 - } - ], - "Sell": [ - { - "price": 0, - "quantity": 0, - "count": 0 - } - ] + "Buy": [ + { + "price": 0, + "quantity": 0, + "count": 0 + } + ], + "Sell": [ + { + "price": 0, + "quantity": 0, + "count": 0 + } + ] } } ``` +--- + ### 6.3 Example: `ohlcv-last-1m` ```json @@ -273,6 +274,8 @@ All messages are delivered in the following **JSON structure**: } ``` +--- + ### 6.4 Example: `aggregate` ```json @@ -281,21 +284,23 @@ All messages are delivered in the following **JSON structure**: "symbolIsin": "IRO1XYZ1234", "timestamp": "2025-11-14T12:00:00.000000", "data": { - "date": "string", - "time": "string", - "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 + "date": "string", + "time": "string", + "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 } } ``` +--- + ### 6.5 Example: `institutional-vs-individual` ```json @@ -304,18 +309,20 @@ All messages are delivered in the following **JSON structure**: "symbolIsin": "IRO1XYZ1234", "timestamp": "2025-11-14T12:00:00.000000", "data": { - "buy_count_individual": 0, - "buy_volume_individual": 0, - "buy_count_institution": 0, - "buy_volume_institution": 0, - "sell_count_individual": 0, - "sell_volume_individual": 0, - "sell_count_institution": 0, - "sell_volume_institution": 0 + "buy_count_individual": 0, + "buy_volume_individual": 0, + "buy_count_institution": 0, + "buy_volume_institution": 0, + "sell_count_individual": 0, + "sell_volume_individual": 0, + "sell_count_institution": 0, + "sell_volume_institution": 0 } } ``` +--- + ### 6.6 Example: `contract-info` ```json @@ -324,13 +331,15 @@ All messages are delivered in the following **JSON structure**: "symbolIsin": "IRO1XYZ1234", "timestamp": "2025-11-14T12:00:00.000000", "data": { - "open_interest": 0, - "initial_margin": 0, - "required_margin": 0 + "open_interest": 0, + "initial_margin": 0, + "required_margin": 0 } } ``` +--- + ### 6.7 Example: `fund-info` ```json @@ -339,17 +348,34 @@ All messages are delivered in the following **JSON structure**: "symbolIsin": "IRO1XYZ1234", "timestamp": "2025-11-14T12:00:00.000000", "data": { - "nav": 0, - "units": 0, - "marketCap": 0, - "as_of": "2025-11-14T22:10:42.802Z" + "nav": 0, + "units": 0, + "marketCap": 0, + "as_of": "2025-11-14T22:10:42.802Z" } } ``` --- -## 7. Error Handling +### 7. Other Channels + +Payload models follow the **Pydantic models** provided (`Aggregate`, `OrderBook`, `InstitutionalVsIndividual`, `ContractInfo`, `FundInfo`) and always adhere to the format: + +```json +{ + "channel": "", + "symbolIsin": "", + "timestamp": "", + "data": { ...channel-specific data... } +} +``` + +> NOTE: Replace `symbolIsin` with `symbolName` when using the `/symbol/name` endpoint. + +--- + +## 8. Error Handling | Code | Description | |------|-------------| @@ -358,9 +384,11 @@ All messages are delivered in the following **JSON structure**: --- -## 8. Code Examples +## 9. Examples -### 8.1 Python (WebSocket Client) +### 9.1 Python (WebSocket Client) + +This example adds a small helper to **support both endpoints** by checking which identifier field exists in incoming messages. ```python import asyncio @@ -385,7 +413,7 @@ token = "" asyncio.run(subscribe(url, token)) ``` -### 8.2 JavaScript (WebSocket Client) +### 9.2 JavaScript (WebSocket Client) ```javascript const WebSocket = require('ws'); @@ -409,7 +437,7 @@ ws.on('message', (data) => { ws.on('close', () => console.log('Disconnected')); ``` -### 8.3 Go (WebSocket Client) +### 9.3 Go (WebSocket Client) ```go package main @@ -452,7 +480,25 @@ func main() { } ``` -### 8.4 Rust (WebSocket Client) +### 9.4 Julia (WebSocket Client) + +```julia +using WebSockets, JSON + +url = "wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001" +token = "" + +WebSockets.open(url, extra_headers=["Authorization" => token]) do ws + while !eof(ws) + msg = String(readavailable(ws)) + data = JSON.parse(msg) + symbol = get(data, "symbolIsin", get(data, "symbolName", "")) + println(data["timestamp"], " ", symbol, " ", data["channel"]) + end +end +``` + +### 9.5 Rust (WebSocket Client) ```rust use tokio_tungstenite::connect_async; @@ -479,7 +525,9 @@ async fn main() { } ``` -### 8.5 Subscription Notes +--- + +### 9.6 Subscription Notes - Multiple symbols and channels can be subscribed in a **single WebSocket connection**. - The server streams messages continuously; handle them asynchronously. @@ -487,7 +535,7 @@ async fn main() { --- -## 9. Best Practices +## 10. Best Practices - Reconnect with **exponential backoff** in case of disconnects. - Validate your JWT **before subscribing**. @@ -498,11 +546,13 @@ async fn main() { --- -## Appendix: Quick Developer Checklist (for avoiding common mistakes) +## Appendix: Quick developer checklist (for avoiding common mistakes) - ✅ Use correct endpoint for your identifier type (`symbol/isin` vs `symbol/name`). - ✅ Provide `Authorization` header with a valid token on the WebSocket handshake. - ✅ Include `channels` and `symbol_isins` / `symbol_names` as repeated query params. - ✅ Handle both `symbolIsin` and `symbolName` in your message parsing to make the client resilient. - ✅ Treat messages' `data` payload consistently across endpoints (same schema). -- ✅ Monitor for WS close code `1008` to detect authorization or policy errors. \ No newline at end of file +- ✅ Monitor for WS close code `1008` to detect authorization or policy errors. + +---