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.
data/README.md CHANGED
@@ -1,884 +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 (static token)
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
- | `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
- `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`.
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
- For detailed behavior, timelines, and error types, see **[docs/AUTHENTICATION.md](docs/AUTHENTICATION.md)**.
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
- ## Quick Start (REST)
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: "MARGIN",
146
- order_type: "LIMIT",
147
- validity: "DAY",
148
- security_id: "43492",
149
- quantity: 50,
150
- 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
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
- # Positions / Holdings
159
- positions = DhanHQ::Models::Position.all
160
- holdings = DhanHQ::Models::Holding.all
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
- # Historical Data (Intraday)
149
+ ### Historical Data
150
+
151
+ ```ruby
163
152
  bars = DhanHQ::Models::HistoricalData.intraday(
164
- security_id: "13", # NIFTY index value
153
+ security_id: "13",
165
154
  exchange_segment: "IDX_I",
166
- instrument: "INDEX",
167
- interval: "5", # minutes
168
- from_date: "2025-08-14",
169
- to_date: "2025-08-18"
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
- ### Rails integration
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
- # Start console
200
- bin/console
201
-
202
- # Load test helpers
203
- load 'bin/test_helpers.rb'
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
- ## WebSocket Integration (Orders, Market Feed, Market Depth)
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
- ### Key Features
176
+ Three real-time feeds, all with **auto-reconnect**, **backoff**, **429 cool-off**, and **thread-safe operation**.
221
177
 
222
- - **🔒 Secure Logging** - Sensitive information (access tokens) are automatically sanitized from logs
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 "Order Update: #{order_update.order_no} - #{order_update.status}"
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
- ### 2. Market Feed WebSocket - Live Market Data
255
-
256
- Subscribe to real-time market data for indices and stocks.
186
+ ### Market Feed (Ticker / Quote / Full)
257
187
 
258
188
  ```ruby
259
- # Ticker data (LTP updates) - Recommended for most use cases
260
- market_client = DhanHQ::WS.connect(mode: :ticker) do |tick|
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
- # Full market data
277
- DhanHQ::WS.connect(mode: :full) do |full|
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
- ### 3. Market Depth WebSocket - Real-time Market Depth
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: symbols) do |depth_data|
300
- puts "Market Depth: #{depth_data[:symbol]}"
301
- puts " Best Bid: #{depth_data[:best_bid]}"
302
- puts " Best Ask: #{depth_data[:best_ask]}"
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
- ### Unified WebSocket Architecture
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
- # Find instrument with trading fields
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
- **Available Trading Fields:**
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
- ### Instrument Convenience Methods
217
+ ## Super Orders
398
218
 
399
- 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:
400
220
 
401
221
  ```ruby
402
- # Find an instrument
403
- instrument = DhanHQ::Models::Instrument.find("IDX_I", "NIFTY")
404
-
405
- # Market Feed Methods - automatically uses instrument's attributes
406
- ltp_data = instrument.ltp # Last traded price
407
- ohlc_data = instrument.ohlc # OHLC data
408
- quote_data = instrument.quote # Full quote depth
409
-
410
- # Historical Data Methods
411
- daily_data = instrument.daily(
412
- from_date: "2024-01-01",
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
- **Available Instance Methods:**
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
- ## Accessing ticks elsewhere in your app
469
-
470
- ### Direct handler
240
+ ## Real-World Example: NIFTY Trend Monitor
471
241
 
472
242
  ```ruby
473
- ws.on(:tick) { |t| do_something_fast(t) } # avoid heavy work here
474
- ```
475
-
476
- ### Shared TickCache (recommended)
243
+ require 'dhan_hq'
477
244
 
478
- ```ruby
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
- ws.on(:tick) { |t| TickCache.put(t) }
488
- ltp = TickCache.ltp("NSE_FNO", "12345")
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
- ### 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)})"
492
258
 
493
- ```ruby
494
- def on_tick_for(ws, segment:, security_id:, &blk)
495
- key = "#{segment}:#{security_id}"
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
- ### 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
+ # )
568
270
 
569
- 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.
570
-
571
- > ℹ️ Static IP whitelisting with Dhan support is required before invoking this API.
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
- ## Packet parsing (for reference)
278
+ ## Rails Integration
832
279
 
833
- * **Response Header (8 bytes)**:
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
- ## Best practices
849
-
850
- * Keep the `on(:tick)` handler **non-blocking**; push work to a queue/thread.
851
- * Use `mode: :quote` for most strategies; switch to `:full` only if you need depth/OI in real-time.
852
- * Call **`ws.disconnect!`** (or `ws.stop`) when leaving IRB / tests.
853
- Use **`DhanHQ::WS.disconnect_all_local!`** to be extra safe.
854
- * Don’t exceed **100 instruments per SUB frame** (the client auto-chunks).
855
- * 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 |
856
301
 
857
302
  ---
858
303
 
859
- ## Troubleshooting
304
+ ## Best Practices
860
305
 
861
- * **429: Unexpected response code**
862
- 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.
863
- * **No ticks after reconnect**
864
- Ensure you re-subscribed after a clean start (the client resends the snapshot automatically on reconnect).
865
- * **Binary parse errors**
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 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.
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)