DhanHQ 3.2.1 → 3.4.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/CHANGELOG.md +76 -0
- data/GUIDE.md +16 -0
- data/README.md +93 -18
- data/docs/CONFIGURATION.md +39 -14
- data/docs/RAILS_INTEGRATION.md +60 -17
- data/docs/RELEASE_GUIDE.md +1 -1
- data/docs/TROUBLESHOOTING.md +15 -1
- data/lib/DhanHQ/backtest/result.rb +87 -0
- data/lib/DhanHQ/backtest/runner.rb +144 -0
- data/lib/DhanHQ/backtest/trade.rb +35 -0
- data/lib/DhanHQ/backtest.rb +24 -0
- data/lib/DhanHQ/concerns/bang_writes.rb +69 -0
- data/lib/DhanHQ/concerns/tracked_writes.rb +56 -0
- data/lib/DhanHQ/configuration.rb +35 -1
- data/lib/DhanHQ/core/base_model.rb +6 -13
- data/lib/DhanHQ/deprecation.rb +62 -0
- data/lib/DhanHQ/jobs/place_order_job.rb +47 -0
- data/lib/DhanHQ/models/alert_order.rb +7 -0
- data/lib/DhanHQ/models/forever_order.rb +9 -0
- data/lib/DhanHQ/models/global_stocks/order.rb +9 -0
- data/lib/DhanHQ/models/iceberg_order.rb +9 -0
- data/lib/DhanHQ/models/multi_order.rb +7 -0
- data/lib/DhanHQ/models/order.rb +28 -0
- data/lib/DhanHQ/models/pnl_exit.rb +7 -0
- data/lib/DhanHQ/models/super_order.rb +9 -0
- data/lib/DhanHQ/models/twap_order.rb +9 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/write_result.rb +163 -0
- data/lib/DhanHQ/ws/base_connection.rb +1 -0
- data/lib/DhanHQ/ws/connection.rb +1 -0
- data/lib/DhanHQ/ws/orders/connection.rb +1 -0
- data/lib/DhanHQ/ws.rb +25 -0
- data/lib/dhan_hq.rb +5 -0
- data/lib/generators/dhanhq/install/install_generator.rb +45 -0
- data/lib/generators/dhanhq/install/templates/initializer.rb +18 -0
- data/lib/generators/dhanhq/install/templates/market_feed_channel.rb +7 -0
- data/lib/generators/dhanhq/install/templates/market_feed_worker.rb +27 -0
- data/lib/generators/dhanhq/install/templates/place_order_service.rb +24 -0
- data/skills/dhanhq-ruby/references/portfolio.md +15 -8
- metadata +30 -13
|
@@ -32,6 +32,13 @@ module DhanHQ
|
|
|
32
32
|
# puts response[:pnl_exit_status] # => "DISABLED"
|
|
33
33
|
#
|
|
34
34
|
class PnlExit < BaseModel
|
|
35
|
+
extend DhanHQ::Concerns::BangWrites
|
|
36
|
+
extend DhanHQ::Concerns::TrackedWrites
|
|
37
|
+
|
|
38
|
+
track_class_writes :configure, :stop
|
|
39
|
+
|
|
40
|
+
bang_class_writes :configure, :stop
|
|
41
|
+
|
|
35
42
|
HTTP_PATH = "/v2/pnlExit"
|
|
36
43
|
|
|
37
44
|
attributes :pnl_exit_status, :profit, :loss, :segments, :enable_kill_switch
|
|
@@ -48,6 +48,15 @@ module DhanHQ
|
|
|
48
48
|
# order.cancel("STOP_LOSS_LEG")
|
|
49
49
|
#
|
|
50
50
|
class SuperOrder < BaseModel
|
|
51
|
+
extend DhanHQ::Concerns::BangWrites
|
|
52
|
+
extend DhanHQ::Concerns::TrackedWrites
|
|
53
|
+
|
|
54
|
+
track_class_writes :create
|
|
55
|
+
track_writes :modify, :cancel
|
|
56
|
+
|
|
57
|
+
bang_class_writes :create
|
|
58
|
+
bang_writes :modify, :cancel
|
|
59
|
+
|
|
51
60
|
include Concerns::ApiResponseHandler
|
|
52
61
|
|
|
53
62
|
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
|
@@ -45,6 +45,15 @@ module DhanHQ
|
|
|
45
45
|
# order.cancel
|
|
46
46
|
#
|
|
47
47
|
class TwapOrder < BaseModel
|
|
48
|
+
extend DhanHQ::Concerns::BangWrites
|
|
49
|
+
extend DhanHQ::Concerns::TrackedWrites
|
|
50
|
+
|
|
51
|
+
track_class_writes :create
|
|
52
|
+
track_writes :modify, :cancel
|
|
53
|
+
|
|
54
|
+
bang_class_writes :create
|
|
55
|
+
bang_writes :modify, :cancel
|
|
56
|
+
|
|
48
57
|
include Concerns::ApiResponseHandler
|
|
49
58
|
|
|
50
59
|
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
data/lib/DhanHQ/version.rb
CHANGED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DhanHQ
|
|
4
|
+
# Interprets the several shapes a write currently uses to signal failure.
|
|
5
|
+
#
|
|
6
|
+
# The model write methods do not share a return contract. Depending on which class
|
|
7
|
+
# and which failure you hit, a rejected write comes back as +nil+, as +false+, or as
|
|
8
|
+
# a {DhanHQ::ErrorObject} — and {DhanHQ::Models::AlertOrder.modify} can return either
|
|
9
|
+
# of the first two from the same method. A caller cannot write one error branch.
|
|
10
|
+
#
|
|
11
|
+
# Unifying those contracts is a breaking change for the applications that depend on
|
|
12
|
+
# this gem, so it is staged (see CHANGELOG). This module is step one: it puts the
|
|
13
|
+
# knowledge of "what does failure look like" in exactly one place, and backs the bang
|
|
14
|
+
# variants (+place!+, +modify!+, +cancel!+, …) that give callers a single, explicit
|
|
15
|
+
# failure mode to rescue today.
|
|
16
|
+
#
|
|
17
|
+
# @example Opting a call site into explicit failures
|
|
18
|
+
# order = DhanHQ::Models::Order.place!(params) # raises DhanHQ::OrderError
|
|
19
|
+
# # instead of
|
|
20
|
+
# order = DhanHQ::Models::Order.place(params) # nil on failure
|
|
21
|
+
module WriteResult
|
|
22
|
+
# Thread-local flag set while a bang variant is calling through.
|
|
23
|
+
SUPPRESSION_KEY = :dhanhq_suppress_write_deprecation
|
|
24
|
+
|
|
25
|
+
module_function
|
|
26
|
+
|
|
27
|
+
# Whether a write result represents a rejected or failed operation.
|
|
28
|
+
#
|
|
29
|
+
# @param result [Object] Return value of a write method.
|
|
30
|
+
# @return [Boolean]
|
|
31
|
+
def failure?(result)
|
|
32
|
+
result.nil? || result == false || result.is_a?(DhanHQ::ErrorObject)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @param result [Object] Return value of a write method.
|
|
36
|
+
# @return [Boolean]
|
|
37
|
+
def success?(result)
|
|
38
|
+
!failure?(result)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the result, or raises carrying whatever diagnostics the failure held.
|
|
42
|
+
#
|
|
43
|
+
# @param result [Object] Return value of a write method.
|
|
44
|
+
# @param operation [String] Human-readable operation name for the message,
|
|
45
|
+
# e.g. +"DhanHQ::Models::Order.place"+.
|
|
46
|
+
# @param error_class [Class] Exception to raise. Defaults to {DhanHQ::OrderError},
|
|
47
|
+
# which descends from {DhanHQ::Error}, so existing `rescue DhanHQ::Error`
|
|
48
|
+
# handlers still catch it.
|
|
49
|
+
# @param errors [Hash, nil] Validation errors to report when the result itself
|
|
50
|
+
# carries none — typically a model's +errors+ hash.
|
|
51
|
+
# @return [Object] The result, when it represents success.
|
|
52
|
+
# @raise [DhanHQ::Error] When the result represents failure.
|
|
53
|
+
def unwrap!(result, operation:, error_class: DhanHQ::OrderError, errors: nil)
|
|
54
|
+
return result if success?(result)
|
|
55
|
+
|
|
56
|
+
raise error_class, "#{operation} failed: #{describe(result, errors)}"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Reports, once per call site, that a non-bang write signalled failure through a
|
|
60
|
+
# value whose shape is going to change in 4.0.0.
|
|
61
|
+
#
|
|
62
|
+
# Step two of the migration: the notice tells a maintainer which of their call
|
|
63
|
+
# sites still branch on the old return value, so they can move to the bang variant
|
|
64
|
+
# before the non-bang contract unifies on {DhanHQ::ErrorObject}. Returns the result
|
|
65
|
+
# untouched — this observes, it never alters behaviour.
|
|
66
|
+
#
|
|
67
|
+
# Silent when the write succeeded, when the caller opted out via
|
|
68
|
+
# +config.warn_on_ambiguous_write_failure = false+, or when reached through a bang
|
|
69
|
+
# variant (those callers have already migrated — see {.suppressing_deprecation}).
|
|
70
|
+
#
|
|
71
|
+
# @param result [Object] Return value of a non-bang write method.
|
|
72
|
+
# @param operation [String] Operation label from {.operation_label}.
|
|
73
|
+
# @return [Object] The result, unchanged.
|
|
74
|
+
def report_ambiguous_failure(result, operation:)
|
|
75
|
+
return result unless failure?(result)
|
|
76
|
+
return result if suppressed?
|
|
77
|
+
return result unless DhanHQ.configuration&.warn_on_ambiguous_write_failure?
|
|
78
|
+
|
|
79
|
+
DhanHQ::Deprecation.warn_once(
|
|
80
|
+
operation,
|
|
81
|
+
"#{operation} reported failure as #{shape_of(result)}. Write methods return " \
|
|
82
|
+
"nil, false or a DhanHQ::ErrorObject inconsistently today and will all return " \
|
|
83
|
+
"DhanHQ::ErrorObject in 4.0.0, which is truthy — an `if result` failure branch " \
|
|
84
|
+
"will invert. Use #{operation}! to get a DhanHQ::OrderError instead, or set " \
|
|
85
|
+
"config.warn_on_ambiguous_write_failure = false to silence this."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
result
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Runs the block with {.report_ambiguous_failure} disabled on this thread.
|
|
92
|
+
#
|
|
93
|
+
# Used by the bang variants: they call the non-bang method to get its result, and a
|
|
94
|
+
# caller who has already moved to `place!` does not need telling to move to
|
|
95
|
+
# `place!`. Thread-local so a concurrent thread still gets its own notices.
|
|
96
|
+
#
|
|
97
|
+
# @return [Object] The block's value.
|
|
98
|
+
def suppressing_deprecation
|
|
99
|
+
previous = Thread.current[SUPPRESSION_KEY]
|
|
100
|
+
Thread.current[SUPPRESSION_KEY] = true
|
|
101
|
+
yield
|
|
102
|
+
ensure
|
|
103
|
+
Thread.current[SUPPRESSION_KEY] = previous
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# @return [Boolean]
|
|
107
|
+
def suppressed?
|
|
108
|
+
Thread.current[SUPPRESSION_KEY] == true
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Names the shape a failure arrived in, for the notice.
|
|
112
|
+
#
|
|
113
|
+
# @return [String]
|
|
114
|
+
def shape_of(result)
|
|
115
|
+
return "nil" if result.nil?
|
|
116
|
+
return "false" if result == false
|
|
117
|
+
|
|
118
|
+
"a DhanHQ::ErrorObject"
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Label identifying the operation that failed, for the exception message.
|
|
122
|
+
#
|
|
123
|
+
# @param receiver [Object] The class (for a class method) or instance.
|
|
124
|
+
# @param name [Symbol] Method name.
|
|
125
|
+
# @return [String] e.g. +"DhanHQ::Models::Order.place"+ or +"…Order#modify"+.
|
|
126
|
+
def operation_label(receiver, name)
|
|
127
|
+
return "#{module_label(receiver)}.#{name}" if receiver.is_a?(Module)
|
|
128
|
+
|
|
129
|
+
"#{module_label(receiver.class)}##{name}"
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Reads a module's declared name, falling back to +to_s+ so an anonymous class
|
|
133
|
+
# still yields something readable rather than an object address.
|
|
134
|
+
#
|
|
135
|
+
# @return [String]
|
|
136
|
+
def module_label(mod)
|
|
137
|
+
mod.name || mod.to_s
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Validation errors carried by a model instance, when it has any.
|
|
141
|
+
#
|
|
142
|
+
# @param receiver [Object]
|
|
143
|
+
# @return [Hash, nil]
|
|
144
|
+
def errors_from(receiver)
|
|
145
|
+
return nil if receiver.is_a?(Module)
|
|
146
|
+
return nil unless receiver.respond_to?(:errors)
|
|
147
|
+
|
|
148
|
+
receiver.errors
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# Best available explanation for a failure.
|
|
152
|
+
#
|
|
153
|
+
# @return [String]
|
|
154
|
+
def describe(result, errors = nil)
|
|
155
|
+
return result.errors.to_s if result.is_a?(DhanHQ::ErrorObject)
|
|
156
|
+
return errors.to_s if errors && !errors.empty?
|
|
157
|
+
|
|
158
|
+
# `nil` and `false` carry nothing, so say which of the two it was rather than
|
|
159
|
+
# inventing a cause.
|
|
160
|
+
result.nil? ? "the API returned no record" : "the API rejected the request"
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
end
|
|
@@ -212,6 +212,7 @@ module DhanHQ
|
|
|
212
212
|
# Handle WebSocket message event
|
|
213
213
|
# @param ev [Event] WebSocket message event
|
|
214
214
|
def handle_message(ev)
|
|
215
|
+
WS.debug_frame(self.class.name, ev.data)
|
|
215
216
|
emit(:raw, ev.data)
|
|
216
217
|
process_message(ev.data) if respond_to?(:process_message, true)
|
|
217
218
|
end
|
data/lib/DhanHQ/ws/connection.rb
CHANGED
|
@@ -63,6 +63,7 @@ module DhanHQ
|
|
|
63
63
|
# Process incoming WebSocket message
|
|
64
64
|
# @param ev [Event] WebSocket message event
|
|
65
65
|
def handle_message(ev)
|
|
66
|
+
WS.debug_frame(self.class.name, ev.data)
|
|
66
67
|
msg = JSON.parse(ev.data, symbolize_names: true)
|
|
67
68
|
emit(:raw, msg)
|
|
68
69
|
emit(:message, msg)
|
data/lib/DhanHQ/ws.rb
CHANGED
|
@@ -34,5 +34,30 @@ module DhanHQ
|
|
|
34
34
|
def self.disconnect_all_local!
|
|
35
35
|
Registry.stop_all
|
|
36
36
|
end
|
|
37
|
+
|
|
38
|
+
# Cap on how many bytes of a frame get hex-dumped by {debug_frame}. A full
|
|
39
|
+
# depth packet can run several KB; at tick frequency that floods the log
|
|
40
|
+
# long before it adds diagnostic value beyond the first couple hundred
|
|
41
|
+
# bytes (header + the first few fields is normally enough to spot a
|
|
42
|
+
# parsing bug). The full frame size is always logged regardless of the cap.
|
|
43
|
+
DEBUG_FRAME_MAX_BYTES = 256
|
|
44
|
+
|
|
45
|
+
# Logs a raw inbound WebSocket frame as a hex dump when
|
|
46
|
+
# +config.ws_debug+ (+DHAN_WS_DEBUG=true+) is enabled. A no-op otherwise --
|
|
47
|
+
# the flag is checked before any hex encoding work, so there's no cost
|
|
48
|
+
# when debug logging is off. Frames longer than {DEBUG_FRAME_MAX_BYTES}
|
|
49
|
+
# are truncated in the dump, but the logged byte count is always the full
|
|
50
|
+
# frame size.
|
|
51
|
+
#
|
|
52
|
+
# @param source [String] short tag identifying which connection the frame came from
|
|
53
|
+
# @param data [String] raw frame bytes
|
|
54
|
+
# @return [void]
|
|
55
|
+
def self.debug_frame(source, data)
|
|
56
|
+
return unless DhanHQ.configuration&.ws_debug?
|
|
57
|
+
|
|
58
|
+
hex = data.byteslice(0, DEBUG_FRAME_MAX_BYTES).unpack1("H*")
|
|
59
|
+
hex += "...truncated" if data.bytesize > DEBUG_FRAME_MAX_BYTES
|
|
60
|
+
DhanHQ.logger&.debug("[DhanHQ::WS::#{source}] frame (#{data.bytesize} bytes): #{hex}")
|
|
61
|
+
end
|
|
37
62
|
end
|
|
38
63
|
end
|
data/lib/dhan_hq.rb
CHANGED
|
@@ -35,6 +35,11 @@ module DhanHQ
|
|
|
35
35
|
"base_api" => "BaseAPI",
|
|
36
36
|
"ip_setup" => "IPSetup",
|
|
37
37
|
"json_loader" => "JSONLoader",
|
|
38
|
+
# Same failure as "ai" above: mcp.rb defines DhanHQ::MCP while Zeitwerk expected
|
|
39
|
+
# DhanHQ::Mcp, so DhanHQ::MCP::Server raised NameError after a bare `require
|
|
40
|
+
# "dhan_hq"` — it only worked via exe/dhanhq-mcp and lib/dhan_hq/mcp.rb, which
|
|
41
|
+
# require_relative the file directly instead of going through the autoloader.
|
|
42
|
+
"mcp" => "MCP",
|
|
38
43
|
"ws" => "WS"
|
|
39
44
|
)
|
|
40
45
|
LOADER.push_dir(File.join(__dir__, "DhanHQ"), namespace: self)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rails/generators"
|
|
4
|
+
|
|
5
|
+
module Dhanhq
|
|
6
|
+
# Scaffolds a DhanHQ initializer, a sample order-placing service object, and
|
|
7
|
+
# a Sidekiq worker + ActionCable channel for streaming market data.
|
|
8
|
+
#
|
|
9
|
+
# rails generate dhanhq:install
|
|
10
|
+
#
|
|
11
|
+
class InstallGenerator < Rails::Generators::Base
|
|
12
|
+
source_root File.expand_path("templates", __dir__)
|
|
13
|
+
|
|
14
|
+
def create_initializer
|
|
15
|
+
template "initializer.rb", "config/initializers/dhanhq.rb"
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def create_order_service
|
|
19
|
+
template "place_order_service.rb", "app/services/dhan/orders/place_order.rb"
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def create_market_feed_worker
|
|
23
|
+
template "market_feed_worker.rb", "app/workers/dhan_market_feed_worker.rb"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_market_feed_channel
|
|
27
|
+
template "market_feed_channel.rb", "app/channels/dhan_market_feed_channel.rb"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def show_post_install_message
|
|
31
|
+
say ""
|
|
32
|
+
say "DhanHQ installed! Next steps:", :green
|
|
33
|
+
say " 1. Add your credentials:"
|
|
34
|
+
say " rails credentials:edit"
|
|
35
|
+
say " dhanhq:"
|
|
36
|
+
say " client_id: \"your_client_id\""
|
|
37
|
+
say " access_token: \"your_access_token\""
|
|
38
|
+
say " 2. Set LIVE_TRADING=true before placing real orders (see docs/CONFIGURATION.md)"
|
|
39
|
+
say " 3. Start the market feed: DhanMarketFeedWorker.perform_async"
|
|
40
|
+
say ""
|
|
41
|
+
say "Full reference: https://github.com/shubhamtaywade82/dhanhq-client/blob/main/docs/RAILS_INTEGRATION.md"
|
|
42
|
+
say ""
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "dhan_hq"
|
|
4
|
+
|
|
5
|
+
if (creds = Rails.application.credentials.dig(:dhanhq))
|
|
6
|
+
ENV["DHAN_CLIENT_ID"] ||= creds[:client_id]
|
|
7
|
+
ENV["DHAN_ACCESS_TOKEN"] ||= creds[:access_token]
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
DhanHQ.configure_with_env
|
|
11
|
+
|
|
12
|
+
log_level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase
|
|
13
|
+
DhanHQ.logger.level = Logger.const_get(log_level)
|
|
14
|
+
|
|
15
|
+
# Full optional configuration (base_url, ws_order_url, partner auth, timeouts,
|
|
16
|
+
# DHAN_WS_DEBUG, ...) is documented in:
|
|
17
|
+
# https://github.com/shubhamtaywade82/dhanhq-client/blob/main/docs/CONFIGURATION.md
|
|
18
|
+
# https://github.com/shubhamtaywade82/dhanhq-client/blob/main/docs/RAILS_INTEGRATION.md
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Keeps a DhanHQ market feed connection open and broadcasts ticks over
|
|
4
|
+
# ActionCable. Start it with DhanMarketFeedWorker.perform_async -- the job
|
|
5
|
+
# blocks for the life of the connection rather than completing immediately,
|
|
6
|
+
# so Sidekiq's dashboard reflects whether the feed is actually running.
|
|
7
|
+
#
|
|
8
|
+
# retry: false because there is nothing to retry: the underlying
|
|
9
|
+
# DhanHQ::WS::Client already reconnects and re-subscribes on its own.
|
|
10
|
+
class DhanMarketFeedWorker
|
|
11
|
+
include Sidekiq::Worker
|
|
12
|
+
sidekiq_options retry: false
|
|
13
|
+
|
|
14
|
+
def perform(mode = "quote")
|
|
15
|
+
client = DhanHQ::WS.connect(mode: mode.to_sym) do |tick|
|
|
16
|
+
ActionCable.server.broadcast("dhan_market_feed", tick)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
client.on(:reconnect) { |info| Rails.logger.warn("[DhanMarketFeedWorker] reconnect ##{info[:attempt]}") }
|
|
20
|
+
client.on(:error) { |message| Rails.logger.error("[DhanMarketFeedWorker] #{message}") }
|
|
21
|
+
|
|
22
|
+
loop do
|
|
23
|
+
sleep 30
|
|
24
|
+
break unless client.connected?
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dhan
|
|
4
|
+
module Orders
|
|
5
|
+
# Places a Dhan order via the bang variant, so a rejection raises a
|
|
6
|
+
# specific, catchable error instead of Order.place's ambiguous
|
|
7
|
+
# nil/false/ErrorObject return.
|
|
8
|
+
class PlaceOrder
|
|
9
|
+
def initialize(params)
|
|
10
|
+
@params = params
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call
|
|
14
|
+
DhanHQ::Models::Order.place!(@params)
|
|
15
|
+
rescue DhanHQ::OrderError => e
|
|
16
|
+
Rails.logger.error("Dhan order rejected: #{e.message}")
|
|
17
|
+
raise
|
|
18
|
+
rescue DhanHQ::RiskViolation => e
|
|
19
|
+
Rails.logger.warn("Dhan risk check blocked the order: #{e.message}")
|
|
20
|
+
raise
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -70,24 +70,31 @@ position.convert(
|
|
|
70
70
|
|
|
71
71
|
## eDIS Authorization
|
|
72
72
|
|
|
73
|
-
For selling delivery holdings, authorization is handled via `DhanHQ::Models::
|
|
73
|
+
For selling delivery holdings, authorization is handled via `DhanHQ::Models::Edis`:
|
|
74
74
|
|
|
75
75
|
### Step 1: Generate TPIN
|
|
76
76
|
```ruby
|
|
77
|
-
DhanHQ::Models::
|
|
77
|
+
DhanHQ::Models::Edis.generate_tpin
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Triggers a TPIN to the user's registered mobile/email. Returns `{status: "accepted"}` — the API responds `202` for this async operation.
|
|
81
|
+
|
|
82
|
+
### Step 2: Generate and Render the Authorization Form
|
|
81
83
|
```ruby
|
|
82
|
-
DhanHQ::Models::
|
|
84
|
+
form = DhanHQ::Models::Edis.generate_form(
|
|
83
85
|
isin: "INE002A01018",
|
|
84
86
|
qty: 5,
|
|
85
|
-
exchange: "NSE"
|
|
87
|
+
exchange: "NSE",
|
|
88
|
+
segment: "EQ"
|
|
86
89
|
)
|
|
90
|
+
# form[:edisFormHtml] is a browser-postable HTML form; render or POST it so the
|
|
91
|
+
# user can complete authorization on Dhan's eDIS page.
|
|
87
92
|
```
|
|
88
93
|
|
|
89
|
-
### Step 3:
|
|
94
|
+
### Step 3: Inquire eDIS Approval
|
|
90
95
|
```ruby
|
|
91
|
-
|
|
92
|
-
puts "Approved Qty: #{
|
|
96
|
+
status = DhanHQ::Models::Edis.inquire(isin: "INE002A01018") # or isin: "ALL"
|
|
97
|
+
puts "Approved Qty: #{status[:aprvdQty]}, Status: #{status[:status]}"
|
|
93
98
|
```
|
|
99
|
+
|
|
100
|
+
`inquire` returns the raw API response (a `HashWithIndifferentAccess`), not a model instance — key names match the API's camelCase (`aprvdQty`, `totalQty`), not the snake_case used elsewhere in this gem.
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: DhanHQ
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 3.
|
|
4
|
+
version: 3.4.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-
|
|
11
|
+
date: 2026-08-15 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: activesupport
|
|
@@ -84,16 +84,16 @@ dependencies:
|
|
|
84
84
|
name: dry-validation
|
|
85
85
|
requirement: !ruby/object:Gem::Requirement
|
|
86
86
|
requirements:
|
|
87
|
-
- - "
|
|
87
|
+
- - "~>"
|
|
88
88
|
- !ruby/object:Gem::Version
|
|
89
|
-
version: '
|
|
89
|
+
version: '1.11'
|
|
90
90
|
type: :runtime
|
|
91
91
|
prerelease: false
|
|
92
92
|
version_requirements: !ruby/object:Gem::Requirement
|
|
93
93
|
requirements:
|
|
94
|
-
- - "
|
|
94
|
+
- - "~>"
|
|
95
95
|
- !ruby/object:Gem::Version
|
|
96
|
-
version: '
|
|
96
|
+
version: '1.11'
|
|
97
97
|
- !ruby/object:Gem::Dependency
|
|
98
98
|
name: eventmachine
|
|
99
99
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -164,10 +164,11 @@ dependencies:
|
|
|
164
164
|
- - ">="
|
|
165
165
|
- !ruby/object:Gem::Version
|
|
166
166
|
version: '0'
|
|
167
|
-
description: A production-grade Ruby SDK for Dhan API v2 built for
|
|
168
|
-
monitoring, and live trading systems
|
|
169
|
-
|
|
170
|
-
|
|
167
|
+
description: A production-grade Ruby SDK and Ruby client for Dhan API v2 built for
|
|
168
|
+
algorithmic trading, portfolio monitoring, and live trading systems on NSE, BSE
|
|
169
|
+
and MCX. Provides typed models, token lifecycle management, dry-validation contracts,
|
|
170
|
+
resilient WebSocket streaming with auto-reconnect, and safety-focused order workflows
|
|
171
|
+
for Ruby on Rails and standalone Ruby applications.
|
|
171
172
|
email:
|
|
172
173
|
- shubhamtaywade82@gmail.com
|
|
173
174
|
executables:
|
|
@@ -228,8 +229,14 @@ files:
|
|
|
228
229
|
- lib/DhanHQ/auth/token_generator.rb
|
|
229
230
|
- lib/DhanHQ/auth/token_manager.rb
|
|
230
231
|
- lib/DhanHQ/auth/token_renewal.rb
|
|
232
|
+
- lib/DhanHQ/backtest.rb
|
|
233
|
+
- lib/DhanHQ/backtest/result.rb
|
|
234
|
+
- lib/DhanHQ/backtest/runner.rb
|
|
235
|
+
- lib/DhanHQ/backtest/trade.rb
|
|
231
236
|
- lib/DhanHQ/client.rb
|
|
237
|
+
- lib/DhanHQ/concerns/bang_writes.rb
|
|
232
238
|
- lib/DhanHQ/concerns/order_audit.rb
|
|
239
|
+
- lib/DhanHQ/concerns/tracked_writes.rb
|
|
233
240
|
- lib/DhanHQ/configuration.rb
|
|
234
241
|
- lib/DhanHQ/constants.rb
|
|
235
242
|
- lib/DhanHQ/contracts/alert_order_contract.rb
|
|
@@ -266,6 +273,7 @@ files:
|
|
|
266
273
|
- lib/DhanHQ/core/base_model.rb
|
|
267
274
|
- lib/DhanHQ/core/base_resource.rb
|
|
268
275
|
- lib/DhanHQ/core/error_handler.rb
|
|
276
|
+
- lib/DhanHQ/deprecation.rb
|
|
269
277
|
- lib/DhanHQ/dry_run/ledger.rb
|
|
270
278
|
- lib/DhanHQ/dry_run/simulator.rb
|
|
271
279
|
- lib/DhanHQ/error_object.rb
|
|
@@ -280,6 +288,7 @@ files:
|
|
|
280
288
|
- lib/DhanHQ/helpers/response_helper.rb
|
|
281
289
|
- lib/DhanHQ/helpers/validation_helper.rb
|
|
282
290
|
- lib/DhanHQ/indicators.rb
|
|
291
|
+
- lib/DhanHQ/jobs/place_order_job.rb
|
|
283
292
|
- lib/DhanHQ/json_loader.rb
|
|
284
293
|
- lib/DhanHQ/market_data.rb
|
|
285
294
|
- lib/DhanHQ/market_data/market_snapshot.rb
|
|
@@ -391,6 +400,7 @@ files:
|
|
|
391
400
|
- lib/DhanHQ/utils/network_inspector.rb
|
|
392
401
|
- lib/DhanHQ/version.rb
|
|
393
402
|
- lib/DhanHQ/write_paths.rb
|
|
403
|
+
- lib/DhanHQ/write_result.rb
|
|
394
404
|
- lib/DhanHQ/ws.rb
|
|
395
405
|
- lib/DhanHQ/ws/base_connection.rb
|
|
396
406
|
- lib/DhanHQ/ws/client.rb
|
|
@@ -429,6 +439,11 @@ files:
|
|
|
429
439
|
- lib/dhanhq/analysis/multi_timeframe_analyzer.rb
|
|
430
440
|
- lib/dhanhq/analysis/options_buying_advisor.rb
|
|
431
441
|
- lib/dhanhq/contracts/options_buying_advisor_contract.rb
|
|
442
|
+
- lib/generators/dhanhq/install/install_generator.rb
|
|
443
|
+
- lib/generators/dhanhq/install/templates/initializer.rb
|
|
444
|
+
- lib/generators/dhanhq/install/templates/market_feed_channel.rb
|
|
445
|
+
- lib/generators/dhanhq/install/templates/market_feed_worker.rb
|
|
446
|
+
- lib/generators/dhanhq/install/templates/place_order_service.rb
|
|
432
447
|
- lib/rubocop/cop/dhanhq/use_constants.rb
|
|
433
448
|
- lib/ta.rb
|
|
434
449
|
- lib/ta/candles.rb
|
|
@@ -465,14 +480,15 @@ files:
|
|
|
465
480
|
- skills/dhanhq-ruby/scripts/resolve_security.rb
|
|
466
481
|
- skills/dhanhq-ruby/scripts/trade_logger.rb
|
|
467
482
|
- skills/dhanhq-ruby/scripts/validate_order.rb
|
|
468
|
-
homepage: https://github.
|
|
483
|
+
homepage: https://shubhamtaywade82.github.io/dhanhq-client/
|
|
469
484
|
licenses:
|
|
470
485
|
- MIT
|
|
471
486
|
metadata:
|
|
472
487
|
allowed_push_host: https://rubygems.org
|
|
473
|
-
homepage_uri: https://github.
|
|
488
|
+
homepage_uri: https://shubhamtaywade82.github.io/dhanhq-client/
|
|
474
489
|
source_code_uri: https://github.com/shubhamtaywade82/dhanhq-client
|
|
475
490
|
changelog_uri: https://github.com/shubhamtaywade82/dhanhq-client/blob/main/CHANGELOG.md
|
|
491
|
+
documentation_uri: https://shubhamtaywade82.github.io/dhanhq-client/
|
|
476
492
|
rubygems_mfa_required: 'true'
|
|
477
493
|
post_install_message:
|
|
478
494
|
rdoc_options: []
|
|
@@ -492,5 +508,6 @@ requirements: []
|
|
|
492
508
|
rubygems_version: 3.5.11
|
|
493
509
|
signing_key:
|
|
494
510
|
specification_version: 4
|
|
495
|
-
summary:
|
|
511
|
+
summary: Production-grade Ruby SDK for Dhan API v2 with REST APIs, WebSocket market
|
|
512
|
+
data, token lifecycle management, dry-validation contracts and trading workflows.
|
|
496
513
|
test_files: []
|