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