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.
- checksums.yaml +4 -4
- data/.rubocop.yml +15 -3
- data/AGENTS.md +23 -0
- data/ARCHITECTURE.md +115 -0
- data/CHANGELOG.md +65 -0
- data/README.md +55 -0
- data/docs/API_VERIFICATION.md +10 -8
- data/docs/ENDPOINTS_AND_SANDBOX.md +12 -0
- data/lib/DhanHQ/auth.rb +2 -2
- data/lib/DhanHQ/client.rb +42 -34
- data/lib/DhanHQ/concerns/order_audit.rb +69 -0
- data/lib/DhanHQ/configuration.rb +5 -6
- data/lib/DhanHQ/constants.rb +67 -7
- 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 +9 -9
- data/lib/DhanHQ/core/base_model.rb +4 -1
- data/lib/DhanHQ/core/error_handler.rb +2 -2
- data/lib/DhanHQ/errors.rb +16 -2
- data/lib/DhanHQ/helpers/request_helper.rb +11 -2
- 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 +16 -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/resources/alert_orders.rb +23 -1
- data/lib/DhanHQ/resources/edis.rb +4 -3
- data/lib/DhanHQ/resources/forever_orders.rb +10 -0
- 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 +13 -1
- data/lib/DhanHQ/resources/pnl_exit.rb +8 -0
- data/lib/DhanHQ/resources/super_orders.rb +21 -2
- data/lib/DhanHQ/resources/trader_control.rb +13 -4
- data/lib/DhanHQ/utils/network_inspector.rb +71 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/base_connection.rb +1 -1
- data/lib/DhanHQ/ws/market_depth/client.rb +11 -4
- data/lib/dhan_hq.rb +17 -20
- data/lib/ta/indicators.rb +15 -18
- metadata +9 -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
|
@@ -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
|
-
|
|
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
|
|
|
@@ -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::
|
|
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
|
-
|
|
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
|
-
#
|
|
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
|
|
@@ -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
|
data/lib/DhanHQ/version.rb
CHANGED
|
@@ -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
|
-
|
|
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(
|
|
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(
|
|
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}
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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.
|
|
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-
|
|
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
|