DhanHQ 2.1.7 → 2.1.10
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 +58 -0
- data/examples/comprehensive_websocket_examples.rb +0 -0
- data/examples/instrument_finder_test.rb +0 -0
- data/examples/live_order_updates.rb +1 -1
- data/examples/market_depth_example.rb +2 -2
- data/examples/order_update_example.rb +0 -0
- data/examples/trading_fields_example.rb +1 -1
- data/lib/DhanHQ/client.rb +2 -1
- data/lib/DhanHQ/contracts/expired_options_data_contract.rb +6 -6
- data/lib/DhanHQ/core/base_model.rb +9 -1
- data/lib/DhanHQ/models/edis.rb +150 -14
- data/lib/DhanHQ/models/expired_options_data.rb +307 -82
- 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 +39 -5
- 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 +399 -34
- 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 +78 -23
- data/lib/DhanHQ/resources/expired_options_data.rb +1 -1
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/DhanHQ/ws/market_depth/client.rb +3 -1
- data/lib/DhanHQ/ws/orders/client.rb +2 -1
- data/lib/DhanHQ/ws/orders/connection.rb +0 -3
- data/lib/DhanHQ/ws/orders.rb +2 -1
- metadata +1 -1
|
@@ -2,7 +2,36 @@
|
|
|
2
2
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Models
|
|
5
|
-
|
|
5
|
+
##
|
|
6
|
+
# Model for managing intraday and carry-forward positions.
|
|
7
|
+
#
|
|
8
|
+
# The Positions API lets you retrieve a list of all open positions for the day.
|
|
9
|
+
# This includes all F&O carryforward positions as well. You can also convert
|
|
10
|
+
# positions between product types (e.g., intraday to delivery or vice versa).
|
|
11
|
+
#
|
|
12
|
+
# @example Fetch all positions
|
|
13
|
+
# positions = DhanHQ::Models::Position.all
|
|
14
|
+
# positions.each do |position|
|
|
15
|
+
# puts "#{position.trading_symbol}: #{position.net_qty} @ ₹#{position.buy_avg}"
|
|
16
|
+
# puts "Unrealized P&L: ₹#{position.unrealized_profit}"
|
|
17
|
+
# end
|
|
18
|
+
#
|
|
19
|
+
# @example Fetch only active (open) positions
|
|
20
|
+
# open_positions = DhanHQ::Models::Position.active
|
|
21
|
+
# long_positions = open_positions.select { |p| p.position_type == "LONG" }
|
|
22
|
+
# puts "Open long positions: #{long_positions.count}"
|
|
23
|
+
#
|
|
24
|
+
# @example Convert intraday position to delivery
|
|
25
|
+
# response = DhanHQ::Models::Position.convert(
|
|
26
|
+
# dhan_client_id: "1000000009",
|
|
27
|
+
# from_product_type: "INTRADAY",
|
|
28
|
+
# to_product_type: "CNC",
|
|
29
|
+
# exchange_segment: "NSE_EQ",
|
|
30
|
+
# position_type: "LONG",
|
|
31
|
+
# security_id: "11536",
|
|
32
|
+
# convert_qty: 40
|
|
33
|
+
# )
|
|
34
|
+
#
|
|
6
35
|
class Position < BaseModel
|
|
7
36
|
# Base path used by the positions resource.
|
|
8
37
|
HTTP_PATH = "/v2/positions"
|
|
@@ -17,17 +46,68 @@ module DhanHQ
|
|
|
17
46
|
|
|
18
47
|
class << self
|
|
19
48
|
##
|
|
20
|
-
# Provides a
|
|
49
|
+
# Provides a shared instance of the Positions resource.
|
|
21
50
|
#
|
|
22
|
-
# @return [DhanHQ::Resources::Positions]
|
|
51
|
+
# @return [DhanHQ::Resources::Positions] The Positions resource client instance
|
|
23
52
|
def resource
|
|
24
53
|
@resource ||= DhanHQ::Resources::Positions.new
|
|
25
54
|
end
|
|
26
55
|
|
|
27
56
|
##
|
|
28
|
-
#
|
|
57
|
+
# Retrieves all positions for the current trading day.
|
|
58
|
+
#
|
|
59
|
+
# Fetches a list of all open positions including F&O carryforward positions.
|
|
60
|
+
# Returns both active (LONG/SHORT) and closed positions. Use {active} if you
|
|
61
|
+
# only need open positions.
|
|
62
|
+
#
|
|
63
|
+
# @return [Array<Position>] Array of Position objects. Returns empty array if no positions exist.
|
|
64
|
+
# Each Position object contains (keys normalized to snake_case):
|
|
65
|
+
# - **:dhan_client_id** [String] User-specific identification generated by Dhan
|
|
66
|
+
# - **:trading_symbol** [String] Trading symbol of the instrument
|
|
67
|
+
# - **:security_id** [String] Exchange standard ID for each scrip
|
|
68
|
+
# - **:position_type** [String] Position type. Valid values: "LONG", "SHORT", "CLOSED"
|
|
69
|
+
# - **:exchange_segment** [String] Exchange and segment.
|
|
70
|
+
# Valid values: "NSE_EQ", "NSE_FNO", "NSE_CURRENCY", "BSE_EQ", "BSE_FNO",
|
|
71
|
+
# "BSE_CURRENCY", "MCX_COMM"
|
|
72
|
+
# - **:product_type** [String] Product type. Valid values: "CNC", "INTRADAY",
|
|
73
|
+
# "MARGIN", "MTF", "CO", "BO"
|
|
74
|
+
# - **:buy_avg** [Float] Average buy price mark to market
|
|
75
|
+
# - **:buy_qty** [Integer] Total quantity bought
|
|
76
|
+
# - **:cost_price** [Float] Actual cost price
|
|
77
|
+
# - **:sell_avg** [Float] Average sell price mark to market
|
|
78
|
+
# - **:sell_qty** [Integer] Total quantities sold
|
|
79
|
+
# - **:net_qty** [Integer] Net quantity (buy_qty - sell_qty)
|
|
80
|
+
# - **:realized_profit** [Float] Profit or loss booked
|
|
81
|
+
# - **:unrealized_profit** [Float] Profit or loss standing for open position
|
|
82
|
+
# - **:rbi_reference_rate** [Float] RBI mandated reference rate for forex
|
|
83
|
+
# - **:multiplier** [Integer] Multiplying factor for currency F&O
|
|
84
|
+
# - **:carry_forward_buy_qty** [Integer] Carry forward F&O long quantities
|
|
85
|
+
# - **:carry_forward_sell_qty** [Integer] Carry forward F&O short quantities
|
|
86
|
+
# - **:carry_forward_buy_value** [Float] Carry forward F&O long value
|
|
87
|
+
# - **:carry_forward_sell_value** [Float] Carry forward F&O short value
|
|
88
|
+
# - **:day_buy_qty** [Integer] Quantities bought today
|
|
89
|
+
# - **:day_sell_qty** [Integer] Quantities sold today
|
|
90
|
+
# - **:day_buy_value** [Float] Value of quantities bought today
|
|
91
|
+
# - **:day_sell_value** [Float] Value of quantities sold today
|
|
92
|
+
# - **:drv_expiry_date** [String] For F&O, expiry date of contract (format: "YYYY-MM-DD")
|
|
93
|
+
# - **:drv_option_type** [String, nil] Type of Option. "CALL" or "PUT"
|
|
94
|
+
# - **:drv_strike_price** [Float] For Options, Strike Price
|
|
95
|
+
# - **:cross_currency** [Boolean] Check for non INR currency pair
|
|
96
|
+
#
|
|
97
|
+
# @example Fetch and analyze positions
|
|
98
|
+
# positions = DhanHQ::Models::Position.all
|
|
99
|
+
# total_unrealized = positions.sum(&:unrealized_profit)
|
|
100
|
+
# total_realized = positions.sum(&:realized_profit)
|
|
101
|
+
# puts "Total Unrealized P&L: ₹#{total_unrealized}"
|
|
102
|
+
# puts "Total Realized P&L: ₹#{total_realized}"
|
|
103
|
+
#
|
|
104
|
+
# @example Analyze F&O positions
|
|
105
|
+
# positions = DhanHQ::Models::Position.all
|
|
106
|
+
# fno_positions = positions.select { |p| p.exchange_segment.include?("FNO") }
|
|
107
|
+
# fno_positions.each do |pos|
|
|
108
|
+
# puts "#{pos.trading_symbol} - Net: #{pos.net_qty}, P&L: ₹#{pos.unrealized_profit}"
|
|
109
|
+
# end
|
|
29
110
|
#
|
|
30
|
-
# @return [Array<Position>]
|
|
31
111
|
def all
|
|
32
112
|
response = resource.all
|
|
33
113
|
return [] unless response.is_a?(Array)
|
|
@@ -37,16 +117,87 @@ module DhanHQ
|
|
|
37
117
|
end
|
|
38
118
|
end
|
|
39
119
|
|
|
40
|
-
|
|
120
|
+
##
|
|
121
|
+
# Filters the position list to return only active (open) positions.
|
|
122
|
+
#
|
|
123
|
+
# Removes positions with "CLOSED" status, returning only positions that are
|
|
124
|
+
# currently open (LONG or SHORT positions).
|
|
125
|
+
#
|
|
126
|
+
# @return [Array<Position>] Array of active Position objects. Excludes positions
|
|
127
|
+
# with position_type "CLOSED"
|
|
128
|
+
#
|
|
129
|
+
# @example Fetch only open positions
|
|
130
|
+
# open_positions = DhanHQ::Models::Position.active
|
|
131
|
+
# puts "Open positions: #{open_positions.count}"
|
|
132
|
+
# open_positions.each do |position|
|
|
133
|
+
# puts "#{position.trading_symbol}: #{position.net_qty} (#{position.position_type})"
|
|
134
|
+
# end
|
|
135
|
+
#
|
|
136
|
+
# @example Calculate total open position value
|
|
137
|
+
# open_positions = DhanHQ::Models::Position.active
|
|
138
|
+
# total_value = open_positions.sum { |p| p.net_qty * p.buy_avg }
|
|
139
|
+
# puts "Total open position value: ₹#{total_value}"
|
|
41
140
|
#
|
|
42
|
-
# @return [Array<Position>]
|
|
43
141
|
def active
|
|
44
142
|
all.reject { |position| position.position_type == "CLOSED" }
|
|
45
143
|
end
|
|
46
144
|
|
|
47
|
-
|
|
48
|
-
#
|
|
49
|
-
#
|
|
145
|
+
##
|
|
146
|
+
# Converts an existing position from one product type to another.
|
|
147
|
+
#
|
|
148
|
+
# Allows conversion between eligible product types, most commonly between intraday
|
|
149
|
+
# (INTRADAY) and delivery (CNC) positions. This is useful when you want to hold
|
|
150
|
+
# an intraday position overnight or convert a delivery position to intraday.
|
|
151
|
+
#
|
|
152
|
+
# @param params [Hash{Symbol => String, Integer}] Position conversion parameters
|
|
153
|
+
# @option params [String] :dhan_client_id (required) User-specific identification generated by Dhan.
|
|
154
|
+
# Must be explicitly provided in the params hash
|
|
155
|
+
# @option params [String] :from_product_type (required) Current product type of the position.
|
|
156
|
+
# Valid values: "CNC", "INTRADAY", "MARGIN", "CO", "BO"
|
|
157
|
+
# @option params [String] :to_product_type (required) Desired product type after conversion.
|
|
158
|
+
# Valid values: "CNC", "INTRADAY", "MARGIN", "CO", "BO"
|
|
159
|
+
# Must be different from from_product_type
|
|
160
|
+
# @option params [String] :exchange_segment (required) Exchange and segment in which position is created.
|
|
161
|
+
# Valid values: See {DhanHQ::Constants::EXCHANGE_SEGMENTS}
|
|
162
|
+
# @option params [String] :position_type (required) Position type to convert.
|
|
163
|
+
# Valid values: "LONG", "SHORT", "CLOSED"
|
|
164
|
+
# @option params [String] :security_id (required) Exchange standard ID for each scrip
|
|
165
|
+
# @option params [String] :trading_symbol (optional) Trading symbol of the instrument
|
|
166
|
+
# @option params [Integer] :convert_qty (required) Number of shares for which conversion is desired.
|
|
167
|
+
# Must be greater than 0
|
|
168
|
+
#
|
|
169
|
+
# @return [Hash, DhanHQ::ErrorObject] API response hash (typically HTTP 202 Accepted) on success,
|
|
170
|
+
# ErrorObject on failure
|
|
171
|
+
#
|
|
172
|
+
# @example Convert intraday position to delivery
|
|
173
|
+
# response = DhanHQ::Models::Position.convert(
|
|
174
|
+
# dhan_client_id: "1000000009",
|
|
175
|
+
# from_product_type: "INTRADAY",
|
|
176
|
+
# to_product_type: "CNC",
|
|
177
|
+
# exchange_segment: "NSE_EQ",
|
|
178
|
+
# position_type: "LONG",
|
|
179
|
+
# security_id: "11536",
|
|
180
|
+
# convert_qty: 40
|
|
181
|
+
# )
|
|
182
|
+
# if response.is_a?(DhanHQ::ErrorObject)
|
|
183
|
+
# puts "Conversion failed: #{response.errors}"
|
|
184
|
+
# else
|
|
185
|
+
# puts "Position converted successfully"
|
|
186
|
+
# end
|
|
187
|
+
#
|
|
188
|
+
# @example Convert delivery position to intraday
|
|
189
|
+
# response = DhanHQ::Models::Position.convert(
|
|
190
|
+
# dhan_client_id: "1000000009",
|
|
191
|
+
# from_product_type: "CNC",
|
|
192
|
+
# to_product_type: "INTRADAY",
|
|
193
|
+
# exchange_segment: "NSE_EQ",
|
|
194
|
+
# position_type: "LONG",
|
|
195
|
+
# security_id: "11536",
|
|
196
|
+
# convert_qty: 10
|
|
197
|
+
# )
|
|
198
|
+
#
|
|
199
|
+
# @raise [DhanHQ::ValidationError] If validation fails for any parameter
|
|
200
|
+
# @note The API returns HTTP 202 Accepted on successful conversion
|
|
50
201
|
def convert(params)
|
|
51
202
|
formatted_params = camelize_keys(params)
|
|
52
203
|
validate_params!(formatted_params, DhanHQ::Contracts::PositionConversionContract)
|
|
@@ -3,8 +3,42 @@
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Models
|
|
5
5
|
##
|
|
6
|
-
#
|
|
7
|
-
#
|
|
6
|
+
# Model for retrieving authenticated user profile and account information.
|
|
7
|
+
#
|
|
8
|
+
# The User Profile API can be used to check the validity of access token and account setup.
|
|
9
|
+
# It is a simple GET request and serves as a great test API to start integration with DhanHQ.
|
|
10
|
+
# The profile contains account-level metadata including token validity, active segments,
|
|
11
|
+
# DDPI status, MTF consent, and Data API subscription status.
|
|
12
|
+
#
|
|
13
|
+
# @example Fetch user profile to verify token validity
|
|
14
|
+
# profile = DhanHQ::Models::Profile.fetch
|
|
15
|
+
# if profile
|
|
16
|
+
# puts "Client ID: #{profile.dhan_client_id}"
|
|
17
|
+
# puts "Token Valid Until: #{profile.token_validity}"
|
|
18
|
+
# puts "Active Segments: #{profile.active_segment}"
|
|
19
|
+
# puts "DDPI: #{profile.ddpi}"
|
|
20
|
+
# puts "MTF: #{profile.mtf}"
|
|
21
|
+
# puts "Data Plan: #{profile.data_plan}"
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# @example Check account status
|
|
25
|
+
# profile = DhanHQ::Models::Profile.fetch
|
|
26
|
+
# if profile
|
|
27
|
+
# if profile.ddpi == "Active" && profile.mtf == "Active"
|
|
28
|
+
# puts "Account is fully active"
|
|
29
|
+
# else
|
|
30
|
+
# puts "Some features may be inactive"
|
|
31
|
+
# end
|
|
32
|
+
# end
|
|
33
|
+
#
|
|
34
|
+
# @example Verify data API subscription
|
|
35
|
+
# profile = DhanHQ::Models::Profile.fetch
|
|
36
|
+
# if profile && profile.data_plan == "Active"
|
|
37
|
+
# puts "Data API subscription is active until: #{profile.data_validity}"
|
|
38
|
+
# else
|
|
39
|
+
# puts "Data API subscription is not active"
|
|
40
|
+
# end
|
|
41
|
+
#
|
|
8
42
|
class Profile < BaseModel
|
|
9
43
|
# Base path for profile retrieval.
|
|
10
44
|
HTTP_PATH = "/v2/profile"
|
|
@@ -14,17 +48,73 @@ module DhanHQ
|
|
|
14
48
|
|
|
15
49
|
class << self
|
|
16
50
|
##
|
|
17
|
-
# Provides a shared instance of the
|
|
51
|
+
# Provides a shared instance of the Profile resource.
|
|
18
52
|
#
|
|
19
|
-
# @return [DhanHQ::Resources::Profile]
|
|
53
|
+
# @return [DhanHQ::Resources::Profile] The Profile resource client instance
|
|
20
54
|
def resource
|
|
21
55
|
@resource ||= DhanHQ::Resources::Profile.new
|
|
22
56
|
end
|
|
23
57
|
|
|
24
58
|
##
|
|
25
|
-
#
|
|
59
|
+
# Fetches the authenticated user's profile details and account information.
|
|
60
|
+
#
|
|
61
|
+
# Retrieves account-level metadata including token validity, active segments,
|
|
62
|
+
# DDPI status, MTF consent status, and Data API subscription status. This is a
|
|
63
|
+
# simple GET request that serves as a great test API to verify your access token
|
|
64
|
+
# and account setup before starting integration.
|
|
65
|
+
#
|
|
66
|
+
# @return [Profile, nil] Profile object with account details if fetch succeeds, nil otherwise.
|
|
67
|
+
# Response structure (keys normalized to snake_case):
|
|
68
|
+
# - **:dhan_client_id** [String] User-specific identification generated by Dhan
|
|
69
|
+
# - **:token_validity** [String] Validity date and time for access token.
|
|
70
|
+
# Format: "DD/MM/YYYY HH:MM" (e.g., "30/03/2025 15:37")
|
|
71
|
+
# - **:active_segment** [String] All active segments in user account.
|
|
72
|
+
# Comma-separated list (e.g., "Equity, Derivative, Currency, Commodity")
|
|
73
|
+
# - **:ddpi** [String] DDPI (Demat Debit Power of Attorney) status of the user.
|
|
74
|
+
# Valid values: "Active", "Deactive"
|
|
75
|
+
# - **:mtf** [String] MTF (Margin Trading Facility) consent status of the user.
|
|
76
|
+
# Valid values: "Active", "Deactive"
|
|
77
|
+
# - **:data_plan** [String] Data API subscription status.
|
|
78
|
+
# Valid values: "Active", "Deactive"
|
|
79
|
+
# - **:data_validity** [String] Validity date and time for Data API Subscription.
|
|
80
|
+
# Format: "YYYY-MM-DD HH:MM:SS.S" (e.g., "2024-12-05 09:37:52.0")
|
|
81
|
+
#
|
|
82
|
+
# @example Fetch profile to verify integration
|
|
83
|
+
# profile = DhanHQ::Models::Profile.fetch
|
|
84
|
+
# if profile
|
|
85
|
+
# puts "✓ Access token is valid"
|
|
86
|
+
# puts "✓ Account setup complete"
|
|
87
|
+
# puts "Client ID: #{profile.dhan_client_id}"
|
|
88
|
+
# else
|
|
89
|
+
# puts "✗ Failed to fetch profile - check access token"
|
|
90
|
+
# end
|
|
91
|
+
#
|
|
92
|
+
# @example Check account features
|
|
93
|
+
# profile = DhanHQ::Models::Profile.fetch
|
|
94
|
+
# if profile
|
|
95
|
+
# puts "Token valid until: #{profile.token_validity}"
|
|
96
|
+
# puts "Active segments: #{profile.active_segment}"
|
|
97
|
+
# puts "DDPI: #{profile.ddpi}"
|
|
98
|
+
# puts "MTF: #{profile.mtf}"
|
|
99
|
+
# puts "Data Plan: #{profile.data_plan}"
|
|
100
|
+
# if profile.data_plan == "Active"
|
|
101
|
+
# puts "Data API valid until: #{profile.data_validity}"
|
|
102
|
+
# end
|
|
103
|
+
# end
|
|
104
|
+
#
|
|
105
|
+
# @example Verify account is ready for trading
|
|
106
|
+
# profile = DhanHQ::Models::Profile.fetch
|
|
107
|
+
# if profile
|
|
108
|
+
# ready = profile.ddpi == "Active" && profile.mtf == "Active"
|
|
109
|
+
# if ready
|
|
110
|
+
# puts "Account is ready for trading"
|
|
111
|
+
# else
|
|
112
|
+
# puts "Account may have restrictions:"
|
|
113
|
+
# puts " DDPI: #{profile.ddpi}"
|
|
114
|
+
# puts " MTF: #{profile.mtf}"
|
|
115
|
+
# end
|
|
116
|
+
# end
|
|
26
117
|
#
|
|
27
|
-
# @return [DhanHQ::Models::Profile, nil]
|
|
28
118
|
def fetch
|
|
29
119
|
response = resource.fetch
|
|
30
120
|
return nil unless response.is_a?(Hash)
|
|
@@ -33,9 +123,15 @@ module DhanHQ
|
|
|
33
123
|
end
|
|
34
124
|
end
|
|
35
125
|
|
|
126
|
+
##
|
|
36
127
|
# Profile responses are informational and not validated locally.
|
|
37
128
|
#
|
|
38
|
-
#
|
|
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
|
|
39
135
|
def validation_contract
|
|
40
136
|
nil
|
|
41
137
|
end
|
|
@@ -2,7 +2,51 @@
|
|
|
2
2
|
|
|
3
3
|
module DhanHQ
|
|
4
4
|
module Models
|
|
5
|
-
|
|
5
|
+
##
|
|
6
|
+
# Model for managing multi-leg super orders with smart execution.
|
|
7
|
+
#
|
|
8
|
+
# Super orders are built for smart execution of trades. They are a collection of orders
|
|
9
|
+
# clubbed into a single order request, which includes an entry leg, target leg, and
|
|
10
|
+
# stop loss leg along with the option to add trailing stop loss. This allows for
|
|
11
|
+
# server-side risk management immediately after entry.
|
|
12
|
+
#
|
|
13
|
+
# Super orders can be placed across all exchanges and segments, supporting intraday,
|
|
14
|
+
# carry forward, or MTF orders.
|
|
15
|
+
#
|
|
16
|
+
# @note **Static IP Whitelisting**: Super order creation, modification, and cancellation
|
|
17
|
+
# APIs require Static IP whitelisting. Ensure your IP is whitelisted before using these endpoints.
|
|
18
|
+
#
|
|
19
|
+
# @example Create a super order
|
|
20
|
+
# order = DhanHQ::Models::SuperOrder.create(
|
|
21
|
+
# dhan_client_id: "1000000003",
|
|
22
|
+
# transaction_type: "BUY",
|
|
23
|
+
# exchange_segment: "NSE_EQ",
|
|
24
|
+
# product_type: "CNC",
|
|
25
|
+
# order_type: "LIMIT",
|
|
26
|
+
# security_id: "11536",
|
|
27
|
+
# quantity: 5,
|
|
28
|
+
# price: 1500,
|
|
29
|
+
# target_price: 1600,
|
|
30
|
+
# stop_loss_price: 1400,
|
|
31
|
+
# trailing_jump: 10
|
|
32
|
+
# )
|
|
33
|
+
# puts "Super Order ID: #{order.order_id} - #{order.order_status}"
|
|
34
|
+
#
|
|
35
|
+
# @example Modify a super order
|
|
36
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
37
|
+
# order.modify(
|
|
38
|
+
# leg_name: "ENTRY_LEG",
|
|
39
|
+
# price: 1300,
|
|
40
|
+
# quantity: 40,
|
|
41
|
+
# target_price: 1450,
|
|
42
|
+
# stop_loss_price: 1350,
|
|
43
|
+
# trailing_jump: 20
|
|
44
|
+
# )
|
|
45
|
+
#
|
|
46
|
+
# @example Cancel a specific leg
|
|
47
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
48
|
+
# order.cancel("STOP_LOSS_LEG")
|
|
49
|
+
#
|
|
6
50
|
class SuperOrder < BaseModel
|
|
7
51
|
attributes :dhan_client_id, :order_id, :correlation_id, :order_status,
|
|
8
52
|
:transaction_type, :exchange_segment, :product_type, :order_type,
|
|
@@ -14,16 +58,78 @@ module DhanHQ
|
|
|
14
58
|
:trailing_jump
|
|
15
59
|
|
|
16
60
|
class << self
|
|
17
|
-
|
|
61
|
+
##
|
|
62
|
+
# Provides a shared instance of the SuperOrders resource.
|
|
18
63
|
#
|
|
19
|
-
# @return [DhanHQ::Resources::SuperOrders]
|
|
64
|
+
# @return [DhanHQ::Resources::SuperOrders] The SuperOrders resource client instance
|
|
20
65
|
def resource
|
|
21
66
|
@resource ||= DhanHQ::Resources::SuperOrders.new
|
|
22
67
|
end
|
|
23
68
|
|
|
24
|
-
|
|
69
|
+
##
|
|
70
|
+
# Retrieves all super orders placed during the current trading day.
|
|
71
|
+
#
|
|
72
|
+
# Fetches a special order book that only consists of Super Orders, where the target
|
|
73
|
+
# and stop loss orders are nested under the main entry order leg. Individual legs of
|
|
74
|
+
# each super order can also be found in the main order book with their Order ID.
|
|
75
|
+
#
|
|
76
|
+
# @return [Array<SuperOrder>] Array of SuperOrder objects. Returns empty array if no orders exist.
|
|
77
|
+
# Each SuperOrder object contains (keys normalized to snake_case):
|
|
78
|
+
# - **:dhan_client_id** [String] User-specific identification generated by Dhan
|
|
79
|
+
# - **:order_id** [String] Order-specific identification generated by Dhan
|
|
80
|
+
# - **:correlation_id** [String] User/partner generated ID for tracking
|
|
81
|
+
# - **:order_status** [String] Last updated status of the order.
|
|
82
|
+
# Valid values: "TRANSIT", "PENDING", "CLOSED", "REJECTED", "CANCELLED",
|
|
83
|
+
# "PART_TRADED", "TRADED"
|
|
84
|
+
# - **:transaction_type** [String] The trading side of transaction. "BUY" or "SELL"
|
|
85
|
+
# - **:exchange_segment** [String] Exchange segment of instrument
|
|
86
|
+
# - **:product_type** [String] Product type. Valid values: "CNC", "INTRADAY", "MARGIN", "MTF"
|
|
87
|
+
# - **:order_type** [String] Order type. "LIMIT" or "MARKET"
|
|
88
|
+
# - **:validity** [String] Validity of order. "DAY"
|
|
89
|
+
# - **:trading_symbol** [String] Trading symbol of the instrument
|
|
90
|
+
# - **:security_id** [String] Exchange standard ID for each scrip
|
|
91
|
+
# - **:quantity** [Integer] Number of shares for the order
|
|
92
|
+
# - **:remaining_quantity** [Integer] Quantity pending execution
|
|
93
|
+
# - **:ltp** [Float] Last Traded Price of the instrument
|
|
94
|
+
# - **:price** [Float] Price at which order is placed
|
|
95
|
+
# - **:after_market_order** [Boolean] Whether the order is placed after market
|
|
96
|
+
# - **:leg_name** [String] Leg identification. "ENTRY_LEG", "TARGET_LEG", "STOP_LOSS_LEG"
|
|
97
|
+
# - **:trailing_jump** [Float] Price jump by which Stop Loss should be trailed
|
|
98
|
+
# - **:exchange_order_id** [String] Exchange generated ID for the order
|
|
99
|
+
# - **:create_time** [String] Time at which the order is created
|
|
100
|
+
# - **:update_time** [String] Last updated time of the order
|
|
101
|
+
# - **:exchange_time** [String] Time at which order was sent to the exchange
|
|
102
|
+
# - **:oms_error_description** [String] Description of error if the order is rejected or failed
|
|
103
|
+
# - **:average_traded_price** [Float] Average price at which order is traded
|
|
104
|
+
# - **:filled_qty** [Integer] Quantity of order traded on Exchange
|
|
105
|
+
# - **:leg_details** [Array<Hash>] Array of leg details for TARGET_LEG and STOP_LOSS_LEG.
|
|
106
|
+
# Each leg detail contains:
|
|
107
|
+
# - **:order_id** [String] Order ID of the leg
|
|
108
|
+
# - **:leg_name** [String] Leg name ("TARGET_LEG" or "STOP_LOSS_LEG")
|
|
109
|
+
# - **:transaction_type** [String] Transaction type of the leg
|
|
110
|
+
# - **:total_quatity** [Integer] Total quantity (note: typo in API response)
|
|
111
|
+
# - **:remaining_quantity** [Integer] Quantity pending execution
|
|
112
|
+
# - **:triggered_quantity** [Integer] Quantity of Stop Loss or Target leg placed on Exchange
|
|
113
|
+
# - **:price** [Float] Price of the leg
|
|
114
|
+
# - **:order_status** [String] Order status of the leg.
|
|
115
|
+
# "TRIGGERED" indicates the leg has been triggered and placed
|
|
116
|
+
# - **:trailing_jump** [Float] Trailing jump for the leg
|
|
117
|
+
#
|
|
118
|
+
# @note **Order Status**: "CLOSED" is used when the ENTRY_LEG and one of either
|
|
119
|
+
# TARGET_LEG or STOP_LOSS_LEG is triggered for entire quantity. "TRIGGERED" status
|
|
120
|
+
# is present for TARGET_LEG and STOP_LOSS_LEG indicating which leg is actually triggered.
|
|
121
|
+
#
|
|
122
|
+
# @example Fetch and analyze super orders
|
|
123
|
+
# orders = DhanHQ::Models::SuperOrder.all
|
|
124
|
+
# pending_orders = orders.select { |o| o.order_status == "PENDING" }
|
|
125
|
+
# pending_orders.each do |order|
|
|
126
|
+
# puts "Order: #{order.order_id}"
|
|
127
|
+
# puts "Target: ₹#{order.target_price}, Stop Loss: ₹#{order.stop_loss_price}"
|
|
128
|
+
# order.leg_details.each do |leg|
|
|
129
|
+
# puts " #{leg[:leg_name]}: ₹#{leg[:price]} (#{leg[:order_status]})"
|
|
130
|
+
# end
|
|
131
|
+
# end
|
|
25
132
|
#
|
|
26
|
-
# @return [Array<SuperOrder>]
|
|
27
133
|
def all
|
|
28
134
|
response = resource.all
|
|
29
135
|
return [] unless response.is_a?(Array)
|
|
@@ -31,22 +137,150 @@ module DhanHQ
|
|
|
31
137
|
response.map { |o| new(o, skip_validation: true) }
|
|
32
138
|
end
|
|
33
139
|
|
|
34
|
-
|
|
140
|
+
##
|
|
141
|
+
# Creates a new super order with entry, target, and stop loss legs.
|
|
142
|
+
#
|
|
143
|
+
# Places a super order that combines entry, target, and stop-loss legs into one
|
|
144
|
+
# atomic instruction. Supports an optional trailing jump for server-side risk
|
|
145
|
+
# management immediately after entry. Available across all exchanges and segments,
|
|
146
|
+
# supporting intraday, carry-forward, or MTF orders.
|
|
147
|
+
#
|
|
148
|
+
# @param params [Hash{Symbol => String, Integer, Float}] Super order creation parameters
|
|
149
|
+
# @option params [String] :dhan_client_id (required) User-specific identification generated by Dhan.
|
|
150
|
+
# Must be explicitly provided in the params hash
|
|
151
|
+
# @option params [String] :correlation_id (optional) User/partner generated ID for tracking.
|
|
152
|
+
# Max length: 25 characters
|
|
153
|
+
# @option params [String] :transaction_type (required) The trading side of transaction.
|
|
154
|
+
# Valid values: "BUY", "SELL"
|
|
155
|
+
# @option params [String] :exchange_segment (required) Exchange segment of instrument.
|
|
156
|
+
# Valid values: See {DhanHQ::Constants::EXCHANGE_SEGMENTS}
|
|
157
|
+
# @option params [String] :product_type (required) Product type.
|
|
158
|
+
# Valid values: "CNC", "INTRADAY", "MARGIN", "MTF"
|
|
159
|
+
# @option params [String] :order_type (required) Order type.
|
|
160
|
+
# Valid values: "LIMIT", "MARKET"
|
|
161
|
+
# @option params [String] :security_id (required) Exchange standard ID for each scrip
|
|
162
|
+
# @option params [Integer] :quantity (required) Number of shares for the order. Must be greater than 0
|
|
163
|
+
# @option params [Float] :price (required) Price at which order is placed. Must be > 0
|
|
164
|
+
# @option params [Float] :target_price (required) Target price for the Super Order. Must be > 0
|
|
165
|
+
# @option params [Float] :stop_loss_price (required) Stop loss price for the Super Order. Must be > 0
|
|
166
|
+
# @option params [Float] :trailing_jump (required) Price jump by which Stop Loss should be trailed.
|
|
167
|
+
# Must be > 0. If set to 0 or omitted, trailing stop loss will be cancelled
|
|
168
|
+
#
|
|
169
|
+
# @return [SuperOrder, nil] SuperOrder object with order_id and order_status if creation succeeds,
|
|
170
|
+
# nil otherwise.
|
|
171
|
+
# Response structure:
|
|
172
|
+
# - **:order_id** [String] Order-specific identification generated by Dhan
|
|
173
|
+
# - **:order_status** [String] Last updated status of the order.
|
|
174
|
+
# Valid values: "TRANSIT", "PENDING", "REJECTED"
|
|
175
|
+
#
|
|
176
|
+
# @example Create a super order with all legs
|
|
177
|
+
# order = DhanHQ::Models::SuperOrder.create(
|
|
178
|
+
# dhan_client_id: "1000000003",
|
|
179
|
+
# transaction_type: "BUY",
|
|
180
|
+
# exchange_segment: "NSE_EQ",
|
|
181
|
+
# product_type: "CNC",
|
|
182
|
+
# order_type: "LIMIT",
|
|
183
|
+
# security_id: "11536",
|
|
184
|
+
# quantity: 5,
|
|
185
|
+
# price: 1500,
|
|
186
|
+
# target_price: 1600,
|
|
187
|
+
# stop_loss_price: 1400,
|
|
188
|
+
# trailing_jump: 10
|
|
189
|
+
# )
|
|
190
|
+
# puts "Super Order ID: #{order.order_id}"
|
|
191
|
+
#
|
|
192
|
+
# @example Create intraday super order
|
|
193
|
+
# order = DhanHQ::Models::SuperOrder.create(
|
|
194
|
+
# dhan_client_id: "1000000003",
|
|
195
|
+
# transaction_type: "SELL",
|
|
196
|
+
# exchange_segment: "NSE_EQ",
|
|
197
|
+
# product_type: "INTRADAY",
|
|
198
|
+
# order_type: "MARKET",
|
|
199
|
+
# security_id: "1333",
|
|
200
|
+
# quantity: 10,
|
|
201
|
+
# price: 2000,
|
|
202
|
+
# target_price: 1950,
|
|
203
|
+
# stop_loss_price: 2050,
|
|
204
|
+
# trailing_jump: 20
|
|
205
|
+
# )
|
|
35
206
|
#
|
|
36
|
-
# @param params [Hash]
|
|
37
|
-
# @return [SuperOrder, nil]
|
|
38
207
|
def create(params)
|
|
39
|
-
|
|
208
|
+
# Normalize params and auto-inject dhan_client_id from configuration if not provided
|
|
209
|
+
normalized_params = snake_case(params)
|
|
210
|
+
normalized_params[:dhan_client_id] ||= DhanHQ.configuration.client_id if DhanHQ.configuration.client_id
|
|
211
|
+
response = resource.create(normalized_params)
|
|
40
212
|
return nil unless response.is_a?(Hash) && response["orderId"]
|
|
41
213
|
|
|
42
214
|
new(order_id: response["orderId"], order_status: response["orderStatus"], skip_validation: true)
|
|
43
215
|
end
|
|
44
216
|
end
|
|
45
217
|
|
|
46
|
-
|
|
218
|
+
##
|
|
219
|
+
# Modifies any leg of a Super Order while it is in PENDING or PART_TRADED state.
|
|
220
|
+
#
|
|
221
|
+
# The ENTRY_LEG can modify the entire super order and can only be modified when
|
|
222
|
+
# the order status is PENDING or PART_TRADED. Once the entry order status is TRADED,
|
|
223
|
+
# only TARGET_LEG and STOP_LOSS_LEG price and trail jump can be modified.
|
|
224
|
+
#
|
|
225
|
+
# @param new_params [Hash{Symbol => String, Integer, Float}] Fields to modify
|
|
226
|
+
# @option new_params [String] :dhan_client_id (required) User-specific identification generated by Dhan.
|
|
227
|
+
# Must be explicitly provided in the params hash
|
|
228
|
+
# @option new_params [String] :order_id (required) Order-specific identification generated by Dhan
|
|
229
|
+
# @option new_params [String] :leg_name (required) Leg to modify.
|
|
230
|
+
# Valid values:
|
|
231
|
+
# - "ENTRY_LEG" - Entire Super Order can be modified, only when main order status
|
|
232
|
+
# is "PENDING" or "PART_TRADED"
|
|
233
|
+
# - "TARGET_LEG" - Target leg can be modified
|
|
234
|
+
# - "STOP_LOSS_LEG" - Stop loss leg can be modified
|
|
235
|
+
# @option new_params [String] :order_type (conditionally required) Order type.
|
|
236
|
+
# Required for ENTRY_LEG. Valid values: "LIMIT", "MARKET"
|
|
237
|
+
# @option new_params [Integer] :quantity (conditionally required) Quantity to be modified.
|
|
238
|
+
# Required for ENTRY_LEG
|
|
239
|
+
# @option new_params [Float] :price (conditionally required) Price to be modified.
|
|
240
|
+
# Required for ENTRY_LEG
|
|
241
|
+
# @option new_params [Float] :target_price (conditionally required) Target price to be modified.
|
|
242
|
+
# Required for ENTRY_LEG or TARGET_LEG
|
|
243
|
+
# @option new_params [Float] :stop_loss_price (conditionally required) Stop loss price to be modified.
|
|
244
|
+
# Required for ENTRY_LEG or STOP_LOSS_LEG
|
|
245
|
+
# @option new_params [Float] :trailing_jump (conditionally required) Stop loss price jump to be modified.
|
|
246
|
+
# Required for ENTRY_LEG or STOP_LOSS_LEG. If set to 0 or not provided, trailing stop loss will be cancelled
|
|
47
247
|
#
|
|
48
|
-
# @
|
|
49
|
-
#
|
|
248
|
+
# @return [Boolean] true if modification succeeds (orderId matches), false otherwise
|
|
249
|
+
#
|
|
250
|
+
# @example Modify entry leg
|
|
251
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
252
|
+
# order.modify(
|
|
253
|
+
# dhan_client_id: "1000000009",
|
|
254
|
+
# order_id: order.order_id,
|
|
255
|
+
# leg_name: "ENTRY_LEG",
|
|
256
|
+
# order_type: "LIMIT",
|
|
257
|
+
# quantity: 40,
|
|
258
|
+
# price: 1300,
|
|
259
|
+
# target_price: 1450,
|
|
260
|
+
# stop_loss_price: 1350,
|
|
261
|
+
# trailing_jump: 20
|
|
262
|
+
# )
|
|
263
|
+
#
|
|
264
|
+
# @example Modify only target leg (after entry is traded)
|
|
265
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
266
|
+
# order.modify(
|
|
267
|
+
# dhan_client_id: "1000000009",
|
|
268
|
+
# order_id: order.order_id,
|
|
269
|
+
# leg_name: "TARGET_LEG",
|
|
270
|
+
# target_price: 1550
|
|
271
|
+
# )
|
|
272
|
+
#
|
|
273
|
+
# @example Modify stop loss with trailing jump
|
|
274
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
275
|
+
# order.modify(
|
|
276
|
+
# dhan_client_id: "1000000009",
|
|
277
|
+
# order_id: order.order_id,
|
|
278
|
+
# leg_name: "STOP_LOSS_LEG",
|
|
279
|
+
# stop_loss_price: 1250,
|
|
280
|
+
# trailing_jump: 15
|
|
281
|
+
# )
|
|
282
|
+
#
|
|
283
|
+
# @raise [RuntimeError] If order ID is missing
|
|
50
284
|
def modify(new_params)
|
|
51
285
|
raise "Order ID is required to modify a super order" unless id
|
|
52
286
|
|
|
@@ -54,10 +288,36 @@ module DhanHQ
|
|
|
54
288
|
response["orderId"] == id
|
|
55
289
|
end
|
|
56
290
|
|
|
57
|
-
|
|
291
|
+
##
|
|
292
|
+
# Cancels a specific leg of a super order, or the entry leg by default.
|
|
293
|
+
#
|
|
294
|
+
# Cancels a pending/active super order leg using the order ID and leg name.
|
|
295
|
+
# Cancelling the main entry order ID (ENTRY_LEG) cancels all legs. If a particular
|
|
296
|
+
# target or stop loss leg is cancelled, it cannot be added again.
|
|
297
|
+
#
|
|
298
|
+
# @param leg_name [String] (default: "ENTRY_LEG") Order leg to be cancelled.
|
|
299
|
+
# Valid values: "ENTRY_LEG", "TARGET_LEG", "STOP_LOSS_LEG"
|
|
300
|
+
#
|
|
301
|
+
# @return [Boolean] true if cancellation succeeds (order status becomes "CANCELLED"),
|
|
302
|
+
# false otherwise
|
|
303
|
+
#
|
|
304
|
+
# @note The API returns HTTP 202 Accepted on successful cancellation.
|
|
305
|
+
#
|
|
306
|
+
# @example Cancel entry leg (cancels entire super order)
|
|
307
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
308
|
+
# if order.cancel("ENTRY_LEG")
|
|
309
|
+
# puts "Super order cancelled successfully"
|
|
310
|
+
# end
|
|
311
|
+
#
|
|
312
|
+
# @example Cancel only stop loss leg
|
|
313
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
314
|
+
# order.cancel("STOP_LOSS_LEG")
|
|
315
|
+
#
|
|
316
|
+
# @example Cancel only target leg
|
|
317
|
+
# order = DhanHQ::Models::SuperOrder.all.first
|
|
318
|
+
# order.cancel("TARGET_LEG")
|
|
58
319
|
#
|
|
59
|
-
# @
|
|
60
|
-
# @return [Boolean]
|
|
320
|
+
# @raise [RuntimeError] If order ID is missing
|
|
61
321
|
def cancel(leg_name = "ENTRY_LEG")
|
|
62
322
|
raise "Order ID is required to cancel a super order" unless id
|
|
63
323
|
|