DhanHQ 3.3.0 → 4.0.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/lib/dhan_hq.rb CHANGED
@@ -3,7 +3,11 @@
3
3
  require "json"
4
4
  require "logger"
5
5
  require "zeitwerk"
6
- require "dotenv/load"
6
+ begin
7
+ require "dotenv/load"
8
+ rescue LoadError
9
+ # Dotenv is optional in host applications
10
+ end
7
11
  require "faraday"
8
12
  # Minimal eager requires for backward-compatible constants.
9
13
  # These are widely referenced (e.g. `DhanHQ::BaseAPI`) and should not depend on
@@ -88,9 +92,13 @@ module DhanHQ
88
92
  # @return [String]
89
93
  BASE_URL = Constants::Urls::REST_API_BASE
90
94
  # The current configuration instance.
91
- #
92
- # @return [DhanHQ::Configuration, nil] The current configuration or `nil` if not set.
93
- attr_accessor :configuration
95
+ attr_reader :configuration
96
+
97
+ def configuration=(config)
98
+ @configuration = config
99
+ @default_client = nil
100
+ reset_model_resources!
101
+ end
94
102
 
95
103
  attr_writer :logger
96
104
 
@@ -106,8 +114,10 @@ module DhanHQ
106
114
  # @return [void]
107
115
  def configure
108
116
  self.configuration ||= Configuration.new
109
- yield(configuration)
117
+ yield(configuration) if block_given?
110
118
  self.logger ||= Logger.new($stdout, level: Logger::INFO)
119
+ @default_client = Client.new(config: configuration)
120
+ reset_model_resources!
111
121
  end
112
122
 
113
123
  # default logger so calls like DhanHQ.logger&.info never explode
@@ -118,6 +128,18 @@ module DhanHQ
118
128
  @logger ||= Logger.new($stdout, level: Logger::INFO)
119
129
  end
120
130
 
131
+ # Global default client (used for backward compatibility with class-level APIs)
132
+ #
133
+ # @return [DhanHQ::Client]
134
+ def default_client
135
+ @default_client ||= Client.new(config: ensure_configuration!)
136
+ end
137
+
138
+ # Resets the global default client
139
+ def reset_default_client!
140
+ @default_client = nil
141
+ end
142
+
121
143
  # Configures the DhanHQ client using environment variables.
122
144
  #
123
145
  # When credentials are injected via `DHAN_ACCESS_TOKEN` and `DHAN_CLIENT_ID` this helper
@@ -129,6 +151,8 @@ module DhanHQ
129
151
  # @return [void]
130
152
  def configure_with_env
131
153
  self.configuration = Configuration.new
154
+ @default_client = Client.new(config: configuration)
155
+ reset_model_resources!
132
156
  end
133
157
 
134
158
  # Ensures configuration exists, bootstrapping from ENV when nil.
@@ -140,9 +164,32 @@ module DhanHQ
140
164
  self.configuration ||= Configuration.new
141
165
  end
142
166
 
143
- # Resets the configuration to nil.
167
+ # Resets the configuration and default client to nil.
144
168
  def reset_configuration!
145
169
  self.configuration = nil
170
+ @default_client = nil
171
+ reset_model_resources!
172
+ end
173
+
174
+ # Clears memoized class-level resource and API instances across all models.
175
+ def reset_model_resources!
176
+ models = [
177
+ BaseModel,
178
+ Models::Order, Models::Holding, Models::Position, Models::HistoricalData,
179
+ Models::Trade, Models::OptionChain, Models::Funds, Models::AlertOrder,
180
+ Models::ForeverOrder, Models::IcebergOrder, Models::SuperOrder, Models::TwapOrder,
181
+ Models::LedgerEntry, Models::Instrument, Models::Edis, Models::Profile,
182
+ Models::Margin, Models::MultiOrder, Models::KillSwitch, Models::PnlExit,
183
+ Models::MarketFeed, Models::ExpiredOptionsData
184
+ ]
185
+
186
+ models.each do |klass|
187
+ next unless klass.is_a?(Class)
188
+
189
+ klass.instance_variables.each do |var|
190
+ klass.instance_variable_set(var, nil) if var.to_s.end_with?("resource", "api")
191
+ end
192
+ end
146
193
  end
147
194
 
148
195
  # Configures the DhanHQ client by fetching credentials from a token endpoint.
@@ -164,39 +211,14 @@ module DhanHQ
164
211
  def configure_from_token_endpoint(base_url: nil, bearer_token: nil)
165
212
  base_url ||= ENV.fetch("DHAN_TOKEN_ENDPOINT_BASE_URL", nil)
166
213
  bearer_token ||= ENV.fetch("DHAN_TOKEN_ENDPOINT_BEARER", nil)
167
-
168
214
  raise DhanHQ::TokenEndpointError, "base_url and bearer_token (or ENV DHAN_TOKEN_ENDPOINT_*) are required" if base_url.to_s.empty? || bearer_token.to_s.empty?
169
215
 
170
- url = "#{base_url.to_s.chomp("/")}/auth/dhan/token"
171
- conn = ::Faraday.new(url: url) do |c|
172
- c.request :url_encoded
173
- c.adapter ::Faraday.default_adapter
174
- end
175
-
176
- response = conn.get("") do |req|
177
- req.headers["Authorization"] = "Bearer #{bearer_token}"
178
- req.headers["Accept"] = "application/json"
179
- end
180
-
181
- unless response.success?
182
- body = parse_json_body(response.body)
183
- msg = body["error"] || body["message"] || body["errorMessage"] || response.body.to_s
184
- raise DhanHQ::TokenEndpointError, "Token endpoint returned #{response.status}: #{msg}"
185
- end
186
-
187
- data = parse_json_body(response.body)
188
- data = data.transform_keys(&:to_s) if data.is_a?(Hash)
189
-
190
- access_token = data["access_token"] || data[:access_token]
191
- client_id = data["client_id"] || data[:client_id]
216
+ data = fetch_token_endpoint("#{base_url.to_s.chomp("/")}/auth/dhan/token", bearer_token, "Token endpoint")
217
+ access_token = data["access_token"]
218
+ client_id = data["client_id"]
192
219
  raise DhanHQ::TokenEndpointError, "Token endpoint response missing access_token or client_id" if access_token.to_s.empty? || client_id.to_s.empty?
193
220
 
194
- self.configuration ||= Configuration.new
195
- configuration.access_token = access_token.to_s
196
- configuration.client_id = client_id.to_s
197
- dhan_base = data["base_url"] || data[:base_url]
198
- configuration.base_url = dhan_base.to_s if dhan_base.to_s != ""
199
- configuration
221
+ apply_token_config(access_token, client_id, data["base_url"])
200
222
  end
201
223
 
202
224
  # Configures the DhanHQ client by fetching credentials from a dashboard API.
@@ -208,6 +230,18 @@ module DhanHQ
208
230
  def configure_from_dashboard(bearer_token:, url: "http://localhost:3011/api/dhan_access_token")
209
231
  raise DhanHQ::TokenEndpointError, "bearer_token is required" if bearer_token.to_s.empty?
210
232
 
233
+ data = fetch_token_endpoint(url, bearer_token, "Dashboard")
234
+ access_token = data["access_token"] || data["accessToken"] || data["dhan_access_token"] || data["dhanaccesstoken"]
235
+ client_id = data["client_id"] || data["clientId"] || data["dhan_client_id"] || data["dhanClientId"]
236
+ client_id ||= configuration&.client_id || ENV.fetch("DHAN_CLIENT_ID", nil)
237
+
238
+ raise DhanHQ::TokenEndpointError, "Dashboard response missing access_token (tried access_token, dhan_access_token, dhanaccesstoken)" if access_token.to_s.empty?
239
+ raise DhanHQ::TokenEndpointError, "Dashboard response missing client_id, and no fallback client_id was found in config or ENV['DHAN_CLIENT_ID']" if client_id.to_s.empty?
240
+
241
+ apply_token_config(access_token, client_id, data["base_url"] || data["baseUrl"])
242
+ end
243
+
244
+ def fetch_token_endpoint(url, bearer_token, label)
211
245
  conn = ::Faraday.new(url: url) do |c|
212
246
  c.request :url_encoded
213
247
  c.adapter ::Faraday.default_adapter
@@ -221,24 +255,18 @@ module DhanHQ
221
255
  unless response.success?
222
256
  body = parse_json_body(response.body)
223
257
  msg = body["error"] || body["message"] || body["errorMessage"] || response.body.to_s
224
- raise DhanHQ::TokenEndpointError, "Dashboard returned #{response.status}: #{msg}"
258
+ raise DhanHQ::TokenEndpointError, "#{label} returned #{response.status}: #{msg}"
225
259
  end
226
260
 
227
261
  data = parse_json_body(response.body)
228
- data = data.transform_keys(&:to_s) if data.is_a?(Hash)
229
-
230
- access_token = data["access_token"] || data["accessToken"] || data["dhan_access_token"] || data["dhanaccesstoken"]
231
- client_id = data["client_id"] || data["clientId"] || data["dhan_client_id"] || data["dhanClientId"]
232
- client_id ||= self.configuration&.client_id || ENV.fetch("DHAN_CLIENT_ID", nil)
233
-
234
- raise DhanHQ::TokenEndpointError, "Dashboard response missing access_token (tried access_token, dhan_access_token, dhanaccesstoken)" if access_token.to_s.empty?
235
- raise DhanHQ::TokenEndpointError, "Dashboard response missing client_id, and no fallback client_id was found in config or ENV['DHAN_CLIENT_ID']" if client_id.to_s.empty?
262
+ data.is_a?(Hash) ? data.transform_keys(&:to_s) : {}
263
+ end
236
264
 
265
+ def apply_token_config(access_token, client_id, base_url)
237
266
  self.configuration ||= Configuration.new
238
267
  configuration.access_token = access_token.to_s
239
268
  configuration.client_id = client_id.to_s
240
- dhan_base = data["base_url"] || data["baseUrl"]
241
- configuration.base_url = dhan_base.to_s if dhan_base.to_s != ""
269
+ configuration.base_url = base_url.to_s if base_url.to_s != ""
242
270
  configuration
243
271
  end
244
272
 
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Dhanhq
6
+ # Scaffolds a DhanHQ initializer, a sample order-placing service object, and
7
+ # a Sidekiq worker + ActionCable channel for streaming market data.
8
+ #
9
+ # rails generate dhanhq:install
10
+ #
11
+ class InstallGenerator < Rails::Generators::Base
12
+ source_root File.expand_path("templates", __dir__)
13
+
14
+ def create_initializer
15
+ template "initializer.rb", "config/initializers/dhanhq.rb"
16
+ end
17
+
18
+ def create_order_service
19
+ template "place_order_service.rb", "app/services/dhan/orders/place_order.rb"
20
+ end
21
+
22
+ def create_market_feed_worker
23
+ template "market_feed_worker.rb", "app/workers/dhan_market_feed_worker.rb"
24
+ end
25
+
26
+ def create_market_feed_channel
27
+ template "market_feed_channel.rb", "app/channels/dhan_market_feed_channel.rb"
28
+ end
29
+
30
+ def show_post_install_message
31
+ say ""
32
+ say "DhanHQ installed! Next steps:", :green
33
+ say " 1. Add your credentials:"
34
+ say " rails credentials:edit"
35
+ say " dhanhq:"
36
+ say " client_id: \"your_client_id\""
37
+ say " access_token: \"your_access_token\""
38
+ say " 2. Set LIVE_TRADING=true before placing real orders (see docs/CONFIGURATION.md)"
39
+ say " 3. Start the market feed: DhanMarketFeedWorker.perform_async"
40
+ say ""
41
+ say "Full reference: https://github.com/shubhamtaywade82/dhanhq-client/blob/main/docs/RAILS_INTEGRATION.md"
42
+ say ""
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dhan_hq"
4
+
5
+ if (creds = Rails.application.credentials.dig(:dhanhq))
6
+ ENV["DHAN_CLIENT_ID"] ||= creds[:client_id]
7
+ ENV["DHAN_ACCESS_TOKEN"] ||= creds[:access_token]
8
+ end
9
+
10
+ DhanHQ.configure_with_env
11
+
12
+ log_level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase
13
+ DhanHQ.logger.level = Logger.const_get(log_level)
14
+
15
+ # Full optional configuration (base_url, ws_order_url, partner auth, timeouts,
16
+ # DHAN_WS_DEBUG, ...) is documented in:
17
+ # https://github.com/shubhamtaywade82/dhanhq-client/blob/main/docs/CONFIGURATION.md
18
+ # https://github.com/shubhamtaywade82/dhanhq-client/blob/main/docs/RAILS_INTEGRATION.md
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class DhanMarketFeedChannel < ApplicationCable::Channel
4
+ def subscribed
5
+ stream_from "dhan_market_feed"
6
+ end
7
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Keeps a DhanHQ market feed connection open and broadcasts ticks over
4
+ # ActionCable. Start it with DhanMarketFeedWorker.perform_async -- the job
5
+ # blocks for the life of the connection rather than completing immediately,
6
+ # so Sidekiq's dashboard reflects whether the feed is actually running.
7
+ #
8
+ # retry: false because there is nothing to retry: the underlying
9
+ # DhanHQ::WS::Client already reconnects and re-subscribes on its own.
10
+ class DhanMarketFeedWorker
11
+ include Sidekiq::Worker
12
+ sidekiq_options retry: false
13
+
14
+ def perform(mode = "quote")
15
+ client = DhanHQ::WS.connect(mode: mode.to_sym) do |tick|
16
+ ActionCable.server.broadcast("dhan_market_feed", tick)
17
+ end
18
+
19
+ client.on(:reconnect) { |info| Rails.logger.warn("[DhanMarketFeedWorker] reconnect ##{info[:attempt]}") }
20
+ client.on(:error) { |message| Rails.logger.error("[DhanMarketFeedWorker] #{message}") }
21
+
22
+ loop do
23
+ sleep 30
24
+ break unless client.connected?
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dhan
4
+ module Orders
5
+ # Places a Dhan order via the bang variant, so a rejection raises a
6
+ # specific, catchable error instead of Order.place's ambiguous
7
+ # nil/false/ErrorObject return.
8
+ class PlaceOrder
9
+ def initialize(params)
10
+ @params = params
11
+ end
12
+
13
+ def call
14
+ DhanHQ::Models::Order.place!(@params)
15
+ rescue DhanHQ::OrderError => e
16
+ Rails.logger.error("Dhan order rejected: #{e.message}")
17
+ raise
18
+ rescue DhanHQ::RiskViolation => e
19
+ Rails.logger.warn("Dhan risk check blocked the order: #{e.message}")
20
+ raise
21
+ end
22
+ end
23
+ end
24
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: DhanHQ
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.3.0
4
+ version: 4.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shubham Taywade
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2026-07-26 00:00:00.000000000 Z
11
+ date: 2026-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -16,98 +16,98 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '0'
19
+ version: '8.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
- version: '0'
26
+ version: '8.0'
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: base64
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - ">="
32
32
  - !ruby/object:Gem::Version
33
- version: '0'
33
+ version: '0.1'
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - ">="
39
39
  - !ruby/object:Gem::Version
40
- version: '0'
40
+ version: '0.1'
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: bindata
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - ">="
46
46
  - !ruby/object:Gem::Version
47
- version: '0'
47
+ version: '2.4'
48
48
  type: :runtime
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - ">="
53
53
  - !ruby/object:Gem::Version
54
- version: '0'
54
+ version: '2.4'
55
55
  - !ruby/object:Gem::Dependency
56
56
  name: concurrent-ruby
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
59
  - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '0'
61
+ version: '1.3'
62
62
  type: :runtime
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
66
  - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '0'
68
+ version: '1.3'
69
69
  - !ruby/object:Gem::Dependency
70
70
  name: csv
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
73
  - - ">="
74
74
  - !ruby/object:Gem::Version
75
- version: '0'
75
+ version: '3.0'
76
76
  type: :runtime
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
80
  - - ">="
81
81
  - !ruby/object:Gem::Version
82
- version: '0'
82
+ version: '3.0'
83
83
  - !ruby/object:Gem::Dependency
84
84
  name: dry-validation
85
85
  requirement: !ruby/object:Gem::Requirement
86
86
  requirements:
87
- - - ">="
87
+ - - "~>"
88
88
  - !ruby/object:Gem::Version
89
- version: '0'
89
+ version: '1.0'
90
90
  type: :runtime
91
91
  prerelease: false
92
92
  version_requirements: !ruby/object:Gem::Requirement
93
93
  requirements:
94
- - - ">="
94
+ - - "~>"
95
95
  - !ruby/object:Gem::Version
96
- version: '0'
96
+ version: '1.0'
97
97
  - !ruby/object:Gem::Dependency
98
98
  name: eventmachine
99
99
  requirement: !ruby/object:Gem::Requirement
100
100
  requirements:
101
101
  - - ">="
102
102
  - !ruby/object:Gem::Version
103
- version: '0'
103
+ version: '1.2'
104
104
  type: :runtime
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - ">="
109
109
  - !ruby/object:Gem::Version
110
- version: '0'
110
+ version: '1.2'
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: faraday
113
113
  requirement: !ruby/object:Gem::Requirement
@@ -128,46 +128,47 @@ dependencies:
128
128
  requirements:
129
129
  - - ">="
130
130
  - !ruby/object:Gem::Version
131
- version: '0'
131
+ version: '0.11'
132
132
  type: :runtime
133
133
  prerelease: false
134
134
  version_requirements: !ruby/object:Gem::Requirement
135
135
  requirements:
136
136
  - - ">="
137
137
  - !ruby/object:Gem::Version
138
- version: '0'
138
+ version: '0.11'
139
139
  - !ruby/object:Gem::Dependency
140
140
  name: rotp
141
141
  requirement: !ruby/object:Gem::Requirement
142
142
  requirements:
143
143
  - - ">="
144
144
  - !ruby/object:Gem::Version
145
- version: '0'
145
+ version: '6.0'
146
146
  type: :runtime
147
147
  prerelease: false
148
148
  version_requirements: !ruby/object:Gem::Requirement
149
149
  requirements:
150
150
  - - ">="
151
151
  - !ruby/object:Gem::Version
152
- version: '0'
152
+ version: '6.0'
153
153
  - !ruby/object:Gem::Dependency
154
154
  name: zeitwerk
155
155
  requirement: !ruby/object:Gem::Requirement
156
156
  requirements:
157
157
  - - ">="
158
158
  - !ruby/object:Gem::Version
159
- version: '0'
159
+ version: '2.6'
160
160
  type: :runtime
161
161
  prerelease: false
162
162
  version_requirements: !ruby/object:Gem::Requirement
163
163
  requirements:
164
164
  - - ">="
165
165
  - !ruby/object:Gem::Version
166
- version: '0'
167
- description: A production-grade Ruby SDK for Dhan API v2 built for algo trading, portfolio
168
- monitoring, and live trading systems. Provides typed models, token lifecycle management,
169
- dry-validation contracts, resilient WebSocket streaming, and safety-focused order
170
- workflows for Ruby applications.
166
+ version: '2.6'
167
+ description: A production-grade Ruby SDK and Ruby client for Dhan API v2 built for
168
+ algorithmic trading, portfolio monitoring, and live trading systems on NSE, BSE
169
+ and MCX. Provides typed models, token lifecycle management, dry-validation contracts,
170
+ resilient WebSocket streaming with auto-reconnect, and safety-focused order workflows
171
+ for Ruby on Rails and standalone Ruby applications.
171
172
  email:
172
173
  - shubhamtaywade82@gmail.com
173
174
  executables:
@@ -228,6 +229,10 @@ files:
228
229
  - lib/DhanHQ/auth/token_generator.rb
229
230
  - lib/DhanHQ/auth/token_manager.rb
230
231
  - lib/DhanHQ/auth/token_renewal.rb
232
+ - lib/DhanHQ/backtest.rb
233
+ - lib/DhanHQ/backtest/result.rb
234
+ - lib/DhanHQ/backtest/runner.rb
235
+ - lib/DhanHQ/backtest/trade.rb
231
236
  - lib/DhanHQ/client.rb
232
237
  - lib/DhanHQ/concerns/bang_writes.rb
233
238
  - lib/DhanHQ/concerns/order_audit.rb
@@ -283,6 +288,7 @@ files:
283
288
  - lib/DhanHQ/helpers/response_helper.rb
284
289
  - lib/DhanHQ/helpers/validation_helper.rb
285
290
  - lib/DhanHQ/indicators.rb
291
+ - lib/DhanHQ/jobs/place_order_job.rb
286
292
  - lib/DhanHQ/json_loader.rb
287
293
  - lib/DhanHQ/market_data.rb
288
294
  - lib/DhanHQ/market_data/market_snapshot.rb
@@ -433,6 +439,11 @@ files:
433
439
  - lib/dhanhq/analysis/multi_timeframe_analyzer.rb
434
440
  - lib/dhanhq/analysis/options_buying_advisor.rb
435
441
  - lib/dhanhq/contracts/options_buying_advisor_contract.rb
442
+ - lib/generators/dhanhq/install/install_generator.rb
443
+ - lib/generators/dhanhq/install/templates/initializer.rb
444
+ - lib/generators/dhanhq/install/templates/market_feed_channel.rb
445
+ - lib/generators/dhanhq/install/templates/market_feed_worker.rb
446
+ - lib/generators/dhanhq/install/templates/place_order_service.rb
436
447
  - lib/rubocop/cop/dhanhq/use_constants.rb
437
448
  - lib/ta.rb
438
449
  - lib/ta/candles.rb
@@ -469,14 +480,15 @@ files:
469
480
  - skills/dhanhq-ruby/scripts/resolve_security.rb
470
481
  - skills/dhanhq-ruby/scripts/trade_logger.rb
471
482
  - skills/dhanhq-ruby/scripts/validate_order.rb
472
- homepage: https://github.com/shubhamtaywade82/dhanhq-client
483
+ homepage: https://shubhamtaywade82.github.io/dhanhq-client/
473
484
  licenses:
474
485
  - MIT
475
486
  metadata:
476
487
  allowed_push_host: https://rubygems.org
477
- homepage_uri: https://github.com/shubhamtaywade82/dhanhq-client
488
+ homepage_uri: https://shubhamtaywade82.github.io/dhanhq-client/
478
489
  source_code_uri: https://github.com/shubhamtaywade82/dhanhq-client
479
490
  changelog_uri: https://github.com/shubhamtaywade82/dhanhq-client/blob/main/CHANGELOG.md
491
+ documentation_uri: https://shubhamtaywade82.github.io/dhanhq-client/
480
492
  rubygems_mfa_required: 'true'
481
493
  post_install_message:
482
494
  rdoc_options: []
@@ -496,5 +508,6 @@ requirements: []
496
508
  rubygems_version: 3.5.11
497
509
  signing_key:
498
510
  specification_version: 4
499
- summary: The Ruby SDK for Dhan API v2 with REST, WebSocket, and trading workflows.
511
+ summary: Production-grade Ruby SDK for Dhan API v2 with REST APIs, WebSocket market
512
+ data, token lifecycle management, dry-validation contracts and trading workflows.
500
513
  test_files: []