DhanHQ 2.4.0 → 2.6.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 +9 -1
- data/CHANGELOG.md +103 -7
- data/GUIDE.md +57 -39
- data/README.md +198 -755
- data/docs/API_DOCS_GAPS.md +128 -0
- data/docs/API_VERIFICATION.md +10 -11
- data/{README1.md → docs/ARCHIVE_README.md} +16 -16
- data/docs/AUTHENTICATION.md +72 -10
- data/docs/CONFIGURATION.md +109 -0
- data/docs/CONSTANTS_REFERENCE.md +477 -0
- data/docs/DATA_API_PARAMETERS.md +7 -7
- data/docs/{rails_websocket_integration.md → RAILS_WEBSOCKET_INTEGRATION.md} +10 -10
- data/docs/{standalone_ruby_websocket_integration.md → STANDALONE_RUBY_WEBSOCKET_INTEGRATION.md} +32 -32
- data/docs/SUPER_ORDERS.md +284 -0
- data/docs/{technical_analysis.md → TECHNICAL_ANALYSIS.md} +3 -3
- data/docs/TESTING_GUIDE.md +84 -82
- data/docs/TROUBLESHOOTING.md +117 -0
- data/docs/{websocket_integration.md → WEBSOCKET_INTEGRATION.md} +19 -19
- data/docs/WEBSOCKET_PROTOCOL.md +154 -0
- data/lib/DhanHQ/constants.rb +456 -151
- data/lib/DhanHQ/contracts/alert_order_contract.rb +37 -10
- data/lib/DhanHQ/contracts/base_contract.rb +22 -0
- data/lib/DhanHQ/contracts/edis_contract.rb +25 -0
- data/lib/DhanHQ/contracts/margin_calculator_contract.rb +27 -4
- data/lib/DhanHQ/contracts/modify_order_contract.rb +65 -12
- data/lib/DhanHQ/contracts/multi_scrip_margin_calc_request_contract.rb +23 -0
- data/lib/DhanHQ/contracts/order_contract.rb +171 -39
- data/lib/DhanHQ/contracts/place_order_contract.rb +14 -141
- data/lib/DhanHQ/contracts/pnl_based_exit_contract.rb +20 -0
- data/lib/DhanHQ/contracts/position_conversion_contract.rb +15 -3
- data/lib/DhanHQ/contracts/slice_order_contract.rb +10 -1
- data/lib/DhanHQ/contracts/user_ip_contract.rb +14 -0
- data/lib/DhanHQ/core/base_model.rb +13 -4
- data/lib/DhanHQ/helpers/response_helper.rb +2 -2
- data/lib/DhanHQ/helpers/validation_helper.rb +1 -1
- data/lib/DhanHQ/models/alert_order.rb +29 -11
- data/lib/DhanHQ/models/concerns/api_response_handler.rb +46 -0
- data/lib/DhanHQ/models/edis.rb +101 -0
- data/lib/DhanHQ/models/expired_options_data.rb +6 -12
- data/lib/DhanHQ/models/forever_order.rb +8 -5
- data/lib/DhanHQ/models/historical_data.rb +0 -8
- data/lib/DhanHQ/models/instrument.rb +1 -7
- data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
- data/lib/DhanHQ/models/kill_switch.rb +23 -11
- data/lib/DhanHQ/models/margin.rb +51 -2
- data/lib/DhanHQ/models/order.rb +107 -126
- data/lib/DhanHQ/models/order_update.rb +7 -13
- data/lib/DhanHQ/models/pnl_exit.rb +122 -0
- data/lib/DhanHQ/models/position.rb +23 -1
- data/lib/DhanHQ/models/postback.rb +114 -0
- data/lib/DhanHQ/models/profile.rb +0 -10
- data/lib/DhanHQ/models/super_order.rb +13 -3
- data/lib/DhanHQ/models/trade.rb +11 -23
- data/lib/DhanHQ/resources/ip_setup.rb +16 -5
- data/lib/DhanHQ/resources/kill_switch.rb +17 -7
- data/lib/DhanHQ/resources/margin_calculator.rb +9 -0
- data/lib/DhanHQ/resources/orders.rb +41 -41
- data/lib/DhanHQ/resources/pnl_exit.rb +37 -0
- data/lib/DhanHQ/resources/positions.rb +8 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/cmd_bus.rb +1 -1
- data/lib/DhanHQ/ws/orders/client.rb +6 -6
- data/lib/DhanHQ/ws/singleton_lock.rb +2 -1
- data/lib/dhanhq/analysis/options_buying_advisor.rb +2 -2
- data/lib/rubocop/cop/dhanhq/use_constants.rb +171 -0
- metadata +29 -24
- data/TODO-1.md +0 -14
- data/TODO.md +0 -127
- data/app/services/live/order_update_guard_support.rb +0 -75
- data/app/services/live/order_update_hub.rb +0 -76
- data/app/services/live/order_update_persistence_support.rb +0 -68
- data/docs/PR_2.2.0.md +0 -48
- data/examples/comprehensive_websocket_examples.rb +0 -148
- data/examples/instrument_finder_test.rb +0 -195
- data/examples/live_order_updates.rb +0 -118
- data/examples/market_depth_example.rb +0 -144
- data/examples/market_feed_example.rb +0 -81
- data/examples/order_update_example.rb +0 -105
- data/examples/trading_fields_example.rb +0 -215
- /data/docs/{live_order_updates.md → LIVE_ORDER_UPDATES.md} +0 -0
- /data/docs/{rails_integration.md → RAILS_INTEGRATION.md} +0 -0
|
@@ -139,7 +139,7 @@ module DhanHQ
|
|
|
139
139
|
# puts "Total open position value: ₹#{total_value}"
|
|
140
140
|
#
|
|
141
141
|
def active
|
|
142
|
-
all.reject { |position| position.position_type ==
|
|
142
|
+
all.reject { |position| position.position_type == DhanHQ::Constants::OrderStatus::CLOSED }
|
|
143
143
|
end
|
|
144
144
|
|
|
145
145
|
##
|
|
@@ -205,6 +205,28 @@ module DhanHQ
|
|
|
205
205
|
response = resource.convert(formatted_params)
|
|
206
206
|
success_response?(response) ? response : DhanHQ::ErrorObject.new(response)
|
|
207
207
|
end
|
|
208
|
+
|
|
209
|
+
##
|
|
210
|
+
# Exits all active positions and cancels all open orders for the current trading day.
|
|
211
|
+
#
|
|
212
|
+
# This is a safety endpoint for emergency position closure. It sends a DELETE request
|
|
213
|
+
# to close all positions and cancel all pending orders in one call.
|
|
214
|
+
#
|
|
215
|
+
# @return [Hash{Symbol => String}] Response hash containing operation result.
|
|
216
|
+
# - **:status** [String] "SUCCESS" or "ERROR"
|
|
217
|
+
# - **:message** [String] Description of the result
|
|
218
|
+
#
|
|
219
|
+
# @example Emergency exit all positions
|
|
220
|
+
# response = DhanHQ::Models::Position.exit_all!
|
|
221
|
+
# if response[:status] == "SUCCESS"
|
|
222
|
+
# puts "✓ All positions exited and orders cancelled"
|
|
223
|
+
# else
|
|
224
|
+
# puts "✗ Failed: #{response[:message]}"
|
|
225
|
+
# end
|
|
226
|
+
#
|
|
227
|
+
def exit_all!
|
|
228
|
+
resource.exit_all
|
|
229
|
+
end
|
|
208
230
|
end
|
|
209
231
|
end
|
|
210
232
|
end
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DhanHQ
|
|
4
|
+
module Models
|
|
5
|
+
##
|
|
6
|
+
# Utility model for parsing Dhan postback (webhook) payloads.
|
|
7
|
+
#
|
|
8
|
+
# Postback is a webhook mechanism where Dhan pushes order status updates to your
|
|
9
|
+
# configured URL. This model provides a convenient way to parse the incoming JSON
|
|
10
|
+
# payload into a typed, attribute-accessible object.
|
|
11
|
+
#
|
|
12
|
+
# @note Postback URL is configured in the Dhan web console when generating an access
|
|
13
|
+
# token. It will NOT work with localhost or 127.0.0.1.
|
|
14
|
+
#
|
|
15
|
+
# @example Parse postback payload in a Rails controller
|
|
16
|
+
# class DhanWebhooksController < ApplicationController
|
|
17
|
+
# skip_before_action :verify_authenticity_token
|
|
18
|
+
#
|
|
19
|
+
# def create
|
|
20
|
+
# postback = DhanHQ::Models::Postback.parse(request.body.read)
|
|
21
|
+
# case postback.order_status
|
|
22
|
+
# when "TRADED"
|
|
23
|
+
# handle_fill(postback)
|
|
24
|
+
# when "REJECTED"
|
|
25
|
+
# handle_rejection(postback)
|
|
26
|
+
# end
|
|
27
|
+
# head :ok
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# @example Parse postback payload from a hash
|
|
32
|
+
# postback = DhanHQ::Models::Postback.parse(params)
|
|
33
|
+
# puts "Order #{postback.order_id} is now #{postback.order_status}"
|
|
34
|
+
# puts "Filled: #{postback.filled_qty}/#{postback.quantity}"
|
|
35
|
+
#
|
|
36
|
+
class Postback < BaseModel
|
|
37
|
+
HTTP_PATH = nil # No API endpoint — postback is pushed to the user
|
|
38
|
+
|
|
39
|
+
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
|
40
|
+
:transaction_type, :exchange_segment, :product_type, :order_type,
|
|
41
|
+
:validity, :trading_symbol, :security_id, :quantity,
|
|
42
|
+
:disclosed_quantity, :price, :trigger_price, :after_market_order,
|
|
43
|
+
:bo_profit_value, :bo_stop_loss_value, :leg_name,
|
|
44
|
+
:create_time, :update_time, :exchange_time,
|
|
45
|
+
:drv_expiry_date, :drv_option_type, :drv_strike_price,
|
|
46
|
+
:oms_error_code, :oms_error_description, :filled_qty, :algo_id
|
|
47
|
+
|
|
48
|
+
class << self
|
|
49
|
+
##
|
|
50
|
+
# Parse a postback webhook payload into a Postback model instance.
|
|
51
|
+
#
|
|
52
|
+
# Accepts either a JSON string (from request body) or a Hash (from parsed params).
|
|
53
|
+
# Keys are normalized to snake_case automatically.
|
|
54
|
+
#
|
|
55
|
+
# @param payload [String, Hash] Raw JSON string or Hash from the webhook
|
|
56
|
+
#
|
|
57
|
+
# @return [Postback] Parsed Postback object with typed attributes
|
|
58
|
+
#
|
|
59
|
+
# @example From raw JSON string
|
|
60
|
+
# postback = DhanHQ::Models::Postback.parse('{"orderId":"123","orderStatus":"TRADED"}')
|
|
61
|
+
# puts postback.order_status # => "TRADED"
|
|
62
|
+
#
|
|
63
|
+
# @example From a hash
|
|
64
|
+
# postback = DhanHQ::Models::Postback.parse(order_id: "123", order_status: "TRADED")
|
|
65
|
+
# puts postback.order_id # => "123"
|
|
66
|
+
#
|
|
67
|
+
def parse(payload)
|
|
68
|
+
data = case payload
|
|
69
|
+
when String
|
|
70
|
+
JSON.parse(payload)
|
|
71
|
+
when Hash
|
|
72
|
+
payload
|
|
73
|
+
else
|
|
74
|
+
raise ArgumentError, "Expected String or Hash, got #{payload.class}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
new(data, skip_validation: true)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
##
|
|
82
|
+
# Whether the order has been fully traded.
|
|
83
|
+
#
|
|
84
|
+
# @return [Boolean]
|
|
85
|
+
def traded?
|
|
86
|
+
order_status == DhanHQ::Constants::OrderStatus::TRADED
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
##
|
|
90
|
+
# Whether the order was rejected.
|
|
91
|
+
#
|
|
92
|
+
# @return [Boolean]
|
|
93
|
+
def rejected?
|
|
94
|
+
order_status == DhanHQ::Constants::OrderStatus::REJECTED
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
##
|
|
98
|
+
# Whether the order is still pending.
|
|
99
|
+
#
|
|
100
|
+
# @return [Boolean]
|
|
101
|
+
def pending?
|
|
102
|
+
order_status == DhanHQ::Constants::OrderStatus::PENDING
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
##
|
|
106
|
+
# Whether the order was cancelled.
|
|
107
|
+
#
|
|
108
|
+
# @return [Boolean]
|
|
109
|
+
def cancelled?
|
|
110
|
+
order_status == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
111
|
+
end
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
@@ -125,16 +125,6 @@ module DhanHQ
|
|
|
125
125
|
|
|
126
126
|
##
|
|
127
127
|
# Profile responses are informational and not validated locally.
|
|
128
|
-
#
|
|
129
|
-
# Since profile data is read-only account metadata, no validation contract
|
|
130
|
-
# is needed. The API response is trusted as-is.
|
|
131
|
-
#
|
|
132
|
-
# @return [nil] Always returns nil as profiles are read-only and informational
|
|
133
|
-
#
|
|
134
|
-
# @api private
|
|
135
|
-
def validation_contract
|
|
136
|
-
nil
|
|
137
|
-
end
|
|
138
128
|
end
|
|
139
129
|
end
|
|
140
130
|
end
|
|
@@ -48,6 +48,8 @@ module DhanHQ
|
|
|
48
48
|
# order.cancel("STOP_LOSS_LEG")
|
|
49
49
|
#
|
|
50
50
|
class SuperOrder < BaseModel
|
|
51
|
+
include Concerns::ApiResponseHandler
|
|
52
|
+
|
|
51
53
|
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
|
52
54
|
:transaction_type, :exchange_segment, :product_type, :order_type,
|
|
53
55
|
:validity, :trading_symbol, :security_id, :quantity,
|
|
@@ -284,8 +286,12 @@ module DhanHQ
|
|
|
284
286
|
def modify(new_params)
|
|
285
287
|
raise "Order ID is required to modify a super order" unless id
|
|
286
288
|
|
|
289
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Modifying super order #{id}")
|
|
287
290
|
response = self.class.resource.update(id, new_params)
|
|
288
|
-
response["orderId"] == id
|
|
291
|
+
return false unless response.is_a?(Hash) && response["orderId"] == id
|
|
292
|
+
|
|
293
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Super order #{id} modified successfully")
|
|
294
|
+
true
|
|
289
295
|
end
|
|
290
296
|
|
|
291
297
|
##
|
|
@@ -318,11 +324,15 @@ module DhanHQ
|
|
|
318
324
|
# order.cancel("TARGET_LEG")
|
|
319
325
|
#
|
|
320
326
|
# @raise [RuntimeError] If order ID is missing
|
|
321
|
-
def cancel(leg_name =
|
|
327
|
+
def cancel(leg_name = DhanHQ::Constants::LegName::ENTRY_LEG)
|
|
322
328
|
raise "Order ID is required to cancel a super order" unless id
|
|
323
329
|
|
|
330
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Cancelling super order #{id} leg #{leg_name}")
|
|
324
331
|
response = self.class.resource.cancel(id, leg_name)
|
|
325
|
-
response["orderStatus"] ==
|
|
332
|
+
return false unless response.is_a?(Hash) && response["orderStatus"] == DhanHQ::Constants::OrderStatus::CANCELLED
|
|
333
|
+
|
|
334
|
+
DhanHQ.logger&.info("[DhanHQ::Models::SuperOrder] Super order #{id} leg #{leg_name} cancelled successfully")
|
|
335
|
+
true
|
|
326
336
|
end
|
|
327
337
|
end
|
|
328
338
|
end
|
data/lib/DhanHQ/models/trade.rb
CHANGED
|
@@ -49,8 +49,8 @@ module DhanHQ
|
|
|
49
49
|
# Provides a shared instance of the Trades resource for current day tradebook APIs.
|
|
50
50
|
#
|
|
51
51
|
# @return [DhanHQ::Resources::Trades] The Trades resource client instance
|
|
52
|
-
def
|
|
53
|
-
@
|
|
52
|
+
def resource
|
|
53
|
+
@resource ||= DhanHQ::Resources::Trades.new
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
##
|
|
@@ -107,10 +107,8 @@ module DhanHQ
|
|
|
107
107
|
# puts "P&L: ₹#{pnl}"
|
|
108
108
|
#
|
|
109
109
|
def today
|
|
110
|
-
response =
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
response.map { |trade_data| new(trade_data, skip_validation: true) }
|
|
110
|
+
response = resource.all
|
|
111
|
+
parse_collection_response(response)
|
|
114
112
|
end
|
|
115
113
|
|
|
116
114
|
##
|
|
@@ -145,7 +143,7 @@ module DhanHQ
|
|
|
145
143
|
raise DhanHQ::ValidationError, "Invalid order_id: #{validation_result.errors.to_h}"
|
|
146
144
|
end
|
|
147
145
|
|
|
148
|
-
response =
|
|
146
|
+
response = resource.find(order_id)
|
|
149
147
|
return nil unless response.is_a?(Hash) || (response.is_a?(Array) && response.any?)
|
|
150
148
|
|
|
151
149
|
data = response.is_a?(Array) ? response.first : response
|
|
@@ -248,16 +246,6 @@ module DhanHQ
|
|
|
248
246
|
alias all history
|
|
249
247
|
end
|
|
250
248
|
|
|
251
|
-
##
|
|
252
|
-
# Trade objects are read-only, so no validation contract needed.
|
|
253
|
-
#
|
|
254
|
-
# @return [nil] Always returns nil as trades are read-only
|
|
255
|
-
#
|
|
256
|
-
# @api private
|
|
257
|
-
def validation_contract
|
|
258
|
-
nil
|
|
259
|
-
end
|
|
260
|
-
|
|
261
249
|
##
|
|
262
250
|
# Checks if the trade is a BUY transaction.
|
|
263
251
|
#
|
|
@@ -270,7 +258,7 @@ module DhanHQ
|
|
|
270
258
|
# end
|
|
271
259
|
#
|
|
272
260
|
def buy?
|
|
273
|
-
transaction_type ==
|
|
261
|
+
transaction_type == DhanHQ::Constants::TransactionType::BUY
|
|
274
262
|
end
|
|
275
263
|
|
|
276
264
|
##
|
|
@@ -285,7 +273,7 @@ module DhanHQ
|
|
|
285
273
|
# end
|
|
286
274
|
#
|
|
287
275
|
def sell?
|
|
288
|
-
transaction_type ==
|
|
276
|
+
transaction_type == DhanHQ::Constants::TransactionType::SELL
|
|
289
277
|
end
|
|
290
278
|
|
|
291
279
|
##
|
|
@@ -299,7 +287,7 @@ module DhanHQ
|
|
|
299
287
|
# puts "Equity trades: #{equity_trades.count}"
|
|
300
288
|
#
|
|
301
289
|
def equity?
|
|
302
|
-
instrument ==
|
|
290
|
+
instrument == DhanHQ::Constants::InstrumentType::EQUITY
|
|
303
291
|
end
|
|
304
292
|
|
|
305
293
|
##
|
|
@@ -327,7 +315,7 @@ module DhanHQ
|
|
|
327
315
|
# puts "Option trades: #{option_trades.count}"
|
|
328
316
|
#
|
|
329
317
|
def option?
|
|
330
|
-
|
|
318
|
+
[DhanHQ::Constants::OptionType::CALL, DhanHQ::Constants::OptionType::PUT].include?(drv_option_type)
|
|
331
319
|
end
|
|
332
320
|
|
|
333
321
|
##
|
|
@@ -341,7 +329,7 @@ module DhanHQ
|
|
|
341
329
|
# puts "Call option trades: #{call_trades.count}"
|
|
342
330
|
#
|
|
343
331
|
def call_option?
|
|
344
|
-
drv_option_type ==
|
|
332
|
+
drv_option_type == DhanHQ::Constants::OptionType::CALL
|
|
345
333
|
end
|
|
346
334
|
|
|
347
335
|
##
|
|
@@ -355,7 +343,7 @@ module DhanHQ
|
|
|
355
343
|
# puts "Put option trades: #{put_trades.count}"
|
|
356
344
|
#
|
|
357
345
|
def put_option?
|
|
358
|
-
drv_option_type ==
|
|
346
|
+
drv_option_type == DhanHQ::Constants::OptionType::PUT
|
|
359
347
|
end
|
|
360
348
|
|
|
361
349
|
##
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Resources
|
|
5
|
-
# Resource for IP whitelist per API docs: GET /ip/getIP, POST /ip/setIP, PUT /ip/modifyIP.
|
|
5
|
+
# Resource for IP whitelist per API docs: GET /v2/ip/getIP, POST /v2/ip/setIP, PUT /v2/ip/modifyIP.
|
|
6
|
+
# Set/Modify require dhanClientId, ip, ipFlag (PRIMARY | SECONDARY). See dhanhq.co/docs/v2/authentication/#setup-static-ip
|
|
6
7
|
class IPSetup < BaseAPI
|
|
7
8
|
API_TYPE = :order_api
|
|
8
9
|
HTTP_PATH = "/ip"
|
|
@@ -11,12 +12,22 @@ module DhanHQ
|
|
|
11
12
|
get("/getIP")
|
|
12
13
|
end
|
|
13
14
|
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
# @param ip [String] Static IP (IPv4 or IPv6)
|
|
16
|
+
# @param ip_flag [String] "PRIMARY" or "SECONDARY"
|
|
17
|
+
# @param dhan_client_id [String, nil] Defaults to DhanHQ.configuration.client_id when nil
|
|
18
|
+
def set(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)
|
|
19
|
+
params = { ip: ip, ip_flag: ip_flag }
|
|
20
|
+
params[:dhan_client_id] = dhan_client_id || DhanHQ.configuration&.client_id
|
|
21
|
+
post("/setIP", params: params)
|
|
16
22
|
end
|
|
17
23
|
|
|
18
|
-
|
|
19
|
-
|
|
24
|
+
# @param ip [String] Static IP (IPv4 or IPv6)
|
|
25
|
+
# @param ip_flag [String] "PRIMARY" or "SECONDARY"
|
|
26
|
+
# @param dhan_client_id [String, nil] Defaults to DhanHQ.configuration.client_id when nil
|
|
27
|
+
def update(ip:, ip_flag: "PRIMARY", dhan_client_id: nil)
|
|
28
|
+
params = { ip: ip, ip_flag: ip_flag }
|
|
29
|
+
params[:dhan_client_id] = dhan_client_id || DhanHQ.configuration&.client_id
|
|
30
|
+
put("/modifyIP", params: params)
|
|
20
31
|
end
|
|
21
32
|
end
|
|
22
33
|
end
|
|
@@ -1,20 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "cgi"
|
|
4
|
+
|
|
3
5
|
module DhanHQ
|
|
4
6
|
module Resources
|
|
5
7
|
# Resource client to control the trading kill switch feature.
|
|
8
|
+
# API expects killSwitchStatus as query parameter (no body). See dhanhq.co/docs/v2/traders-control/
|
|
6
9
|
class KillSwitch < BaseAPI
|
|
7
|
-
|
|
8
|
-
API_TYPE = :order_api
|
|
9
|
-
# Base path for kill switch operations.
|
|
10
|
+
API_TYPE = :order_api
|
|
10
11
|
HTTP_PATH = "/v2/killswitch"
|
|
11
12
|
|
|
12
|
-
# Enables or disables the kill switch.
|
|
13
|
+
# Enables or disables the kill switch via query parameter (doc: no body).
|
|
13
14
|
#
|
|
14
|
-
# @param
|
|
15
|
+
# @param status [String] "ACTIVATE" or "DEACTIVATE"
|
|
15
16
|
# @return [Hash]
|
|
16
|
-
def update(
|
|
17
|
-
|
|
17
|
+
def update(status)
|
|
18
|
+
query = "?killSwitchStatus=#{CGI.escape(status.to_s)}"
|
|
19
|
+
handle_response(client.post(build_path(query), {}))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
##
|
|
23
|
+
# Fetches the current kill switch status.
|
|
24
|
+
#
|
|
25
|
+
# @return [Hash] API response containing dhan_client_id and kill_switch_status.
|
|
26
|
+
def status
|
|
27
|
+
get("")
|
|
18
28
|
end
|
|
19
29
|
end
|
|
20
30
|
end
|
|
@@ -17,6 +17,15 @@ module DhanHQ
|
|
|
17
17
|
def calculate(params)
|
|
18
18
|
post("", params: params)
|
|
19
19
|
end
|
|
20
|
+
|
|
21
|
+
##
|
|
22
|
+
# Calculate margin requirements for multiple scripts in one request.
|
|
23
|
+
#
|
|
24
|
+
# @param params [Hash] Request parameters including scripList, includePosition, includeOrder.
|
|
25
|
+
# @return [Hash] API response containing combined margin details with hedge benefit.
|
|
26
|
+
def calculate_multi(params)
|
|
27
|
+
post("/multi", params: params)
|
|
28
|
+
end
|
|
20
29
|
end
|
|
21
30
|
end
|
|
22
31
|
end
|
|
@@ -1,70 +1,70 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module DhanHQ
|
|
4
|
-
# REST API wrappers grouped by resource type.
|
|
5
4
|
module Resources
|
|
6
|
-
#
|
|
5
|
+
# Handles order placement, modification, and cancellation
|
|
7
6
|
class Orders < BaseAPI
|
|
8
|
-
# Orders are routed through the trading API tier.
|
|
9
7
|
API_TYPE = :order_api
|
|
10
|
-
# Base path for order endpoints.
|
|
11
8
|
HTTP_PATH = "/v2/orders"
|
|
12
9
|
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
16
|
-
def all
|
|
17
|
-
get("")
|
|
18
|
-
end
|
|
10
|
+
# --------------------------------------------------
|
|
11
|
+
# PUBLIC API
|
|
12
|
+
# --------------------------------------------------
|
|
19
13
|
|
|
20
|
-
# Places a new order using the provided payload.
|
|
21
|
-
#
|
|
22
|
-
# @param params [Hash]
|
|
23
|
-
# @return [Hash]
|
|
24
14
|
def create(params)
|
|
15
|
+
validate_place_order!(params)
|
|
25
16
|
post("", params: params)
|
|
26
17
|
end
|
|
27
18
|
|
|
28
|
-
# Fetches a single order by broker order id.
|
|
29
|
-
#
|
|
30
|
-
# @param order_id [String]
|
|
31
|
-
# @return [Hash]
|
|
32
|
-
def find(order_id)
|
|
33
|
-
get("/#{order_id}")
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
# Modifies an existing order.
|
|
37
|
-
#
|
|
38
|
-
# @param order_id [String]
|
|
39
|
-
# @param params [Hash]
|
|
40
|
-
# @return [Hash]
|
|
41
19
|
def update(order_id, params)
|
|
20
|
+
validate_modify_order!(params.merge(order_id: order_id))
|
|
42
21
|
put("/#{order_id}", params: params)
|
|
43
22
|
end
|
|
44
23
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
24
|
+
def slicing(params)
|
|
25
|
+
validate_place_order!(params)
|
|
26
|
+
post("/slicing", params: params)
|
|
27
|
+
end
|
|
28
|
+
|
|
49
29
|
def cancel(order_id)
|
|
50
30
|
delete("/#{order_id}")
|
|
51
31
|
end
|
|
52
32
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
def
|
|
58
|
-
|
|
33
|
+
def all
|
|
34
|
+
get("")
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def find(order_id)
|
|
38
|
+
get("/#{order_id}")
|
|
59
39
|
end
|
|
60
40
|
|
|
61
|
-
# Retrieve an order by client-supplied correlation id.
|
|
62
|
-
#
|
|
63
|
-
# @param correlation_id [String]
|
|
64
|
-
# @return [Hash]
|
|
65
41
|
def by_correlation(correlation_id)
|
|
66
42
|
get("/external/#{correlation_id}")
|
|
67
43
|
end
|
|
44
|
+
|
|
45
|
+
# --------------------------------------------------
|
|
46
|
+
# VALIDATION LAYER
|
|
47
|
+
# --------------------------------------------------
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def validate_place_order!(params)
|
|
52
|
+
result = Contracts::PlaceOrderContract.new.call(normalize_keys_for_validation(params))
|
|
53
|
+
raise_validation_error!(result) unless result.success?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_modify_order!(params)
|
|
57
|
+
result = Contracts::ModifyOrderContract.new.call(normalize_keys_for_validation(params))
|
|
58
|
+
raise_validation_error!(result) unless result.success?
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def normalize_keys_for_validation(params)
|
|
62
|
+
snake_case(params)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def raise_validation_error!(result)
|
|
66
|
+
raise DhanHQ::Error, "Validation Error: #{result.errors.to_h}"
|
|
67
|
+
end
|
|
68
68
|
end
|
|
69
69
|
end
|
|
70
70
|
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DhanHQ
|
|
4
|
+
module Resources
|
|
5
|
+
# Resource for P&L Based Exit endpoints per https://dhanhq.co/docs/v2/traders-control/
|
|
6
|
+
# POST /v2/pnlExit — configure, DELETE /v2/pnlExit — stop, GET /v2/pnlExit — status.
|
|
7
|
+
class PnlExit < BaseAPI
|
|
8
|
+
API_TYPE = :order_api
|
|
9
|
+
HTTP_PATH = "/v2/pnlExit"
|
|
10
|
+
|
|
11
|
+
##
|
|
12
|
+
# Configure automatic P&L-based position exit.
|
|
13
|
+
#
|
|
14
|
+
# @param params [Hash] Request body with profitValue, lossValue, productType, enableKillSwitch.
|
|
15
|
+
# @return [Hash] API response containing pnlExitStatus and message.
|
|
16
|
+
def configure(params)
|
|
17
|
+
post("", params: params)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
##
|
|
21
|
+
# Stop/disable the active P&L-based exit configuration.
|
|
22
|
+
#
|
|
23
|
+
# @return [Hash] API response containing pnlExitStatus and message.
|
|
24
|
+
def stop
|
|
25
|
+
delete("")
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Fetch the currently active P&L-based exit configuration.
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash] API response containing pnlExitStatus, profit, loss, segments, enable_kill_switch.
|
|
32
|
+
def status
|
|
33
|
+
get("")
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -24,6 +24,14 @@ module DhanHQ
|
|
|
24
24
|
def convert(params)
|
|
25
25
|
post("/convert", params: params)
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
##
|
|
29
|
+
# Exit all active positions and cancel all open orders.
|
|
30
|
+
#
|
|
31
|
+
# @return [Hash] API response containing status and message.
|
|
32
|
+
def exit_all
|
|
33
|
+
delete("")
|
|
34
|
+
end
|
|
27
35
|
end
|
|
28
36
|
end
|
|
29
37
|
end
|
data/lib/DhanHQ/version.rb
CHANGED
data/lib/DhanHQ/ws/cmd_bus.rb
CHANGED
|
@@ -201,17 +201,17 @@ module DhanHQ
|
|
|
201
201
|
# @param _previous_state [OrderUpdate, nil] Previous order state (unused parameter)
|
|
202
202
|
def emit_status_specific_events(order_update, _previous_state)
|
|
203
203
|
case order_update.status
|
|
204
|
-
when
|
|
204
|
+
when DhanHQ::Constants::OrderStatus::TRANSIT
|
|
205
205
|
emit(:order_transit, order_update)
|
|
206
|
-
when
|
|
206
|
+
when DhanHQ::Constants::OrderStatus::PENDING
|
|
207
207
|
emit(:order_pending, order_update)
|
|
208
|
-
when
|
|
208
|
+
when DhanHQ::Constants::OrderStatus::REJECTED
|
|
209
209
|
emit(:order_rejected, order_update)
|
|
210
|
-
when
|
|
210
|
+
when DhanHQ::Constants::OrderStatus::CANCELLED
|
|
211
211
|
emit(:order_cancelled, order_update)
|
|
212
|
-
when
|
|
212
|
+
when DhanHQ::Constants::OrderStatus::TRADED
|
|
213
213
|
emit(:order_traded, order_update)
|
|
214
|
-
when
|
|
214
|
+
when DhanHQ::Constants::OrderStatus::EXPIRED
|
|
215
215
|
emit(:order_expired, order_update)
|
|
216
216
|
end
|
|
217
217
|
end
|
|
@@ -14,7 +14,8 @@ module DhanHQ
|
|
|
14
14
|
key = Digest::SHA256.hexdigest("#{client_id}:#{token}")[0, 12]
|
|
15
15
|
@path = File.expand_path("tmp/dhanhq_ws_#{key}.lock", Dir.pwd)
|
|
16
16
|
FileUtils.mkdir_p(File.dirname(@path))
|
|
17
|
-
|
|
17
|
+
# Lock file must stay open until release!; block form would close it and release the lock.
|
|
18
|
+
@fh = File.open(@path, File::RDWR | File::CREAT, 0o644) # rubocop:disable Style/FileOpen
|
|
18
19
|
end
|
|
19
20
|
|
|
20
21
|
# Attempts to acquire the lock for the current process.
|
|
@@ -91,7 +91,7 @@ module DhanHQ
|
|
|
91
91
|
|
|
92
92
|
# Use OptionChain model: pick nearest/next expiry and fetch chain
|
|
93
93
|
sid = @data.dig(:meta, :security_id)
|
|
94
|
-
seg = @data.dig(:meta, :exchange_segment) ||
|
|
94
|
+
seg = @data.dig(:meta, :exchange_segment) || DhanHQ::Constants::ExchangeSegment::IDX_I
|
|
95
95
|
return unless sid && seg
|
|
96
96
|
|
|
97
97
|
expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(underlying_scrip: sid.to_i, underlying_seg: seg)
|
|
@@ -128,7 +128,7 @@ module DhanHQ
|
|
|
128
128
|
end
|
|
129
129
|
|
|
130
130
|
def index_instrument?(meta)
|
|
131
|
-
meta[:instrument].to_s ==
|
|
131
|
+
meta[:instrument].to_s == DhanHQ::Constants::InstrumentType::INDEX || meta[:exchange_segment].to_s == DhanHQ::Constants::ExchangeSegment::IDX_I
|
|
132
132
|
end
|
|
133
133
|
|
|
134
134
|
def select_strike(side:, moneyness:)
|