DhanHQ 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (113) hide show
  1. checksums.yaml +7 -0
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +26 -0
  4. data/CHANGELOG.md +20 -0
  5. data/CODE_OF_CONDUCT.md +132 -0
  6. data/GUIDE.md +555 -0
  7. data/LICENSE.txt +21 -0
  8. data/README.md +463 -0
  9. data/README1.md +521 -0
  10. data/Rakefile +12 -0
  11. data/TAGS +10 -0
  12. data/TODO-1.md +14 -0
  13. data/TODO.md +127 -0
  14. data/app/services/live/order_update_guard_support.rb +75 -0
  15. data/app/services/live/order_update_hub.rb +76 -0
  16. data/app/services/live/order_update_persistence_support.rb +68 -0
  17. data/config/initializers/order_update_hub.rb +16 -0
  18. data/diagram.html +184 -0
  19. data/diagram.md +34 -0
  20. data/docs/rails_integration.md +304 -0
  21. data/exe/DhanHQ +4 -0
  22. data/lib/DhanHQ/client.rb +116 -0
  23. data/lib/DhanHQ/config.rb +32 -0
  24. data/lib/DhanHQ/configuration.rb +72 -0
  25. data/lib/DhanHQ/constants.rb +170 -0
  26. data/lib/DhanHQ/contracts/base_contract.rb +15 -0
  27. data/lib/DhanHQ/contracts/historical_data_contract.rb +28 -0
  28. data/lib/DhanHQ/contracts/margin_calculator_contract.rb +19 -0
  29. data/lib/DhanHQ/contracts/modify_order_contract copy.rb +100 -0
  30. data/lib/DhanHQ/contracts/modify_order_contract.rb +22 -0
  31. data/lib/DhanHQ/contracts/option_chain_contract.rb +31 -0
  32. data/lib/DhanHQ/contracts/order_contract.rb +102 -0
  33. data/lib/DhanHQ/contracts/place_order_contract.rb +119 -0
  34. data/lib/DhanHQ/contracts/position_conversion_contract.rb +24 -0
  35. data/lib/DhanHQ/contracts/slice_order_contract.rb +111 -0
  36. data/lib/DhanHQ/core/base_api.rb +105 -0
  37. data/lib/DhanHQ/core/base_model.rb +266 -0
  38. data/lib/DhanHQ/core/base_resource.rb +50 -0
  39. data/lib/DhanHQ/core/error_handler.rb +19 -0
  40. data/lib/DhanHQ/error_object.rb +49 -0
  41. data/lib/DhanHQ/errors.rb +45 -0
  42. data/lib/DhanHQ/helpers/api_helper.rb +17 -0
  43. data/lib/DhanHQ/helpers/attribute_helper.rb +72 -0
  44. data/lib/DhanHQ/helpers/model_helper.rb +7 -0
  45. data/lib/DhanHQ/helpers/request_helper.rb +69 -0
  46. data/lib/DhanHQ/helpers/response_helper.rb +98 -0
  47. data/lib/DhanHQ/helpers/validation_helper.rb +36 -0
  48. data/lib/DhanHQ/json_loader.rb +23 -0
  49. data/lib/DhanHQ/models/edis.rb +58 -0
  50. data/lib/DhanHQ/models/forever_order.rb +85 -0
  51. data/lib/DhanHQ/models/funds.rb +50 -0
  52. data/lib/DhanHQ/models/historical_data.rb +77 -0
  53. data/lib/DhanHQ/models/holding.rb +56 -0
  54. data/lib/DhanHQ/models/kill_switch.rb +49 -0
  55. data/lib/DhanHQ/models/ledger_entry.rb +60 -0
  56. data/lib/DhanHQ/models/margin.rb +54 -0
  57. data/lib/DhanHQ/models/market_feed.rb +41 -0
  58. data/lib/DhanHQ/models/option_chain.rb +79 -0
  59. data/lib/DhanHQ/models/order.rb +239 -0
  60. data/lib/DhanHQ/models/position.rb +60 -0
  61. data/lib/DhanHQ/models/profile.rb +44 -0
  62. data/lib/DhanHQ/models/super_order.rb +69 -0
  63. data/lib/DhanHQ/models/trade.rb +79 -0
  64. data/lib/DhanHQ/rate_limiter.rb +107 -0
  65. data/lib/DhanHQ/requests/optionchain/nifty.json +5 -0
  66. data/lib/DhanHQ/requests/optionchain/nifty_expiries.json +4 -0
  67. data/lib/DhanHQ/requests/orders/create.json +0 -0
  68. data/lib/DhanHQ/resources/edis.rb +44 -0
  69. data/lib/DhanHQ/resources/forever_orders.rb +53 -0
  70. data/lib/DhanHQ/resources/funds.rb +21 -0
  71. data/lib/DhanHQ/resources/historical_data.rb +34 -0
  72. data/lib/DhanHQ/resources/holdings.rb +21 -0
  73. data/lib/DhanHQ/resources/kill_switch.rb +21 -0
  74. data/lib/DhanHQ/resources/margin_calculator.rb +22 -0
  75. data/lib/DhanHQ/resources/market_feed.rb +56 -0
  76. data/lib/DhanHQ/resources/option_chain.rb +31 -0
  77. data/lib/DhanHQ/resources/orders.rb +70 -0
  78. data/lib/DhanHQ/resources/positions.rb +29 -0
  79. data/lib/DhanHQ/resources/profile.rb +25 -0
  80. data/lib/DhanHQ/resources/statements.rb +42 -0
  81. data/lib/DhanHQ/resources/super_orders.rb +46 -0
  82. data/lib/DhanHQ/resources/trades.rb +23 -0
  83. data/lib/DhanHQ/version.rb +6 -0
  84. data/lib/DhanHQ/ws/client.rb +182 -0
  85. data/lib/DhanHQ/ws/cmd_bus.rb +38 -0
  86. data/lib/DhanHQ/ws/connection.rb +240 -0
  87. data/lib/DhanHQ/ws/decoder.rb +83 -0
  88. data/lib/DhanHQ/ws/errors.rb +0 -0
  89. data/lib/DhanHQ/ws/orders/client.rb +59 -0
  90. data/lib/DhanHQ/ws/orders/connection.rb +148 -0
  91. data/lib/DhanHQ/ws/orders.rb +13 -0
  92. data/lib/DhanHQ/ws/packets/depth_delta_packet.rb +20 -0
  93. data/lib/DhanHQ/ws/packets/disconnect_packet.rb +15 -0
  94. data/lib/DhanHQ/ws/packets/full_packet.rb +40 -0
  95. data/lib/DhanHQ/ws/packets/header.rb +23 -0
  96. data/lib/DhanHQ/ws/packets/index_packet.rb +14 -0
  97. data/lib/DhanHQ/ws/packets/market_depth_level.rb +21 -0
  98. data/lib/DhanHQ/ws/packets/market_status_packet.rb +14 -0
  99. data/lib/DhanHQ/ws/packets/oi_packet.rb +15 -0
  100. data/lib/DhanHQ/ws/packets/prev_close_packet.rb +16 -0
  101. data/lib/DhanHQ/ws/packets/quote_packet.rb +26 -0
  102. data/lib/DhanHQ/ws/packets/ticker_packet.rb +16 -0
  103. data/lib/DhanHQ/ws/registry.rb +46 -0
  104. data/lib/DhanHQ/ws/segments.rb +75 -0
  105. data/lib/DhanHQ/ws/singleton_lock.rb +54 -0
  106. data/lib/DhanHQ/ws/sub_state.rb +59 -0
  107. data/lib/DhanHQ/ws/websocket_packet_parser.rb +165 -0
  108. data/lib/DhanHQ/ws.rb +37 -0
  109. data/lib/DhanHQ.rb +135 -0
  110. data/lib/ta/technical_analysis.rb +405 -0
  111. data/sig/DhanHQ.rbs +4 -0
  112. data/watchlist.csv +3 -0
  113. metadata +283 -0
data/diagram.md ADDED
@@ -0,0 +1,34 @@
1
+
2
+ ```mermaid
3
+ flowchart LR
4
+ subgraph DhanHQ_Gem["dhanhq-client (gem)"]
5
+ A[WS::Client] --> B[WS::Connection]
6
+ B --> C{Dhan Feed<br/>wss://api-feed.dhan.co}
7
+ B --> D[WebsocketPacketParser + Packets]
8
+ D --> E[Decoder -> normalized tick]
9
+ A -->|:tick| E
10
+ end
11
+
12
+ subgraph Rails_API["Rails API app"]
13
+ E --> F[WSSupervisor]
14
+ F --> G[TickCache (latest LTP)]
15
+ F --> H[TickBus]
16
+ F --> I[CandleAggregator (5m)]
17
+ I -->|bar close| J[Strategy::SupertrendOptionLong]
18
+ F --> K[CloseStrikesManager (ATM ±1 manager)]
19
+ J --> L[ExecutionIntent + Signal (DB)]
20
+ J --> M[Execution::DhanRouter]
21
+ M -->|try SuperOrder| N((Dhan Super Orders))
22
+ M -->|fallback| O[Market BUY + LocalTrailing]
23
+ O --> G
24
+ subgraph Backoffice
25
+ P[SyncLoop] --> Q[(Positions/Holdings DB)]
26
+ end
27
+ end
28
+
29
+ C -.binary frames.-> D
30
+ K -->|subscribe CE/PE| A
31
+ M -->|REST| N
32
+ P -->|REST| Q
33
+
34
+ ```
@@ -0,0 +1,304 @@
1
+ # Rails Integration Guide for DhanHQ
2
+
3
+ This guide demonstrates how to wire the `DhanHQ` Ruby client into a Rails
4
+ application so you can automate trading flows, fetch data, and stream market
5
+ updates via WebSockets. The examples assume Rails 7+, but the concepts apply to
6
+ older versions as well.
7
+
8
+ ## 1. Install the gem
9
+
10
+ Add the gem to your Rails application's `Gemfile` and bundle:
11
+
12
+ ```ruby
13
+ # Gemfile
14
+ gem 'DhanHQ', git: 'https://github.com/shubhamtaywade82/dhanhq-client.git', branch: 'main'
15
+ ```
16
+
17
+ ```bash
18
+ bundle install
19
+ ```
20
+
21
+ If you package the gem privately you can also point to a released version from
22
+ RubyGems.
23
+
24
+ ## 2. Configure credentials & initializer
25
+
26
+ Store the Dhan client id and access token using Rails credentials or ENV
27
+ variables. These two keys are **required** for `DhanHQ.configure_with_env` to
28
+ boot successfully:
29
+
30
+ | Variable | Description |
31
+ | --- | --- |
32
+ | `CLIENT_ID` | Dhan trading client id for the account you want to trade with. |
33
+ | `ACCESS_TOKEN` | REST/WebSocket access token (regenerate via the Dhan console or APIs). |
34
+
35
+ ```bash
36
+ bin/rails credentials:edit
37
+ ```
38
+
39
+ ```yaml
40
+ dhanhq:
41
+ client_id: "1001234567"
42
+ access_token: "eyJhbGciOi..."
43
+ log_level: "info" # optional
44
+ base_url: "https://api.dhan.co/v2" # optional
45
+ ws_order_url: "wss://api-order-update.dhan.co" # optional
46
+ ws_user_type: "SELF" # optional (SELF or PARTNER)
47
+ partner_id: "your-partner-id" # optional when ws_user_type: PARTNER
48
+ partner_secret: "your-partner-secret" # optional when ws_user_type: PARTNER
49
+ ```
50
+
51
+ Create an initializer so your app boots with the correct configuration via
52
+ environment variables (Rails credentials can be copied into ENV on boot):
53
+
54
+ ```ruby
55
+ # config/initializers/dhanhq.rb
56
+ require 'DhanHQ'
57
+
58
+ if (creds = Rails.application.credentials.dig(:dhanhq))
59
+ ENV['CLIENT_ID'] ||= creds[:client_id]
60
+ ENV['ACCESS_TOKEN'] ||= creds[:access_token]
61
+ ENV['DHAN_LOG_LEVEL'] ||= creds[:log_level]&.upcase
62
+ ENV['DHAN_BASE_URL'] ||= creds[:base_url]
63
+ ENV['DHAN_WS_ORDER_URL'] ||= creds[:ws_order_url]
64
+ ENV['DHAN_WS_USER_TYPE'] ||= creds[:ws_user_type]
65
+ ENV['DHAN_PARTNER_ID'] ||= creds[:partner_id]
66
+ ENV['DHAN_PARTNER_SECRET'] ||= creds[:partner_secret]
67
+ end
68
+
69
+ # fall back to traditional ENV variables when credentials are not defined
70
+ ENV['CLIENT_ID'] ||= ENV.fetch('DHAN_CLIENT_ID', nil)
71
+ ENV['ACCESS_TOKEN'] ||= ENV.fetch('DHAN_ACCESS_TOKEN', nil)
72
+
73
+ DhanHQ.configure_with_env
74
+
75
+ log_level = (ENV['DHAN_LOG_LEVEL'] || 'INFO').upcase
76
+ DhanHQ.logger.level = Logger.const_get(log_level)
77
+ ```
78
+
79
+ **Optional configuration**
80
+
81
+ Populate any of the following keys when you need to override the gem defaults
82
+ or enable partner streaming flows:
83
+
84
+ | Variable | Purpose |
85
+ | --- | --- |
86
+ | `DHAN_LOG_LEVEL` | Change the logger level (`INFO` default). |
87
+ | `DHAN_BASE_URL` | Target a different REST API host. |
88
+ | `DHAN_WS_VERSION` | Pin WebSocket connections to a specific API version. |
89
+ | `DHAN_WS_ORDER_URL` | Override the order update WebSocket endpoint. |
90
+ | `DHAN_WS_USER_TYPE` | Switch between `SELF` and `PARTNER` WebSocket auth. |
91
+ | `DHAN_PARTNER_ID` / `DHAN_PARTNER_SECRET` | Required when `DHAN_WS_USER_TYPE=PARTNER`. |
92
+
93
+ Set the variables in ENV (or in credentials copied to ENV) **before** the
94
+ initializer calls `DhanHQ.configure_with_env`.
95
+
96
+ ## 3. Build service objects for REST flows
97
+
98
+ Wrap trading actions in plain-old Ruby objects so controllers and jobs stay thin:
99
+
100
+ ```ruby
101
+ # app/services/dhan/orders/place_order.rb
102
+ module Dhan
103
+ module Orders
104
+ class PlaceOrder
105
+ def initialize(params)
106
+ @params = params
107
+ end
108
+
109
+ def call
110
+ order = DhanHQ::Models::Order.new(@params)
111
+ order.save
112
+ order
113
+ rescue DhanHQ::Error => e
114
+ Rails.logger.error("Dhan order failed: #{e.message}")
115
+ raise
116
+ end
117
+ end
118
+ end
119
+ end
120
+ ```
121
+
122
+ Use the service from controllers, background jobs, or scheduled tasks:
123
+
124
+ ```ruby
125
+ class OrdersController < ApplicationController
126
+ def create
127
+ order = Dhan::Orders::PlaceOrder.new(order_params).call
128
+ render json: order.attributes
129
+ end
130
+
131
+ private
132
+
133
+ def order_params
134
+ params.require(:order).permit(:transaction_type, :exchange_segment, :product_type,
135
+ :order_type, :validity, :security_id, :quantity,
136
+ :price, :trigger_price, :correlation_id)
137
+ end
138
+ end
139
+ ```
140
+
141
+ The gem exposes models for positions, holdings, trades, funds, option chains,
142
+ historical bars, etc. Instantiate them the same way (`Model.all`, `.find`,
143
+ `.where`, `#save`).
144
+
145
+ ## 4. Centralise error handling
146
+
147
+ Wrap the gem's exceptions in a concern so Rails controllers and jobs return
148
+ consistent responses:
149
+
150
+ ```ruby
151
+ # app/controllers/concerns/handles_dhan_errors.rb
152
+ module HandlesDhanErrors
153
+ extend ActiveSupport::Concern
154
+
155
+ included do
156
+ rescue_from DhanHQ::Error, with: :render_dhan_error
157
+ end
158
+
159
+ private
160
+
161
+ def render_dhan_error(error)
162
+ Rails.logger.warn("Dhan API error: #{error.message}")
163
+ render json: { error: error.message, details: error.details }, status: :unprocessable_entity
164
+ end
165
+ end
166
+ ```
167
+
168
+ Include the concern in API controllers or base controllers as needed.
169
+
170
+ ## 5. Consume market data via WebSockets
171
+
172
+ The gem ships with an EventMachine-based client that can run inside your Rails
173
+ processes. The simplest approach is to start a dedicated process (e.g. a
174
+ Sidekiq worker or a Rails runner) that keeps the connection alive and publishes
175
+ ticks through ActionCable, Redis, or a database.
176
+
177
+ ```ruby
178
+ # app/workers/dhan/market_feed_worker.rb
179
+ class Dhan::MarketFeedWorker
180
+ include Sidekiq::Worker
181
+
182
+ def perform(mode = :quote, securities = [])
183
+ client = DhanHQ::WS::Client.new(mode: mode.to_sym)
184
+
185
+ client.on(:open) { Rails.logger.info('Dhan WS connected') }
186
+ client.on(:close) { Rails.logger.warn('Dhan WS closed; worker will retry') }
187
+ client.on(:error) { |err| Rails.logger.error("Dhan WS error: #{err}") }
188
+
189
+ client.on(:tick) do |tick|
190
+ ActionCable.server.broadcast('market_feed', tick)
191
+ end
192
+
193
+ client.start
194
+ client.subscribe(securities) if securities.any?
195
+ client.wait! # blocks the worker thread while EventMachine runs
196
+ end
197
+ end
198
+ ```
199
+
200
+ Schedule the worker from `sidekiq.yml`, a scheduler, or run on demand:
201
+
202
+ ```bash
203
+ bundle exec sidekiq -q default
204
+ bundle exec sidekiq-client push '{"class":"Dhan::MarketFeedWorker","args":["quote",[["NSE_EQ","1333"]]]}'
205
+ ```
206
+
207
+ Define an ActionCable channel so browsers receive updates in real time:
208
+
209
+ ```ruby
210
+ # app/channels/market_feed_channel.rb
211
+ class MarketFeedChannel < ApplicationCable::Channel
212
+ def subscribed
213
+ stream_from 'market_feed'
214
+ end
215
+ end
216
+ ```
217
+
218
+ ## 6. Stream order updates
219
+
220
+ Use the order-update WebSocket endpoint (configure `ws_order_url` and
221
+ `ws_user_type`) and process callbacks similarly:
222
+
223
+ ```ruby
224
+ # app/workers/dhan/order_updates_worker.rb
225
+ class Dhan::OrderUpdatesWorker
226
+ include Sidekiq::Worker
227
+
228
+ def perform
229
+ client = DhanHQ::WS::Client.new(kind: :order_updates)
230
+
231
+ client.on(:order_update) do |payload|
232
+ OrderStatusUpdater.call(payload)
233
+ end
234
+
235
+ client.on(:error) { |err| Rails.logger.error("Dhan order WS error: #{err}") }
236
+
237
+ client.start
238
+ client.wait!
239
+ end
240
+ end
241
+ ```
242
+
243
+ Inside `OrderStatusUpdater` you can reconcile the payload with your local order
244
+ records, notify users via ActionCable or email, etc.
245
+
246
+ ## 7. Schedule automation & backfills
247
+
248
+ Leverage ActiveJob, Sidekiq, or any scheduler (Whenever, Clockwork, Cron) to run
249
+ recurring jobs that pull data or enforce trading rules:
250
+
251
+ ```ruby
252
+ # app/jobs/dhan/refresh_positions_job.rb
253
+ class Dhan::RefreshPositionsJob < ApplicationJob
254
+ queue_as :default
255
+
256
+ def perform
257
+ positions = DhanHQ::Models::Position.all
258
+ positions.each { |position| PositionSync.call(position) }
259
+ end
260
+ end
261
+ ```
262
+
263
+ Trigger from cron using `whenever`:
264
+
265
+ ```ruby
266
+ # config/schedule.rb
267
+ every 5.minutes do
268
+ runner 'Dhan::RefreshPositionsJob.perform_later'
269
+ end
270
+ ```
271
+
272
+ ## 8. Testing helpers
273
+
274
+ For tests, stub HTTP requests using WebMock or VCR. The client delegates all
275
+ REST calls through Faraday, so you can match on URLs under
276
+ `https://api.dhan.co/v2`. For WebSockets, inject a fake transport by stubbing
277
+ `DhanHQ::WS::Connection`.
278
+
279
+ ```ruby
280
+ # spec/support/dhanhq.rb
281
+ RSpec.configure do |config|
282
+ config.before(:each, dhan: true) do
283
+ stub_request(:post, %r{https://api\.dhan\.co/v2/orders}).to_return(
284
+ status: 200,
285
+ body: { status: 'success', order_id: '123' }.to_json,
286
+ headers: { 'Content-Type' => 'application/json' }
287
+ )
288
+ end
289
+ end
290
+ ```
291
+
292
+ ## 9. Deployment notes
293
+
294
+ - Run WebSocket consumers outside the web dynos/processes so Puma/Passenger
295
+ threads are not blocked.
296
+ - Ensure the `access_token` is refreshed before expiry; wire a cron job or
297
+ admin panel action that updates the stored token and restarts workers.
298
+ - Monitor the gem's logger output for `429` or `503` responses to adjust retry
299
+ logic.
300
+
301
+ ## 10. Further reading
302
+
303
+ - [GUIDE.md](../GUIDE.md) — in-depth overview of the gem's models and APIs.
304
+ - [README.md](../README.md) — quick start, features, and WebSocket usage.
data/exe/DhanHQ ADDED
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "DhanHQ"
@@ -0,0 +1,116 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "faraday"
4
+ require "json"
5
+ require "active_support/core_ext/hash/indifferent_access"
6
+ require_relative "errors"
7
+ require_relative "rate_limiter"
8
+
9
+ module DhanHQ
10
+ # The `Client` class provides a wrapper for HTTP requests to interact with the DhanHQ API.
11
+ # Responsible for:
12
+ # - Establishing and managing the HTTP connection
13
+ # - Handling authentication and request headers
14
+ # - Sending raw HTTP requests (`GET`, `POST`, `PUT`, `DELETE`)
15
+ # - Parsing JSON responses into HashWithIndifferentAccess
16
+ # - Handling standard HTTP errors (400, 401, 403, etc.)
17
+ # - Implementing **Rate Limiting** to avoid hitting API limits.
18
+ #
19
+ # It supports `GET`, `POST`, `PUT`, and `DELETE` requests with JSON encoding/decoding.
20
+ # Credentials (`access_token`, `client_id`) are automatically added to each request.
21
+ #
22
+ # @see https://dhanhq.co/docs/v2/ DhanHQ API Documentation
23
+ class Client
24
+ include DhanHQ::RequestHelper
25
+ include DhanHQ::ResponseHelper
26
+
27
+ # The Faraday connection object used for HTTP requests.
28
+ #
29
+ # @return [Faraday::Connection] The connection instance used for API requests.
30
+ attr_reader :connection
31
+
32
+ # Initializes a new DhanHQ Client instance with a Faraday connection.
33
+ #
34
+ # @example Create a new client:
35
+ # client = DhanHQ::Client.new(api_type: :order_api)
36
+ #
37
+ # @param api_type [Symbol] Type of API (`:order_api`, `:data_api`, `:non_trading_api`)
38
+ # @return [DhanHQ::Client] A new client instance.
39
+ def initialize(api_type:)
40
+ DhanHQ.configure_with_env if ENV.fetch("CLIENT_ID", nil)
41
+ @rate_limiter = RateLimiter.new(api_type)
42
+
43
+ raise "RateLimiter initialization failed" unless @rate_limiter
44
+
45
+ @connection = Faraday.new(url: DhanHQ.configuration.base_url) do |conn|
46
+ conn.request :json, parser_options: { symbolize_names: true }
47
+ conn.response :json, content_type: /\bjson$/
48
+ conn.response :logger if ENV["DHAN_DEBUG"] == "true"
49
+ conn.adapter Faraday.default_adapter
50
+ end
51
+ end
52
+
53
+ # Sends an HTTP request to the API.
54
+ #
55
+ # @param method [Symbol] The HTTP method (`:get`, `:post`, `:put`, `:delete`)
56
+ # @param path [String] The API endpoint path.
57
+ # @param payload [Hash] The request parameters or body.
58
+ # @return [HashWithIndifferentAccess, Array<HashWithIndifferentAccess>] Parsed JSON response.
59
+ # @raise [DhanHQ::Error] If an HTTP error occurs.
60
+ def request(method, path, payload)
61
+ @rate_limiter.throttle! # **Ensure we don't hit rate limit before calling API**
62
+
63
+ response = connection.send(method) do |req|
64
+ req.url path
65
+ req.headers.merge!(build_headers(path))
66
+ prepare_payload(req, payload, method)
67
+ end
68
+
69
+ handle_response(response)
70
+ end
71
+
72
+ # Convenience wrapper for issuing a GET request.
73
+ #
74
+ # @param path [String] The API endpoint path.
75
+ # @param params [Hash] Query parameters for the request.
76
+ # @return [HashWithIndifferentAccess, Array<HashWithIndifferentAccess>]
77
+ # Parsed JSON response.
78
+ # @see #request
79
+ def get(path, params = {})
80
+ request(:get, path, params)
81
+ end
82
+
83
+ # Convenience wrapper for issuing a POST request.
84
+ #
85
+ # @param path [String] The API endpoint path.
86
+ # @param params [Hash] JSON payload for the request.
87
+ # @return [HashWithIndifferentAccess, Array<HashWithIndifferentAccess>]
88
+ # Parsed JSON response.
89
+ # @see #request
90
+ def post(path, params = {})
91
+ request(:post, path, params)
92
+ end
93
+
94
+ # Convenience wrapper for issuing a PUT request.
95
+ #
96
+ # @param path [String] The API endpoint path.
97
+ # @param params [Hash] JSON payload for the request.
98
+ # @return [HashWithIndifferentAccess, Array<HashWithIndifferentAccess>]
99
+ # Parsed JSON response.
100
+ # @see #request
101
+ def put(path, params = {})
102
+ request(:put, path, params)
103
+ end
104
+
105
+ # Convenience wrapper for issuing a DELETE request.
106
+ #
107
+ # @param path [String] The API endpoint path.
108
+ # @param params [Hash] Optional request payload (rare for DELETE).
109
+ # @return [HashWithIndifferentAccess, Array<HashWithIndifferentAccess>]
110
+ # Parsed JSON response.
111
+ # @see #request
112
+ def delete(path, params = {})
113
+ request(:delete, path, params)
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "logger"
4
+
5
+ module DhanHQ
6
+ class << self
7
+ # keep whatever you already have; add these if missing:
8
+ attr_accessor :client_id, :access_token, :base_url, :ws_version
9
+
10
+ # default logger so calls like DhanHQ.logger&.info never explode
11
+ def logger
12
+ @logger ||= Logger.new($stdout, level: Logger::INFO)
13
+ end
14
+
15
+ attr_writer :logger
16
+
17
+ # same API style as your README
18
+ def configure
19
+ yield self
20
+ # ensure a logger is present even if user didn’t set one
21
+ self.logger ||= Logger.new($stdout, level: Logger::INFO)
22
+ end
23
+
24
+ # if you support env bootstrap
25
+ def configure_with_env
26
+ self.client_id = ENV.fetch("CLIENT_ID", nil)
27
+ self.access_token = ENV.fetch("ACCESS_TOKEN", nil)
28
+ self.base_url = ENV.fetch("DHAN_BASE_URL", "https://api.dhan.co/v2")
29
+ self.ws_version = ENV.fetch("DHAN_WS_VERSION", 2).to_i
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DhanHQ
4
+ # The `Configuration` class manages API credentials and settings.
5
+ #
6
+ # Use this class to set the required `access_token` and `client_id`, as well as optional
7
+ # settings such as the base URL and CSV URLs.
8
+ #
9
+ # @see https://dhanhq.co/docs/v2/ DhanHQ API Documentation
10
+ class Configuration
11
+ # Default REST API host used when the base URL is not overridden.
12
+ #
13
+ # @return [String]
14
+ BASE_URL = "https://api.dhan.co/v2"
15
+ # The client ID for API authentication.
16
+ # @return [String, nil] The client ID or `nil` if not set.
17
+ attr_accessor :client_id
18
+
19
+ # The access token for API authentication.
20
+ # @return [String, nil] The access token or `nil` if not set.
21
+ attr_accessor :access_token
22
+
23
+ # The base URL for API requests.
24
+ # @return [String] The base URL for the DhanHQ API.
25
+ attr_accessor :base_url
26
+
27
+ # URL for the compact CSV format of instruments.
28
+ # @return [String] URL for compact CSV.
29
+ attr_accessor :compact_csv_url
30
+
31
+ # URL for the detailed CSV format of instruments.
32
+ # @return [String] URL for detailed CSV.
33
+ attr_accessor :detailed_csv_url
34
+
35
+ # Websocket API version.
36
+ # @return [Integer]
37
+ attr_accessor :ws_version
38
+
39
+ # Websocket order updates endpoint.
40
+ # @return [String]
41
+ attr_accessor :ws_order_url
42
+
43
+ # Websocket user type for order updates.
44
+ # @return [String] "SELF" or "PARTNER".
45
+ attr_accessor :ws_user_type
46
+
47
+ # Partner ID for order updates when `ws_user_type` is "PARTNER".
48
+ # @return [String, nil]
49
+ attr_accessor :partner_id
50
+
51
+ # Partner secret for order updates when `ws_user_type` is "PARTNER".
52
+ # @return [String, nil]
53
+ attr_accessor :partner_secret
54
+
55
+ # Initializes a new configuration instance with default values.
56
+ #
57
+ # @example
58
+ # config = DhanHQ::Configuration.new
59
+ # config.client_id = "your_client_id"
60
+ # config.access_token = "your_access_token"
61
+ def initialize
62
+ @client_id = ENV.fetch("CLIENT_ID", nil)
63
+ @access_token = ENV.fetch("ACCESS_TOKEN", nil)
64
+ @base_url = ENV.fetch("DHAN_BASE_URL", "https://api.dhan.co/v2")
65
+ @ws_version = ENV.fetch("DHAN_WS_VERSION", 2).to_i
66
+ @ws_order_url = ENV.fetch("DHAN_WS_ORDER_URL", "wss://api-order-update.dhan.co")
67
+ @ws_user_type = ENV.fetch("DHAN_WS_USER_TYPE", "SELF")
68
+ @partner_id = ENV.fetch("DHAN_PARTNER_ID", nil)
69
+ @partner_secret = ENV.fetch("DHAN_PARTNER_SECRET", nil)
70
+ end
71
+ end
72
+ end