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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +58 -0
  3. data/examples/comprehensive_websocket_examples.rb +0 -0
  4. data/examples/instrument_finder_test.rb +0 -0
  5. data/examples/live_order_updates.rb +1 -1
  6. data/examples/market_depth_example.rb +2 -2
  7. data/examples/order_update_example.rb +0 -0
  8. data/examples/trading_fields_example.rb +1 -1
  9. data/lib/DhanHQ/client.rb +2 -1
  10. data/lib/DhanHQ/contracts/expired_options_data_contract.rb +6 -6
  11. data/lib/DhanHQ/core/base_model.rb +9 -1
  12. data/lib/DhanHQ/models/edis.rb +150 -14
  13. data/lib/DhanHQ/models/expired_options_data.rb +307 -82
  14. data/lib/DhanHQ/models/forever_order.rb +261 -22
  15. data/lib/DhanHQ/models/funds.rb +76 -10
  16. data/lib/DhanHQ/models/historical_data.rb +148 -31
  17. data/lib/DhanHQ/models/holding.rb +82 -6
  18. data/lib/DhanHQ/models/instrument_helpers.rb +39 -5
  19. data/lib/DhanHQ/models/kill_switch.rb +113 -11
  20. data/lib/DhanHQ/models/ledger_entry.rb +101 -13
  21. data/lib/DhanHQ/models/margin.rb +133 -8
  22. data/lib/DhanHQ/models/market_feed.rb +181 -17
  23. data/lib/DhanHQ/models/option_chain.rb +184 -12
  24. data/lib/DhanHQ/models/order.rb +399 -34
  25. data/lib/DhanHQ/models/position.rb +161 -10
  26. data/lib/DhanHQ/models/profile.rb +103 -7
  27. data/lib/DhanHQ/models/super_order.rb +275 -15
  28. data/lib/DhanHQ/models/trade.rb +279 -26
  29. data/lib/DhanHQ/rate_limiter.rb +78 -23
  30. data/lib/DhanHQ/resources/expired_options_data.rb +1 -1
  31. data/lib/DhanHQ/version.rb +1 -1
  32. data/lib/DhanHQ/ws/market_depth/client.rb +3 -1
  33. data/lib/DhanHQ/ws/orders/client.rb +2 -1
  34. data/lib/DhanHQ/ws/orders/connection.rb +0 -3
  35. data/lib/DhanHQ/ws/orders.rb +2 -1
  36. metadata +1 -1
@@ -2,7 +2,36 @@
2
2
 
3
3
  module DhanHQ
4
4
  module Models
5
- # Model representing an intraday or carry-forward position snapshot.
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 **shared instance** of the `Positions` resource.
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
- # Fetch all positions for the day.
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
- # Filters the position list down to non-closed entries.
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
- # Convert an existing position (intraday <-> delivery)
48
- # @param params [Hash] parameters as required by the API
49
- # @return [Hash, DhanHQ::ErrorObject]
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
- # Ruby wrapper around the `/v2/profile` endpoint. Provides typed accessors
7
- # and snake_case keys while leaving the underlying response untouched.
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 profile resource.
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
- # Fetch the authenticated user's profile details.
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
- # @return [nil]
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
- # Model wrapping multi-leg super order payloads.
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
- # Shared resource instance used for API calls.
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
- # Fetches all configured super orders.
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
- # Creates a new super order with the provided legs.
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
- response = resource.create(params)
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
- # Updates the order legs for an existing super order.
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
- # @param new_params [Hash]
49
- # @return [Boolean]
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
- # Cancels a specific leg (or the entry leg by default).
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
- # @param leg_name [String]
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