DhanHQ 2.1.8 → 2.2.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 +1 -1
- data/.rubocop_todo.yml +143 -118
- data/CHANGELOG.md +177 -0
- data/CODE_REVIEW_ISSUES.md +397 -0
- data/FIXES_APPLIED.md +373 -0
- data/GUIDE.md +41 -0
- data/README.md +55 -0
- data/RELEASING.md +60 -0
- data/REVIEW_SUMMARY.md +120 -0
- data/VERSION_UPDATE.md +82 -0
- data/core +0 -0
- data/docs/AUTHENTICATION.md +63 -0
- data/docs/DATA_API_PARAMETERS.md +278 -0
- data/docs/PR_2.2.0.md +48 -0
- data/docs/RELEASE_GUIDE.md +492 -0
- data/docs/TESTING_GUIDE.md +1514 -0
- data/docs/live_order_updates.md +25 -1
- data/docs/rails_integration.md +29 -0
- data/docs/websocket_integration.md +22 -0
- data/lib/DhanHQ/client.rb +65 -9
- data/lib/DhanHQ/configuration.rb +26 -0
- data/lib/DhanHQ/constants.rb +1 -1
- data/lib/DhanHQ/contracts/expired_options_data_contract.rb +6 -6
- data/lib/DhanHQ/contracts/place_order_contract.rb +51 -0
- data/lib/DhanHQ/core/base_model.rb +26 -11
- data/lib/DhanHQ/errors.rb +4 -0
- data/lib/DhanHQ/helpers/request_helper.rb +17 -2
- data/lib/DhanHQ/helpers/response_helper.rb +34 -13
- data/lib/DhanHQ/models/edis.rb +150 -14
- data/lib/DhanHQ/models/expired_options_data.rb +307 -88
- data/lib/DhanHQ/models/forever_order.rb +261 -22
- data/lib/DhanHQ/models/funds.rb +76 -10
- data/lib/DhanHQ/models/historical_data.rb +148 -31
- data/lib/DhanHQ/models/holding.rb +82 -6
- data/lib/DhanHQ/models/instrument_helpers.rb +4 -4
- data/lib/DhanHQ/models/kill_switch.rb +113 -11
- data/lib/DhanHQ/models/ledger_entry.rb +101 -13
- data/lib/DhanHQ/models/margin.rb +133 -8
- data/lib/DhanHQ/models/market_feed.rb +181 -17
- data/lib/DhanHQ/models/option_chain.rb +184 -12
- data/lib/DhanHQ/models/order.rb +418 -36
- data/lib/DhanHQ/models/order_update.rb +0 -4
- data/lib/DhanHQ/models/position.rb +161 -10
- data/lib/DhanHQ/models/profile.rb +103 -7
- data/lib/DhanHQ/models/super_order.rb +275 -15
- data/lib/DhanHQ/models/trade.rb +279 -26
- data/lib/DhanHQ/rate_limiter.rb +40 -6
- data/lib/DhanHQ/resources/expired_options_data.rb +1 -1
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/client.rb +11 -5
- data/lib/DhanHQ/ws/connection.rb +16 -2
- data/lib/DhanHQ/ws/market_depth/client.rb +2 -1
- data/lib/DhanHQ/ws/market_depth.rb +12 -12
- data/lib/DhanHQ/ws/orders/client.rb +78 -12
- data/lib/DhanHQ/ws/orders/connection.rb +2 -1
- data/lib/DhanHQ/ws/orders.rb +2 -1
- metadata +18 -5
- data/lib/DhanHQ/contracts/modify_order_contract_copy.rb +0 -100
data/lib/DhanHQ/models/order.rb
CHANGED
|
@@ -6,7 +6,45 @@ require_relative "../contracts/modify_order_contract"
|
|
|
6
6
|
module DhanHQ
|
|
7
7
|
# ActiveRecord-style models built on top of the REST resources.
|
|
8
8
|
module Models
|
|
9
|
-
|
|
9
|
+
##
|
|
10
|
+
# Model for managing equity and F&O orders.
|
|
11
|
+
#
|
|
12
|
+
# The Order Management API lets you place new orders, cancel or modify pending orders,
|
|
13
|
+
# and retrieve order status, trade status, order book, and trade book. This model provides
|
|
14
|
+
# an ActiveRecord-style interface for order management operations.
|
|
15
|
+
#
|
|
16
|
+
# @note **Static IP Whitelisting**: Order placement, modification, and cancellation APIs
|
|
17
|
+
# require Static IP whitelisting. Ensure your IP is whitelisted before using these endpoints.
|
|
18
|
+
#
|
|
19
|
+
# @example Place a new market order
|
|
20
|
+
# order = DhanHQ::Models::Order.place(
|
|
21
|
+
# dhan_client_id: "1000000003",
|
|
22
|
+
# transaction_type: "BUY",
|
|
23
|
+
# exchange_segment: "NSE_EQ",
|
|
24
|
+
# product_type: "INTRADAY",
|
|
25
|
+
# order_type: "MARKET",
|
|
26
|
+
# validity: "DAY",
|
|
27
|
+
# security_id: "11536",
|
|
28
|
+
# quantity: 5
|
|
29
|
+
# )
|
|
30
|
+
# puts "Order placed: #{order.order_id} - #{order.order_status}"
|
|
31
|
+
#
|
|
32
|
+
# @example Modify an existing order
|
|
33
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
34
|
+
# order.modify(price: 3345.8, quantity: 40)
|
|
35
|
+
# puts "Order modified: #{order.order_status}"
|
|
36
|
+
#
|
|
37
|
+
# @example Cancel an order
|
|
38
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
39
|
+
# if order.cancel
|
|
40
|
+
# puts "Order cancelled successfully"
|
|
41
|
+
# end
|
|
42
|
+
#
|
|
43
|
+
# @example Fetch all orders for the day
|
|
44
|
+
# orders = DhanHQ::Models::Order.all
|
|
45
|
+
# pending_orders = orders.select { |o| o.order_status == "PENDING" }
|
|
46
|
+
# puts "Pending orders: #{pending_orders.count}"
|
|
47
|
+
#
|
|
10
48
|
class Order < BaseModel
|
|
11
49
|
# Attributes eligible for modification requests.
|
|
12
50
|
MODIFIABLE_FIELDS = %i[
|
|
@@ -35,17 +73,36 @@ module DhanHQ
|
|
|
35
73
|
|
|
36
74
|
class << self
|
|
37
75
|
##
|
|
38
|
-
# Provides a
|
|
76
|
+
# Provides a shared instance of the Orders resource.
|
|
39
77
|
#
|
|
40
|
-
# @return [DhanHQ::Resources::Orders]
|
|
78
|
+
# @return [DhanHQ::Resources::Orders] The Orders resource client instance
|
|
41
79
|
def resource
|
|
42
80
|
@resource ||= DhanHQ::Resources::Orders.new
|
|
43
81
|
end
|
|
44
82
|
|
|
45
83
|
##
|
|
46
|
-
#
|
|
84
|
+
# Retrieves all orders placed during the current trading day.
|
|
85
|
+
#
|
|
86
|
+
# Fetches an array of all orders requested in a day with their last updated status.
|
|
87
|
+
# Returns empty array if no orders are found or if the response is not in the expected format.
|
|
88
|
+
#
|
|
89
|
+
# @return [Array<Order>] Array of Order objects. Returns empty array if no orders exist.
|
|
90
|
+
# Each Order object contains comprehensive order details including status, timestamps,
|
|
91
|
+
# quantities, prices, and error information if applicable.
|
|
92
|
+
#
|
|
93
|
+
# @example Fetch all orders for the day
|
|
94
|
+
# orders = DhanHQ::Models::Order.all
|
|
95
|
+
# puts "Total orders: #{orders.count}"
|
|
96
|
+
# orders.each do |order|
|
|
97
|
+
# puts "#{order.order_id}: #{order.order_status} - #{order.transaction_type} #{order.quantity}"
|
|
98
|
+
# end
|
|
99
|
+
#
|
|
100
|
+
# @example Filter orders by status
|
|
101
|
+
# orders = DhanHQ::Models::Order.all
|
|
102
|
+
# pending = orders.select { |o| o.order_status == "PENDING" }
|
|
103
|
+
# traded = orders.select { |o| o.order_status == "TRADED" }
|
|
104
|
+
# puts "Pending: #{pending.count}, Traded: #{traded.count}"
|
|
47
105
|
#
|
|
48
|
-
# @return [Array<Order>]
|
|
49
106
|
def all
|
|
50
107
|
response = resource.all
|
|
51
108
|
return [] unless response.is_a?(Array)
|
|
@@ -54,10 +111,61 @@ module DhanHQ
|
|
|
54
111
|
end
|
|
55
112
|
|
|
56
113
|
##
|
|
57
|
-
#
|
|
114
|
+
# Retrieves the details and status of a specific order by order ID.
|
|
115
|
+
#
|
|
116
|
+
# Fetches comprehensive order information including all order parameters, status,
|
|
117
|
+
# timestamps, trade information, and error details if the order was rejected.
|
|
118
|
+
#
|
|
119
|
+
# @param order_id [String] Order-specific identification generated by Dhan
|
|
120
|
+
#
|
|
121
|
+
# @return [Order, nil] Order object with complete details if found, nil otherwise.
|
|
122
|
+
# Response structure includes (keys normalized to snake_case):
|
|
123
|
+
# - **:dhan_client_id** [String] User-specific identification generated by Dhan
|
|
124
|
+
# - **:order_id** [String] Order-specific identification generated by Dhan
|
|
125
|
+
# - **:correlation_id** [String] User/partner generated ID for tracking
|
|
126
|
+
# - **:order_status** [String] Last updated status of the order.
|
|
127
|
+
# Valid values: "TRANSIT", "PENDING", "REJECTED", "CANCELLED", "PART_TRADED",
|
|
128
|
+
# "TRADED", "EXPIRED"
|
|
129
|
+
# - **:transaction_type** [String] The trading side of transaction. "BUY" or "SELL"
|
|
130
|
+
# - **:exchange_segment** [String] Exchange segment of instrument
|
|
131
|
+
# - **:product_type** [String] Product type. Valid values: "CNC", "INTRADAY",
|
|
132
|
+
# "MARGIN", "MTF", "CO", "BO"
|
|
133
|
+
# - **:order_type** [String] Order type. Valid values: "LIMIT", "MARKET",
|
|
134
|
+
# "STOP_LOSS", "STOP_LOSS_MARKET"
|
|
135
|
+
# - **:validity** [String] Validity of order. "DAY" or "IOC"
|
|
136
|
+
# - **:trading_symbol** [String] Trading symbol of the instrument
|
|
137
|
+
# - **:security_id** [String] Exchange standard ID for each scrip
|
|
138
|
+
# - **:quantity** [Integer] Number of shares for the order
|
|
139
|
+
# - **:disclosed_quantity** [Integer] Number of shares visible
|
|
140
|
+
# - **:price** [Float] Price at which order is placed
|
|
141
|
+
# - **:trigger_price** [Float] Price at which order is triggered (for SL-M, SL-L, CO & BO)
|
|
142
|
+
# - **:after_market_order** [Boolean] Whether the order placed is an AMO
|
|
143
|
+
# - **:bo_profit_value** [Float] Bracket Order Target Price change
|
|
144
|
+
# - **:bo_stop_loss_value** [Float] Bracket Order Stop Loss Price change
|
|
145
|
+
# - **:leg_name** [String] Leg identification in case of BO.
|
|
146
|
+
# Valid values: "ENTRY_LEG", "TARGET_LEG", "STOP_LOSS_LEG"
|
|
147
|
+
# - **:create_time** [String] Time at which the order is created
|
|
148
|
+
# - **:update_time** [String] Time at which the last activity happened
|
|
149
|
+
# - **:exchange_time** [String] Time at which order reached at exchange
|
|
150
|
+
# - **:drv_expiry_date** [String, nil] For F&O, expiry date of contract
|
|
151
|
+
# - **:drv_option_type** [String, nil] Type of Option. "CALL" or "PUT"
|
|
152
|
+
# - **:drv_strike_price** [Float] For Options, Strike Price
|
|
153
|
+
# - **:oms_error_code** [String, nil] Error code if the order is rejected or failed
|
|
154
|
+
# - **:oms_error_description** [String, nil] Description of error if the order is rejected
|
|
155
|
+
# - **:algo_id** [String] Exchange Algo ID for Dhan
|
|
156
|
+
# - **:remaining_quantity** [Integer] Quantity pending at the exchange to be traded
|
|
157
|
+
# (quantity - filled_qty)
|
|
158
|
+
# - **:average_traded_price** [Float] Average price at which order is traded
|
|
159
|
+
# - **:filled_qty** [Integer] Quantity of order traded on Exchange
|
|
160
|
+
#
|
|
161
|
+
# @example Fetch order details
|
|
162
|
+
# order = DhanHQ::Models::Order.find("112111182198")
|
|
163
|
+
# if order
|
|
164
|
+
# puts "Status: #{order.order_status}"
|
|
165
|
+
# puts "Quantity: #{order.quantity}, Filled: #{order.filled_qty}"
|
|
166
|
+
# puts "Remaining: #{order.remaining_quantity}"
|
|
167
|
+
# end
|
|
58
168
|
#
|
|
59
|
-
# @param order_id [String]
|
|
60
|
-
# @return [Order, nil]
|
|
61
169
|
def find(order_id)
|
|
62
170
|
response = resource.find(order_id)
|
|
63
171
|
return nil unless response.is_a?(Hash) || (response.is_a?(Array) && response.any?)
|
|
@@ -67,10 +175,24 @@ module DhanHQ
|
|
|
67
175
|
end
|
|
68
176
|
|
|
69
177
|
##
|
|
70
|
-
#
|
|
178
|
+
# Retrieves the status of an order using a user-supplied correlation ID.
|
|
179
|
+
#
|
|
180
|
+
# Useful when you need to track orders using your own correlation ID instead of
|
|
181
|
+
# the Dhan-generated order ID. This is helpful if you missed the order ID due to
|
|
182
|
+
# unforeseen reasons but have your correlation ID stored.
|
|
183
|
+
#
|
|
184
|
+
# @param correlation_id [String] The user/partner generated ID for tracking
|
|
185
|
+
#
|
|
186
|
+
# @return [Order, nil] Order object with complete details if found, nil otherwise.
|
|
187
|
+
# Response structure is the same as {find}.
|
|
188
|
+
#
|
|
189
|
+
# @example Fetch order by correlation ID
|
|
190
|
+
# order = DhanHQ::Models::Order.find_by_correlation("123abc678")
|
|
191
|
+
# if order
|
|
192
|
+
# puts "Order ID: #{order.order_id}"
|
|
193
|
+
# puts "Status: #{order.order_status}"
|
|
194
|
+
# end
|
|
71
195
|
#
|
|
72
|
-
# @param correlation_id [String]
|
|
73
|
-
# @return [Order, nil]
|
|
74
196
|
def find_by_correlation(correlation_id)
|
|
75
197
|
response = resource.by_correlation(correlation_id)
|
|
76
198
|
return nil unless response[:status] == "success"
|
|
@@ -78,12 +200,94 @@ module DhanHQ
|
|
|
78
200
|
new(response, skip_validation: true)
|
|
79
201
|
end
|
|
80
202
|
|
|
81
|
-
|
|
203
|
+
##
|
|
204
|
+
# Places a new order.
|
|
205
|
+
#
|
|
206
|
+
# Validates order parameters and places a new order via the API. After successful
|
|
207
|
+
# placement, fetches and returns the complete order details including the generated
|
|
208
|
+
# order ID and current status.
|
|
209
|
+
#
|
|
210
|
+
# @param params [Hash{Symbol => String, Integer, Float, Boolean}] Order placement parameters
|
|
211
|
+
# @option params [String] :dhan_client_id (required) User-specific identification generated by Dhan.
|
|
212
|
+
# Must be explicitly provided in the params hash
|
|
213
|
+
# @option params [String] :correlation_id (optional) User/partner generated ID for tracking.
|
|
214
|
+
# Max length: 25 characters
|
|
215
|
+
# @option params [String] :transaction_type (required) The trading side of transaction.
|
|
216
|
+
# Valid values: "BUY", "SELL"
|
|
217
|
+
# @option params [String] :exchange_segment (required) Exchange and segment of instrument.
|
|
218
|
+
# Valid values: See {DhanHQ::Constants::EXCHANGE_SEGMENTS}
|
|
219
|
+
# @option params [String] :product_type (required) Product type.
|
|
220
|
+
# Valid values: "CNC", "INTRADAY", "MARGIN", "MTF", "CO", "BO"
|
|
221
|
+
# @option params [String] :order_type (required) Order type.
|
|
222
|
+
# Valid values: "LIMIT", "MARKET", "STOP_LOSS", "STOP_LOSS_MARKET"
|
|
223
|
+
# @option params [String] :validity (required) Validity of order for execution.
|
|
224
|
+
# Valid values: "DAY", "IOC"
|
|
225
|
+
# @option params [String] :security_id (required) Exchange standard ID for each scrip
|
|
226
|
+
# @option params [Integer] :quantity (required) Number of shares for the order. Must be greater than 0
|
|
227
|
+
# @option params [Integer] :disclosed_quantity (optional) Number of shares visible to market.
|
|
228
|
+
# Keep more than 30% of quantity if providing. Must be >= 0 if provided
|
|
229
|
+
# @option params [Float] :price (conditionally required) Price at which order is placed.
|
|
230
|
+
# Required for LIMIT orders. Must be > 0 if provided
|
|
231
|
+
# @option params [Float] :trigger_price (conditionally required) Price at which the order is triggered.
|
|
232
|
+
# Required for STOP_LOSS and STOP_LOSS_MARKET order types. Must be > 0 if provided
|
|
233
|
+
# @option params [Boolean] :after_market_order (optional) Flag for orders placed after market hours
|
|
234
|
+
# @option params [String] :amo_time (conditionally required) Timing to place the after market order.
|
|
235
|
+
# Required when after_market_order is true.
|
|
236
|
+
# Valid values: "PRE_OPEN", "OPEN", "OPEN_30", "OPEN_60"
|
|
237
|
+
# @option params [Float] :bo_profit_value (conditionally required) Bracket Order Target Price change.
|
|
238
|
+
# Required for BO product type. Must be > 0 if provided
|
|
239
|
+
# @option params [Float] :bo_stop_loss_value (conditionally required) Bracket Order Stop Loss Price change.
|
|
240
|
+
# Required for BO product type. Must be > 0 if provided
|
|
241
|
+
#
|
|
242
|
+
# @return [Order, nil] Order object with complete details if placement succeeds, nil otherwise.
|
|
243
|
+
# The order object includes the generated :order_id and current :order_status.
|
|
82
244
|
#
|
|
83
|
-
# @
|
|
84
|
-
#
|
|
245
|
+
# @example Place a market order
|
|
246
|
+
# order = DhanHQ::Models::Order.place(
|
|
247
|
+
# dhan_client_id: "1000000003",
|
|
248
|
+
# transaction_type: "BUY",
|
|
249
|
+
# exchange_segment: "NSE_EQ",
|
|
250
|
+
# product_type: "INTRADAY",
|
|
251
|
+
# order_type: "MARKET",
|
|
252
|
+
# validity: "DAY",
|
|
253
|
+
# security_id: "11536",
|
|
254
|
+
# quantity: 5
|
|
255
|
+
# )
|
|
256
|
+
# puts "Order ID: #{order.order_id}"
|
|
257
|
+
# puts "Status: #{order.order_status}"
|
|
258
|
+
#
|
|
259
|
+
# @example Place a limit order
|
|
260
|
+
# order = DhanHQ::Models::Order.place(
|
|
261
|
+
# dhan_client_id: "1000000003",
|
|
262
|
+
# transaction_type: "BUY",
|
|
263
|
+
# exchange_segment: "NSE_EQ",
|
|
264
|
+
# product_type: "CNC",
|
|
265
|
+
# order_type: "LIMIT",
|
|
266
|
+
# validity: "DAY",
|
|
267
|
+
# security_id: "11536",
|
|
268
|
+
# quantity: 10,
|
|
269
|
+
# price: 1500.0
|
|
270
|
+
# )
|
|
271
|
+
#
|
|
272
|
+
# @example Place a stop-loss order
|
|
273
|
+
# order = DhanHQ::Models::Order.place(
|
|
274
|
+
# dhan_client_id: "1000000003",
|
|
275
|
+
# transaction_type: "SELL",
|
|
276
|
+
# exchange_segment: "NSE_EQ",
|
|
277
|
+
# product_type: "INTRADAY",
|
|
278
|
+
# order_type: "STOP_LOSS",
|
|
279
|
+
# validity: "DAY",
|
|
280
|
+
# security_id: "11536",
|
|
281
|
+
# quantity: 5,
|
|
282
|
+
# price: 1480.0,
|
|
283
|
+
# trigger_price: 1490.0
|
|
284
|
+
# )
|
|
285
|
+
#
|
|
286
|
+
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
85
287
|
def place(params)
|
|
86
288
|
normalized_params = snake_case(params)
|
|
289
|
+
# Auto-inject dhan_client_id from configuration if not provided
|
|
290
|
+
normalized_params[:dhan_client_id] ||= DhanHQ.configuration.client_id if DhanHQ.configuration.client_id
|
|
87
291
|
validate_params!(normalized_params, DhanHQ::Contracts::PlaceOrderContract)
|
|
88
292
|
|
|
89
293
|
response = resource.create(camelize_keys(normalized_params))
|
|
@@ -94,11 +298,29 @@ module DhanHQ
|
|
|
94
298
|
end
|
|
95
299
|
|
|
96
300
|
##
|
|
97
|
-
#
|
|
98
|
-
#
|
|
99
|
-
#
|
|
301
|
+
# ActiveRecord-style create method.
|
|
302
|
+
#
|
|
303
|
+
# Creates a new Order instance, validates it, and saves it. This provides an
|
|
304
|
+
# ActiveRecord-like interface: `Order.create(params)` or `Order.new(params).save`.
|
|
305
|
+
#
|
|
306
|
+
# @param params [Hash{Symbol => String, Integer, Float, Boolean}] Order creation parameters.
|
|
307
|
+
# See {place} for parameter details.
|
|
308
|
+
#
|
|
309
|
+
# @return [Order] Order instance. Note that validation failures may result in an
|
|
310
|
+
# invalid order that couldn't be saved.
|
|
311
|
+
#
|
|
312
|
+
# @example Create order using AR-style method
|
|
313
|
+
# order = DhanHQ::Models::Order.create(
|
|
314
|
+
# dhan_client_id: "1000000003",
|
|
315
|
+
# transaction_type: "BUY",
|
|
316
|
+
# exchange_segment: "NSE_EQ",
|
|
317
|
+
# product_type: "INTRADAY",
|
|
318
|
+
# order_type: "MARKET",
|
|
319
|
+
# validity: "DAY",
|
|
320
|
+
# security_id: "11536",
|
|
321
|
+
# quantity: 5
|
|
322
|
+
# )
|
|
100
323
|
#
|
|
101
|
-
# For the typical usage "Order.new(...).save", we rely on #save below.
|
|
102
324
|
def create(params)
|
|
103
325
|
order = new(params) # build it
|
|
104
326
|
return order unless order.valid? # run place order contract?
|
|
@@ -108,13 +330,48 @@ module DhanHQ
|
|
|
108
330
|
end
|
|
109
331
|
end
|
|
110
332
|
|
|
111
|
-
|
|
333
|
+
##
|
|
334
|
+
# Modifies a pending order in the orderbook.
|
|
112
335
|
#
|
|
113
|
-
#
|
|
114
|
-
#
|
|
336
|
+
# Allows modification of price, quantity, order type, and validity for pending orders.
|
|
337
|
+
# The method preserves existing attributes and only updates the fields specified in
|
|
338
|
+
# `new_params`. Only modifiable fields are included in the update request.
|
|
339
|
+
#
|
|
340
|
+
# @param new_params [Hash{Symbol => String, Integer, Float}] Fields to modify.
|
|
341
|
+
# Only modifiable fields are accepted:
|
|
342
|
+
# @option new_params [String] :order_type (optional) New order type.
|
|
343
|
+
# Valid values: "LIMIT", "MARKET", "STOP_LOSS", "STOP_LOSS_MARKET"
|
|
344
|
+
# @option new_params [Integer] :quantity (optional) New quantity to modify
|
|
345
|
+
# @option new_params [Float] :price (optional) New price to modify
|
|
346
|
+
# @option new_params [Float] :trigger_price (optional) New trigger price for SL-M and SL-L orders
|
|
347
|
+
# @option new_params [Integer] :disclosed_quantity (optional) New disclosed quantity
|
|
348
|
+
# @option new_params [String] :validity (optional) New validity. "DAY" or "IOC"
|
|
349
|
+
# @option new_params [String] :leg_name (optional) Leg identification for BO & CO orders.
|
|
350
|
+
# Valid values: "ENTRY_LEG", "TARGET_LEG", "STOP_LOSS_LEG"
|
|
351
|
+
#
|
|
352
|
+
# @return [Order, ErrorObject] Updated Order object on success, ErrorObject on failure.
|
|
353
|
+
# The order's attributes are updated with the latest values from the API response.
|
|
354
|
+
#
|
|
355
|
+
# @example Modify order price and quantity
|
|
356
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
357
|
+
# order.modify(price: 3345.8, quantity: 40)
|
|
358
|
+
# puts "Modified: #{order.order_status}"
|
|
359
|
+
#
|
|
360
|
+
# @example Modify order validity
|
|
361
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
362
|
+
# order.modify(validity: "IOC")
|
|
363
|
+
#
|
|
364
|
+
# @raise [RuntimeError] If order ID is missing
|
|
365
|
+
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
115
366
|
def modify(new_params)
|
|
116
367
|
raise "Order ID is required to modify an order" unless id
|
|
117
368
|
|
|
369
|
+
# Log warning for invalid states but still attempt API call (let API handle validation)
|
|
370
|
+
# This maintains backward compatibility - API will return appropriate error
|
|
371
|
+
if order_status && %w[TRADED CANCELLED EXPIRED CLOSED].include?(order_status)
|
|
372
|
+
DhanHQ.logger&.warn("[DhanHQ::Models::Order] Attempting to modify order #{id} in #{order_status} state - API will reject")
|
|
373
|
+
end
|
|
374
|
+
|
|
118
375
|
base_payload = attributes.merge(new_params)
|
|
119
376
|
normalized_payload = snake_case(base_payload).merge(order_id: id)
|
|
120
377
|
filtered_payload = normalized_payload.each_with_object({}) do |(key, value), memo|
|
|
@@ -122,7 +379,7 @@ module DhanHQ
|
|
|
122
379
|
memo[symbolized_key] = value if MODIFIABLE_FIELDS.include?(symbolized_key)
|
|
123
380
|
end
|
|
124
381
|
filtered_payload[:order_id] ||= id
|
|
125
|
-
filtered_payload[:dhan_client_id] ||= attributes[:dhan_client_id]
|
|
382
|
+
filtered_payload[:dhan_client_id] ||= attributes[:dhan_client_id] || DhanHQ.configuration&.client_id
|
|
126
383
|
|
|
127
384
|
cleaned_payload = filtered_payload.compact
|
|
128
385
|
formatted_payload = camelize_keys(cleaned_payload)
|
|
@@ -138,9 +395,23 @@ module DhanHQ
|
|
|
138
395
|
self
|
|
139
396
|
end
|
|
140
397
|
|
|
141
|
-
|
|
398
|
+
##
|
|
399
|
+
# Cancels a pending order in the orderbook.
|
|
400
|
+
#
|
|
401
|
+
# Cancels an existing order using its order ID. Returns true if the cancellation
|
|
402
|
+
# was successful (order status becomes "CANCELLED").
|
|
403
|
+
#
|
|
404
|
+
# @return [Boolean] true if cancellation succeeds, false otherwise
|
|
405
|
+
#
|
|
406
|
+
# @example Cancel a pending order
|
|
407
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
408
|
+
# if order.cancel
|
|
409
|
+
# puts "Order cancelled successfully"
|
|
410
|
+
# else
|
|
411
|
+
# puts "Failed to cancel order"
|
|
412
|
+
# end
|
|
142
413
|
#
|
|
143
|
-
# @
|
|
414
|
+
# @raise [RuntimeError] If order ID is missing
|
|
144
415
|
def cancel
|
|
145
416
|
raise "Order ID is required to cancel an order" unless id
|
|
146
417
|
|
|
@@ -148,9 +419,21 @@ module DhanHQ
|
|
|
148
419
|
response["orderStatus"] == "CANCELLED"
|
|
149
420
|
end
|
|
150
421
|
|
|
151
|
-
|
|
422
|
+
##
|
|
423
|
+
# Fetches the latest details and status of the order.
|
|
424
|
+
#
|
|
425
|
+
# Refreshes the order object with the most current information from the API,
|
|
426
|
+
# including updated status, trade information, and any other changes.
|
|
427
|
+
#
|
|
428
|
+
# @return [Order, nil] Updated Order object with latest details, nil if order not found
|
|
429
|
+
#
|
|
430
|
+
# @example Refresh order status
|
|
431
|
+
# order = DhanHQ::Models::Order.find("112111182198")
|
|
432
|
+
# order.refresh
|
|
433
|
+
# puts "Updated status: #{order.order_status}"
|
|
434
|
+
# puts "Filled: #{order.filled_qty}/#{order.quantity}"
|
|
152
435
|
#
|
|
153
|
-
# @
|
|
436
|
+
# @raise [RuntimeError] If order ID is missing
|
|
154
437
|
def refresh
|
|
155
438
|
raise "Order ID is required to refresh an order" unless id
|
|
156
439
|
|
|
@@ -158,49 +441,110 @@ module DhanHQ
|
|
|
158
441
|
end
|
|
159
442
|
|
|
160
443
|
##
|
|
161
|
-
#
|
|
444
|
+
# Checks if this is a new (unsaved) order record.
|
|
445
|
+
#
|
|
446
|
+
# Determines whether the order has been saved to the API or is a new instance
|
|
447
|
+
# that hasn't been placed yet. Used internally to decide between create and update operations.
|
|
448
|
+
#
|
|
449
|
+
# @return [Boolean] true if order_id is nil or empty (new record), false otherwise
|
|
450
|
+
#
|
|
451
|
+
# @api private
|
|
162
452
|
def new_record?
|
|
163
453
|
order_id.nil? || order_id.to_s.empty?
|
|
164
454
|
end
|
|
165
455
|
|
|
166
456
|
##
|
|
167
|
-
#
|
|
457
|
+
# Returns the order ID used for resource calls.
|
|
458
|
+
#
|
|
459
|
+
# Provides a consistent identifier method for API operations. This is an alias
|
|
460
|
+
# for `order_id` that follows ActiveRecord conventions.
|
|
461
|
+
#
|
|
462
|
+
# @return [String, nil] Order ID if present, nil otherwise
|
|
463
|
+
#
|
|
464
|
+
# @api private
|
|
168
465
|
def id
|
|
169
466
|
order_id
|
|
170
467
|
end
|
|
171
468
|
|
|
172
469
|
##
|
|
173
|
-
#
|
|
174
|
-
#
|
|
470
|
+
# Saves the order (places new or modifies existing).
|
|
471
|
+
#
|
|
472
|
+
# For new records (no order_id), places a new order via the API.
|
|
473
|
+
# For existing records (has order_id), modifies the order via the API.
|
|
474
|
+
# The order's attributes are updated with the API response after successful save.
|
|
475
|
+
#
|
|
476
|
+
# @return [Boolean] true if save succeeds, false otherwise
|
|
477
|
+
#
|
|
478
|
+
# @example Save a new order
|
|
479
|
+
# order = DhanHQ::Models::Order.new(
|
|
480
|
+
# dhan_client_id: "1000000003",
|
|
481
|
+
# transaction_type: "BUY",
|
|
482
|
+
# exchange_segment: "NSE_EQ",
|
|
483
|
+
# product_type: "INTRADAY",
|
|
484
|
+
# order_type: "MARKET",
|
|
485
|
+
# validity: "DAY",
|
|
486
|
+
# security_id: "11536",
|
|
487
|
+
# quantity: 5
|
|
488
|
+
# )
|
|
489
|
+
# if order.save
|
|
490
|
+
# puts "Order placed: #{order.order_id}"
|
|
491
|
+
# end
|
|
492
|
+
#
|
|
493
|
+
# @note Validation is performed before save. Invalid orders will not be saved.
|
|
175
494
|
def save
|
|
176
495
|
return false unless valid?
|
|
177
496
|
|
|
178
497
|
if new_record?
|
|
179
498
|
# PLACE ORDER
|
|
499
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Placing order: #{attributes.slice(:transaction_type,
|
|
500
|
+
:exchange_segment, :security_id, :quantity, :price).inspect}")
|
|
180
501
|
response = self.class.resource.create(to_request_params)
|
|
181
502
|
if success_response?(response) && response["orderId"]
|
|
182
503
|
@attributes.merge!(normalize_keys(response))
|
|
183
504
|
assign_attributes
|
|
505
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Order placed successfully: #{response["orderId"]}")
|
|
184
506
|
true
|
|
185
507
|
else
|
|
186
|
-
|
|
508
|
+
error_msg = response.is_a?(Hash) ? response[:errorMessage] || response[:message] || "Unknown error" : "Invalid response format"
|
|
509
|
+
DhanHQ.logger&.error("[DhanHQ::Models::Order] Order placement failed: #{error_msg}")
|
|
510
|
+
@errors = response if response.is_a?(Hash)
|
|
187
511
|
false
|
|
188
512
|
end
|
|
189
513
|
else
|
|
190
514
|
# MODIFY ORDER
|
|
515
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Modifying order #{id}: #{attributes.slice(:price, :quantity,
|
|
516
|
+
:order_type).inspect}")
|
|
191
517
|
response = self.class.resource.update(id, to_request_params)
|
|
192
518
|
if success_response?(response) && response["orderStatus"]
|
|
193
519
|
@attributes.merge!(normalize_keys(response))
|
|
194
520
|
assign_attributes
|
|
521
|
+
DhanHQ.logger&.info("[DhanHQ::Models::Order] Order modified successfully: #{id}")
|
|
195
522
|
true
|
|
196
523
|
else
|
|
524
|
+
error_msg = response.is_a?(Hash) ? response[:errorMessage] || response[:message] || "Unknown error" : "Invalid response format"
|
|
525
|
+
DhanHQ.logger&.error("[DhanHQ::Models::Order] Order modification failed for #{id}: #{error_msg}")
|
|
526
|
+
@errors = response if response.is_a?(Hash)
|
|
197
527
|
false
|
|
198
528
|
end
|
|
199
529
|
end
|
|
200
530
|
end
|
|
201
531
|
|
|
202
532
|
##
|
|
203
|
-
#
|
|
533
|
+
# Cancels (destroys) the order.
|
|
534
|
+
#
|
|
535
|
+
# Cancels an existing order by calling the delete endpoint. This is an alias
|
|
536
|
+
# for the cancel operation following ActiveRecord conventions.
|
|
537
|
+
#
|
|
538
|
+
# @return [Boolean] true if cancellation succeeds (order status becomes "CANCELLED"),
|
|
539
|
+
# false otherwise
|
|
540
|
+
#
|
|
541
|
+
# @example Destroy an order
|
|
542
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
543
|
+
# if order.destroy
|
|
544
|
+
# puts "Order cancelled"
|
|
545
|
+
# end
|
|
546
|
+
#
|
|
547
|
+
# @note This method does nothing for new (unsaved) orders.
|
|
204
548
|
def destroy
|
|
205
549
|
return false if new_record?
|
|
206
550
|
|
|
@@ -215,8 +559,38 @@ module DhanHQ
|
|
|
215
559
|
alias delete destroy
|
|
216
560
|
|
|
217
561
|
##
|
|
218
|
-
#
|
|
219
|
-
#
|
|
562
|
+
# Slices an order into multiple legs to place orders over freeze limit quantity.
|
|
563
|
+
#
|
|
564
|
+
# This API helps you slice your order request into multiple orders to allow you
|
|
565
|
+
# to place over freeze limit quantity for F&O instruments. Returns an array of
|
|
566
|
+
# orders created from the slice operation.
|
|
567
|
+
#
|
|
568
|
+
# @param params [Hash{Symbol => String, Integer, Float, Boolean}] Order parameters for slicing.
|
|
569
|
+
# Same parameters as {place}, but quantity can exceed freeze limits as it will be
|
|
570
|
+
# automatically split into multiple orders.
|
|
571
|
+
#
|
|
572
|
+
# @return [Array<Hash>] Array of order objects created from the slice operation.
|
|
573
|
+
# Each hash contains:
|
|
574
|
+
# - **:order_id** [String] Order-specific identification generated by Dhan
|
|
575
|
+
# - **:order_status** [String] Order status. Valid values: "TRANSIT", "PENDING",
|
|
576
|
+
# "REJECTED", "CANCELLED", "TRADED", "EXPIRED", "CONFIRM"
|
|
577
|
+
#
|
|
578
|
+
# @example Slice a large order
|
|
579
|
+
# order = DhanHQ::Models::Order.find("112111182045")
|
|
580
|
+
# sliced_orders = order.slice_order(
|
|
581
|
+
# dhan_client_id: "1000000003",
|
|
582
|
+
# transaction_type: "BUY",
|
|
583
|
+
# exchange_segment: "NSE_FNO",
|
|
584
|
+
# product_type: "INTRADAY",
|
|
585
|
+
# order_type: "MARKET",
|
|
586
|
+
# validity: "DAY",
|
|
587
|
+
# security_id: "49081",
|
|
588
|
+
# quantity: 50000 # Will be split into multiple orders
|
|
589
|
+
# )
|
|
590
|
+
# puts "Created #{sliced_orders.count} orders"
|
|
591
|
+
#
|
|
592
|
+
# @raise [RuntimeError] If order ID is missing
|
|
593
|
+
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
220
594
|
def slice_order(params)
|
|
221
595
|
raise "Order ID is required to slice an order" unless id
|
|
222
596
|
|
|
@@ -229,8 +603,16 @@ module DhanHQ
|
|
|
229
603
|
end
|
|
230
604
|
|
|
231
605
|
##
|
|
232
|
-
#
|
|
233
|
-
#
|
|
606
|
+
# Returns the appropriate validation contract based on order state.
|
|
607
|
+
#
|
|
608
|
+
# For new records, returns PlaceOrderContract. For existing records, returns
|
|
609
|
+
# ModifyOrderContract. This allows the same validation logic to be used for
|
|
610
|
+
# both creating and modifying orders.
|
|
611
|
+
#
|
|
612
|
+
# @return [DhanHQ::Contracts::PlaceOrderContract, DhanHQ::Contracts::ModifyOrderContract]
|
|
613
|
+
# The appropriate validation contract based on whether this is a new or existing order
|
|
614
|
+
#
|
|
615
|
+
# @api private
|
|
234
616
|
def validation_contract
|
|
235
617
|
new_record? ? DhanHQ::Contracts::PlaceOrderContract.new : DhanHQ::Contracts::ModifyOrderContract.new
|
|
236
618
|
end
|
|
@@ -5,7 +5,6 @@ module DhanHQ
|
|
|
5
5
|
##
|
|
6
6
|
# Represents a real-time order update received via WebSocket
|
|
7
7
|
# Parses and provides access to all order update fields as per DhanHQ API documentation
|
|
8
|
-
# rubocop:disable Metrics/ClassLength
|
|
9
8
|
class OrderUpdate < BaseModel
|
|
10
9
|
# All order update attributes as per API documentation
|
|
11
10
|
attributes :exchange, :segment, :source, :security_id, :client_id,
|
|
@@ -206,7 +205,6 @@ module DhanHQ
|
|
|
206
205
|
|
|
207
206
|
##
|
|
208
207
|
# Status summary for logging/debugging
|
|
209
|
-
# rubocop:disable Metrics/MethodLength
|
|
210
208
|
def status_summary
|
|
211
209
|
{
|
|
212
210
|
order_no: order_no,
|
|
@@ -222,7 +220,6 @@ module DhanHQ
|
|
|
222
220
|
super_order: super_order?
|
|
223
221
|
}
|
|
224
222
|
end
|
|
225
|
-
# rubocop:enable Metrics/MethodLength
|
|
226
223
|
|
|
227
224
|
##
|
|
228
225
|
# Convert to hash for serialization
|
|
@@ -230,6 +227,5 @@ module DhanHQ
|
|
|
230
227
|
@attributes.dup
|
|
231
228
|
end
|
|
232
229
|
end
|
|
233
|
-
# rubocop:enable Metrics/ClassLength
|
|
234
230
|
end
|
|
235
231
|
end
|