DhanHQ 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -1
- data/CHANGELOG.md +103 -7
- data/GUIDE.md +57 -39
- data/README.md +198 -755
- data/docs/API_DOCS_GAPS.md +128 -0
- data/docs/API_VERIFICATION.md +10 -11
- data/{README1.md → docs/ARCHIVE_README.md} +16 -16
- data/docs/AUTHENTICATION.md +72 -10
- data/docs/CONFIGURATION.md +109 -0
- data/docs/CONSTANTS_REFERENCE.md +477 -0
- data/docs/DATA_API_PARAMETERS.md +7 -7
- data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
- data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
- data/docs/SUPER_ORDERS.md +284 -0
- data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
- data/docs/TESTING_GUIDE.md +84 -82
- data/docs/TROUBLESHOOTING.md +117 -0
- data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
- data/docs/WEBSOCKET_PROTOCOL.md +154 -0
- data/lib/DhanHQ/constants.rb +456 -151
- data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
- data/lib/DhanHQ/contracts/base_contract.rb +22 -0
- data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
- data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
- data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
- data/lib/DhanHQ/contracts/order_contract.rb +171 -39
- data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
- data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
- data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
- data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
- data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
- data/lib/DhanHQ/core/base_model.rb +13 -4
- data/lib/DhanHQ/helpers/response_helper.rb +2 -2
- data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
- data/lib/DhanHQ/models/alert_order.rb +29 -11
- data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
- data/lib/DhanHQ/models/edis.rb +101 -0
- data/lib/DhanHQ/models/expired_options_data.rb +6 -12
- data/lib/DhanHQ/models/forever_order.rb +8 -5
- data/lib/DhanHQ/models/historical_data.rb +0 -8
- data/lib/DhanHQ/models/instrument.rb +1 -7
- data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
- data/lib/DhanHQ/models/kill_switch.rb +23 -11
- data/lib/DhanHQ/models/margin.rb +51 -2
- data/lib/DhanHQ/models/order.rb +107 -126
- data/lib/DhanHQ/models/order_update.rb +7 -13
- data/lib/DhanHQ/models/pnl_exit.rb +122 -0
- data/lib/DhanHQ/models/position.rb +23 -1
- data/lib/DhanHQ/models/postback.rb +114 -0
- data/lib/DhanHQ/models/profile.rb +0 -10
- data/lib/DhanHQ/models/super_order.rb +13 -3
- data/lib/DhanHQ/models/trade.rb +11 -23
- data/lib/DhanHQ/resources/ip_setup.rb +16 -5
- data/lib/DhanHQ/resources/kill_switch.rb +17 -7
- data/lib/DhanHQ/resources/margin_calculator.rb +9 -0
- data/lib/DhanHQ/resources/orders.rb +41 -41
- data/lib/DhanHQ/resources/pnl_exit.rb +37 -0
- data/lib/DhanHQ/resources/positions.rb +8 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
- data/lib/DhanHQ/ws/orders/client.rb +6 -6
- data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
- data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
- data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
- metadata +29 -24
- data/TODO-1.md +0 -14
- data/TODO.md +0 -127
- data/app/services/live/order_update_guard_support.rb +0 -75
- data/app/services/live/order_update_hub.rb +0 -76
- data/app/services/live/order_update_persistence_support.rb +0 -68
- data/docs/PR_2.2.0.md +0 -48
- data/examples/comprehensive_websocket_examples.rb +0 -148
- data/examples/instrument_finder_test.rb +0 -195
- data/examples/live_order_updates.rb +0 -118
- data/examples/market_depth_example.rb +0 -144
- data/examples/market_feed_example.rb +0 -81
- data/examples/order_update_example.rb +0 -105
- data/examples/trading_fields_example.rb +0 -215
- /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
- /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# WebSocket Protocol Reference
|
|
2
|
+
|
|
3
|
+
Low-level protocol details for the DhanHQ WebSocket market feed. For high-level usage, see the [WebSocket Integration Guide](WEBSOCKET_INTEGRATION.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Subscription Modes
|
|
8
|
+
|
|
9
|
+
| Mode | What you get | Best for |
|
|
10
|
+
| --------- | ----------------------------------------- | ------------------------------- |
|
|
11
|
+
| `:ticker` | LTP + LTT | Lightweight price monitoring |
|
|
12
|
+
| `:quote` | LTP + LTT + OHLCV + totals | Most trading strategies |
|
|
13
|
+
| `:full` | Quote + OI + best-5 depth (bid/ask) | Order book analysis, depth-based strategies |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## Request Codes
|
|
18
|
+
|
|
19
|
+
Per Dhan documentation:
|
|
20
|
+
|
|
21
|
+
| Action | Ticker | Quote | Full |
|
|
22
|
+
| ------------ | ------ | ----- | ---- |
|
|
23
|
+
| Subscribe | 15 | 17 | 21 |
|
|
24
|
+
| Unsubscribe | 16 | 18 | 22 |
|
|
25
|
+
| Disconnect | 12 | 12 | 12 |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Packet Parsing
|
|
30
|
+
|
|
31
|
+
### Response Header (8 bytes)
|
|
32
|
+
|
|
33
|
+
| Field | Size | Encoding | Description |
|
|
34
|
+
| -------------------- | ------ | -------- | ----------------------------- |
|
|
35
|
+
| `feed_response_code` | 1 byte | u8, BE | Identifies the packet type |
|
|
36
|
+
| `message_length` | 2 bytes| u16, BE | Total message length in bytes |
|
|
37
|
+
| `exchange_segment` | 1 byte | u8, BE | Exchange segment identifier |
|
|
38
|
+
| `security_id` | 4 bytes| i32, LE | Security identifier |
|
|
39
|
+
|
|
40
|
+
### Packet Types
|
|
41
|
+
|
|
42
|
+
| Code | Type | Fields |
|
|
43
|
+
| ---- | ------------- | ------------------------------------------------------------- |
|
|
44
|
+
| 1 | Index | Surfaced as raw/misc unless documented |
|
|
45
|
+
| 2 | Ticker | `ltp`, `ltt` |
|
|
46
|
+
| 4 | Quote | `ltp`, `ltt`, `atp`, `volume`, totals, `day_*` |
|
|
47
|
+
| 5 | OI | `open_interest` |
|
|
48
|
+
| 6 | Prev Close | `prev_close`, `oi_prev` |
|
|
49
|
+
| 7 | Market Status | Raw/misc unless documented |
|
|
50
|
+
| 8 | Full | Quote fields + `open_interest` + 5× depth (bid/ask) |
|
|
51
|
+
| 50 | Disconnect | Reason code |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Normalized Tick Schema
|
|
56
|
+
|
|
57
|
+
All ticks are delivered as a Ruby Hash with consistent keys:
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
{
|
|
61
|
+
kind: :quote, # :ticker | :quote | :full | :oi | :prev_close | :misc
|
|
62
|
+
segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO, # string enum
|
|
63
|
+
security_id: "12345",
|
|
64
|
+
ltp: 101.5,
|
|
65
|
+
ts: 1723791300, # LTT epoch (sec) if present
|
|
66
|
+
vol: 123456, # quote/full only
|
|
67
|
+
atp: 100.9, # quote/full only
|
|
68
|
+
day_open: 100.1,
|
|
69
|
+
day_high: 102.4,
|
|
70
|
+
day_low: 99.5,
|
|
71
|
+
day_close: nil,
|
|
72
|
+
oi: 987654, # full or OI packet
|
|
73
|
+
bid: 101.45, # from depth (mode :full)
|
|
74
|
+
ask: 101.55 # from depth (mode :full)
|
|
75
|
+
}
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## Connection Limits & Behavior
|
|
81
|
+
|
|
82
|
+
### Limits
|
|
83
|
+
|
|
84
|
+
- **100 instruments** per subscribe/unsubscribe frame (auto-chunked by the client)
|
|
85
|
+
- **5 WebSocket connections** per user (per Dhan)
|
|
86
|
+
|
|
87
|
+
### Backoff & 429 Cool-Off
|
|
88
|
+
|
|
89
|
+
- Exponential backoff with jitter on connection failure
|
|
90
|
+
- Handshake **429** triggers a **60-second cool-off** before retry
|
|
91
|
+
- The client handles this automatically — avoid manual rapid reconnect loops
|
|
92
|
+
|
|
93
|
+
### Reconnect & Resubscribe
|
|
94
|
+
|
|
95
|
+
- On reconnect, the client resends the **current subscription snapshot** (idempotent)
|
|
96
|
+
- No manual re-subscribe needed after automatic reconnection
|
|
97
|
+
|
|
98
|
+
### Graceful Shutdown
|
|
99
|
+
|
|
100
|
+
- `ws.disconnect!` — sends broker disconnect code 12, prevents reconnects
|
|
101
|
+
- `ws.stop` — hard stop (no broker message, just closes and halts loop)
|
|
102
|
+
- `DhanHQ::WS.disconnect_all_local!` — kills all registered WS clients
|
|
103
|
+
- An `at_exit` hook stops all registered clients to avoid leaked sockets
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Exchange Segment Enums
|
|
108
|
+
|
|
109
|
+
Use these string enums in WebSocket `subscribe_*` calls and REST parameters:
|
|
110
|
+
|
|
111
|
+
| Enum | Exchange | Segment |
|
|
112
|
+
| -------------- | -------- | ----------------- |
|
|
113
|
+
| `IDX_I` | Index | Index Value |
|
|
114
|
+
| `NSE_EQ` | NSE | Equity Cash |
|
|
115
|
+
| `NSE_FNO` | NSE | Futures & Options |
|
|
116
|
+
| `NSE_CURRENCY` | NSE | Currency |
|
|
117
|
+
| `BSE_EQ` | BSE | Equity Cash |
|
|
118
|
+
| `BSE_FNO` | BSE | Futures & Options |
|
|
119
|
+
| `BSE_CURRENCY` | BSE | Currency |
|
|
120
|
+
| `MCX_COMM` | MCX | Commodity |
|
|
121
|
+
|
|
122
|
+
---
|
|
123
|
+
|
|
124
|
+
## Tick Access Patterns
|
|
125
|
+
|
|
126
|
+
### Direct Handler
|
|
127
|
+
|
|
128
|
+
```ruby
|
|
129
|
+
ws.on(:tick) { |t| do_something_fast(t) } # avoid heavy work here
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### Shared TickCache (Recommended)
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
# app/services/live/tick_cache.rb
|
|
136
|
+
class TickCache
|
|
137
|
+
MAP = Concurrent::Map.new
|
|
138
|
+
def self.put(t) = MAP["#{t[:segment]}:#{t[:security_id]}"] = t
|
|
139
|
+
def self.get(seg, sid) = MAP["#{seg}:#{sid}"]
|
|
140
|
+
def self.ltp(seg, sid) = get(seg, sid)&.dig(:ltp)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
ws.on(:tick) { |t| TickCache.put(t) }
|
|
144
|
+
ltp = TickCache.ltp("NSE_FNO", "12345")
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Filtered Callback
|
|
148
|
+
|
|
149
|
+
```ruby
|
|
150
|
+
def on_tick_for(ws, segment:, security_id:, &blk)
|
|
151
|
+
key = "#{segment}:#{security_id}"
|
|
152
|
+
ws.on(:tick) { |t| blk.call(t) if "#{t[:segment]}:#{t[:security_id]}" == key }
|
|
153
|
+
end
|
|
154
|
+
```
|