DhanHQ 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +17 -0
- data/CODE_REVIEW_ISSUES.md +2 -2
- data/GUIDE.md +2 -2
- data/README.md +15 -4
- data/README1.md +4 -4
- data/REVIEW_SUMMARY.md +2 -2
- data/docs/AUTHENTICATION.md +54 -2
- data/docs/TESTING_GUIDE.md +8 -8
- data/docs/live_order_updates.md +2 -2
- data/docs/rails_integration.md +7 -7
- data/docs/standalone_ruby_websocket_integration.md +24 -24
- data/docs/technical_analysis.md +1 -1
- data/docs/websocket_integration.md +4 -4
- data/examples/comprehensive_websocket_examples.rb +2 -2
- data/examples/instrument_finder_test.rb +2 -2
- data/examples/market_depth_example.rb +2 -2
- data/examples/market_feed_example.rb +2 -2
- data/examples/order_update_example.rb +2 -2
- data/examples/trading_fields_example.rb +2 -2
- data/lib/DhanHQ/auth/token_generator.rb +33 -0
- data/lib/DhanHQ/auth/token_manager.rb +88 -0
- data/lib/DhanHQ/auth/token_renewal.rb +25 -0
- data/lib/DhanHQ/auth.rb +91 -31
- data/lib/DhanHQ/client.rb +42 -2
- data/lib/DhanHQ/configuration.rb +2 -2
- data/lib/DhanHQ/contracts/order_contract.rb +0 -23
- data/lib/DhanHQ/contracts/trade_by_order_id_contract.rb +12 -0
- data/lib/DhanHQ/contracts/trade_contract.rb +0 -65
- data/lib/DhanHQ/contracts/trade_history_contract.rb +52 -0
- data/lib/DhanHQ/core/auth_api.rb +21 -0
- data/lib/DhanHQ/helpers/request_helper.rb +1 -1
- data/lib/DhanHQ/models/token_response.rb +88 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/dhan_hq.rb +31 -81
- metadata +37 -3
- data/lib/DhanHQ/config.rb +0 -33
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b583e87aa598f3891e937f44859f9b451fc8f6bcec7a304cc0c33790fe9117ea
|
|
4
|
+
data.tar.gz: 1a090fa608fba141da311ba8aaf21d36e750f78e90497f728c5f96d580044c7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0966f2a2aa331b675ecc1675510f6ad3dca6b7a987c8dde15dde2efe506ac659157b6e0979d0c23f480d2835fa603bfd37530f33275d422226abb2419e46bfd0'
|
|
7
|
+
data.tar.gz: 74ea10d0744fb01324afd4a1768fa6969b07f4d1fcb9da94d7a6caf01fd01c2863d6a3746d288628d10e497bd55c68687e0b54e237bd91736ad4bb8c1decc9f8
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
5
|
+
## [2.4.0] - 2026-02-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Support for Individual TOTP-based access token generation
|
|
9
|
+
- `DhanHQ::Auth.generate_access_token`
|
|
10
|
+
- `DhanHQ::Auth.generate_totp`
|
|
11
|
+
|
|
12
|
+
### Updated
|
|
13
|
+
- RenewToken now uses POST (aligned with Dhan API behavior)
|
|
14
|
+
- Improved error handling for authentication APIs
|
|
15
|
+
|
|
16
|
+
### Notes
|
|
17
|
+
- TOTP tokens can now be programmatically regenerated without browser login
|
|
18
|
+
- RenewToken still applies only to web-generated tokens
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
5
22
|
## [2.3.0] - 2026-02-04
|
|
6
23
|
|
|
7
24
|
### Added
|
data/CODE_REVIEW_ISSUES.md
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
**Location**: `lib/DhanHQ/client.rb:40`
|
|
13
13
|
**Issue**: The client initializes configuration conditionally, which can lead to runtime errors:
|
|
14
14
|
```ruby
|
|
15
|
-
DhanHQ.configure_with_env if ENV.fetch("
|
|
15
|
+
DhanHQ.configure_with_env if ENV.fetch("DHAN_CLIENT_ID", nil)
|
|
16
16
|
```
|
|
17
|
-
**Problem**: If `
|
|
17
|
+
**Problem**: If `DHAN_CLIENT_ID` exists but `DHAN_ACCESS_TOKEN` doesn't, configuration will be partially initialized, leading to authentication failures later.
|
|
18
18
|
**Recommendation**: Validate both required credentials before proceeding or fail fast with a clear error message.
|
|
19
19
|
|
|
20
20
|
### 2. **Race Condition in Rate Limiter**
|
data/GUIDE.md
CHANGED
|
@@ -44,8 +44,8 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
44
44
|
|
|
45
45
|
| Variable | Description |
|
|
46
46
|
| -------------- | ---------------------------------------------------- |
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
47
|
+
| `DHAN_CLIENT_ID` | Your Dhan trading client id. |
|
|
48
|
+
| `DHAN_ACCESS_TOKEN` | REST/WebSocket access token generated via Dhan APIs. |
|
|
49
49
|
|
|
50
50
|
Provide them via `.env`, Rails credentials, or your secret manager of choice
|
|
51
51
|
before the initializer runs.
|
data/README.md
CHANGED
|
@@ -53,7 +53,7 @@ gem install DhanHQ
|
|
|
53
53
|
|
|
54
54
|
## Configuration
|
|
55
55
|
|
|
56
|
-
### From ENV / .env
|
|
56
|
+
### From ENV / .env (static token)
|
|
57
57
|
|
|
58
58
|
```ruby
|
|
59
59
|
require 'dhan_hq'
|
|
@@ -66,8 +66,8 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
66
66
|
|
|
67
67
|
| Variable | Purpose |
|
|
68
68
|
| -------------- | ------------------------------------------------- |
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
69
|
+
| `DHAN_CLIENT_ID` | Trading account client id issued by Dhan. |
|
|
70
|
+
| `DHAN_ACCESS_TOKEN` | API access token generated from the Dhan console. |
|
|
71
71
|
|
|
72
72
|
`configure_with_env` raises if either value is missing. Load them via `dotenv`,
|
|
73
73
|
Rails credentials, or any other mechanism that populates `ENV` before
|
|
@@ -114,7 +114,18 @@ end
|
|
|
114
114
|
|
|
115
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
116
|
|
|
117
|
-
|
|
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.
|
|
127
|
+
|
|
128
|
+
For detailed behavior, timelines, and error types, see **[docs/AUTHENTICATION.md](docs/AUTHENTICATION.md)**.
|
|
118
129
|
|
|
119
130
|
### Logging
|
|
120
131
|
|
data/README1.md
CHANGED
|
@@ -45,8 +45,8 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
45
45
|
|
|
46
46
|
**Minimum environment variables**
|
|
47
47
|
|
|
48
|
-
* `
|
|
49
|
-
* `
|
|
48
|
+
* `DHAN_CLIENT_ID` – trading account client id issued by Dhan.
|
|
49
|
+
* `DHAN_ACCESS_TOKEN` – API access token generated from the Dhan console.
|
|
50
50
|
|
|
51
51
|
If either key is missing `configure_with_env` raises an error. Ensure your
|
|
52
52
|
application loads them into `ENV` before requiring the gem.
|
|
@@ -64,8 +64,8 @@ Create a `.env` file in your project root to supply the minimum values (and any
|
|
|
64
64
|
optional overrides you need):
|
|
65
65
|
|
|
66
66
|
```dotenv
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
DHAN_CLIENT_ID=your_client_id
|
|
68
|
+
DHAN_ACCESS_TOKEN=your_access_token
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The gem requires `dotenv/load`, so these variables are loaded automatically when you require `DhanHQ`.
|
data/REVIEW_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DhanHQ Client Gem - Review Summary
|
|
2
2
|
|
|
3
|
-
**API Documentation**: https://api.dhan.co/v2/#/
|
|
3
|
+
**API Documentation**: https://api.dhan.co/v2/#/
|
|
4
4
|
**Review Date**: 2025-01-27
|
|
5
5
|
|
|
6
6
|
## 🚨 Top 10 Critical Issues
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- **File**: `lib/DhanHQ/client.rb:40`
|
|
16
16
|
- **Issue**: Partial configuration can lead to runtime authentication failures
|
|
17
17
|
- **Impact**: Silent failures, difficult debugging
|
|
18
|
-
- **Fix**: Validate both `
|
|
18
|
+
- **Fix**: Validate both `DHAN_CLIENT_ID` and `DHAN_ACCESS_TOKEN` before proceeding
|
|
19
19
|
|
|
20
20
|
### 3. **Memory Leak in Order Tracker** (HIGH)
|
|
21
21
|
- **File**: `lib/DhanHQ/ws/orders/client.rb:18`
|
data/docs/AUTHENTICATION.md
CHANGED
|
@@ -23,7 +23,7 @@ Set `access_token` once; it is sent on every request:
|
|
|
23
23
|
```ruby
|
|
24
24
|
DhanHQ.configure do |config|
|
|
25
25
|
config.client_id = ENV["DHAN_CLIENT_ID"]
|
|
26
|
-
config.access_token = ENV["
|
|
26
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"]
|
|
27
27
|
end
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -68,7 +68,7 @@ Example: refresh in provider and cache the result until near expiry:
|
|
|
68
68
|
config.access_token_provider = lambda do
|
|
69
69
|
stored = YourTokenStore.current
|
|
70
70
|
if stored.nil? || stored.expired_soon?
|
|
71
|
-
response = DhanHQ::Auth.renew_token(stored&.access_token || ENV["
|
|
71
|
+
response = DhanHQ::Auth.renew_token(stored&.access_token || ENV["DHAN_ACCESS_TOKEN"], config.client_id)
|
|
72
72
|
YourTokenStore.update!(response["accessToken"], response["expiryTime"])
|
|
73
73
|
stored = YourTokenStore.current
|
|
74
74
|
end
|
|
@@ -108,3 +108,55 @@ Rescue `AuthenticationError` for local config/token resolution failures; rescue
|
|
|
108
108
|
- [rails_integration.md](rails_integration.md) — Rails initializer with optional `access_token_provider` and RenewToken
|
|
109
109
|
- [TESTING_GUIDE.md](TESTING_GUIDE.md) — Config examples and RenewToken
|
|
110
110
|
- [CHANGELOG.md](../CHANGELOG.md) — 2.2.0 and 2.2.1 auth and token changes
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# SUPPORTED AUTHENTICATION AND TOKEN GENERATION APPROACHES:
|
|
114
|
+
|
|
115
|
+
We now support five distinct authentication approaches in this gem.
|
|
116
|
+
1️⃣ Static token (manual, simplest)
|
|
117
|
+
What: You paste a token you got from Dhan (web, OAuth, partner, whatever) into config.
|
|
118
|
+
How:
|
|
119
|
+
DhanHQ.configure do |config| config.client_id = ENV["DHAN_CLIENT_ID"] config.access_token = ENV["DHAN_ACCESS_TOKEN"]end
|
|
120
|
+
When: You’re OK rotating tokens manually (e.g. cron job, ops runbook).
|
|
121
|
+
2️⃣ Dynamic token via access_token_provider
|
|
122
|
+
What: Gem asks you for a token on every request (proc/lambda).
|
|
123
|
+
How:
|
|
124
|
+
DhanHQ.configure do |config| config.client_id = ENV["DHAN_CLIENT_ID"] config.access_token_provider = -> { MyTokenStore.fetch_current_token } config.on_token_expired = ->(error) { MyTokenStore.refresh!(error) }end
|
|
125
|
+
Behavior:
|
|
126
|
+
On 401 (auth failure), client calls on_token_expired, then retries once using a fresh token from access_token_provider.
|
|
127
|
+
3️⃣ Fetch-from-token-endpoint (configure_from_token_endpoint)
|
|
128
|
+
What: Gem calls your HTTP endpoint once to get access_token + client_id.
|
|
129
|
+
How:
|
|
130
|
+
DhanHQ.configure_from_token_endpoint( base_url: "https://myapp.com", bearer_token: ENV["DHAN_TOKEN_ENDPOINT_BEARER"])# expects JSON: { access_token: "...", client_id: "...", base_url: "..." (optional) }
|
|
131
|
+
When: Multi-tenant or central credential service, you don’t want tokens in ENV directly.
|
|
132
|
+
4️⃣ TOTP-based token generation (new DhanHQ::Auth flow)
|
|
133
|
+
Module API (low-level)
|
|
134
|
+
What: Direct call to Dhan’s generateAccessToken endpoint using TOTP.
|
|
135
|
+
totp = DhanHQ::Auth.generate_totp(ENV["DHAN_TOTP_SECRET"])response = DhanHQ::Auth.generate_access_token( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp: totp)token = response["accessToken"]expiry = response["expiryTime"]
|
|
136
|
+
Client API (high-level, returns TokenResponse)
|
|
137
|
+
client = DhanHQ::Client.new(api_type: :order_api)token = client.generate_access_token( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp_secret: ENV["DHAN_TOTP_SECRET"] # or `totp:` if you computed it)# auto-applies token + client_id to `DhanHQ.configuration`
|
|
138
|
+
When: Fully automated individual setup (no manual web token generation).
|
|
139
|
+
5️⃣ Auto token lifecycle management (TokenManager)
|
|
140
|
+
What: Gem handles generate + renew + retry around every API call.
|
|
141
|
+
How:
|
|
142
|
+
client = DhanHQ::Client.new(api_type: :order_api)client.enable_auto_token_management!( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp_secret: ENV["DHAN_TOTP_SECRET"])# From now on, `client.request` auto-ensures a valid token.
|
|
143
|
+
Behavior:
|
|
144
|
+
On first use: calls TOTP TokenGenerator → applies token.
|
|
145
|
+
Before each request: ensure_valid_token!:
|
|
146
|
+
If no token → generate.
|
|
147
|
+
If needs_refresh? → TokenRenewal (POST /v2/RenewToken) with current token + dhanClientId.
|
|
148
|
+
If renewal fails with auth error → falls back to full generate.
|
|
149
|
+
6️⃣ Web-token renewal only (RenewToken)
|
|
150
|
+
Module API:
|
|
151
|
+
response = DhanHQ::Auth.renew_token( access_token: current_token, client_id: ENV["DHAN_CLIENT_ID"])
|
|
152
|
+
Client / manager (high-level):
|
|
153
|
+
client.renew_access_token (returns TokenResponse, updates config).
|
|
154
|
+
TokenManager#refresh! internally uses Auth::TokenRenewal.
|
|
155
|
+
When: You’re using web-generated 24h tokens and want to extend them without switching to TOTP.
|
|
156
|
+
TL;DR
|
|
157
|
+
Manual: Static token (access_token)
|
|
158
|
+
Dynamic: access_token_provider (+ optional on_token_expired)
|
|
159
|
+
Central service: configure_from_token_endpoint
|
|
160
|
+
Fully automated: TOTP generate (Auth / Client#generate_access_token)
|
|
161
|
+
Production-grade automation: enable_auto_token_management! (generate + renew)
|
|
162
|
+
If you tell me your exact deployment style (single user box, multi-user SaaS, on-prem, etc.), I can tell you which one you should actually use and what to delete as overkill.
|
data/docs/TESTING_GUIDE.md
CHANGED
|
@@ -72,8 +72,8 @@ puts "Token provider: #{DhanHQ.configuration.access_token_provider ? 'Set' : 'No
|
|
|
72
72
|
|
|
73
73
|
```ruby
|
|
74
74
|
# Check current environment variables
|
|
75
|
-
puts "
|
|
76
|
-
puts "
|
|
75
|
+
puts "DHAN_CLIENT_ID: #{ENV['DHAN_CLIENT_ID']}"
|
|
76
|
+
puts "DHAN_ACCESS_TOKEN: #{ENV['DHAN_ACCESS_TOKEN'] ? 'Set' : 'Not Set'}"
|
|
77
77
|
puts "DHAN_LOG_LEVEL: #{ENV['DHAN_LOG_LEVEL'] || 'INFO'}"
|
|
78
78
|
puts "DHAN_CONNECT_TIMEOUT: #{ENV['DHAN_CONNECT_TIMEOUT'] || '10'}"
|
|
79
79
|
puts "DHAN_READ_TIMEOUT: #{ENV['DHAN_READ_TIMEOUT'] || '30'}"
|
|
@@ -521,7 +521,7 @@ if position
|
|
|
521
521
|
to_product_type: "MARGIN",
|
|
522
522
|
quantity: position.net_qty.abs
|
|
523
523
|
)
|
|
524
|
-
|
|
524
|
+
|
|
525
525
|
if result
|
|
526
526
|
puts "Position converted successfully"
|
|
527
527
|
else
|
|
@@ -784,22 +784,22 @@ if instrument
|
|
|
784
784
|
# Get LTP
|
|
785
785
|
ltp_data = instrument.ltp
|
|
786
786
|
puts "LTP: ₹#{ltp_data[:last_price]}"
|
|
787
|
-
|
|
787
|
+
|
|
788
788
|
# Get OHLC
|
|
789
789
|
ohlc_data = instrument.ohlc
|
|
790
790
|
puts "OHLC: #{ohlc_data[:ohlc]}"
|
|
791
|
-
|
|
791
|
+
|
|
792
792
|
# Get Quote
|
|
793
793
|
quote_data = instrument.quote
|
|
794
794
|
puts "Quote: #{quote_data[:ltp]}"
|
|
795
|
-
|
|
795
|
+
|
|
796
796
|
# Get Daily Historical Data
|
|
797
797
|
daily_data = instrument.daily(
|
|
798
798
|
from_date: Date.today - 7,
|
|
799
799
|
to_date: Date.today
|
|
800
800
|
)
|
|
801
801
|
puts "Daily candles: #{daily_data.size}"
|
|
802
|
-
|
|
802
|
+
|
|
803
803
|
# Get Intraday Historical Data
|
|
804
804
|
intraday_data = instrument.intraday(
|
|
805
805
|
from_date: Date.today,
|
|
@@ -807,7 +807,7 @@ if instrument
|
|
|
807
807
|
interval: 5
|
|
808
808
|
)
|
|
809
809
|
puts "Intraday candles: #{intraday_data.size}"
|
|
810
|
-
|
|
810
|
+
|
|
811
811
|
# Get Expiry List (for F&O instruments)
|
|
812
812
|
if instrument.instrument == "FUTSTK" || instrument.instrument == "OPTSTK"
|
|
813
813
|
expiry_list = instrument.expiry_list
|
data/docs/live_order_updates.md
CHANGED
|
@@ -194,8 +194,8 @@ client.start
|
|
|
194
194
|
### Environment Variables
|
|
195
195
|
```bash
|
|
196
196
|
# Required
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
DHAN_CLIENT_ID=your_client_id
|
|
198
|
+
DHAN_ACCESS_TOKEN=your_access_token
|
|
199
199
|
|
|
200
200
|
# Optional WebSocket settings
|
|
201
201
|
DHAN_WS_ORDER_URL=wss://api-order-update.dhan.co
|
data/docs/rails_integration.md
CHANGED
|
@@ -29,8 +29,8 @@ boot successfully:
|
|
|
29
29
|
|
|
30
30
|
| Variable | Description |
|
|
31
31
|
| --- | --- |
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
32
|
+
| `DHAN_CLIENT_ID` | Dhan trading client id for the account you want to trade with. |
|
|
33
|
+
| `DHAN_ACCESS_TOKEN` | REST/WebSocket access token (regenerate via the Dhan console or APIs). |
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
bin/rails credentials:edit
|
|
@@ -61,8 +61,8 @@ environment variables (Rails credentials can be copied into ENV on boot):
|
|
|
61
61
|
require 'dhan_hq'
|
|
62
62
|
|
|
63
63
|
if (creds = Rails.application.credentials.dig(:dhanhq))
|
|
64
|
-
ENV['
|
|
65
|
-
ENV['
|
|
64
|
+
ENV['DHAN_CLIENT_ID'] ||= creds[:client_id]
|
|
65
|
+
ENV['DHAN_ACCESS_TOKEN'] ||= creds[:access_token]
|
|
66
66
|
ENV['DHAN_LOG_LEVEL'] ||= creds[:log_level]&.upcase
|
|
67
67
|
ENV['DHAN_BASE_URL'] ||= creds[:base_url]
|
|
68
68
|
ENV['DHAN_WS_ORDER_URL'] ||= creds[:ws_order_url]
|
|
@@ -77,8 +77,8 @@ if (creds = Rails.application.credentials.dig(:dhanhq))
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# fall back to traditional ENV variables when credentials are not defined
|
|
80
|
-
ENV['
|
|
81
|
-
ENV['
|
|
80
|
+
ENV['DHAN_CLIENT_ID'] ||= ENV.fetch('DHAN_CLIENT_ID', nil)
|
|
81
|
+
ENV['DHAN_ACCESS_TOKEN'] ||= ENV.fetch('DHAN_ACCESS_TOKEN', nil)
|
|
82
82
|
|
|
83
83
|
DhanHQ.configure_with_env
|
|
84
84
|
|
|
@@ -108,7 +108,7 @@ initializer calls `DhanHQ.configure_with_env`.
|
|
|
108
108
|
For token rotation without restarting the app (e.g. token stored in DB or refreshed via OAuth), use `access_token_provider` so the token is resolved at request time:
|
|
109
109
|
|
|
110
110
|
```ruby
|
|
111
|
-
# config/initializers/dhanhq.rb (alternative to static
|
|
111
|
+
# config/initializers/dhanhq.rb (alternative to static DHAN_ACCESS_TOKEN)
|
|
112
112
|
DhanHQ.configure do |config|
|
|
113
113
|
config.client_id = ENV["DHAN_CLIENT_ID"] || Rails.application.credentials.dig(:dhanhq, :client_id)
|
|
114
114
|
config.access_token_provider = lambda do
|
|
@@ -31,8 +31,8 @@ require 'dhan_hq'
|
|
|
31
31
|
|
|
32
32
|
# Configure DhanHQ
|
|
33
33
|
DhanHQ.configure do |config|
|
|
34
|
-
config.client_id = ENV["
|
|
35
|
-
config.access_token = ENV["
|
|
34
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
35
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
36
36
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
37
37
|
end
|
|
38
38
|
|
|
@@ -59,8 +59,8 @@ market_client.stop
|
|
|
59
59
|
|
|
60
60
|
```bash
|
|
61
61
|
# Set environment variables
|
|
62
|
-
export
|
|
63
|
-
export
|
|
62
|
+
export DHAN_CLIENT_ID="your_client_id"
|
|
63
|
+
export DHAN_ACCESS_TOKEN="your_access_token"
|
|
64
64
|
|
|
65
65
|
# Run the script
|
|
66
66
|
ruby market_feed_script.rb
|
|
@@ -74,8 +74,8 @@ For dynamic token at request time use `config.access_token_provider`. For web-ge
|
|
|
74
74
|
|
|
75
75
|
```bash
|
|
76
76
|
# Required
|
|
77
|
-
export
|
|
78
|
-
export
|
|
77
|
+
export DHAN_CLIENT_ID="your_client_id"
|
|
78
|
+
export DHAN_ACCESS_TOKEN="your_access_token"
|
|
79
79
|
|
|
80
80
|
# Optional
|
|
81
81
|
export DHAN_WS_USER_TYPE="SELF" # or "PARTNER"
|
|
@@ -132,8 +132,8 @@ require 'json'
|
|
|
132
132
|
|
|
133
133
|
# Configure DhanHQ
|
|
134
134
|
DhanHQ.configure do |config|
|
|
135
|
-
config.client_id = ENV["
|
|
136
|
-
config.access_token = ENV["
|
|
135
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
136
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
137
137
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
138
138
|
end
|
|
139
139
|
|
|
@@ -264,8 +264,8 @@ require 'json'
|
|
|
264
264
|
|
|
265
265
|
# Configure DhanHQ
|
|
266
266
|
DhanHQ.configure do |config|
|
|
267
|
-
config.client_id = ENV["
|
|
268
|
-
config.access_token = ENV["
|
|
267
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
268
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
269
269
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
270
270
|
end
|
|
271
271
|
|
|
@@ -394,8 +394,8 @@ require 'json'
|
|
|
394
394
|
|
|
395
395
|
# Configure DhanHQ
|
|
396
396
|
DhanHQ.configure do |config|
|
|
397
|
-
config.client_id = ENV["
|
|
398
|
-
config.access_token = ENV["
|
|
397
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
398
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
399
399
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
400
400
|
end
|
|
401
401
|
|
|
@@ -542,8 +542,8 @@ WorkingDirectory=/opt/dhanhq-market-feed
|
|
|
542
542
|
ExecStart=/usr/bin/ruby market_feed_daemon.rb
|
|
543
543
|
Restart=always
|
|
544
544
|
RestartSec=10
|
|
545
|
-
Environment=
|
|
546
|
-
Environment=
|
|
545
|
+
Environment=DHAN_CLIENT_ID=your_client_id
|
|
546
|
+
Environment=DHAN_ACCESS_TOKEN=your_access_token
|
|
547
547
|
Environment=DHAN_WS_USER_TYPE=SELF
|
|
548
548
|
|
|
549
549
|
[Install]
|
|
@@ -562,8 +562,8 @@ require 'fileutils'
|
|
|
562
562
|
|
|
563
563
|
# Configure DhanHQ
|
|
564
564
|
DhanHQ.configure do |config|
|
|
565
|
-
config.client_id = ENV["
|
|
566
|
-
config.access_token = ENV["
|
|
565
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
566
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
567
567
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
568
568
|
end
|
|
569
569
|
|
|
@@ -779,8 +779,8 @@ require 'json'
|
|
|
779
779
|
|
|
780
780
|
# Configure DhanHQ
|
|
781
781
|
DhanHQ.configure do |config|
|
|
782
|
-
config.client_id = ENV["
|
|
783
|
-
config.access_token = ENV["
|
|
782
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
783
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
784
784
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
785
785
|
end
|
|
786
786
|
|
|
@@ -1061,8 +1061,8 @@ require 'json'
|
|
|
1061
1061
|
|
|
1062
1062
|
# Configure DhanHQ
|
|
1063
1063
|
DhanHQ.configure do |config|
|
|
1064
|
-
config.client_id = ENV["
|
|
1065
|
-
config.access_token = ENV["
|
|
1064
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
1065
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
1066
1066
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
1067
1067
|
end
|
|
1068
1068
|
|
|
@@ -1264,8 +1264,8 @@ end
|
|
|
1264
1264
|
|
|
1265
1265
|
# Configure DhanHQ
|
|
1266
1266
|
DhanHQ.configure do |config|
|
|
1267
|
-
config.client_id = ENV["
|
|
1268
|
-
config.access_token = ENV["
|
|
1267
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
1268
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
1269
1269
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
1270
1270
|
config.logger = logger
|
|
1271
1271
|
end
|
|
@@ -1383,8 +1383,8 @@ require 'json'
|
|
|
1383
1383
|
|
|
1384
1384
|
# Configure DhanHQ
|
|
1385
1385
|
DhanHQ.configure do |config|
|
|
1386
|
-
config.client_id = ENV["
|
|
1387
|
-
config.access_token = ENV["
|
|
1386
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
1387
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
1388
1388
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
1389
1389
|
end
|
|
1390
1390
|
|
data/docs/technical_analysis.md
CHANGED
|
@@ -12,7 +12,7 @@ This guide explains how to use the technical analysis modules bundled with this
|
|
|
12
12
|
|
|
13
13
|
## Prerequisites
|
|
14
14
|
|
|
15
|
-
- Environment variables set: `
|
|
15
|
+
- Environment variables set: `DHAN_CLIENT_ID`, `DHAN_ACCESS_TOKEN`
|
|
16
16
|
- Optional indicator gems:
|
|
17
17
|
- `gem install ruby-technical-analysis technical-analysis`
|
|
18
18
|
|
|
@@ -28,8 +28,8 @@ require 'dhan_hq'
|
|
|
28
28
|
|
|
29
29
|
# Configure DhanHQ
|
|
30
30
|
DhanHQ.configure do |config|
|
|
31
|
-
config.client_id = ENV["
|
|
32
|
-
config.access_token = ENV["
|
|
31
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
32
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
33
33
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
34
34
|
end
|
|
35
35
|
# For dynamic token: use config.access_token_provider. For web-generated tokens, refresh with DhanHQ::Auth.renew_token. API key/Partner flows: implement in your app. See docs/AUTHENTICATION.md.
|
|
@@ -787,8 +787,8 @@ puts "Depth subscriptions: #{depth_client.subscriptions}"
|
|
|
787
787
|
```ruby
|
|
788
788
|
# ✅ Good: Environment variables
|
|
789
789
|
DhanHQ.configure do |config|
|
|
790
|
-
config.client_id = ENV["
|
|
791
|
-
config.access_token = ENV["
|
|
790
|
+
config.client_id = ENV["DHAN_CLIENT_ID"]
|
|
791
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"]
|
|
792
792
|
end
|
|
793
793
|
|
|
794
794
|
# ❌ Bad: Hardcoded credentials
|
|
@@ -12,8 +12,8 @@ require "dhan_hq"
|
|
|
12
12
|
|
|
13
13
|
# Configure DhanHQ
|
|
14
14
|
DhanHQ.configure do |config|
|
|
15
|
-
config.client_id = ENV["
|
|
16
|
-
config.access_token = ENV["
|
|
15
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
16
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
17
17
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
18
18
|
end
|
|
19
19
|
|
|
@@ -8,8 +8,8 @@ require "dhan_hq"
|
|
|
8
8
|
|
|
9
9
|
# Configure DhanHQ
|
|
10
10
|
DhanHQ.configure do |config|
|
|
11
|
-
config.client_id = ENV["
|
|
12
|
-
config.access_token = ENV["
|
|
11
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
12
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
13
13
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
14
14
|
end
|
|
15
15
|
|
|
@@ -10,8 +10,8 @@ require "dhan_hq"
|
|
|
10
10
|
|
|
11
11
|
# Configure DhanHQ
|
|
12
12
|
DhanHQ.configure do |config|
|
|
13
|
-
config.client_id = ENV["
|
|
14
|
-
config.access_token = ENV["
|
|
13
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
14
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
15
15
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -10,8 +10,8 @@ require "dhan_hq"
|
|
|
10
10
|
|
|
11
11
|
# Configure DhanHQ
|
|
12
12
|
DhanHQ.configure do |config|
|
|
13
|
-
config.client_id = ENV["
|
|
14
|
-
config.access_token = ENV["
|
|
13
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
14
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
15
15
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -10,8 +10,8 @@ require "dhan_hq"
|
|
|
10
10
|
|
|
11
11
|
# Configure DhanHQ
|
|
12
12
|
DhanHQ.configure do |config|
|
|
13
|
-
config.client_id = ENV["
|
|
14
|
-
config.access_token = ENV["
|
|
13
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
14
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
15
15
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
16
16
|
end
|
|
17
17
|
|
|
@@ -9,8 +9,8 @@ require "dhan_hq"
|
|
|
9
9
|
|
|
10
10
|
# Configure DhanHQ
|
|
11
11
|
DhanHQ.configure do |config|
|
|
12
|
-
config.client_id = ENV["
|
|
13
|
-
config.access_token = ENV["
|
|
12
|
+
config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
|
|
13
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
|
|
14
14
|
config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
|
|
15
15
|
end
|
|
16
16
|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DhanHQ
|
|
4
|
+
module Auth
|
|
5
|
+
# Backward-compatible wrapper for token generation.
|
|
6
|
+
# Delegates to module-level Auth.generate_access_token.
|
|
7
|
+
class TokenGenerator
|
|
8
|
+
def generate(dhan_client_id:, pin:, totp: nil, totp_secret: nil)
|
|
9
|
+
otp = resolve_totp(totp, totp_secret)
|
|
10
|
+
|
|
11
|
+
response = Auth.generate_access_token(
|
|
12
|
+
dhan_client_id: dhan_client_id,
|
|
13
|
+
pin: pin,
|
|
14
|
+
totp: otp
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
Models::TokenResponse.new(response)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def resolve_totp(totp, secret)
|
|
23
|
+
totp = totp.to_s.strip
|
|
24
|
+
return totp unless totp.empty?
|
|
25
|
+
|
|
26
|
+
secret = secret.to_s.strip
|
|
27
|
+
raise ArgumentError, "Provide `totp` or `totp_secret` (e.g. ENV['DHAN_TOTP_SECRET'])" if secret.empty?
|
|
28
|
+
|
|
29
|
+
Auth.generate_totp(secret)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|