DhanHQ 2.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +26 -0
- data/CHANGELOG.md +20 -0
- data/CODE_OF_CONDUCT.md +132 -0
- data/GUIDE.md +555 -0
- data/LICENSE.txt +21 -0
- data/README.md +463 -0
- data/README1.md +521 -0
- data/Rakefile +12 -0
- data/TAGS +10 -0
- data/TODO-1.md +14 -0
- data/TODO.md +127 -0
- data/app/services/live/order_update_guard_support.rb +75 -0
- data/app/services/live/order_update_hub.rb +76 -0
- data/app/services/live/order_update_persistence_support.rb +68 -0
- data/config/initializers/order_update_hub.rb +16 -0
- data/diagram.html +184 -0
- data/diagram.md +34 -0
- data/docs/rails_integration.md +304 -0
- data/exe/DhanHQ +4 -0
- data/lib/DhanHQ/client.rb +116 -0
- data/lib/DhanHQ/config.rb +32 -0
- data/lib/DhanHQ/configuration.rb +72 -0
- data/lib/DhanHQ/constants.rb +170 -0
- data/lib/DhanHQ/contracts/base_contract.rb +15 -0
- data/lib/DhanHQ/contracts/historical_data_contract.rb +28 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +19 -0
- data/lib/DhanHQ/contracts/modify_order_contract copy.rb +100 -0
- data/lib/DhanHQ/contracts/modify_order_contract.rb +22 -0
- data/lib/DhanHQ/contracts/option_chain_contract.rb +31 -0
- data/lib/DhanHQ/contracts/order_contract.rb +102 -0
- data/lib/DhanHQ/contracts/place_order_contract.rb +119 -0
- data/lib/DhanHQ/contracts/position_conversion_contract.rb +24 -0
- data/lib/DhanHQ/contracts/slice_order_contract.rb +111 -0
- data/lib/DhanHQ/core/base_api.rb +105 -0
- data/lib/DhanHQ/core/base_model.rb +266 -0
- data/lib/DhanHQ/core/base_resource.rb +50 -0
- data/lib/DhanHQ/core/error_handler.rb +19 -0
- data/lib/DhanHQ/error_object.rb +49 -0
- data/lib/DhanHQ/errors.rb +45 -0
- data/lib/DhanHQ/helpers/api_helper.rb +17 -0
- data/lib/DhanHQ/helpers/attribute_helper.rb +72 -0
- data/lib/DhanHQ/helpers/model_helper.rb +7 -0
- data/lib/DhanHQ/helpers/request_helper.rb +69 -0
- data/lib/DhanHQ/helpers/response_helper.rb +98 -0
- data/lib/DhanHQ/helpers/validation_helper.rb +36 -0
- data/lib/DhanHQ/json_loader.rb +23 -0
- data/lib/DhanHQ/models/edis.rb +58 -0
- data/lib/DhanHQ/models/forever_order.rb +85 -0
- data/lib/DhanHQ/models/funds.rb +50 -0
- data/lib/DhanHQ/models/historical_data.rb +77 -0
- data/lib/DhanHQ/models/holding.rb +56 -0
- data/lib/DhanHQ/models/kill_switch.rb +49 -0
- data/lib/DhanHQ/models/ledger_entry.rb +60 -0
- data/lib/DhanHQ/models/margin.rb +54 -0
- data/lib/DhanHQ/models/market_feed.rb +41 -0
- data/lib/DhanHQ/models/option_chain.rb +79 -0
- data/lib/DhanHQ/models/order.rb +239 -0
- data/lib/DhanHQ/models/position.rb +60 -0
- data/lib/DhanHQ/models/profile.rb +44 -0
- data/lib/DhanHQ/models/super_order.rb +69 -0
- data/lib/DhanHQ/models/trade.rb +79 -0
- data/lib/DhanHQ/rate_limiter.rb +107 -0
- data/lib/DhanHQ/requests/optionchain/nifty.json +5 -0
- data/lib/DhanHQ/requests/optionchain/nifty_expiries.json +4 -0
- data/lib/DhanHQ/requests/orders/create.json +0 -0
- data/lib/DhanHQ/resources/edis.rb +44 -0
- data/lib/DhanHQ/resources/forever_orders.rb +53 -0
- data/lib/DhanHQ/resources/funds.rb +21 -0
- data/lib/DhanHQ/resources/historical_data.rb +34 -0
- data/lib/DhanHQ/resources/holdings.rb +21 -0
- data/lib/DhanHQ/resources/kill_switch.rb +21 -0
- data/lib/DhanHQ/resources/margin_calculator.rb +22 -0
- data/lib/DhanHQ/resources/market_feed.rb +56 -0
- data/lib/DhanHQ/resources/option_chain.rb +31 -0
- data/lib/DhanHQ/resources/orders.rb +70 -0
- data/lib/DhanHQ/resources/positions.rb +29 -0
- data/lib/DhanHQ/resources/profile.rb +25 -0
- data/lib/DhanHQ/resources/statements.rb +42 -0
- data/lib/DhanHQ/resources/super_orders.rb +46 -0
- data/lib/DhanHQ/resources/trades.rb +23 -0
- data/lib/DhanHQ/version.rb +6 -0
- data/lib/DhanHQ/ws/client.rb +182 -0
- data/lib/DhanHQ/ws/cmd_bus.rb +38 -0
- data/lib/DhanHQ/ws/connection.rb +240 -0
- data/lib/DhanHQ/ws/decoder.rb +83 -0
- data/lib/DhanHQ/ws/errors.rb +0 -0
- data/lib/DhanHQ/ws/orders/client.rb +59 -0
- data/lib/DhanHQ/ws/orders/connection.rb +148 -0
- data/lib/DhanHQ/ws/orders.rb +13 -0
- data/lib/DhanHQ/ws/packets/depth_delta_packet.rb +20 -0
- data/lib/DhanHQ/ws/packets/disconnect_packet.rb +15 -0
- data/lib/DhanHQ/ws/packets/full_packet.rb +40 -0
- data/lib/DhanHQ/ws/packets/header.rb +23 -0
- data/lib/DhanHQ/ws/packets/index_packet.rb +14 -0
- data/lib/DhanHQ/ws/packets/market_depth_level.rb +21 -0
- data/lib/DhanHQ/ws/packets/market_status_packet.rb +14 -0
- data/lib/DhanHQ/ws/packets/oi_packet.rb +15 -0
- data/lib/DhanHQ/ws/packets/prev_close_packet.rb +16 -0
- data/lib/DhanHQ/ws/packets/quote_packet.rb +26 -0
- data/lib/DhanHQ/ws/packets/ticker_packet.rb +16 -0
- data/lib/DhanHQ/ws/registry.rb +46 -0
- data/lib/DhanHQ/ws/segments.rb +75 -0
- data/lib/DhanHQ/ws/singleton_lock.rb +54 -0
- data/lib/DhanHQ/ws/sub_state.rb +59 -0
- data/lib/DhanHQ/ws/websocket_packet_parser.rb +165 -0
- data/lib/DhanHQ/ws.rb +37 -0
- data/lib/DhanHQ.rb +135 -0
- data/lib/ta/technical_analysis.rb +405 -0
- data/sig/DhanHQ.rbs +4 -0
- data/watchlist.csv +3 -0
- metadata +283 -0
data/GUIDE.md
ADDED
@@ -0,0 +1,555 @@
|
|
1
|
+
# DhanHQ Client API Guide
|
2
|
+
|
3
|
+
Use this guide as the companion to the official Dhan API v2 documentation. It maps the public DhanHQ Ruby client classes to the REST and WebSocket endpoints, highlights the validations enforced by the gem, and shows how to compose end-to-end flows without tripping over common pitfalls.
|
4
|
+
|
5
|
+
## Table of Contents
|
6
|
+
|
7
|
+
1. [Getting Started](#getting-started)
|
8
|
+
2. [Working With Models](#working-with-models)
|
9
|
+
3. [Orders](#orders)
|
10
|
+
4. [Super & Forever Orders](#super--forever-orders)
|
11
|
+
5. [Portfolio & Funds](#portfolio--funds)
|
12
|
+
6. [Trade & Ledger Data](#trade--ledger-data)
|
13
|
+
7. [Data & Market Services](#data--market-services)
|
14
|
+
8. [Account Utilities](#account-utilities)
|
15
|
+
9. [Constants & Enums](#constants--enums)
|
16
|
+
10. [Error Handling](#error-handling)
|
17
|
+
11. [Best Practices](#best-practices)
|
18
|
+
|
19
|
+
---
|
20
|
+
|
21
|
+
## Getting Started
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
# Gemfile
|
25
|
+
gem 'DhanHQ', git: 'https://github.com/shubhamtaywade82/dhanhq-client.git', branch: 'main'
|
26
|
+
```
|
27
|
+
|
28
|
+
```bash
|
29
|
+
bundle install
|
30
|
+
```
|
31
|
+
|
32
|
+
Bootstrap from environment variables:
|
33
|
+
|
34
|
+
```ruby
|
35
|
+
require 'DhanHQ'
|
36
|
+
|
37
|
+
DhanHQ.configure_with_env
|
38
|
+
DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Logger.const_get(level) }
|
39
|
+
```
|
40
|
+
|
41
|
+
**Minimum requirements**
|
42
|
+
|
43
|
+
`configure_with_env` reads from `ENV` and raises unless both variables are set:
|
44
|
+
|
45
|
+
| Variable | Description |
|
46
|
+
| --- | --- |
|
47
|
+
| `CLIENT_ID` | Your Dhan trading client id. |
|
48
|
+
| `ACCESS_TOKEN` | REST/WebSocket access token generated via Dhan APIs. |
|
49
|
+
|
50
|
+
Provide them via `.env`, Rails credentials, or your secret manager of choice
|
51
|
+
before the initializer runs.
|
52
|
+
|
53
|
+
**Optional overrides**
|
54
|
+
|
55
|
+
Set any of the following environment variables _before_ calling
|
56
|
+
`configure_with_env` to customise runtime behaviour:
|
57
|
+
|
58
|
+
| Variable | Purpose |
|
59
|
+
| --- | --- |
|
60
|
+
| `DHAN_LOG_LEVEL` | Change logger verbosity (`INFO` default). |
|
61
|
+
| `DHAN_BASE_URL` | Override the REST API host. |
|
62
|
+
| `DHAN_WS_VERSION` | Target a specific WebSocket API version. |
|
63
|
+
| `DHAN_WS_ORDER_URL` | Customise the order update WebSocket endpoint. |
|
64
|
+
| `DHAN_WS_USER_TYPE` | Toggle between `SELF` and `PARTNER` streaming modes. |
|
65
|
+
| `DHAN_PARTNER_ID` / `DHAN_PARTNER_SECRET` | Required when streaming as a partner. |
|
66
|
+
|
67
|
+
---
|
68
|
+
|
69
|
+
## Working With Models
|
70
|
+
|
71
|
+
All models inherit from `DhanHQ::BaseModel` and expose a consistent API:
|
72
|
+
|
73
|
+
- **Class helpers**: `.all`, `.find`, `.create`, and, where available, `.where`, `.history`, `.today`
|
74
|
+
- **Instance helpers**: `#save`, `#modify`, `#cancel`, `#refresh`, `#destroy`
|
75
|
+
- **Validation**: the gem wraps Dry::Validation contracts. Validation errors raise `DhanHQ::Error`.
|
76
|
+
- **Parameter naming**:
|
77
|
+
- Ruby-facing APIs (e.g. `Order.place`, `Order#slice_order`, `Margin.calculate`, `Position.convert`) accept snake_case keys and symbols. The client handles camelCase conversion before hitting the REST API.
|
78
|
+
- When you work with the raw `DhanHQ::Resources::*` classes directly, supply the fields exactly as documented by the REST API.
|
79
|
+
- **Responses**: model constructors normalise keys to snake_case and expose attribute reader methods. Raw API hashes are wrapped in `HashWithIndifferentAccess` for easy lookup.
|
80
|
+
|
81
|
+
---
|
82
|
+
|
83
|
+
## Orders
|
84
|
+
|
85
|
+
### Available Methods
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
order = DhanHQ::Models::Order.place(payload) # validate + POST + fetch order details
|
89
|
+
order = DhanHQ::Models::Order.create(payload) # build + #save (AR-style)
|
90
|
+
orders = DhanHQ::Models::Order.all # current-day order book
|
91
|
+
order = DhanHQ::Models::Order.find(order_id)
|
92
|
+
order = DhanHQ::Models::Order.find_by_correlation(correlation_id)
|
93
|
+
```
|
94
|
+
|
95
|
+
Instance workflow:
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
order = DhanHQ::Models::Order.new(params)
|
99
|
+
order.save # place or modify depending on presence of order_id
|
100
|
+
order.modify(price: 101.5)
|
101
|
+
order.cancel
|
102
|
+
order.refresh
|
103
|
+
```
|
104
|
+
|
105
|
+
### Placement Payload (Order.place / Order#create / Order#save)
|
106
|
+
|
107
|
+
Required fields validated by `DhanHQ::Contracts::PlaceOrderContract`:
|
108
|
+
|
109
|
+
| Key | Type | Allowed Values / Notes |
|
110
|
+
| ----------------- | ------- | ---------------------- |
|
111
|
+
| `transaction_type`| String | `BUY`, `SELL` |
|
112
|
+
| `exchange_segment`| String | Use `DhanHQ::Constants::EXCHANGE_SEGMENTS` |
|
113
|
+
| `product_type` | String | `CNC`, `INTRADAY`, `MARGIN`, `MTF`, `CO`, `BO` |
|
114
|
+
| `order_type` | String | `LIMIT`, `MARKET`, `STOP_LOSS`, `STOP_LOSS_MARKET` |
|
115
|
+
| `validity` | String | `DAY`, `IOC` |
|
116
|
+
| `security_id` | String | Security identifier from the scrip master |
|
117
|
+
| `quantity` | Integer | Must be > 0 |
|
118
|
+
|
119
|
+
Optional fields and special rules:
|
120
|
+
|
121
|
+
| Key | Type | Notes |
|
122
|
+
| --------------------- | ------- | ----- |
|
123
|
+
| `correlation_id` | String | ≤ 25 chars; useful for idempotency |
|
124
|
+
| `disclosed_quantity` | Integer | ≥ 0 and ≤ 30% of `quantity` |
|
125
|
+
| `trading_symbol` | String | Optional label |
|
126
|
+
| `price` | Float | Mandatory for `LIMIT` |
|
127
|
+
| `trigger_price` | Float | Mandatory for SL / SLM |
|
128
|
+
| `after_market_order` | Boolean | Require `amo_time` when true |
|
129
|
+
| `amo_time` | String | `OPEN`, `OPEN_30`, `OPEN_60` (check `DhanHQ::Constants::AMO_TIMINGS` for updates) |
|
130
|
+
| `bo_profit_value` | Float | Required with `product_type: "BO"` |
|
131
|
+
| `bo_stop_loss_value` | Float | Required with `product_type: "BO"` |
|
132
|
+
| `drv_expiry_date` | String | Pass ISO `YYYY-MM-DD` for derivatives |
|
133
|
+
| `drv_option_type` | String | `CALL`, `PUT`, `NA` |
|
134
|
+
| `drv_strike_price` | Float | > 0 |
|
135
|
+
|
136
|
+
Example:
|
137
|
+
|
138
|
+
```ruby
|
139
|
+
payload = {
|
140
|
+
transaction_type: "BUY",
|
141
|
+
exchange_segment: "NSE_EQ",
|
142
|
+
product_type: "CNC",
|
143
|
+
order_type: "LIMIT",
|
144
|
+
validity: "DAY",
|
145
|
+
security_id: "1333",
|
146
|
+
quantity: 10,
|
147
|
+
price: 150.0,
|
148
|
+
correlation_id: "hs20240910-01"
|
149
|
+
}
|
150
|
+
|
151
|
+
order = DhanHQ::Models::Order.place(payload)
|
152
|
+
puts order.order_status # => "TRADED" / "PENDING" / ...
|
153
|
+
```
|
154
|
+
|
155
|
+
### Modification & Cancellation
|
156
|
+
|
157
|
+
`Order#modify` merges the existing attributes with the supplied overrides and validates against `ModifyOrderContract`.
|
158
|
+
|
159
|
+
- Required: the instance must have an `order_id` and `dhan_client_id`.
|
160
|
+
- At least one of `order_type`, `quantity`, `price`, `trigger_price`, `disclosed_quantity`, `validity` must change.
|
161
|
+
- Payload is camelised automatically before hitting `/v2/orders/{orderId}`.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
order.modify(price: 154.2, trigger_price: 149.5)
|
165
|
+
order.cancel
|
166
|
+
```
|
167
|
+
|
168
|
+
For raw updates (e.g. background jobs) you can call the resource directly:
|
169
|
+
|
170
|
+
```ruby
|
171
|
+
params = DhanHQ::Models::Order.camelize_keys(order_id: "123", price: 100.0)
|
172
|
+
DhanHQ::Contracts::ModifyOrderContract.new.call(params).success?
|
173
|
+
DhanHQ::Models::Order.resource.update("123", params)
|
174
|
+
```
|
175
|
+
|
176
|
+
### Slicing Orders
|
177
|
+
|
178
|
+
Use the same fields as placement, but the contract allows additional validity options (`GTC`, `GTD`). The model helper accepts snake_case parameters and handles camelCase conversion as part of validation:
|
179
|
+
|
180
|
+
```ruby
|
181
|
+
slice_payload = {
|
182
|
+
order_id: order.order_id,
|
183
|
+
transaction_type: "BUY",
|
184
|
+
exchange_segment: "NSE_EQ",
|
185
|
+
product_type: "STOP_LOSS",
|
186
|
+
order_type: "STOP_LOSS",
|
187
|
+
validity: "GTC",
|
188
|
+
security_id: "1333",
|
189
|
+
quantity: 100,
|
190
|
+
trigger_price: 148.5,
|
191
|
+
price: 150.0
|
192
|
+
}
|
193
|
+
|
194
|
+
order.slice_order(slice_payload)
|
195
|
+
```
|
196
|
+
|
197
|
+
When you call the resource layer directly, camelCase the keys first so they match the REST contract:
|
198
|
+
|
199
|
+
```ruby
|
200
|
+
payload = DhanHQ::Models::Order.camelize_keys(slice_payload)
|
201
|
+
DhanHQ::Contracts::SliceOrderContract.new.call(payload).success?
|
202
|
+
DhanHQ::Models::Order.resource.slicing(payload)
|
203
|
+
```
|
204
|
+
|
205
|
+
---
|
206
|
+
|
207
|
+
## Super & Forever Orders
|
208
|
+
|
209
|
+
### Super Orders
|
210
|
+
|
211
|
+
`DhanHQ::Models::SuperOrder` wraps `/v2/super/orders`.
|
212
|
+
|
213
|
+
```ruby
|
214
|
+
legs = {
|
215
|
+
transactionType: "BUY",
|
216
|
+
exchangeSegment: "NSE_FNO",
|
217
|
+
productType: "CO",
|
218
|
+
orderType: "LIMIT",
|
219
|
+
validity: "DAY",
|
220
|
+
securityId: "43492",
|
221
|
+
quantity: 50,
|
222
|
+
price: 100.0,
|
223
|
+
stopLossPrice: 95.0,
|
224
|
+
targetPrice: 110.0
|
225
|
+
}
|
226
|
+
|
227
|
+
super_order = DhanHQ::Models::SuperOrder.create(legs)
|
228
|
+
super_order.modify(trailingJump: 2.5)
|
229
|
+
super_order.cancel("ENTRY_LEG")
|
230
|
+
```
|
231
|
+
|
232
|
+
### Forever Orders (GTT)
|
233
|
+
|
234
|
+
`DhanHQ::Models::ForeverOrder` maps to `/v2/forever/orders`.
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
params = {
|
238
|
+
dhanClientId: "123456",
|
239
|
+
transactionType: "SELL",
|
240
|
+
exchangeSegment: "NSE_EQ",
|
241
|
+
productType: "CNC",
|
242
|
+
orderType: "LIMIT",
|
243
|
+
validity: "DAY",
|
244
|
+
securityId: "1333",
|
245
|
+
price: 200.0,
|
246
|
+
triggerPrice: 198.0
|
247
|
+
}
|
248
|
+
|
249
|
+
forever_order = DhanHQ::Models::ForeverOrder.create(params)
|
250
|
+
forever_order.modify(price: 205.0)
|
251
|
+
forever_order.cancel
|
252
|
+
```
|
253
|
+
|
254
|
+
The forever order helpers accept snake_case parameters and camelize them internally; only the low-level resource requires raw API casing.
|
255
|
+
|
256
|
+
---
|
257
|
+
|
258
|
+
## Portfolio & Funds
|
259
|
+
|
260
|
+
### Positions
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
positions = DhanHQ::Models::Position.all # includes closed legs
|
264
|
+
open_positions = DhanHQ::Models::Position.active
|
265
|
+
```
|
266
|
+
|
267
|
+
Convert an intraday position to delivery (or vice versa):
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
convert_payload = {
|
271
|
+
dhan_client_id: "123456",
|
272
|
+
security_id: "1333",
|
273
|
+
from_product_type: "INTRADAY",
|
274
|
+
to_product_type: "CNC",
|
275
|
+
convert_qty: 10,
|
276
|
+
exchange_segment: "NSE_EQ",
|
277
|
+
position_type: "LONG"
|
278
|
+
}
|
279
|
+
|
280
|
+
response = DhanHQ::Models::Position.convert(convert_payload)
|
281
|
+
raise response.errors.to_s if response.is_a?(DhanHQ::ErrorObject)
|
282
|
+
```
|
283
|
+
|
284
|
+
The conversion helper validates the payload with `PositionConversionContract`; missing or invalid fields raise `DhanHQ::Error` before the request is sent.
|
285
|
+
|
286
|
+
### Holdings
|
287
|
+
|
288
|
+
```ruby
|
289
|
+
holdings = DhanHQ::Models::Holding.all
|
290
|
+
holdings.first.avg_cost_price
|
291
|
+
```
|
292
|
+
|
293
|
+
### Funds
|
294
|
+
|
295
|
+
```ruby
|
296
|
+
funds = DhanHQ::Models::Funds.fetch
|
297
|
+
puts funds.available_balance
|
298
|
+
|
299
|
+
balance = DhanHQ::Models::Funds.balance
|
300
|
+
```
|
301
|
+
|
302
|
+
API quirk: the REST response currently returns `availabelBalance`. The model maps it automatically to `available_balance`.
|
303
|
+
|
304
|
+
---
|
305
|
+
|
306
|
+
## Trade & Ledger Data
|
307
|
+
|
308
|
+
### Trades
|
309
|
+
|
310
|
+
```ruby
|
311
|
+
# Historical trades
|
312
|
+
history = DhanHQ::Models::Trade.history(from_date: "2024-01-01", to_date: "2024-01-31", page: 0)
|
313
|
+
|
314
|
+
# Current day trade book
|
315
|
+
trade_book = DhanHQ::Models::Trade.today
|
316
|
+
|
317
|
+
# Trade details for a specific order (today)
|
318
|
+
trade = DhanHQ::Models::Trade.find_by_order_id("ORDER123")
|
319
|
+
```
|
320
|
+
|
321
|
+
### Ledger Entries
|
322
|
+
|
323
|
+
```ruby
|
324
|
+
ledger = DhanHQ::Models::LedgerEntry.all(from_date: "2024-04-01", to_date: "2024-04-30")
|
325
|
+
ledger.each { |entry| puts "#{entry.voucherdate} #{entry.narration} #{entry.runbal}" }
|
326
|
+
```
|
327
|
+
|
328
|
+
Both endpoints return arrays and skip validation because they represent historical data dumps.
|
329
|
+
|
330
|
+
---
|
331
|
+
|
332
|
+
## Data & Market Services
|
333
|
+
|
334
|
+
### Historical Data
|
335
|
+
|
336
|
+
`DhanHQ::Models::HistoricalData` enforces `HistoricalDataContract` before delegating to `/v2/charts`.
|
337
|
+
|
338
|
+
| Key | Type | Notes |
|
339
|
+
| ------------------ | ------ | ----- |
|
340
|
+
| `security_id` | String | Required |
|
341
|
+
| `exchange_segment` | String | See `EXCHANGE_SEGMENTS` |
|
342
|
+
| `instrument` | String | Use `DhanHQ::Constants::INSTRUMENTS` |
|
343
|
+
| `from_date` | String | `YYYY-MM-DD` |
|
344
|
+
| `to_date` | String | `YYYY-MM-DD` |
|
345
|
+
| `expiry_code` | Integer| Optional (`0`, `1`, `2`) |
|
346
|
+
| `interval` | String | Optional (`1`, `5`, `15`, `25`, `60`) for intraday |
|
347
|
+
|
348
|
+
```ruby
|
349
|
+
bars = DhanHQ::Models::HistoricalData.intraday(
|
350
|
+
security_id: "13",
|
351
|
+
exchange_segment: "IDX_I",
|
352
|
+
instrument: "INDEX",
|
353
|
+
interval: "5",
|
354
|
+
from_date: "2024-08-14",
|
355
|
+
to_date: "2024-08-14"
|
356
|
+
)
|
357
|
+
```
|
358
|
+
|
359
|
+
### Option Chain
|
360
|
+
|
361
|
+
```ruby
|
362
|
+
chain = DhanHQ::Models::OptionChain.fetch(
|
363
|
+
underlying_scrip: 1333,
|
364
|
+
underlying_seg: "NSE_FNO",
|
365
|
+
expiry: "2024-12-26"
|
366
|
+
)
|
367
|
+
|
368
|
+
expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(
|
369
|
+
underlying_scrip: 1333,
|
370
|
+
underlying_seg: "NSE_FNO"
|
371
|
+
)
|
372
|
+
```
|
373
|
+
|
374
|
+
The model filters strikes where both CE and PE have zero `last_price`, keeping the payload compact.
|
375
|
+
|
376
|
+
### Margin Calculator
|
377
|
+
|
378
|
+
`DhanHQ::Models::Margin.calculate` camelizes your snake_case keys and validates with `MarginCalculatorContract` before posting to `/v2/margincalculator`:
|
379
|
+
|
380
|
+
```ruby
|
381
|
+
params = {
|
382
|
+
dhan_client_id: "123456",
|
383
|
+
exchange_segment: "NSE_EQ",
|
384
|
+
transaction_type: "BUY",
|
385
|
+
quantity: 10,
|
386
|
+
product_type: "INTRADAY",
|
387
|
+
security_id: "1333",
|
388
|
+
price: 150.0
|
389
|
+
}
|
390
|
+
|
391
|
+
margin = DhanHQ::Models::Margin.calculate(params)
|
392
|
+
puts margin.total_margin
|
393
|
+
```
|
394
|
+
|
395
|
+
If a required field is missing (for example `transaction_type`), the contract raises `DhanHQ::Error` before any API call is issued.
|
396
|
+
|
397
|
+
### REST Market Feed (Batch LTP/OHLC/Quote)
|
398
|
+
|
399
|
+
```ruby
|
400
|
+
payload = {
|
401
|
+
"NSE_EQ" => [11536, 3456],
|
402
|
+
"NSE_FNO" => [49081, 49082]
|
403
|
+
}
|
404
|
+
|
405
|
+
ltp = DhanHQ::Models::MarketFeed.ltp(payload)
|
406
|
+
ohlc = DhanHQ::Models::MarketFeed.ohlc(payload)
|
407
|
+
quote = DhanHQ::Models::MarketFeed.quote(payload)
|
408
|
+
```
|
409
|
+
|
410
|
+
These endpoints are rate-limited by Dhan. The client’s internal `RateLimiter` throttles calls—consider batching symbols sensibly.
|
411
|
+
|
412
|
+
### WebSocket Market Feed
|
413
|
+
|
414
|
+
The gem provides a resilient EventMachine + Faye wrapper. Minimal setup:
|
415
|
+
|
416
|
+
```ruby
|
417
|
+
DhanHQ.configure_with_env
|
418
|
+
ws = DhanHQ::WS::Client.new(mode: :quote).start
|
419
|
+
|
420
|
+
ws.on(:tick) do |tick|
|
421
|
+
puts "[#{tick[:segment]} #{tick[:security_id]}] LTP=#{tick[:ltp]} kind=#{tick[:kind]}"
|
422
|
+
end
|
423
|
+
|
424
|
+
ws.subscribe_one(segment: "IDX_I", security_id: "13")
|
425
|
+
ws.unsubscribe_one(segment: "IDX_I", security_id: "13")
|
426
|
+
|
427
|
+
ws.disconnect!
|
428
|
+
```
|
429
|
+
|
430
|
+
Modes: `:ticker`, `:quote`, `:full`. The client handles reconnects, 429 cool-offs, and idempotent subscriptions.
|
431
|
+
|
432
|
+
---
|
433
|
+
|
434
|
+
## Account Utilities
|
435
|
+
|
436
|
+
### Profile
|
437
|
+
|
438
|
+
```ruby
|
439
|
+
profile = DhanHQ::Models::Profile.fetch
|
440
|
+
profile.dhan_client_id # => "1100003626"
|
441
|
+
profile.token_validity # => "30/03/2025 15:37"
|
442
|
+
profile.active_segment # => "Equity, Derivative, Currency, Commodity"
|
443
|
+
```
|
444
|
+
|
445
|
+
If the credentials are invalid the helper raises `DhanHQ::InvalidAuthenticationError`.
|
446
|
+
|
447
|
+
### EDIS (Electronic Delivery Instruction Slip)
|
448
|
+
|
449
|
+
```ruby
|
450
|
+
# Generate a CDSL form for a single ISIN
|
451
|
+
form = DhanHQ::Models::Edis.form(
|
452
|
+
isin: "INE0ABCDE123",
|
453
|
+
qty: 1,
|
454
|
+
exchange: "NSE",
|
455
|
+
segment: "EQ",
|
456
|
+
bulk: false
|
457
|
+
)
|
458
|
+
|
459
|
+
# Prepare a bulk file
|
460
|
+
bulk_form = DhanHQ::Models::Edis.bulk_form(
|
461
|
+
isin: %w[INE0ABCDE123 INE0XYZ89012],
|
462
|
+
exchange: "NSE",
|
463
|
+
segment: "EQ"
|
464
|
+
)
|
465
|
+
|
466
|
+
# Manage T-PIN and status inquiries
|
467
|
+
DhanHQ::Models::Edis.tpin # => {"status"=>"TPIN sent"}
|
468
|
+
authorisations = DhanHQ::Models::Edis.inquire("ALL")
|
469
|
+
```
|
470
|
+
|
471
|
+
All helpers accept snake_case keys; the client camelizes them before calling `/v2/edis/...`.
|
472
|
+
|
473
|
+
### Kill Switch
|
474
|
+
|
475
|
+
```ruby
|
476
|
+
DhanHQ::Models::KillSwitch.activate # => {"killSwitchStatus"=>"ACTIVATE"}
|
477
|
+
DhanHQ::Models::KillSwitch.deactivate # => {"killSwitchStatus"=>"DEACTIVATE"}
|
478
|
+
|
479
|
+
# Explicit status update
|
480
|
+
DhanHQ::Models::KillSwitch.update("ACTIVATE")
|
481
|
+
```
|
482
|
+
|
483
|
+
Only `"ACTIVATE"` and `"DEACTIVATE"` are accepted—any other value raises `DhanHQ::Error`.
|
484
|
+
|
485
|
+
---
|
486
|
+
|
487
|
+
## Constants & Enums
|
488
|
+
|
489
|
+
Use `DhanHQ::Constants` for canonical values:
|
490
|
+
|
491
|
+
- `TRANSACTION_TYPES`
|
492
|
+
- `EXCHANGE_SEGMENTS`
|
493
|
+
- `PRODUCT_TYPES`
|
494
|
+
- `ORDER_TYPES`
|
495
|
+
- `VALIDITY_TYPES`
|
496
|
+
- `AMO_TIMINGS`
|
497
|
+
- `INSTRUMENTS`
|
498
|
+
- `ORDER_STATUSES`
|
499
|
+
- CSV URLs: `COMPACT_CSV_URL`, `DETAILED_CSV_URL`
|
500
|
+
- `DHAN_ERROR_MAPPING` for mapping broker error codes to Ruby exceptions
|
501
|
+
|
502
|
+
Example:
|
503
|
+
|
504
|
+
```ruby
|
505
|
+
validity = DhanHQ::Constants::VALIDITY_TYPES # => ["DAY", "IOC"]
|
506
|
+
```
|
507
|
+
|
508
|
+
---
|
509
|
+
|
510
|
+
## Error Handling
|
511
|
+
|
512
|
+
The client normalises broker error payloads and raises specific subclasses of `DhanHQ::Error` (see `lib/DhanHQ/errors.rb`). Key mappings:
|
513
|
+
|
514
|
+
- `InvalidAuthenticationError` → `DH-901`
|
515
|
+
- `InvalidAccessError` → `DH-902`
|
516
|
+
- `UserAccountError` → `DH-903`
|
517
|
+
- `RateLimitError` → `DH-904`, HTTP 429/805
|
518
|
+
- `InputExceptionError` → `DH-905`
|
519
|
+
- `OrderError` → `DH-906`
|
520
|
+
- `DataError` → `DH-907`
|
521
|
+
- `InternalServerError` → `DH-908`, `800`
|
522
|
+
- `NetworkError` → `DH-909`
|
523
|
+
- `OtherError` → `DH-910`
|
524
|
+
- `InvalidTokenError`, `InvalidClientIDError`, `InvalidRequestError` for the remaining broker error codes (`807`–`814`)
|
525
|
+
|
526
|
+
Handle errors explicitly while placing orders:
|
527
|
+
|
528
|
+
```ruby
|
529
|
+
begin
|
530
|
+
order = DhanHQ::Models::Order.place(payload)
|
531
|
+
puts "Order status: #{order.order_status}"
|
532
|
+
rescue DhanHQ::InvalidAuthenticationError => e
|
533
|
+
warn "Auth failed: #{e.message}"
|
534
|
+
rescue DhanHQ::OrderError => e
|
535
|
+
warn "Order rejected: #{e.message}"
|
536
|
+
rescue DhanHQ::RateLimitError => e
|
537
|
+
warn "Slow down: #{e.message}"
|
538
|
+
end
|
539
|
+
```
|
540
|
+
|
541
|
+
---
|
542
|
+
|
543
|
+
## Best Practices
|
544
|
+
|
545
|
+
1. Validate payloads locally (`DhanHQ::Contracts::*`) before hitting the API in batch scripts.
|
546
|
+
2. Use `correlation_id` to make order placement idempotent across retries.
|
547
|
+
3. Call `Order#refresh` or `Order.find` after placement when you depend on derived fields like `average_traded_price` or `filled_qty`.
|
548
|
+
4. Respect the built-in rate limiter; space out historical data and market feed calls to avoid `DH-904`/805 errors.
|
549
|
+
5. Keep enum values in sync by referencing `DhanHQ::Constants`; avoid hardcoding strings in application code.
|
550
|
+
6. Capture and persist broker error codes—they are mapped to Ruby error classes but still valuable for support tickets.
|
551
|
+
7. For WebSocket feeds, subscribe in frames ≤ 100 instruments and handle reconnect callbacks to resubscribe cleanly.
|
552
|
+
|
553
|
+
---
|
554
|
+
|
555
|
+
Always cross-check with https://dhanhq.co/docs/v2/ for endpoint-specific nuances. The Ruby client aims to mirror those contracts while adding guard rails and idiomatic ergonomics.
|
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2025 TODO: Write your name
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|