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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +7 -0
  3. data/GUIDE.md +215 -73
  4. data/README.md +416 -132
  5. data/README1.md +267 -26
  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 +871 -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/contracts/expired_options_data_contract.rb +103 -0
  19. data/lib/DhanHQ/contracts/trade_contract.rb +70 -0
  20. data/lib/DhanHQ/errors.rb +2 -0
  21. data/lib/DhanHQ/models/expired_options_data.rb +331 -0
  22. data/lib/DhanHQ/models/instrument.rb +96 -2
  23. data/lib/DhanHQ/models/order_update.rb +235 -0
  24. data/lib/DhanHQ/models/trade.rb +118 -31
  25. data/lib/DhanHQ/resources/expired_options_data.rb +22 -0
  26. data/lib/DhanHQ/version.rb +1 -1
  27. data/lib/DhanHQ/ws/base_connection.rb +249 -0
  28. data/lib/DhanHQ/ws/connection.rb +2 -2
  29. data/lib/DhanHQ/ws/decoder.rb +3 -3
  30. data/lib/DhanHQ/ws/market_depth/client.rb +376 -0
  31. data/lib/DhanHQ/ws/market_depth/decoder.rb +131 -0
  32. data/lib/DhanHQ/ws/market_depth.rb +74 -0
  33. data/lib/DhanHQ/ws/orders/client.rb +175 -11
  34. data/lib/DhanHQ/ws/orders/connection.rb +40 -81
  35. data/lib/DhanHQ/ws/orders.rb +28 -0
  36. data/lib/DhanHQ/ws/segments.rb +18 -2
  37. data/lib/DhanHQ/ws.rb +3 -2
  38. data/lib/dhan_hq.rb +5 -0
  39. 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.