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
|
@@ -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.
|
data/docs/API_VERIFICATION.md
CHANGED
|
@@ -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:**
|
|
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
|
|
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:**
|
|
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
|
|
48
|
-
|
|
49
|
-
| GET /ip/getIP | `#current` | `get("/getIP")`
|
|
50
|
-
| POST /ip/setIP | `#set(ip:)`
|
|
51
|
-
| PUT /ip/modifyIP| `#update(ip:)` | `put("/modifyIP", params: { 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
|
-
- **
|
|
58
|
-
- **
|
|
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:
|
|
85
|
-
exchange_segment:
|
|
86
|
-
product_type:
|
|
87
|
-
order_type:
|
|
88
|
-
validity:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
200
|
-
DhanHQ::Models::HistoricalData.intraday(security_id: "1333", exchange_segment:
|
|
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:
|
|
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:
|
|
307
|
-
ws.subscribe_one(segment:
|
|
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:
|
|
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:
|
|
416
|
-
{ segment:
|
|
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
|
data/docs/AUTHENTICATION.md
CHANGED
|
@@ -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
|
-
- [
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
154
|
-
|
|
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` |
|