DhanHQ 2.6.2 → 2.7.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 (66) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +15 -3
  3. data/AGENTS.md +23 -0
  4. data/ARCHITECTURE.md +115 -0
  5. data/CHANGELOG.md +65 -0
  6. data/README.md +55 -0
  7. data/docs/API_VERIFICATION.md +10 -8
  8. data/docs/ENDPOINTS_AND_SANDBOX.md +12 -0
  9. data/lib/DhanHQ/auth.rb +2 -2
  10. data/lib/DhanHQ/client.rb +42 -34
  11. data/lib/DhanHQ/concerns/order_audit.rb +69 -0
  12. data/lib/DhanHQ/configuration.rb +5 -6
  13. data/lib/DhanHQ/constants.rb +67 -7
  14. data/lib/DhanHQ/contracts/alert_order_contract.rb +23 -16
  15. data/lib/DhanHQ/contracts/expired_options_data_contract.rb +4 -2
  16. data/lib/DhanHQ/contracts/forever_order_contract.rb +55 -0
  17. data/lib/DhanHQ/contracts/historical_data_contract.rb +17 -19
  18. data/lib/DhanHQ/contracts/intraday_historical_data_contract.rb +12 -0
  19. data/lib/DhanHQ/contracts/margin_calculator_contract.rb +19 -17
  20. data/lib/DhanHQ/contracts/market_feed_contract.rb +42 -0
  21. data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +8 -5
  22. data/lib/DhanHQ/contracts/option_chain_contract.rb +17 -19
  23. data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +1 -1
  24. data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -10
  25. data/lib/DhanHQ/core/auth_api.rb +1 -1
  26. data/lib/DhanHQ/core/base_api.rb +9 -9
  27. data/lib/DhanHQ/core/base_model.rb +4 -1
  28. data/lib/DhanHQ/core/error_handler.rb +2 -2
  29. data/lib/DhanHQ/errors.rb +16 -2
  30. data/lib/DhanHQ/helpers/request_helper.rb +11 -2
  31. data/lib/DhanHQ/helpers/response_helper.rb +48 -19
  32. data/lib/DhanHQ/helpers/validation_helper.rb +4 -2
  33. data/lib/DhanHQ/models/alert_order.rb +6 -2
  34. data/lib/DhanHQ/models/edis.rb +20 -13
  35. data/lib/DhanHQ/models/expired_options_data.rb +54 -44
  36. data/lib/DhanHQ/models/forever_order.rb +16 -7
  37. data/lib/DhanHQ/models/historical_data.rb +40 -6
  38. data/lib/DhanHQ/models/instrument_helpers.rb +2 -1
  39. data/lib/DhanHQ/models/margin.rb +62 -82
  40. data/lib/DhanHQ/models/market_feed.rb +14 -3
  41. data/lib/DhanHQ/models/option_chain.rb +50 -150
  42. data/lib/DhanHQ/models/order.rb +19 -4
  43. data/lib/DhanHQ/resources/alert_orders.rb +23 -1
  44. data/lib/DhanHQ/resources/edis.rb +4 -3
  45. data/lib/DhanHQ/resources/forever_orders.rb +10 -0
  46. data/lib/DhanHQ/resources/instruments.rb +3 -2
  47. data/lib/DhanHQ/resources/ip_setup.rb +4 -1
  48. data/lib/DhanHQ/resources/kill_switch.rb +7 -1
  49. data/lib/DhanHQ/resources/orders.rb +13 -1
  50. data/lib/DhanHQ/resources/pnl_exit.rb +8 -0
  51. data/lib/DhanHQ/resources/super_orders.rb +21 -2
  52. data/lib/DhanHQ/resources/trader_control.rb +13 -4
  53. data/lib/DhanHQ/utils/network_inspector.rb +71 -0
  54. data/lib/DhanHQ/version.rb +1 -1
  55. data/lib/DhanHQ/ws/base_connection.rb +1 -1
  56. data/lib/DhanHQ/ws/market_depth/client.rb +11 -4
  57. data/lib/dhan_hq.rb +17 -20
  58. data/lib/ta/indicators.rb +15 -18
  59. metadata +9 -9
  60. data/CODE_REVIEW_ISSUES.md +0 -397
  61. data/FIXES_APPLIED.md +0 -373
  62. data/RELEASING.md +0 -60
  63. data/REVIEW_SUMMARY.md +0 -120
  64. data/VERSION_UPDATE.md +0 -82
  65. data/diagram.md +0 -34
  66. data/docs/ARCHIVE_README.md +0 -784
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../concerns/order_audit"
4
+
3
5
  module DhanHQ
4
6
  module Resources
5
7
  # Resource client for GTT/forever order management.
6
8
  class ForeverOrders < BaseAPI
9
+ include DhanHQ::Concerns::OrderAudit
10
+
7
11
  # Uses the trading API tier.
8
12
  API_TYPE = :order_api
9
13
  # Root path for forever order operations.
@@ -21,6 +25,8 @@ module DhanHQ
21
25
  # @param params [Hash]
22
26
  # @return [Hash]
23
27
  def create(params)
28
+ ensure_live_trading!
29
+ log_order_context("DHAN_FOREVER_ORDER_ATTEMPT", params)
24
30
  post("/orders", params: params)
25
31
  end
26
32
 
@@ -38,6 +44,8 @@ module DhanHQ
38
44
  # @param params [Hash]
39
45
  # @return [Hash]
40
46
  def update(order_id, params)
47
+ ensure_live_trading!
48
+ log_order_context("DHAN_FOREVER_ORDER_MODIFY_ATTEMPT", params.merge(order_id: order_id))
41
49
  put("/orders/#{order_id}", params: params)
42
50
  end
43
51
 
@@ -46,6 +54,8 @@ module DhanHQ
46
54
  # @param order_id [String]
47
55
  # @return [Hash]
48
56
  def cancel(order_id)
57
+ ensure_live_trading!
58
+ log_order_context("DHAN_FOREVER_ORDER_CANCEL_ATTEMPT", { order_id: order_id })
49
59
  delete("/orders/#{order_id}")
50
60
  end
51
61
  end
@@ -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
- return Faraday.get(redirect_url).body
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
- query = "?killSwitchStatus=#{CGI.escape(status.to_s)}"
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
 
@@ -1,9 +1,13 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../concerns/order_audit"
4
+
3
5
  module DhanHQ
4
6
  module Resources
5
7
  # Handles order placement, modification, and cancellation
6
8
  class Orders < BaseAPI
9
+ include DhanHQ::Concerns::OrderAudit
10
+
7
11
  API_TYPE = :order_api
8
12
  HTTP_PATH = "/v2/orders"
9
13
 
@@ -12,21 +16,29 @@ module DhanHQ
12
16
  # --------------------------------------------------
13
17
 
14
18
  def create(params)
19
+ ensure_live_trading!
20
+ log_order_context("DHAN_ORDER_ATTEMPT", params)
15
21
  validate_place_order!(params)
16
22
  post("", params: params)
17
23
  end
18
24
 
19
25
  def update(order_id, params)
26
+ ensure_live_trading!
27
+ log_order_context("DHAN_ORDER_MODIFY_ATTEMPT", params.merge(order_id: order_id))
20
28
  validate_modify_order!(params.merge(order_id: order_id))
21
29
  put("/#{order_id}", params: params)
22
30
  end
23
31
 
24
32
  def slicing(params)
33
+ ensure_live_trading!
34
+ log_order_context("DHAN_ORDER_SLICING_ATTEMPT", params)
25
35
  validate_place_order!(params)
26
36
  post("/slicing", params: params)
27
37
  end
28
38
 
29
39
  def cancel(order_id)
40
+ ensure_live_trading!
41
+ log_order_context("DHAN_ORDER_CANCEL_ATTEMPT", { order_id: order_id })
30
42
  delete("/#{order_id}")
31
43
  end
32
44
 
@@ -63,7 +75,7 @@ module DhanHQ
63
75
  end
64
76
 
65
77
  def raise_validation_error!(result)
66
- raise DhanHQ::Error, "Validation Error: #{result.errors.to_h}"
78
+ raise DhanHQ::ValidationError, "Invalid parameters: #{result.errors.to_h}"
67
79
  end
68
80
  end
69
81
  end
@@ -1,10 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../concerns/order_audit"
4
+
3
5
  module DhanHQ
4
6
  module Resources
5
7
  # Resource for P&L Based Exit endpoints per https://dhanhq.co/docs/v2/traders-control/
6
8
  # POST /v2/pnlExit — configure, DELETE /v2/pnlExit — stop, GET /v2/pnlExit — status.
7
9
  class PnlExit < BaseAPI
10
+ include DhanHQ::Concerns::OrderAudit
11
+
8
12
  API_TYPE = :order_api
9
13
  HTTP_PATH = "/v2/pnlExit"
10
14
 
@@ -14,6 +18,8 @@ module DhanHQ
14
18
  # @param params [Hash] Request body with profitValue, lossValue, productType, enableKillSwitch.
15
19
  # @return [Hash] API response containing pnlExitStatus and message.
16
20
  def configure(params)
21
+ ensure_live_trading!
22
+ log_order_context("DHAN_PNL_EXIT_CONFIGURE_ATTEMPT", params)
17
23
  post("", params: params)
18
24
  end
19
25
 
@@ -22,6 +28,8 @@ module DhanHQ
22
28
  #
23
29
  # @return [Hash] API response containing pnlExitStatus and message.
24
30
  def stop
31
+ ensure_live_trading!
32
+ log_order_context("DHAN_PNL_EXIT_STOP_ATTEMPT")
25
33
  delete("")
26
34
  end
27
35
 
@@ -1,14 +1,20 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../concerns/order_audit"
4
+
3
5
  module DhanHQ
4
6
  module Resources
5
7
  # Resource client for multi-leg super orders.
6
8
  class SuperOrders < BaseAPI
9
+ include DhanHQ::Concerns::OrderAudit
10
+
7
11
  # Super orders are executed via the trading API.
8
12
  API_TYPE = :order_api
9
13
  # Base path for super order endpoints.
10
14
  HTTP_PATH = "/v2/super/orders"
11
15
 
16
+ SUPER_ORDER_LEGS = %w[ENTRY_LEG STOP_LOSS_LEG TARGET_LEG].freeze
17
+
12
18
  # Lists all configured super orders.
13
19
  #
14
20
  # @return [Array<Hash>]
@@ -21,6 +27,8 @@ module DhanHQ
21
27
  # @param params [Hash]
22
28
  # @return [Hash]
23
29
  def create(params)
30
+ ensure_live_trading!
31
+ log_order_context("DHAN_SUPER_ORDER_ATTEMPT", params)
24
32
  post("", params: params)
25
33
  end
26
34
 
@@ -30,16 +38,27 @@ module DhanHQ
30
38
  # @param params [Hash]
31
39
  # @return [Hash]
32
40
  def update(order_id, params)
41
+ ensure_live_trading!
42
+ log_order_context("DHAN_SUPER_ORDER_MODIFY_ATTEMPT", params.merge(order_id: order_id))
33
43
  put("/#{order_id}", params: params)
34
44
  end
35
45
 
36
46
  # Cancels a specific leg from a super order.
37
47
  #
38
48
  # @param order_id [String]
39
- # @param leg_name [String]
49
+ # @param leg_name [String] One of ENTRY_LEG, STOP_LOSS_LEG, TARGET_LEG (per API path enum)
40
50
  # @return [Hash]
51
+ # @raise [DhanHQ::ValidationError] if leg_name is not a valid leg
41
52
  def cancel(order_id, leg_name)
42
- delete("/#{order_id}/#{leg_name}")
53
+ normalized = leg_name.to_s.upcase.strip
54
+ unless SUPER_ORDER_LEGS.include?(normalized)
55
+ raise DhanHQ::ValidationError,
56
+ "leg_name must be one of: #{SUPER_ORDER_LEGS.join(", ")}"
57
+ end
58
+
59
+ ensure_live_trading!
60
+ log_order_context("DHAN_SUPER_ORDER_CANCEL_ATTEMPT", { order_id: order_id, leg_name: normalized })
61
+ delete("/#{order_id}/#{normalized}")
43
62
  end
44
63
  end
45
64
  end
@@ -2,21 +2,30 @@
2
2
 
3
3
  module DhanHQ
4
4
  module Resources
5
- # Resource for trader control (kill switch): status (GET), enable/disable (POST /trader-control).
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
- get("")
20
+ raise DhanHQ::Error, MSG
12
21
  end
13
22
 
14
23
  def enable
15
- post("", params: { action: "ENABLE" })
24
+ raise DhanHQ::Error, MSG
16
25
  end
17
26
 
18
27
  def disable
19
- post("", params: { action: "DISABLE" })
28
+ raise DhanHQ::Error, MSG
20
29
  end
21
30
  end
22
31
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "socket"
5
+
6
+ module DhanHQ
7
+ module Utils
8
+ # Collects network and environment metadata for order audit logging.
9
+ #
10
+ # Results are memoized at class level so that repeated calls within a single
11
+ # process do not incur additional HTTP round-trips to the IP-lookup services.
12
+ #
13
+ # @example
14
+ # DhanHQ::Utils::NetworkInspector.public_ipv4 # => "122.171.22.40"
15
+ # DhanHQ::Utils::NetworkInspector.hostname # => "DESKTOP-SHUBHAM"
16
+ # DhanHQ::Utils::NetworkInspector.environment # => "production"
17
+ class NetworkInspector
18
+ IPV4_URI = URI("https://api.ipify.org")
19
+ IPV6_URI = URI("https://api64.ipify.org")
20
+
21
+ class << self
22
+ # Returns the public IPv4 address of this machine.
23
+ # Cached after the first successful lookup. Returns "unknown" on failure.
24
+ #
25
+ # @return [String]
26
+ def public_ipv4
27
+ @public_ipv4 ||= fetch_ip(IPV4_URI)
28
+ end
29
+
30
+ # Returns the public IPv6 address of this machine.
31
+ # Cached after the first successful lookup. Returns "unknown" on failure.
32
+ #
33
+ # @return [String]
34
+ def public_ipv6
35
+ @public_ipv6 ||= fetch_ip(IPV6_URI)
36
+ end
37
+
38
+ # Returns the system hostname.
39
+ #
40
+ # @return [String]
41
+ def hostname
42
+ Socket.gethostname
43
+ end
44
+
45
+ # Returns the current runtime environment name.
46
+ # Checks RAILS_ENV, RACK_ENV, APP_ENV in order; falls back to "unknown".
47
+ #
48
+ # @return [String]
49
+ def environment
50
+ ENV["RAILS_ENV"] || ENV["RACK_ENV"] || ENV["APP_ENV"] || "unknown"
51
+ end
52
+
53
+ # Clears the memoized IP cache (useful in tests or when the IP may change).
54
+ #
55
+ # @return [void]
56
+ def reset_cache!
57
+ @public_ipv4 = nil
58
+ @public_ipv6 = nil
59
+ end
60
+
61
+ private
62
+
63
+ def fetch_ip(uri)
64
+ Net::HTTP.get(uri).strip
65
+ rescue StandardError
66
+ "unknown"
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DhanHQ
4
4
  # Semantic version of the DhanHQ client gem.
5
- VERSION = "2.6.2"
5
+ VERSION = "2.7.0"
6
6
  end
@@ -173,7 +173,7 @@ module DhanHQ
173
173
  def default_headers
174
174
  {
175
175
  "User-Agent" => "DhanHQ-Ruby-Client/#{DhanHQ::VERSION}",
176
- "Origin" => "https://dhanhq.co"
176
+ "Origin" => Constants::Urls::ORIGIN
177
177
  }
178
178
  end
179
179
 
@@ -87,7 +87,7 @@ module DhanHQ
87
87
  depth_level = config.market_depth_level || 20 # Default to 20 level depth
88
88
 
89
89
  base = if depth_level == 200
90
- "wss://full-depth-api.dhan.co/twohundreddepth"
90
+ Constants::Urls::WS_DEPTH_200
91
91
  else
92
92
  config.ws_market_depth_url
93
93
  end
@@ -164,7 +164,10 @@ module DhanHQ
164
164
 
165
165
  send_message(subscription_message)
166
166
  @subscriptions[label] = resolution
167
- DhanHQ.logger&.info("[DhanHQ::WS::MarketDepth] Subscribed to #{resolution[:original_label]} (#{resolution[:exchange_segment]}:#{resolution[:security_id]})")
167
+ DhanHQ.logger&.info(
168
+ "[DhanHQ::WS::MarketDepth] Subscribed to #{resolution[:original_label]} " \
169
+ "(#{resolution[:exchange_segment]}:#{resolution[:security_id]})"
170
+ )
168
171
  rescue StandardError => e
169
172
  DhanHQ.logger&.error("[DhanHQ::WS::MarketDepth] Subscription error for #{symbol.inspect}: #{e.class} #{e.message}")
170
173
  end
@@ -192,7 +195,10 @@ module DhanHQ
192
195
 
193
196
  send_message(unsubscribe_message)
194
197
  @subscriptions.delete(label)
195
- DhanHQ.logger&.info("[DhanHQ::WS::MarketDepth] Unsubscribed from #{security_data[:original_label]} (#{security_data[:exchange_segment]}:#{security_data[:security_id]})")
198
+ DhanHQ.logger&.info(
199
+ "[DhanHQ::WS::MarketDepth] Unsubscribed from #{security_data[:original_label]} " \
200
+ "(#{security_data[:exchange_segment]}:#{security_data[:security_id]})"
201
+ )
196
202
  rescue StandardError => e
197
203
  DhanHQ.logger&.error("[DhanHQ::WS::MarketDepth] Unsubscribe error for #{symbol.inspect}: #{e.class} #{e.message}")
198
204
  end
@@ -217,7 +223,8 @@ module DhanHQ
217
223
  instrument = find_instrument(symbol_code, segment_hint)
218
224
  unless instrument
219
225
  DhanHQ.logger&.warn(
220
- "[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code} (segment hint: #{segment_hint || "AUTO"})"
226
+ "[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code} " \
227
+ "(segment hint: #{segment_hint || "AUTO"})"
221
228
  )
222
229
  return nil
223
230
  end
data/lib/dhan_hq.rb CHANGED
@@ -12,8 +12,9 @@ require_relative "DhanHQ/helpers/api_helper"
12
12
  require_relative "DhanHQ/helpers/attribute_helper"
13
13
  require_relative "DhanHQ/helpers/validation_helper"
14
14
  require_relative "DhanHQ/helpers/request_helper"
15
- require_relative "DhanHQ/helpers/response_helper"
16
15
  require_relative "DhanHQ/errors"
16
+ require_relative "DhanHQ/version"
17
+ require_relative "DhanHQ/helpers/response_helper"
17
18
  require_relative "DhanHQ/core/base_api"
18
19
  require_relative "DhanHQ/core/base_model"
19
20
  require_relative "DhanHQ/core/base_resource"
@@ -49,7 +50,7 @@ module DhanHQ
49
50
  # Default REST API host used when no custom base URL is provided.
50
51
  #
51
52
  # @return [String]
52
- BASE_URL = "https://api.dhan.co/v2"
53
+ BASE_URL = Constants::Urls::REST_API_BASE
53
54
  # The current configuration instance.
54
55
  #
55
56
  # @return [DhanHQ::Configuration, nil] The current configuration or `nil` if not set.
@@ -142,28 +143,12 @@ module DhanHQ
142
143
  end
143
144
 
144
145
  unless response.success?
145
- body = if response.body.is_a?(Hash)
146
- response.body
147
- else
148
- begin
149
- JSON.parse(response.body.to_s)
150
- rescue StandardError
151
- {}
152
- end
153
- end
146
+ body = parse_json_body(response.body)
154
147
  msg = body["error"] || body["message"] || body["errorMessage"] || response.body.to_s
155
148
  raise DhanHQ::TokenEndpointError, "Token endpoint returned #{response.status}: #{msg}"
156
149
  end
157
150
 
158
- data = if response.body.is_a?(Hash)
159
- response.body
160
- else
161
- begin
162
- JSON.parse(response.body.to_s)
163
- rescue StandardError
164
- {}
165
- end
166
- end
151
+ data = parse_json_body(response.body)
167
152
  data = data.transform_keys(&:to_s) if data.is_a?(Hash)
168
153
 
169
154
  access_token = data["access_token"] || data[:access_token]
@@ -177,5 +162,17 @@ module DhanHQ
177
162
  configuration.base_url = dhan_base.to_s if dhan_base.to_s != ""
178
163
  configuration
179
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
180
177
  end
181
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
- return RubyTechnicalAnalysis::RSI.new(series: series, period: period).call
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
- return RubyTechnicalAnalysis::ADX.new(high: high, low: low, close: close, period: period).call
60
- end
61
- if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:adx)
62
- return TechnicalAnalysis.adx(high: high, low: low, close: close, period: period)
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
- return RubyTechnicalAnalysis::ATR.new(high: high, low: low, close: close, period: period).call
71
- end
72
- if defined?(TechnicalAnalysis) && TechnicalAnalysis.respond_to?(:atr)
73
- return TechnicalAnalysis.atr(high: high, low: low, close: close, period: period)
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.2
4
+ version: 2.7.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-03-07 00:00:00.000000000 Z
11
+ date: 2026-03-16 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -191,25 +191,20 @@ files:
191
191
  - ".rspec"
192
192
  - ".rubocop.yml"
193
193
  - ".rubocop_todo.yml"
194
+ - AGENTS.md
195
+ - ARCHITECTURE.md
194
196
  - CHANGELOG.md
195
197
  - CODE_OF_CONDUCT.md
196
- - CODE_REVIEW_ISSUES.md
197
- - FIXES_APPLIED.md
198
198
  - GUIDE.md
199
199
  - LICENSE.txt
200
200
  - README.md
201
- - RELEASING.md
202
- - REVIEW_SUMMARY.md
203
201
  - Rakefile
204
202
  - TAGS
205
- - VERSION_UPDATE.md
206
203
  - config/initializers/order_update_hub.rb
207
204
  - core
208
205
  - diagram.html
209
- - diagram.md
210
206
  - docs/API_DOCS_GAPS.md
211
207
  - docs/API_VERIFICATION.md
212
- - docs/ARCHIVE_README.md
213
208
  - docs/AUTHENTICATION.md
214
209
  - docs/CONFIGURATION.md
215
210
  - docs/CONSTANTS_REFERENCE.md
@@ -232,15 +227,19 @@ files:
232
227
  - lib/DhanHQ/auth/token_manager.rb
233
228
  - lib/DhanHQ/auth/token_renewal.rb
234
229
  - lib/DhanHQ/client.rb
230
+ - lib/DhanHQ/concerns/order_audit.rb
235
231
  - lib/DhanHQ/configuration.rb
236
232
  - lib/DhanHQ/constants.rb
237
233
  - lib/DhanHQ/contracts/alert_order_contract.rb
238
234
  - lib/DhanHQ/contracts/base_contract.rb
239
235
  - lib/DhanHQ/contracts/edis_contract.rb
240
236
  - lib/DhanHQ/contracts/expired_options_data_contract.rb
237
+ - lib/DhanHQ/contracts/forever_order_contract.rb
241
238
  - lib/DhanHQ/contracts/historical_data_contract.rb
242
239
  - lib/DhanHQ/contracts/instrument_list_contract.rb
240
+ - lib/DhanHQ/contracts/intraday_historical_data_contract.rb
243
241
  - lib/DhanHQ/contracts/margin_calculator_contract.rb
242
+ - lib/DhanHQ/contracts/market_feed_contract.rb
244
243
  - lib/DhanHQ/contracts/modify_order_contract.rb
245
244
  - lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb
246
245
  - lib/DhanHQ/contracts/option_chain_contract.rb
@@ -316,6 +315,7 @@ files:
316
315
  - lib/DhanHQ/resources/super_orders.rb
317
316
  - lib/DhanHQ/resources/trader_control.rb
318
317
  - lib/DhanHQ/resources/trades.rb
318
+ - lib/DhanHQ/utils/network_inspector.rb
319
319
  - lib/DhanHQ/version.rb
320
320
  - lib/DhanHQ/ws.rb
321
321
  - lib/DhanHQ/ws/base_connection.rb