commit 604461071922bf28a512d7c90e0b9100f62a468f Author: mtrshirazi21 Date: Sun May 17 21:27:01 2026 +0330 Add README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..61c3421 --- /dev/null +++ b/README.md @@ -0,0 +1,508 @@ +# HT Data Engine | TSE & IFB | WebSocket + +## 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. + +All WebSocket endpoints require **JWT token-based authentication** and support multiple symbols or ISINs 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 +``` + +**Headers:** + +``` +Content-Type: application/x-www-form-urlencoded +``` + +**Body:** + +``` +username=your_username&password=your_password +``` + +### Response Example + +```json +{ + "Authorization": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +### Usage + +Include the token in the WebSocket connection headers: + +``` +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/tse-ifb/live/data/websocket/symbol/isin` | Subscribe using **ISIN** codes | +| `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` + +--- + +### 🔶 Important Clarification: ISIN vs Symbol-Name WebSocket Endpoints + +The data engine provides **two separate WebSocket endpoints**: + +1. **Subscribe using ISIN codes** + ``` + wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin + ``` + +2. **Subscribe using Symbol Names** + ``` + 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. + +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:** + +```json +{ + "channel": "best-limit", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { ... } +} +``` + +**Example for Symbol-Name endpoint:** + +```json +{ + "channel": "best-limit", + "symbolName": "XYZ", + "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 symbol-name endpoint returns a different schema — it does not. +- Client implementations should be prepared to handle either identifier field (`symbolIsin` or `symbolName`) depending on which endpoint they connect to. +- This avoids confusing bugs (for example: looking for `symbolIsin` in messages coming from the `/symbol/name` endpoint). + +--- + +## 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= + ``` + + For symbol names endpoint: + ``` + 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**. + +--- + +## 5. Query Parameters + +| Parameter | Type | Description | +|-----------|------|-------------| +| `symbol_isins` | list of strings | List of ISIN codes to subscribe (for `/symbol/isin`) | +| `symbol_names` | list of strings | List of symbol names to subscribe (for `/symbol/name`) | +| `channels` | list of strings | List of channels to subscribe (see Section 6) | + +**Example URL:** + +``` +wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001&symbol_isins=IRTKMOFD0001 +``` + +--- + +## 6. Channel List & Payload Schemas + +All messages are delivered in the following **JSON structure**: + +```json +{ + "channel": "best-limit", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { ... channel-specific payload ... } +} +``` + +> NOTE: For the `/symbol/name` endpoint the `symbolIsin` field above is replaced by `symbolName`. Everything else remains the same. + +| Channel | Payload Model | Description | +|---------|---------------|-------------| +| `best-limit` | `BestLimit` | Top buy/sell orders at each level | +| `order-book` | `OrderBook` | Full order book with aggregated price levels | +| `ohlcv-last-1m` | `OHLCV` | 1-minute candlestick data | +| `aggregate` | `Aggregate` | Aggregate trade data including volume, count, prices | +| `institutional-vs-individual` | `InstitutionalVsIndividual` | Buy/sell breakdown between individual and institutional investors | +| `contract-info` | `ContractInfo` | Contract details such as open interest and margin | +| `fund-info` | `FundInfo` | Fund NAV, units, market capitalization, timestamp | + +--- + +### 6.1 Example: `best-limit` + +```json +{ + "channel": "best-limit", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { + "1": { + "buy_order_count": 5, + "buy_quantity": 1000, + "buy_price": 10000, + "sell_order_count": 3, + "sell_quantity": 800, + "sell_price": 10050 + }, + "2": { + "buy_order_count": 4, + "buy_quantity": 500, + "buy_price": 9950, + "sell_order_count": 2, + "sell_quantity": 400, + "sell_price": 10100 + } + } +} +``` + +### 6.2 Example: `order-book` + +```json +{ + "channel": "order-book", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { + "Buy": [ + { + "price": 0, + "quantity": 0, + "count": 0 + } + ], + "Sell": [ + { + "price": 0, + "quantity": 0, + "count": 0 + } + ] + } +} +``` + +### 6.3 Example: `ohlcv-last-1m` + +```json +{ + "channel": "ohlcv-last-1m", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { + "open": 10000.0, + "high": 10100.0, + "low": 9950.0, + "close": 10050.0, + "volume": 3500 + } +} +``` + +### 6.4 Example: `aggregate` + +```json +{ + "channel": "aggregate", + "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 + } +} +``` + +### 6.5 Example: `institutional-vs-individual` + +```json +{ + "channel": "institutional-vs-individual", + "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 + } +} +``` + +### 6.6 Example: `contract-info` + +```json +{ + "channel": "contract-info", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { + "open_interest": 0, + "initial_margin": 0, + "required_margin": 0 + } +} +``` + +### 6.7 Example: `fund-info` + +```json +{ + "channel": "fund-info", + "symbolIsin": "IRO1XYZ1234", + "timestamp": "2025-11-14T12:00:00.000000", + "data": { + "nav": 0, + "units": 0, + "marketCap": 0, + "as_of": "2025-11-14T22:10:42.802Z" + } +} +``` + +--- + +## 7. Error Handling + +| Code | Description | +|------|-------------| +| `1008` | Policy violation (invalid JWT, invalid symbols, or invalid channels) | +| Connection closed | Occurs if Redis stream fails or server error | + +--- + +## 8. Code Examples + +### 8.1 Python (WebSocket Client) + +```python +import asyncio +import websockets +import json + +async def subscribe(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) + # safely extract identifier regardless of endpoint + symbol = data.get("symbolIsin") or data.get("symbolName") + channel = data.get("channel") + ts = data.get("timestamp") + # process payload... + print(f"{ts} | {symbol} | {channel} -> {data['data']}") + +# example use (choose the endpoint you need) +url = "wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001" +token = "" +asyncio.run(subscribe(url, token)) +``` + +### 8.2 JavaScript (WebSocket Client) + +```javascript +const WebSocket = require('ws'); + +function normalizeMessage(message) { + const msg = JSON.parse(message); + const symbol = msg.symbolIsin || msg.symbolName; + return { ...msg, symbol }; +} + +const url = 'wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001'; +const token = ''; + +const ws = new WebSocket(url, { headers: { Authorization: token } }); + +ws.on('open', () => console.log('Connected')); +ws.on('message', (data) => { + const message = normalizeMessage(data); + console.log(message.timestamp, message.symbol, message.channel, message.data); +}); +ws.on('close', () => console.log('Disconnected')); +``` + +### 8.3 Go (WebSocket Client) + +```go +package main + +import ( + "encoding/json" + "fmt" + "log" + "github.com/gorilla/websocket" +) + +func main() { + url := "wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001" + header := map[string][]string{ + "Authorization": {""}, + } + + c, _, err := websocket.DefaultDialer.Dial(url, header) + if err != nil { + log.Fatal("dial:", err) + } + defer c.Close() + + for { + _, message, err := c.ReadMessage() + if err != nil { + log.Println("read:", err) + break + } + var m map[string]interface{} + json.Unmarshal(message, &m) + symbol := "" + if v, ok := m["symbolIsin"]; ok { + symbol = v.(string) + } else if v, ok := m["symbolName"]; ok { + symbol = v.(string) + } + fmt.Println(m["timestamp"], symbol, m["channel"]) + } +} +``` + +### 8.4 Rust (WebSocket Client) + +```rust +use tokio_tungstenite::connect_async; +use futures_util::{StreamExt}; +use url::Url; +use serde_json::Value; + +#[tokio::main] +async fn main() { + let url = Url::parse("wss://core.hedgetech.ir/data-engine/tse-ifb/live/data/websocket/symbol/isin?channels=order-book&channels=best-limit&symbol_isins=IRT3SATF0001").unwrap(); + let req = tokio_tungstenite::tungstenite::client::IntoClientRequest::into_client_request(url).unwrap(); + let (ws_stream, _) = connect_async(req).await.expect("Failed to connect"); + + let (_write, mut read) = ws_stream.split(); + + while let Some(message) = read.next().await { + let msg = message.unwrap(); + if msg.is_text() { + let v: Value = serde_json::from_str(msg.to_text().unwrap()).unwrap(); + let symbol = v.get("symbolIsin").or_else(|| v.get("symbolName")); + println!("{:?} {:?} {:?}", v["timestamp"], symbol, v["channel"]); + } + } +} +``` + +### 8.5 Subscription Notes + +- Multiple symbols and channels can be subscribed in a **single WebSocket connection**. +- The server streams messages continuously; handle them asynchronously. +- Always respect **Rate Limits** if applicable. + +--- + +## 9. Best Practices + +- Reconnect with **exponential backoff** in case of disconnects. +- Validate your JWT **before subscribing**. +- Subscribe only to the channels you need to reduce bandwidth. +- Handle `symbolIsin` / `symbolName` extraction consistently in your client code: + - Prefer a helper function that returns a single canonical `symbol` value. + - Log incoming messages and detect which endpoint you are connected to (optional, for debugging). + +--- + +## 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