DhanHQ 2.5.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.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/CHANGELOG.md +70 -6
  4. data/GUIDE.md +57 -39
  5. data/README.md +24 -23
  6. data/docs/API_DOCS_GAPS.md +128 -0
  7. data/docs/API_VERIFICATION.md +10 -11
  8. data/docs/ARCHIVE_README.md +16 -16
  9. data/docs/AUTHENTICATION.md +1 -1
  10. data/docs/CONSTANTS_REFERENCE.md +477 -0
  11. data/docs/DATA_API_PARAMETERS.md +7 -7
  12. data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
  13. data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
  14. data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
  15. data/docs/TESTING_GUIDE.md +84 -82
  16. data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
  17. data/docs/WEBSOCKET_PROTOCOL.md +2 -2
  18. data/lib/DhanHQ/constants.rb +456 -151
  19. data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
  20. data/lib/DhanHQ/contracts/base_contract.rb +22 -0
  21. data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
  22. data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
  23. data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
  24. data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
  25. data/lib/DhanHQ/contracts/order_contract.rb +171 -39
  26. data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
  27. data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
  28. data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
  29. data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
  30. data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
  31. data/lib/DhanHQ/core/base_model.rb +13 -4
  32. data/lib/DhanHQ/helpers/response_helper.rb +2 -2
  33. data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
  34. data/lib/DhanHQ/models/alert_order.rb +7 -11
  35. data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
  36. data/lib/DhanHQ/models/edis.rb +0 -9
  37. data/lib/DhanHQ/models/expired_options_data.rb +6 -12
  38. data/lib/DhanHQ/models/forever_order.rb +8 -5
  39. data/lib/DhanHQ/models/historical_data.rb +0 -8
  40. data/lib/DhanHQ/models/instrument.rb +1 -7
  41. data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
  42. data/lib/DhanHQ/models/kill_switch.rb +1 -11
  43. data/lib/DhanHQ/models/margin.rb +2 -2
  44. data/lib/DhanHQ/models/order.rb +107 -126
  45. data/lib/DhanHQ/models/order_update.rb +7 -13
  46. data/lib/DhanHQ/models/pnl_exit.rb +1 -9
  47. data/lib/DhanHQ/models/position.rb +1 -1
  48. data/lib/DhanHQ/models/postback.rb +4 -13
  49. data/lib/DhanHQ/models/profile.rb +0 -10
  50. data/lib/DhanHQ/models/super_order.rb +13 -3
  51. data/lib/DhanHQ/models/trade.rb +11 -23
  52. data/lib/DhanHQ/resources/ip_setup.rb +16 -5
  53. data/lib/DhanHQ/resources/kill_switch.rb +9 -7
  54. data/lib/DhanHQ/resources/orders.rb +41 -41
  55. data/lib/DhanHQ/version.rb +1 -1
  56. data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
  57. data/lib/DhanHQ/ws/orders/client.rb +6 -6
  58. data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
  59. data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
  60. data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
  61. metadata +20 -23
  62. data/TODO-1.md +0 -14
  63. data/TODO.md +0 -127
  64. data/app/services/live/order_update_guard_support.rb +0 -75
  65. data/app/services/live/order_update_hub.rb +0 -76
  66. data/app/services/live/order_update_persistence_support.rb +0 -68
  67. data/docs/PR_2.2.0.md +0 -48
  68. data/examples/comprehensive_websocket_examples.rb +0 -148
  69. data/examples/instrument_finder_test.rb +0 -195
  70. data/examples/live_order_updates.rb +0 -118
  71. data/examples/market_depth_example.rb +0 -144
  72. data/examples/market_feed_example.rb +0 -81
  73. data/examples/order_update_example.rb +0 -105
  74. data/examples/trading_fields_example.rb +0 -215
  75. /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
  76. /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 77553eb49e2a23ec764bc3baeebd5426f60054fbdbc4eb4c7c49d7f2c7e87515
4
- data.tar.gz: 9fc44cb543abbc725cdee1489c66a08bd5d2615772640b454e46d54fb7b139f3
3
+ metadata.gz: 30825da308f5f85870f4efe9b702f7e2f653840b396b3265d36383d4a8c56f5e
4
+ data.tar.gz: b2874edeb45b60b6e68b4d8c69b9e9e3c6a0b6d2734ca645168626afd27cde76
5
5
  SHA512:
6
- metadata.gz: 47344dfbf5aba9b0c48e05937e3d9ff28d7a43db87442d1e695b84477568d5df02e83c918e884000751f094996e43984000b8f4c32bec235313f64673756b176
7
- data.tar.gz: f68c7be56db1807c284e01007cf0003f8519f92d57209d4073686f84e35d36db06e36f151c8b3b3df5dbae59bc272010c7e3ce4898e5a23eeddb5d6162f10b2c
6
+ metadata.gz: fe803337db74aa9689001b458627a532101fd0d298142b74ed0fefd87e7938ef4fd2b5e75bb53d871d8c535df35347065a43428986d930e6a0fa1ebae0c19aaf
7
+ data.tar.gz: aae78dbbbea337312262239f083c8fbb96f65b51b2d70cb9dce2683204f5e506d5a0835c85af6766ce0233262e79aaaf5dde45561eb410d51c52c74a185d5184
data/.rubocop.yml CHANGED
@@ -6,6 +6,7 @@ plugins:
6
6
 
7
7
  require:
8
8
  - rubocop-rake
9
+ - ./lib/rubocop/cop/dhanhq/use_constants.rb
9
10
 
10
11
  AllCops:
11
12
  TargetRubyVersion: 3.2
@@ -25,4 +26,11 @@ RSpec/ExampleLength:
25
26
  Max: 15
26
27
 
27
28
  RSpec/MultipleMemoizedHelpers:
28
- Max: 10
29
+ Max: 10
30
+
31
+ DhanHQ/UseConstants:
32
+ Enabled: true
33
+ Exclude:
34
+ - 'lib/DhanHQ/constants.rb'
35
+ - 'lib/DhanHQ/ws/segments.rb'
36
+ - 'spec/**/*'
data/CHANGELOG.md CHANGED
@@ -1,3 +1,67 @@
1
+ ## [2.6.0] - 2026-03-06
2
+
3
+ ### Fixed (API docs alignment)
4
+
5
+ - **Kill Switch**: Manage API now uses query parameter per [dhanhq.co/docs/v2/traders-control](https://dhanhq.co/docs/v2/traders-control/). `Resources::KillSwitch#update(status)` sends `POST /v2/killswitch?killSwitchStatus=ACTIVATE` (or `DEACTIVATE`) with no body. `Models::KillSwitch.update("ACTIVATE")` / `.activate` / `.deactivate` unchanged.
6
+ - **IP Setup**: Set/Modify now send required API fields. `Resources::IPSetup#set` and `#update` accept `ip:`, `ip_flag: "PRIMARY"` (or `"SECONDARY"`), and optional `dhan_client_id:` (defaults from `DhanHQ.configuration.client_id`). See [dhanhq.co/docs/v2/authentication/#setup-static-ip](https://dhanhq.co/docs/v2/authentication/#setup-static-ip).
7
+ - **Alert Orders (Conditional Trigger)**: Condition now requires `exchange_segment`, `exp_date`, and `frequency` per [dhanhq.co/docs/v2/conditional-trigger](https://dhanhq.co/docs/v2/conditional-trigger/). `time_frame` is required when `comparison_type` starts with `TECHNICAL`. `AlertOrderContract` and all examples updated.
8
+
9
+ ### Breaking changes
10
+
11
+ - **AlertOrder.create / AlertOrderContract**: Contract expects nested structure (see 2.5.0). Condition hash must include `exchange_segment`, `exp_date`, and `frequency`; for `comparison_type` starting with `TECHNICAL`, `time_frame` is required. See GUIDE.md and [conditional-trigger](https://dhanhq.co/docs/v2/conditional-trigger/).
12
+ - **Resources::KillSwitch#update**: Signature is now `update(status)` (string). Use `update("ACTIVATE")` or `Models::KillSwitch.activate` / `.deactivate`, unchanged.
13
+ - **AlertOrderContract** — expected payload shape:
14
+
15
+ ```ruby
16
+ AlertOrderContract.new.call(
17
+ condition: {
18
+ exchange_segment: "NSE_EQ",
19
+ security_id: "11536",
20
+ comparison_type: "PRICE_WITH_VALUE",
21
+ operator: "GREATER_THAN",
22
+ exp_date: "2026-12-31",
23
+ frequency: "ONCE"
24
+ },
25
+ orders: [
26
+ { transaction_type: "BUY", exchange_segment: "NSE_EQ", product_type: "INTRADAY", order_type: "MARKET", security_id: "11536", quantity: 5, validity: "DAY" }
27
+ ]
28
+ )
29
+ ```
30
+
31
+ ### Added
32
+
33
+ #### Order Model — New Public Methods
34
+ - **`Order#destroy` / `#delete`**: Cancels an order via `DELETE /v2/orders/{id}`. Returns `true` if the API confirms `CANCELLED` status, `false` otherwise. `#delete` is an alias.
35
+ - **`Order#slice_order(params)`**: Splits a large order into multiple legs to exceed freeze-limit quantities on F&O instruments via `POST /v2/slicing`. Delegates to `Resources::Orders#slicing`.
36
+ - **`Order#save`**: ActiveRecord-style save — places a new order for new records, modifies existing records. Returns `true`/`false`.
37
+ - **`Order.place` — `dhan_client_id` auto-injection**: If `dhan_client_id` is not passed in params, it is automatically read from `DhanHQ.configuration.client_id`. Existing code that passes `dhan_client_id` explicitly continues to work unchanged.
38
+
39
+ #### Contract Hardening
40
+ - **`OrderContract`** (base for `PlaceOrderContract` and `ModifyOrderContract`) now enforces:
41
+ - `LIMIT` orders require `price`; `MARKET` orders reject `price`
42
+ - `STOP_LOSS` / `STOP_LOSS_MARKET` require `trigger_price`
43
+ - Stop-loss price relationships: BUY requires `trigger_price >= price`; SELL requires `trigger_price <= price`
44
+ - Bracket Order (BO): both `bo_profit_value` and `bo_stop_loss_value` required; directional profit/loss relationship validated
45
+ - `disclosed_quantity` cannot exceed 30% of `quantity`
46
+ - `amo_time` required when `after_market_order: true`
47
+ - Lot-size and tick-size enforcement when `instrument_meta` is provided
48
+ - Segment-based product restrictions (CNC equity-only, BO/CO currency restrictions)
49
+ - **`ModifyOrderContract`**: Requires at least one modifiable field; inherits all `OrderContract` business rules.
50
+ - **New contracts**: `EdisContract`, `UserIpContract`, `PnlBasedExitContract`, `MultiScripMarginCalcContract`, `SliceOrderContract`.
51
+
52
+ #### Infrastructure
53
+ - **`ApiResponseHandler` concern** (`lib/DhanHQ/models/concerns/api_response_handler.rb`): Shared module for uniform API response handling, attribute merging, and structured logging. Included in `Order` and `ForeverOrder`.
54
+ - **Global Constants Enforcement**: Replaced all hardcoded API strings with `DhanHQ::Constants` across the repository. Built a custom RuboCop cop (`RuboCop::Cop::DhanHQ::UseConstants`) that strictly enforces typed constants instead of loose strings for robust API payloads.
55
+ - **Constants Documentation**: Added `docs/CONSTANTS_REFERENCE.md` detailing all SDK constants (e.g., `ExchangeSegment`, `ProductType`, `OrderType`, etc.).
56
+
57
+ ### Changed
58
+ - Replaced 160+ hardcoded usages of strings like `"NSE_EQ"` and `"BUY"` with `DhanHQ::Constants::ExchangeSegment::NSE_EQ` and `DhanHQ::Constants::TransactionType::BUY`.
59
+ - Added `NO_HOLDINGS` (value `"DH-1111"`) to `TradingErrorCode`.
60
+ - `PlaceOrderContract` refactored to inherit from `OrderContract`, eliminating duplicated validation logic. Derivative-specific fields (`drv_expiry_date`, `drv_option_type`, `drv_strike_price`) remain on `PlaceOrderContract`.
61
+ - `Resources::Orders` now fetches optional instrument metadata (lot size, tick size) to pass into contract validation.
62
+
63
+ ---
64
+
1
65
  ## [2.5.0] - 2026-02-21
2
66
 
3
67
  ### Added
@@ -55,7 +119,7 @@
55
119
 
56
120
  ### Added
57
121
  - **Alert Orders**: `DhanHQ::Resources::AlertOrders` (BaseResource) and `DhanHQ::Models::AlertOrder` with full CRUD. Endpoints: GET/POST `/alerts/orders`, GET/PUT/DELETE `/alerts/orders/{id}` (per API docs). Validation via `DhanHQ::Contracts::AlertOrderContract`.
58
- - **IP Setup**: `DhanHQ::Resources::IPSetup` (resource-only). Methods: `current` (GET `/ip/getIP`), `set(ip:)` (POST `/ip/setIP`), `update(ip:)` (PUT `/ip/modifyIP`) per API docs.
122
+ - **IP Setup**: `DhanHQ::Resources::IPSetup` (resource-only). Methods: `current` (GET `/ip/getIP`), `set(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)` (POST `/ip/setIP`), `update(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)` (PUT `/ip/modifyIP`). Body includes `dhanClientId` (default from config) and `ipFlag` per API docs.
59
123
  - **Trader Control (Kill Switch)**: `DhanHQ::Resources::TraderControl` (resource-only). Methods: `status` (GET `/trader-control`), `enable` (POST action ENABLE), `disable` (POST action DISABLE). `DhanHQ::Resources::KillSwitch` and `DhanHQ::Models::KillSwitch` remain for backward compatibility.
60
124
  - **docs/API_VERIFICATION.md**: Documents alignment with [dhanhq.co/docs/v2](https://dhanhq.co/docs/v2/) and [api.dhan.co/v2](https://api.dhan.co/v2/#/) for EDIS, Alert Orders, IP Setup.
61
125
 
@@ -92,9 +156,9 @@
92
156
  - **README.md**: Note under Dynamic access token for RenewToken via `DhanHQ::Auth.renew_token` and that API key/Partner flows are implemented in the app.
93
157
  - **GUIDE.md**: “Dynamic access token” section extended with RenewToken (`DhanHQ::Auth.renew_token`) and note that API key/Partner flows are in the app.
94
158
  - **docs/TESTING_GUIDE.md**: Optional config comment for RenewToken and pointer to AUTHENTICATION.md (API key/Partner in app).
95
- - **docs/rails_integration.md**: “Dynamic access token” section extended with RenewToken (web-generated tokens) and link to AUTHENTICATION.md.
96
- - **docs/websocket_integration.md**, **docs/live_order_updates.md**: Notes updated for dynamic token, RenewToken, and API key/Partner in app.
97
- - **docs/standalone_ruby_websocket_integration.md**, **docs/rails_websocket_integration.md**: Configuration section updated with RenewToken and AUTHENTICATION.md link.
159
+ - **docs/RAILS_INTEGRATION.md**: “Dynamic access token” section extended with RenewToken (web-generated tokens) and link to AUTHENTICATION.md.
160
+ - **docs/WEBSOCKET_INTEGRATION.md**, **docs/LIVE_ORDER_UPDATES.md**: Notes updated for dynamic token, RenewToken, and API key/Partner in app.
161
+ - **docs/STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md**, **docs/RAILS_WEBSOCKET_INTEGRATION.md**: Configuration section updated with RenewToken and AUTHENTICATION.md link.
98
162
 
99
163
  ### CI / Release
100
164
 
@@ -144,8 +208,8 @@
144
208
  - **GUIDE.md**: Short “Dynamic access token” note and link to docs/AUTHENTICATION.md.
145
209
  - **docs/AUTHENTICATION.md**: New doc for static vs dynamic token, retry-on-401, and auth-related errors.
146
210
  - **docs/TESTING_GUIDE.md**: Optional access_token_provider / on_token_expired in config examples.
147
- - **docs/rails_integration.md**: “Dynamic access token (optional)” with Rails initializer example.
148
- - **docs/websocket_integration.md**, **docs/live_order_updates.md**: Pointer to docs/AUTHENTICATION.md for dynamic token.
211
+ - **docs/RAILS_INTEGRATION.md**: “Dynamic access token (optional)” with Rails initializer example.
212
+ - **docs/WEBSOCKET_INTEGRATION.md**, **docs/LIVE_ORDER_UPDATES.md**: Pointer to docs/AUTHENTICATION.md for dynamic token.
149
213
 
150
214
  ### Backward compatibility
151
215
 
data/GUIDE.md CHANGED
@@ -150,11 +150,11 @@ Example:
150
150
 
151
151
  ```ruby
152
152
  payload = {
153
- transaction_type: "BUY",
154
- exchange_segment: "NSE_EQ",
155
- product_type: "CNC",
156
- order_type: "LIMIT",
157
- validity: "DAY",
153
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
154
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
155
+ product_type: DhanHQ::Constants::ProductType::CNC,
156
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
157
+ validity: DhanHQ::Constants::Validity::DAY,
158
158
  security_id: "1333",
159
159
  quantity: 10,
160
160
  price: 150.0,
@@ -162,7 +162,7 @@ payload = {
162
162
  }
163
163
 
164
164
  order = DhanHQ::Models::Order.place(payload)
165
- puts order.order_status # => "TRADED" / "PENDING" / ...
165
+ puts order.order_status # => DhanHQ::Constants::OrderStatus::TRADED / "PENDING" / ...
166
166
  ```
167
167
 
168
168
  ### Modification & Cancellation
@@ -193,10 +193,10 @@ Use the same fields as placement, but the contract allows additional validity op
193
193
  ```ruby
194
194
  slice_payload = {
195
195
  order_id: order.order_id,
196
- transaction_type: "BUY",
197
- exchange_segment: "NSE_EQ",
198
- product_type: "STOP_LOSS",
199
- order_type: "STOP_LOSS",
196
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
197
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
198
+ product_type: DhanHQ::Constants::OrderType::STOP_LOSS,
199
+ order_type: DhanHQ::Constants::OrderType::STOP_LOSS,
200
200
  validity: "GTC",
201
201
  security_id: "1333",
202
202
  quantity: 100,
@@ -381,11 +381,11 @@ The response returns one object per super order with nested `leg_details`. Key a
381
381
 
382
382
  ```ruby
383
383
  params = {
384
- transaction_type: "SELL",
385
- exchange_segment: "NSE_EQ",
386
- product_type: "CNC",
387
- order_type: "LIMIT",
388
- validity: "DAY",
384
+ transaction_type: DhanHQ::Constants::TransactionType::SELL,
385
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
386
+ product_type: DhanHQ::Constants::ProductType::CNC,
387
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
388
+ validity: DhanHQ::Constants::Validity::DAY,
389
389
  security_id: "1333",
390
390
  price: 200.0,
391
391
  trigger_price: 198.0
@@ -416,10 +416,10 @@ Convert an intraday position to delivery (or vice versa):
416
416
  ```ruby
417
417
  convert_payload = {
418
418
  security_id: "1333",
419
- from_product_type: "INTRADAY",
420
- to_product_type: "CNC",
419
+ from_product_type: DhanHQ::Constants::ProductType::INTRADAY,
420
+ to_product_type: DhanHQ::Constants::ProductType::CNC,
421
421
  convert_qty: 10,
422
- exchange_segment: "NSE_EQ",
422
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
423
423
  position_type: "LONG"
424
424
  }
425
425
 
@@ -496,8 +496,8 @@ Both endpoints return arrays and skip validation because they represent historic
496
496
  ```ruby
497
497
  bars = DhanHQ::Models::HistoricalData.intraday(
498
498
  security_id: "13",
499
- exchange_segment: "IDX_I",
500
- instrument: "INDEX",
499
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::IDX_I,
500
+ instrument: DhanHQ::Constants::InstrumentType::INDEX,
501
501
  interval: "5",
502
502
  from_date: "2024-08-14",
503
503
  to_date: "2024-08-14"
@@ -509,13 +509,13 @@ bars = DhanHQ::Models::HistoricalData.intraday(
509
509
  ```ruby
510
510
  chain = DhanHQ::Models::OptionChain.fetch(
511
511
  underlying_scrip: 1333,
512
- underlying_seg: "NSE_FNO",
512
+ underlying_seg: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
513
513
  expiry: "2024-12-26"
514
514
  )
515
515
 
516
516
  expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(
517
517
  underlying_scrip: 1333,
518
- underlying_seg: "NSE_FNO"
518
+ underlying_seg: DhanHQ::Constants::ExchangeSegment::NSE_FNO
519
519
  )
520
520
  ```
521
521
 
@@ -527,10 +527,10 @@ The model filters strikes where both CE and PE have zero `last_price`, keeping t
527
527
 
528
528
  ```ruby
529
529
  params = {
530
- exchange_segment: "NSE_EQ",
531
- transaction_type: "BUY",
530
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
531
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
532
532
  quantity: 10,
533
- product_type: "INTRADAY",
533
+ product_type: DhanHQ::Constants::ProductType::INTRADAY,
534
534
  security_id: "1333",
535
535
  price: 150.0
536
536
  }
@@ -618,8 +618,8 @@ ws.on(:tick) do |tick|
618
618
  puts "[#{tick[:segment]} #{tick[:security_id]}] LTP=#{tick[:ltp]} kind=#{tick[:kind]}"
619
619
  end
620
620
 
621
- ws.subscribe_one(segment: "IDX_I", security_id: "13")
622
- ws.unsubscribe_one(segment: "IDX_I", security_id: "13")
621
+ ws.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
622
+ ws.unsubscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
623
623
 
624
624
  ws.disconnect!
625
625
  ```
@@ -643,17 +643,34 @@ If the credentials are invalid the helper raises `DhanHQ::InvalidAuthenticationE
643
643
 
644
644
  ### Alert Orders
645
645
 
646
+ Condition must include `exchange_segment`, `exp_date`, and `frequency` per [conditional-trigger API](https://dhanhq.co/docs/v2/conditional-trigger/). For technical indicators, `time_frame` is also required.
647
+
646
648
  ```ruby
647
649
  # Model (CRUD)
648
650
  DhanHQ::Models::AlertOrder.all
649
651
  alert = DhanHQ::Models::AlertOrder.find("alert-id")
650
652
  alert = DhanHQ::Models::AlertOrder.create(
651
- exchange_segment: "NSE_EQ",
652
- security_id: "11536",
653
- condition: "GTE",
654
- trigger_price: 100.0,
655
- transaction_type: "BUY",
656
- quantity: 10
653
+ condition: {
654
+ security_id: "11536",
655
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
656
+ comparison_type: DhanHQ::Constants::ComparisonType::PRICE_WITH_VALUE,
657
+ operator: DhanHQ::Constants::Operator::GREATER_THAN,
658
+ comparing_value: 100.0,
659
+ exp_date: (Date.today + 365).strftime("%Y-%m-%d"),
660
+ frequency: "ONCE"
661
+ },
662
+ orders: [
663
+ {
664
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
665
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
666
+ product_type: DhanHQ::Constants::ProductType::CNC,
667
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
668
+ security_id: "11536",
669
+ quantity: 10,
670
+ validity: DhanHQ::Constants::Validity::DAY,
671
+ price: 150.0
672
+ }
673
+ ]
657
674
  )
658
675
  alert.save
659
676
  alert.destroy
@@ -687,13 +704,14 @@ Params use snake_case; the client camelizes them before calling `/edis/...`.
687
704
 
688
705
  ### IP Setup
689
706
 
690
- Resource-only (account configuration) per API docs: GET /ip/getIP, POST /ip/setIP, PUT /ip/modifyIP.
707
+ Resource-only (account configuration) per [API docs](https://dhanhq.co/docs/v2/authentication/#setup-static-ip): GET /ip/getIP, POST /ip/setIP, PUT /ip/modifyIP. Set/Modify require `dhanClientId`, `ip`, and `ipFlag` (PRIMARY or SECONDARY).
691
708
 
692
709
  ```ruby
693
710
  ip = DhanHQ::Resources::IPSetup.new
694
- ip.current # GET /ip/getIP
695
- ip.set(ip: "103.21.58.121")
696
- ip.update(ip: "103.21.58.121")
711
+ ip.current # GET /ip/getIP
712
+ ip.set(ip: "103.21.58.121") # ip_flag defaults to "PRIMARY", dhan_client_id from config
713
+ ip.set(ip: "103.21.58.121", ip_flag: "SECONDARY")
714
+ ip.update(ip: "103.21.58.121", ip_flag: "PRIMARY")
697
715
  ```
698
716
 
699
717
  ### Trader Control (Kill Switch)
@@ -709,7 +727,7 @@ tc.enable # Trading resumed
709
727
 
710
728
  ### Kill Switch (model)
711
729
 
712
- Existing model API for backward compatibility:
730
+ Uses query parameter per API doc: `POST /v2/killswitch?killSwitchStatus=ACTIVATE` (or DEACTIVATE), no body.
713
731
 
714
732
  ```ruby
715
733
  activate_payload = DhanHQ::Models::KillSwitch.activate
@@ -725,7 +743,7 @@ DhanHQ::Models::KillSwitch.snake_case(deactivate_payload)
725
743
  DhanHQ::Models::KillSwitch.update("ACTIVATE")
726
744
  ```
727
745
 
728
- Only `"ACTIVATE"` and `"DEACTIVATE"` are accepted—any other value raises `DhanHQ::Error`. Use the `snake_case` helper to normalise API responses when you prefer underscore keys.
746
+ Only `"ACTIVATE"` and `"DEACTIVATE"` are accepted. Use the `snake_case` helper to normalise API responses when you prefer underscore keys.
729
747
 
730
748
  ---
731
749
 
data/README.md CHANGED
@@ -124,11 +124,11 @@ When the API returns 401, the client retries **once** with a fresh token from yo
124
124
 
125
125
  ```ruby
126
126
  order = DhanHQ::Models::Order.new(
127
- transaction_type: "BUY",
128
- exchange_segment: "NSE_FNO",
129
- product_type: "MARGIN",
130
- order_type: "LIMIT",
131
- validity: "DAY",
127
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
128
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
129
+ product_type: DhanHQ::Constants::ProductType::MARGIN,
130
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
131
+ validity: DhanHQ::Constants::Validity::DAY,
132
132
  security_id: "43492",
133
133
  quantity: 50,
134
134
  price: 100.0
@@ -151,8 +151,8 @@ DhanHQ::Models::Fund.balance
151
151
  ```ruby
152
152
  bars = DhanHQ::Models::HistoricalData.intraday(
153
153
  security_id: "13",
154
- exchange_segment: "IDX_I",
155
- instrument: "INDEX",
154
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::IDX_I,
155
+ instrument: DhanHQ::Constants::InstrumentType::INDEX,
156
156
  interval: "5",
157
157
  from_date: "2025-08-14",
158
158
  to_date: "2025-08-18"
@@ -190,8 +190,8 @@ client = DhanHQ::WS.connect(mode: :ticker) do |tick|
190
190
  puts "#{tick[:security_id]} = ₹#{tick[:ltp]}"
191
191
  end
192
192
 
193
- client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
194
- client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
193
+ client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13") # NIFTY
194
+ client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25") # BANKNIFTY
195
195
  ```
196
196
 
197
197
  ### Market Depth
@@ -220,10 +220,10 @@ Entry + target + stop-loss + trailing jump in a single request:
220
220
 
221
221
  ```ruby
222
222
  DhanHQ::Models::SuperOrder.create(
223
- transaction_type: "BUY",
224
- exchange_segment: "NSE_EQ",
225
- product_type: "CNC",
226
- order_type: "LIMIT",
223
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
224
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ,
225
+ product_type: DhanHQ::Constants::ProductType::CNC,
226
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
227
227
  security_id: "11536",
228
228
  quantity: 5,
229
229
  price: 1500,
@@ -246,8 +246,8 @@ DhanHQ.configure_with_env
246
246
 
247
247
  # 1. Check the trend using historical 5-min bars
248
248
  bars = DhanHQ::Models::HistoricalData.intraday(
249
- security_id: "13", exchange_segment: "IDX_I",
250
- instrument: "INDEX", interval: "5",
249
+ security_id: "13", exchange_segment: DhanHQ::Constants::ExchangeSegment::IDX_I,
250
+ instrument: DhanHQ::Constants::InstrumentType::INDEX, interval: "5",
251
251
  from_date: Date.today.to_s, to_date: Date.today.to_s
252
252
  )
253
253
 
@@ -260,11 +260,11 @@ puts "NIFTY trend: #{trend} (LTP: #{closes.last}, SMA20: #{sma_20.round(2)})"
260
260
  client = DhanHQ::WS.connect(mode: :quote) do |tick|
261
261
  puts "NIFTY ₹#{tick[:ltp]} | Vol: #{tick[:vol]} | #{Time.now.strftime('%H:%M:%S')}"
262
262
  end
263
- client.subscribe_one(segment: "IDX_I", security_id: "13")
263
+ client.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13")
264
264
 
265
265
  # 3. On signal, place a super order with built-in risk management
266
266
  # DhanHQ::Models::SuperOrder.create(
267
- # transaction_type: "BUY", exchange_segment: "NSE_FNO", ...
267
+ # transaction_type: DhanHQ::Constants::TransactionType::BUY, exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO, ...
268
268
  # target_price: entry + 50, stop_loss_price: entry - 30, trailing_jump: 5
269
269
  # )
270
270
 
@@ -277,7 +277,7 @@ sleep # keep the script alive
277
277
 
278
278
  ## Rails Integration
279
279
 
280
- Need initializers, service objects, ActionCable wiring, and background workers? See the [Rails Integration Guide](docs/rails_integration.md).
280
+ Need initializers, service objects, ActionCable wiring, and background workers? See the [Rails Integration Guide](docs/RAILS_INTEGRATION.md).
281
281
 
282
282
  ---
283
283
 
@@ -287,15 +287,16 @@ Need initializers, service objects, ActionCable wiring, and background workers?
287
287
  | ----- | -------------- |
288
288
  | [Authentication](docs/AUTHENTICATION.md) | Token flows, TOTP, OAuth, auto-management |
289
289
  | [Configuration Reference](docs/CONFIGURATION.md) | Full ENV matrix, logging, timeouts, available resources |
290
- | [WebSocket Integration](docs/websocket_integration.md) | All WS types, architecture, best practices |
290
+ | [WebSocket Integration](docs/WEBSOCKET_INTEGRATION.md) | All WS types, architecture, best practices |
291
291
  | [WebSocket Protocol](docs/WEBSOCKET_PROTOCOL.md) | Packet parsing, request codes, tick schema, exchange enums |
292
- | [Rails WebSocket Guide](docs/rails_websocket_integration.md) | Rails-specific patterns, ActionCable |
293
- | [Rails Integration](docs/rails_integration.md) | Initializers, service objects, workers |
294
- | [Standalone Ruby Guide](docs/standalone_ruby_websocket_integration.md) | Scripts, daemons, CLI tools |
292
+ | [Rails WebSocket Guide](docs/RAILS_WEBSOCKET_INTEGRATION.md) | Rails-specific patterns, ActionCable |
293
+ | [Rails Integration](docs/RAILS_INTEGRATION.md) | Initializers, service objects, workers |
294
+ | [Standalone Ruby Guide](docs/STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md) | Scripts, daemons, CLI tools |
295
295
  | [Super Orders API](docs/SUPER_ORDERS.md) | Full REST reference for super orders |
296
+ | [API Constants Reference](docs/CONSTANTS_REFERENCE.md) | All valid enums, exchange segments, and order parameters |
296
297
  | [Data API Parameters](docs/DATA_API_PARAMETERS.md) | Historical data, option chain parameters |
297
298
  | [Testing Guide](docs/TESTING_GUIDE.md) | WebSocket testing, model testing, console helpers |
298
- | [Technical Analysis](docs/technical_analysis.md) | Indicators, multi-timeframe aggregation |
299
+ | [Technical Analysis](docs/TECHNICAL_ANALYSIS.md) | Indicators, multi-timeframe aggregation |
299
300
  | [Troubleshooting](docs/TROUBLESHOOTING.md) | 429 errors, reconnect, auth issues, debug logging |
300
301
  | [Release Guide](docs/RELEASE_GUIDE.md) | Versioning, publishing, changelog |
301
302
 
@@ -0,0 +1,128 @@
1
+ # DhanHQ v2 API docs vs dhanhq-client — gaps and fixes
2
+
3
+ Reference: [dhanhq.co/docs/v2](https://dhanhq.co/docs/v2/)
4
+
5
+ This document lists mismatches between the official DhanHQ v2 API documentation and the dhanhq-client gem, and suggested fixes.
6
+
7
+ ---
8
+
9
+ ## 1. Kill Switch — request format
10
+
11
+ **Doc:** [Trader's Control](https://dhanhq.co/docs/v2/traders-control/)
12
+
13
+ - **Manage Kill Switch:** *"You can pass header parameter as ACTIVATE or DEACTIVATE"* and the curl example uses a **query parameter**:
14
+ `POST https://api.dhan.co/v2/killswitch?killSwitchStatus=ACTIVATE`
15
+ **Request structure: No Body**
16
+
17
+ **Gem:** `Resources::KillSwitch#update(params)` sends a **POST body** with `kill_switch_status: "ACTIVATE"` or `"DEACTIVATE"`.
18
+
19
+ **Gap:** The API may expect `killSwitchStatus` as a **query parameter** (or header), not in the JSON body. That can explain `DH-905: Missing required fields` when calling activate/deactivate.
20
+
21
+ **Suggested fix:** In `Resources::KillSwitch#update(status)`, call the API with a query string, e.g. `post("?killSwitchStatus=#{status}")` (or equivalent), and do not send a body. If the live API accepts body and your integration works, this may be a doc inaccuracy; otherwise prefer matching the doc (query param).
22
+
23
+ **Fixed:** Resource now sends `POST /v2/killswitch?killSwitchStatus=ACTIVATE` (or DEACTIVATE) with no body. Model passes status string to resource.
24
+
25
+ ---
26
+
27
+ ## 2. IP Setup — missing required parameters
28
+
29
+ **Doc:** [Authentication → Setup Static IP](https://dhanhq.co/docs/v2/authentication/#setup-static-ip)
30
+
31
+ - **Set IP:** Body must include:
32
+ - `dhanClientId` (required)
33
+ - `ip` (required)
34
+ - `ipFlag` (required): `"PRIMARY"` or `"SECONDARY"`
35
+ - **Modify IP:** Same body as Set IP.
36
+
37
+ **Gem:** `Resources::IPSetup#set(ip:)` and `#update(ip:)` send only `{ ip: ip }`.
38
+
39
+ **Gap:** `dhanClientId` and `ipFlag` are not sent. Set/Modify IP may fail or behave incorrectly for accounts that require them.
40
+
41
+ **Suggested fix:** Add `dhan_client_id` and `ip_flag` to the resource (e.g. `set(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)`), defaulting `dhan_client_id` from `DhanHQ.configuration.client_id` when not provided.
42
+
43
+ **Fixed:** `IPSetup#set` and `#update` now accept `ip:, ip_flag: "PRIMARY"` (or `"SECONDARY"`), and `dhan_client_id:` (defaults from config when nil).
44
+
45
+ ---
46
+
47
+ ## 3. Alert Orders (Conditional Trigger) — condition fields
48
+
49
+ **Doc:** [Conditional Trigger](https://dhanhq.co/docs/v2/conditional-trigger/)
50
+
51
+ For **Place** and **Modify**:
52
+
53
+ - `condition.exchangeSegment` — **required**
54
+ - `condition.expDate` — **required** (date, default 1 year)
55
+ - `condition.frequency` — **required** (e.g. `"ONCE"`)
56
+ - `condition.timeFrame` — required for technical conditions (e.g. `"DAY"`, `"ONE_MIN"`)
57
+
58
+ **Gem:** `Contracts::AlertOrderContract` and `Models::AlertOrder`:
59
+
60
+ - Condition has: `security_id`, `comparison_type`, `operator`, and optional `indicator_name`, `time_frame`, `comparing_value`, `comparing_indicator_name`.
61
+ - **Missing from contract/model:** `exchange_segment`, `exp_date`, `frequency` in the condition hash.
62
+
63
+ **Gap:** Creating or modifying alert orders that satisfy the API may require these fields; the gem does not expose or validate them.
64
+
65
+ **Suggested fix:** Add `exchange_segment`, `exp_date`, and `frequency` to the alert order condition in the contract and model (and ensure they are sent in the API payload in the expected format, e.g. camelCase).
66
+
67
+ **Fixed:** `AlertOrderContract` condition now requires `exchange_segment`, `exp_date`, `frequency`; rule added so `time_frame` is required when `comparison_type` starts with `TECHNICAL`. Test script and specs updated.
68
+
69
+ ---
70
+
71
+ ## 4. EDIS — doc typo only
72
+
73
+ **Doc:** [EDIS](https://dhanhq.co/docs/v2/edis/) table lists `POST /edis/from` for the form endpoint.
74
+
75
+ **Gem:** Uses `POST /edis/form`.
76
+
77
+ **Conclusion:** The doc table has a typo ("from" vs "form"). The doc’s curl and request structure use `/edis/form`. No change needed in the gem.
78
+
79
+ ---
80
+
81
+ ## 5. Forever Order list — doc inconsistency
82
+
83
+ **Doc:** [Forever Order](https://dhanhq.co/docs/v2/forever/)
84
+
85
+ - Table: `GET /forever/orders` — retrieve all forever orders.
86
+ - Section "All Forever Order Detail": curl shows `GET /forever/all`.
87
+
88
+ **Gem:** Uses `GET /forever/orders` (e.g. `ForeverOrders#all` → `get("/orders")` with `HTTP_PATH = "/v2/forever"`).
89
+
90
+ **Conclusion:** Doc is inconsistent. The gem matches the table. If the API actually uses `/forever/all`, the resource path would need to change; otherwise no change.
91
+
92
+ ---
93
+
94
+ ## 6. P&amp;L Exit — dhanClientId
95
+
96
+ **Doc:** [Trader's Control → P&amp;L Based Exit](https://dhanhq.co/docs/v2/traders-control/) does not list `dhanClientId` in the request body for Configure.
97
+
98
+ **Gem:** Was updated to send `dhanClientId` from config when present, to fix `DH-905: dhanClientId is required`.
99
+
100
+ **Conclusion:** Either the doc omits this field or the API was updated to require it. The gem’s behaviour is aligned with the API response you saw; no further change unless the doc is updated.
101
+
102
+ ---
103
+
104
+ ## Summary table
105
+
106
+ | Area | Doc reference | Gap / note | Status |
107
+ |-----------------|----------------------|----------------------------------------------------------------------------|----------|
108
+ | Kill Switch | traders-control | Manage API expects query param `killSwitchStatus`, not body | **Fixed** |
109
+ | IP Setup | authentication | Set/Modify IP required `dhanClientId`, `ipFlag` | **Fixed** |
110
+ | Alert Orders | conditional-trigger | Condition required `exchangeSegment`, `expDate`, `frequency` | **Fixed** |
111
+ | EDIS | edis | Doc typo `/edis/from`; gem correctly uses `/edis/form` | None |
112
+ | Forever list | forever | Doc inconsistency `/forever/orders` vs `/forever/all`; gem uses `/orders` | Low |
113
+ | P&amp;L Exit | traders-control | Gem sends `dhanClientId`; doc does not mention it | None |
114
+
115
+ ---
116
+
117
+ ## Endpoints covered by the gem (no gaps found)
118
+
119
+ - **Orders:** Place, modify, cancel, slicing, order book, get by id, get by correlation id — paths and behaviour match the [Orders](https://dhanhq.co/docs/v2/orders/) doc.
120
+ - **Trades:** Trade book, trades by order id — match doc.
121
+ - **Super Order:** Create, modify, cancel, list — match [Super Order](https://dhanhq.co/docs/v2/super-order/) doc.
122
+ - **Forever Order:** Create, modify, cancel, list — paths match [Forever Order](https://dhanhq.co/docs/v2/forever/) table (list path as above).
123
+ - **Profile:** GET /v2/profile — match [Authentication → User Profile](https://dhanhq.co/docs/v2/authentication/#user-profile).
124
+ - **Trader’s Control:** Kill Switch status (GET), manage via query param (see above). P&amp;L Exit configure/stop/status — match doc.
125
+ - **EDIS:** tpin, form, bulkform, inquire — paths and params match [EDIS](https://dhanhq.co/docs/v2/edis/) (form URL as above).
126
+ - **Postback:** Incoming webhook payload; gem’s `Postback.parse` is for parsing, not an outgoing API — no endpoint gap.
127
+
128
+ The Kill Switch query-param change, IP Setup parameter additions, and Alert Order condition fields have been implemented in the gem. See CHANGELOG (Unreleased), GUIDE.md, docs/API_VERIFICATION.md, and docs/TESTING_GUIDE.md for updated documentation.
@@ -28,7 +28,7 @@ This document records how the gem’s implementation aligns with the official Dh
28
28
 
29
29
  ## Alert Orders
30
30
 
31
- **Doc ref:** `CODE_REVIEW_ISSUES.md` (§31) – Alert Orders endpoints.
31
+ **Doc ref:** [dhanhq.co/docs/v2/conditional-trigger](https://dhanhq.co/docs/v2/conditional-trigger/).
32
32
 
33
33
  | Doc path | Gem resource | Path used |
34
34
  |---------------------------|---------------------------|------------------|
@@ -36,27 +36,26 @@ This document records how the gem’s implementation aligns with the official Dh
36
36
  | GET/POST `/alerts/orders` | `#all`, `#create` | BaseResource |
37
37
  | GET/PUT/DELETE `/alerts/orders/{trigger-id}` | `#find`, `#update`, `#delete` | `/{id}` |
38
38
 
39
- Model: `Models::AlertOrder`; ID attribute `alert_id` (response field name may be `alertId` or `triggerId` depending on API; adjust if production returns `triggerId`).
39
+ Model: `Models::AlertOrder`. Condition must include `exchange_segment`, `exp_date`, `frequency`; `time_frame` required when `comparison_type` starts with `TECHNICAL`. Validated by `AlertOrderContract`.
40
40
 
41
41
  ---
42
42
 
43
43
  ## IP Setup
44
44
 
45
- **Doc ref:** `CODE_REVIEW_ISSUES.md` (§32) – IP Setup endpoints.
45
+ **Doc ref:** [dhanhq.co/docs/v2/authentication/#setup-static-ip](https://dhanhq.co/docs/v2/authentication/#setup-static-ip).
46
46
 
47
- | Doc path | Gem method | Path used |
48
- |-----------------|----------------|---------------|
49
- | GET /ip/getIP | `#current` | `get("/getIP")` |
50
- | POST /ip/setIP | `#set(ip:)` | `post("/setIP", params: { ip: ip })` |
51
- | PUT /ip/modifyIP| `#update(ip:)` | `put("/modifyIP", params: { ip: ip })` |
47
+ | Doc path | Gem method | Path / body |
48
+ |-----------------|----------------|-------------|
49
+ | GET /ip/getIP | `#current` | `get("/getIP")` |
50
+ | POST /ip/setIP | `#set(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)` | `post("/setIP", params: { ip:, ip_flag:, dhan_client_id: })`; `dhan_client_id` defaults from config |
51
+ | PUT /ip/modifyIP| `#update(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)` | `put("/modifyIP", params: { ip:, ip_flag:, dhan_client_id: })` |
52
52
 
53
53
  ---
54
54
 
55
55
  ## Trader Control / Kill Switch
56
56
 
57
- - **Existing:** `Resources::KillSwitch`, `Models::KillSwitch` – path `/v2/killswitch`.
58
- - **Added:** `Resources::TraderControl` – path `/trader-control`; `#status`, `#enable`, `#disable`.
59
- Not found on the public docs pages checked; kept for compatibility with design that referenced trader-control.
57
+ - **Kill Switch:** `Resources::KillSwitch`, `Models::KillSwitch` – path `/v2/killswitch`. Manage (activate/deactivate) uses **query parameter** per [traders-control](https://dhanhq.co/docs/v2/traders-control/): `POST /v2/killswitch?killSwitchStatus=ACTIVATE` (or `DEACTIVATE`) with no body. `#status` is GET.
58
+ - **Trader Control:** `Resources::TraderControl` – path `/trader-control`; `#status`, `#enable`, `#disable`. Not found on the public docs; kept for compatibility.
60
59
 
61
60
  ---
62
61