DhanHQ 2.1.5 โ†’ 2.1.7

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.
Files changed (41) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/GUIDE.md +221 -31
  4. data/README.md +453 -126
  5. data/README1.md +293 -30
  6. data/docs/live_order_updates.md +319 -0
  7. data/docs/rails_websocket_integration.md +847 -0
  8. data/docs/standalone_ruby_websocket_integration.md +1588 -0
  9. data/docs/websocket_integration.md +918 -0
  10. data/examples/comprehensive_websocket_examples.rb +148 -0
  11. data/examples/instrument_finder_test.rb +195 -0
  12. data/examples/live_order_updates.rb +118 -0
  13. data/examples/market_depth_example.rb +144 -0
  14. data/examples/market_feed_example.rb +81 -0
  15. data/examples/order_update_example.rb +105 -0
  16. data/examples/trading_fields_example.rb +215 -0
  17. data/lib/DhanHQ/configuration.rb +16 -1
  18. data/lib/DhanHQ/constants.rb +16 -0
  19. data/lib/DhanHQ/contracts/expired_options_data_contract.rb +103 -0
  20. data/lib/DhanHQ/contracts/trade_contract.rb +70 -0
  21. data/lib/DhanHQ/errors.rb +2 -0
  22. data/lib/DhanHQ/models/expired_options_data.rb +331 -0
  23. data/lib/DhanHQ/models/instrument.rb +114 -4
  24. data/lib/DhanHQ/models/instrument_helpers.rb +141 -0
  25. data/lib/DhanHQ/models/order_update.rb +235 -0
  26. data/lib/DhanHQ/models/trade.rb +118 -31
  27. data/lib/DhanHQ/resources/expired_options_data.rb +22 -0
  28. data/lib/DhanHQ/version.rb +1 -1
  29. data/lib/DhanHQ/ws/base_connection.rb +249 -0
  30. data/lib/DhanHQ/ws/connection.rb +2 -2
  31. data/lib/DhanHQ/ws/decoder.rb +3 -3
  32. data/lib/DhanHQ/ws/market_depth/client.rb +376 -0
  33. data/lib/DhanHQ/ws/market_depth/decoder.rb +131 -0
  34. data/lib/DhanHQ/ws/market_depth.rb +74 -0
  35. data/lib/DhanHQ/ws/orders/client.rb +175 -11
  36. data/lib/DhanHQ/ws/orders/connection.rb +40 -81
  37. data/lib/DhanHQ/ws/orders.rb +28 -0
  38. data/lib/DhanHQ/ws/segments.rb +18 -2
  39. data/lib/DhanHQ/ws.rb +3 -2
  40. data/lib/dhan_hq.rb +5 -0
  41. metadata +36 -1
@@ -0,0 +1,918 @@
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
+ ### Instrument Convenience Methods
515
+
516
+ The Instrument model provides convenient instance methods that automatically use the instrument's attributes (`security_id`, `exchange_segment`, `instrument`) to fetch market data. This eliminates the need to manually construct parameters for each API call.
517
+
518
+ ```ruby
519
+ # Find an instrument first
520
+ instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
521
+ reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
522
+
523
+ # Market Feed Methods - automatically uses instrument's attributes
524
+ ltp_data = instrument.ltp # Last traded price
525
+ ohlc_data = instrument.ohlc # OHLC data
526
+ quote_data = instrument.quote # Full quote depth
527
+
528
+ # Historical Data Methods
529
+ daily_data = instrument.daily(
530
+ from_date: "2024-01-01",
531
+ to_date: "2024-01-31",
532
+ expiry_code: 0 # Optional, only for derivatives
533
+ )
534
+
535
+ intraday_data = instrument.intraday(
536
+ from_date: "2024-09-11",
537
+ to_date: "2024-09-15",
538
+ interval: "15" # 1, 5, 15, 25, or 60 minutes
539
+ )
540
+
541
+ # Option Chain Methods (for F&O instruments)
542
+ fn_instrument = DhanHQ::Models::Instrument.find("NSE_FNO", "NIFTY")
543
+ expiries = fn_instrument.expiry_list # Get all available expiries
544
+ chain = fn_instrument.option_chain(expiry: "2024-02-29") # Get option chain for specific expiry
545
+ ```
546
+
547
+ **Available Instance Methods:**
548
+
549
+ | Method | Description | Underlying API |
550
+ | ----------------------------------------------------------------- | -------------------------------- | ----------------------------------------------- |
551
+ | `instrument.ltp` | Fetches last traded price | `DhanHQ::Models::MarketFeed.ltp` |
552
+ | `instrument.ohlc` | Fetches OHLC data | `DhanHQ::Models::MarketFeed.ohlc` |
553
+ | `instrument.quote` | Fetches full quote depth | `DhanHQ::Models::MarketFeed.quote` |
554
+ | `instrument.daily(from_date:, to_date:, **options)` | Fetches daily historical data | `DhanHQ::Models::HistoricalData.daily` |
555
+ | `instrument.intraday(from_date:, to_date:, interval:, **options)` | Fetches intraday historical data | `DhanHQ::Models::HistoricalData.intraday` |
556
+ | `instrument.expiry_list` | Fetches expiry list | `DhanHQ::Models::OptionChain.fetch_expiry_list` |
557
+ | `instrument.option_chain(expiry:)` | Fetches option chain | `DhanHQ::Models::OptionChain.fetch` |
558
+
559
+ All methods automatically extract `security_id`, `exchange_segment`, and `instrument` from the instrument instance, making it easier to work with market data without manually managing these parameters.
560
+
561
+ ## Rails Integration
562
+
563
+ ### Basic Rails Integration
564
+
565
+ ```ruby
566
+ # config/initializers/dhan_hq.rb
567
+ DhanHQ.configure do |config|
568
+ config.client_id = Rails.application.credentials.dhanhq[:client_id]
569
+ config.access_token = Rails.application.credentials.dhanhq[:access_token]
570
+ config.ws_user_type = Rails.application.credentials.dhanhq[:ws_user_type]
571
+ end
572
+ ```
573
+
574
+ ### Service Class Pattern
575
+
576
+ ```ruby
577
+ # app/services/market_data_service.rb
578
+ class MarketDataService
579
+ def initialize
580
+ @market_client = nil
581
+ end
582
+
583
+ def start_market_feed
584
+ @market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
585
+ process_market_data(tick)
586
+ end
587
+
588
+ # Subscribe to indices
589
+ @market_client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
590
+ @market_client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
591
+ @market_client.subscribe_one(segment: "IDX_I", security_id: "29") # NIFTYIT
592
+ @market_client.subscribe_one(segment: "IDX_I", security_id: "51") # SENSEX
593
+ end
594
+
595
+ def stop_market_feed
596
+ @market_client&.stop
597
+ @market_client = nil
598
+ end
599
+
600
+ private
601
+
602
+ def process_market_data(tick)
603
+ # Store in database
604
+ MarketData.create!(
605
+ segment: tick[:segment],
606
+ security_id: tick[:security_id],
607
+ ltp: tick[:ltp],
608
+ timestamp: tick[:ts] ? Time.at(tick[:ts]) : Time.now
609
+ )
610
+
611
+ # Broadcast via ActionCable
612
+ ActionCable.server.broadcast(
613
+ "market_data_#{tick[:segment]}",
614
+ {
615
+ segment: tick[:segment],
616
+ security_id: tick[:security_id],
617
+ ltp: tick[:ltp],
618
+ timestamp: tick[:ts]
619
+ }
620
+ )
621
+ end
622
+ end
623
+ ```
624
+
625
+ ### Order Update Service
626
+
627
+ ```ruby
628
+ # app/services/order_update_service.rb
629
+ class OrderUpdateService
630
+ def initialize
631
+ @orders_client = nil
632
+ end
633
+
634
+ def start_order_updates
635
+ @orders_client = DhanHQ::WS::Orders.connect do |order_update|
636
+ process_order_update(order_update)
637
+ end
638
+
639
+ # Add error handling
640
+ @orders_client.on(:error) do |error|
641
+ Rails.logger.error "Order WebSocket error: #{error}"
642
+ end
643
+
644
+ @orders_client.on(:close) do |close_info|
645
+ Rails.logger.warn "Order WebSocket closed: #{close_info[:code]}"
646
+ end
647
+ end
648
+
649
+ def stop_order_updates
650
+ @orders_client&.stop
651
+ @orders_client = nil
652
+ end
653
+
654
+ private
655
+
656
+ def process_order_update(order_update)
657
+ # Update database
658
+ order = Order.find_by(order_no: order_update.order_no)
659
+ if order
660
+ order.update!(
661
+ status: order_update.status,
662
+ traded_qty: order_update.traded_qty,
663
+ avg_price: order_update.avg_traded_price
664
+ )
665
+
666
+ # Broadcast to user
667
+ ActionCable.server.broadcast(
668
+ "order_updates_#{order.user_id}",
669
+ {
670
+ order_no: order_update.order_no,
671
+ status: order_update.status,
672
+ traded_qty: order_update.traded_qty,
673
+ execution_percentage: order_update.execution_percentage
674
+ }
675
+ )
676
+ end
677
+ end
678
+ end
679
+ ```
680
+
681
+ ### Background Job Processing
682
+
683
+ ```ruby
684
+ # app/jobs/process_market_data_job.rb
685
+ class ProcessMarketDataJob < ApplicationJob
686
+ queue_as :market_data
687
+
688
+ def perform(market_data)
689
+ # Process market data
690
+ MarketData.create!(
691
+ segment: market_data[:segment],
692
+ security_id: market_data[:security_id],
693
+ ltp: market_data[:ltp],
694
+ volume: market_data[:vol],
695
+ timestamp: Time.at(market_data[:ts])
696
+ )
697
+
698
+ # Update cache
699
+ Rails.cache.write(
700
+ "market_data_#{market_data[:segment]}:#{market_data[:security_id]}",
701
+ market_data,
702
+ expires_in: 1.minute
703
+ )
704
+ end
705
+ end
706
+
707
+ # In your WebSocket handler
708
+ DhanHQ::WS.connect(mode: :ticker) do |tick|
709
+ ProcessMarketDataJob.perform_later(tick)
710
+ end
711
+ ```
712
+
713
+ ### Application Controller Integration
714
+
715
+ ```ruby
716
+ # app/controllers/application_controller.rb
717
+ class ApplicationController < ActionController::Base
718
+ around_action :ensure_websocket_cleanup
719
+
720
+ private
721
+
722
+ def ensure_websocket_cleanup
723
+ yield
724
+ ensure
725
+ # Clean up any stray WebSocket connections
726
+ DhanHQ::WS.disconnect_all_local!
727
+ end
728
+ end
729
+ ```
730
+
731
+ ## Connection Management
732
+
733
+ ### Rate Limiting Protection
734
+
735
+ DhanHQ allows up to 5 WebSocket connections per user. To avoid 429 errors:
736
+
737
+ ```ruby
738
+ # โœ… Good: Sequential connections
739
+ orders_client = DhanHQ::WS::Orders.connect { |order| puts order.order_no }
740
+ orders_client.stop
741
+ sleep(2) # Wait between connections
742
+
743
+ market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
744
+ market_client.stop
745
+ sleep(2)
746
+
747
+ # โŒ Bad: Multiple simultaneous connections
748
+ orders_client = DhanHQ::WS::Orders.connect { |order| puts order.order_no }
749
+ market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
750
+ depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) { |depth| puts depth[:symbol] }
751
+ ```
752
+
753
+ ### Graceful Shutdown
754
+
755
+ ```ruby
756
+ # Disconnect all WebSocket connections
757
+ DhanHQ::WS.disconnect_all_local!
758
+
759
+ # Or disconnect individual clients
760
+ orders_client.stop
761
+ market_client.stop
762
+ depth_client.stop
763
+ ```
764
+
765
+ ### Connection Status Monitoring
766
+
767
+ ```ruby
768
+ # Check connection status
769
+ puts "Orders connected: #{orders_client.connected?}"
770
+ puts "Market connected: #{market_client.connected?}"
771
+ puts "Depth connected: #{depth_client.connected?}"
772
+
773
+ # Get subscription info
774
+ puts "Market subscriptions: #{market_client.subscriptions}"
775
+ puts "Depth subscriptions: #{depth_client.subscriptions}"
776
+ ```
777
+
778
+ ## Best Practices
779
+
780
+ ### 1. Security
781
+
782
+ - **โœ… Sensitive information is automatically sanitized** from logs
783
+ - **โœ… Use environment variables** for credentials
784
+ - **โœ… Never log access tokens** or client IDs
785
+
786
+ ```ruby
787
+ # โœ… Good: Environment variables
788
+ DhanHQ.configure do |config|
789
+ config.client_id = ENV["CLIENT_ID"]
790
+ config.access_token = ENV["ACCESS_TOKEN"]
791
+ end
792
+
793
+ # โŒ Bad: Hardcoded credentials
794
+ DhanHQ.configure do |config|
795
+ config.client_id = "your_client_id"
796
+ config.access_token = "your_access_token"
797
+ end
798
+ ```
799
+
800
+ ### 2. Error Handling
801
+
802
+ ```ruby
803
+ client = DhanHQ::WS::Orders.client
804
+
805
+ client.on(:error) do |error|
806
+ Rails.logger.error "WebSocket error: #{error}"
807
+ # Implement retry logic or alerting
808
+ end
809
+
810
+ client.on(:close) do |close_info|
811
+ Rails.logger.warn "WebSocket closed: #{close_info[:code]} - #{close_info[:reason]}"
812
+ # Handle disconnection
813
+ end
814
+
815
+ client.start
816
+ ```
817
+
818
+ ### 3. Resource Management
819
+
820
+ ```ruby
821
+ # In Rails application
822
+ class ApplicationController < ActionController::Base
823
+ around_action :ensure_websocket_cleanup
824
+
825
+ private
826
+
827
+ def ensure_websocket_cleanup
828
+ yield
829
+ ensure
830
+ DhanHQ::WS.disconnect_all_local!
831
+ end
832
+ end
833
+ ```
834
+
835
+ ### 4. Thread Safety
836
+
837
+ All WebSocket connections are thread-safe and can be used in Rails applications:
838
+
839
+ ```ruby
840
+ # Safe to use in Rails controllers, jobs, etc.
841
+ class MarketDataController < ApplicationController
842
+ def stream
843
+ DhanHQ::WS.connect(mode: :ticker) do |tick|
844
+ # This is thread-safe
845
+ Rails.logger.info "Received tick: #{tick[:symbol]}"
846
+ end
847
+ end
848
+ end
849
+ ```
850
+
851
+ ## Troubleshooting
852
+
853
+ ### Common Issues
854
+
855
+ 1. **429 Rate Limiting Errors**
856
+ - Use sequential connections instead of simultaneous ones
857
+ - Wait 2-5 seconds between connection attempts
858
+ - The client automatically implements 60-second cool-off periods
859
+
860
+ 2. **"Unable to locate instrument" Warnings**
861
+ - Use correct exchange segments and security IDs
862
+ - Find instruments using `DhanHQ::Models::Instrument.by_segment()`
863
+ - Use the proper symbol format for Market Depth WebSocket
864
+
865
+ 3. **Connection Failures**
866
+ - Check credentials and network connectivity
867
+ - Verify WebSocket URLs are correct
868
+ - Check for firewall or proxy issues
869
+
870
+ ### Debugging
871
+
872
+ Enable debug logging:
873
+
874
+ ```ruby
875
+ # Set log level to debug
876
+ DhanHQ.logger.level = Logger::DEBUG
877
+
878
+ # Or use Rails logger
879
+ DhanHQ.logger = Rails.logger
880
+ ```
881
+
882
+ ### Monitoring
883
+
884
+ Monitor WebSocket connections:
885
+
886
+ ```ruby
887
+ # Check connection status
888
+ puts "Orders connected: #{orders_client.connected?}"
889
+ puts "Market connected: #{market_client.connected?}"
890
+ puts "Depth connected: #{depth_client.connected?}"
891
+
892
+ # Get subscription info
893
+ puts "Market subscriptions: #{market_client.subscriptions}"
894
+ puts "Depth subscriptions: #{depth_client.subscriptions}"
895
+ ```
896
+
897
+ ## Examples
898
+
899
+ The gem includes comprehensive examples in the `examples/` directory:
900
+
901
+ - `market_feed_example.rb` - Market Feed WebSocket with major indices
902
+ - `order_update_example.rb` - Order Update WebSocket with event handling
903
+ - `market_depth_example.rb` - Market Depth WebSocket with RELIANCE and TCS
904
+ - `comprehensive_websocket_examples.rb` - All three WebSocket types
905
+
906
+ Run examples:
907
+
908
+ ```bash
909
+ # Individual examples
910
+ bundle exec ruby examples/market_feed_example.rb
911
+ bundle exec ruby examples/order_update_example.rb
912
+ bundle exec ruby examples/market_depth_example.rb
913
+
914
+ # Comprehensive example
915
+ bundle exec ruby examples/comprehensive_websocket_examples.rb
916
+ ```
917
+
918
+ This comprehensive WebSocket integration provides everything needed for real-time trading applications with DhanHQ, featuring improved security, reliability, and ease of use.