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.
Files changed (82) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +9 -1
  3. data/CHANGELOG.md +103 -7
  4. data/GUIDE.md +57 -39
  5. data/README.md +198 -755
  6. data/docs/API_DOCS_GAPS.md +128 -0
  7. data/docs/API_VERIFICATION.md +10 -11
  8. data/{README1.md → docs/ARCHIVE_README.md} +16 -16
  9. data/docs/AUTHENTICATION.md +72 -10
  10. data/docs/CONFIGURATION.md +109 -0
  11. data/docs/CONSTANTS_REFERENCE.md +477 -0
  12. data/docs/DATA_API_PARAMETERS.md +7 -7
  13. data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
  14. data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
  15. data/docs/SUPER_ORDERS.md +284 -0
  16. data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
  17. data/docs/TESTING_GUIDE.md +84 -82
  18. data/docs/TROUBLESHOOTING.md +117 -0
  19. data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
  20. data/docs/WEBSOCKET_PROTOCOL.md +154 -0
  21. data/lib/DhanHQ/constants.rb +456 -151
  22. data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
  23. data/lib/DhanHQ/contracts/base_contract.rb +22 -0
  24. data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
  25. data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
  26. data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
  27. data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
  28. data/lib/DhanHQ/contracts/order_contract.rb +171 -39
  29. data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
  30. data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
  31. data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
  32. data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
  33. data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
  34. data/lib/DhanHQ/core/base_model.rb +13 -4
  35. data/lib/DhanHQ/helpers/response_helper.rb +2 -2
  36. data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
  37. data/lib/DhanHQ/models/alert_order.rb +29 -11
  38. data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
  39. data/lib/DhanHQ/models/edis.rb +101 -0
  40. data/lib/DhanHQ/models/expired_options_data.rb +6 -12
  41. data/lib/DhanHQ/models/forever_order.rb +8 -5
  42. data/lib/DhanHQ/models/historical_data.rb +0 -8
  43. data/lib/DhanHQ/models/instrument.rb +1 -7
  44. data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
  45. data/lib/DhanHQ/models/kill_switch.rb +23 -11
  46. data/lib/DhanHQ/models/margin.rb +51 -2
  47. data/lib/DhanHQ/models/order.rb +107 -126
  48. data/lib/DhanHQ/models/order_update.rb +7 -13
  49. data/lib/DhanHQ/models/pnl_exit.rb +122 -0
  50. data/lib/DhanHQ/models/position.rb +23 -1
  51. data/lib/DhanHQ/models/postback.rb +114 -0
  52. data/lib/DhanHQ/models/profile.rb +0 -10
  53. data/lib/DhanHQ/models/super_order.rb +13 -3
  54. data/lib/DhanHQ/models/trade.rb +11 -23
  55. data/lib/DhanHQ/resources/ip_setup.rb +16 -5
  56. data/lib/DhanHQ/resources/kill_switch.rb +17 -7
  57. data/lib/DhanHQ/resources/margin_calculator.rb +9 -0
  58. data/lib/DhanHQ/resources/orders.rb +41 -41
  59. data/lib/DhanHQ/resources/pnl_exit.rb +37 -0
  60. data/lib/DhanHQ/resources/positions.rb +8 -0
  61. data/lib/DhanHQ/version.rb +1 -1
  62. data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
  63. data/lib/DhanHQ/ws/orders/client.rb +6 -6
  64. data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
  65. data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
  66. data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
  67. metadata +29 -24
  68. data/TODO-1.md +0 -14
  69. data/TODO.md +0 -127
  70. data/app/services/live/order_update_guard_support.rb +0 -75
  71. data/app/services/live/order_update_hub.rb +0 -76
  72. data/app/services/live/order_update_persistence_support.rb +0 -68
  73. data/docs/PR_2.2.0.md +0 -48
  74. data/examples/comprehensive_websocket_examples.rb +0 -148
  75. data/examples/instrument_finder_test.rb +0 -195
  76. data/examples/live_order_updates.rb +0 -118
  77. data/examples/market_depth_example.rb +0 -144
  78. data/examples/market_feed_example.rb +0 -81
  79. data/examples/order_update_example.rb +0 -105
  80. data/examples/trading_fields_example.rb +0 -215
  81. /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
  82. /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
@@ -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&L Exit — dhanClientId
95
+
96
+ **Doc:** [Trader's Control → P&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&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&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
 
@@ -81,11 +81,11 @@ To override defaults (base URL, WebSocket settings, partner credentials), set
81
81
 
82
82
  ```ruby
83
83
  order = DhanHQ::Models::Order.new(
84
- transaction_type: "BUY",
85
- exchange_segment: "NSE_FNO",
86
- product_type: "MARGIN",
87
- order_type: "LIMIT",
88
- validity: "DAY",
84
+ transaction_type: DhanHQ::Constants::TransactionType::BUY,
85
+ exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO,
86
+ product_type: DhanHQ::Constants::ProductType::MARGIN,
87
+ order_type: DhanHQ::Constants::OrderType::LIMIT,
88
+ validity: DhanHQ::Constants::Validity::DAY,
89
89
  security_id: "43492",
90
90
  quantity: 125,
91
91
  price: 100.0
@@ -125,7 +125,7 @@ puts orders.count
125
125
  ✅ Querying Orders
126
126
 
127
127
  ```ruby
128
- pending_orders = DhanHQ::Models::Order.where(status: "PENDING")
128
+ pending_orders = DhanHQ::Models::Order.where(status: DhanHQ::Constants::OrderStatus::PENDING)
129
129
  puts pending_orders.first.order_id
130
130
  ```
131
131
 
@@ -142,7 +142,7 @@ position.exit!
142
142
  #### Place
143
143
 
144
144
  ```ruby
145
- order = DhanHQ::Models::Order.new(transaction_type: "BUY", security_id: "123", quantity: 1)
145
+ order = DhanHQ::Models::Order.new(transaction_type: DhanHQ::Constants::TransactionType::BUY, security_id: "123", quantity: 1)
146
146
  order.save
147
147
  ```
148
148
 
@@ -170,7 +170,7 @@ DhanHQ::Models::Trade.find_by_order_id("452501297117")
170
170
  ```ruby
171
171
  positions = DhanHQ::Models::Position.all
172
172
  active = DhanHQ::Models::Position.active
173
- DhanHQ::Models::Position.convert(position_id: "1", product_type: "CNC")
173
+ DhanHQ::Models::Position.convert(position_id: "1", product_type: DhanHQ::Constants::ProductType::CNC)
174
174
  ```
175
175
 
176
176
  ### Holdings
@@ -196,8 +196,8 @@ DhanHQ::Models::OptionChain.fetch_expiry_list(security_id: "1333")
196
196
  ### Historical Data
197
197
 
198
198
  ```ruby
199
- DhanHQ::Models::HistoricalData.daily(security_id: "1333", exchange_segment: "NSE_EQ", instrument: "EQUITY", from_date: "2024-01-01", to_date: "2024-01-31")
200
- DhanHQ::Models::HistoricalData.intraday(security_id: "1333", exchange_segment: "NSE_EQ", instrument: "EQUITY", interval: "15", from_date: "2024-09-11", to_date: "2024-09-15")
199
+ DhanHQ::Models::HistoricalData.daily(security_id: "1333", exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, instrument: DhanHQ::Constants::InstrumentType::EQUITY, from_date: "2024-01-01", to_date: "2024-01-31")
200
+ DhanHQ::Models::HistoricalData.intraday(security_id: "1333", exchange_segment: DhanHQ::Constants::ExchangeSegment::NSE_EQ, instrument: DhanHQ::Constants::InstrumentType::EQUITY, interval: "15", from_date: "2024-09-11", to_date: "2024-09-15")
201
201
  ```
202
202
 
203
203
  ### Instrument Model with Convenience Methods
@@ -278,7 +278,7 @@ bundle exec rake release
278
278
  ```ruby
279
279
  {
280
280
  kind: :quote, # :ticker | :quote | :full | :oi | :prev_close | :misc
281
- segment: "NSE_FNO", # string enum
281
+ segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO, # string enum
282
282
  security_id: "12345",
283
283
  ltp: 101.5,
284
284
  ts: 1723791300, # LTT epoch (sec) if present
@@ -303,11 +303,11 @@ ws.on(:tick) do |t|
303
303
  end
304
304
 
305
305
  # Subscribe instruments (≤100 per frame; send multiple frames if needed)
306
- ws.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY index value
307
- ws.subscribe_one(segment: "NSE_FNO", security_id: "12345") # an option
306
+ ws.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13") # NIFTY index value
307
+ ws.subscribe_one(segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO, security_id: "12345") # an option
308
308
 
309
309
  # Unsubscribe
310
- ws.unsubscribe_one(segment: "NSE_FNO", security_id: "12345")
310
+ ws.unsubscribe_one(segment: DhanHQ::Constants::ExchangeSegment::NSE_FNO, security_id: "12345")
311
311
 
312
312
  # Graceful disconnect (sends broker disconnect code 12, no reconnect)
313
313
  ws.disconnect!
@@ -412,8 +412,8 @@ end
412
412
 
413
413
  ```ruby
414
414
  INDICES = [
415
- { segment: "IDX_I", security_id: "13" }, # NIFTY index value
416
- { segment: "IDX_I", security_id: "25" } # BANKNIFTY index value
415
+ { segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "13" }, # NIFTY index value
416
+ { segment: DhanHQ::Constants::ExchangeSegment::IDX_I, security_id: "25" } # BANKNIFTY index value
417
417
  ]
418
418
 
419
419
  Rails.application.config.to_prepare do
@@ -105,7 +105,7 @@ Rescue `AuthenticationError` for local config/token resolution failures; rescue
105
105
 
106
106
  - [README.md](../README.md) — Configuration and “Dynamic access token”
107
107
  - [GUIDE.md](../GUIDE.md) — Dynamic access token and RenewToken
108
- - [rails_integration.md](rails_integration.md) — Rails initializer with optional `access_token_provider` and RenewToken
108
+ - [RAILS_INTEGRATION.md](RAILS_INTEGRATION.md) — Rails initializer with optional `access_token_provider` and RenewToken
109
109
  - [TESTING_GUIDE.md](TESTING_GUIDE.md) — Config examples and RenewToken
110
110
  - [CHANGELOG.md](../CHANGELOG.md) — 2.2.0 and 2.2.1 auth and token changes
111
111
 
@@ -113,50 +113,112 @@ Rescue `AuthenticationError` for local config/token resolution failures; rescue
113
113
  # SUPPORTED AUTHENTICATION AND TOKEN GENERATION APPROACHES:
114
114
 
115
115
  We now support five distinct authentication approaches in this gem.
116
+
116
117
  1️⃣ Static token (manual, simplest)
117
118
  What: You paste a token you got from Dhan (web, OAuth, partner, whatever) into config.
118
119
  How:
119
- DhanHQ.configure do |config| config.client_id = ENV["DHAN_CLIENT_ID"] config.access_token = ENV["DHAN_ACCESS_TOKEN"]end
120
+ ```ruby
121
+ DhanHQ.configure do |config|
122
+ config.client_id = ENV["DHAN_CLIENT_ID"]
123
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"]
124
+ end
125
+ ```
120
126
  When: You’re OK rotating tokens manually (e.g. cron job, ops runbook).
127
+
121
128
  2️⃣ Dynamic token via access_token_provider
122
129
  What: Gem asks you for a token on every request (proc/lambda).
123
130
  How:
124
- DhanHQ.configure do |config| config.client_id = ENV["DHAN_CLIENT_ID"] config.access_token_provider = -> { MyTokenStore.fetch_current_token } config.on_token_expired = ->(error) { MyTokenStore.refresh!(error) }end
131
+ ```ruby
132
+ DhanHQ.configure do |config|
133
+ config.client_id = ENV["DHAN_CLIENT_ID"]
134
+ config.access_token_provider = -> { MyTokenStore.fetch_current_token }
135
+ config.on_token_expired = ->(error) { MyTokenStore.refresh!(error) }
136
+ end
137
+ ```
125
138
  Behavior:
126
139
  On 401 (auth failure), client calls on_token_expired, then retries once using a fresh token from access_token_provider.
140
+
127
141
  3️⃣ Fetch-from-token-endpoint (configure_from_token_endpoint)
128
142
  What: Gem calls your HTTP endpoint once to get access_token + client_id.
129
143
  How:
130
- DhanHQ.configure_from_token_endpoint( base_url: "https://myapp.com", bearer_token: ENV["DHAN_TOKEN_ENDPOINT_BEARER"])# expects JSON: { access_token: "...", client_id: "...", base_url: "..." (optional) }
144
+ ```ruby
145
+ DhanHQ.configure_from_token_endpoint(
146
+ base_url: "https://myapp.com",
147
+ bearer_token: ENV["DHAN_TOKEN_ENDPOINT_BEARER"]
148
+ )
149
+ # expects JSON: { access_token: "...", client_id: "...", base_url: "..." (optional) }
150
+ ```
131
151
  When: Multi-tenant or central credential service, you don’t want tokens in ENV directly.
152
+
132
153
  4️⃣ TOTP-based token generation (new DhanHQ::Auth flow)
133
154
  Module API (low-level)
134
155
  What: Direct call to Dhan’s generateAccessToken endpoint using TOTP.
135
- totp = DhanHQ::Auth.generate_totp(ENV["DHAN_TOTP_SECRET"])response = DhanHQ::Auth.generate_access_token( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp: totp)token = response["accessToken"]expiry = response["expiryTime"]
156
+ ```ruby
157
+ totp = DhanHQ::Auth.generate_totp(ENV["DHAN_TOTP_SECRET"])
158
+ response = DhanHQ::Auth.generate_access_token(
159
+ dhan_client_id: ENV["DHAN_CLIENT_ID"],
160
+ pin: ENV["DHAN_PIN"],
161
+ totp: totp
162
+ )
163
+ token = response["accessToken"]
164
+ expiry = response["expiryTime"]
165
+ ```
166
+
136
167
  Client API (high-level, returns TokenResponse)
137
- client = DhanHQ::Client.new(api_type: :order_api)token = client.generate_access_token( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp_secret: ENV["DHAN_TOTP_SECRET"] # or `totp:` if you computed it)# auto-applies token + client_id to `DhanHQ.configuration`
168
+ ```
169
+ client = DhanHQ::Client.new(api_type: :order_api)
170
+ token = client.generate_access_token(
171
+ dhan_client_id: ENV["DHAN_CLIENT_ID"],
172
+ pin: ENV["DHAN_PIN"],
173
+ totp_secret: ENV["DHAN_TOTP_SECRET"] # or `totp:` if you computed it
174
+ )
175
+ # auto-applies token + client_id to `DhanHQ.configuration`
176
+ ```
177
+
138
178
  When: Fully automated individual setup (no manual web token generation).
179
+
139
180
  5️⃣ Auto token lifecycle management (TokenManager)
140
181
  What: Gem handles generate + renew + retry around every API call.
141
182
  How:
142
- client = DhanHQ::Client.new(api_type: :order_api)client.enable_auto_token_management!( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp_secret: ENV["DHAN_TOTP_SECRET"])# From now on, `client.request` auto-ensures a valid token.
183
+ ```ruby
184
+ client = DhanHQ::Client.new(api_type: :order_api)
185
+ client.enable_auto_token_management!(
186
+ dhan_client_id: ENV["DHAN_CLIENT_ID"],
187
+ pin: ENV["DHAN_PIN"],
188
+ totp_secret: ENV["DHAN_TOTP_SECRET"]
189
+ )
190
+ # From now on, `client.request` auto-ensures a valid token.
191
+ ```
143
192
  Behavior:
144
193
  On first use: calls TOTP TokenGenerator → applies token.
145
194
  Before each request: ensure_valid_token!:
146
195
  If no token → generate.
147
196
  If needs_refresh? → TokenRenewal (POST /v2/RenewToken) with current token + dhanClientId.
148
197
  If renewal fails with auth error → falls back to full generate.
198
+
149
199
  6️⃣ Web-token renewal only (RenewToken)
150
200
  Module API:
151
- response = DhanHQ::Auth.renew_token( access_token: current_token, client_id: ENV["DHAN_CLIENT_ID"])
201
+ ```ruby
202
+ response = DhanHQ::Auth.renew_token(
203
+ access_token: current_token,
204
+ client_id: ENV["DHAN_CLIENT_ID"]
205
+ )
206
+ ```
152
207
  Client / manager (high-level):
153
- client.renew_access_token (returns TokenResponse, updates config).
154
- TokenManager#refresh! internally uses Auth::TokenRenewal.
208
+
209
+ `client.renew_access_token` (returns TokenResponse, updates config).
210
+ `TokenManager#refresh!` internally uses Auth::TokenRenewal.
155
211
  When: You’re using web-generated 24h tokens and want to extend them without switching to TOTP.
212
+
156
213
  TL;DR
157
214
  Manual: Static token (access_token)
215
+
158
216
  Dynamic: access_token_provider (+ optional on_token_expired)
217
+
159
218
  Central service: configure_from_token_endpoint
219
+
160
220
  Fully automated: TOTP generate (Auth / Client#generate_access_token)
221
+
161
222
  Production-grade automation: enable_auto_token_management! (generate + renew)
223
+
162
224
  If you tell me your exact deployment style (single user box, multi-user SaaS, on-prem, etc.), I can tell you which one you should actually use and what to delete as overkill.
@@ -0,0 +1,109 @@
1
+ # Configuration Reference
2
+
3
+ This document covers all configuration options for the DhanHQ Ruby client.
4
+
5
+ ## Quick Setup
6
+
7
+ ```ruby
8
+ require 'dhan_hq'
9
+ DhanHQ.configure_with_env
10
+ ```
11
+
12
+ `configure_with_env` reads `DHAN_CLIENT_ID` and `DHAN_ACCESS_TOKEN` from `ENV` and raises if either is missing.
13
+
14
+ ## Required Environment Variables
15
+
16
+ | Variable | Purpose |
17
+ | -------------------- | ------------------------------------------------- |
18
+ | `DHAN_CLIENT_ID` | Trading account client ID issued by Dhan |
19
+ | `DHAN_ACCESS_TOKEN` | API access token generated from the Dhan console |
20
+
21
+ ## Optional Environment Variables
22
+
23
+ Set these _before_ calling `configure_with_env` to override defaults:
24
+
25
+ | Variable | Default | Description |
26
+ | -------------------------------- | ---------- | ------------------------------------------------------- |
27
+ | `DHAN_LOG_LEVEL` | `INFO` | Logger verbosity (`DEBUG`, `INFO`, `WARN`, `ERROR`) |
28
+ | `DHAN_BASE_URL` | Dhan prod | Point REST calls to a different API hostname |
29
+ | `DHAN_WS_VERSION` | latest | Pin WebSocket connections to a specific API version |
30
+ | `DHAN_WS_ORDER_URL` | Dhan prod | Override the order update WebSocket endpoint |
31
+ | `DHAN_WS_USER_TYPE` | `SELF` | Switch between `SELF` and `PARTNER` streaming modes |
32
+ | `DHAN_PARTNER_ID` | — | Required when `DHAN_WS_USER_TYPE=PARTNER` |
33
+ | `DHAN_PARTNER_SECRET` | — | Required when `DHAN_WS_USER_TYPE=PARTNER` |
34
+ | `DHAN_CONNECT_TIMEOUT` | `10` | Connection timeout in seconds |
35
+ | `DHAN_READ_TIMEOUT` | `30` | Read timeout in seconds |
36
+ | `DHAN_WRITE_TIMEOUT` | `30` | Write timeout in seconds |
37
+ | `DHAN_WS_MAX_TRACKED_ORDERS` | `10000` | Maximum orders to track in WebSocket |
38
+ | `DHAN_WS_MAX_ORDER_AGE` | `604800` | Maximum order age in seconds before cleanup (7 days) |
39
+
40
+ ## `.env` File Setup
41
+
42
+ Create a `.env` file in your project root:
43
+
44
+ ```dotenv
45
+ DHAN_CLIENT_ID=your_client_id
46
+ DHAN_ACCESS_TOKEN=your_access_token
47
+
48
+ # Optional overrides
49
+ DHAN_LOG_LEVEL=DEBUG
50
+ DHAN_CONNECT_TIMEOUT=15
51
+ DHAN_READ_TIMEOUT=60
52
+ ```
53
+
54
+ The gem requires `dotenv/load`, so these variables are loaded automatically when you require `dhan_hq`.
55
+
56
+ ## Block-Style Configuration
57
+
58
+ ```ruby
59
+ DhanHQ.configure do |config|
60
+ config.client_id = ENV["DHAN_CLIENT_ID"]
61
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"]
62
+ end
63
+ ```
64
+
65
+ ## Logging
66
+
67
+ ```ruby
68
+ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Logger.const_get(level) }
69
+ ```
70
+
71
+ Set `DHAN_LOG_LEVEL=DEBUG` for full HTTP request/response and WebSocket frame logging during development.
72
+
73
+ ## Dynamic Access Token
74
+
75
+ For production or OAuth-style flows, resolve the token at **request time**:
76
+
77
+ ```ruby
78
+ DhanHQ.configure do |config|
79
+ config.client_id = ENV["DHAN_CLIENT_ID"]
80
+ config.access_token_provider = lambda do
81
+ token = YourTokenStore.active_token # e.g. from DB or OAuth
82
+ raise "Token expired or missing" unless token
83
+ token
84
+ end
85
+ # Optional: called when the API returns 401/token-expired
86
+ config.on_token_expired = ->(error) { YourTokenStore.refresh! }
87
+ end
88
+ ```
89
+
90
+ - **`access_token_provider`**: Callable (Proc/lambda) returning the token string. Called on every request (no memoization). When set, the gem uses it instead of `access_token`.
91
+ - **`on_token_expired`**: Optional callable invoked when a 401/token-expired triggers a **single retry** (only when `access_token_provider` is set).
92
+
93
+ For detailed authentication flows, see [AUTHENTICATION.md](AUTHENTICATION.md).
94
+
95
+ ## Available Resources
96
+
97
+ | Resource | Model | Actions |
98
+ | ------------------------ | -------------------------------------- | --------------------------------------------------- |
99
+ | Orders | `DhanHQ::Models::Order` | `find`, `all`, `where`, `place`, `update`, `cancel` |
100
+ | Trades | `DhanHQ::Models::Trade` | `all`, `find_by_order_id` |
101
+ | Forever Orders | `DhanHQ::Models::ForeverOrder` | `create`, `find`, `modify`, `cancel`, `all` |
102
+ | Holdings | `DhanHQ::Models::Holding` | `all` |
103
+ | Positions | `DhanHQ::Models::Position` | `all`, `find`, `exit!` |
104
+ | Funds & Margin | `DhanHQ::Models::Fund` | `fund_limit`, `margin_calculator` |
105
+ | Ledger | `DhanHQ::Models::Ledger` | `all` |
106
+ | Market Feeds | `DhanHQ::Models::MarketFeed` | `ltp`, `ohlc`, `quote` |
107
+ | Historical Data (Charts) | `DhanHQ::Models::HistoricalData` | `daily`, `intraday` |
108
+ | Option Chain | `DhanHQ::Models::OptionChain` | `fetch`, `fetch_expiry_list` |
109
+ | Super Orders | `DhanHQ::Models::SuperOrder` | `create`, `modify`, `cancel`, `all` |