DhanHQ 2.1.6 → 2.1.8
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/CHANGELOG.md +22 -0
- data/GUIDE.md +124 -76
- data/README.md +43 -0
- data/README1.md +99 -77
- data/docs/websocket_integration.md +47 -0
- data/examples/comprehensive_websocket_examples.rb +0 -0
- data/examples/instrument_finder_test.rb +0 -0
- data/examples/live_order_updates.rb +1 -1
- data/examples/market_depth_example.rb +2 -2
- data/examples/order_update_example.rb +0 -0
- data/examples/trading_fields_example.rb +1 -1
- data/lib/DhanHQ/client.rb +2 -1
- data/lib/DhanHQ/constants.rb +16 -0
- data/lib/DhanHQ/models/instrument.rb +18 -2
- data/lib/DhanHQ/models/instrument_helpers.rb +175 -0
- data/lib/DhanHQ/rate_limiter.rb +78 -23
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/market_depth/client.rb +3 -1
- data/lib/DhanHQ/ws/orders/connection.rb +0 -3
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 9a414cb1f2ba6e22e0c4014ea9ca5656607611787d7f4f18e26c50069a8c3811
|
|
4
|
+
data.tar.gz: 05bbdf626ae74c2a64b963bccbff2ff35bd64972a7ea825491c42ae2069377f3
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b9cb7044f23b775360cc999bec4c3475278d923f8f11d083fc3874827f3565c19dbe8bc1f201da6619b9bd6c1d6911f2774ee0d8e329b0c8f2f7e66155f161d0
|
|
7
|
+
data.tar.gz: c81c3c6685511cfef78b1083c49e5d4ea4ec692f494d36b2ee6a74580c1de4655aa9e9139c7b63dd3c40a075c02e07bd7137a30b81e6062b83f4ec7000498fc5
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,27 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
## [2.1.8] - 2025-10-30
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Correctly map `underlying_seg` for option chain APIs:
|
|
7
|
+
- Index instruments use `IDX_I`.
|
|
8
|
+
- Stocks map to `NSE_FNO` or `BSE_FNO` based on the instrument's exchange.
|
|
9
|
+
- Implemented via `underlying_segment_for_options` in `DhanHQ::Models::InstrumentHelpers` and applied to `expiry_list` and `option_chain`.
|
|
10
|
+
|
|
11
|
+
## [2.1.7] - 2025-01-28
|
|
12
|
+
|
|
13
|
+
### Added
|
|
14
|
+
- **Instrument instance methods**: Added convenience methods to Instrument model for accessing market feed, historical data, and option chain data
|
|
15
|
+
- `instrument.ltp` - Fetches last traded price using `DhanHQ::Models::MarketFeed.ltp`
|
|
16
|
+
- `instrument.ohlc` - Fetches OHLC data using `DhanHQ::Models::MarketFeed.ohlc`
|
|
17
|
+
- `instrument.quote` - Fetches full quote depth using `DhanHQ::Models::MarketFeed.quote`
|
|
18
|
+
- `instrument.daily(from_date:, to_date:, **options)` - Fetches daily historical data using `DhanHQ::Models::HistoricalData.daily`
|
|
19
|
+
- `instrument.intraday(from_date:, to_date:, interval:, **options)` - Fetches intraday historical data using `DhanHQ::Models::HistoricalData.intraday`
|
|
20
|
+
- `instrument.expiry_list` - Fetches expiry list using `DhanHQ::Models::OptionChain.fetch_expiry_list`
|
|
21
|
+
- `instrument.option_chain(expiry:)` - Fetches option chain using `DhanHQ::Models::OptionChain.fetch`
|
|
22
|
+
- All methods automatically use the instrument's `security_id`, `exchange_segment`, and `instrument` attributes
|
|
23
|
+
- **InstrumentHelpers module**: Created reusable module to provide these convenience methods
|
|
24
|
+
|
|
3
25
|
### Changed
|
|
4
26
|
- Align Super Order documentation across README, README1, and GUIDE with the latest API contract (place, modify, cancel, list).
|
|
5
27
|
- Normalise remaining documentation examples to snake_case, including order update WebSocket callbacks and kill switch response guidance.
|
data/GUIDE.md
CHANGED
|
@@ -42,9 +42,9 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
42
42
|
|
|
43
43
|
`configure_with_env` reads from `ENV` and raises unless both variables are set:
|
|
44
44
|
|
|
45
|
-
| Variable
|
|
46
|
-
|
|
|
47
|
-
| `CLIENT_ID`
|
|
45
|
+
| Variable | Description |
|
|
46
|
+
| -------------- | ---------------------------------------------------- |
|
|
47
|
+
| `CLIENT_ID` | Your Dhan trading client id. |
|
|
48
48
|
| `ACCESS_TOKEN` | REST/WebSocket access token generated via Dhan APIs. |
|
|
49
49
|
|
|
50
50
|
Provide them via `.env`, Rails credentials, or your secret manager of choice
|
|
@@ -55,14 +55,14 @@ before the initializer runs.
|
|
|
55
55
|
Set any of the following environment variables _before_ calling
|
|
56
56
|
`configure_with_env` to customise runtime behaviour:
|
|
57
57
|
|
|
58
|
-
| Variable
|
|
59
|
-
|
|
|
60
|
-
| `DHAN_LOG_LEVEL`
|
|
61
|
-
| `DHAN_BASE_URL`
|
|
62
|
-
| `DHAN_WS_VERSION`
|
|
63
|
-
| `DHAN_WS_ORDER_URL`
|
|
64
|
-
| `DHAN_WS_USER_TYPE`
|
|
65
|
-
| `DHAN_PARTNER_ID` / `DHAN_PARTNER_SECRET` | Required when streaming as a partner.
|
|
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
66
|
|
|
67
67
|
---
|
|
68
68
|
|
|
@@ -106,32 +106,32 @@ order.refresh
|
|
|
106
106
|
|
|
107
107
|
Required fields validated by `DhanHQ::Contracts::PlaceOrderContract`:
|
|
108
108
|
|
|
109
|
-
| Key
|
|
110
|
-
|
|
|
111
|
-
| `transaction_type
|
|
112
|
-
| `exchange_segment
|
|
113
|
-
| `product_type`
|
|
114
|
-
| `order_type`
|
|
115
|
-
| `validity`
|
|
116
|
-
| `security_id`
|
|
117
|
-
| `quantity`
|
|
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
118
|
|
|
119
119
|
Optional fields and special rules:
|
|
120
120
|
|
|
121
|
-
| Key
|
|
122
|
-
|
|
|
123
|
-
| `correlation_id`
|
|
124
|
-
| `disclosed_quantity`
|
|
125
|
-
| `trading_symbol`
|
|
126
|
-
| `price`
|
|
127
|
-
| `trigger_price`
|
|
128
|
-
| `after_market_order`
|
|
129
|
-
| `amo_time`
|
|
130
|
-
| `bo_profit_value`
|
|
131
|
-
| `bo_stop_loss_value`
|
|
132
|
-
| `drv_expiry_date`
|
|
133
|
-
| `drv_option_type`
|
|
134
|
-
| `drv_strike_price`
|
|
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
135
|
|
|
136
136
|
Example:
|
|
137
137
|
|
|
@@ -212,12 +212,12 @@ DhanHQ::Models::Order.resource.slicing(payload)
|
|
|
212
212
|
|
|
213
213
|
#### Endpoints
|
|
214
214
|
|
|
215
|
-
| Method
|
|
216
|
-
|
|
|
217
|
-
| `POST`
|
|
218
|
-
| `PUT`
|
|
219
|
-
| `DELETE` | `/super/orders/{order_id}/{order_leg}` | Cancel a pending super order leg
|
|
220
|
-
| `GET`
|
|
215
|
+
| Method | Path | Description |
|
|
216
|
+
| -------- | -------------------------------------- | ------------------------------------- |
|
|
217
|
+
| `POST` | `/super/orders` | Create a new super order |
|
|
218
|
+
| `PUT` | `/super/orders/{order_id}` | Modify a pending super order |
|
|
219
|
+
| `DELETE` | `/super/orders/{order_id}/{order_leg}` | Cancel a pending super order leg |
|
|
220
|
+
| `GET` | `/super/orders` | Retrieve the list of all super orders |
|
|
221
221
|
|
|
222
222
|
#### Place Super Order
|
|
223
223
|
|
|
@@ -252,20 +252,20 @@ Request body:
|
|
|
252
252
|
|
|
253
253
|
Key parameters:
|
|
254
254
|
|
|
255
|
-
| Field
|
|
256
|
-
|
|
|
257
|
-
| `dhan_client_id`
|
|
258
|
-
| `correlation_id`
|
|
259
|
-
| `transaction_type` | enum string *(required)* | `BUY` or `SELL`.
|
|
260
|
-
| `exchange_segment` | enum string *(required)* | Exchange segment.
|
|
261
|
-
| `product_type`
|
|
262
|
-
| `order_type`
|
|
263
|
-
| `security_id`
|
|
264
|
-
| `quantity`
|
|
265
|
-
| `price`
|
|
266
|
-
| `target_price`
|
|
267
|
-
| `stop_loss_price`
|
|
268
|
-
| `trailing_jump`
|
|
255
|
+
| Field | Type | Notes |
|
|
256
|
+
| ------------------ | ------------------------ | -------------------------------------------------------------------------------------------------------------- |
|
|
257
|
+
| `dhan_client_id` | string *(required)* | User specific identifier generated by Dhan. Automatically merged when you call through the Ruby model helpers. |
|
|
258
|
+
| `correlation_id` | string | Optional caller supplied correlation id. |
|
|
259
|
+
| `transaction_type` | enum string *(required)* | `BUY` or `SELL`. |
|
|
260
|
+
| `exchange_segment` | enum string *(required)* | Exchange segment. |
|
|
261
|
+
| `product_type` | enum string *(required)* | `CNC`, `INTRADAY`, `MARGIN`, or `MTF`. |
|
|
262
|
+
| `order_type` | enum string *(required)* | `LIMIT` or `MARKET`. |
|
|
263
|
+
| `security_id` | string *(required)* | Exchange security identifier. |
|
|
264
|
+
| `quantity` | integer *(required)* | Entry quantity. |
|
|
265
|
+
| `price` | float *(required)* | Entry price. |
|
|
266
|
+
| `target_price` | float *(required)* | Target price for the super order. |
|
|
267
|
+
| `stop_loss_price` | float *(required)* | Stop-loss price for the super order. |
|
|
268
|
+
| `trailing_jump` | float *(required)* | Trailing jump size. |
|
|
269
269
|
|
|
270
270
|
> 🐍 Pass snake_case keys when invoking `DhanHQ::Models::SuperOrder.create`—the client camelizes internally before calling the REST API and injects your configured `dhan_client_id`, so the key is optional in Ruby payloads.
|
|
271
271
|
|
|
@@ -308,14 +308,14 @@ Example payload:
|
|
|
308
308
|
|
|
309
309
|
Conditional fields:
|
|
310
310
|
|
|
311
|
-
| Field
|
|
312
|
-
|
|
|
313
|
-
| `order_type`
|
|
314
|
-
| `quantity`
|
|
315
|
-
| `price`
|
|
316
|
-
| `target_price`
|
|
317
|
-
| `stop_loss_price` | Updating `ENTRY_LEG` or `STOP_LOSS_LEG` | Adjusts stop-loss price.
|
|
318
|
-
| `trailing_jump`
|
|
311
|
+
| Field | Required when | Notes |
|
|
312
|
+
| ----------------- | --------------------------------------- | ------------------------------------ |
|
|
313
|
+
| `order_type` | Updating `ENTRY_LEG` | `LIMIT` or `MARKET`. |
|
|
314
|
+
| `quantity` | Updating `ENTRY_LEG` | Adjusts entry quantity. |
|
|
315
|
+
| `price` | Updating `ENTRY_LEG` | Adjusts entry price. |
|
|
316
|
+
| `target_price` | Updating `ENTRY_LEG` or `TARGET_LEG` | Adjusts target price. |
|
|
317
|
+
| `stop_loss_price` | Updating `ENTRY_LEG` or `STOP_LOSS_LEG` | Adjusts stop-loss price. |
|
|
318
|
+
| `trailing_jump` | Updating `ENTRY_LEG` or `STOP_LOSS_LEG` | Pass `0` or omit to cancel trailing. |
|
|
319
319
|
|
|
320
320
|
Response:
|
|
321
321
|
|
|
@@ -337,10 +337,10 @@ curl --request DELETE \
|
|
|
337
337
|
|
|
338
338
|
Path parameters:
|
|
339
339
|
|
|
340
|
-
| Field
|
|
341
|
-
|
|
|
342
|
-
| `order_id`
|
|
343
|
-
| `order_leg` | Leg to cancel (`ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`). | `ENTRY_LEG`
|
|
340
|
+
| Field | Description | Example |
|
|
341
|
+
| ----------- | -------------------------------------------------------------- | ------------- |
|
|
342
|
+
| `order_id` | Super order identifier. | `11211182198` |
|
|
343
|
+
| `order_leg` | Leg to cancel (`ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`). | `ENTRY_LEG` |
|
|
344
344
|
|
|
345
345
|
Response:
|
|
346
346
|
|
|
@@ -470,15 +470,15 @@ Both endpoints return arrays and skip validation because they represent historic
|
|
|
470
470
|
|
|
471
471
|
`DhanHQ::Models::HistoricalData` enforces `HistoricalDataContract` before delegating to `/v2/charts`.
|
|
472
472
|
|
|
473
|
-
| Key | Type
|
|
474
|
-
| ------------------ |
|
|
475
|
-
| `security_id` | String
|
|
476
|
-
| `exchange_segment` | String
|
|
477
|
-
| `instrument` | String
|
|
478
|
-
| `from_date` | String
|
|
479
|
-
| `to_date` | String
|
|
480
|
-
| `expiry_code` | Integer| Optional (`0`, `1`, `2`)
|
|
481
|
-
| `interval` | String
|
|
473
|
+
| Key | Type | Notes |
|
|
474
|
+
| ------------------ | ------- | -------------------------------------------------- |
|
|
475
|
+
| `security_id` | String | Required |
|
|
476
|
+
| `exchange_segment` | String | See `EXCHANGE_SEGMENTS` |
|
|
477
|
+
| `instrument` | String | Use `DhanHQ::Constants::INSTRUMENTS` |
|
|
478
|
+
| `from_date` | String | `YYYY-MM-DD` |
|
|
479
|
+
| `to_date` | String | `YYYY-MM-DD` |
|
|
480
|
+
| `expiry_code` | Integer | Optional (`0`, `1`, `2`) |
|
|
481
|
+
| `interval` | String | Optional (`1`, `5`, `15`, `25`, `60`) for intraday |
|
|
482
482
|
|
|
483
483
|
```ruby
|
|
484
484
|
bars = DhanHQ::Models::HistoricalData.intraday(
|
|
@@ -543,7 +543,55 @@ ohlc = DhanHQ::Models::MarketFeed.ohlc(payload)
|
|
|
543
543
|
quote = DhanHQ::Models::MarketFeed.quote(payload)
|
|
544
544
|
```
|
|
545
545
|
|
|
546
|
-
These endpoints are rate-limited by Dhan. The client
|
|
546
|
+
These endpoints are rate-limited by Dhan. The client's internal `RateLimiter` throttles calls—consider batching symbols sensibly.
|
|
547
|
+
|
|
548
|
+
### Instrument Convenience Methods
|
|
549
|
+
|
|
550
|
+
The Instrument model provides convenient instance methods that automatically use the instrument's attributes (`security_id`, `exchange_segment`, `instrument`) to fetch market data. This eliminates the need to manually construct parameters for each API call.
|
|
551
|
+
|
|
552
|
+
```ruby
|
|
553
|
+
# Find an instrument first
|
|
554
|
+
instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
555
|
+
# or
|
|
556
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
|
557
|
+
|
|
558
|
+
# Market Feed Methods - automatically uses instrument's attributes
|
|
559
|
+
ltp_data = instrument.ltp # Last traded price
|
|
560
|
+
ohlc_data = instrument.ohlc # OHLC data
|
|
561
|
+
quote_data = instrument.quote # Full quote depth
|
|
562
|
+
|
|
563
|
+
# Historical Data Methods
|
|
564
|
+
daily_data = instrument.daily(
|
|
565
|
+
from_date: "2024-01-01",
|
|
566
|
+
to_date: "2024-01-31",
|
|
567
|
+
expiry_code: 0 # Optional, only for derivatives
|
|
568
|
+
)
|
|
569
|
+
|
|
570
|
+
intraday_data = instrument.intraday(
|
|
571
|
+
from_date: "2024-09-11",
|
|
572
|
+
to_date: "2024-09-15",
|
|
573
|
+
interval: "15" # 1, 5, 15, 25, or 60 minutes
|
|
574
|
+
)
|
|
575
|
+
|
|
576
|
+
# Option Chain Methods (for F&O instruments)
|
|
577
|
+
fn_instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
578
|
+
expiries = fn_instrument.expiry_list # Get all available expiries
|
|
579
|
+
chain = fn_instrument.option_chain(expiry: "2024-02-29") # Get option chain for specific expiry
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
**Available Instance Methods:**
|
|
583
|
+
|
|
584
|
+
| Method | Description | Underlying API |
|
|
585
|
+
| ----------------------------------------------------------------- | -------------------------------- | ----------------------------------------------- |
|
|
586
|
+
| `instrument.ltp` | Fetches last traded price | `DhanHQ::Models::MarketFeed.ltp` |
|
|
587
|
+
| `instrument.ohlc` | Fetches OHLC data | `DhanHQ::Models::MarketFeed.ohlc` |
|
|
588
|
+
| `instrument.quote` | Fetches full quote depth | `DhanHQ::Models::MarketFeed.quote` |
|
|
589
|
+
| `instrument.daily(from_date:, to_date:, **options)` | Fetches daily historical data | `DhanHQ::Models::HistoricalData.daily` |
|
|
590
|
+
| `instrument.intraday(from_date:, to_date:, interval:, **options)` | Fetches intraday historical data | `DhanHQ::Models::HistoricalData.intraday` |
|
|
591
|
+
| `instrument.expiry_list` | Fetches expiry list | `DhanHQ::Models::OptionChain.fetch_expiry_list` |
|
|
592
|
+
| `instrument.option_chain(expiry:)` | Fetches option chain | `DhanHQ::Models::OptionChain.fetch` |
|
|
593
|
+
|
|
594
|
+
All methods automatically extract `security_id`, `exchange_segment`, and `instrument` from the instrument instance, making it easier to work with market data without manually managing these parameters.
|
|
547
595
|
|
|
548
596
|
### WebSocket Market Feed
|
|
549
597
|
|
data/README.md
CHANGED
|
@@ -327,6 +327,49 @@ puts "Sell Margin %: #{reliance.sell_co_min_margin_per}%"
|
|
|
327
327
|
- `sell_co_min_margin_per` - Sell CO minimum margin percentage
|
|
328
328
|
- `mtf_leverage` - Margin Trading Facility leverage
|
|
329
329
|
|
|
330
|
+
### Instrument Convenience Methods
|
|
331
|
+
|
|
332
|
+
The Instrument model provides convenient instance methods that automatically use the instrument's attributes (`security_id`, `exchange_segment`, `instrument`) to fetch market data:
|
|
333
|
+
|
|
334
|
+
```ruby
|
|
335
|
+
# Find an instrument
|
|
336
|
+
instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
337
|
+
|
|
338
|
+
# Market Feed Methods - automatically uses instrument's attributes
|
|
339
|
+
ltp_data = instrument.ltp # Last traded price
|
|
340
|
+
ohlc_data = instrument.ohlc # OHLC data
|
|
341
|
+
quote_data = instrument.quote # Full quote depth
|
|
342
|
+
|
|
343
|
+
# Historical Data Methods
|
|
344
|
+
daily_data = instrument.daily(
|
|
345
|
+
from_date: "2024-01-01",
|
|
346
|
+
to_date: "2024-01-31",
|
|
347
|
+
expiry_code: 0 # Optional
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
intraday_data = instrument.intraday(
|
|
351
|
+
from_date: "2024-09-11",
|
|
352
|
+
to_date: "2024-09-15",
|
|
353
|
+
interval: "15" # 1, 5, 15, 25, or 60 minutes
|
|
354
|
+
)
|
|
355
|
+
|
|
356
|
+
# Option Chain Methods
|
|
357
|
+
expiries = instrument.expiry_list # Get all available expiries
|
|
358
|
+
|
|
359
|
+
chain = instrument.option_chain(expiry: "2024-02-29") # Get option chain for specific expiry
|
|
360
|
+
```
|
|
361
|
+
|
|
362
|
+
**Available Instance Methods:**
|
|
363
|
+
- `instrument.ltp` - Fetches last traded price using `DhanHQ::Models::MarketFeed.ltp`
|
|
364
|
+
- `instrument.ohlc` - Fetches OHLC data using `DhanHQ::Models::MarketFeed.ohlc`
|
|
365
|
+
- `instrument.quote` - Fetches full quote depth using `DhanHQ::Models::MarketFeed.quote`
|
|
366
|
+
- `instrument.daily(from_date:, to_date:, **options)` - Fetches daily historical data using `DhanHQ::Models::HistoricalData.daily`
|
|
367
|
+
- `instrument.intraday(from_date:, to_date:, interval:, **options)` - Fetches intraday historical data using `DhanHQ::Models::HistoricalData.intraday`
|
|
368
|
+
- `instrument.expiry_list` - Fetches expiry list using `DhanHQ::Models::OptionChain.fetch_expiry_list`
|
|
369
|
+
- `instrument.option_chain(expiry:)` - Fetches option chain using `DhanHQ::Models::OptionChain.fetch`
|
|
370
|
+
|
|
371
|
+
All methods automatically use the instrument's `security_id`, `exchange_segment`, and `instrument` attributes, eliminating the need to manually pass these parameters.
|
|
372
|
+
|
|
330
373
|
### Comprehensive Documentation
|
|
331
374
|
|
|
332
375
|
The gem includes detailed documentation for different integration scenarios:
|
data/README1.md
CHANGED
|
@@ -196,14 +196,36 @@ 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", from_date: "2024-01-01", to_date: "2024-01-31")
|
|
200
|
-
DhanHQ::Models::HistoricalData.intraday(security_id: "1333", interval: "15")
|
|
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")
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Instrument Model with Convenience Methods
|
|
204
|
+
|
|
205
|
+
The Instrument model provides convenient instance methods that automatically use the instrument's attributes:
|
|
206
|
+
|
|
207
|
+
```ruby
|
|
208
|
+
# Find an instrument
|
|
209
|
+
instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
210
|
+
|
|
211
|
+
# Market Feed Methods - automatically uses instrument's attributes
|
|
212
|
+
ltp_data = instrument.ltp # Last traded price
|
|
213
|
+
ohlc_data = instrument.ohlc # OHLC data
|
|
214
|
+
quote_data = instrument.quote # Full quote depth
|
|
215
|
+
|
|
216
|
+
# Historical Data Methods
|
|
217
|
+
daily_data = instrument.daily(from_date: "2024-01-01", to_date: "2024-01-31")
|
|
218
|
+
intraday_data = instrument.intraday(from_date: "2024-09-11", to_date: "2024-09-15", interval: "15")
|
|
219
|
+
|
|
220
|
+
# Option Chain Methods
|
|
221
|
+
expiries = instrument.expiry_list
|
|
222
|
+
chain = instrument.option_chain(expiry: "2024-02-29")
|
|
201
223
|
```
|
|
202
224
|
|
|
203
225
|
## 🔹 Available Resources
|
|
204
226
|
|
|
205
|
-
| Resource | Model
|
|
206
|
-
| ------------------------ |
|
|
227
|
+
| Resource | Model | Actions |
|
|
228
|
+
| ------------------------ | ---------------------------------------- | --------------------------------------------------- |
|
|
207
229
|
| Orders | `DhanHQ::Models::Models::Order` | `find`, `all`, `where`, `place`, `update`, `cancel` |
|
|
208
230
|
| Trades | `DhanHQ::Models::Models::Trade` | `all`, `find_by_order_id` |
|
|
209
231
|
| Forever Orders | `DhanHQ::Models::Models::ForeverOrder` | `create`, `find`, `modify`, `cancel`, `all` |
|
|
@@ -431,12 +453,12 @@ The gem exposes all related REST endpoints so you can create, modify, cancel, an
|
|
|
431
453
|
|
|
432
454
|
### Endpoints
|
|
433
455
|
|
|
434
|
-
| Method
|
|
435
|
-
|
|
|
436
|
-
| `POST`
|
|
437
|
-
| `PUT`
|
|
438
|
-
| `DELETE` | `/super/orders/{order_id}/{order_leg}` | Cancel a pending super order leg
|
|
439
|
-
| `GET`
|
|
456
|
+
| Method | Path | Description |
|
|
457
|
+
| -------- | -------------------------------------- | ------------------------------------- |
|
|
458
|
+
| `POST` | `/super/orders` | Create a new super order |
|
|
459
|
+
| `PUT` | `/super/orders/{order_id}` | Modify a pending super order |
|
|
460
|
+
| `DELETE` | `/super/orders/{order_id}/{order_leg}` | Cancel a pending super order leg |
|
|
461
|
+
| `GET` | `/super/orders` | Retrieve the list of all super orders |
|
|
440
462
|
|
|
441
463
|
### Place Super Order
|
|
442
464
|
|
|
@@ -473,20 +495,20 @@ curl --request POST \
|
|
|
473
495
|
|
|
474
496
|
#### Parameters
|
|
475
497
|
|
|
476
|
-
| Field
|
|
477
|
-
|
|
|
478
|
-
| `dhan_client_id`
|
|
479
|
-
| `correlation_id`
|
|
480
|
-
| `transaction_type` | enum string *(required)* | Trading side. `BUY` or `SELL`.
|
|
481
|
-
| `exchange_segment` | enum string *(required)* | Exchange segment (see appendix).
|
|
482
|
-
| `product_type`
|
|
483
|
-
| `order_type`
|
|
484
|
-
| `security_id`
|
|
485
|
-
| `quantity`
|
|
486
|
-
| `price`
|
|
487
|
-
| `target_price`
|
|
488
|
-
| `stop_loss_price`
|
|
489
|
-
| `trailing_jump`
|
|
498
|
+
| Field | Type | Description |
|
|
499
|
+
| ------------------ | ------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
500
|
+
| `dhan_client_id` | string *(required)* | User specific identification generated by Dhan. When you call via `DhanHQ::Models::SuperOrder`, the gem injects your configured client id so you can omit the key in Ruby. |
|
|
501
|
+
| `correlation_id` | string | Caller supplied identifier for tracking |
|
|
502
|
+
| `transaction_type` | enum string *(required)* | Trading side. `BUY` or `SELL`. |
|
|
503
|
+
| `exchange_segment` | enum string *(required)* | Exchange segment (see appendix). |
|
|
504
|
+
| `product_type` | enum string *(required)* | Product type. `CNC`, `INTRADAY`, `MARGIN`, or `MTF`. |
|
|
505
|
+
| `order_type` | enum string *(required)* | Order type. `LIMIT` or `MARKET`. |
|
|
506
|
+
| `security_id` | string *(required)* | Exchange standard security identifier. |
|
|
507
|
+
| `quantity` | integer *(required)* | Quantity for the entry leg. |
|
|
508
|
+
| `price` | float *(required)* | Entry price. |
|
|
509
|
+
| `target_price` | float *(required)* | Target price for the super order. |
|
|
510
|
+
| `stop_loss_price` | float *(required)* | Stop-loss price for the super order. |
|
|
511
|
+
| `trailing_jump` | float *(required)* | Price jump size for trailing stop-loss. |
|
|
490
512
|
|
|
491
513
|
> 🐍 When you call `DhanHQ::Models::SuperOrder.create`, supply snake_case keys exactly as shown. The client automatically camelizes
|
|
492
514
|
> them before submitting to Dhan's REST API and injects your configured `dhan_client_id`, allowing you to omit the key in Ruby code.
|
|
@@ -500,9 +522,9 @@ curl --request POST \
|
|
|
500
522
|
}
|
|
501
523
|
```
|
|
502
524
|
|
|
503
|
-
| Field
|
|
504
|
-
|
|
|
505
|
-
| `order_id`
|
|
525
|
+
| Field | Type | Description |
|
|
526
|
+
| -------------- | ----------- | --------------------------------------------------- |
|
|
527
|
+
| `order_id` | string | Order identifier generated by Dhan |
|
|
506
528
|
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, or `REJECTED`. |
|
|
507
529
|
|
|
508
530
|
### Modify Super Order
|
|
@@ -537,17 +559,17 @@ curl --request PUT \
|
|
|
537
559
|
|
|
538
560
|
#### Parameters
|
|
539
561
|
|
|
540
|
-
| Field
|
|
541
|
-
|
|
|
542
|
-
| `dhan_client_id`
|
|
543
|
-
| `order_id`
|
|
544
|
-
| `order_type`
|
|
545
|
-
| `leg_name`
|
|
546
|
-
| `quantity`
|
|
547
|
-
| `price`
|
|
548
|
-
| `target_price`
|
|
549
|
-
| `stop_loss_price` | float *(conditionally required)*
|
|
550
|
-
| `trailing_jump`
|
|
562
|
+
| Field | Type | Description |
|
|
563
|
+
| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
564
|
+
| `dhan_client_id` | string *(required)* | User specific identification generated by Dhan. Automatically merged when using the Ruby model helpers. |
|
|
565
|
+
| `order_id` | string *(required)* | Super order identifier generated by Dhan. |
|
|
566
|
+
| `order_type` | enum string *(conditionally required)* | `LIMIT` or `MARKET`. Required when changing `ENTRY_LEG`. |
|
|
567
|
+
| `leg_name` | enum string *(required)* | `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. Entry edits allowed only while status is `PENDING` or `PART_TRADED`. |
|
|
568
|
+
| `quantity` | integer *(conditionally required)* | Quantity update for `ENTRY_LEG`. |
|
|
569
|
+
| `price` | float *(conditionally required)* | Entry price update for `ENTRY_LEG`. |
|
|
570
|
+
| `target_price` | float *(conditionally required)* | Target price update for `ENTRY_LEG` or `TARGET_LEG`. |
|
|
571
|
+
| `stop_loss_price` | float *(conditionally required)* | Stop-loss price update for `ENTRY_LEG` or `STOP_LOSS_LEG`. |
|
|
572
|
+
| `trailing_jump` | float *(conditionally required)* | Trailing jump update for `ENTRY_LEG` or `STOP_LOSS_LEG`. Omit or set to `0` to cancel trailing. |
|
|
551
573
|
|
|
552
574
|
#### Response
|
|
553
575
|
|
|
@@ -558,9 +580,9 @@ curl --request PUT \
|
|
|
558
580
|
}
|
|
559
581
|
```
|
|
560
582
|
|
|
561
|
-
| Field
|
|
562
|
-
|
|
|
563
|
-
| `order_id`
|
|
583
|
+
| Field | Type | Description |
|
|
584
|
+
| -------------- | ----------- | ------------------------------------------------------------- |
|
|
585
|
+
| `order_id` | string | Order identifier generated by Dhan |
|
|
564
586
|
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, `REJECTED`, or `TRADED`. |
|
|
565
587
|
|
|
566
588
|
### Cancel Super Order
|
|
@@ -578,10 +600,10 @@ curl --request DELETE \
|
|
|
578
600
|
|
|
579
601
|
#### Path parameters
|
|
580
602
|
|
|
581
|
-
| Field
|
|
582
|
-
|
|
|
583
|
-
| `order_id`
|
|
584
|
-
| `order_leg` | Leg to cancel. `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. | `ENTRY_LEG`
|
|
603
|
+
| Field | Description | Example |
|
|
604
|
+
| ----------- | ------------------------------------------------------------- | ------------- |
|
|
605
|
+
| `order_id` | Super order identifier. | `11211182198` |
|
|
606
|
+
| `order_leg` | Leg to cancel. `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. | `ENTRY_LEG` |
|
|
585
607
|
|
|
586
608
|
#### Response
|
|
587
609
|
|
|
@@ -592,9 +614,9 @@ curl --request DELETE \
|
|
|
592
614
|
}
|
|
593
615
|
```
|
|
594
616
|
|
|
595
|
-
| Field
|
|
596
|
-
|
|
|
597
|
-
| `order_id`
|
|
617
|
+
| Field | Type | Description |
|
|
618
|
+
| -------------- | ----------- | ---------------------------------------------------------------- |
|
|
619
|
+
| `order_id` | string | Order identifier generated by Dhan |
|
|
598
620
|
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, `REJECTED`, or `CANCELLED`. |
|
|
599
621
|
|
|
600
622
|
### Super Order List
|
|
@@ -666,35 +688,35 @@ curl --request GET \
|
|
|
666
688
|
|
|
667
689
|
#### Parameters
|
|
668
690
|
|
|
669
|
-
| Field
|
|
670
|
-
|
|
|
671
|
-
| `dhan_client_id`
|
|
672
|
-
| `order_id`
|
|
673
|
-
| `correlation_id`
|
|
674
|
-
| `order_status`
|
|
675
|
-
| `transaction_type`
|
|
676
|
-
| `exchange_segment`
|
|
677
|
-
| `product_type`
|
|
678
|
-
| `order_type`
|
|
679
|
-
| `validity`
|
|
680
|
-
| `trading_symbol`
|
|
681
|
-
| `security_id`
|
|
682
|
-
| `quantity`
|
|
683
|
-
| `remaining_quantity`
|
|
684
|
-
| `ltp`
|
|
685
|
-
| `price`
|
|
686
|
-
| `after_market_order`
|
|
687
|
-
| `leg_name`
|
|
688
|
-
| `trailing_jump`
|
|
689
|
-
| `exchange_order_id`
|
|
690
|
-
| `create_time`
|
|
691
|
-
| `update_time`
|
|
692
|
-
| `exchange_time`
|
|
693
|
-
| `oms_error_description` | string
|
|
694
|
-
| `average_traded_price`
|
|
695
|
-
| `filled_qty`
|
|
696
|
-
| `triggered_quantity`
|
|
697
|
-
| `leg_details`
|
|
691
|
+
| Field | Type | Description |
|
|
692
|
+
| ----------------------- | ----------- | --------------------------------------------------------------------------------------------------- |
|
|
693
|
+
| `dhan_client_id` | string | User specific identification generated by Dhan. |
|
|
694
|
+
| `order_id` | string | Order identifier generated by Dhan. |
|
|
695
|
+
| `correlation_id` | string | Caller supplied correlation identifier. |
|
|
696
|
+
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, `CLOSED`, `REJECTED`, `CANCELLED`, `PART_TRADED`, or `TRADED`. |
|
|
697
|
+
| `transaction_type` | enum string | Trading side. `BUY` or `SELL`. |
|
|
698
|
+
| `exchange_segment` | enum string | Exchange segment. |
|
|
699
|
+
| `product_type` | enum string | Product type. `CNC`, `INTRADAY`, `MARGIN`, or `MTF`. |
|
|
700
|
+
| `order_type` | enum string | Order type. `LIMIT` or `MARKET`. |
|
|
701
|
+
| `validity` | enum string | Order validity. `DAY`. |
|
|
702
|
+
| `trading_symbol` | string | Trading symbol reference. |
|
|
703
|
+
| `security_id` | string | Exchange security identifier. |
|
|
704
|
+
| `quantity` | integer | Ordered quantity. |
|
|
705
|
+
| `remaining_quantity` | integer | Quantity pending execution. |
|
|
706
|
+
| `ltp` | float | Last traded price. |
|
|
707
|
+
| `price` | float | Order price. |
|
|
708
|
+
| `after_market_order` | boolean | Indicates if order was placed after market hours. |
|
|
709
|
+
| `leg_name` | enum string | Leg identifier: `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. |
|
|
710
|
+
| `trailing_jump` | float | Trailing jump for stop-loss. |
|
|
711
|
+
| `exchange_order_id` | string | Exchange generated identifier. |
|
|
712
|
+
| `create_time` | string | Order creation timestamp. |
|
|
713
|
+
| `update_time` | string | Latest update timestamp. |
|
|
714
|
+
| `exchange_time` | string | Exchange timestamp. |
|
|
715
|
+
| `oms_error_description` | string | OMS error description when applicable. |
|
|
716
|
+
| `average_traded_price` | float | Average traded price. |
|
|
717
|
+
| `filled_qty` | integer | Quantity traded on the exchange. |
|
|
718
|
+
| `triggered_quantity` | integer | Quantity triggered for stop-loss or target legs. |
|
|
719
|
+
| `leg_details` | array | Nested leg details for the super order. |
|
|
698
720
|
|
|
699
721
|
> ✅ `CLOSED` indicates the entry leg and either target or stop-loss completed for the full quantity. `TRIGGERED` appears on target and stop-loss legs to highlight which one fired; inspect `triggered_quantity` for executed quantity.
|
|
700
722
|
|
|
@@ -511,6 +511,53 @@ end
|
|
|
511
511
|
| `sell_co_min_margin_per` | Float | Sell CO minimum margin percentage | 0.0, 20.0 |
|
|
512
512
|
| `mtf_leverage` | Float | Margin Trading Facility leverage | 0.0, 4.545455 |
|
|
513
513
|
|
|
514
|
+
### Instrument Convenience Methods
|
|
515
|
+
|
|
516
|
+
The Instrument model provides convenient instance methods that automatically use the instrument's attributes (`security_id`, `exchange_segment`, `instrument`) to fetch market data. This eliminates the need to manually construct parameters for each API call.
|
|
517
|
+
|
|
518
|
+
```ruby
|
|
519
|
+
# Find an instrument first
|
|
520
|
+
instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
521
|
+
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
|
522
|
+
|
|
523
|
+
# Market Feed Methods - automatically uses instrument's attributes
|
|
524
|
+
ltp_data = instrument.ltp # Last traded price
|
|
525
|
+
ohlc_data = instrument.ohlc # OHLC data
|
|
526
|
+
quote_data = instrument.quote # Full quote depth
|
|
527
|
+
|
|
528
|
+
# Historical Data Methods
|
|
529
|
+
daily_data = instrument.daily(
|
|
530
|
+
from_date: "2024-01-01",
|
|
531
|
+
to_date: "2024-01-31",
|
|
532
|
+
expiry_code: 0 # Optional, only for derivatives
|
|
533
|
+
)
|
|
534
|
+
|
|
535
|
+
intraday_data = instrument.intraday(
|
|
536
|
+
from_date: "2024-09-11",
|
|
537
|
+
to_date: "2024-09-15",
|
|
538
|
+
interval: "15" # 1, 5, 15, 25, or 60 minutes
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
# Option Chain Methods (for F&O instruments)
|
|
542
|
+
fn_instrument = DhanHQ::Models::Instrument.find("NSE_FNO", "NIFTY")
|
|
543
|
+
expiries = fn_instrument.expiry_list # Get all available expiries
|
|
544
|
+
chain = fn_instrument.option_chain(expiry: "2024-02-29") # Get option chain for specific expiry
|
|
545
|
+
```
|
|
546
|
+
|
|
547
|
+
**Available Instance Methods:**
|
|
548
|
+
|
|
549
|
+
| Method | Description | Underlying API |
|
|
550
|
+
| ----------------------------------------------------------------- | -------------------------------- | ----------------------------------------------- |
|
|
551
|
+
| `instrument.ltp` | Fetches last traded price | `DhanHQ::Models::MarketFeed.ltp` |
|
|
552
|
+
| `instrument.ohlc` | Fetches OHLC data | `DhanHQ::Models::MarketFeed.ohlc` |
|
|
553
|
+
| `instrument.quote` | Fetches full quote depth | `DhanHQ::Models::MarketFeed.quote` |
|
|
554
|
+
| `instrument.daily(from_date:, to_date:, **options)` | Fetches daily historical data | `DhanHQ::Models::HistoricalData.daily` |
|
|
555
|
+
| `instrument.intraday(from_date:, to_date:, interval:, **options)` | Fetches intraday historical data | `DhanHQ::Models::HistoricalData.intraday` |
|
|
556
|
+
| `instrument.expiry_list` | Fetches expiry list | `DhanHQ::Models::OptionChain.fetch_expiry_list` |
|
|
557
|
+
| `instrument.option_chain(expiry:)` | Fetches option chain | `DhanHQ::Models::OptionChain.fetch` |
|
|
558
|
+
|
|
559
|
+
All methods automatically extract `security_id`, `exchange_segment`, and `instrument` from the instrument instance, making it easier to work with market data without manually managing these parameters.
|
|
560
|
+
|
|
514
561
|
## Rails Integration
|
|
515
562
|
|
|
516
563
|
### Basic Rails Integration
|
|
File without changes
|
|
File without changes
|
|
@@ -74,14 +74,14 @@ depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) do |depth_data|
|
|
|
74
74
|
puts " Ask Levels: #{depth_data[:asks].size}"
|
|
75
75
|
|
|
76
76
|
# Show top 3 bid/ask levels if available
|
|
77
|
-
if depth_data[:bids]
|
|
77
|
+
if depth_data[:bids]&.size&.positive?
|
|
78
78
|
puts " Top Bids:"
|
|
79
79
|
depth_data[:bids].first(3).each_with_index do |bid, i|
|
|
80
80
|
puts " #{i + 1}. Price: #{bid[:price]}, Qty: #{bid[:quantity]}"
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
|
|
84
|
-
if depth_data[:asks]
|
|
84
|
+
if depth_data[:asks]&.size&.positive?
|
|
85
85
|
puts " Top Asks:"
|
|
86
86
|
depth_data[:asks].first(3).each_with_index do |ask, i|
|
|
87
87
|
puts " #{i + 1}. Price: #{ask[:price]}, Qty: #{ask[:quantity]}"
|
|
File without changes
|
|
@@ -109,7 +109,7 @@ def calculate_margin_requirements(instrument, name, quantity, price)
|
|
|
109
109
|
puts " Sell CO Margin Required: ₹#{sell_margin}"
|
|
110
110
|
|
|
111
111
|
# Calculate MTF leverage
|
|
112
|
-
if instrument.mtf_leverage
|
|
112
|
+
if instrument.mtf_leverage.positive?
|
|
113
113
|
mtf_value = total_value * instrument.mtf_leverage
|
|
114
114
|
puts " MTF Leverage: #{instrument.mtf_leverage}x"
|
|
115
115
|
puts " MTF Value: ₹#{mtf_value}"
|
data/lib/DhanHQ/client.rb
CHANGED
|
@@ -38,7 +38,8 @@ module DhanHQ
|
|
|
38
38
|
# @return [DhanHQ::Client] A new client instance.
|
|
39
39
|
def initialize(api_type:)
|
|
40
40
|
DhanHQ.configure_with_env if ENV.fetch("CLIENT_ID", nil)
|
|
41
|
-
|
|
41
|
+
# Use shared rate limiter instance per API type to ensure proper coordination
|
|
42
|
+
@rate_limiter = RateLimiter.for(api_type)
|
|
42
43
|
|
|
43
44
|
raise "RateLimiter initialization failed" unless @rate_limiter
|
|
44
45
|
|
data/lib/DhanHQ/constants.rb
CHANGED
|
@@ -138,6 +138,22 @@ module DhanHQ
|
|
|
138
138
|
/v2/instrument/
|
|
139
139
|
].freeze
|
|
140
140
|
|
|
141
|
+
# Mapping of exchange and segment combinations to canonical exchange segment names.
|
|
142
|
+
# Used for translating between CSV column values (EXCH_ID, SEGMENT) and API segment names.
|
|
143
|
+
# Key format: [exchange, segment] => exchange_segment
|
|
144
|
+
# Example: ["NSE", "E"] => "NSE_EQ"
|
|
145
|
+
SEGMENT_MAP = {
|
|
146
|
+
%w[NSE E] => "NSE_EQ",
|
|
147
|
+
%w[BSE E] => "BSE_EQ",
|
|
148
|
+
%w[NSE D] => "NSE_FNO",
|
|
149
|
+
%w[BSE D] => "BSE_FNO",
|
|
150
|
+
%w[NSE C] => "NSE_CURRENCY",
|
|
151
|
+
%w[BSE C] => "BSE_CURRENCY",
|
|
152
|
+
%w[MCX M] => "MCX_COMM",
|
|
153
|
+
%w[NSE I] => "IDX_I",
|
|
154
|
+
%w[BSE I] => "IDX_I"
|
|
155
|
+
}.freeze
|
|
156
|
+
|
|
141
157
|
# Mapping of DhanHQ error codes to SDK error classes for consistent exception handling.
|
|
142
158
|
DHAN_ERROR_MAPPING = {
|
|
143
159
|
"DH-901" => DhanHQ::InvalidAuthenticationError,
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative "../contracts/instrument_list_contract"
|
|
4
|
+
require_relative "instrument_helpers"
|
|
4
5
|
|
|
5
6
|
module DhanHQ
|
|
6
7
|
module Models
|
|
7
8
|
# Model wrapper for fetching instruments by exchange segment.
|
|
8
9
|
class Instrument < BaseModel
|
|
9
|
-
|
|
10
|
+
include InstrumentHelpers
|
|
11
|
+
|
|
12
|
+
attributes :security_id, :symbol_name, :display_name, :exchange, :segment, :exchange_segment, :instrument, :series,
|
|
10
13
|
:lot_size, :tick_size, :expiry_date, :strike_price, :option_type, :underlying_symbol,
|
|
11
14
|
:isin, :instrument_type, :expiry_flag, :bracket_flag, :cover_flag, :asm_gsm_flag,
|
|
12
15
|
:asm_gsm_category, :buy_sell_indicator, :buy_co_min_margin_per, :sell_co_min_margin_per,
|
|
@@ -112,11 +115,24 @@ module DhanHQ
|
|
|
112
115
|
end
|
|
113
116
|
|
|
114
117
|
def normalize_csv_row(row)
|
|
118
|
+
# Extract exchange and segment from CSV
|
|
119
|
+
exchange_id = row["EXCH_ID"] || row["EXCHANGE"]
|
|
120
|
+
segment_code = row["SEGMENT"]
|
|
121
|
+
|
|
122
|
+
# Calculate exchange_segment using SEGMENT_MAP from Constants
|
|
123
|
+
exchange_segment = if exchange_id && segment_code
|
|
124
|
+
DhanHQ::Constants::SEGMENT_MAP[[exchange_id, segment_code]]
|
|
125
|
+
else
|
|
126
|
+
row["EXCH_ID"] # Fallback to original value
|
|
127
|
+
end
|
|
128
|
+
|
|
115
129
|
{
|
|
116
130
|
security_id: row["SECURITY_ID"].to_s,
|
|
117
131
|
symbol_name: row["SYMBOL_NAME"],
|
|
118
132
|
display_name: row["DISPLAY_NAME"],
|
|
119
|
-
|
|
133
|
+
exchange: exchange_id,
|
|
134
|
+
segment: segment_code,
|
|
135
|
+
exchange_segment: exchange_segment,
|
|
120
136
|
instrument: row["INSTRUMENT"],
|
|
121
137
|
series: row["SERIES"],
|
|
122
138
|
lot_size: row["LOT_SIZE"]&.to_f,
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DhanHQ
|
|
4
|
+
module Models
|
|
5
|
+
# Helper module providing instance methods for Instrument objects
|
|
6
|
+
# to access market feed, historical data, and option chain data.
|
|
7
|
+
module InstrumentHelpers
|
|
8
|
+
##
|
|
9
|
+
# Fetches last traded price (LTP) for this instrument.
|
|
10
|
+
#
|
|
11
|
+
# @return [Float, nil] Last traded price value, or nil if not available
|
|
12
|
+
# @example
|
|
13
|
+
# instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
14
|
+
# instrument.ltp # => 26053.9
|
|
15
|
+
def ltp
|
|
16
|
+
params = build_market_feed_params
|
|
17
|
+
response = DhanHQ::Models::MarketFeed.ltp(params)
|
|
18
|
+
|
|
19
|
+
# Extract last_price from nested response structure
|
|
20
|
+
# Response format: {"data" => {"EXCHANGE_SEGMENT" => {"security_id" => {"last_price" => value}}}, "status" => "success"}
|
|
21
|
+
data = response[:data] || response["data"]
|
|
22
|
+
return nil unless data
|
|
23
|
+
|
|
24
|
+
segment_data = data[exchange_segment] || data[exchange_segment.to_sym]
|
|
25
|
+
return nil unless segment_data
|
|
26
|
+
|
|
27
|
+
security_data = segment_data[security_id] || segment_data[security_id.to_s]
|
|
28
|
+
return nil unless security_data
|
|
29
|
+
|
|
30
|
+
security_data[:last_price] || security_data["last_price"]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
##
|
|
34
|
+
# Fetches OHLC (Open-High-Low-Close) data for this instrument.
|
|
35
|
+
#
|
|
36
|
+
# @return [Hash] Market feed OHLC response
|
|
37
|
+
# @example
|
|
38
|
+
# instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
39
|
+
# instrument.ohlc
|
|
40
|
+
def ohlc
|
|
41
|
+
params = build_market_feed_params
|
|
42
|
+
DhanHQ::Models::MarketFeed.ohlc(params)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
##
|
|
46
|
+
# Fetches full quote depth and analytics for this instrument.
|
|
47
|
+
#
|
|
48
|
+
# @return [Hash] Market feed quote response
|
|
49
|
+
# @example
|
|
50
|
+
# instrument = DhanHQ::Models::Instrument.find("NSE_FNO", "RELIANCE")
|
|
51
|
+
# instrument.quote
|
|
52
|
+
def quote
|
|
53
|
+
params = build_market_feed_params
|
|
54
|
+
DhanHQ::Models::MarketFeed.quote(params)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
##
|
|
58
|
+
# Fetches daily historical data for this instrument.
|
|
59
|
+
#
|
|
60
|
+
# @param from_date [String] Start date in YYYY-MM-DD format
|
|
61
|
+
# @param to_date [String] End date in YYYY-MM-DD format
|
|
62
|
+
# @param options [Hash] Additional options (e.g., expiry_code)
|
|
63
|
+
# @return [Hash] Historical data with open, high, low, close, volume, timestamp arrays
|
|
64
|
+
# @example
|
|
65
|
+
# instrument = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
|
66
|
+
# instrument.daily(from_date: "2024-01-01", to_date: "2024-01-31")
|
|
67
|
+
def daily(from_date:, to_date:, **options)
|
|
68
|
+
params = build_historical_data_params(from_date: from_date, to_date: to_date, **options)
|
|
69
|
+
DhanHQ::Models::HistoricalData.daily(params)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
##
|
|
73
|
+
# Fetches intraday historical data for this instrument.
|
|
74
|
+
#
|
|
75
|
+
# @param from_date [String] Start date in YYYY-MM-DD format
|
|
76
|
+
# @param to_date [String] End date in YYYY-MM-DD format
|
|
77
|
+
# @param interval [String] Time interval in minutes (1, 5, 15, 25, 60)
|
|
78
|
+
# @param options [Hash] Additional options
|
|
79
|
+
# @return [Hash] Historical data with open, high, low, close, volume, timestamp arrays
|
|
80
|
+
# @example
|
|
81
|
+
# instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
82
|
+
# instrument.intraday(from_date: "2024-09-11", to_date: "2024-09-15", interval: "15")
|
|
83
|
+
def intraday(from_date:, to_date:, interval:, **options)
|
|
84
|
+
params = build_historical_data_params(from_date: from_date, to_date: to_date, interval: interval, **options)
|
|
85
|
+
DhanHQ::Models::HistoricalData.intraday(params)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
##
|
|
89
|
+
# Fetches the expiry list for this instrument (option chain).
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<String>] List of expiry dates in YYYY-MM-DD format
|
|
92
|
+
# @example
|
|
93
|
+
# instrument = DhanHQ::Models::Instrument.find("NSE_FNO", "NIFTY")
|
|
94
|
+
# expiries = instrument.expiry_list
|
|
95
|
+
def expiry_list
|
|
96
|
+
params = {
|
|
97
|
+
underlying_scrip: security_id.to_i,
|
|
98
|
+
underlying_seg: underlying_segment_for_options
|
|
99
|
+
}
|
|
100
|
+
DhanHQ::Models::OptionChain.fetch_expiry_list(params)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
##
|
|
104
|
+
# Fetches the option chain for this instrument.
|
|
105
|
+
#
|
|
106
|
+
# @param expiry [String] Expiry date in YYYY-MM-DD format
|
|
107
|
+
# @return [Hash] Option chain data
|
|
108
|
+
# @example
|
|
109
|
+
# instrument = DhanHQ::Models::Instrument.find("NSE_FNO", "NIFTY")
|
|
110
|
+
# chain = instrument.option_chain(expiry: "2024-02-29")
|
|
111
|
+
def option_chain(expiry:)
|
|
112
|
+
params = {
|
|
113
|
+
underlying_scrip: security_id.to_i,
|
|
114
|
+
underlying_seg: underlying_segment_for_options,
|
|
115
|
+
expiry: expiry
|
|
116
|
+
}
|
|
117
|
+
DhanHQ::Models::OptionChain.fetch(params)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
private
|
|
121
|
+
|
|
122
|
+
##
|
|
123
|
+
# Builds market feed params from instrument attributes.
|
|
124
|
+
#
|
|
125
|
+
# @return [Hash] Market feed params in format { "EXCHANGE_SEGMENT": [security_id] }
|
|
126
|
+
def build_market_feed_params
|
|
127
|
+
{ exchange_segment => [security_id.to_i] }
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
##
|
|
131
|
+
# Builds historical data params from instrument attributes.
|
|
132
|
+
#
|
|
133
|
+
# @param from_date [String] Start date
|
|
134
|
+
# @param to_date [String] End date
|
|
135
|
+
# @param interval [String, nil] Time interval for intraday
|
|
136
|
+
# @param options [Hash] Additional options
|
|
137
|
+
# @return [Hash] Historical data params
|
|
138
|
+
def build_historical_data_params(from_date:, to_date:, interval: nil, **options)
|
|
139
|
+
params = {
|
|
140
|
+
security_id: security_id,
|
|
141
|
+
exchange_segment: exchange_segment,
|
|
142
|
+
instrument: instrument,
|
|
143
|
+
from_date: from_date,
|
|
144
|
+
to_date: to_date
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
params[:interval] = interval if interval
|
|
148
|
+
params.merge!(options) if options.any?
|
|
149
|
+
|
|
150
|
+
params
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
##
|
|
154
|
+
# Determines the correct underlying segment for option chain APIs.
|
|
155
|
+
# Index uses IDX_I; stocks map to NSE_FNO or BSE_FNO by exchange.
|
|
156
|
+
def underlying_segment_for_options
|
|
157
|
+
seg = exchange_segment.to_s
|
|
158
|
+
ins = instrument.to_s
|
|
159
|
+
|
|
160
|
+
# If already a valid underlying seg, return as-is
|
|
161
|
+
return seg if %w[IDX_I NSE_FNO BSE_FNO MCX_FO].include?(seg)
|
|
162
|
+
|
|
163
|
+
# Index detection by instrument kind or segment
|
|
164
|
+
return "IDX_I" if ins == "INDEX" || seg == "IDX_I"
|
|
165
|
+
|
|
166
|
+
# Map equities/stock-related segments to respective FNO
|
|
167
|
+
return "NSE_FNO" if seg.start_with?("NSE")
|
|
168
|
+
return "BSE_FNO" if seg.start_with?("BSE")
|
|
169
|
+
|
|
170
|
+
# Fallback to IDX_I to avoid contract rejection
|
|
171
|
+
"IDX_I"
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
end
|
|
175
|
+
end
|
data/lib/DhanHQ/rate_limiter.rb
CHANGED
|
@@ -15,13 +15,31 @@ module DhanHQ
|
|
|
15
15
|
per_day: Float::INFINITY }
|
|
16
16
|
}.freeze
|
|
17
17
|
|
|
18
|
+
# Thread-safe shared rate limiters per API type
|
|
19
|
+
@shared_limiters = Concurrent::Map.new
|
|
20
|
+
@mutexes = Concurrent::Map.new
|
|
21
|
+
|
|
22
|
+
class << self
|
|
23
|
+
attr_reader :shared_limiters, :mutexes
|
|
24
|
+
|
|
25
|
+
# Get or create a shared rate limiter instance for the given API type
|
|
26
|
+
def for(api_type)
|
|
27
|
+
@shared_limiters[api_type] ||= new(api_type)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
18
31
|
# Creates a rate limiter for a given API type.
|
|
32
|
+
# Note: For proper rate limiting coordination, use RateLimiter.for(api_type) instead
|
|
33
|
+
# of RateLimiter.new(api_type) to get a shared instance.
|
|
19
34
|
#
|
|
20
35
|
# @param api_type [Symbol] One of the keys from {RATE_LIMITS}.
|
|
21
36
|
def initialize(api_type)
|
|
22
37
|
@api_type = api_type
|
|
23
38
|
@buckets = Concurrent::Hash.new
|
|
24
39
|
@buckets[:last_request_time] = Time.at(0) if api_type == :option_chain
|
|
40
|
+
# Track request timestamps for per-second limiting
|
|
41
|
+
@request_times = []
|
|
42
|
+
@window_start = Time.now
|
|
25
43
|
initialize_buckets
|
|
26
44
|
start_cleanup_threads
|
|
27
45
|
end
|
|
@@ -30,34 +48,70 @@ module DhanHQ
|
|
|
30
48
|
#
|
|
31
49
|
# @return [void]
|
|
32
50
|
def throttle!
|
|
33
|
-
|
|
34
|
-
|
|
51
|
+
mutex.synchronize do
|
|
52
|
+
if @api_type == :option_chain
|
|
53
|
+
last_request_time = @buckets[:last_request_time]
|
|
35
54
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
55
|
+
sleep_time = 3 - (Time.now - last_request_time)
|
|
56
|
+
if sleep_time.positive?
|
|
57
|
+
if ENV["DHAN_DEBUG"] == "true"
|
|
58
|
+
puts "Sleeping for #{sleep_time.round(2)} seconds due to option_chain rate limit"
|
|
59
|
+
end
|
|
60
|
+
sleep(sleep_time)
|
|
40
61
|
end
|
|
41
|
-
|
|
62
|
+
|
|
63
|
+
@buckets[:last_request_time] = Time.now
|
|
64
|
+
return
|
|
42
65
|
end
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
67
|
+
# For per-second limits, use timestamp-based sliding window
|
|
68
|
+
per_second_limit = RATE_LIMITS[@api_type][:per_second]
|
|
69
|
+
if per_second_limit && per_second_limit != Float::INFINITY
|
|
70
|
+
now = Time.now
|
|
71
|
+
# Remove requests older than 1 second
|
|
72
|
+
@request_times.reject! { |t| now - t >= 1.0 }
|
|
73
|
+
|
|
74
|
+
# Check if we've hit the per-second limit
|
|
75
|
+
if @request_times.size >= per_second_limit
|
|
76
|
+
# Calculate how long to wait until the oldest request is 1 second old
|
|
77
|
+
oldest_time = @request_times.min
|
|
78
|
+
wait_time = 1.0 - (now - oldest_time)
|
|
79
|
+
|
|
80
|
+
if wait_time.positive?
|
|
81
|
+
sleep(wait_time)
|
|
82
|
+
# Recalculate after sleep
|
|
83
|
+
now = Time.now
|
|
84
|
+
@request_times.reject! { |t| now - t >= 1.0 }
|
|
85
|
+
end
|
|
86
|
+
end
|
|
47
87
|
|
|
48
|
-
|
|
49
|
-
|
|
88
|
+
# Record this request time
|
|
89
|
+
@request_times << Time.now
|
|
90
|
+
end
|
|
50
91
|
|
|
51
|
-
|
|
92
|
+
# Check other limits (per_minute, per_hour, per_day)
|
|
93
|
+
loop do
|
|
94
|
+
break if allow_request?
|
|
95
|
+
|
|
96
|
+
sleep(0.1)
|
|
97
|
+
end
|
|
98
|
+
record_request
|
|
52
99
|
end
|
|
53
|
-
record_request
|
|
54
100
|
end
|
|
55
101
|
|
|
56
102
|
private
|
|
57
103
|
|
|
104
|
+
# Gets or creates a mutex for this API type for thread-safe throttling
|
|
105
|
+
def mutex
|
|
106
|
+
self.class.mutexes[@api_type] ||= Mutex.new
|
|
107
|
+
end
|
|
108
|
+
|
|
58
109
|
# Prepares the counters used for each interval in {RATE_LIMITS}.
|
|
59
110
|
def initialize_buckets
|
|
60
111
|
RATE_LIMITS[@api_type].each_key do |interval|
|
|
112
|
+
# Skip per_second as we handle it with timestamps
|
|
113
|
+
next if interval == :per_second
|
|
114
|
+
|
|
61
115
|
@buckets[interval] = Concurrent::AtomicFixnum.new(0)
|
|
62
116
|
end
|
|
63
117
|
end
|
|
@@ -67,6 +121,9 @@ module DhanHQ
|
|
|
67
121
|
# @return [Boolean]
|
|
68
122
|
def allow_request?
|
|
69
123
|
RATE_LIMITS[@api_type].all? do |interval, limit|
|
|
124
|
+
# Skip per_second check as it's handled in throttle! with timestamps
|
|
125
|
+
next true if interval == :per_second
|
|
126
|
+
|
|
70
127
|
@buckets[interval].value < limit
|
|
71
128
|
end
|
|
72
129
|
end
|
|
@@ -74,34 +131,32 @@ module DhanHQ
|
|
|
74
131
|
# Increments the counters for each time window once a request is made.
|
|
75
132
|
def record_request
|
|
76
133
|
RATE_LIMITS[@api_type].each_key do |interval|
|
|
134
|
+
# Skip per_second as it's handled with timestamps
|
|
135
|
+
next if interval == :per_second
|
|
136
|
+
|
|
77
137
|
@buckets[interval].increment
|
|
78
138
|
end
|
|
79
139
|
end
|
|
80
140
|
|
|
81
141
|
# Spawns background threads to reset counters after each interval elapses.
|
|
82
142
|
def start_cleanup_threads
|
|
83
|
-
|
|
84
|
-
loop do
|
|
85
|
-
sleep(1)
|
|
86
|
-
@buckets[:per_second].value = 0
|
|
87
|
-
end
|
|
88
|
-
end
|
|
143
|
+
# Don't create per_second cleanup thread - we handle it with timestamps
|
|
89
144
|
Thread.new do
|
|
90
145
|
loop do
|
|
91
146
|
sleep(60)
|
|
92
|
-
@buckets[:per_minute]
|
|
147
|
+
@buckets[:per_minute]&.value = 0
|
|
93
148
|
end
|
|
94
149
|
end
|
|
95
150
|
Thread.new do
|
|
96
151
|
loop do
|
|
97
152
|
sleep(3600)
|
|
98
|
-
@buckets[:per_hour]
|
|
153
|
+
@buckets[:per_hour]&.value = 0
|
|
99
154
|
end
|
|
100
155
|
end
|
|
101
156
|
Thread.new do
|
|
102
157
|
loop do
|
|
103
158
|
sleep(86_400)
|
|
104
|
-
@buckets[:per_day]
|
|
159
|
+
@buckets[:per_day]&.value = 0
|
|
105
160
|
end
|
|
106
161
|
end
|
|
107
162
|
end
|
data/lib/DhanHQ/version.rb
CHANGED
|
@@ -213,7 +213,9 @@ module DhanHQ
|
|
|
213
213
|
|
|
214
214
|
instrument = find_instrument(symbol_code, segment_hint)
|
|
215
215
|
unless instrument
|
|
216
|
-
DhanHQ.logger&.warn(
|
|
216
|
+
DhanHQ.logger&.warn(
|
|
217
|
+
"[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code} (segment hint: #{segment_hint || "AUTO"})"
|
|
218
|
+
)
|
|
217
219
|
return nil
|
|
218
220
|
end
|
|
219
221
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: DhanHQ
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 2.1.
|
|
4
|
+
version: 2.1.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Shubham Taywade
|
|
@@ -227,6 +227,7 @@ files:
|
|
|
227
227
|
- lib/DhanHQ/models/historical_data.rb
|
|
228
228
|
- lib/DhanHQ/models/holding.rb
|
|
229
229
|
- lib/DhanHQ/models/instrument.rb
|
|
230
|
+
- lib/DhanHQ/models/instrument_helpers.rb
|
|
230
231
|
- lib/DhanHQ/models/kill_switch.rb
|
|
231
232
|
- lib/DhanHQ/models/ledger_entry.rb
|
|
232
233
|
- lib/DhanHQ/models/margin.rb
|