DhanHQ 2.4.0 → 2.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.rubocop.yml +9 -1
- data/CHANGELOG.md +103 -7
- data/GUIDE.md +57 -39
- data/README.md +198 -755
- data/docs/API_DOCS_GAPS.md +128 -0
- data/docs/API_VERIFICATION.md +10 -11
- data/{README1.md → docs/ARCHIVE_README.md} +16 -16
- data/docs/AUTHENTICATION.md +72 -10
- data/docs/CONFIGURATION.md +109 -0
- data/docs/CONSTANTS_REFERENCE.md +477 -0
- data/docs/DATA_API_PARAMETERS.md +7 -7
- data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
- data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
- data/docs/SUPER_ORDERS.md +284 -0
- data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
- data/docs/TESTING_GUIDE.md +84 -82
- data/docs/TROUBLESHOOTING.md +117 -0
- data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
- data/docs/WEBSOCKET_PROTOCOL.md +154 -0
- data/lib/DhanHQ/constants.rb +456 -151
- data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
- data/lib/DhanHQ/contracts/base_contract.rb +22 -0
- data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
- data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
- data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
- data/lib/DhanHQ/contracts/order_contract.rb +171 -39
- data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
- data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
- data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
- data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
- data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
- data/lib/DhanHQ/core/base_model.rb +13 -4
- data/lib/DhanHQ/helpers/response_helper.rb +2 -2
- data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
- data/lib/DhanHQ/models/alert_order.rb +29 -11
- data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
- data/lib/DhanHQ/models/edis.rb +101 -0
- data/lib/DhanHQ/models/expired_options_data.rb +6 -12
- data/lib/DhanHQ/models/forever_order.rb +8 -5
- data/lib/DhanHQ/models/historical_data.rb +0 -8
- data/lib/DhanHQ/models/instrument.rb +1 -7
- data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
- data/lib/DhanHQ/models/kill_switch.rb +23 -11
- data/lib/DhanHQ/models/margin.rb +51 -2
- data/lib/DhanHQ/models/order.rb +107 -126
- data/lib/DhanHQ/models/order_update.rb +7 -13
- data/lib/DhanHQ/models/pnl_exit.rb +122 -0
- data/lib/DhanHQ/models/position.rb +23 -1
- data/lib/DhanHQ/models/postback.rb +114 -0
- data/lib/DhanHQ/models/profile.rb +0 -10
- data/lib/DhanHQ/models/super_order.rb +13 -3
- data/lib/DhanHQ/models/trade.rb +11 -23
- data/lib/DhanHQ/resources/ip_setup.rb +16 -5
- data/lib/DhanHQ/resources/kill_switch.rb +17 -7
- data/lib/DhanHQ/resources/margin_calculator.rb +9 -0
- data/lib/DhanHQ/resources/orders.rb +41 -41
- data/lib/DhanHQ/resources/pnl_exit.rb +37 -0
- data/lib/DhanHQ/resources/positions.rb +8 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
- data/lib/DhanHQ/ws/orders/client.rb +6 -6
- data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
- data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
- data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
- metadata +29 -24
- data/TODO-1.md +0 -14
- data/TODO.md +0 -127
- data/app/services/live/order_update_guard_support.rb +0 -75
- data/app/services/live/order_update_hub.rb +0 -76
- data/app/services/live/order_update_persistence_support.rb +0 -68
- data/docs/PR_2.2.0.md +0 -48
- data/examples/comprehensive_websocket_examples.rb +0 -148
- data/examples/instrument_finder_test.rb +0 -195
- data/examples/live_order_updates.rb +0 -118
- data/examples/market_depth_example.rb +0 -144
- data/examples/market_feed_example.rb +0 -81
- data/examples/order_update_example.rb +0 -105
- data/examples/trading_fields_example.rb +0 -215
- /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
- /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
data/docs/TESTING_GUIDE.md
CHANGED
|
@@ -95,10 +95,10 @@ market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
# Subscribe to NIFTY (Security ID: 13, Segment: IDX_I)
|
|
98
|
-
market_client.subscribe_one(segment:
|
|
98
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
|
|
99
99
|
|
|
100
100
|
# Subscribe to BANKNIFTY (Security ID: 25, Segment: IDX_I)
|
|
101
|
-
market_client.subscribe_one(segment:
|
|
101
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25")
|
|
102
102
|
|
|
103
103
|
# Wait for data (in console, you can continue working)
|
|
104
104
|
sleep(10)
|
|
@@ -117,7 +117,7 @@ ohlc_client = DhanHQ::WS.connect(mode: :ohlc) do |data|
|
|
|
117
117
|
end
|
|
118
118
|
|
|
119
119
|
# Subscribe to NIFTY
|
|
120
|
-
ohlc_client.subscribe_one(segment:
|
|
120
|
+
ohlc_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
|
|
121
121
|
|
|
122
122
|
sleep(10)
|
|
123
123
|
ohlc_client.stop
|
|
@@ -135,7 +135,7 @@ quote_client = DhanHQ::WS.connect(mode: :quote) do |data|
|
|
|
135
135
|
end
|
|
136
136
|
|
|
137
137
|
# Subscribe to NIFTY
|
|
138
|
-
quote_client.subscribe_one(segment:
|
|
138
|
+
quote_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
|
|
139
139
|
|
|
140
140
|
sleep(10)
|
|
141
141
|
quote_client.stop
|
|
@@ -151,10 +151,10 @@ end
|
|
|
151
151
|
|
|
152
152
|
# Subscribe to multiple indices
|
|
153
153
|
indices = [
|
|
154
|
-
{ segment:
|
|
155
|
-
{ segment:
|
|
156
|
-
{ segment:
|
|
157
|
-
{ segment:
|
|
154
|
+
{ segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13" }, # NIFTY
|
|
155
|
+
{ segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25" }, # BANKNIFTY
|
|
156
|
+
{ segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "29" }, # NIFTYIT
|
|
157
|
+
{ segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "51" } # SENSEX
|
|
158
158
|
]
|
|
159
159
|
|
|
160
160
|
indices.each do |idx|
|
|
@@ -169,7 +169,7 @@ market_client.stop
|
|
|
169
169
|
|
|
170
170
|
```ruby
|
|
171
171
|
market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
|
|
172
|
-
market_client.subscribe_one(segment:
|
|
172
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
|
|
173
173
|
|
|
174
174
|
# Check connection state
|
|
175
175
|
puts "Connected: #{market_client.connected?}"
|
|
@@ -326,11 +326,11 @@ depth_client.stop
|
|
|
326
326
|
# Place a market order
|
|
327
327
|
order = DhanHQ::Models::Order.place(
|
|
328
328
|
dhan_client_id: "1000000003",
|
|
329
|
-
transaction_type:
|
|
330
|
-
exchange_segment:
|
|
331
|
-
product_type:
|
|
332
|
-
order_type:
|
|
333
|
-
validity:
|
|
329
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
330
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
331
|
+
product_type: DhanHQ::Constants::ProductType::INTRADAY,
|
|
332
|
+
order_type: DhanHQ::Constants::OrderType::MARKET,
|
|
333
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
334
334
|
security_id: "11536", # TCS
|
|
335
335
|
quantity: 1
|
|
336
336
|
)
|
|
@@ -345,11 +345,11 @@ puts "Status: #{order.order_status}"
|
|
|
345
345
|
# Place a limit order
|
|
346
346
|
order = DhanHQ::Models::Order.place(
|
|
347
347
|
dhan_client_id: "1000000003",
|
|
348
|
-
transaction_type:
|
|
349
|
-
exchange_segment:
|
|
350
|
-
product_type:
|
|
351
|
-
order_type:
|
|
352
|
-
validity:
|
|
348
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
349
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
350
|
+
product_type: DhanHQ::Constants::ProductType::INTRADAY,
|
|
351
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
352
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
353
353
|
security_id: "11536",
|
|
354
354
|
quantity: 1,
|
|
355
355
|
price: 3500.0
|
|
@@ -364,11 +364,11 @@ puts "Order ID: #{order.order_id}"
|
|
|
364
364
|
# Place stop loss order
|
|
365
365
|
order = DhanHQ::Models::Order.place(
|
|
366
366
|
dhan_client_id: "1000000003",
|
|
367
|
-
transaction_type:
|
|
368
|
-
exchange_segment:
|
|
369
|
-
product_type:
|
|
367
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
368
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
369
|
+
product_type: DhanHQ::Constants::ProductType::INTRADAY,
|
|
370
370
|
order_type: "STOPLOSS",
|
|
371
|
-
validity:
|
|
371
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
372
372
|
security_id: "11536",
|
|
373
373
|
quantity: 1,
|
|
374
374
|
price: 3500.0,
|
|
@@ -410,11 +410,11 @@ orders = DhanHQ::Models::Order.all
|
|
|
410
410
|
puts "Total orders: #{orders.size}"
|
|
411
411
|
|
|
412
412
|
# Filter pending orders
|
|
413
|
-
pending = orders.select { |o| o.order_status ==
|
|
413
|
+
pending = orders.select { |o| o.order_status == DhanHQ::Constants::OrderStatus::PENDING }
|
|
414
414
|
puts "Pending orders: #{pending.size}"
|
|
415
415
|
|
|
416
416
|
# Filter executed orders
|
|
417
|
-
executed = orders.select { |o| o.order_status ==
|
|
417
|
+
executed = orders.select { |o| o.order_status == DhanHQ::Constants::OrderStatus::TRADED }
|
|
418
418
|
puts "Executed orders: #{executed.size}"
|
|
419
419
|
```
|
|
420
420
|
|
|
@@ -472,11 +472,11 @@ end
|
|
|
472
472
|
# Create order instance
|
|
473
473
|
order = DhanHQ::Models::Order.new(
|
|
474
474
|
dhan_client_id: "1000000003",
|
|
475
|
-
transaction_type:
|
|
476
|
-
exchange_segment:
|
|
477
|
-
product_type:
|
|
478
|
-
order_type:
|
|
479
|
-
validity:
|
|
475
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
476
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
477
|
+
product_type: DhanHQ::Constants::ProductType::INTRADAY,
|
|
478
|
+
order_type: DhanHQ::Constants::OrderType::MARKET,
|
|
479
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
480
480
|
security_id: "11536",
|
|
481
481
|
quantity: 1
|
|
482
482
|
)
|
|
@@ -499,7 +499,7 @@ positions = DhanHQ::Models::Position.all
|
|
|
499
499
|
puts "Total positions: #{positions.size}"
|
|
500
500
|
|
|
501
501
|
# Filter by exchange
|
|
502
|
-
nse_positions = positions.select { |p| p.exchange_segment ==
|
|
502
|
+
nse_positions = positions.select { |p| p.exchange_segment == DhanHQ::Constants::ExchangeSegment::NSE_EQ }
|
|
503
503
|
puts "NSE positions: #{nse_positions.size}"
|
|
504
504
|
|
|
505
505
|
# Filter long positions
|
|
@@ -517,8 +517,8 @@ position = DhanHQ::Models::Position.all.first
|
|
|
517
517
|
if position
|
|
518
518
|
result = position.convert(
|
|
519
519
|
dhan_client_id: "1000000003",
|
|
520
|
-
from_product_type:
|
|
521
|
-
to_product_type:
|
|
520
|
+
from_product_type: DhanHQ::Constants::ProductType::INTRADAY,
|
|
521
|
+
to_product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
522
522
|
quantity: position.net_qty.abs
|
|
523
523
|
)
|
|
524
524
|
|
|
@@ -677,7 +677,7 @@ puts " Ask Price: ₹#{quote_data[:ask_price]}"
|
|
|
677
677
|
# Get daily candles
|
|
678
678
|
historical_data = DhanHQ::Models::HistoricalData.daily(
|
|
679
679
|
security_id: "11536",
|
|
680
|
-
exchange_segment:
|
|
680
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
681
681
|
from_date: Date.today - 30,
|
|
682
682
|
to_date: Date.today
|
|
683
683
|
)
|
|
@@ -694,7 +694,7 @@ end
|
|
|
694
694
|
# Get intraday candles (5-minute interval)
|
|
695
695
|
historical_data = DhanHQ::Models::HistoricalData.intraday(
|
|
696
696
|
security_id: "11536",
|
|
697
|
-
exchange_segment:
|
|
697
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
698
698
|
from_date: Date.today,
|
|
699
699
|
to_date: Date.today,
|
|
700
700
|
interval: 5
|
|
@@ -714,7 +714,7 @@ end
|
|
|
714
714
|
# Get expiry list for NIFTY
|
|
715
715
|
expiry_list = DhanHQ::Models::OptionChain.fetch_expiry_list(
|
|
716
716
|
underlying_scrip: "NIFTY",
|
|
717
|
-
underlying_seg:
|
|
717
|
+
underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I
|
|
718
718
|
)
|
|
719
719
|
|
|
720
720
|
puts "Available expiries:"
|
|
@@ -731,7 +731,7 @@ expiry_date = expiry_list.first[:expiry_date] # Use first expiry from above
|
|
|
731
731
|
|
|
732
732
|
option_chain = DhanHQ::Models::OptionChain.fetch(
|
|
733
733
|
underlying_scrip: "NIFTY",
|
|
734
|
-
underlying_seg:
|
|
734
|
+
underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I,
|
|
735
735
|
expiry: expiry_date
|
|
736
736
|
)
|
|
737
737
|
|
|
@@ -835,22 +835,22 @@ super_order = DhanHQ::Models::SuperOrder.create(
|
|
|
835
835
|
legs: [
|
|
836
836
|
{
|
|
837
837
|
leg_name: "ENTRY_LEG",
|
|
838
|
-
transaction_type:
|
|
839
|
-
exchange_segment:
|
|
840
|
-
product_type:
|
|
841
|
-
order_type:
|
|
842
|
-
validity:
|
|
838
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
839
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
|
|
840
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
841
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
842
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
843
843
|
security_id: "49081",
|
|
844
844
|
quantity: 50,
|
|
845
845
|
price: 18000.0
|
|
846
846
|
},
|
|
847
847
|
{
|
|
848
848
|
leg_name: "EXIT_LEG",
|
|
849
|
-
transaction_type:
|
|
850
|
-
exchange_segment:
|
|
851
|
-
product_type:
|
|
852
|
-
order_type:
|
|
853
|
-
validity:
|
|
849
|
+
transaction_type: DhanHQ::Constants::TransactionType::SELL,
|
|
850
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
|
|
851
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
852
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
853
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
854
854
|
security_id: "49081",
|
|
855
855
|
quantity: 50,
|
|
856
856
|
price: 18100.0
|
|
@@ -891,10 +891,10 @@ puts "Total forever orders: #{forever_orders.size}"
|
|
|
891
891
|
# Create a forever order (GTT)
|
|
892
892
|
forever_order = DhanHQ::Models::ForeverOrder.create(
|
|
893
893
|
dhan_client_id: "1000000003",
|
|
894
|
-
transaction_type:
|
|
895
|
-
exchange_segment:
|
|
896
|
-
product_type:
|
|
897
|
-
order_type:
|
|
894
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
895
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
896
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
897
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
898
898
|
validity: "GTC",
|
|
899
899
|
security_id: "11536",
|
|
900
900
|
quantity: 1,
|
|
@@ -971,6 +971,8 @@ tc.status
|
|
|
971
971
|
|
|
972
972
|
#### Kill Switch (model, backward compatible)
|
|
973
973
|
|
|
974
|
+
Uses query param per API doc: `POST /v2/killswitch?killSwitchStatus=ACTIVATE` (no body).
|
|
975
|
+
|
|
974
976
|
```ruby
|
|
975
977
|
# Activate kill switch
|
|
976
978
|
result = DhanHQ::Models::KillSwitch.update("ACTIVATE")
|
|
@@ -1010,10 +1012,10 @@ end
|
|
|
1010
1012
|
# Calculate margin for an order
|
|
1011
1013
|
margin = DhanHQ::Models::Margin.calculate(
|
|
1012
1014
|
dhan_client_id: "1000000003",
|
|
1013
|
-
transaction_type:
|
|
1014
|
-
exchange_segment:
|
|
1015
|
-
product_type:
|
|
1016
|
-
order_type:
|
|
1015
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
1016
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1017
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
1018
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
1017
1019
|
security_id: "11536",
|
|
1018
1020
|
quantity: 1,
|
|
1019
1021
|
price: 3500.0
|
|
@@ -1054,11 +1056,11 @@ end
|
|
|
1054
1056
|
# Test valid order
|
|
1055
1057
|
valid_params = {
|
|
1056
1058
|
dhan_client_id: "1000000003",
|
|
1057
|
-
transaction_type:
|
|
1058
|
-
exchange_segment:
|
|
1059
|
-
product_type:
|
|
1060
|
-
order_type:
|
|
1061
|
-
validity:
|
|
1059
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
1060
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1061
|
+
product_type: DhanHQ::Constants::ProductType::INTRADAY,
|
|
1062
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
1063
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
1062
1064
|
security_id: "11536",
|
|
1063
1065
|
quantity: 1,
|
|
1064
1066
|
price: 3500.0
|
|
@@ -1074,8 +1076,8 @@ end
|
|
|
1074
1076
|
|
|
1075
1077
|
# Test invalid order (missing required field)
|
|
1076
1078
|
invalid_params = {
|
|
1077
|
-
transaction_type:
|
|
1078
|
-
exchange_segment:
|
|
1079
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
1080
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ
|
|
1079
1081
|
# Missing required fields
|
|
1080
1082
|
}
|
|
1081
1083
|
|
|
@@ -1133,10 +1135,10 @@ end
|
|
|
1133
1135
|
# Test valid margin calculation
|
|
1134
1136
|
valid_params = {
|
|
1135
1137
|
dhan_client_id: "1000000003",
|
|
1136
|
-
transaction_type:
|
|
1137
|
-
exchange_segment:
|
|
1138
|
-
product_type:
|
|
1139
|
-
order_type:
|
|
1138
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
1139
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1140
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
1141
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
1140
1142
|
security_id: "11536",
|
|
1141
1143
|
quantity: 1,
|
|
1142
1144
|
price: 3500.0
|
|
@@ -1157,7 +1159,7 @@ end
|
|
|
1157
1159
|
# Test valid option chain request
|
|
1158
1160
|
valid_params = {
|
|
1159
1161
|
underlying_scrip: "NIFTY",
|
|
1160
|
-
underlying_seg:
|
|
1162
|
+
underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I,
|
|
1161
1163
|
expiry: "2024-01-25"
|
|
1162
1164
|
}
|
|
1163
1165
|
|
|
@@ -1183,7 +1185,7 @@ end
|
|
|
1183
1185
|
# Test valid historical data request
|
|
1184
1186
|
valid_params = {
|
|
1185
1187
|
security_id: "11536",
|
|
1186
|
-
exchange_segment:
|
|
1188
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1187
1189
|
from_date: Date.today - 7,
|
|
1188
1190
|
to_date: Date.today,
|
|
1189
1191
|
interval: 5
|
|
@@ -1252,7 +1254,7 @@ end
|
|
|
1252
1254
|
begin
|
|
1253
1255
|
order = DhanHQ::Models::Order.place(
|
|
1254
1256
|
transaction_type: "INVALID_TYPE",
|
|
1255
|
-
exchange_segment:
|
|
1257
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ
|
|
1256
1258
|
)
|
|
1257
1259
|
rescue DhanHQ::Error => e
|
|
1258
1260
|
puts "Validation error caught: #{e.message}"
|
|
@@ -1327,10 +1329,10 @@ puts "1. Available Margin: ₹#{funds.available_margin}"
|
|
|
1327
1329
|
# 2. Calculate margin
|
|
1328
1330
|
margin = DhanHQ::Models::Margin.calculate(
|
|
1329
1331
|
dhan_client_id: "1000000003",
|
|
1330
|
-
transaction_type:
|
|
1331
|
-
exchange_segment:
|
|
1332
|
-
product_type:
|
|
1333
|
-
order_type:
|
|
1332
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
1333
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1334
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
1335
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
1334
1336
|
security_id: "11536",
|
|
1335
1337
|
quantity: 1,
|
|
1336
1338
|
price: 3500.0
|
|
@@ -1340,11 +1342,11 @@ puts "2. Margin Required: ₹#{margin.margin_required}"
|
|
|
1340
1342
|
# 3. Place order
|
|
1341
1343
|
order = DhanHQ::Models::Order.place(
|
|
1342
1344
|
dhan_client_id: "1000000003",
|
|
1343
|
-
transaction_type:
|
|
1344
|
-
exchange_segment:
|
|
1345
|
-
product_type:
|
|
1346
|
-
order_type:
|
|
1347
|
-
validity:
|
|
1345
|
+
transaction_type: DhanHQ::Constants::TransactionType::BUY,
|
|
1346
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1347
|
+
product_type: DhanHQ::Constants::ProductType::MARGIN,
|
|
1348
|
+
order_type: DhanHQ::Constants::OrderType::LIMIT,
|
|
1349
|
+
validity: DhanHQ::Constants::Validity::DAY,
|
|
1348
1350
|
security_id: "11536",
|
|
1349
1351
|
quantity: 1,
|
|
1350
1352
|
price: 3500.0
|
|
@@ -1363,7 +1365,7 @@ orders_client.start
|
|
|
1363
1365
|
# 5. Modify order (if pending)
|
|
1364
1366
|
sleep(2)
|
|
1365
1367
|
order.refresh
|
|
1366
|
-
if order.order_status ==
|
|
1368
|
+
if order.order_status == DhanHQ::Constants::OrderStatus::PENDING
|
|
1367
1369
|
if order.modify(price: 3501.0)
|
|
1368
1370
|
puts "5. Order Modified"
|
|
1369
1371
|
end
|
|
@@ -1390,7 +1392,7 @@ puts "=== Test Complete ==="
|
|
|
1390
1392
|
# Get historical data and analyze
|
|
1391
1393
|
historical_data = DhanHQ::Models::HistoricalData.daily(
|
|
1392
1394
|
security_id: "11536",
|
|
1393
|
-
exchange_segment:
|
|
1395
|
+
exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
|
|
1394
1396
|
from_date: Date.today - 30,
|
|
1395
1397
|
to_date: Date.today
|
|
1396
1398
|
)
|
|
@@ -1419,13 +1421,13 @@ end
|
|
|
1419
1421
|
# Get option chain
|
|
1420
1422
|
expiry_list = DhanHQ::Models::OptionChain.fetch_expiry_list(
|
|
1421
1423
|
underlying_scrip: "NIFTY",
|
|
1422
|
-
underlying_seg:
|
|
1424
|
+
underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I
|
|
1423
1425
|
)
|
|
1424
1426
|
|
|
1425
1427
|
expiry = expiry_list.first[:expiry_date]
|
|
1426
1428
|
option_chain = DhanHQ::Models::OptionChain.fetch(
|
|
1427
1429
|
underlying_scrip: "NIFTY",
|
|
1428
|
-
underlying_seg:
|
|
1430
|
+
underlying_seg: DhanHQ::Constants::ExchangeSegment::IDX_I,
|
|
1429
1431
|
expiry: expiry
|
|
1430
1432
|
)
|
|
1431
1433
|
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Troubleshooting
|
|
2
|
+
|
|
3
|
+
Common issues and solutions when working with the DhanHQ Ruby client.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## 429: Unexpected Response Code
|
|
8
|
+
|
|
9
|
+
**Symptom:** WebSocket connection fails with a 429 status.
|
|
10
|
+
|
|
11
|
+
**Cause:** Too many connections opened in quick succession, or exceeding the per-user WebSocket connection limit (5 per user).
|
|
12
|
+
|
|
13
|
+
**Solution:**
|
|
14
|
+
- The client automatically cools off for **60 seconds** and retries with exponential backoff.
|
|
15
|
+
- Prefer `ws.disconnect!` before reconnecting to cleanly release server-side resources.
|
|
16
|
+
- Call `DhanHQ::WS.disconnect_all_local!` to kill any straggler connections.
|
|
17
|
+
- Avoid rapid connect/disconnect loops — the client handles backoff internally.
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Kill all local WebSocket connections
|
|
21
|
+
DhanHQ::WS.disconnect_all_local!
|
|
22
|
+
|
|
23
|
+
# Wait before reconnecting
|
|
24
|
+
sleep(2)
|
|
25
|
+
|
|
26
|
+
# Reconnect
|
|
27
|
+
client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## No Ticks After Reconnect
|
|
33
|
+
|
|
34
|
+
**Symptom:** WebSocket reconnects successfully but no market data arrives.
|
|
35
|
+
|
|
36
|
+
**Cause:** Subscriptions were not restored after the connection dropped.
|
|
37
|
+
|
|
38
|
+
**Solution:**
|
|
39
|
+
- The client **automatically resends** the current subscription snapshot on reconnect — this should work transparently.
|
|
40
|
+
- If you're managing connections manually, ensure you re-subscribe after a clean start.
|
|
41
|
+
- Check that your instruments are valid and the market is open.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## Binary Parse Errors
|
|
46
|
+
|
|
47
|
+
**Symptom:** Errors in logs related to binary frame parsing.
|
|
48
|
+
|
|
49
|
+
**Cause:** Malformed or unexpected binary frames from the server.
|
|
50
|
+
|
|
51
|
+
**Solution:**
|
|
52
|
+
- The client safely drops malformed frames and keeps the event loop alive.
|
|
53
|
+
- Run with `DHAN_LOG_LEVEL=DEBUG` to inspect raw frames:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
export DHAN_LOG_LEVEL=DEBUG
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
DhanHQ.logger.level = Logger::DEBUG
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Authentication Errors
|
|
66
|
+
|
|
67
|
+
| Error Class | Meaning |
|
|
68
|
+
| ------------------------------------ | ---------------------------------------------------------- |
|
|
69
|
+
| `DhanHQ::AuthenticationError` | Token could not be resolved (missing config, nil provider) |
|
|
70
|
+
| `DhanHQ::InvalidAuthenticationError` | API returned 401 or error code DH-901 |
|
|
71
|
+
| `DhanHQ::TokenExpiredError` | API returned error code 807 (token expired) |
|
|
72
|
+
| `DhanHQ::InvalidTokenError` | API returned error code 809 (invalid token) |
|
|
73
|
+
|
|
74
|
+
**Solutions:**
|
|
75
|
+
- Verify `DHAN_CLIENT_ID` and `DHAN_ACCESS_TOKEN` are set correctly.
|
|
76
|
+
- If using `access_token_provider`, ensure it returns a non-nil string.
|
|
77
|
+
- For 401 retries: the client retries **once** with a fresh token when `access_token_provider` is configured.
|
|
78
|
+
- See [AUTHENTICATION.md](AUTHENTICATION.md) for detailed token lifecycle handling.
|
|
79
|
+
|
|
80
|
+
---
|
|
81
|
+
|
|
82
|
+
## Connection Timeouts
|
|
83
|
+
|
|
84
|
+
**Symptom:** REST API calls hang or fail with timeout errors.
|
|
85
|
+
|
|
86
|
+
**Solution:** Adjust timeout settings via environment variables:
|
|
87
|
+
|
|
88
|
+
```dotenv
|
|
89
|
+
DHAN_CONNECT_TIMEOUT=15 # default: 10 seconds
|
|
90
|
+
DHAN_READ_TIMEOUT=60 # default: 30 seconds
|
|
91
|
+
DHAN_WRITE_TIMEOUT=60 # default: 30 seconds
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Debug Logging
|
|
97
|
+
|
|
98
|
+
Enable full debug output to diagnose any issue:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
DhanHQ.logger.level = Logger::DEBUG
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
Or via environment:
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
export DHAN_LOG_LEVEL=DEBUG
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
This logs HTTP requests/responses, WebSocket frames, and internal state transitions.
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Getting Help
|
|
115
|
+
|
|
116
|
+
- [DhanHQ GitHub Issues](https://github.com/shubhamtaywade82/dhanhq-client/issues)
|
|
117
|
+
- [Dhan API Documentation](https://dhanhq.co/docs/v2/)
|
|
@@ -45,10 +45,10 @@ market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
|
|
45
45
|
end
|
|
46
46
|
|
|
47
47
|
# Subscribe to specific indices
|
|
48
|
-
market_client.subscribe_one(segment:
|
|
49
|
-
market_client.subscribe_one(segment:
|
|
50
|
-
market_client.subscribe_one(segment:
|
|
51
|
-
market_client.subscribe_one(segment:
|
|
48
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13") # NIFTY
|
|
49
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25") # BANKNIFTY
|
|
50
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "29") # NIFTYIT
|
|
51
|
+
market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "51") # SENSEX
|
|
52
52
|
|
|
53
53
|
# Clean shutdown
|
|
54
54
|
market_client.stop
|
|
@@ -136,20 +136,20 @@ end
|
|
|
136
136
|
client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts tick[:ltp] }
|
|
137
137
|
|
|
138
138
|
# Subscribe to individual instruments
|
|
139
|
-
client.subscribe_one(segment:
|
|
140
|
-
client.subscribe_one(segment:
|
|
141
|
-
client.subscribe_one(segment:
|
|
139
|
+
client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13") # NIFTY
|
|
140
|
+
client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25") # BANKNIFTY
|
|
141
|
+
client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "2885") # RELIANCE
|
|
142
142
|
|
|
143
143
|
# Subscribe to multiple instruments
|
|
144
144
|
instruments = [
|
|
145
|
-
{ ExchangeSegment:
|
|
146
|
-
{ ExchangeSegment:
|
|
147
|
-
{ ExchangeSegment:
|
|
145
|
+
{ ExchangeSegment: DhanHQ::Constants::ExchangeSegment::IDX_I, SecurityId: "13" },
|
|
146
|
+
{ ExchangeSegment: DhanHQ::Constants::ExchangeSegment::IDX_I, SecurityId: "25" },
|
|
147
|
+
{ ExchangeSegment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, SecurityId: "2885" }
|
|
148
148
|
]
|
|
149
149
|
client.subscribe_many(instruments)
|
|
150
150
|
|
|
151
151
|
# Unsubscribe
|
|
152
|
-
client.unsubscribe_one(segment:
|
|
152
|
+
client.unsubscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
|
|
153
153
|
```
|
|
154
154
|
|
|
155
155
|
#### Finding Correct Security IDs
|
|
@@ -297,8 +297,8 @@ end
|
|
|
297
297
|
|
|
298
298
|
# Method 2: Direct specification (legacy)
|
|
299
299
|
symbols_direct = [
|
|
300
|
-
{ symbol: "RELIANCE", exchange_segment:
|
|
301
|
-
{ symbol: "TCS", exchange_segment:
|
|
300
|
+
{ symbol: "RELIANCE", exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "2885" },
|
|
301
|
+
{ symbol: "TCS", exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "11536" }
|
|
302
302
|
]
|
|
303
303
|
|
|
304
304
|
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols_direct) do |depth_data|
|
|
@@ -333,8 +333,8 @@ client.start
|
|
|
333
333
|
|
|
334
334
|
# Subscribe to symbols
|
|
335
335
|
symbols = [
|
|
336
|
-
{ symbol: "RELIANCE", exchange_segment:
|
|
337
|
-
{ symbol: "TCS", exchange_segment:
|
|
336
|
+
{ symbol: "RELIANCE", exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "2885" },
|
|
337
|
+
{ symbol: "TCS", exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, security_id: "11536" }
|
|
338
338
|
]
|
|
339
339
|
client.subscribe(symbols)
|
|
340
340
|
```
|
|
@@ -587,10 +587,10 @@ class MarketDataService
|
|
|
587
587
|
end
|
|
588
588
|
|
|
589
589
|
# Subscribe to indices
|
|
590
|
-
@market_client.subscribe_one(segment:
|
|
591
|
-
@market_client.subscribe_one(segment:
|
|
592
|
-
@market_client.subscribe_one(segment:
|
|
593
|
-
@market_client.subscribe_one(segment:
|
|
590
|
+
@market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13") # NIFTY
|
|
591
|
+
@market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25") # BANKNIFTY
|
|
592
|
+
@market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "29") # NIFTYIT
|
|
593
|
+
@market_client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "51") # SENSEX
|
|
594
594
|
end
|
|
595
595
|
|
|
596
596
|
def stop_market_feed
|