DhanHQ 2.6.1 → 2.6.3
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/.rubocop.yml +15 -3
- data/ARCHITECTURE.md +113 -0
- data/CHANGELOG.md +55 -0
- data/README.md +2 -0
- data/Rakefile +3 -1
- data/docs/API_VERIFICATION.md +10 -8
- data/docs/ENDPOINTS_AND_SANDBOX.md +115 -0
- data/lib/DhanHQ/auth.rb +2 -2
- data/lib/DhanHQ/client.rb +72 -51
- data/lib/DhanHQ/configuration.rb +45 -11
- data/lib/DhanHQ/constants.rb +68 -4
- data/lib/DhanHQ/contracts/alert_order_contract.rb +23 -16
- data/lib/DhanHQ/contracts/expired_options_data_contract.rb +4 -2
- data/lib/DhanHQ/contracts/forever_order_contract.rb +55 -0
- data/lib/DhanHQ/contracts/historical_data_contract.rb +17 -19
- data/lib/DhanHQ/contracts/intraday_historical_data_contract.rb +12 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +19 -17
- data/lib/DhanHQ/contracts/market_feed_contract.rb +42 -0
- data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +8 -5
- data/lib/DhanHQ/contracts/option_chain_contract.rb +17 -19
- data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +1 -1
- data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -10
- data/lib/DhanHQ/core/auth_api.rb +1 -1
- data/lib/DhanHQ/core/base_api.rb +10 -9
- data/lib/DhanHQ/core/base_model.rb +4 -1
- data/lib/DhanHQ/core/error_handler.rb +2 -2
- data/lib/DhanHQ/errors.rb +14 -2
- data/lib/DhanHQ/helpers/request_helper.rb +27 -5
- data/lib/DhanHQ/helpers/response_helper.rb +48 -19
- data/lib/DhanHQ/helpers/validation_helper.rb +4 -2
- data/lib/DhanHQ/models/alert_order.rb +6 -2
- data/lib/DhanHQ/models/edis.rb +20 -13
- data/lib/DhanHQ/models/expired_options_data.rb +54 -44
- data/lib/DhanHQ/models/forever_order.rb +17 -7
- data/lib/DhanHQ/models/historical_data.rb +40 -6
- data/lib/DhanHQ/models/instrument_helpers.rb +2 -1
- data/lib/DhanHQ/models/margin.rb +62 -82
- data/lib/DhanHQ/models/market_feed.rb +14 -3
- data/lib/DhanHQ/models/option_chain.rb +50 -150
- data/lib/DhanHQ/models/order.rb +19 -4
- data/lib/DhanHQ/models/super_order.rb +2 -2
- data/lib/DhanHQ/resources/alert_orders.rb +1 -1
- data/lib/DhanHQ/resources/edis.rb +4 -3
- data/lib/DhanHQ/resources/instruments.rb +3 -2
- data/lib/DhanHQ/resources/ip_setup.rb +4 -1
- data/lib/DhanHQ/resources/kill_switch.rb +7 -1
- data/lib/DhanHQ/resources/orders.rb +1 -1
- data/lib/DhanHQ/resources/super_orders.rb +8 -2
- data/lib/DhanHQ/resources/trader_control.rb +13 -4
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/base_connection.rb +1 -1
- data/lib/DhanHQ/ws/client.rb +2 -1
- data/lib/DhanHQ/ws/market_depth/client.rb +16 -8
- data/lib/dhan_hq.rb +37 -32
- data/lib/ta/indicators.rb +15 -18
- metadata +7 -9
- data/CODE_REVIEW_ISSUES.md +0 -397
- data/FIXES_APPLIED.md +0 -373
- data/RELEASING.md +0 -60
- data/REVIEW_SUMMARY.md +0 -120
- data/VERSION_UPDATE.md +0 -82
- data/diagram.md +0 -34
- data/docs/ARCHIVE_README.md +0 -784
data/lib/DhanHQ/models/order.rb
CHANGED
|
@@ -370,9 +370,17 @@ module DhanHQ
|
|
|
370
370
|
#
|
|
371
371
|
# @raise [RuntimeError] If order ID is missing
|
|
372
372
|
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
373
|
+
# @raise [DhanHQ::ModificationLimitError] If this instance has already been modified 25 times (Dhan API cap)
|
|
374
|
+
# @note Count is per Order instance in this process; a fresh find() resets it.
|
|
373
375
|
def modify(new_params)
|
|
374
376
|
raise "Order ID is required to modify an order" unless id
|
|
375
377
|
|
|
378
|
+
count = @modification_count || 0
|
|
379
|
+
if count >= Constants::RateLimit::ORDER_MODIFICATIONS_PER_ORDER
|
|
380
|
+
raise ModificationLimitError,
|
|
381
|
+
"Order modification limit reached (#{Constants::RateLimit::ORDER_MODIFICATIONS_PER_ORDER} per order)"
|
|
382
|
+
end
|
|
383
|
+
|
|
376
384
|
warn_invalid_state if order_status_invalid_for_modification?
|
|
377
385
|
|
|
378
386
|
filtered_payload = prepare_modify_payload(new_params)
|
|
@@ -384,6 +392,7 @@ module DhanHQ
|
|
|
384
392
|
|
|
385
393
|
return DhanHQ::ErrorObject.new(response) unless success_response?(response)
|
|
386
394
|
|
|
395
|
+
@modification_count = count + 1
|
|
387
396
|
@attributes.merge!(normalize_keys(response))
|
|
388
397
|
assign_attributes
|
|
389
398
|
self
|
|
@@ -543,13 +552,15 @@ module DhanHQ
|
|
|
543
552
|
private
|
|
544
553
|
|
|
545
554
|
def save_new_order
|
|
546
|
-
|
|
555
|
+
slice_attrs = attributes.slice(:transaction_type, :exchange_segment, :security_id, :quantity, :price)
|
|
556
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Placing order: #{slice_attrs.inspect}")
|
|
547
557
|
response = self.class.resource.create(to_request_params)
|
|
548
558
|
handle_api_response(response, success_key: "orderId", context: "[DhanHQ::Models::Order] Order placement")
|
|
549
559
|
end
|
|
550
560
|
|
|
551
561
|
def modify_existing_order
|
|
552
|
-
|
|
562
|
+
slice_attrs = attributes.slice(:price, :quantity, :order_type)
|
|
563
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Modifying order #{id}: #{slice_attrs.inspect}")
|
|
553
564
|
response = self.class.resource.update(id, to_request_params)
|
|
554
565
|
handle_api_response(response, success_key: "orderStatus", context: "[DhanHQ::Models::Order] Order modification")
|
|
555
566
|
end
|
|
@@ -559,7 +570,9 @@ module DhanHQ
|
|
|
559
570
|
end
|
|
560
571
|
|
|
561
572
|
def warn_invalid_state
|
|
562
|
-
DhanHQ.logger&.warn(
|
|
573
|
+
DhanHQ.logger&.warn(
|
|
574
|
+
"[DhanHQ::Models::Order] Attempting to modify order #{id} in #{order_status} state - API will reject"
|
|
575
|
+
)
|
|
563
576
|
end
|
|
564
577
|
|
|
565
578
|
def prepare_modify_payload(new_params)
|
|
@@ -576,7 +589,9 @@ module DhanHQ
|
|
|
576
589
|
|
|
577
590
|
# Don't send trigger_price when it's 0 for non–stop-loss orders (API default; avoids validation noise).
|
|
578
591
|
order_type = filtered_payload[:order_type].to_s
|
|
579
|
-
|
|
592
|
+
trigger_zero = filtered_payload[:trigger_price].to_f.zero?
|
|
593
|
+
drop_trigger = !%w[STOP_LOSS STOP_LOSS_MARKET].include?(order_type) && trigger_zero
|
|
594
|
+
filtered_payload.delete(:trigger_price) if drop_trigger
|
|
580
595
|
|
|
581
596
|
filtered_payload.compact
|
|
582
597
|
end
|
|
@@ -207,9 +207,9 @@ module DhanHQ
|
|
|
207
207
|
# )
|
|
208
208
|
#
|
|
209
209
|
def create(params)
|
|
210
|
-
# Normalize params and auto-inject dhan_client_id from configuration if not provided
|
|
211
210
|
normalized_params = snake_case(params)
|
|
212
|
-
|
|
211
|
+
config = DhanHQ.configuration
|
|
212
|
+
normalized_params[:dhan_client_id] ||= config.client_id if config&.client_id
|
|
213
213
|
response = resource.create(normalized_params)
|
|
214
214
|
return nil unless response.is_a?(Hash) && response["orderId"]
|
|
215
215
|
|
|
@@ -3,11 +3,12 @@
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Resources
|
|
5
5
|
# Resource for EDIS per https://dhanhq.co/docs/v2/edis/
|
|
6
|
-
# GET /edis/tpin, POST /edis/form (body: isin, qty, exchange, segment, bulk),
|
|
7
|
-
# POST /edis/bulkform, GET /edis/inquire/{isin}.
|
|
6
|
+
# GET /edis/tpin (generate T-PIN, 202 Accepted), POST /edis/form (body: isin, qty, exchange, segment, bulk),
|
|
7
|
+
# POST /edis/bulkform, GET /edis/inquire/{isin} (or "ALL").
|
|
8
|
+
# Form response: dhanClientId, edisFormHtml. Inquire response: clientId, isin, totalQty, aprvdQty, status, remarks.
|
|
8
9
|
class Edis < BaseAPI
|
|
9
10
|
API_TYPE = :order_api
|
|
10
|
-
HTTP_PATH = "/edis"
|
|
11
|
+
HTTP_PATH = "/v2/edis"
|
|
11
12
|
|
|
12
13
|
def form(params)
|
|
13
14
|
post("/form", params: params)
|
|
@@ -19,9 +19,10 @@ module DhanHQ
|
|
|
19
19
|
resp = client.connection.get(path)
|
|
20
20
|
if resp.status.between?(300, 399) && resp.headers["location"]
|
|
21
21
|
redirect_url = resp.headers["location"]
|
|
22
|
-
|
|
22
|
+
Faraday.get(redirect_url).body
|
|
23
|
+
else
|
|
24
|
+
resp.body
|
|
23
25
|
end
|
|
24
|
-
resp.body
|
|
25
26
|
end
|
|
26
27
|
end
|
|
27
28
|
end
|
|
@@ -4,9 +4,12 @@ module DhanHQ
|
|
|
4
4
|
module Resources
|
|
5
5
|
# Resource for IP whitelist per API docs: GET /v2/ip/getIP, POST /v2/ip/setIP, PUT /v2/ip/modifyIP.
|
|
6
6
|
# Set/Modify require dhanClientId, ip, ipFlag (PRIMARY | SECONDARY). See dhanhq.co/docs/v2/authentication/#setup-static-ip
|
|
7
|
+
#
|
|
8
|
+
# GET /v2/ip/getIP response: modifyDateSecondary, secondaryIP, modifyDatePrimary, primaryIP
|
|
9
|
+
# (dates are YYYY-MM-DD from which the IP can be modified; IPs are IPv4 or IPv6).
|
|
7
10
|
class IPSetup < BaseAPI
|
|
8
11
|
API_TYPE = :order_api
|
|
9
|
-
HTTP_PATH = "/ip"
|
|
12
|
+
HTTP_PATH = "/v2/ip"
|
|
10
13
|
|
|
11
14
|
def current
|
|
12
15
|
get("/getIP")
|
|
@@ -10,12 +10,18 @@ module DhanHQ
|
|
|
10
10
|
API_TYPE = :order_api
|
|
11
11
|
HTTP_PATH = "/v2/killswitch"
|
|
12
12
|
|
|
13
|
+
KILL_SWITCH_STATUSES = %w[ACTIVATE DEACTIVATE].freeze
|
|
14
|
+
|
|
13
15
|
# Enables or disables the kill switch via query parameter (doc: no body).
|
|
14
16
|
#
|
|
15
17
|
# @param status [String] "ACTIVATE" or "DEACTIVATE"
|
|
16
18
|
# @return [Hash]
|
|
19
|
+
# @raise [DhanHQ::ValidationError] if status is not ACTIVATE or DEACTIVATE
|
|
17
20
|
def update(status)
|
|
18
|
-
|
|
21
|
+
normalized = status.to_s.upcase.strip
|
|
22
|
+
raise DhanHQ::ValidationError, "killSwitchStatus must be one of: #{KILL_SWITCH_STATUSES.join(", ")}" unless KILL_SWITCH_STATUSES.include?(normalized)
|
|
23
|
+
|
|
24
|
+
query = "?killSwitchStatus=#{CGI.escape(normalized)}"
|
|
19
25
|
handle_response(client.post(build_path(query), {}))
|
|
20
26
|
end
|
|
21
27
|
|
|
@@ -33,13 +33,19 @@ module DhanHQ
|
|
|
33
33
|
put("/#{order_id}", params: params)
|
|
34
34
|
end
|
|
35
35
|
|
|
36
|
+
SUPER_ORDER_LEGS = %w[ENTRY_LEG STOP_LOSS_LEG TARGET_LEG].freeze
|
|
37
|
+
|
|
36
38
|
# Cancels a specific leg from a super order.
|
|
37
39
|
#
|
|
38
40
|
# @param order_id [String]
|
|
39
|
-
# @param leg_name [String]
|
|
41
|
+
# @param leg_name [String] One of ENTRY_LEG, STOP_LOSS_LEG, TARGET_LEG (per API path enum)
|
|
40
42
|
# @return [Hash]
|
|
43
|
+
# @raise [DhanHQ::ValidationError] if leg_name is not a valid leg
|
|
41
44
|
def cancel(order_id, leg_name)
|
|
42
|
-
|
|
45
|
+
normalized = leg_name.to_s.upcase.strip
|
|
46
|
+
raise DhanHQ::ValidationError, "leg_name must be one of: #{SUPER_ORDER_LEGS.join(", ")}" unless SUPER_ORDER_LEGS.include?(normalized)
|
|
47
|
+
|
|
48
|
+
delete("/#{order_id}/#{normalized}")
|
|
43
49
|
end
|
|
44
50
|
end
|
|
45
51
|
end
|
|
@@ -2,21 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Resources
|
|
5
|
-
#
|
|
5
|
+
# The path /trader-control is not part of the Dhan v2 API (https://dhanhq.co/docs/v2).
|
|
6
|
+
# Trader's Control in the docs is implemented via:
|
|
7
|
+
# - Kill Switch: GET/POST /v2/killswitch → use DhanHQ::Models::KillSwitch or DhanHQ::Resources::KillSwitch
|
|
8
|
+
# - P&L Exit: GET/POST/DELETE /v2/pnlExit → use DhanHQ::Models::PnlExit
|
|
9
|
+
#
|
|
10
|
+
# This class is kept for backward compatibility but raises when used.
|
|
6
11
|
class TraderControl < BaseAPI
|
|
7
12
|
API_TYPE = :order_api
|
|
8
13
|
HTTP_PATH = "/trader-control"
|
|
9
14
|
|
|
15
|
+
MSG = "The /trader-control endpoint is not part of the Dhan v2 API. " \
|
|
16
|
+
"Use DhanHQ::Models::KillSwitch or DhanHQ::Resources::KillSwitch for kill switch " \
|
|
17
|
+
"(GET/POST /v2/killswitch). See https://dhanhq.co/docs/v2"
|
|
18
|
+
|
|
10
19
|
def status
|
|
11
|
-
|
|
20
|
+
raise DhanHQ::Error, MSG
|
|
12
21
|
end
|
|
13
22
|
|
|
14
23
|
def enable
|
|
15
|
-
|
|
24
|
+
raise DhanHQ::Error, MSG
|
|
16
25
|
end
|
|
17
26
|
|
|
18
27
|
def disable
|
|
19
|
-
|
|
28
|
+
raise DhanHQ::Error, MSG
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
end
|
data/lib/DhanHQ/version.rb
CHANGED
data/lib/DhanHQ/ws/client.rb
CHANGED
|
@@ -32,7 +32,8 @@ module DhanHQ
|
|
|
32
32
|
|
|
33
33
|
cid = DhanHQ.configuration.client_id or raise "DhanHQ.client_id not set"
|
|
34
34
|
ver = (DhanHQ.configuration.respond_to?(:ws_version) && DhanHQ.configuration.ws_version) || 2
|
|
35
|
-
|
|
35
|
+
base = url || DhanHQ.configuration.ws_market_feed_url
|
|
36
|
+
@url = base.include?("?") ? base : "#{base}?version=#{ver}&token=#{token}&clientId=#{cid}&authType=2"
|
|
36
37
|
end
|
|
37
38
|
|
|
38
39
|
# Starts the WebSocket connection and event loop.
|
|
@@ -86,11 +86,12 @@ module DhanHQ
|
|
|
86
86
|
cid = config.client_id or raise "DhanHQ.client_id not set"
|
|
87
87
|
depth_level = config.market_depth_level || 20 # Default to 20 level depth
|
|
88
88
|
|
|
89
|
-
if depth_level == 200
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
89
|
+
base = if depth_level == 200
|
|
90
|
+
Constants::Urls::WS_DEPTH_200
|
|
91
|
+
else
|
|
92
|
+
config.ws_market_depth_url
|
|
93
|
+
end
|
|
94
|
+
base.include?("?") ? base : "#{base}?token=#{token}&clientId=#{cid}&authType=2"
|
|
94
95
|
end
|
|
95
96
|
|
|
96
97
|
##
|
|
@@ -163,7 +164,10 @@ module DhanHQ
|
|
|
163
164
|
|
|
164
165
|
send_message(subscription_message)
|
|
165
166
|
@subscriptions[label] = resolution
|
|
166
|
-
DhanHQ.logger&.info(
|
|
167
|
+
DhanHQ.logger&.info(
|
|
168
|
+
"[DhanHQ::WS::MarketDepth] Subscribed to #{resolution[:original_label]} " \
|
|
169
|
+
"(#{resolution[:exchange_segment]}:#{resolution[:security_id]})"
|
|
170
|
+
)
|
|
167
171
|
rescue StandardError => e
|
|
168
172
|
DhanHQ.logger&.error("[DhanHQ::WS::MarketDepth] Subscription error for #{symbol.inspect}: #{e.class} #{e.message}")
|
|
169
173
|
end
|
|
@@ -191,7 +195,10 @@ module DhanHQ
|
|
|
191
195
|
|
|
192
196
|
send_message(unsubscribe_message)
|
|
193
197
|
@subscriptions.delete(label)
|
|
194
|
-
DhanHQ.logger&.info(
|
|
198
|
+
DhanHQ.logger&.info(
|
|
199
|
+
"[DhanHQ::WS::MarketDepth] Unsubscribed from #{security_data[:original_label]} " \
|
|
200
|
+
"(#{security_data[:exchange_segment]}:#{security_data[:security_id]})"
|
|
201
|
+
)
|
|
195
202
|
rescue StandardError => e
|
|
196
203
|
DhanHQ.logger&.error("[DhanHQ::WS::MarketDepth] Unsubscribe error for #{symbol.inspect}: #{e.class} #{e.message}")
|
|
197
204
|
end
|
|
@@ -216,7 +223,8 @@ module DhanHQ
|
|
|
216
223
|
instrument = find_instrument(symbol_code, segment_hint)
|
|
217
224
|
unless instrument
|
|
218
225
|
DhanHQ.logger&.warn(
|
|
219
|
-
"[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code}
|
|
226
|
+
"[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code} " \
|
|
227
|
+
"(segment hint: #{segment_hint || "AUTO"})"
|
|
220
228
|
)
|
|
221
229
|
return nil
|
|
222
230
|
end
|
data/lib/dhan_hq.rb
CHANGED
|
@@ -4,6 +4,7 @@ require "json"
|
|
|
4
4
|
require "logger"
|
|
5
5
|
require "zeitwerk"
|
|
6
6
|
require "dotenv/load"
|
|
7
|
+
require "faraday"
|
|
7
8
|
# Minimal eager requires for backward-compatible constants.
|
|
8
9
|
# These are widely referenced (e.g. `DhanHQ::BaseAPI`) and should not depend on
|
|
9
10
|
# the autoloader being fully configured.
|
|
@@ -11,6 +12,8 @@ require_relative "DhanHQ/helpers/api_helper"
|
|
|
11
12
|
require_relative "DhanHQ/helpers/attribute_helper"
|
|
12
13
|
require_relative "DhanHQ/helpers/validation_helper"
|
|
13
14
|
require_relative "DhanHQ/helpers/request_helper"
|
|
15
|
+
require_relative "DhanHQ/errors"
|
|
16
|
+
require_relative "DhanHQ/version"
|
|
14
17
|
require_relative "DhanHQ/helpers/response_helper"
|
|
15
18
|
require_relative "DhanHQ/core/base_api"
|
|
16
19
|
require_relative "DhanHQ/core/base_model"
|
|
@@ -47,7 +50,7 @@ module DhanHQ
|
|
|
47
50
|
# Default REST API host used when no custom base URL is provided.
|
|
48
51
|
#
|
|
49
52
|
# @return [String]
|
|
50
|
-
BASE_URL =
|
|
53
|
+
BASE_URL = Constants::Urls::REST_API_BASE
|
|
51
54
|
# The current configuration instance.
|
|
52
55
|
#
|
|
53
56
|
# @return [DhanHQ::Configuration, nil] The current configuration or `nil` if not set.
|
|
@@ -89,15 +92,21 @@ module DhanHQ
|
|
|
89
92
|
#
|
|
90
93
|
# @return [void]
|
|
91
94
|
def configure_with_env
|
|
95
|
+
self.configuration = Configuration.new
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Ensures configuration exists, bootstrapping from ENV when nil.
|
|
99
|
+
# Called automatically when building a Client so env-only integrations work without
|
|
100
|
+
# an explicit configure/configure_with_env call. Idempotent when configuration is already set.
|
|
101
|
+
#
|
|
102
|
+
# @return [DhanHQ::Configuration]
|
|
103
|
+
def ensure_configuration!
|
|
92
104
|
self.configuration ||= Configuration.new
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
configuration
|
|
98
|
-
configuration.ws_user_type = ENV.fetch("DHAN_WS_USER_TYPE", configuration.ws_user_type)
|
|
99
|
-
configuration.partner_id = ENV.fetch("DHAN_PARTNER_ID", configuration.partner_id)
|
|
100
|
-
configuration.partner_secret = ENV.fetch("DHAN_PARTNER_SECRET", configuration.partner_secret)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Resets the configuration to nil.
|
|
108
|
+
def reset_configuration!
|
|
109
|
+
self.configuration = nil
|
|
101
110
|
end
|
|
102
111
|
|
|
103
112
|
# Configures the DhanHQ client by fetching credentials from a token endpoint.
|
|
@@ -120,12 +129,12 @@ module DhanHQ
|
|
|
120
129
|
base_url ||= ENV.fetch("DHAN_TOKEN_ENDPOINT_BASE_URL", nil)
|
|
121
130
|
bearer_token ||= ENV.fetch("DHAN_TOKEN_ENDPOINT_BEARER", nil)
|
|
122
131
|
|
|
123
|
-
raise TokenEndpointError, "base_url and bearer_token (or ENV DHAN_TOKEN_ENDPOINT_*) are required" if base_url.to_s.empty? || bearer_token.to_s.empty?
|
|
132
|
+
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?
|
|
124
133
|
|
|
125
134
|
url = "#{base_url.to_s.chomp("/")}/auth/dhan/token"
|
|
126
|
-
conn = Faraday.new(url: url) do |c|
|
|
135
|
+
conn = ::Faraday.new(url: url) do |c|
|
|
127
136
|
c.response :json, content_type: /\bjson$/
|
|
128
|
-
c.adapter Faraday.default_adapter
|
|
137
|
+
c.adapter ::Faraday.default_adapter
|
|
129
138
|
end
|
|
130
139
|
|
|
131
140
|
response = conn.get("") do |req|
|
|
@@ -134,33 +143,17 @@ module DhanHQ
|
|
|
134
143
|
end
|
|
135
144
|
|
|
136
145
|
unless response.success?
|
|
137
|
-
body =
|
|
138
|
-
response.body
|
|
139
|
-
else
|
|
140
|
-
begin
|
|
141
|
-
JSON.parse(response.body.to_s)
|
|
142
|
-
rescue StandardError
|
|
143
|
-
{}
|
|
144
|
-
end
|
|
145
|
-
end
|
|
146
|
+
body = parse_json_body(response.body)
|
|
146
147
|
msg = body["error"] || body["message"] || body["errorMessage"] || response.body.to_s
|
|
147
|
-
raise TokenEndpointError, "Token endpoint returned #{response.status}: #{msg}"
|
|
148
|
+
raise DhanHQ::TokenEndpointError, "Token endpoint returned #{response.status}: #{msg}"
|
|
148
149
|
end
|
|
149
150
|
|
|
150
|
-
data =
|
|
151
|
-
response.body
|
|
152
|
-
else
|
|
153
|
-
begin
|
|
154
|
-
JSON.parse(response.body.to_s)
|
|
155
|
-
rescue StandardError
|
|
156
|
-
{}
|
|
157
|
-
end
|
|
158
|
-
end
|
|
151
|
+
data = parse_json_body(response.body)
|
|
159
152
|
data = data.transform_keys(&:to_s) if data.is_a?(Hash)
|
|
160
153
|
|
|
161
154
|
access_token = data["access_token"] || data[:access_token]
|
|
162
155
|
client_id = data["client_id"] || data[:client_id]
|
|
163
|
-
raise TokenEndpointError, "Token endpoint response missing access_token or client_id" if access_token.to_s.empty? || client_id.to_s.empty?
|
|
156
|
+
raise DhanHQ::TokenEndpointError, "Token endpoint response missing access_token or client_id" if access_token.to_s.empty? || client_id.to_s.empty?
|
|
164
157
|
|
|
165
158
|
self.configuration ||= Configuration.new
|
|
166
159
|
configuration.access_token = access_token.to_s
|
|
@@ -169,5 +162,17 @@ module DhanHQ
|
|
|
169
162
|
configuration.base_url = dhan_base.to_s if dhan_base.to_s != ""
|
|
170
163
|
configuration
|
|
171
164
|
end
|
|
165
|
+
|
|
166
|
+
# @param body [String, Hash] Raw response body
|
|
167
|
+
# @return [Hash] Parsed hash; empty hash on parse failure or empty string
|
|
168
|
+
def parse_json_body(body)
|
|
169
|
+
return {} if body.nil?
|
|
170
|
+
return body if body.is_a?(Hash)
|
|
171
|
+
return {} if body.to_s.strip.empty?
|
|
172
|
+
|
|
173
|
+
JSON.parse(body.to_s)
|
|
174
|
+
rescue StandardError
|
|
175
|
+
{}
|
|
176
|
+
end
|
|
172
177
|
end
|
|
173
178
|
end
|
data/lib/ta/indicators.rb
CHANGED
|
@@ -16,13 +16,12 @@ module TA
|
|
|
16
16
|
|
|
17
17
|
def rsi(series, period)
|
|
18
18
|
if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:RSI)
|
|
19
|
-
|
|
19
|
+
RubyTechnicalAnalysis::RSI.new(series: series, period: period).call
|
|
20
|
+
elsif defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:rsi)
|
|
21
|
+
TechnicalAnalysis.rsi(series, period: period)
|
|
22
|
+
else
|
|
23
|
+
simple_rsi(series, period)
|
|
20
24
|
end
|
|
21
|
-
if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:rsi)
|
|
22
|
-
return TechnicalAnalysis.rsi(series, period: period)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
simple_rsi(series, period)
|
|
26
25
|
end
|
|
27
26
|
|
|
28
27
|
def macd(series, fast, slow, signal)
|
|
@@ -56,24 +55,22 @@ module TA
|
|
|
56
55
|
|
|
57
56
|
def adx(high, low, close, period)
|
|
58
57
|
if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:ADX)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
58
|
+
RubyTechnicalAnalysis::ADX.new(high: high, low: low, close: close, period: period).call
|
|
59
|
+
elsif defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:adx)
|
|
60
|
+
TechnicalAnalysis.adx(high: high, low: low, close: close, period: period)
|
|
61
|
+
else
|
|
62
|
+
simple_adx(high, low, close, period)
|
|
63
63
|
end
|
|
64
|
-
|
|
65
|
-
simple_adx(high, low, close, period)
|
|
66
64
|
end
|
|
67
65
|
|
|
68
66
|
def atr(high, low, close, period)
|
|
69
67
|
if defined?(RubyTechnicalAnalysis) && RubyTechnicalAnalysis.const_defined?(:ATR)
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
RubyTechnicalAnalysis::ATR.new(high: high, low: low, close: close, period: period).call
|
|
69
|
+
elsif defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:atr)
|
|
70
|
+
TechnicalAnalysis.atr(high: high, low: low, close: close, period: period)
|
|
71
|
+
else
|
|
72
|
+
simple_atr(high, low, close, period)
|
|
74
73
|
end
|
|
75
|
-
|
|
76
|
-
simple_atr(high, low, close, period)
|
|
77
74
|
end
|
|
78
75
|
|
|
79
76
|
def simple_rsi(series, period)
|
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: 2.6.
|
|
4
|
+
version: 2.6.3
|
|
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-03-
|
|
11
|
+
date: 2026-03-14 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -191,29 +191,24 @@ files:
|
|
|
191
191
|
- ".rspec"
|
|
192
192
|
- ".rubocop.yml"
|
|
193
193
|
- ".rubocop_todo.yml"
|
|
194
|
+
- ARCHITECTURE.md
|
|
194
195
|
- CHANGELOG.md
|
|
195
196
|
- CODE_OF_CONDUCT.md
|
|
196
|
-
- CODE_REVIEW_ISSUES.md
|
|
197
|
-
- FIXES_APPLIED.md
|
|
198
197
|
- GUIDE.md
|
|
199
198
|
- LICENSE.txt
|
|
200
199
|
- README.md
|
|
201
|
-
- RELEASING.md
|
|
202
|
-
- REVIEW_SUMMARY.md
|
|
203
200
|
- Rakefile
|
|
204
201
|
- TAGS
|
|
205
|
-
- VERSION_UPDATE.md
|
|
206
202
|
- config/initializers/order_update_hub.rb
|
|
207
203
|
- core
|
|
208
204
|
- diagram.html
|
|
209
|
-
- diagram.md
|
|
210
205
|
- docs/API_DOCS_GAPS.md
|
|
211
206
|
- docs/API_VERIFICATION.md
|
|
212
|
-
- docs/ARCHIVE_README.md
|
|
213
207
|
- docs/AUTHENTICATION.md
|
|
214
208
|
- docs/CONFIGURATION.md
|
|
215
209
|
- docs/CONSTANTS_REFERENCE.md
|
|
216
210
|
- docs/DATA_API_PARAMETERS.md
|
|
211
|
+
- docs/ENDPOINTS_AND_SANDBOX.md
|
|
217
212
|
- docs/LIVE_ORDER_UPDATES.md
|
|
218
213
|
- docs/RAILS_INTEGRATION.md
|
|
219
214
|
- docs/RAILS_WEBSOCKET_INTEGRATION.md
|
|
@@ -237,9 +232,12 @@ files:
|
|
|
237
232
|
- lib/DhanHQ/contracts/base_contract.rb
|
|
238
233
|
- lib/DhanHQ/contracts/edis_contract.rb
|
|
239
234
|
- lib/DhanHQ/contracts/expired_options_data_contract.rb
|
|
235
|
+
- lib/DhanHQ/contracts/forever_order_contract.rb
|
|
240
236
|
- lib/DhanHQ/contracts/historical_data_contract.rb
|
|
241
237
|
- lib/DhanHQ/contracts/instrument_list_contract.rb
|
|
238
|
+
- lib/DhanHQ/contracts/intraday_historical_data_contract.rb
|
|
242
239
|
- lib/DhanHQ/contracts/margin_calculator_contract.rb
|
|
240
|
+
- lib/DhanHQ/contracts/market_feed_contract.rb
|
|
243
241
|
- lib/DhanHQ/contracts/modify_order_contract.rb
|
|
244
242
|
- lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb
|
|
245
243
|
- lib/DhanHQ/contracts/option_chain_contract.rb
|