DhanHQ 2.1.10 → 2.2.1

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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +1 -1
  3. data/.rubocop_todo.yml +143 -118
  4. data/CHANGELOG.md +127 -0
  5. data/CODE_REVIEW_ISSUES.md +397 -0
  6. data/FIXES_APPLIED.md +373 -0
  7. data/GUIDE.md +41 -0
  8. data/README.md +55 -0
  9. data/RELEASING.md +60 -0
  10. data/REVIEW_SUMMARY.md +120 -0
  11. data/VERSION_UPDATE.md +82 -0
  12. data/core +0 -0
  13. data/docs/AUTHENTICATION.md +63 -0
  14. data/docs/DATA_API_PARAMETERS.md +278 -0
  15. data/docs/PR_2.2.0.md +48 -0
  16. data/docs/RELEASE_GUIDE.md +492 -0
  17. data/docs/TESTING_GUIDE.md +1514 -0
  18. data/docs/live_order_updates.md +25 -1
  19. data/docs/rails_integration.md +29 -0
  20. data/docs/websocket_integration.md +22 -0
  21. data/lib/DhanHQ/client.rb +65 -9
  22. data/lib/DhanHQ/configuration.rb +27 -0
  23. data/lib/DhanHQ/constants.rb +1 -1
  24. data/lib/DhanHQ/contracts/place_order_contract.rb +51 -0
  25. data/lib/DhanHQ/core/base_model.rb +17 -10
  26. data/lib/DhanHQ/errors.rb +4 -0
  27. data/lib/DhanHQ/helpers/request_helper.rb +17 -2
  28. data/lib/DhanHQ/helpers/response_helper.rb +34 -13
  29. data/lib/DhanHQ/models/expired_options_data.rb +0 -6
  30. data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
  31. data/lib/DhanHQ/models/order.rb +19 -2
  32. data/lib/DhanHQ/models/order_update.rb +0 -4
  33. data/lib/DhanHQ/rate_limiter.rb +40 -6
  34. data/lib/DhanHQ/version.rb +1 -1
  35. data/lib/DhanHQ/ws/client.rb +12 -5
  36. data/lib/DhanHQ/ws/connection.rb +16 -2
  37. data/lib/DhanHQ/ws/market_depth/client.rb +3 -1
  38. data/lib/DhanHQ/ws/market_depth.rb +12 -12
  39. data/lib/DhanHQ/ws/orders/client.rb +76 -11
  40. data/lib/DhanHQ/ws/orders/connection.rb +3 -1
  41. metadata +18 -5
  42. data/lib/DhanHQ/contracts/modify_order_contract_copy.rb +0 -100
@@ -0,0 +1,1514 @@
1
+ # DhanHQ Client Gem - Comprehensive Testing Guide
2
+
3
+ This guide provides interactive testing examples for all features of the DhanHQ client gem. You can use these examples in `bin/console` to test and explore the gem's functionality.
4
+
5
+ ## Table of Contents
6
+
7
+ 1. [Setup & Configuration](#setup--configuration)
8
+ 2. [WebSocket Testing](#websocket-testing)
9
+ - [Market Feed WebSocket](#market-feed-websocket)
10
+ - [Order Update WebSocket](#order-update-websocket)
11
+ - [Market Depth WebSocket](#market-depth-websocket)
12
+ 3. [Model Testing](#model-testing)
13
+ - [Orders](#orders)
14
+ - [Positions](#positions)
15
+ - [Holdings](#holdings)
16
+ - [Funds](#funds)
17
+ - [Trades](#trades)
18
+ - [Profile](#profile)
19
+ - [Market Feed](#market-feed)
20
+ - [Historical Data](#historical-data)
21
+ - [Option Chain](#option-chain)
22
+ - [Instruments](#instruments)
23
+ - [Super Orders](#super-orders)
24
+ - [Forever Orders (GTT)](#forever-orders-gtt)
25
+ - [EDIS](#edis)
26
+ - [Kill Switch](#kill-switch)
27
+ - [Expired Options Data](#expired-options-data)
28
+ - [Margin Calculator](#margin-calculator)
29
+ - [Ledger Entries](#ledger-entries)
30
+ 4. [Validation Contracts Testing](#validation-contracts-testing)
31
+ 5. [Error Handling Testing](#error-handling-testing)
32
+ 6. [Rate Limiting Testing](#rate-limiting-testing)
33
+
34
+ ---
35
+
36
+ ## Setup & Configuration
37
+
38
+ ### Basic Setup in Console
39
+
40
+ ```ruby
41
+ # Start console: bin/console
42
+
43
+ # Configure from environment variables
44
+ DhanHQ.configure_with_env
45
+
46
+ # Or configure manually
47
+ DhanHQ.configure do |config|
48
+ config.client_id = "your_client_id"
49
+ config.access_token = "your_access_token"
50
+ config.ws_user_type = "SELF" # or "PARTNER"
51
+ end
52
+
53
+ # Optional: dynamic token at request time (e.g. from DB or OAuth)
54
+ # DhanHQ.configure do |config|
55
+ # config.client_id = ENV["DHAN_CLIENT_ID"]
56
+ # config.access_token_provider = -> { YourTokenStore.active_token }
57
+ # config.on_token_expired = ->(err) { YourTokenStore.refresh! }
58
+ # end
59
+
60
+ # Set log level for debugging
61
+ DhanHQ.logger.level = Logger::DEBUG
62
+
63
+ # Verify configuration
64
+ puts "Client ID: #{DhanHQ.configuration.client_id}"
65
+ puts "Access Token: #{DhanHQ.configuration.access_token ? 'Set' : 'Not Set'}"
66
+ puts "Token provider: #{DhanHQ.configuration.access_token_provider ? 'Set' : 'Not Set'}"
67
+ ```
68
+
69
+ ### Environment Variables
70
+
71
+ ```ruby
72
+ # Check current environment variables
73
+ puts "CLIENT_ID: #{ENV['CLIENT_ID']}"
74
+ puts "ACCESS_TOKEN: #{ENV['ACCESS_TOKEN'] ? 'Set' : 'Not Set'}"
75
+ puts "DHAN_LOG_LEVEL: #{ENV['DHAN_LOG_LEVEL'] || 'INFO'}"
76
+ puts "DHAN_CONNECT_TIMEOUT: #{ENV['DHAN_CONNECT_TIMEOUT'] || '10'}"
77
+ puts "DHAN_READ_TIMEOUT: #{ENV['DHAN_READ_TIMEOUT'] || '30'}"
78
+ ```
79
+
80
+ ---
81
+
82
+ ## WebSocket Testing
83
+
84
+ ### Market Feed WebSocket
85
+
86
+ #### Basic Ticker Subscription
87
+
88
+ ```ruby
89
+ # Create market feed WebSocket connection
90
+ market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
91
+ timestamp = tick[:ts] ? Time.at(tick[:ts]) : Time.now
92
+ puts "[#{timestamp}] #{tick[:segment]}:#{tick[:security_id]} = ₹#{tick[:ltp]}"
93
+ end
94
+
95
+ # Subscribe to NIFTY (Security ID: 13, Segment: IDX_I)
96
+ market_client.subscribe_one(segment: "IDX_I", security_id: "13")
97
+
98
+ # Subscribe to BANKNIFTY (Security ID: 25, Segment: IDX_I)
99
+ market_client.subscribe_one(segment: "IDX_I", security_id: "25")
100
+
101
+ # Wait for data (in console, you can continue working)
102
+ sleep(10)
103
+
104
+ # Stop connection
105
+ market_client.stop
106
+ ```
107
+
108
+ #### OHLC Subscription
109
+
110
+ ```ruby
111
+ # Create OHLC WebSocket connection
112
+ ohlc_client = DhanHQ::WS.connect(mode: :ohlc) do |data|
113
+ puts "OHLC: #{data[:segment]}:#{data[:security_id]}"
114
+ puts " Open: ₹#{data[:open]}, High: ₹#{data[:high]}, Low: ₹#{data[:low]}, Close: ₹#{data[:close]}"
115
+ end
116
+
117
+ # Subscribe to NIFTY
118
+ ohlc_client.subscribe_one(segment: "IDX_I", security_id: "13")
119
+
120
+ sleep(10)
121
+ ohlc_client.stop
122
+ ```
123
+
124
+ #### Quote Subscription (Full Market Depth)
125
+
126
+ ```ruby
127
+ # Create quote WebSocket connection
128
+ quote_client = DhanHQ::WS.connect(mode: :quote) do |data|
129
+ puts "Quote: #{data[:segment]}:#{data[:security_id]}"
130
+ puts " LTP: ₹#{data[:ltp]}"
131
+ puts " Volume: #{data[:volume]}"
132
+ puts " Open Interest: #{data[:oi]}" if data[:oi]
133
+ end
134
+
135
+ # Subscribe to NIFTY
136
+ quote_client.subscribe_one(segment: "IDX_I", security_id: "13")
137
+
138
+ sleep(10)
139
+ quote_client.stop
140
+ ```
141
+
142
+ #### Multiple Subscriptions
143
+
144
+ ```ruby
145
+ # Subscribe to multiple instruments
146
+ market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
147
+ puts "#{tick[:segment]}:#{tick[:security_id]} = ₹#{tick[:ltp]}"
148
+ end
149
+
150
+ # Subscribe to multiple indices
151
+ indices = [
152
+ { segment: "IDX_I", security_id: "13" }, # NIFTY
153
+ { segment: "IDX_I", security_id: "25" }, # BANKNIFTY
154
+ { segment: "IDX_I", security_id: "29" }, # NIFTYIT
155
+ { segment: "IDX_I", security_id: "51" } # SENSEX
156
+ ]
157
+
158
+ indices.each do |idx|
159
+ market_client.subscribe_one(segment: idx[:segment], security_id: idx[:security_id])
160
+ end
161
+
162
+ sleep(15)
163
+ market_client.stop
164
+ ```
165
+
166
+ #### Testing Connection State
167
+
168
+ ```ruby
169
+ market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
170
+ market_client.subscribe_one(segment: "IDX_I", security_id: "13")
171
+
172
+ # Check connection state
173
+ puts "Connected: #{market_client.connected?}"
174
+
175
+ sleep(5)
176
+ market_client.stop
177
+ puts "Connected after stop: #{market_client.connected?}"
178
+ ```
179
+
180
+ ### Order Update WebSocket
181
+
182
+ #### Basic Order Tracking
183
+
184
+ ```ruby
185
+ # Create order update client
186
+ orders_client = DhanHQ::WS::Orders.client
187
+
188
+ # Track all order updates
189
+ orders_client.on(:update) do |order|
190
+ puts "\n📋 Order Update: #{order.order_no}"
191
+ puts " Symbol: #{order.symbol}"
192
+ puts " Status: #{order.status}"
193
+ puts " Quantity: #{order.traded_qty}/#{order.quantity}"
194
+ puts " Price: ₹#{order.price}"
195
+ end
196
+
197
+ # Start monitoring
198
+ orders_client.start
199
+ puts "✅ Order tracking started. Place orders to see updates..."
200
+
201
+ # In console, you can continue working while tracking orders
202
+ # Stop when done
203
+ # orders_client.stop
204
+ ```
205
+
206
+ #### Comprehensive Event Handling
207
+
208
+ ```ruby
209
+ orders_client = DhanHQ::WS::Orders.client
210
+
211
+ # Track status changes
212
+ orders_client.on(:status_change) do |data|
213
+ order = data[:order_update]
214
+ puts "\n🔄 Status Change: #{order.order_no}"
215
+ puts " #{data[:previous_status]} -> #{data[:new_status]}"
216
+ end
217
+
218
+ # Track executions
219
+ orders_client.on(:execution) do |data|
220
+ order = data[:order_update]
221
+ puts "\n💰 Execution: #{order.order_no}"
222
+ puts " Traded: #{data[:previous_traded_qty]} -> #{data[:new_traded_qty]} shares"
223
+ end
224
+
225
+ # Track fully traded orders
226
+ orders_client.on(:order_traded) do |order|
227
+ puts "\n✅ Order Fully Executed: #{order.order_no}"
228
+ puts " Average Price: ₹#{order.avg_traded_price}"
229
+ end
230
+
231
+ # Track rejected orders
232
+ orders_client.on(:order_rejected) do |order|
233
+ puts "\n❌ Order Rejected: #{order.order_no}"
234
+ puts " Reason: #{order.reason_description}"
235
+ end
236
+
237
+ # Track cancelled orders
238
+ orders_client.on(:order_cancelled) do |order|
239
+ puts "\n🚫 Order Cancelled: #{order.order_no}"
240
+ end
241
+
242
+ orders_client.start
243
+ ```
244
+
245
+ #### Querying Tracked Orders
246
+
247
+ ```ruby
248
+ orders_client = DhanHQ::WS::Orders.client
249
+ orders_client.start
250
+
251
+ # Get all tracked orders
252
+ all_orders = orders_client.all_orders
253
+ puts "Total tracked orders: #{all_orders.size}"
254
+
255
+ # Get specific order
256
+ if all_orders.any?
257
+ order_no = all_orders.keys.first
258
+ order = orders_client.order(order_no)
259
+ puts "Order #{order_no}:"
260
+ puts " Status: #{order.status}"
261
+ puts " Symbol: #{order.symbol}"
262
+ puts " Execution: #{order.execution_percentage}%"
263
+ end
264
+
265
+ # Check if order is tracked
266
+ order_no = "112111182045"
267
+ if orders_client.tracked?(order_no)
268
+ puts "Order #{order_no} is being tracked"
269
+ else
270
+ puts "Order #{order_no} is not tracked"
271
+ end
272
+ ```
273
+
274
+ ### Market Depth WebSocket
275
+
276
+ #### Basic Market Depth
277
+
278
+ ```ruby
279
+ # First, find instruments
280
+ reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
281
+ tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
282
+
283
+ # Prepare symbols
284
+ symbols = []
285
+ if reliance
286
+ symbols << {
287
+ symbol: "RELIANCE",
288
+ exchange_segment: reliance.exchange_segment,
289
+ security_id: reliance.security_id
290
+ }
291
+ end
292
+
293
+ if tcs
294
+ symbols << {
295
+ symbol: "TCS",
296
+ exchange_segment: tcs.exchange_segment,
297
+ security_id: tcs.security_id
298
+ }
299
+ end
300
+
301
+ # Create market depth client
302
+ depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) do |depth_data|
303
+ puts "\n📊 Market Depth: #{depth_data[:symbol]}"
304
+ puts " Best Bid: ₹#{depth_data[:best_bid]}"
305
+ puts " Best Ask: ₹#{depth_data[:best_ask]}"
306
+ puts " Spread: ₹#{depth_data[:spread]}"
307
+ puts " Bid Levels: #{depth_data[:bids].size}"
308
+ puts " Ask Levels: #{depth_data[:asks].size}"
309
+ end
310
+
311
+ sleep(10)
312
+ depth_client.stop
313
+ ```
314
+
315
+ ---
316
+
317
+ ## Model Testing
318
+
319
+ ### Orders
320
+
321
+ #### Place Order
322
+
323
+ ```ruby
324
+ # Place a market order
325
+ order = DhanHQ::Models::Order.place(
326
+ dhan_client_id: "1000000003",
327
+ transaction_type: "BUY",
328
+ exchange_segment: "NSE_EQ",
329
+ product_type: "INTRADAY",
330
+ order_type: "MARKET",
331
+ validity: "DAY",
332
+ security_id: "11536", # TCS
333
+ quantity: 1
334
+ )
335
+
336
+ puts "Order placed: #{order.order_id}"
337
+ puts "Status: #{order.order_status}"
338
+ ```
339
+
340
+ #### Place Limit Order
341
+
342
+ ```ruby
343
+ # Place a limit order
344
+ order = DhanHQ::Models::Order.place(
345
+ dhan_client_id: "1000000003",
346
+ transaction_type: "BUY",
347
+ exchange_segment: "NSE_EQ",
348
+ product_type: "INTRADAY",
349
+ order_type: "LIMIT",
350
+ validity: "DAY",
351
+ security_id: "11536",
352
+ quantity: 1,
353
+ price: 3500.0
354
+ )
355
+
356
+ puts "Order ID: #{order.order_id}"
357
+ ```
358
+
359
+ #### Place Stop Loss Order
360
+
361
+ ```ruby
362
+ # Place stop loss order
363
+ order = DhanHQ::Models::Order.place(
364
+ dhan_client_id: "1000000003",
365
+ transaction_type: "BUY",
366
+ exchange_segment: "NSE_EQ",
367
+ product_type: "INTRADAY",
368
+ order_type: "STOPLOSS",
369
+ validity: "DAY",
370
+ security_id: "11536",
371
+ quantity: 1,
372
+ price: 3500.0,
373
+ trigger_price: 3450.0
374
+ )
375
+
376
+ puts "Order ID: #{order.order_id}"
377
+ ```
378
+
379
+ #### Find Order
380
+
381
+ ```ruby
382
+ # Find order by ID
383
+ order = DhanHQ::Models::Order.find("112111182045")
384
+ puts "Order Status: #{order.order_status}"
385
+ puts "Symbol: #{order.trading_symbol}"
386
+ puts "Quantity: #{order.quantity}"
387
+ puts "Traded Qty: #{order.traded_qty}"
388
+ ```
389
+
390
+ #### Find Order by Correlation ID
391
+
392
+ ```ruby
393
+ # Find order by correlation ID
394
+ correlation_id = "my-unique-id-123"
395
+ order = DhanHQ::Models::Order.find_by_correlation(correlation_id)
396
+ if order
397
+ puts "Order found: #{order.order_id}"
398
+ else
399
+ puts "Order not found"
400
+ end
401
+ ```
402
+
403
+ #### Get All Orders
404
+
405
+ ```ruby
406
+ # Get all orders for the day
407
+ orders = DhanHQ::Models::Order.all
408
+ puts "Total orders: #{orders.size}"
409
+
410
+ # Filter pending orders
411
+ pending = orders.select { |o| o.order_status == "PENDING" }
412
+ puts "Pending orders: #{pending.size}"
413
+
414
+ # Filter executed orders
415
+ executed = orders.select { |o| o.order_status == "TRADED" }
416
+ puts "Executed orders: #{executed.size}"
417
+ ```
418
+
419
+ #### Modify Order
420
+
421
+ ```ruby
422
+ # Find order
423
+ order = DhanHQ::Models::Order.find("112111182045")
424
+
425
+ # Modify price and quantity
426
+ if order.modify(price: 3501.0, quantity: 2)
427
+ puts "Order modified successfully"
428
+ order.refresh
429
+ puts "New price: #{order.price}"
430
+ puts "New quantity: #{order.quantity}"
431
+ else
432
+ puts "Order modification failed"
433
+ end
434
+ ```
435
+
436
+ #### Cancel Order
437
+
438
+ ```ruby
439
+ # Cancel order
440
+ order = DhanHQ::Models::Order.find("112111182045")
441
+ if order.cancel
442
+ puts "Order cancelled successfully"
443
+ else
444
+ puts "Order cancellation failed"
445
+ end
446
+ ```
447
+
448
+ #### Slice Order
449
+
450
+ ```ruby
451
+ # Slice order into multiple smaller orders
452
+ order = DhanHQ::Models::Order.find("112111182045")
453
+
454
+ # Slice into 3 orders of 10 shares each
455
+ sliced_orders = order.slice_order(
456
+ slice_count: 3,
457
+ slice_size: 10,
458
+ price: 3500.0
459
+ )
460
+
461
+ puts "Created #{sliced_orders.size} sliced orders"
462
+ sliced_orders.each do |sliced_order|
463
+ puts " Order ID: #{sliced_order.order_id}, Quantity: #{sliced_order.quantity}"
464
+ end
465
+ ```
466
+
467
+ #### Create Order (ActiveRecord-style)
468
+
469
+ ```ruby
470
+ # Create order instance
471
+ order = DhanHQ::Models::Order.new(
472
+ dhan_client_id: "1000000003",
473
+ transaction_type: "BUY",
474
+ exchange_segment: "NSE_EQ",
475
+ product_type: "INTRADAY",
476
+ order_type: "MARKET",
477
+ validity: "DAY",
478
+ security_id: "11536",
479
+ quantity: 1
480
+ )
481
+
482
+ # Save (places order)
483
+ if order.save
484
+ puts "Order placed: #{order.order_id}"
485
+ else
486
+ puts "Order placement failed"
487
+ end
488
+ ```
489
+
490
+ ### Positions
491
+
492
+ #### Get All Positions
493
+
494
+ ```ruby
495
+ # Get all positions
496
+ positions = DhanHQ::Models::Position.all
497
+ puts "Total positions: #{positions.size}"
498
+
499
+ # Filter by exchange
500
+ nse_positions = positions.select { |p| p.exchange_segment == "NSE_EQ" }
501
+ puts "NSE positions: #{nse_positions.size}"
502
+
503
+ # Filter long positions
504
+ long_positions = positions.select { |p| p.net_qty > 0 }
505
+ puts "Long positions: #{long_positions.size}"
506
+ ```
507
+
508
+ #### Convert Position
509
+
510
+ ```ruby
511
+ # Find position
512
+ position = DhanHQ::Models::Position.all.first
513
+
514
+ # Convert position (e.g., from INTRADAY to DELIVERY)
515
+ if position
516
+ result = position.convert(
517
+ dhan_client_id: "1000000003",
518
+ from_product_type: "INTRADAY",
519
+ to_product_type: "MARGIN",
520
+ quantity: position.net_qty.abs
521
+ )
522
+
523
+ if result
524
+ puts "Position converted successfully"
525
+ else
526
+ puts "Position conversion failed"
527
+ end
528
+ end
529
+ ```
530
+
531
+ ### Holdings
532
+
533
+ #### Get All Holdings
534
+
535
+ ```ruby
536
+ # Get all holdings
537
+ holdings = DhanHQ::Models::Holding.all
538
+ puts "Total holdings: #{holdings.size}"
539
+
540
+ # Display holdings
541
+ holdings.each do |holding|
542
+ puts "#{holding.trading_symbol}: #{holding.quantity} shares"
543
+ puts " Average Price: ₹#{holding.average_price}"
544
+ puts " Current Value: ₹#{holding.current_value}"
545
+ end
546
+ ```
547
+
548
+ ### Funds
549
+
550
+ #### Get Funds
551
+
552
+ ```ruby
553
+ # Get account funds
554
+ funds = DhanHQ::Models::Funds.fetch
555
+ puts "Available Margin: ₹#{funds.available_margin}"
556
+ puts "Collateral: ₹#{funds.collateral}"
557
+ puts "Utilized Margin: ₹#{funds.utilized_margin}"
558
+ puts "Opening Balance: ₹#{funds.opening_balance}"
559
+ ```
560
+
561
+ ### Trades
562
+
563
+ #### Get Today's Trades
564
+
565
+ ```ruby
566
+ # Get today's trades
567
+ trades = DhanHQ::Models::Trade.today
568
+ puts "Total trades today: #{trades.size}"
569
+
570
+ trades.each do |trade|
571
+ puts "#{trade.trading_symbol}: #{trade.traded_qty} @ ₹#{trade.traded_price}"
572
+ end
573
+ ```
574
+
575
+ #### Get Trade History
576
+
577
+ ```ruby
578
+ # Get trade history
579
+ from_date = Date.today - 7
580
+ to_date = Date.today
581
+
582
+ trades = DhanHQ::Models::Trade.history(from_date: from_date, to_date: to_date)
583
+ puts "Total trades: #{trades.size}"
584
+ ```
585
+
586
+ #### Find Trades by Order ID
587
+
588
+ ```ruby
589
+ # Find trades for specific order
590
+ order_id = "112111182045"
591
+ trades = DhanHQ::Models::Trade.find_by_order_id(order_id)
592
+ puts "Trades for order #{order_id}: #{trades.size}"
593
+
594
+ trades.each do |trade|
595
+ puts " Trade ID: #{trade.trade_id}"
596
+ puts " Quantity: #{trade.traded_qty}"
597
+ puts " Price: ₹#{trade.traded_price}"
598
+ end
599
+ ```
600
+
601
+ ### Profile
602
+
603
+ #### Get Profile
604
+
605
+ ```ruby
606
+ # Get user profile
607
+ profile = DhanHQ::Models::Profile.fetch
608
+ puts "Name: #{profile.name}"
609
+ puts "Email: #{profile.email}"
610
+ puts "Mobile: #{profile.mobile}"
611
+ puts "PAN: #{profile.pan}"
612
+ puts "Client ID: #{profile.dhan_client_id}"
613
+ ```
614
+
615
+ ### Market Feed
616
+
617
+ #### Get LTP (Last Traded Price)
618
+
619
+ ```ruby
620
+ # Get LTP for multiple instruments
621
+ payload = {
622
+ "NSE_EQ" => [11536, 3456], # TCS, RELIANCE
623
+ "NSE_FNO" => [49081, 49082]
624
+ }
625
+
626
+ response = DhanHQ::Models::MarketFeed.ltp(payload)
627
+ puts "LTP Data:"
628
+ response[:data].each do |segment, instruments|
629
+ instruments.each do |security_id, data|
630
+ puts " #{segment}:#{security_id} = ₹#{data[:last_price]}"
631
+ end
632
+ end
633
+ ```
634
+
635
+ #### Get OHLC Data
636
+
637
+ ```ruby
638
+ # Get OHLC for instruments
639
+ payload = {
640
+ "NSE_EQ" => [11536]
641
+ }
642
+
643
+ response = DhanHQ::Models::MarketFeed.ohlc(payload)
644
+ tcs_data = response[:data]["NSE_EQ"]["11536"]
645
+ puts "TCS OHLC:"
646
+ puts " Open: ₹#{tcs_data[:ohlc][:open]}"
647
+ puts " High: ₹#{tcs_data[:ohlc][:high]}"
648
+ puts " Low: ₹#{tcs_data[:ohlc][:low]}"
649
+ puts " Close: ₹#{tcs_data[:ohlc][:close]}"
650
+ ```
651
+
652
+ #### Get Quote (Full Market Depth)
653
+
654
+ ```ruby
655
+ # Get full quote
656
+ payload = {
657
+ "NSE_FNO" => [49081]
658
+ }
659
+
660
+ response = DhanHQ::Models::MarketFeed.quote(payload)
661
+ quote_data = response[:data]["NSE_FNO"]["49081"]
662
+ puts "Quote Data:"
663
+ puts " LTP: ₹#{quote_data[:ltp]}"
664
+ puts " Volume: #{quote_data[:volume]}"
665
+ puts " Open Interest: #{quote_data[:oi]}"
666
+ puts " Bid Price: ₹#{quote_data[:bid_price]}"
667
+ puts " Ask Price: ₹#{quote_data[:ask_price]}"
668
+ ```
669
+
670
+ ### Historical Data
671
+
672
+ #### Get Daily Historical Data
673
+
674
+ ```ruby
675
+ # Get daily candles
676
+ historical_data = DhanHQ::Models::HistoricalData.daily(
677
+ security_id: "11536",
678
+ exchange_segment: "NSE_EQ",
679
+ from_date: Date.today - 30,
680
+ to_date: Date.today
681
+ )
682
+
683
+ puts "Total candles: #{historical_data.size}"
684
+ historical_data.first(5).each do |candle|
685
+ puts "#{candle[:date]}: O=₹#{candle[:open]}, H=₹#{candle[:high]}, L=₹#{candle[:low]}, C=₹#{candle[:close]}"
686
+ end
687
+ ```
688
+
689
+ #### Get Intraday Historical Data
690
+
691
+ ```ruby
692
+ # Get intraday candles (5-minute interval)
693
+ historical_data = DhanHQ::Models::HistoricalData.intraday(
694
+ security_id: "11536",
695
+ exchange_segment: "NSE_EQ",
696
+ from_date: Date.today,
697
+ to_date: Date.today,
698
+ interval: 5
699
+ )
700
+
701
+ puts "Total candles: #{historical_data.size}"
702
+ historical_data.first(5).each do |candle|
703
+ puts "#{candle[:time]}: O=₹#{candle[:open]}, H=₹#{candle[:high]}, L=₹#{candle[:low]}, C=₹#{candle[:close]}"
704
+ end
705
+ ```
706
+
707
+ ### Option Chain
708
+
709
+ #### Get Expiry List
710
+
711
+ ```ruby
712
+ # Get expiry list for NIFTY
713
+ expiry_list = DhanHQ::Models::OptionChain.fetch_expiry_list(
714
+ underlying_scrip: "NIFTY",
715
+ underlying_seg: "IDX_I"
716
+ )
717
+
718
+ puts "Available expiries:"
719
+ expiry_list.each do |expiry|
720
+ puts " #{expiry[:expiry_date]}"
721
+ end
722
+ ```
723
+
724
+ #### Get Option Chain
725
+
726
+ ```ruby
727
+ # Get option chain for NIFTY
728
+ expiry_date = expiry_list.first[:expiry_date] # Use first expiry from above
729
+
730
+ option_chain = DhanHQ::Models::OptionChain.fetch(
731
+ underlying_scrip: "NIFTY",
732
+ underlying_seg: "IDX_I",
733
+ expiry: expiry_date
734
+ )
735
+
736
+ puts "Option Chain for #{expiry_date}:"
737
+ puts "Total strikes: #{option_chain.size}"
738
+
739
+ # Display first few strikes
740
+ option_chain.first(5).each do |strike|
741
+ puts " Strike: #{strike[:strike_price]}"
742
+ puts " CE: #{strike[:call_option]&.dig(:trading_symbol)}"
743
+ puts " PE: #{strike[:put_option]&.dig(:trading_symbol)}"
744
+ end
745
+ ```
746
+
747
+ ### Instruments
748
+
749
+ #### Find Instrument
750
+
751
+ ```ruby
752
+ # Find instrument by symbol
753
+ tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
754
+ if tcs
755
+ puts "Found: #{tcs.symbol_name}"
756
+ puts "Security ID: #{tcs.security_id}"
757
+ puts "Exchange Segment: #{tcs.exchange_segment}"
758
+ puts "Instrument Type: #{tcs.instrument}"
759
+ end
760
+ ```
761
+
762
+ #### Find Instrument Anywhere
763
+
764
+ ```ruby
765
+ # Search across all exchanges
766
+ reliance = DhanHQ::Models::Instrument.find_anywhere("RELIANCE")
767
+ if reliance
768
+ puts "Found: #{reliance.symbol_name}"
769
+ puts "Exchange: #{reliance.exchange_segment}"
770
+ puts "Security ID: #{reliance.security_id}"
771
+ end
772
+ ```
773
+
774
+ #### Use Instrument Helper Methods
775
+
776
+ ```ruby
777
+ # Get instrument
778
+ instrument = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
779
+
780
+ # Use convenience methods
781
+ if instrument
782
+ # Get LTP
783
+ ltp_data = instrument.ltp
784
+ puts "LTP: ₹#{ltp_data[:last_price]}"
785
+
786
+ # Get OHLC
787
+ ohlc_data = instrument.ohlc
788
+ puts "OHLC: #{ohlc_data[:ohlc]}"
789
+
790
+ # Get Quote
791
+ quote_data = instrument.quote
792
+ puts "Quote: #{quote_data[:ltp]}"
793
+
794
+ # Get Daily Historical Data
795
+ daily_data = instrument.daily(
796
+ from_date: Date.today - 7,
797
+ to_date: Date.today
798
+ )
799
+ puts "Daily candles: #{daily_data.size}"
800
+
801
+ # Get Intraday Historical Data
802
+ intraday_data = instrument.intraday(
803
+ from_date: Date.today,
804
+ to_date: Date.today,
805
+ interval: 5
806
+ )
807
+ puts "Intraday candles: #{intraday_data.size}"
808
+
809
+ # Get Expiry List (for F&O instruments)
810
+ if instrument.instrument == "FUTSTK" || instrument.instrument == "OPTSTK"
811
+ expiry_list = instrument.expiry_list
812
+ puts "Expiries: #{expiry_list.size}"
813
+ end
814
+ end
815
+ ```
816
+
817
+ ### Super Orders
818
+
819
+ #### Get All Super Orders
820
+
821
+ ```ruby
822
+ # Get all super orders
823
+ super_orders = DhanHQ::Models::SuperOrder.all
824
+ puts "Total super orders: #{super_orders.size}"
825
+ ```
826
+
827
+ #### Create Super Order
828
+
829
+ ```ruby
830
+ # Create a multi-leg super order
831
+ super_order = DhanHQ::Models::SuperOrder.create(
832
+ dhan_client_id: "1000000003",
833
+ legs: [
834
+ {
835
+ leg_name: "ENTRY_LEG",
836
+ transaction_type: "BUY",
837
+ exchange_segment: "NSE_FNO",
838
+ product_type: "MARGIN",
839
+ order_type: "LIMIT",
840
+ validity: "DAY",
841
+ security_id: "49081",
842
+ quantity: 50,
843
+ price: 18000.0
844
+ },
845
+ {
846
+ leg_name: "EXIT_LEG",
847
+ transaction_type: "SELL",
848
+ exchange_segment: "NSE_FNO",
849
+ product_type: "MARGIN",
850
+ order_type: "LIMIT",
851
+ validity: "DAY",
852
+ security_id: "49081",
853
+ quantity: 50,
854
+ price: 18100.0
855
+ }
856
+ ]
857
+ )
858
+
859
+ puts "Super Order ID: #{super_order.super_order_id}"
860
+ ```
861
+
862
+ #### Cancel Super Order
863
+
864
+ ```ruby
865
+ # Find super order
866
+ super_order = DhanHQ::Models::SuperOrder.all.first
867
+
868
+ # Cancel specific leg
869
+ if super_order
870
+ if super_order.cancel("ENTRY_LEG")
871
+ puts "Leg cancelled successfully"
872
+ end
873
+ end
874
+ ```
875
+
876
+ ### Forever Orders (GTT)
877
+
878
+ #### Get All Forever Orders
879
+
880
+ ```ruby
881
+ # Get all forever orders (GTT)
882
+ forever_orders = DhanHQ::Models::ForeverOrder.all
883
+ puts "Total forever orders: #{forever_orders.size}"
884
+ ```
885
+
886
+ #### Create Forever Order
887
+
888
+ ```ruby
889
+ # Create a forever order (GTT)
890
+ forever_order = DhanHQ::Models::ForeverOrder.create(
891
+ dhan_client_id: "1000000003",
892
+ transaction_type: "BUY",
893
+ exchange_segment: "NSE_EQ",
894
+ product_type: "MARGIN",
895
+ order_type: "LIMIT",
896
+ validity: "GTC",
897
+ security_id: "11536",
898
+ quantity: 1,
899
+ price: 3500.0,
900
+ trigger_price: 3450.0,
901
+ condition: "LTP_LESS_THAN_OR_EQUAL"
902
+ )
903
+
904
+ puts "Forever Order ID: #{forever_order.order_id}"
905
+ ```
906
+
907
+ #### Cancel Forever Order
908
+
909
+ ```ruby
910
+ # Find forever order
911
+ forever_order = DhanHQ::Models::ForeverOrder.all.first
912
+
913
+ # Cancel forever order
914
+ if forever_order
915
+ if forever_order.cancel
916
+ puts "Forever order cancelled successfully"
917
+ end
918
+ end
919
+ ```
920
+
921
+ ### EDIS
922
+
923
+ #### Get EDIS Form
924
+
925
+ ```ruby
926
+ # Get EDIS form
927
+ edis_form = DhanHQ::Models::Edis.form
928
+ puts "EDIS Form:"
929
+ puts " ISIN: #{edis_form[:isin]}"
930
+ puts " Quantity: #{edis_form[:quantity]}"
931
+ ```
932
+
933
+ #### Get Bulk EDIS Form
934
+
935
+ ```ruby
936
+ # Get bulk EDIS form
937
+ bulk_form = DhanHQ::Models::Edis.bulk_form
938
+ puts "Bulk Form: #{bulk_form.size} entries"
939
+ ```
940
+
941
+ #### Generate TPIN
942
+
943
+ ```ruby
944
+ # Generate TPIN
945
+ tpin = DhanHQ::Models::Edis.generate_tpin
946
+ puts "TPIN: #{tpin[:tpin]}"
947
+ ```
948
+
949
+ #### Inquire EDIS Status
950
+
951
+ ```ruby
952
+ # Inquire EDIS status by ISIN
953
+ isin = "INE467B01029" # Example ISIN
954
+ status = DhanHQ::Models::Edis.inquire(isin: isin)
955
+ puts "EDIS Status: #{status[:status]}"
956
+ ```
957
+
958
+ ### Kill Switch
959
+
960
+ #### Activate Kill Switch
961
+
962
+ ```ruby
963
+ # Activate kill switch
964
+ result = DhanHQ::Models::KillSwitch.update(status: "ACTIVATE")
965
+ if result
966
+ puts "Kill switch activated"
967
+ else
968
+ puts "Failed to activate kill switch"
969
+ end
970
+ ```
971
+
972
+ #### Deactivate Kill Switch
973
+
974
+ ```ruby
975
+ # Deactivate kill switch
976
+ result = DhanHQ::Models::KillSwitch.update(status: "DEACTIVATE")
977
+ if result
978
+ puts "Kill switch deactivated"
979
+ else
980
+ puts "Failed to deactivate kill switch"
981
+ end
982
+ ```
983
+
984
+ ### Expired Options Data
985
+
986
+ #### Fetch Expired Options Data
987
+
988
+ ```ruby
989
+ # Fetch expired options data
990
+ expired_data = DhanHQ::Models::ExpiredOptionsData.fetch(
991
+ security_id: 12345,
992
+ expiry_code: "20240125",
993
+ strike: 18000,
994
+ interval: "5",
995
+ required_data: ["ohlc", "volume"]
996
+ )
997
+
998
+ puts "Expired Options Data:"
999
+ puts " Total records: #{expired_data.size}"
1000
+ expired_data.first(5).each do |record|
1001
+ puts " #{record[:time]}: O=₹#{record[:open]}, H=₹#{record[:high]}, L=₹#{record[:low]}, C=₹#{record[:close]}"
1002
+ end
1003
+ ```
1004
+
1005
+ ### Margin Calculator
1006
+
1007
+ #### Calculate Margin
1008
+
1009
+ ```ruby
1010
+ # Calculate margin for an order
1011
+ margin = DhanHQ::Models::Margin.calculate(
1012
+ dhan_client_id: "1000000003",
1013
+ transaction_type: "BUY",
1014
+ exchange_segment: "NSE_EQ",
1015
+ product_type: "MARGIN",
1016
+ order_type: "LIMIT",
1017
+ security_id: "11536",
1018
+ quantity: 1,
1019
+ price: 3500.0
1020
+ )
1021
+
1022
+ puts "Margin Required: ₹#{margin.margin_required}"
1023
+ puts "Available Margin: ₹#{margin.available_margin}"
1024
+ puts "Utilized Margin: ₹#{margin.utilized_margin}"
1025
+ ```
1026
+
1027
+ ### Ledger Entries
1028
+
1029
+ #### Get Ledger Entries
1030
+
1031
+ ```ruby
1032
+ # Get ledger entries
1033
+ from_date = Date.today - 7
1034
+ to_date = Date.today
1035
+
1036
+ ledger_entries = DhanHQ::Models::LedgerEntry.all(
1037
+ from_date: from_date,
1038
+ to_date: to_date
1039
+ )
1040
+
1041
+ puts "Total ledger entries: #{ledger_entries.size}"
1042
+ ledger_entries.first(10).each do |entry|
1043
+ puts "#{entry[:date]}: #{entry[:description]} - ₹#{entry[:amount]}"
1044
+ end
1045
+ ```
1046
+
1047
+ ---
1048
+
1049
+ ## Validation Contracts Testing
1050
+
1051
+ ### Place Order Contract
1052
+
1053
+ ```ruby
1054
+ # Test valid order
1055
+ valid_params = {
1056
+ dhan_client_id: "1000000003",
1057
+ transaction_type: "BUY",
1058
+ exchange_segment: "NSE_EQ",
1059
+ product_type: "INTRADAY",
1060
+ order_type: "LIMIT",
1061
+ validity: "DAY",
1062
+ security_id: "11536",
1063
+ quantity: 1,
1064
+ price: 3500.0
1065
+ }
1066
+
1067
+ contract = DhanHQ::Contracts::PlaceOrderContract.new
1068
+ result = contract.call(valid_params)
1069
+ if result.success?
1070
+ puts "Validation passed"
1071
+ else
1072
+ puts "Validation errors: #{result.errors.to_h}"
1073
+ end
1074
+
1075
+ # Test invalid order (missing required field)
1076
+ invalid_params = {
1077
+ transaction_type: "BUY",
1078
+ exchange_segment: "NSE_EQ"
1079
+ # Missing required fields
1080
+ }
1081
+
1082
+ result = contract.call(invalid_params)
1083
+ if result.failure?
1084
+ puts "Validation failed as expected"
1085
+ puts "Errors: #{result.errors.to_h}"
1086
+ end
1087
+
1088
+ # Test invalid price (NaN)
1089
+ invalid_price_params = valid_params.merge(price: Float::NAN)
1090
+ result = contract.call(invalid_price_params)
1091
+ if result.failure?
1092
+ puts "NaN validation caught: #{result.errors.to_h}"
1093
+ end
1094
+
1095
+ # Test invalid price (Infinity)
1096
+ invalid_inf_params = valid_params.merge(price: Float::INFINITY)
1097
+ result = contract.call(invalid_inf_params)
1098
+ if result.failure?
1099
+ puts "Infinity validation caught: #{result.errors.to_h}"
1100
+ end
1101
+
1102
+ # Test price exceeding upper bound
1103
+ invalid_upper_params = valid_params.merge(price: 2_000_000_000)
1104
+ result = contract.call(invalid_upper_params)
1105
+ if result.failure?
1106
+ puts "Upper bound validation caught: #{result.errors.to_h}"
1107
+ end
1108
+ ```
1109
+
1110
+ ### Modify Order Contract
1111
+
1112
+ ```ruby
1113
+ # Test valid modification
1114
+ valid_params = {
1115
+ dhan_client_id: "1000000003",
1116
+ order_id: "112111182045",
1117
+ price: 3501.0,
1118
+ quantity: 2
1119
+ }
1120
+
1121
+ contract = DhanHQ::Contracts::ModifyOrderContract.new
1122
+ result = contract.call(valid_params)
1123
+ if result.success?
1124
+ puts "Validation passed"
1125
+ else
1126
+ puts "Validation errors: #{result.errors.to_h}"
1127
+ end
1128
+ ```
1129
+
1130
+ ### Margin Calculator Contract
1131
+
1132
+ ```ruby
1133
+ # Test valid margin calculation
1134
+ valid_params = {
1135
+ dhan_client_id: "1000000003",
1136
+ transaction_type: "BUY",
1137
+ exchange_segment: "NSE_EQ",
1138
+ product_type: "MARGIN",
1139
+ order_type: "LIMIT",
1140
+ security_id: "11536",
1141
+ quantity: 1,
1142
+ price: 3500.0
1143
+ }
1144
+
1145
+ contract = DhanHQ::Contracts::MarginCalculatorContract.new
1146
+ result = contract.call(valid_params)
1147
+ if result.success?
1148
+ puts "Validation passed"
1149
+ else
1150
+ puts "Validation errors: #{result.errors.to_h}"
1151
+ end
1152
+ ```
1153
+
1154
+ ### Option Chain Contract
1155
+
1156
+ ```ruby
1157
+ # Test valid option chain request
1158
+ valid_params = {
1159
+ underlying_scrip: "NIFTY",
1160
+ underlying_seg: "IDX_I",
1161
+ expiry: "2024-01-25"
1162
+ }
1163
+
1164
+ contract = DhanHQ::Contracts::OptionChainContract.new
1165
+ result = contract.call(valid_params)
1166
+ if result.success?
1167
+ puts "Validation passed"
1168
+ else
1169
+ puts "Validation errors: #{result.errors.to_h}"
1170
+ end
1171
+
1172
+ # Test invalid expiry format
1173
+ invalid_params = valid_params.merge(expiry: "25-01-2024")
1174
+ result = contract.call(invalid_params)
1175
+ if result.failure?
1176
+ puts "Invalid expiry format caught: #{result.errors.to_h}"
1177
+ end
1178
+ ```
1179
+
1180
+ ### Historical Data Contract
1181
+
1182
+ ```ruby
1183
+ # Test valid historical data request
1184
+ valid_params = {
1185
+ security_id: "11536",
1186
+ exchange_segment: "NSE_EQ",
1187
+ from_date: Date.today - 7,
1188
+ to_date: Date.today,
1189
+ interval: 5
1190
+ }
1191
+
1192
+ contract = DhanHQ::Contracts::HistoricalDataContract.new
1193
+ result = contract.call(valid_params)
1194
+ if result.success?
1195
+ puts "Validation passed"
1196
+ else
1197
+ puts "Validation errors: #{result.errors.to_h}"
1198
+ end
1199
+
1200
+ # Test date range validation (max 31 days)
1201
+ invalid_params = valid_params.merge(
1202
+ from_date: Date.today - 35,
1203
+ to_date: Date.today
1204
+ )
1205
+ result = contract.call(invalid_params)
1206
+ if result.failure?
1207
+ puts "Date range validation caught: #{result.errors.to_h}"
1208
+ end
1209
+ ```
1210
+
1211
+ ---
1212
+
1213
+ ## Error Handling Testing
1214
+
1215
+ ### Test Rate Limit Error
1216
+
1217
+ ```ruby
1218
+ # Make rapid requests to trigger rate limit
1219
+ 10.times do |i|
1220
+ begin
1221
+ DhanHQ::Models::Funds.fetch
1222
+ puts "Request #{i + 1} succeeded"
1223
+ rescue DhanHQ::RateLimitError => e
1224
+ puts "Rate limit error: #{e.message}"
1225
+ break
1226
+ end
1227
+ sleep(0.1)
1228
+ end
1229
+ ```
1230
+
1231
+ ### Test Authentication Error
1232
+
1233
+ ```ruby
1234
+ # Temporarily remove access token
1235
+ original_token = DhanHQ.configuration.access_token
1236
+ DhanHQ.configuration.access_token = nil
1237
+
1238
+ begin
1239
+ DhanHQ::Models::Funds.fetch
1240
+ rescue DhanHQ::InvalidAuthenticationError => e
1241
+ puts "Authentication error caught: #{e.message}"
1242
+ ensure
1243
+ # Restore token
1244
+ DhanHQ.configuration.access_token = original_token
1245
+ end
1246
+ ```
1247
+
1248
+ ### Test Validation Error
1249
+
1250
+ ```ruby
1251
+ # Try to place order with invalid data
1252
+ begin
1253
+ order = DhanHQ::Models::Order.place(
1254
+ transaction_type: "INVALID_TYPE",
1255
+ exchange_segment: "NSE_EQ"
1256
+ )
1257
+ rescue DhanHQ::Error => e
1258
+ puts "Validation error caught: #{e.message}"
1259
+ end
1260
+ ```
1261
+
1262
+ ### Test Network Error Handling
1263
+
1264
+ ```ruby
1265
+ # Test retry logic (will retry on transient errors)
1266
+ begin
1267
+ # This will use retry logic for transient errors
1268
+ funds = DhanHQ::Models::Funds.fetch
1269
+ puts "Success: #{funds.available_margin}"
1270
+ rescue DhanHQ::NetworkError => e
1271
+ puts "Network error after retries: #{e.message}"
1272
+ end
1273
+ ```
1274
+
1275
+ ---
1276
+
1277
+ ## Rate Limiting Testing
1278
+
1279
+ ### Test Rate Limiter
1280
+
1281
+ ```ruby
1282
+ # Check rate limiter status
1283
+ rate_limiter = DhanHQ::Client.new(api_type: :trading).instance_variable_get(:@rate_limiter)
1284
+
1285
+ # Make multiple requests and observe throttling
1286
+ start_time = Time.now
1287
+ 5.times do |i|
1288
+ rate_limiter.throttle!
1289
+ puts "Request #{i + 1} at #{Time.now - start_time}s"
1290
+ end
1291
+
1292
+ # Shutdown rate limiter (cleanup)
1293
+ rate_limiter.shutdown
1294
+ ```
1295
+
1296
+ ### Test Rate Limit Per Second
1297
+
1298
+ ```ruby
1299
+ # Test per-second rate limiting
1300
+ start_time = Time.now
1301
+ 10.times do |i|
1302
+ begin
1303
+ DhanHQ::Models::Funds.fetch
1304
+ elapsed = Time.now - start_time
1305
+ puts "Request #{i + 1} completed at #{elapsed.round(2)}s"
1306
+ rescue DhanHQ::RateLimitError => e
1307
+ puts "Rate limited at request #{i + 1}: #{e.message}"
1308
+ end
1309
+ sleep(0.1)
1310
+ end
1311
+ ```
1312
+
1313
+ ---
1314
+
1315
+ ## Advanced Testing Scenarios
1316
+
1317
+ ### End-to-End Order Flow
1318
+
1319
+ ```ruby
1320
+ # Complete order lifecycle test
1321
+ puts "=== Order Lifecycle Test ==="
1322
+
1323
+ # 1. Check funds
1324
+ funds = DhanHQ::Models::Funds.fetch
1325
+ puts "1. Available Margin: ₹#{funds.available_margin}"
1326
+
1327
+ # 2. Calculate margin
1328
+ margin = DhanHQ::Models::Margin.calculate(
1329
+ dhan_client_id: "1000000003",
1330
+ transaction_type: "BUY",
1331
+ exchange_segment: "NSE_EQ",
1332
+ product_type: "MARGIN",
1333
+ order_type: "LIMIT",
1334
+ security_id: "11536",
1335
+ quantity: 1,
1336
+ price: 3500.0
1337
+ )
1338
+ puts "2. Margin Required: ₹#{margin.margin_required}"
1339
+
1340
+ # 3. Place order
1341
+ order = DhanHQ::Models::Order.place(
1342
+ dhan_client_id: "1000000003",
1343
+ transaction_type: "BUY",
1344
+ exchange_segment: "NSE_EQ",
1345
+ product_type: "MARGIN",
1346
+ order_type: "LIMIT",
1347
+ validity: "DAY",
1348
+ security_id: "11536",
1349
+ quantity: 1,
1350
+ price: 3500.0
1351
+ )
1352
+ puts "3. Order Placed: #{order.order_id}, Status: #{order.order_status}"
1353
+
1354
+ # 4. Monitor order via WebSocket
1355
+ orders_client = DhanHQ::WS::Orders.client
1356
+ orders_client.on(:update) do |update|
1357
+ if update.order_no == order.order_id
1358
+ puts "4. Order Update: #{update.status}"
1359
+ end
1360
+ end
1361
+ orders_client.start
1362
+
1363
+ # 5. Modify order (if pending)
1364
+ sleep(2)
1365
+ order.refresh
1366
+ if order.order_status == "PENDING"
1367
+ if order.modify(price: 3501.0)
1368
+ puts "5. Order Modified"
1369
+ end
1370
+ end
1371
+
1372
+ # 6. Cancel order
1373
+ sleep(2)
1374
+ if order.cancel
1375
+ puts "6. Order Cancelled"
1376
+ end
1377
+
1378
+ # 7. Check trades
1379
+ trades = DhanHQ::Models::Trade.find_by_order_id(order.order_id)
1380
+ puts "7. Trades: #{trades.size}"
1381
+
1382
+ # Cleanup
1383
+ orders_client.stop
1384
+ puts "=== Test Complete ==="
1385
+ ```
1386
+
1387
+ ### Market Data Analysis
1388
+
1389
+ ```ruby
1390
+ # Get historical data and analyze
1391
+ historical_data = DhanHQ::Models::HistoricalData.daily(
1392
+ security_id: "11536",
1393
+ exchange_segment: "NSE_EQ",
1394
+ from_date: Date.today - 30,
1395
+ to_date: Date.today
1396
+ )
1397
+
1398
+ # Calculate simple moving average
1399
+ prices = historical_data.map { |c| c[:close] }
1400
+ sma_20 = prices.last(20).sum / 20.0
1401
+ puts "20-day SMA: ₹#{sma_20.round(2)}"
1402
+
1403
+ # Get current LTP
1404
+ ltp_data = DhanHQ::Models::MarketFeed.ltp("NSE_EQ" => [11536])
1405
+ current_price = ltp_data[:data]["NSE_EQ"]["11536"][:last_price]
1406
+ puts "Current Price: ₹#{current_price}"
1407
+
1408
+ # Compare
1409
+ if current_price > sma_20
1410
+ puts "Price is above 20-day SMA"
1411
+ else
1412
+ puts "Price is below 20-day SMA"
1413
+ end
1414
+ ```
1415
+
1416
+ ### Option Strategy Testing
1417
+
1418
+ ```ruby
1419
+ # Get option chain
1420
+ expiry_list = DhanHQ::Models::OptionChain.fetch_expiry_list(
1421
+ underlying_scrip: "NIFTY",
1422
+ underlying_seg: "IDX_I"
1423
+ )
1424
+
1425
+ expiry = expiry_list.first[:expiry_date]
1426
+ option_chain = DhanHQ::Models::OptionChain.fetch(
1427
+ underlying_scrip: "NIFTY",
1428
+ underlying_seg: "IDX_I",
1429
+ expiry: expiry
1430
+ )
1431
+
1432
+ # Find ATM strike
1433
+ ltp_data = DhanHQ::Models::MarketFeed.ltp("IDX_I" => [13])
1434
+ nifty_ltp = ltp_data[:data]["IDX_I"]["13"][:last_price]
1435
+ atm_strike = (nifty_ltp / 50).round * 50
1436
+
1437
+ # Find ATM options
1438
+ atm_option = option_chain.find { |o| o[:strike_price] == atm_strike }
1439
+ if atm_option
1440
+ puts "ATM Strike: #{atm_strike}"
1441
+ puts "CE Symbol: #{atm_option[:call_option]&.dig(:trading_symbol)}"
1442
+ puts "PE Symbol: #{atm_option[:put_option]&.dig(:trading_symbol)}"
1443
+ end
1444
+ ```
1445
+
1446
+ ---
1447
+
1448
+ ## Tips for Console Testing
1449
+
1450
+ 1. **Use Variables**: Store results in variables for reuse
1451
+ ```ruby
1452
+ order = DhanHQ::Models::Order.find("112111182045")
1453
+ order.modify(price: 3501.0)
1454
+ ```
1455
+
1456
+ 2. **Use Helper Methods**: Leverage instrument helper methods
1457
+ ```ruby
1458
+ instrument = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
1459
+ ltp = instrument.ltp
1460
+ ```
1461
+
1462
+ 3. **Monitor WebSockets**: Keep WebSocket clients running in background
1463
+ ```ruby
1464
+ orders_client = DhanHQ::WS::Orders.client
1465
+ orders_client.start
1466
+ # Continue working, updates will print automatically
1467
+ ```
1468
+
1469
+ 4. **Error Handling**: Wrap risky operations in begin/rescue
1470
+ ```ruby
1471
+ begin
1472
+ order = DhanHQ::Models::Order.place(params)
1473
+ rescue DhanHQ::Error => e
1474
+ puts "Error: #{e.message}"
1475
+ end
1476
+ ```
1477
+
1478
+ 5. **Cleanup**: Always stop WebSocket connections when done
1479
+ ```ruby
1480
+ orders_client.stop
1481
+ market_client.stop
1482
+ ```
1483
+
1484
+ ---
1485
+
1486
+ ## Quick Reference
1487
+
1488
+ ### Common Exchange Segments
1489
+ - `NSE_EQ` - NSE Equity
1490
+ - `BSE_EQ` - BSE Equity
1491
+ - `NSE_FNO` - NSE F&O
1492
+ - `BSE_FNO` - BSE F&O
1493
+ - `IDX_I` - Indices (NIFTY, BANKNIFTY, etc.)
1494
+
1495
+ ### Common Product Types
1496
+ - `INTRADAY` - Intraday
1497
+ - `MARGIN` - Margin
1498
+ - `CNC` - Cash and Carry
1499
+ - `CO` - Cover Order
1500
+ - `BO` - Bracket Order
1501
+
1502
+ ### Common Order Types
1503
+ - `MARKET` - Market Order
1504
+ - `LIMIT` - Limit Order
1505
+ - `STOPLOSS` - Stop Loss Order
1506
+ - `STOPLOSS_MARKET` - Stop Loss Market Order
1507
+
1508
+ ### Common Transaction Types
1509
+ - `BUY` - Buy
1510
+ - `SELL` - Sell
1511
+
1512
+ ---
1513
+
1514
+ This guide provides comprehensive examples for testing all features of the DhanHQ client gem. Use these examples in `bin/console` to explore and test the gem's functionality.