DhanHQ 2.4.0 → 2.5.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/CHANGELOG.md +33 -1
- data/README.md +194 -752
- data/docs/AUTHENTICATION.md +71 -9
- data/docs/CONFIGURATION.md +109 -0
- data/docs/SUPER_ORDERS.md +284 -0
- data/docs/TROUBLESHOOTING.md +117 -0
- data/docs/WEBSOCKET_PROTOCOL.md +154 -0
- data/lib/DhanHQ/models/alert_order.rb +22 -0
- data/lib/DhanHQ/models/edis.rb +110 -0
- data/lib/DhanHQ/models/kill_switch.rb +22 -0
- data/lib/DhanHQ/models/margin.rb +49 -0
- data/lib/DhanHQ/models/pnl_exit.rb +130 -0
- data/lib/DhanHQ/models/position.rb +22 -0
- data/lib/DhanHQ/models/postback.rb +123 -0
- data/lib/DhanHQ/resources/kill_switch.rb +8 -0
- data/lib/DhanHQ/resources/margin_calculator.rb +9 -0
- data/lib/DhanHQ/resources/pnl_exit.rb +37 -0
- data/lib/DhanHQ/resources/positions.rb +8 -0
- data/lib/DhanHQ/version.rb +1 -1
- metadata +11 -3
- /data/{README1.md → docs/ARCHIVE_README.md} +0 -0
data/README.md
CHANGED
|
@@ -1,884 +1,326 @@
|
|
|
1
|
-
# DhanHQ — Ruby Client for
|
|
1
|
+
# DhanHQ — Ruby Client for Dhan API v2
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
[](https://rubygems.org/gems/DhanHQ)
|
|
4
|
+
[](https://github.com/shubhamtaywade82/dhanhq-client/actions/workflows/main.yml)
|
|
5
|
+
[](https://www.ruby-lang.org)
|
|
6
|
+
[](LICENSE.txt)
|
|
4
7
|
|
|
5
|
-
|
|
6
|
-
* Validations & errors exposed via ActiveModel-like interfaces
|
|
7
|
-
* REST coverage: Orders, Alert Orders, Super Orders, Forever Orders, Trades, Positions, Holdings, Funds/Margin, HistoricalData, OptionChain, MarketFeed, ExpiredOptionsData, EDIS, IP Setup, Trader Control (Kill Switch)
|
|
8
|
-
* **WebSocket**: Orders, Market Feed, Market Depth - subscribe/unsubscribe dynamically, auto-reconnect with backoff, 429 cool-off, idempotent subs, header+payload binary parsing, normalized ticks
|
|
8
|
+
A production-grade Ruby SDK for the [Dhan trading API](https://dhanhq.co/docs/v2/) — ORM-like models, WebSocket market feeds, and battle-tested reliability for real trading.
|
|
9
9
|
|
|
10
|
-
##
|
|
11
|
-
|
|
12
|
-
**IMPORTANT**: Starting from version 2.1.5, the require statement has changed:
|
|
10
|
+
## 🚀 60-Second Quick Start
|
|
13
11
|
|
|
14
12
|
```ruby
|
|
15
|
-
#
|
|
16
|
-
|
|
13
|
+
# Gemfile
|
|
14
|
+
gem 'DhanHQ'
|
|
15
|
+
```
|
|
17
16
|
|
|
18
|
-
|
|
17
|
+
```ruby
|
|
19
18
|
require 'dhan_hq'
|
|
19
|
+
|
|
20
|
+
DhanHQ.configure do |c|
|
|
21
|
+
c.client_id = ENV["DHAN_CLIENT_ID"]
|
|
22
|
+
c.access_token = ENV["DHAN_ACCESS_TOKEN"]
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# You're live
|
|
26
|
+
positions = DhanHQ::Models::Position.all
|
|
27
|
+
holdings = DhanHQ::Models::Holding.all
|
|
20
28
|
```
|
|
21
29
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Why DhanHQ?
|
|
33
|
+
|
|
34
|
+
You could wire up Faraday and parse JSON yourself. Here's why you shouldn't:
|
|
27
35
|
|
|
28
|
-
|
|
36
|
+
| You get | Instead of |
|
|
37
|
+
| ------------------------------ | --------------------------------------------- |
|
|
38
|
+
| ActiveModel-style `find`, `all`, `save`, `cancel` | Manual HTTP + JSON wrangling |
|
|
39
|
+
| Typed models with validations | Hash soup and runtime surprises |
|
|
40
|
+
| Auto token refresh + retry-on-401 | Silent auth failures at 3 AM |
|
|
41
|
+
| WebSocket reconnection with backoff | Dropped connections during volatile moves |
|
|
42
|
+
| 429 rate-limit cool-off | Getting banned by the exchange |
|
|
43
|
+
| Thread-safe, secure logging | Leaked tokens in production logs |
|
|
29
44
|
|
|
30
45
|
---
|
|
31
46
|
|
|
32
|
-
##
|
|
47
|
+
## ✨ Key Features
|
|
33
48
|
|
|
34
|
-
|
|
49
|
+
- **ActiveRecord-style models** — `find`, `all`, `where`, `save`, `cancel` across Orders, Positions, Holdings, Funds, and more
|
|
50
|
+
- **Auto token refresh** — 401 retry with fresh token via provider callback
|
|
51
|
+
- **Thread-safe WebSocket client** — Orders, Market Feed, Market Depth with auto-reconnect
|
|
52
|
+
- **Exponential backoff + 429 cool-off** — no manual rate-limit management
|
|
53
|
+
- **Secure logging** — automatic token sanitization in all log output
|
|
54
|
+
- **Super Orders** — entry + stop-loss + target + trailing jump in one request
|
|
55
|
+
- **Instrument convenience methods** — `.ltp`, `.ohlc`, `.option_chain` directly on instruments
|
|
56
|
+
- **Full REST coverage** — Orders, Trades, Forever Orders, Super Orders, Positions, Holdings, Funds, HistoricalData, OptionChain, MarketFeed, EDIS, Kill Switch, P&L Exit, Alert Orders, Margin Calculator
|
|
57
|
+
- **P&L Based Exit** — automatic position exit on profit/loss thresholds
|
|
58
|
+
- **Postback parser** — parse Dhan webhook payloads with `Postback.parse` and status predicates
|
|
59
|
+
- **EDIS model** — ORM-style T-PIN, form, and status inquiry for delivery instruction slips
|
|
35
60
|
|
|
36
|
-
|
|
37
|
-
gem 'DhanHQ', git: 'https://github.com/shubhamtaywade82/dhanhq-client.git', branch: 'main'
|
|
38
|
-
```
|
|
61
|
+
---
|
|
39
62
|
|
|
40
|
-
|
|
63
|
+
## Installation
|
|
41
64
|
|
|
42
|
-
```
|
|
43
|
-
|
|
65
|
+
```ruby
|
|
66
|
+
# Gemfile (recommended)
|
|
67
|
+
gem 'DhanHQ'
|
|
44
68
|
```
|
|
45
69
|
|
|
46
|
-
Or:
|
|
47
|
-
|
|
48
70
|
```bash
|
|
71
|
+
bundle install
|
|
72
|
+
# or
|
|
49
73
|
gem install DhanHQ
|
|
50
74
|
```
|
|
51
75
|
|
|
52
|
-
|
|
76
|
+
> **Bleeding edge?** Use `gem 'DhanHQ', git: 'https://github.com/shubhamtaywade82/dhanhq-client.git', branch: 'main'` only if you need unreleased features.
|
|
53
77
|
|
|
54
|
-
|
|
78
|
+
### ⚠️ Breaking Change (v2.1.5+)
|
|
55
79
|
|
|
56
|
-
|
|
80
|
+
The require statement changed:
|
|
57
81
|
|
|
58
82
|
```ruby
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
DhanHQ.configure_with_env
|
|
62
|
-
DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Logger.const_get(level) }
|
|
83
|
+
# Before # Now
|
|
84
|
+
require 'DhanHQ' → require 'dhan_hq'
|
|
63
85
|
```
|
|
64
86
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
| Variable | Purpose |
|
|
68
|
-
| -------------- | ------------------------------------------------- |
|
|
69
|
-
| `DHAN_CLIENT_ID` | Trading account client id issued by Dhan. |
|
|
70
|
-
| `DHAN_ACCESS_TOKEN` | API access token generated from the Dhan console. |
|
|
87
|
+
The gem name in your Gemfile stays `DhanHQ` — only the `require` changes.
|
|
71
88
|
|
|
72
|
-
|
|
73
|
-
Rails credentials, or any other mechanism that populates `ENV` before
|
|
74
|
-
initialisation.
|
|
89
|
+
---
|
|
75
90
|
|
|
76
|
-
|
|
91
|
+
## Configuration
|
|
77
92
|
|
|
78
|
-
|
|
79
|
-
override defaults supplied by the gem:
|
|
93
|
+
### Static token (simplest)
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| `DHAN_WS_VERSION` | Pin to a specific WebSocket API version. |
|
|
86
|
-
| `DHAN_WS_ORDER_URL` | Override the order update WebSocket endpoint. |
|
|
87
|
-
| `DHAN_WS_USER_TYPE` | Switch between `SELF` and `PARTNER` streaming modes. |
|
|
88
|
-
| `DHAN_PARTNER_ID` / `DHAN_PARTNER_SECRET` | Required when `DHAN_WS_USER_TYPE=PARTNER`. |
|
|
89
|
-
| `DHAN_CONNECT_TIMEOUT` | Connection timeout in seconds (default: 10). |
|
|
90
|
-
| `DHAN_READ_TIMEOUT` | Read timeout in seconds (default: 30). |
|
|
91
|
-
| `DHAN_WRITE_TIMEOUT` | Write timeout in seconds (default: 30). |
|
|
92
|
-
| `DHAN_WS_MAX_TRACKED_ORDERS` | Maximum orders to track in WebSocket (default: 10,000). |
|
|
93
|
-
| `DHAN_WS_MAX_ORDER_AGE` | Maximum order age in seconds before cleanup (default: 604,800 = 7 days). |
|
|
95
|
+
```ruby
|
|
96
|
+
require 'dhan_hq'
|
|
97
|
+
DhanHQ.configure_with_env # reads DHAN_CLIENT_ID + DHAN_ACCESS_TOKEN from ENV
|
|
98
|
+
```
|
|
94
99
|
|
|
95
|
-
|
|
100
|
+
| Variable | Purpose |
|
|
101
|
+
| -------------------- | -------------------------------------- |
|
|
102
|
+
| `DHAN_CLIENT_ID` | Your Dhan trading account client ID |
|
|
103
|
+
| `DHAN_ACCESS_TOKEN` | API token from the Dhan console |
|
|
96
104
|
|
|
97
|
-
|
|
105
|
+
### Dynamic token (production / OAuth)
|
|
98
106
|
|
|
99
107
|
```ruby
|
|
100
108
|
DhanHQ.configure do |config|
|
|
101
109
|
config.client_id = ENV["DHAN_CLIENT_ID"]
|
|
102
|
-
config.access_token_provider =
|
|
103
|
-
|
|
104
|
-
raise "Token expired or missing" unless token
|
|
105
|
-
token
|
|
106
|
-
end
|
|
107
|
-
# Optional: called when the API returns 401/token-expired and the client is about to retry
|
|
108
|
-
config.on_token_expired = ->(error) { YourTokenStore.refresh! }
|
|
110
|
+
config.access_token_provider = -> { YourTokenStore.active_token }
|
|
111
|
+
config.on_token_expired = ->(error) { YourTokenStore.refresh! } # optional
|
|
109
112
|
end
|
|
110
113
|
```
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
- **`on_token_expired`**: Optional callable invoked when a 401/token-expired triggers a **single retry** (only when `access_token_provider` is set). Use for logging or refreshing your store; the retry then uses the token from the provider.
|
|
114
|
-
|
|
115
|
-
If the API returns **401** or error code **807** (token expired) and `access_token_provider` is set, the client retries the request **once** with a fresh token from the provider. Otherwise it raises `DhanHQ::InvalidAuthenticationError` or `DhanHQ::TokenExpiredError`. Missing or nil token from config raises `DhanHQ::AuthenticationError`.
|
|
116
|
-
|
|
117
|
-
### Authentication approaches (overview)
|
|
118
|
-
|
|
119
|
-
The gem intentionally **does not own** Dhan’s consent flows (web, API key/secret, Partner). You obtain a token using Dhan’s docs, then choose one of these approaches:
|
|
120
|
-
|
|
121
|
-
- **Static token (ENV / config)**: Set `DHAN_CLIENT_ID` and `DHAN_ACCESS_TOKEN` via ENV and call `DhanHQ.configure_with_env`, or assign `config.client_id` / `config.access_token` manually. Best when you’re comfortable rotating tokens out-of-band (cron, runbook).
|
|
122
|
-
- **Dynamic token provider**: Use `config.access_token_provider` (and optional `config.on_token_expired`) so the gem asks your app for the token on every request and retries **once** on 401 with a fresh token. Ideal for OAuth-style flows or central token stores.
|
|
123
|
-
- **Token endpoint bootstrap**: Call `DhanHQ.configure_from_token_endpoint(base_url:, bearer_token:)` to fetch `{ access_token, client_id, base_url? }` from your own HTTP endpoint and populate configuration in one step.
|
|
124
|
-
- **TOTP-based token generation (individuals)**: Use `DhanHQ::Auth.generate_totp(secret)` + `DhanHQ::Auth.generate_access_token(dhan_client_id:, pin:, totp:)` when you want fully automated, TOTP-backed tokens for a single account. The low-level API returns the raw response hash from Dhan.
|
|
125
|
-
- **Client helpers + auto management**: Use `client.generate_access_token(dhan_client_id:, pin:, totp: nil, totp_secret: nil)` and `client.enable_auto_token_management!(dhan_client_id:, pin:, totp_secret:)` when you want `TokenResponse` objects, config auto-updates, and background token lifecycle (generate + renew) wrapped around each request.
|
|
126
|
-
- **RenewToken (web-generated tokens)**: For 24h tokens generated from **Dhan Web**, call `DhanHQ::Auth.renew_token(access_token:, client_id:)` or `client.renew_access_token` / `TokenManager#refresh!` to renew. This only works for active web tokens; for TOTP-generated tokens prefer regenerating.
|
|
115
|
+
When the API returns 401, the client retries **once** with a fresh token from your provider.
|
|
127
116
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
### Logging
|
|
131
|
-
|
|
132
|
-
```ruby
|
|
133
|
-
DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Logger.const_get(level) }
|
|
134
|
-
```
|
|
117
|
+
> **Full details**: TOTP flows, partner mode, token endpoint bootstrap, auto-management — see [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md).
|
|
135
118
|
|
|
136
119
|
---
|
|
137
120
|
|
|
138
|
-
##
|
|
121
|
+
## REST API
|
|
122
|
+
|
|
123
|
+
### Orders — Place, Modify, Cancel
|
|
139
124
|
|
|
140
125
|
```ruby
|
|
141
|
-
# Place an order
|
|
142
126
|
order = DhanHQ::Models::Order.new(
|
|
143
127
|
transaction_type: "BUY",
|
|
144
128
|
exchange_segment: "NSE_FNO",
|
|
145
|
-
product_type:
|
|
146
|
-
order_type:
|
|
147
|
-
validity:
|
|
148
|
-
security_id:
|
|
149
|
-
quantity:
|
|
150
|
-
price:
|
|
129
|
+
product_type: "MARGIN",
|
|
130
|
+
order_type: "LIMIT",
|
|
131
|
+
validity: "DAY",
|
|
132
|
+
security_id: "43492",
|
|
133
|
+
quantity: 50,
|
|
134
|
+
price: 100.0
|
|
151
135
|
)
|
|
152
|
-
order.save
|
|
153
|
-
|
|
154
|
-
# Modify / Cancel
|
|
136
|
+
order.save # places the order
|
|
155
137
|
order.modify(price: 101.5)
|
|
156
138
|
order.cancel
|
|
139
|
+
```
|
|
157
140
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
141
|
+
### Positions, Holdings, Funds
|
|
142
|
+
|
|
143
|
+
```ruby
|
|
144
|
+
DhanHQ::Models::Position.all
|
|
145
|
+
DhanHQ::Models::Holding.all
|
|
146
|
+
DhanHQ::Models::Fund.balance
|
|
147
|
+
```
|
|
161
148
|
|
|
162
|
-
|
|
149
|
+
### Historical Data
|
|
150
|
+
|
|
151
|
+
```ruby
|
|
163
152
|
bars = DhanHQ::Models::HistoricalData.intraday(
|
|
164
|
-
security_id:
|
|
153
|
+
security_id: "13",
|
|
165
154
|
exchange_segment: "IDX_I",
|
|
166
|
-
instrument:
|
|
167
|
-
interval:
|
|
168
|
-
from_date:
|
|
169
|
-
to_date:
|
|
170
|
-
)
|
|
171
|
-
|
|
172
|
-
# Option Chain (example)
|
|
173
|
-
oc = DhanHQ::Models::OptionChain.fetch(
|
|
174
|
-
underlying_scrip: 1333, # example underlying ID
|
|
175
|
-
underlying_seg: "NSE_FNO",
|
|
176
|
-
expiry: "2025-08-21"
|
|
155
|
+
instrument: "INDEX",
|
|
156
|
+
interval: "5",
|
|
157
|
+
from_date: "2025-08-14",
|
|
158
|
+
to_date: "2025-08-18"
|
|
177
159
|
)
|
|
178
160
|
```
|
|
179
161
|
|
|
180
|
-
###
|
|
181
|
-
|
|
182
|
-
Need a full-stack example inside Rails (REST + WebSockets + automation)? Check
|
|
183
|
-
out the [Rails integration guide](docs/rails_integration.md) for
|
|
184
|
-
initializers, service objects, workers, and ActionCable wiring tailored for the
|
|
185
|
-
`DhanHQ` gem.
|
|
186
|
-
|
|
187
|
-
### Testing & Development
|
|
162
|
+
### Instrument Lookup
|
|
188
163
|
|
|
189
|
-
For comprehensive testing examples and interactive console helpers, see the [Testing Guide](docs/TESTING_GUIDE.md). The guide includes:
|
|
190
|
-
|
|
191
|
-
- **WebSocket Testing**: Market feed, order updates, and market depth examples
|
|
192
|
-
- **Model Testing**: Complete examples for all models (Orders, Positions, Holdings, etc.)
|
|
193
|
-
- **Validation Contracts**: Testing all validation contracts
|
|
194
|
-
- **Error Handling**: Testing error scenarios and recovery
|
|
195
|
-
- **Quick Helpers**: Load `bin/test_helpers.rb` in console for quick test functions
|
|
196
|
-
|
|
197
|
-
**Quick start in console:**
|
|
198
164
|
```ruby
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
# Run quick tests
|
|
206
|
-
run_all_tests
|
|
207
|
-
|
|
208
|
-
# Or test individual features
|
|
209
|
-
test_funds
|
|
210
|
-
test_market_feed
|
|
211
|
-
test_orders
|
|
165
|
+
nifty = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
|
|
166
|
+
nifty.ltp # last traded price
|
|
167
|
+
nifty.ohlc # OHLC data
|
|
168
|
+
nifty.option_chain(expiry: "2025-02-28")
|
|
169
|
+
nifty.intraday(from_date: "2025-08-14", to_date: "2025-08-18", interval: "15")
|
|
212
170
|
```
|
|
213
171
|
|
|
214
172
|
---
|
|
215
173
|
|
|
216
|
-
##
|
|
217
|
-
|
|
218
|
-
The DhanHQ gem provides comprehensive WebSocket integration with three distinct WebSocket types, featuring improved architecture, security, and reliability:
|
|
174
|
+
## WebSockets
|
|
219
175
|
|
|
220
|
-
|
|
176
|
+
Three real-time feeds, all with **auto-reconnect**, **backoff**, **429 cool-off**, and **thread-safe operation**.
|
|
221
177
|
|
|
222
|
-
|
|
223
|
-
- **⚡ Rate Limit Protection** - Built-in protection against 429 errors with proper connection management
|
|
224
|
-
- **🔄 Automatic Reconnection** - Exponential backoff with 60-second cool-off periods
|
|
225
|
-
- **🧵 Thread-Safe Operation** - Safe for Rails applications and multi-threaded environments
|
|
226
|
-
- **📊 Comprehensive Examples** - Ready-to-use examples for all WebSocket types
|
|
227
|
-
- **🛡️ Error Handling** - Robust error handling and connection management
|
|
228
|
-
- **🔍 Dynamic Symbol Resolution** - Easy instrument lookup using `.find()` method
|
|
229
|
-
|
|
230
|
-
### 1. Orders WebSocket - Real-time Order Updates
|
|
231
|
-
|
|
232
|
-
Receive live updates whenever your orders transition between states (placed → traded → cancelled, etc.).
|
|
178
|
+
### Order Updates
|
|
233
179
|
|
|
234
180
|
```ruby
|
|
235
|
-
# Simple connection
|
|
236
181
|
DhanHQ::WS::Orders.connect do |order_update|
|
|
237
|
-
puts "
|
|
238
|
-
puts " Symbol: #{order_update.symbol}"
|
|
239
|
-
puts " Quantity: #{order_update.quantity}"
|
|
240
|
-
puts " Traded Qty: #{order_update.traded_qty}"
|
|
241
|
-
puts " Price: #{order_update.price}"
|
|
242
|
-
puts " Execution: #{order_update.execution_percentage}%"
|
|
182
|
+
puts "#{order_update.order_no} → #{order_update.status} (#{order_update.traded_qty}/#{order_update.quantity})"
|
|
243
183
|
end
|
|
244
|
-
|
|
245
|
-
# Advanced usage with multiple event handlers
|
|
246
|
-
client = DhanHQ::WS::Orders.client
|
|
247
|
-
client.on(:update) { |order| puts "📝 Order updated: #{order.order_no}" }
|
|
248
|
-
client.on(:status_change) { |change| puts "🔄 Status: #{change[:previous_status]} -> #{change[:new_status]}" }
|
|
249
|
-
client.on(:execution) { |exec| puts "✅ Executed: #{exec[:new_traded_qty]} shares" }
|
|
250
|
-
client.on(:order_rejected) { |order| puts "❌ Rejected: #{order.order_no}" }
|
|
251
|
-
client.start
|
|
252
184
|
```
|
|
253
185
|
|
|
254
|
-
###
|
|
255
|
-
|
|
256
|
-
Subscribe to real-time market data for indices and stocks.
|
|
186
|
+
### Market Feed (Ticker / Quote / Full)
|
|
257
187
|
|
|
258
188
|
```ruby
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
timestamp = tick[:ts] ? Time.at(tick[:ts]) : Time.now
|
|
262
|
-
puts "Market Data: #{tick[:segment]}:#{tick[:security_id]} = #{tick[:ltp]} at #{timestamp}"
|
|
263
|
-
end
|
|
264
|
-
|
|
265
|
-
# Subscribe to major Indian indices
|
|
266
|
-
market_client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
|
|
267
|
-
market_client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
|
|
268
|
-
market_client.subscribe_one(segment: "IDX_I", security_id: "29") # NIFTYIT
|
|
269
|
-
market_client.subscribe_one(segment: "IDX_I", security_id: "51") # SENSEX
|
|
270
|
-
|
|
271
|
-
# Quote data (LTP + volume + OHLC)
|
|
272
|
-
DhanHQ::WS.connect(mode: :quote) do |quote|
|
|
273
|
-
puts "#{quote[:symbol]}: LTP=#{quote[:ltp]}, Volume=#{quote[:vol]}"
|
|
189
|
+
client = DhanHQ::WS.connect(mode: :ticker) do |tick|
|
|
190
|
+
puts "#{tick[:security_id]} = ₹#{tick[:ltp]}"
|
|
274
191
|
end
|
|
275
192
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
puts "#{full[:symbol]}: #{full.inspect}"
|
|
279
|
-
end
|
|
193
|
+
client.subscribe_one(segment: "IDX_I", security_id: "13") # NIFTY
|
|
194
|
+
client.subscribe_one(segment: "IDX_I", security_id: "25") # BANKNIFTY
|
|
280
195
|
```
|
|
281
196
|
|
|
282
|
-
###
|
|
283
|
-
|
|
284
|
-
Get real-time market depth data including bid/ask levels and order book information.
|
|
197
|
+
### Market Depth
|
|
285
198
|
|
|
286
199
|
```ruby
|
|
287
|
-
# Real-time market depth for stocks (using dynamic symbol resolution with underlying_symbol)
|
|
288
200
|
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
|
289
|
-
tcs = DhanHQ::Models::Instrument.find("NSE_EQ", "TCS")
|
|
290
|
-
|
|
291
|
-
symbols = []
|
|
292
|
-
if reliance
|
|
293
|
-
symbols << { symbol: "RELIANCE", exchange_segment: reliance.exchange_segment, security_id: reliance.security_id }
|
|
294
|
-
end
|
|
295
|
-
if tcs
|
|
296
|
-
symbols << { symbol: "TCS", exchange_segment: tcs.exchange_segment, security_id: tcs.security_id }
|
|
297
|
-
end
|
|
298
201
|
|
|
299
|
-
DhanHQ::WS::MarketDepth.connect(symbols:
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
puts "
|
|
303
|
-
puts " Spread: #{depth_data[:spread]}"
|
|
304
|
-
puts " Bid Levels: #{depth_data[:bids].size}"
|
|
305
|
-
puts " Ask Levels: #{depth_data[:asks].size}"
|
|
202
|
+
DhanHQ::WS::MarketDepth.connect(symbols: [
|
|
203
|
+
{ symbol: "RELIANCE", exchange_segment: reliance.exchange_segment, security_id: reliance.security_id }
|
|
204
|
+
]) do |depth|
|
|
205
|
+
puts "Best Bid: #{depth[:best_bid]} | Best Ask: #{depth[:best_ask]} | Spread: #{depth[:spread]}"
|
|
306
206
|
end
|
|
307
207
|
```
|
|
308
208
|
|
|
309
|
-
###
|
|
310
|
-
|
|
311
|
-
All WebSocket connections provide:
|
|
312
|
-
- **Automatic reconnection** with exponential backoff
|
|
313
|
-
- **Thread-safe operation** for Rails applications
|
|
314
|
-
- **Consistent event handling** patterns
|
|
315
|
-
- **Built-in error handling** and logging
|
|
316
|
-
- **429 rate limiting** protection with cool-off periods
|
|
317
|
-
- **Secure logging** with automatic credential sanitization
|
|
318
|
-
|
|
319
|
-
### Connection Management
|
|
320
|
-
|
|
321
|
-
```ruby
|
|
322
|
-
# Sequential connections to avoid rate limiting (recommended)
|
|
323
|
-
orders_client = DhanHQ::WS::Orders.connect { |order| puts "Order: #{order.order_no}" }
|
|
324
|
-
orders_client.stop
|
|
325
|
-
sleep(2) # Wait between connections
|
|
326
|
-
|
|
327
|
-
market_client = DhanHQ::WS.connect(mode: :ticker) { |tick| puts "Market: #{tick[:symbol]}" }
|
|
328
|
-
market_client.stop
|
|
329
|
-
sleep(2)
|
|
330
|
-
|
|
331
|
-
depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) { |depth| puts "Depth: #{depth[:symbol]}" }
|
|
332
|
-
depth_client.stop
|
|
333
|
-
|
|
334
|
-
# Check connection status
|
|
335
|
-
puts "Orders connected: #{orders_client.connected?}"
|
|
336
|
-
puts "Market connected: #{market_client.connected?}"
|
|
337
|
-
puts "Depth connected: #{depth_client.connected?}"
|
|
338
|
-
|
|
339
|
-
# Graceful shutdown
|
|
340
|
-
DhanHQ::WS.disconnect_all_local!
|
|
341
|
-
```
|
|
342
|
-
|
|
343
|
-
### Examples
|
|
344
|
-
|
|
345
|
-
The gem includes comprehensive examples in the `examples/` directory:
|
|
346
|
-
|
|
347
|
-
- `market_feed_example.rb` - Market Feed WebSocket with major indices
|
|
348
|
-
- `order_update_example.rb` - Order Update WebSocket with event handling
|
|
349
|
-
- `market_depth_example.rb` - Market Depth WebSocket with RELIANCE and TCS
|
|
350
|
-
- `comprehensive_websocket_examples.rb` - All three WebSocket types
|
|
351
|
-
|
|
352
|
-
Run examples:
|
|
353
|
-
|
|
354
|
-
```bash
|
|
355
|
-
# Individual examples
|
|
356
|
-
bundle exec ruby examples/market_feed_example.rb
|
|
357
|
-
bundle exec ruby examples/order_update_example.rb
|
|
358
|
-
bundle exec ruby examples/market_depth_example.rb
|
|
359
|
-
|
|
360
|
-
# Comprehensive example
|
|
361
|
-
bundle exec ruby examples/comprehensive_websocket_examples.rb
|
|
362
|
-
```
|
|
363
|
-
|
|
364
|
-
### Instrument Model with Trading Fields
|
|
365
|
-
|
|
366
|
-
The Instrument model now includes comprehensive trading fields for order validation, risk management, and compliance:
|
|
209
|
+
### Cleanup
|
|
367
210
|
|
|
368
211
|
```ruby
|
|
369
|
-
#
|
|
370
|
-
reliance = DhanHQ::Models::Instrument.find("NSE_EQ", "RELIANCE")
|
|
371
|
-
|
|
372
|
-
# Trading permissions and restrictions
|
|
373
|
-
puts "Trading Allowed: #{reliance.buy_sell_indicator == 'A'}"
|
|
374
|
-
puts "Bracket Orders: #{reliance.bracket_flag == 'Y' ? 'Supported' : 'Not Supported'}"
|
|
375
|
-
puts "Cover Orders: #{reliance.cover_flag == 'Y' ? 'Supported' : 'Not Supported'}"
|
|
376
|
-
puts "ASM/GSM Status: #{reliance.asm_gsm_flag == 'Y' ? reliance.asm_gsm_category : 'No Restrictions'}"
|
|
377
|
-
|
|
378
|
-
# Margin and leverage information
|
|
379
|
-
puts "ISIN: #{reliance.isin}"
|
|
380
|
-
puts "MTF Leverage: #{reliance.mtf_leverage}x"
|
|
381
|
-
puts "Buy Margin %: #{reliance.buy_co_min_margin_per}%"
|
|
382
|
-
puts "Sell Margin %: #{reliance.sell_co_min_margin_per}%"
|
|
212
|
+
DhanHQ::WS.disconnect_all_local! # kills all local WS connections
|
|
383
213
|
```
|
|
384
214
|
|
|
385
|
-
|
|
386
|
-
- `isin` - International Securities Identification Number
|
|
387
|
-
- `instrument_type` - Classification (ES, INDEX, FUT, OPT)
|
|
388
|
-
- `expiry_flag` - Whether instrument has expiry
|
|
389
|
-
- `bracket_flag` - Bracket order support
|
|
390
|
-
- `cover_flag` - Cover order support
|
|
391
|
-
- `asm_gsm_flag` - Additional Surveillance Measure status
|
|
392
|
-
- `buy_sell_indicator` - Trading permission
|
|
393
|
-
- `buy_co_min_margin_per` - Buy CO minimum margin percentage
|
|
394
|
-
- `sell_co_min_margin_per` - Sell CO minimum margin percentage
|
|
395
|
-
- `mtf_leverage` - Margin Trading Facility leverage
|
|
215
|
+
---
|
|
396
216
|
|
|
397
|
-
|
|
217
|
+
## Super Orders
|
|
398
218
|
|
|
399
|
-
|
|
219
|
+
Entry + target + stop-loss + trailing jump in a single request:
|
|
400
220
|
|
|
401
221
|
```ruby
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
to_date: "2024-01-31",
|
|
414
|
-
expiry_code: 0 # Optional
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
intraday_data = instrument.intraday(
|
|
418
|
-
from_date: "2024-09-11",
|
|
419
|
-
to_date: "2024-09-15",
|
|
420
|
-
interval: "15" # 1, 5, 15, 25, or 60 minutes
|
|
222
|
+
DhanHQ::Models::SuperOrder.create(
|
|
223
|
+
transaction_type: "BUY",
|
|
224
|
+
exchange_segment: "NSE_EQ",
|
|
225
|
+
product_type: "CNC",
|
|
226
|
+
order_type: "LIMIT",
|
|
227
|
+
security_id: "11536",
|
|
228
|
+
quantity: 5,
|
|
229
|
+
price: 1500,
|
|
230
|
+
target_price: 1600,
|
|
231
|
+
stop_loss_price: 1400,
|
|
232
|
+
trailing_jump: 10
|
|
421
233
|
)
|
|
422
|
-
|
|
423
|
-
# Option Chain Methods
|
|
424
|
-
expiries = instrument.expiry_list # Get all available expiries
|
|
425
|
-
|
|
426
|
-
chain = instrument.option_chain(expiry: "2024-02-29") # Get option chain for specific expiry
|
|
427
234
|
```
|
|
428
235
|
|
|
429
|
-
**
|
|
430
|
-
- `instrument.ltp` - Fetches last traded price using `DhanHQ::Models::MarketFeed.ltp`
|
|
431
|
-
- `instrument.ohlc` - Fetches OHLC data using `DhanHQ::Models::MarketFeed.ohlc`
|
|
432
|
-
- `instrument.quote` - Fetches full quote depth using `DhanHQ::Models::MarketFeed.quote`
|
|
433
|
-
- `instrument.daily(from_date:, to_date:, **options)` - Fetches daily historical data using `DhanHQ::Models::HistoricalData.daily`
|
|
434
|
-
- `instrument.intraday(from_date:, to_date:, interval:, **options)` - Fetches intraday historical data using `DhanHQ::Models::HistoricalData.intraday`
|
|
435
|
-
- `instrument.expiry_list` - Fetches expiry list using `DhanHQ::Models::OptionChain.fetch_expiry_list`
|
|
436
|
-
- `instrument.option_chain(expiry:)` - Fetches option chain using `DhanHQ::Models::OptionChain.fetch`
|
|
437
|
-
|
|
438
|
-
All methods automatically use the instrument's `security_id`, `exchange_segment`, and `instrument` attributes, eliminating the need to manually pass these parameters.
|
|
439
|
-
|
|
440
|
-
### Comprehensive Documentation
|
|
441
|
-
|
|
442
|
-
The gem includes detailed documentation for different integration scenarios:
|
|
443
|
-
|
|
444
|
-
- **[Authentication & token handling](docs/AUTHENTICATION.md)** - Dynamic access token, retry-on-401, and auth-related errors
|
|
445
|
-
- **[WebSocket Integration Guide](docs/websocket_integration.md)** - Complete guide covering all WebSocket types and trading fields
|
|
446
|
-
- **[Rails Integration Guide](docs/rails_websocket_integration.md)** - Rails-specific patterns and best practices
|
|
447
|
-
- **[Standalone Ruby Guide](docs/standalone_ruby_websocket_integration.md)** - Scripts, daemons, and CLI tools
|
|
448
|
-
|
|
449
|
-
---
|
|
450
|
-
|
|
451
|
-
## Exchange Segment Enums
|
|
452
|
-
|
|
453
|
-
Use the string enums below in WS `subscribe_*` and REST params:
|
|
454
|
-
|
|
455
|
-
| Enum | Exchange | Segment |
|
|
456
|
-
| -------------- | -------- | ----------------- |
|
|
457
|
-
| `IDX_I` | Index | Index Value |
|
|
458
|
-
| `NSE_EQ` | NSE | Equity Cash |
|
|
459
|
-
| `NSE_FNO` | NSE | Futures & Options |
|
|
460
|
-
| `NSE_CURRENCY` | NSE | Currency |
|
|
461
|
-
| `BSE_EQ` | BSE | Equity Cash |
|
|
462
|
-
| `MCX_COMM` | MCX | Commodity |
|
|
463
|
-
| `BSE_CURRENCY` | BSE | Currency |
|
|
464
|
-
| `BSE_FNO` | BSE | Futures & Options |
|
|
236
|
+
> **Full API reference** (modify, cancel, list, response schemas): [docs/SUPER_ORDERS.md](docs/SUPER_ORDERS.md)
|
|
465
237
|
|
|
466
238
|
---
|
|
467
239
|
|
|
468
|
-
##
|
|
469
|
-
|
|
470
|
-
### Direct handler
|
|
240
|
+
## Real-World Example: NIFTY Trend Monitor
|
|
471
241
|
|
|
472
242
|
```ruby
|
|
473
|
-
|
|
474
|
-
```
|
|
475
|
-
|
|
476
|
-
### Shared TickCache (recommended)
|
|
243
|
+
require 'dhan_hq'
|
|
477
244
|
|
|
478
|
-
|
|
479
|
-
# app/services/live/tick_cache.rb
|
|
480
|
-
class TickCache
|
|
481
|
-
MAP = Concurrent::Map.new
|
|
482
|
-
def self.put(t) = MAP["#{t[:segment]}:#{t[:security_id]}"] = t
|
|
483
|
-
def self.get(seg, sid) = MAP["#{seg}:#{sid}"]
|
|
484
|
-
def self.ltp(seg, sid) = get(seg, sid)&.dig(:ltp)
|
|
485
|
-
end
|
|
245
|
+
DhanHQ.configure_with_env
|
|
486
246
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
247
|
+
# 1. Check the trend using historical 5-min bars
|
|
248
|
+
bars = DhanHQ::Models::HistoricalData.intraday(
|
|
249
|
+
security_id: "13", exchange_segment: "IDX_I",
|
|
250
|
+
instrument: "INDEX", interval: "5",
|
|
251
|
+
from_date: Date.today.to_s, to_date: Date.today.to_s
|
|
252
|
+
)
|
|
490
253
|
|
|
491
|
-
|
|
254
|
+
closes = bars.map { |b| b[:close] }
|
|
255
|
+
sma_20 = closes.last(20).sum / 20.0
|
|
256
|
+
trend = closes.last > sma_20 ? :bullish : :bearish
|
|
257
|
+
puts "NIFTY trend: #{trend} (LTP: #{closes.last}, SMA20: #{sma_20.round(2)})"
|
|
492
258
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
ws.on(:tick){ |t| blk.call(t) if "#{t[:segment]}:#{t[:security_id]}" == key }
|
|
259
|
+
# 2. Stream live ticks for real-time monitoring
|
|
260
|
+
client = DhanHQ::WS.connect(mode: :quote) do |tick|
|
|
261
|
+
puts "NIFTY ₹#{tick[:ltp]} | Vol: #{tick[:vol]} | #{Time.now.strftime('%H:%M:%S')}"
|
|
497
262
|
end
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
---
|
|
501
|
-
|
|
502
|
-
## Rails integration (example)
|
|
503
|
-
|
|
504
|
-
**Goal:** Generate signals from clean **Historical Intraday OHLC** (5-min bars), and use **WebSocket** only for **exits/trailing** on open option legs.
|
|
505
|
-
|
|
506
|
-
1. **Initializer**
|
|
507
|
-
`config/initializers/dhanhq.rb`
|
|
508
|
-
|
|
509
|
-
```ruby
|
|
510
|
-
DhanHQ.configure_with_env
|
|
511
|
-
DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Logger.const_get(level) }
|
|
512
|
-
```
|
|
513
|
-
|
|
514
|
-
2. **Start WS supervisor**
|
|
515
|
-
`config/initializers/stream.rb`
|
|
516
|
-
|
|
517
|
-
```ruby
|
|
518
|
-
INDICES = [
|
|
519
|
-
{ segment: "IDX_I", security_id: "13" }, # NIFTY index value
|
|
520
|
-
{ segment: "IDX_I", security_id: "25" } # BANKNIFTY index value
|
|
521
|
-
]
|
|
522
|
-
|
|
523
|
-
Rails.application.config.to_prepare do
|
|
524
|
-
$WS = DhanHQ::WS::Client.new(mode: :quote).start
|
|
525
|
-
$WS.on(:tick) do |t|
|
|
526
|
-
TickCache.put(t)
|
|
527
|
-
Execution::PositionGuard.instance.on_tick(t) # trailing & fast exits
|
|
528
|
-
end
|
|
529
|
-
INDICES.each { |i| $WS.subscribe_one(segment: i[:segment], security_id: i[:security_id]) }
|
|
530
|
-
end
|
|
531
|
-
```
|
|
532
|
-
|
|
533
|
-
3. **Bar fetch (every 5 min) via Historical API**
|
|
534
|
-
|
|
535
|
-
* Fetch intraday OHLC at 5-minute boundaries.
|
|
536
|
-
* Update your `CandleSeries`; on each closed bar, run strategy to emit signals.
|
|
537
|
-
*(Use your existing `Bars::FetchLoop` + `CandleSeries` code.)*
|
|
538
|
-
|
|
539
|
-
4. **Routing & orders**
|
|
540
|
-
|
|
541
|
-
* On signal: place **Super Order** (SL/TP/TSL) or fallback to Market + local trailing.
|
|
542
|
-
* After a successful place, **register** the leg in `PositionGuard` and **subscribe** its option on WS.
|
|
543
|
-
|
|
544
|
-
5. **Shutdown**
|
|
545
|
-
|
|
546
|
-
```ruby
|
|
547
|
-
at_exit { DhanHQ::WS.disconnect_all_local! }
|
|
548
|
-
```
|
|
549
|
-
|
|
550
|
-
---
|
|
551
|
-
|
|
552
|
-
## Super Orders
|
|
553
|
-
|
|
554
|
-
Super orders are built for smart execution. They club the entry, target, and stop-loss legs (with optional trailing jump) into a single request so you can manage risk immediately after entry.
|
|
555
|
-
|
|
556
|
-
This gem exposes the full REST surface to create, modify, cancel, and list super orders across all supported exchanges and segments.
|
|
557
|
-
|
|
558
|
-
### Endpoints
|
|
559
|
-
|
|
560
|
-
| Method | Path | Description |
|
|
561
|
-
| -------- | -------------------------------------- | ------------------------------------- |
|
|
562
|
-
| `POST` | `/super/orders` | Create a new super order |
|
|
563
|
-
| `PUT` | `/super/orders/{order_id}` | Modify a pending super order |
|
|
564
|
-
| `DELETE` | `/super/orders/{order_id}/{order_leg}` | Cancel a pending super order leg |
|
|
565
|
-
| `GET` | `/super/orders` | Retrieve the list of all super orders |
|
|
263
|
+
client.subscribe_one(segment: "IDX_I", security_id: "13")
|
|
566
264
|
|
|
567
|
-
|
|
265
|
+
# 3. On signal, place a super order with built-in risk management
|
|
266
|
+
# DhanHQ::Models::SuperOrder.create(
|
|
267
|
+
# transaction_type: "BUY", exchange_segment: "NSE_FNO", ...
|
|
268
|
+
# target_price: entry + 50, stop_loss_price: entry - 30, trailing_jump: 5
|
|
269
|
+
# )
|
|
568
270
|
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
```bash
|
|
574
|
-
curl --request POST \
|
|
575
|
-
--url https://api.dhan.co/v2/super/orders \
|
|
576
|
-
--header 'Content-Type: application/json' \
|
|
577
|
-
--header 'access-token: JWT' \
|
|
578
|
-
--data '{Request JSON}'
|
|
579
|
-
```
|
|
580
|
-
|
|
581
|
-
#### Request body
|
|
582
|
-
|
|
583
|
-
```json
|
|
584
|
-
{
|
|
585
|
-
"dhan_client_id": "1000000003",
|
|
586
|
-
"correlation_id": "123abc678",
|
|
587
|
-
"transaction_type": "BUY",
|
|
588
|
-
"exchange_segment": "NSE_EQ",
|
|
589
|
-
"product_type": "CNC",
|
|
590
|
-
"order_type": "LIMIT",
|
|
591
|
-
"security_id": "11536",
|
|
592
|
-
"quantity": 5,
|
|
593
|
-
"price": 1500,
|
|
594
|
-
"target_price": 1600,
|
|
595
|
-
"stop_loss_price": 1400,
|
|
596
|
-
"trailing_jump": 10
|
|
597
|
-
}
|
|
598
|
-
```
|
|
599
|
-
|
|
600
|
-
#### Parameters
|
|
601
|
-
|
|
602
|
-
| Field | Type | Description |
|
|
603
|
-
| ------------------ | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
604
|
-
| `dhan_client_id` | string *(required)* | User specific identification generated by Dhan. When you call through `DhanHQ::Models::SuperOrder`, the gem injects your configured client id so you can omit this key locally. |
|
|
605
|
-
| `correlation_id` | string | Caller generated correlation identifier |
|
|
606
|
-
| `transaction_type` | enum string *(required)* | Trading side. `BUY` or `SELL`. |
|
|
607
|
-
| `exchange_segment` | enum string *(required)* | Exchange segment (see appendix). |
|
|
608
|
-
| `product_type` | enum string *(required)* | Product type. `CNC`, `INTRADAY`, `MARGIN`, or `MTF`. |
|
|
609
|
-
| `order_type` | enum string *(required)* | Order type. `LIMIT` or `MARKET`. |
|
|
610
|
-
| `security_id` | string *(required)* | Exchange standard security identifier. |
|
|
611
|
-
| `quantity` | integer *(required)* | Number of shares for the order. |
|
|
612
|
-
| `price` | float *(required)* | Price at which the entry leg is placed. |
|
|
613
|
-
| `target_price` | float *(required)* | Target price for the super order. |
|
|
614
|
-
| `stop_loss_price` | float *(required)* | Stop-loss price for the super order. |
|
|
615
|
-
| `trailing_jump` | float *(required)* | Price jump size used to trail the stop-loss. |
|
|
616
|
-
|
|
617
|
-
> 🐍 When you call `DhanHQ::Models::SuperOrder.create`, pass snake_case keys as shown above. The client automatically camelizes
|
|
618
|
-
> them before posting to Dhan's REST API and injects your configured `dhan_client_id`, so you can omit that key in Ruby code.
|
|
619
|
-
|
|
620
|
-
#### Response
|
|
621
|
-
|
|
622
|
-
```json
|
|
623
|
-
{
|
|
624
|
-
"order_id": "112111182198",
|
|
625
|
-
"order_status": "PENDING"
|
|
626
|
-
}
|
|
627
|
-
```
|
|
628
|
-
|
|
629
|
-
| Field | Type | Description |
|
|
630
|
-
| -------------- | ----------- | --------------------------------------------------- |
|
|
631
|
-
| `order_id` | string | Order identifier generated by Dhan |
|
|
632
|
-
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, or `REJECTED`. |
|
|
633
|
-
|
|
634
|
-
### Modify Super Order
|
|
635
|
-
|
|
636
|
-
Use the modify endpoint to update any leg while the super order remains in `PENDING` or `PART_TRADED` status.
|
|
637
|
-
|
|
638
|
-
> ℹ️ Static IP whitelisting with Dhan support is required before invoking this API.
|
|
639
|
-
|
|
640
|
-
```bash
|
|
641
|
-
curl --request PUT \
|
|
642
|
-
--url https://api.dhan.co/v2/super/orders/{order_id} \
|
|
643
|
-
--header 'Content-Type: application/json' \
|
|
644
|
-
--header 'access-token: JWT' \
|
|
645
|
-
--data '{Request JSON}'
|
|
646
|
-
```
|
|
647
|
-
|
|
648
|
-
#### Request body
|
|
649
|
-
|
|
650
|
-
```json
|
|
651
|
-
{
|
|
652
|
-
"dhan_client_id": "1000000009",
|
|
653
|
-
"order_id": "112111182045",
|
|
654
|
-
"order_type": "LIMIT",
|
|
655
|
-
"leg_name": "ENTRY_LEG",
|
|
656
|
-
"quantity": 40,
|
|
657
|
-
"price": 1300,
|
|
658
|
-
"target_price": 1450,
|
|
659
|
-
"stop_loss_price": 1350,
|
|
660
|
-
"trailing_jump": 20
|
|
661
|
-
}
|
|
662
|
-
```
|
|
663
|
-
|
|
664
|
-
#### Parameters
|
|
665
|
-
|
|
666
|
-
| Field | Type | Description |
|
|
667
|
-
| ----------------- | -------------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
|
|
668
|
-
| `dhan_client_id` | string *(required)* | User specific identification generated by Dhan. Automatically added when you call through the Ruby models. |
|
|
669
|
-
| `order_id` | string *(required)* | Super order identifier generated by Dhan. |
|
|
670
|
-
| `order_type` | enum string *(conditionally required)* | `LIMIT` or `MARKET`. Required when modifying `ENTRY_LEG`. |
|
|
671
|
-
| `leg_name` | enum string *(required)* | `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. Entry leg updates entire order while status is `PENDING` or `PART_TRADED`. |
|
|
672
|
-
| `quantity` | integer *(conditionally required)* | Quantity update for `ENTRY_LEG`. |
|
|
673
|
-
| `price` | float *(conditionally required)* | Entry price update for `ENTRY_LEG`. |
|
|
674
|
-
| `target_price` | float *(conditionally required)* | Target price update for `ENTRY_LEG` or `TARGET_LEG`. |
|
|
675
|
-
| `stop_loss_price` | float *(conditionally required)* | Stop-loss price update for `ENTRY_LEG` or `STOP_LOSS_LEG`. |
|
|
676
|
-
| `trailing_jump` | float *(conditionally required)* | Trailing jump update for `ENTRY_LEG` or `STOP_LOSS_LEG`. Omit or set to `0` to cancel trailing. |
|
|
677
|
-
|
|
678
|
-
> ℹ️ Once the entry leg status becomes `TRADED`, only the `TARGET_LEG` and `STOP_LOSS_LEG` can be modified (price and trailing jump).
|
|
679
|
-
|
|
680
|
-
#### Response
|
|
681
|
-
|
|
682
|
-
```json
|
|
683
|
-
{
|
|
684
|
-
"order_id": "112111182045",
|
|
685
|
-
"order_status": "TRANSIT"
|
|
686
|
-
}
|
|
271
|
+
# 4. Clean shutdown
|
|
272
|
+
at_exit { DhanHQ::WS.disconnect_all_local! }
|
|
273
|
+
sleep # keep the script alive
|
|
687
274
|
```
|
|
688
275
|
|
|
689
|
-
| Field | Type | Description |
|
|
690
|
-
| -------------- | ----------- | ------------------------------------------------------------- |
|
|
691
|
-
| `order_id` | string | Order identifier generated by Dhan |
|
|
692
|
-
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, `REJECTED`, or `TRADED`. |
|
|
693
|
-
|
|
694
|
-
### Cancel Super Order
|
|
695
|
-
|
|
696
|
-
Cancel a pending or active super order leg using the order ID. Cancelling the entry leg removes every leg. Cancelling a specific target or stop-loss leg removes only that leg and it cannot be re-added.
|
|
697
|
-
|
|
698
|
-
> ℹ️ Static IP whitelisting with Dhan support is required before invoking this API.
|
|
699
|
-
|
|
700
|
-
```bash
|
|
701
|
-
curl --request DELETE \
|
|
702
|
-
--url https://api.dhan.co/v2/super/orders/{order_id}/{order_leg} \
|
|
703
|
-
--header 'Content-Type: application/json' \
|
|
704
|
-
--header 'access-token: JWT'
|
|
705
|
-
```
|
|
706
|
-
|
|
707
|
-
#### Path parameters
|
|
708
|
-
|
|
709
|
-
| Field | Description | Example |
|
|
710
|
-
| ----------- | ------------------------------------------------------------- | ------------- |
|
|
711
|
-
| `order_id` | Super order identifier. | `11211182198` |
|
|
712
|
-
| `order_leg` | Leg to cancel. `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. | `ENTRY_LEG` |
|
|
713
|
-
|
|
714
|
-
#### Response
|
|
715
|
-
|
|
716
|
-
```json
|
|
717
|
-
{
|
|
718
|
-
"order_id": "112111182045",
|
|
719
|
-
"order_status": "CANCELLED"
|
|
720
|
-
}
|
|
721
|
-
```
|
|
722
|
-
|
|
723
|
-
| Field | Type | Description |
|
|
724
|
-
| -------------- | ----------- | ---------------------------------------------------------------- |
|
|
725
|
-
| `order_id` | string | Order identifier generated by Dhan |
|
|
726
|
-
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, `REJECTED`, or `CANCELLED`. |
|
|
727
|
-
|
|
728
|
-
### Super Order List
|
|
729
|
-
|
|
730
|
-
List every super order placed during the trading day. The API nests leg details under the entry leg, and individual legs also appear in the main order book.
|
|
731
|
-
|
|
732
|
-
```bash
|
|
733
|
-
curl --request GET \
|
|
734
|
-
--url https://api.dhan.co/v2/super/orders \
|
|
735
|
-
--header 'Content-Type: application/json' \
|
|
736
|
-
--header 'access-token: JWT'
|
|
737
|
-
```
|
|
738
|
-
|
|
739
|
-
#### Response
|
|
740
|
-
|
|
741
|
-
```json
|
|
742
|
-
[
|
|
743
|
-
{
|
|
744
|
-
"dhan_client_id": "1100003626",
|
|
745
|
-
"order_id": "5925022734212",
|
|
746
|
-
"correlation_id": "string",
|
|
747
|
-
"order_status": "PENDING",
|
|
748
|
-
"transaction_type": "BUY",
|
|
749
|
-
"exchange_segment": "NSE_EQ",
|
|
750
|
-
"product_type": "CNC",
|
|
751
|
-
"order_type": "LIMIT",
|
|
752
|
-
"validity": "DAY",
|
|
753
|
-
"trading_symbol": "HDFCBANK",
|
|
754
|
-
"security_id": "1333",
|
|
755
|
-
"quantity": 10,
|
|
756
|
-
"remaining_quantity": 10,
|
|
757
|
-
"ltp": 1660.95,
|
|
758
|
-
"price": 1500,
|
|
759
|
-
"after_market_order": false,
|
|
760
|
-
"leg_name": "ENTRY_LEG",
|
|
761
|
-
"exchange_order_id": "11925022734212",
|
|
762
|
-
"create_time": "2025-02-27 19:09:42",
|
|
763
|
-
"update_time": "2025-02-27 19:09:42",
|
|
764
|
-
"exchange_time": "2025-02-27 19:09:42",
|
|
765
|
-
"oms_error_description": "",
|
|
766
|
-
"average_traded_price": 0,
|
|
767
|
-
"filled_qty": 0,
|
|
768
|
-
"leg_details": [
|
|
769
|
-
{
|
|
770
|
-
"order_id": "5925022734212",
|
|
771
|
-
"leg_name": "STOP_LOSS_LEG",
|
|
772
|
-
"transaction_type": "SELL",
|
|
773
|
-
"total_quantity": 0,
|
|
774
|
-
"remaining_quantity": 0,
|
|
775
|
-
"triggered_quantity": 0,
|
|
776
|
-
"price": 1400,
|
|
777
|
-
"order_status": "PENDING",
|
|
778
|
-
"trailing_jump": 10
|
|
779
|
-
},
|
|
780
|
-
{
|
|
781
|
-
"order_id": "5925022734212",
|
|
782
|
-
"leg_name": "TARGET_LEG",
|
|
783
|
-
"transaction_type": "SELL",
|
|
784
|
-
"remaining_quantity": 0,
|
|
785
|
-
"triggered_quantity": 0,
|
|
786
|
-
"price": 1550,
|
|
787
|
-
"order_status": "PENDING",
|
|
788
|
-
"trailing_jump": 0
|
|
789
|
-
}
|
|
790
|
-
]
|
|
791
|
-
}
|
|
792
|
-
]
|
|
793
|
-
```
|
|
794
|
-
|
|
795
|
-
#### Parameters
|
|
796
|
-
|
|
797
|
-
| Field | Type | Description |
|
|
798
|
-
| ----------------------- | ----------- | --------------------------------------------------------------------------------------------------- |
|
|
799
|
-
| `dhan_client_id` | string | User specific identification generated by Dhan. |
|
|
800
|
-
| `order_id` | string | Order identifier generated by Dhan. |
|
|
801
|
-
| `correlation_id` | string | Correlation identifier supplied by the caller. |
|
|
802
|
-
| `order_status` | enum string | Latest status. `TRANSIT`, `PENDING`, `CLOSED`, `REJECTED`, `CANCELLED`, `PART_TRADED`, or `TRADED`. |
|
|
803
|
-
| `transaction_type` | enum string | Trading side. `BUY` or `SELL`. |
|
|
804
|
-
| `exchange_segment` | enum string | Exchange segment. |
|
|
805
|
-
| `product_type` | enum string | Product type. `CNC`, `INTRADAY`, `MARGIN`, or `MTF`. |
|
|
806
|
-
| `order_type` | enum string | Order type. `LIMIT` or `MARKET`. |
|
|
807
|
-
| `validity` | enum string | Order validity. `DAY`. |
|
|
808
|
-
| `trading_symbol` | string | Trading symbol reference. |
|
|
809
|
-
| `security_id` | string | Exchange security identifier. |
|
|
810
|
-
| `quantity` | integer | Ordered quantity. |
|
|
811
|
-
| `remaining_quantity` | integer | Quantity pending execution. |
|
|
812
|
-
| `ltp` | float | Last traded price. |
|
|
813
|
-
| `price` | float | Order price. |
|
|
814
|
-
| `after_market_order` | boolean | Indicates if the order was placed after market hours. |
|
|
815
|
-
| `leg_name` | enum string | Leg identifier: `ENTRY_LEG`, `TARGET_LEG`, or `STOP_LOSS_LEG`. |
|
|
816
|
-
| `trailing_jump` | float | Trailing jump for stop-loss. |
|
|
817
|
-
| `exchange_order_id` | string | Exchange-generated order identifier. |
|
|
818
|
-
| `create_time` | string | Order creation timestamp. |
|
|
819
|
-
| `update_time` | string | Latest update timestamp. |
|
|
820
|
-
| `exchange_time` | string | Exchange timestamp. |
|
|
821
|
-
| `oms_error_description` | string | OMS error description when applicable. |
|
|
822
|
-
| `average_traded_price` | float | Average traded price. |
|
|
823
|
-
| `filled_qty` | integer | Quantity traded on the exchange. |
|
|
824
|
-
| `triggered_quantity` | integer | Quantity triggered for stop-loss or target legs. |
|
|
825
|
-
| `leg_details` | array | Nested leg details for the super order. |
|
|
826
|
-
|
|
827
|
-
> ✅ `CLOSED` indicates the entry leg plus either target or stop-loss leg completed for the entire quantity. `TRIGGERED` appears on target and stop-loss legs to show which leg fired; inspect `triggered_quantity` for the executed quantity.
|
|
828
|
-
|
|
829
276
|
---
|
|
830
277
|
|
|
831
|
-
##
|
|
278
|
+
## Rails Integration
|
|
832
279
|
|
|
833
|
-
|
|
834
|
-
`feed_response_code (u8, BE)`, `message_length (u16, BE)`, `exchange_segment (u8, BE)`, `security_id (i32, LE)`
|
|
835
|
-
* **Packets supported**:
|
|
836
|
-
|
|
837
|
-
* **1** Index (surface as raw/misc unless documented)
|
|
838
|
-
* **2** Ticker: `ltp`, `ltt`
|
|
839
|
-
* **4** Quote: `ltp`, `ltt`, `atp`, `volume`, totals, `day_*`
|
|
840
|
-
* **5** OI: `open_interest`
|
|
841
|
-
* **6** Prev Close: `prev_close`, `oi_prev`
|
|
842
|
-
* **7** Market Status (raw/misc unless documented)
|
|
843
|
-
* **8** Full: quote + `open_interest` + 5× depth (bid/ask)
|
|
844
|
-
* **50** Disconnect: reason code
|
|
280
|
+
Need initializers, service objects, ActionCable wiring, and background workers? See the [Rails Integration Guide](docs/rails_integration.md).
|
|
845
281
|
|
|
846
282
|
---
|
|
847
283
|
|
|
848
|
-
##
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
284
|
+
## 📚 Documentation
|
|
285
|
+
|
|
286
|
+
| Guide | What it covers |
|
|
287
|
+
| ----- | -------------- |
|
|
288
|
+
| [Authentication](docs/AUTHENTICATION.md) | Token flows, TOTP, OAuth, auto-management |
|
|
289
|
+
| [Configuration Reference](docs/CONFIGURATION.md) | Full ENV matrix, logging, timeouts, available resources |
|
|
290
|
+
| [WebSocket Integration](docs/websocket_integration.md) | All WS types, architecture, best practices |
|
|
291
|
+
| [WebSocket Protocol](docs/WEBSOCKET_PROTOCOL.md) | Packet parsing, request codes, tick schema, exchange enums |
|
|
292
|
+
| [Rails WebSocket Guide](docs/rails_websocket_integration.md) | Rails-specific patterns, ActionCable |
|
|
293
|
+
| [Rails Integration](docs/rails_integration.md) | Initializers, service objects, workers |
|
|
294
|
+
| [Standalone Ruby Guide](docs/standalone_ruby_websocket_integration.md) | Scripts, daemons, CLI tools |
|
|
295
|
+
| [Super Orders API](docs/SUPER_ORDERS.md) | Full REST reference for super orders |
|
|
296
|
+
| [Data API Parameters](docs/DATA_API_PARAMETERS.md) | Historical data, option chain parameters |
|
|
297
|
+
| [Testing Guide](docs/TESTING_GUIDE.md) | WebSocket testing, model testing, console helpers |
|
|
298
|
+
| [Technical Analysis](docs/technical_analysis.md) | Indicators, multi-timeframe aggregation |
|
|
299
|
+
| [Troubleshooting](docs/TROUBLESHOOTING.md) | 429 errors, reconnect, auth issues, debug logging |
|
|
300
|
+
| [Release Guide](docs/RELEASE_GUIDE.md) | Versioning, publishing, changelog |
|
|
856
301
|
|
|
857
302
|
---
|
|
858
303
|
|
|
859
|
-
##
|
|
304
|
+
## Best Practices
|
|
860
305
|
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
Run with `DHAN_LOG_LEVEL=DEBUG` to inspect; we safely drop malformed frames and keep the loop alive.
|
|
306
|
+
- Keep `on(:tick)` handlers **non-blocking** — push heavy work to a queue/thread
|
|
307
|
+
- Use `mode: :quote` for most strategies; `:full` only if you need depth/OI
|
|
308
|
+
- Don't exceed **100 instruments per subscribe frame** (auto-chunked by the client)
|
|
309
|
+
- Call `DhanHQ::WS.disconnect_all_local!` on shutdown
|
|
310
|
+
- Avoid rapid connect/disconnect loops — the client already backs off on 429
|
|
867
311
|
|
|
868
312
|
---
|
|
869
313
|
|
|
870
314
|
## Contributing
|
|
871
315
|
|
|
872
|
-
PRs welcome! Please include tests for new
|
|
316
|
+
PRs welcome! Please include tests for new features. See [CHANGELOG.md](CHANGELOG.md) for recent changes.
|
|
873
317
|
|
|
874
|
-
|
|
318
|
+
```bash
|
|
319
|
+
bundle exec rake # run tests
|
|
320
|
+
bundle exec rubocop # lint
|
|
321
|
+
bin/console # interactive console
|
|
322
|
+
```
|
|
875
323
|
|
|
876
324
|
## License
|
|
877
325
|
|
|
878
|
-
MIT.
|
|
879
|
-
|
|
880
|
-
## Technical Analysis (Indicators + Multi-Timeframe)
|
|
881
|
-
|
|
882
|
-
See the guide for computing indicators and aggregating cross-timeframe bias:
|
|
883
|
-
|
|
884
|
-
- docs/technical_analysis.md
|
|
326
|
+
[MIT](LICENSE.txt)
|