DhanHQ 2.1.5 โ 2.1.6
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/CHANGELOG.md +7 -0
- data/GUIDE.md +215 -73
- data/README.md +416 -132
- data/README1.md +267 -26
- data/docs/live_order_updates.md +319 -0
- data/docs/rails_websocket_integration.md +847 -0
- data/docs/standalone_ruby_websocket_integration.md +1588 -0
- data/docs/websocket_integration.md +871 -0
- data/examples/comprehensive_websocket_examples.rb +148 -0
- data/examples/instrument_finder_test.rb +195 -0
- data/examples/live_order_updates.rb +118 -0
- data/examples/market_depth_example.rb +144 -0
- data/examples/market_feed_example.rb +81 -0
- data/examples/order_update_example.rb +105 -0
- data/examples/trading_fields_example.rb +215 -0
- data/lib/DhanHQ/configuration.rb +16 -1
- data/lib/DhanHQ/contracts/expired_options_data_contract.rb +103 -0
- data/lib/DhanHQ/contracts/trade_contract.rb +70 -0
- data/lib/DhanHQ/errors.rb +2 -0
- data/lib/DhanHQ/models/expired_options_data.rb +331 -0
- data/lib/DhanHQ/models/instrument.rb +96 -2
- data/lib/DhanHQ/models/order_update.rb +235 -0
- data/lib/DhanHQ/models/trade.rb +118 -31
- data/lib/DhanHQ/resources/expired_options_data.rb +22 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/base_connection.rb +249 -0
- data/lib/DhanHQ/ws/connection.rb +2 -2
- data/lib/DhanHQ/ws/decoder.rb +3 -3
- data/lib/DhanHQ/ws/market_depth/client.rb +376 -0
- data/lib/DhanHQ/ws/market_depth/decoder.rb +131 -0
- data/lib/DhanHQ/ws/market_depth.rb +74 -0
- data/lib/DhanHQ/ws/orders/client.rb +175 -11
- data/lib/DhanHQ/ws/orders/connection.rb +40 -81
- data/lib/DhanHQ/ws/orders.rb +28 -0
- data/lib/DhanHQ/ws/segments.rb +18 -2
- data/lib/DhanHQ/ws.rb +3 -2
- data/lib/dhan_hq.rb +5 -0
- metadata +35 -1
@@ -0,0 +1,871 @@
|
|
1
|
+
# WebSocket Integration Guide
|
2
|
+
|
3
|
+
This guide covers the comprehensive WebSocket integration provided by the DhanHQ Ruby client gem. The gem provides three types of WebSocket connections for real-time data streaming with improved architecture, security, and reliability.
|
4
|
+
|
5
|
+
## Overview
|
6
|
+
|
7
|
+
The DhanHQ WebSocket integration provides three distinct WebSocket types:
|
8
|
+
|
9
|
+
1. **Market Feed WebSocket** - Live market data for indices and stocks
|
10
|
+
2. **Order Update WebSocket** - Real-time order updates and status changes
|
11
|
+
3. **Market Depth WebSocket** - Real-time market depth (bid/ask levels)
|
12
|
+
|
13
|
+
### Key Features
|
14
|
+
|
15
|
+
- **๐ Secure Logging** - Sensitive information (access tokens) are automatically sanitized from logs
|
16
|
+
- **โก Rate Limit Protection** - Built-in protection against 429 errors with proper connection management
|
17
|
+
- **๐ Automatic Reconnection** - Exponential backoff with 60-second cool-off periods
|
18
|
+
- **๐งต Thread-Safe Operation** - Safe for Rails applications and multi-threaded environments
|
19
|
+
- **๐ Comprehensive Examples** - Ready-to-use examples for all WebSocket types
|
20
|
+
- **๐ก๏ธ Error Handling** - Robust error handling and connection management
|
21
|
+
|
22
|
+
## Quick Start
|
23
|
+
|
24
|
+
### 1. Configuration
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
require 'dhan_hq'
|
28
|
+
|
29
|
+
# Configure DhanHQ
|
30
|
+
DhanHQ.configure do |config|
|
31
|
+
config.client_id = ENV["CLIENT_ID"] || "your_client_id"
|
32
|
+
config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
|
33
|
+
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
### 2. Market Feed WebSocket (Recommended for Beginners)
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
# Subscribe to major Indian indices
|
41
|
+
market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
42
|
+
timestamp = tick[:ts] ? Time.at(tick[:ts]) : Time.now
|
43
|
+
puts "Market Data: #{tick[:segment]}:#{tick[:security_id]} = #{tick[:ltp]} at #{timestamp}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Subscribe to specific indices
|
47
|
+
market_client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
|
48
|
+
market_client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
|
49
|
+
market_client.subscribe_one(segment: "IDX_I", security_id: "29") # NIFTYIT
|
50
|
+
market_client.subscribe_one(segment: "IDX_I", security_id: "51") # SENSEX
|
51
|
+
|
52
|
+
# Clean shutdown
|
53
|
+
market_client.stop
|
54
|
+
```
|
55
|
+
|
56
|
+
### 3. Order Update WebSocket
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
# Real-time order updates
|
60
|
+
orders_client = DhanHQ::WS::Orders.connect do |order_update|
|
61
|
+
puts "Order Update: #{order_update.order_no} - #{order_update.status}"
|
62
|
+
puts " Symbol: #{order_update.symbol}"
|
63
|
+
puts " Quantity: #{order_update.quantity}"
|
64
|
+
puts " Traded Qty: #{order_update.traded_qty}"
|
65
|
+
puts " Price: #{order_update.price}"
|
66
|
+
puts " Execution: #{order_update.execution_percentage}%"
|
67
|
+
end
|
68
|
+
|
69
|
+
# Clean shutdown
|
70
|
+
orders_client.stop
|
71
|
+
```
|
72
|
+
|
73
|
+
### 4. Market Depth WebSocket
|
74
|
+
|
75
|
+
```ruby
|
76
|
+
# Real-time market depth for stocks (using dynamic symbol resolution with underlying_symbol)
|
77
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
78
|
+
tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
|
79
|
+
|
80
|
+
symbols = []
|
81
|
+
if reliance
|
82
|
+
symbols << { symbol: "RELIANCE", exchange_segment: reliance.exchange_segment, security_id: reliance.security_id }
|
83
|
+
end
|
84
|
+
if tcs
|
85
|
+
symbols << { symbol: "TCS", exchange_segment: tcs.exchange_segment, security_id: tcs.security_id }
|
86
|
+
end
|
87
|
+
|
88
|
+
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) do |depth_data|
|
89
|
+
puts "Market Depth: #{depth_data[:symbol]}"
|
90
|
+
puts " Best Bid: #{depth_data[:best_bid]}"
|
91
|
+
puts " Best Ask: #{depth_data[:best_ask]}"
|
92
|
+
puts " Spread: #{depth_data[:spread]}"
|
93
|
+
puts " Bid Levels: #{depth_data[:bids].size}"
|
94
|
+
puts " Ask Levels: #{depth_data[:asks].size}"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Clean shutdown
|
98
|
+
depth_client.stop
|
99
|
+
```
|
100
|
+
|
101
|
+
## Detailed Usage
|
102
|
+
|
103
|
+
### Market Feed WebSocket
|
104
|
+
|
105
|
+
The Market Feed WebSocket provides real-time market data for indices and stocks.
|
106
|
+
|
107
|
+
#### Available Modes
|
108
|
+
|
109
|
+
- `:ticker` - Last traded price (LTP) updates
|
110
|
+
- `:quote` - LTP + volume + OHLC data
|
111
|
+
- `:full` - Complete market data including depth
|
112
|
+
|
113
|
+
#### Basic Usage
|
114
|
+
|
115
|
+
```ruby
|
116
|
+
# Ticker mode (recommended for most use cases)
|
117
|
+
market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
118
|
+
timestamp = tick[:ts] ? Time.at(tick[:ts]) : Time.now
|
119
|
+
puts "Market Data: #{tick[:segment]}:#{tick[:security_id]} = #{tick[:ltp]} at #{timestamp}"
|
120
|
+
end
|
121
|
+
|
122
|
+
# Quote mode (includes volume and OHLC)
|
123
|
+
market_client = DhanHQ::WS.connect(mode: :quote) do |quote|
|
124
|
+
puts "Quote: #{quote[:segment]}:#{quote[:security_id]}"
|
125
|
+
puts " LTP: #{quote[:ltp]}"
|
126
|
+
puts " Volume: #{quote[:vol]}"
|
127
|
+
puts " Day High: #{quote[:day_high]}"
|
128
|
+
puts " Day Low: #{quote[:day_low]}"
|
129
|
+
end
|
130
|
+
```
|
131
|
+
|
132
|
+
#### Subscription Management
|
133
|
+
|
134
|
+
```ruby
|
135
|
+
client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
|
136
|
+
|
137
|
+
# Subscribe to individual instruments
|
138
|
+
client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
|
139
|
+
client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
|
140
|
+
client.subscribe_one(segment: "NSE_EQ", security_id: "2885") # RELIANCE
|
141
|
+
|
142
|
+
# Subscribe to multiple instruments
|
143
|
+
instruments = [
|
144
|
+
{ ExchangeSegment: "IDX_I", SecurityId: "13" },
|
145
|
+
{ ExchangeSegment: "IDX_I", SecurityId: "25" },
|
146
|
+
{ ExchangeSegment: "NSE_EQ", SecurityId: "2885" }
|
147
|
+
]
|
148
|
+
client.subscribe_many(instruments)
|
149
|
+
|
150
|
+
# Unsubscribe
|
151
|
+
client.unsubscribe_one(segment: "IDX_I", security_id: "13")
|
152
|
+
```
|
153
|
+
|
154
|
+
#### Finding Correct Security IDs
|
155
|
+
|
156
|
+
```ruby
|
157
|
+
# Method 1: Using the new .find method (recommended)
|
158
|
+
# For equity instruments, uses underlying_symbol (e.g., "RELIANCE" instead of "RELIANCE INDUSTRIES LTD")
|
159
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
160
|
+
tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
|
161
|
+
nifty = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
162
|
+
|
163
|
+
puts "RELIANCE Security ID: #{reliance.security_id}" # 2885
|
164
|
+
puts "TCS Security ID: #{tcs.security_id}" # 11536
|
165
|
+
puts "NIFTY Security ID: #{nifty.security_id}" # 13
|
166
|
+
|
167
|
+
# Method 2: Using .find_anywhere for cross-segment search
|
168
|
+
reliance_anywhere = DhanHQ::Models::Instrument.find_anywhere("RELIANCE")
|
169
|
+
puts "RELIANCE found in: #{reliance_anywhere.exchange_segment}:#{reliance_anywhere.security_id}"
|
170
|
+
|
171
|
+
# Method 3: Manual search (legacy)
|
172
|
+
nse_instruments = DhanHQ::Models::Instrument.by_segment("NSE_EQ")
|
173
|
+
idx_instruments = DhanHQ::Models::Instrument.by_segment("IDX_I")
|
174
|
+
|
175
|
+
reliance_manual = nse_instruments.select { |i| i.symbol_name == "RELIANCE INDUSTRIES LTD" }
|
176
|
+
nifty_manual = idx_instruments.select { |i| i.symbol_name == "NIFTY" }
|
177
|
+
|
178
|
+
puts "RELIANCE Security ID: #{reliance_manual.first.security_id}" # 2885
|
179
|
+
puts "NIFTY Security ID: #{nifty_manual.first.security_id}" # 13
|
180
|
+
```
|
181
|
+
|
182
|
+
#### Advanced Symbol Search Options
|
183
|
+
|
184
|
+
```ruby
|
185
|
+
# Exact match search
|
186
|
+
reliance_exact = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE INDUSTRIES LTD", exact_match: true)
|
187
|
+
|
188
|
+
# Case sensitive search
|
189
|
+
reliance_case = DhanHQ::Models::Instrument.find("NSE_EQ", "reliance industries ltd", case_sensitive: true)
|
190
|
+
|
191
|
+
# Search across specific segments only
|
192
|
+
reliance_limited = DhanHQ::Models::Instrument.find_anywhere("RELIANCE", segments: ["NSE_EQ", "BSE_EQ"])
|
193
|
+
|
194
|
+
# Find multiple instruments
|
195
|
+
symbols = ["RELIANCE INDUSTRIES LTD", "TATA CONSULTANCY SERV LT", "NIFTY", "BANKNIFTY"]
|
196
|
+
instruments = symbols.map do |symbol|
|
197
|
+
DhanHQ::Models::Instrument.find_anywhere(symbol, exact_match: true)
|
198
|
+
end.compact
|
199
|
+
```
|
200
|
+
|
201
|
+
### Order Update WebSocket
|
202
|
+
|
203
|
+
The Order Update WebSocket provides real-time updates for all orders placed through your account.
|
204
|
+
|
205
|
+
#### Basic Usage
|
206
|
+
|
207
|
+
```ruby
|
208
|
+
# Simple connection
|
209
|
+
orders_client = DhanHQ::WS::Orders.connect do |order_update|
|
210
|
+
puts "Order Update: #{order_update.order_no} - #{order_update.status}"
|
211
|
+
puts " Symbol: #{order_update.symbol}"
|
212
|
+
puts " Quantity: #{order_update.quantity}"
|
213
|
+
puts " Traded Qty: #{order_update.traded_qty}"
|
214
|
+
puts " Price: #{order_update.price}"
|
215
|
+
puts " Execution: #{order_update.execution_percentage}%"
|
216
|
+
end
|
217
|
+
```
|
218
|
+
|
219
|
+
#### Advanced Event Handling
|
220
|
+
|
221
|
+
```ruby
|
222
|
+
client = DhanHQ::WS::Orders.client
|
223
|
+
|
224
|
+
# Multiple event handlers
|
225
|
+
client.on(:update) do |order_update|
|
226
|
+
puts "๐ Order Updated: #{order_update.order_no}"
|
227
|
+
end
|
228
|
+
|
229
|
+
client.on(:status_change) do |change_data|
|
230
|
+
puts "๐ Status Changed: #{change_data[:previous_status]} -> #{change_data[:new_status]}"
|
231
|
+
end
|
232
|
+
|
233
|
+
client.on(:execution) do |execution_data|
|
234
|
+
puts "โ
Execution: #{execution_data[:new_traded_qty]} shares executed"
|
235
|
+
end
|
236
|
+
|
237
|
+
client.on(:order_traded) do |order_update|
|
238
|
+
puts "๐ฐ Order Traded: #{order_update.order_no} - #{order_update.symbol}"
|
239
|
+
end
|
240
|
+
|
241
|
+
client.on(:order_rejected) do |order_update|
|
242
|
+
puts "โ Order Rejected: #{order_update.order_no} - #{order_update.reason_description}"
|
243
|
+
end
|
244
|
+
|
245
|
+
client.on(:error) do |error|
|
246
|
+
puts "โ ๏ธ WebSocket Error: #{error}"
|
247
|
+
end
|
248
|
+
|
249
|
+
client.on(:close) do |close_info|
|
250
|
+
puts "๐ WebSocket Closed: #{close_info[:code]} - #{close_info[:reason]}"
|
251
|
+
end
|
252
|
+
|
253
|
+
client.start
|
254
|
+
```
|
255
|
+
|
256
|
+
#### Available Events
|
257
|
+
|
258
|
+
- `:update` - Any order update
|
259
|
+
- `:status_change` - Order status changed
|
260
|
+
- `:execution` - Order execution update
|
261
|
+
- `:order_traded` - Order traded
|
262
|
+
- `:order_rejected` - Order rejected
|
263
|
+
- `:order_cancelled` - Order cancelled
|
264
|
+
- `:order_expired` - Order expired
|
265
|
+
- `:open` - Connection opened
|
266
|
+
- `:close` - Connection closed
|
267
|
+
- `:error` - Connection error
|
268
|
+
|
269
|
+
### Market Depth WebSocket
|
270
|
+
|
271
|
+
The Market Depth WebSocket provides real-time market depth data including bid/ask levels.
|
272
|
+
|
273
|
+
#### Basic Usage
|
274
|
+
|
275
|
+
```ruby
|
276
|
+
# Method 1: Using the new .find method (recommended)
|
277
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
278
|
+
tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
|
279
|
+
|
280
|
+
symbols = []
|
281
|
+
if reliance
|
282
|
+
symbols << { symbol: "RELIANCE", exchange_segment: reliance.exchange_segment, security_id: reliance.security_id }
|
283
|
+
end
|
284
|
+
if tcs
|
285
|
+
symbols << { symbol: "TCS", exchange_segment: tcs.exchange_segment, security_id: tcs.security_id }
|
286
|
+
end
|
287
|
+
|
288
|
+
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) do |depth_data|
|
289
|
+
puts "Market Depth: #{depth_data[:symbol]}"
|
290
|
+
puts " Best Bid: #{depth_data[:best_bid]}"
|
291
|
+
puts " Best Ask: #{depth_data[:best_ask]}"
|
292
|
+
puts " Spread: #{depth_data[:spread]}"
|
293
|
+
puts " Bid Levels: #{depth_data[:bids].size}"
|
294
|
+
puts " Ask Levels: #{depth_data[:asks].size}"
|
295
|
+
end
|
296
|
+
|
297
|
+
# Method 2: Direct specification (legacy)
|
298
|
+
symbols_direct = [
|
299
|
+
{ symbol: "RELIANCE", exchange_segment: "NSE_EQ", security_id: "2885" },
|
300
|
+
{ symbol: "TCS", exchange_segment: "NSE_EQ", security_id: "11536" }
|
301
|
+
]
|
302
|
+
|
303
|
+
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols_direct) do |depth_data|
|
304
|
+
puts "Market Depth: #{depth_data[:symbol]}"
|
305
|
+
puts " Best Bid: #{depth_data[:best_bid]}"
|
306
|
+
puts " Best Ask: #{depth_data[:best_ask]}"
|
307
|
+
puts " Spread: #{depth_data[:spread]}"
|
308
|
+
puts " Bid Levels: #{depth_data[:bids].size}"
|
309
|
+
puts " Ask Levels: #{depth_data[:asks].size}"
|
310
|
+
end
|
311
|
+
```
|
312
|
+
|
313
|
+
#### Advanced Usage
|
314
|
+
|
315
|
+
```ruby
|
316
|
+
client = DhanHQ::WS::MarketDepth.client
|
317
|
+
|
318
|
+
# Event handlers
|
319
|
+
client.on(:depth_update) do |update_data|
|
320
|
+
puts "๐ Depth Update: #{update_data[:symbol]} - #{update_data[:side]} side updated"
|
321
|
+
end
|
322
|
+
|
323
|
+
client.on(:depth_snapshot) do |snapshot_data|
|
324
|
+
puts "๐ธ Depth Snapshot: #{snapshot_data[:symbol]} - Full order book received"
|
325
|
+
end
|
326
|
+
|
327
|
+
client.on(:error) do |error|
|
328
|
+
puts "โ ๏ธ WebSocket Error: #{error}"
|
329
|
+
end
|
330
|
+
|
331
|
+
client.start
|
332
|
+
|
333
|
+
# Subscribe to symbols
|
334
|
+
symbols = [
|
335
|
+
{ symbol: "RELIANCE", exchange_segment: "NSE_EQ", security_id: "2885" },
|
336
|
+
{ symbol: "TCS", exchange_segment: "NSE_EQ", security_id: "11536" }
|
337
|
+
]
|
338
|
+
client.subscribe(symbols)
|
339
|
+
```
|
340
|
+
|
341
|
+
#### Finding Correct Symbols
|
342
|
+
|
343
|
+
```ruby
|
344
|
+
# Method 1: Using the new .find method (recommended)
|
345
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
346
|
+
tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
|
347
|
+
|
348
|
+
puts "RELIANCE: #{reliance.exchange_segment}:#{reliance.security_id}" # NSE:2885
|
349
|
+
puts "TCS: #{tcs.exchange_segment}:#{tcs.security_id}" # NSE:11536
|
350
|
+
|
351
|
+
# Method 2: Using .find_anywhere for cross-segment search
|
352
|
+
reliance_anywhere = DhanHQ::Models::Instrument.find_anywhere("RELIANCE")
|
353
|
+
tcs_anywhere = DhanHQ::Models::Instrument.find_anywhere("TCS")
|
354
|
+
|
355
|
+
puts "RELIANCE found in: #{reliance_anywhere.exchange_segment}:#{reliance_anywhere.security_id}"
|
356
|
+
puts "TCS found in: #{tcs_anywhere.exchange_segment}:#{tcs_anywhere.security_id}"
|
357
|
+
|
358
|
+
# Method 3: Manual search (legacy)
|
359
|
+
nse_instruments = DhanHQ::Models::Instrument.by_segment("NSE_EQ")
|
360
|
+
|
361
|
+
reliance_manual = nse_instruments.select { |i| i.underlying_symbol == "RELIANCE" }
|
362
|
+
tcs_manual = nse_instruments.select { |i| i.underlying_symbol == "TCS" }
|
363
|
+
|
364
|
+
puts "RELIANCE: NSE_EQ:#{reliance_manual.first.security_id}" # NSE_EQ:2885
|
365
|
+
puts "TCS: NSE_EQ:#{tcs_manual.first.security_id}" # NSE_EQ:11536
|
366
|
+
```
|
367
|
+
|
368
|
+
## Instrument Model with Trading Fields
|
369
|
+
|
370
|
+
The DhanHQ Instrument model now includes comprehensive trading fields essential for order validation, risk management, and compliance monitoring.
|
371
|
+
|
372
|
+
### Available Trading Fields
|
373
|
+
|
374
|
+
```ruby
|
375
|
+
# Find an instrument with all trading fields
|
376
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
377
|
+
|
378
|
+
# Core identification fields
|
379
|
+
puts "Symbol: #{reliance.symbol_name}" # RELIANCE INDUSTRIES LTD
|
380
|
+
puts "Underlying Symbol: #{reliance.underlying_symbol}" # RELIANCE
|
381
|
+
puts "Security ID: #{reliance.security_id}" # 2885
|
382
|
+
puts "ISIN: #{reliance.isin}" # INE002A01018
|
383
|
+
puts "Instrument Type: #{reliance.instrument_type}" # ES
|
384
|
+
|
385
|
+
# Trading permission and restrictions
|
386
|
+
puts "Buy/Sell Indicator: #{reliance.buy_sell_indicator}" # A (Allowed)
|
387
|
+
puts "Bracket Flag: #{reliance.bracket_flag}" # N (Not Allowed)
|
388
|
+
puts "Cover Flag: #{reliance.cover_flag}" # N (Not Allowed)
|
389
|
+
puts "ASM/GSM Flag: #{reliance.asm_gsm_flag}" # N (No Restrictions)
|
390
|
+
puts "ASM/GSM Category: #{reliance.asm_gsm_category}" # NA
|
391
|
+
|
392
|
+
# Margin and leverage information
|
393
|
+
puts "Buy CO Min Margin %: #{reliance.buy_co_min_margin_per}" # 0.0
|
394
|
+
puts "Sell CO Min Margin %: #{reliance.sell_co_min_margin_per}" # 0.0
|
395
|
+
puts "MTF Leverage: #{reliance.mtf_leverage}" # 4.545455
|
396
|
+
|
397
|
+
# Expiry information
|
398
|
+
puts "Expiry Flag: #{reliance.expiry_flag}" # NA (No Expiry)
|
399
|
+
puts "Expiry Date: #{reliance.expiry_date}" # nil
|
400
|
+
```
|
401
|
+
|
402
|
+
### Trading Field Usage Examples
|
403
|
+
|
404
|
+
#### Order Validation
|
405
|
+
```ruby
|
406
|
+
def validate_order(instrument, order_type)
|
407
|
+
# Check if trading is allowed
|
408
|
+
return false unless instrument.buy_sell_indicator == "A"
|
409
|
+
|
410
|
+
# Check order type support
|
411
|
+
case order_type
|
412
|
+
when :bracket
|
413
|
+
return false unless instrument.bracket_flag == "Y"
|
414
|
+
when :cover
|
415
|
+
return false unless instrument.cover_flag == "Y"
|
416
|
+
end
|
417
|
+
|
418
|
+
# Check ASM/GSM restrictions
|
419
|
+
if instrument.asm_gsm_flag == "Y"
|
420
|
+
puts "Warning: ASM/GSM applied - #{instrument.asm_gsm_category}"
|
421
|
+
end
|
422
|
+
|
423
|
+
true
|
424
|
+
end
|
425
|
+
|
426
|
+
# Usage
|
427
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
428
|
+
if validate_order(reliance, :bracket)
|
429
|
+
puts "Bracket order allowed for RELIANCE"
|
430
|
+
else
|
431
|
+
puts "Bracket order not allowed for RELIANCE"
|
432
|
+
end
|
433
|
+
```
|
434
|
+
|
435
|
+
#### Margin Calculations
|
436
|
+
```ruby
|
437
|
+
def calculate_margin(instrument, quantity, price)
|
438
|
+
total_value = quantity * price * instrument.lot_size
|
439
|
+
|
440
|
+
# Calculate margin requirements
|
441
|
+
buy_margin = total_value * (instrument.buy_co_min_margin_per / 100.0)
|
442
|
+
sell_margin = total_value * (instrument.sell_co_min_margin_per / 100.0)
|
443
|
+
|
444
|
+
# Calculate MTF leverage
|
445
|
+
mtf_value = instrument.mtf_leverage > 0 ? total_value * instrument.mtf_leverage : 0
|
446
|
+
|
447
|
+
{
|
448
|
+
total_value: total_value,
|
449
|
+
buy_margin: buy_margin,
|
450
|
+
sell_margin: sell_margin,
|
451
|
+
mtf_value: mtf_value,
|
452
|
+
mtf_leverage: instrument.mtf_leverage
|
453
|
+
}
|
454
|
+
end
|
455
|
+
|
456
|
+
# Usage
|
457
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
458
|
+
margin_info = calculate_margin(reliance, 10, 2500)
|
459
|
+
puts "Total Value: โน#{margin_info[:total_value]}"
|
460
|
+
puts "MTF Leverage: #{margin_info[:mtf_leverage]}x"
|
461
|
+
puts "MTF Value: โน#{margin_info[:mtf_value]}"
|
462
|
+
```
|
463
|
+
|
464
|
+
#### Risk Management
|
465
|
+
```ruby
|
466
|
+
def assess_risk(instrument)
|
467
|
+
risk_factors = []
|
468
|
+
|
469
|
+
# Check ASM/GSM status
|
470
|
+
if instrument.asm_gsm_flag == "Y"
|
471
|
+
risk_factors << "ASM/GSM Applied: #{instrument.asm_gsm_category}"
|
472
|
+
end
|
473
|
+
|
474
|
+
# Check expiry
|
475
|
+
if instrument.expiry_flag == "Y"
|
476
|
+
risk_factors << "Instrument has expiry: #{instrument.expiry_date}"
|
477
|
+
end
|
478
|
+
|
479
|
+
# Check high leverage
|
480
|
+
if instrument.mtf_leverage > 5.0
|
481
|
+
risk_factors << "High MTF leverage: #{instrument.mtf_leverage}x"
|
482
|
+
end
|
483
|
+
|
484
|
+
risk_factors
|
485
|
+
end
|
486
|
+
|
487
|
+
# Usage
|
488
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
489
|
+
risks = assess_risk(reliance)
|
490
|
+
if risks.any?
|
491
|
+
puts "Risk factors:"
|
492
|
+
risks.each { |risk| puts " - #{risk}" }
|
493
|
+
else
|
494
|
+
puts "No significant risk factors identified"
|
495
|
+
end
|
496
|
+
```
|
497
|
+
|
498
|
+
### Trading Fields Reference
|
499
|
+
|
500
|
+
| Field | Type | Description | Example Values |
|
501
|
+
| ------------------------ | ------ | ---------------------------------------------- | --------------------------- |
|
502
|
+
| `isin` | String | International Securities Identification Number | "INE002A01018" |
|
503
|
+
| `instrument_type` | String | Instrument classification | "ES", "INDEX", "FUT", "OPT" |
|
504
|
+
| `expiry_flag` | String | Whether instrument has expiry | "Y", "N", "NA" |
|
505
|
+
| `bracket_flag` | String | Whether bracket orders are allowed | "Y", "N" |
|
506
|
+
| `cover_flag` | String | Whether cover orders are allowed | "Y", "N" |
|
507
|
+
| `asm_gsm_flag` | String | Additional Surveillance Measure flag | "Y", "N" |
|
508
|
+
| `asm_gsm_category` | String | ASM/GSM category classification | "ASM", "GSM", "NA" |
|
509
|
+
| `buy_sell_indicator` | String | Whether instrument allows buy/sell | "A", "N" |
|
510
|
+
| `buy_co_min_margin_per` | Float | Buy CO minimum margin percentage | 0.0, 20.0 |
|
511
|
+
| `sell_co_min_margin_per` | Float | Sell CO minimum margin percentage | 0.0, 20.0 |
|
512
|
+
| `mtf_leverage` | Float | Margin Trading Facility leverage | 0.0, 4.545455 |
|
513
|
+
|
514
|
+
## Rails Integration
|
515
|
+
|
516
|
+
### Basic Rails Integration
|
517
|
+
|
518
|
+
```ruby
|
519
|
+
# config/initializers/dhan_hq.rb
|
520
|
+
DhanHQ.configure do |config|
|
521
|
+
config.client_id = Rails.application.credentials.dhanhq[:client_id]
|
522
|
+
config.access_token = Rails.application.credentials.dhanhq[:access_token]
|
523
|
+
config.ws_user_type = Rails.application.credentials.dhanhq[:ws_user_type]
|
524
|
+
end
|
525
|
+
```
|
526
|
+
|
527
|
+
### Service Class Pattern
|
528
|
+
|
529
|
+
```ruby
|
530
|
+
# app/services/market_data_service.rb
|
531
|
+
class MarketDataService
|
532
|
+
def initialize
|
533
|
+
@market_client = nil
|
534
|
+
end
|
535
|
+
|
536
|
+
def start_market_feed
|
537
|
+
@market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
538
|
+
process_market_data(tick)
|
539
|
+
end
|
540
|
+
|
541
|
+
# Subscribe to indices
|
542
|
+
@market_client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
|
543
|
+
@market_client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
|
544
|
+
@market_client.subscribe_one(segment: "IDX_I", security_id: "29") # NIFTYIT
|
545
|
+
@market_client.subscribe_one(segment: "IDX_I", security_id: "51") # SENSEX
|
546
|
+
end
|
547
|
+
|
548
|
+
def stop_market_feed
|
549
|
+
@market_client&.stop
|
550
|
+
@market_client = nil
|
551
|
+
end
|
552
|
+
|
553
|
+
private
|
554
|
+
|
555
|
+
def process_market_data(tick)
|
556
|
+
# Store in database
|
557
|
+
MarketData.create!(
|
558
|
+
segment: tick[:segment],
|
559
|
+
security_id: tick[:security_id],
|
560
|
+
ltp: tick[:ltp],
|
561
|
+
timestamp: tick[:ts] ? Time.at(tick[:ts]) : Time.now
|
562
|
+
)
|
563
|
+
|
564
|
+
# Broadcast via ActionCable
|
565
|
+
ActionCable.server.broadcast(
|
566
|
+
"market_data_#{tick[:segment]}",
|
567
|
+
{
|
568
|
+
segment: tick[:segment],
|
569
|
+
security_id: tick[:security_id],
|
570
|
+
ltp: tick[:ltp],
|
571
|
+
timestamp: tick[:ts]
|
572
|
+
}
|
573
|
+
)
|
574
|
+
end
|
575
|
+
end
|
576
|
+
```
|
577
|
+
|
578
|
+
### Order Update Service
|
579
|
+
|
580
|
+
```ruby
|
581
|
+
# app/services/order_update_service.rb
|
582
|
+
class OrderUpdateService
|
583
|
+
def initialize
|
584
|
+
@orders_client = nil
|
585
|
+
end
|
586
|
+
|
587
|
+
def start_order_updates
|
588
|
+
@orders_client = DhanHQ::WS::Orders.connect do |order_update|
|
589
|
+
process_order_update(order_update)
|
590
|
+
end
|
591
|
+
|
592
|
+
# Add error handling
|
593
|
+
@orders_client.on(:error) do |error|
|
594
|
+
Rails.logger.error "Order WebSocket error: #{error}"
|
595
|
+
end
|
596
|
+
|
597
|
+
@orders_client.on(:close) do |close_info|
|
598
|
+
Rails.logger.warn "Order WebSocket closed: #{close_info[:code]}"
|
599
|
+
end
|
600
|
+
end
|
601
|
+
|
602
|
+
def stop_order_updates
|
603
|
+
@orders_client&.stop
|
604
|
+
@orders_client = nil
|
605
|
+
end
|
606
|
+
|
607
|
+
private
|
608
|
+
|
609
|
+
def process_order_update(order_update)
|
610
|
+
# Update database
|
611
|
+
order = Order.find_by(order_no: order_update.order_no)
|
612
|
+
if order
|
613
|
+
order.update!(
|
614
|
+
status: order_update.status,
|
615
|
+
traded_qty: order_update.traded_qty,
|
616
|
+
avg_price: order_update.avg_traded_price
|
617
|
+
)
|
618
|
+
|
619
|
+
# Broadcast to user
|
620
|
+
ActionCable.server.broadcast(
|
621
|
+
"order_updates_#{order.user_id}",
|
622
|
+
{
|
623
|
+
order_no: order_update.order_no,
|
624
|
+
status: order_update.status,
|
625
|
+
traded_qty: order_update.traded_qty,
|
626
|
+
execution_percentage: order_update.execution_percentage
|
627
|
+
}
|
628
|
+
)
|
629
|
+
end
|
630
|
+
end
|
631
|
+
end
|
632
|
+
```
|
633
|
+
|
634
|
+
### Background Job Processing
|
635
|
+
|
636
|
+
```ruby
|
637
|
+
# app/jobs/process_market_data_job.rb
|
638
|
+
class ProcessMarketDataJob < ApplicationJob
|
639
|
+
queue_as :market_data
|
640
|
+
|
641
|
+
def perform(market_data)
|
642
|
+
# Process market data
|
643
|
+
MarketData.create!(
|
644
|
+
segment: market_data[:segment],
|
645
|
+
security_id: market_data[:security_id],
|
646
|
+
ltp: market_data[:ltp],
|
647
|
+
volume: market_data[:vol],
|
648
|
+
timestamp: Time.at(market_data[:ts])
|
649
|
+
)
|
650
|
+
|
651
|
+
# Update cache
|
652
|
+
Rails.cache.write(
|
653
|
+
"market_data_#{market_data[:segment]}:#{market_data[:security_id]}",
|
654
|
+
market_data,
|
655
|
+
expires_in: 1.minute
|
656
|
+
)
|
657
|
+
end
|
658
|
+
end
|
659
|
+
|
660
|
+
# In your WebSocket handler
|
661
|
+
DhanHQ::WS.connect(mode: :ticker) do |tick|
|
662
|
+
ProcessMarketDataJob.perform_later(tick)
|
663
|
+
end
|
664
|
+
```
|
665
|
+
|
666
|
+
### Application Controller Integration
|
667
|
+
|
668
|
+
```ruby
|
669
|
+
# app/controllers/application_controller.rb
|
670
|
+
class ApplicationController < ActionController::Base
|
671
|
+
around_action :ensure_websocket_cleanup
|
672
|
+
|
673
|
+
private
|
674
|
+
|
675
|
+
def ensure_websocket_cleanup
|
676
|
+
yield
|
677
|
+
ensure
|
678
|
+
# Clean up any stray WebSocket connections
|
679
|
+
DhanHQ::WS.disconnect_all_local!
|
680
|
+
end
|
681
|
+
end
|
682
|
+
```
|
683
|
+
|
684
|
+
## Connection Management
|
685
|
+
|
686
|
+
### Rate Limiting Protection
|
687
|
+
|
688
|
+
DhanHQ allows up to 5 WebSocket connections per user. To avoid 429 errors:
|
689
|
+
|
690
|
+
```ruby
|
691
|
+
# โ
Good: Sequential connections
|
692
|
+
orders_client = DhanHQ::WS::Orders.connect { |order| puts order.order_no }
|
693
|
+
orders_client.stop
|
694
|
+
sleep(2) # Wait between connections
|
695
|
+
|
696
|
+
market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
|
697
|
+
market_client.stop
|
698
|
+
sleep(2)
|
699
|
+
|
700
|
+
# โ Bad: Multiple simultaneous connections
|
701
|
+
orders_client = DhanHQ::WS::Orders.connect { |order| puts order.order_no }
|
702
|
+
market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
|
703
|
+
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) { |depth| puts depth[:symbol] }
|
704
|
+
```
|
705
|
+
|
706
|
+
### Graceful Shutdown
|
707
|
+
|
708
|
+
```ruby
|
709
|
+
# Disconnect all WebSocket connections
|
710
|
+
DhanHQ::WS.disconnect_all_local!
|
711
|
+
|
712
|
+
# Or disconnect individual clients
|
713
|
+
orders_client.stop
|
714
|
+
market_client.stop
|
715
|
+
depth_client.stop
|
716
|
+
```
|
717
|
+
|
718
|
+
### Connection Status Monitoring
|
719
|
+
|
720
|
+
```ruby
|
721
|
+
# Check connection status
|
722
|
+
puts "Orders connected: #{orders_client.connected?}"
|
723
|
+
puts "Market connected: #{market_client.connected?}"
|
724
|
+
puts "Depth connected: #{depth_client.connected?}"
|
725
|
+
|
726
|
+
# Get subscription info
|
727
|
+
puts "Market subscriptions: #{market_client.subscriptions}"
|
728
|
+
puts "Depth subscriptions: #{depth_client.subscriptions}"
|
729
|
+
```
|
730
|
+
|
731
|
+
## Best Practices
|
732
|
+
|
733
|
+
### 1. Security
|
734
|
+
|
735
|
+
- **โ
Sensitive information is automatically sanitized** from logs
|
736
|
+
- **โ
Use environment variables** for credentials
|
737
|
+
- **โ
Never log access tokens** or client IDs
|
738
|
+
|
739
|
+
```ruby
|
740
|
+
# โ
Good: Environment variables
|
741
|
+
DhanHQ.configure do |config|
|
742
|
+
config.client_id = ENV["CLIENT_ID"]
|
743
|
+
config.access_token = ENV["ACCESS_TOKEN"]
|
744
|
+
end
|
745
|
+
|
746
|
+
# โ Bad: Hardcoded credentials
|
747
|
+
DhanHQ.configure do |config|
|
748
|
+
config.client_id = "your_client_id"
|
749
|
+
config.access_token = "your_access_token"
|
750
|
+
end
|
751
|
+
```
|
752
|
+
|
753
|
+
### 2. Error Handling
|
754
|
+
|
755
|
+
```ruby
|
756
|
+
client = DhanHQ::WS::Orders.client
|
757
|
+
|
758
|
+
client.on(:error) do |error|
|
759
|
+
Rails.logger.error "WebSocket error: #{error}"
|
760
|
+
# Implement retry logic or alerting
|
761
|
+
end
|
762
|
+
|
763
|
+
client.on(:close) do |close_info|
|
764
|
+
Rails.logger.warn "WebSocket closed: #{close_info[:code]} - #{close_info[:reason]}"
|
765
|
+
# Handle disconnection
|
766
|
+
end
|
767
|
+
|
768
|
+
client.start
|
769
|
+
```
|
770
|
+
|
771
|
+
### 3. Resource Management
|
772
|
+
|
773
|
+
```ruby
|
774
|
+
# In Rails application
|
775
|
+
class ApplicationController < ActionController::Base
|
776
|
+
around_action :ensure_websocket_cleanup
|
777
|
+
|
778
|
+
private
|
779
|
+
|
780
|
+
def ensure_websocket_cleanup
|
781
|
+
yield
|
782
|
+
ensure
|
783
|
+
DhanHQ::WS.disconnect_all_local!
|
784
|
+
end
|
785
|
+
end
|
786
|
+
```
|
787
|
+
|
788
|
+
### 4. Thread Safety
|
789
|
+
|
790
|
+
All WebSocket connections are thread-safe and can be used in Rails applications:
|
791
|
+
|
792
|
+
```ruby
|
793
|
+
# Safe to use in Rails controllers, jobs, etc.
|
794
|
+
class MarketDataController < ApplicationController
|
795
|
+
def stream
|
796
|
+
DhanHQ::WS.connect(mode: :ticker) do |tick|
|
797
|
+
# This is thread-safe
|
798
|
+
Rails.logger.info "Received tick: #{tick[:symbol]}"
|
799
|
+
end
|
800
|
+
end
|
801
|
+
end
|
802
|
+
```
|
803
|
+
|
804
|
+
## Troubleshooting
|
805
|
+
|
806
|
+
### Common Issues
|
807
|
+
|
808
|
+
1. **429 Rate Limiting Errors**
|
809
|
+
- Use sequential connections instead of simultaneous ones
|
810
|
+
- Wait 2-5 seconds between connection attempts
|
811
|
+
- The client automatically implements 60-second cool-off periods
|
812
|
+
|
813
|
+
2. **"Unable to locate instrument" Warnings**
|
814
|
+
- Use correct exchange segments and security IDs
|
815
|
+
- Find instruments using `DhanHQ::Models::Instrument.by_segment()`
|
816
|
+
- Use the proper symbol format for Market Depth WebSocket
|
817
|
+
|
818
|
+
3. **Connection Failures**
|
819
|
+
- Check credentials and network connectivity
|
820
|
+
- Verify WebSocket URLs are correct
|
821
|
+
- Check for firewall or proxy issues
|
822
|
+
|
823
|
+
### Debugging
|
824
|
+
|
825
|
+
Enable debug logging:
|
826
|
+
|
827
|
+
```ruby
|
828
|
+
# Set log level to debug
|
829
|
+
DhanHQ.logger.level = Logger::DEBUG
|
830
|
+
|
831
|
+
# Or use Rails logger
|
832
|
+
DhanHQ.logger = Rails.logger
|
833
|
+
```
|
834
|
+
|
835
|
+
### Monitoring
|
836
|
+
|
837
|
+
Monitor WebSocket connections:
|
838
|
+
|
839
|
+
```ruby
|
840
|
+
# Check connection status
|
841
|
+
puts "Orders connected: #{orders_client.connected?}"
|
842
|
+
puts "Market connected: #{market_client.connected?}"
|
843
|
+
puts "Depth connected: #{depth_client.connected?}"
|
844
|
+
|
845
|
+
# Get subscription info
|
846
|
+
puts "Market subscriptions: #{market_client.subscriptions}"
|
847
|
+
puts "Depth subscriptions: #{depth_client.subscriptions}"
|
848
|
+
```
|
849
|
+
|
850
|
+
## Examples
|
851
|
+
|
852
|
+
The gem includes comprehensive examples in the `examples/` directory:
|
853
|
+
|
854
|
+
- `market_feed_example.rb` - Market Feed WebSocket with major indices
|
855
|
+
- `order_update_example.rb` - Order Update WebSocket with event handling
|
856
|
+
- `market_depth_example.rb` - Market Depth WebSocket with RELIANCE and TCS
|
857
|
+
- `comprehensive_websocket_examples.rb` - All three WebSocket types
|
858
|
+
|
859
|
+
Run examples:
|
860
|
+
|
861
|
+
```bash
|
862
|
+
# Individual examples
|
863
|
+
bundle exec ruby examples/market_feed_example.rb
|
864
|
+
bundle exec ruby examples/order_update_example.rb
|
865
|
+
bundle exec ruby examples/market_depth_example.rb
|
866
|
+
|
867
|
+
# Comprehensive example
|
868
|
+
bundle exec ruby examples/comprehensive_websocket_examples.rb
|
869
|
+
```
|
870
|
+
|
871
|
+
This comprehensive WebSocket integration provides everything needed for real-time trading applications with DhanHQ, featuring improved security, reliability, and ease of use.
|