DhanHQ 2.1.8 → 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.
@@ -2,7 +2,47 @@
2
2
 
3
3
  module DhanHQ
4
4
  module Models
5
- # Model for the on-demand margin calculator response.
5
+ ##
6
+ # Model for fetching margin calculation results for orders.
7
+ #
8
+ # The Margin Calculator API provides span margin, exposure margin, VAR (variable margin),
9
+ # brokerage, leverage, and available margin values for any type of order and instrument
10
+ # you want to place. This helps you determine the margin requirements before placing an order.
11
+ #
12
+ # @example Calculate margin for a CNC order
13
+ # margin = DhanHQ::Models::Margin.calculate(
14
+ # dhan_client_id: "1000000132",
15
+ # exchange_segment: "NSE_EQ",
16
+ # transaction_type: "BUY",
17
+ # quantity: 5,
18
+ # product_type: "CNC",
19
+ # security_id: "1333",
20
+ # price: 1428.0
21
+ # )
22
+ # puts "Total margin required: ₹#{margin.total_margin}"
23
+ # puts "Available balance: ₹#{margin.available_balance}"
24
+ #
25
+ # @example Calculate margin for stop-loss order
26
+ # margin = DhanHQ::Models::Margin.calculate(
27
+ # dhan_client_id: "1000000132",
28
+ # exchange_segment: "NSE_EQ",
29
+ # transaction_type: "BUY",
30
+ # quantity: 10,
31
+ # product_type: "INTRADAY",
32
+ # security_id: "1333",
33
+ # price: 1428.0,
34
+ # trigger_price: 1427.0
35
+ # )
36
+ # puts "Leverage: #{margin.leverage}x"
37
+ #
38
+ # @example Check if sufficient margin is available
39
+ # margin = DhanHQ::Models::Margin.calculate(params)
40
+ # if margin.insufficient_balance > 0
41
+ # puts "Insufficient balance: ₹#{margin.insufficient_balance}"
42
+ # else
43
+ # puts "Sufficient margin available"
44
+ # end
45
+ #
6
46
  class Margin < BaseModel
7
47
  # Base path used to invoke the calculator.
8
48
  HTTP_PATH = "/v2/margincalculator"
@@ -12,18 +52,86 @@ module DhanHQ
12
52
 
13
53
  class << self
14
54
  ##
15
- # Provides a **shared instance** of the `MarginCalculator` resource.
55
+ # Provides a shared instance of the MarginCalculator resource.
16
56
  #
17
- # @return [DhanHQ::Resources::MarginCalculator]
57
+ # @return [DhanHQ::Resources::MarginCalculator] The MarginCalculator resource client instance
18
58
  def resource
19
59
  @resource ||= DhanHQ::Resources::MarginCalculator.new
20
60
  end
21
61
 
22
62
  ##
23
- # Calculate margin requirements for an order.
63
+ # Calculates margin requirements for an order before placement.
24
64
  #
25
- # @param params [Hash] Request parameters for margin calculation.
26
- # @return [Margin]
65
+ # Fetches span margin, exposure margin, VAR (variable margin), brokerage, leverage,
66
+ # and available margin values for the specified order parameters. This allows you to
67
+ # check margin requirements and availability before actually placing the order.
68
+ #
69
+ # @param params [Hash{Symbol => String, Integer, Float}] Request parameters for margin calculation
70
+ # @option params [String] :dhan_client_id (required) User-specific identification generated by Dhan.
71
+ # Must be explicitly provided in the params hash
72
+ # @option params [String] :exchange_segment (required) Exchange and segment identifier.
73
+ # Valid values: "NSE_EQ", "NSE_FNO", "BSE_EQ", "BSE_FNO", "MCX_COMM"
74
+ # @option params [String] :transaction_type (required) The trading side of transaction.
75
+ # Valid values: "BUY", "SELL"
76
+ # @option params [Integer] :quantity (required) Number of shares for the order. Must be greater than 0
77
+ # @option params [String] :product_type (required) Product type.
78
+ # Valid values: "CNC", "INTRADAY", "MARGIN", "MTF", "CO", "BO"
79
+ # @option params [String] :security_id (required) Exchange standard ID for each scrip
80
+ # @option params [Float] :price (required) Price at which order is placed. Must be greater than 0
81
+ # @option params [Float] :trigger_price (conditionally required) Price at which the order is triggered.
82
+ # Required for STOP_LOSS and STOP_LOSS_MARKET order types
83
+ #
84
+ # @return [Margin] Margin object containing margin calculation results.
85
+ # Response structure (keys normalized to snake_case):
86
+ # - **:total_margin** [Float] Total margin required for placing the order successfully
87
+ # - **:span_margin** [Float] SPAN margin required
88
+ # - **:exposure_margin** [Float] Exposure margin required
89
+ # - **:available_balance** [Float] Available amount in trading account
90
+ # - **:variable_margin** [Float] VAR or Variable margin required
91
+ # - **:insufficient_balance** [Float] Insufficient amount in trading account
92
+ # (Available Balance - Total Margin). Negative or zero indicates sufficient margin
93
+ # - **:brokerage** [Float] Brokerage charges for executing the order
94
+ # - **:leverage** [String] Margin leverage provided for the order as per product type
95
+ #
96
+ # @example Calculate margin for CNC equity order
97
+ # margin = DhanHQ::Models::Margin.calculate(
98
+ # dhan_client_id: "1000000132",
99
+ # exchange_segment: "NSE_EQ",
100
+ # transaction_type: "BUY",
101
+ # quantity: 5,
102
+ # product_type: "CNC",
103
+ # security_id: "1333",
104
+ # price: 1428.0
105
+ # )
106
+ # puts "Total Margin: ₹#{margin.total_margin}"
107
+ # puts "Brokerage: ₹#{margin.brokerage}"
108
+ #
109
+ # @example Calculate margin for intraday order
110
+ # margin = DhanHQ::Models::Margin.calculate(
111
+ # dhan_client_id: "1000000132",
112
+ # exchange_segment: "NSE_EQ",
113
+ # transaction_type: "SELL",
114
+ # quantity: 10,
115
+ # product_type: "INTRADAY",
116
+ # security_id: "1333",
117
+ # price: 1500.0
118
+ # )
119
+ # puts "Leverage: #{margin.leverage}x"
120
+ # puts "SPAN Margin: ₹#{margin.span_margin}"
121
+ #
122
+ # @example Calculate margin for stop-loss order
123
+ # margin = DhanHQ::Models::Margin.calculate(
124
+ # dhan_client_id: "1000000132",
125
+ # exchange_segment: "NSE_EQ",
126
+ # transaction_type: "BUY",
127
+ # quantity: 5,
128
+ # product_type: "INTRADAY",
129
+ # security_id: "1333",
130
+ # price: 1428.0,
131
+ # trigger_price: 1427.0
132
+ # )
133
+ #
134
+ # @raise [DhanHQ::ValidationError] If validation fails for any parameter
27
135
  def calculate(params)
28
136
  formatted_params = camelize_keys(params)
29
137
  validate_params!(formatted_params, DhanHQ::Contracts::MarginCalculatorContract)
@@ -34,9 +142,26 @@ module DhanHQ
34
142
  end
35
143
 
36
144
  ##
37
- # Convert model attributes to a hash.
145
+ # Converts the Margin model attributes to a hash representation.
146
+ #
147
+ # Useful for serialization, logging, or passing margin data to other methods.
148
+ #
149
+ # @return [Hash{Symbol => Float, String}] Hash representation of the Margin model containing:
150
+ # - **:total_margin** [Float] Total margin required
151
+ # - **:span_margin** [Float] SPAN margin
152
+ # - **:exposure_margin** [Float] Exposure margin
153
+ # - **:available_balance** [Float] Available balance
154
+ # - **:variable_margin** [Float] Variable margin
155
+ # - **:insufficient_balance** [Float] Insufficient balance amount
156
+ # - **:brokerage** [Float] Brokerage charges
157
+ # - **:leverage** [String] Leverage as string
158
+ #
159
+ # @example Convert margin to hash
160
+ # margin = DhanHQ::Models::Margin.calculate(params)
161
+ # margin_hash = margin.to_h
162
+ # puts margin_hash[:total_margin] # => 2800.00
163
+ # puts margin_hash[:leverage] # => "4.00"
38
164
  #
39
- # @return [Hash] Hash representation of the Margin model.
40
165
  def to_h
41
166
  {
42
167
  total_margin: total_margin,
@@ -2,39 +2,203 @@
2
2
 
3
3
  module DhanHQ
4
4
  module Models
5
- # Lightweight wrapper exposing market feed resources.
5
+ ##
6
+ # Model for fetching real-time market snapshots for multiple instruments.
7
+ #
8
+ # The Market Feed API provides snapshots of multiple instruments at once. You can fetch
9
+ # LTP (Last Traded Price), OHLC (Open, High, Low, Close), or Market Depth (quote) data
10
+ # for instruments via a single API request. Data is returned in real-time at the time
11
+ # of the API request.
12
+ #
13
+ # @note **Rate Limits**: You can fetch up to 1000 instruments in a single API request
14
+ # with a rate limit of 1 request per second. The client's internal rate limiter
15
+ # automatically throttles calls to prevent exceeding limits.
16
+ #
17
+ # @example Fetch LTP for multiple instruments
18
+ # payload = {
19
+ # "NSE_EQ" => [11536, 3456],
20
+ # "NSE_FNO" => [49081, 49082]
21
+ # }
22
+ # response = DhanHQ::Models::MarketFeed.ltp(payload)
23
+ # nse_eq_data = response[:data]["NSE_EQ"]
24
+ # puts "TCS LTP: ₹#{nse_eq_data["11536"][:last_price]}"
25
+ #
26
+ # @example Fetch OHLC data for equity instruments
27
+ # payload = {
28
+ # "NSE_EQ" => [11536]
29
+ # }
30
+ # response = DhanHQ::Models::MarketFeed.ohlc(payload)
31
+ # tcs_data = response[:data]["NSE_EQ"]["11536"]
32
+ # puts "Open: ₹#{tcs_data[:ohlc][:open]}"
33
+ # puts "High: ₹#{tcs_data[:ohlc][:high]}"
34
+ #
35
+ # @example Fetch full market depth quote
36
+ # payload = {
37
+ # "NSE_FNO" => [49081]
38
+ # }
39
+ # response = DhanHQ::Models::MarketFeed.quote(payload)
40
+ # quote_data = response[:data]["NSE_FNO"]["49081"]
41
+ # puts "Volume: #{quote_data[:volume]}"
42
+ # puts "Open Interest: #{quote_data[:oi]}"
43
+ #
6
44
  class MarketFeed < BaseModel
7
45
  class << self
8
- # Fetches last traded price snapshots.
46
+ ##
47
+ # Provides a shared instance of the MarketFeed resource.
48
+ #
49
+ # @return [DhanHQ::Resources::MarketFeed] The MarketFeed resource client instance
50
+ def resource
51
+ @resource ||= DhanHQ::Resources::MarketFeed.new
52
+ end
53
+
54
+ ##
55
+ # Fetches Last Traded Price (LTP) snapshots for multiple instruments.
56
+ #
57
+ # Retrieves the last traded price for a list of instruments with a single API request.
58
+ # Supports up to 1000 instruments per request, organized by exchange segment.
59
+ #
60
+ # @param params [Hash{String => Array<Integer>}] Request payload mapping exchange segments
61
+ # to arrays of security IDs. Exchange segments are keys (e.g., "NSE_EQ", "NSE_FNO", "BSE_EQ").
62
+ # Values are arrays of security IDs (integer identifiers for each scrip).
63
+ # Valid exchange segment values: "NSE_EQ", "NSE_FNO", "BSE_EQ", "BSE_FNO", etc.
64
+ #
65
+ # @return [HashWithIndifferentAccess] Response hash containing market data.
66
+ # Response structure:
67
+ # - **:data** [Hash{String => Hash{String => Hash}}] Market data organized by exchange segment
68
+ # and security ID. Each instrument's data contains:
69
+ # - **:last_price** [Float] Last traded price of the instrument
70
+ # - **:status** [String] Response status (typically "success")
71
+ #
72
+ # @example Fetch LTP for equity and F&O instruments
73
+ # payload = {
74
+ # "NSE_EQ" => [11536],
75
+ # "NSE_FNO" => [49081, 49082]
76
+ # }
77
+ # response = DhanHQ::Models::MarketFeed.ltp(payload)
78
+ # tcs_ltp = response[:data]["NSE_EQ"]["11536"][:last_price]
79
+ # puts "TCS LTP: ₹#{tcs_ltp}"
80
+ #
81
+ # @example Access data from response
82
+ # response = DhanHQ::Models::MarketFeed.ltp("NSE_EQ" => [11536])
83
+ # data = response[:data]["NSE_EQ"]["11536"]
84
+ # puts "Last Price: ₹#{data[:last_price]}"
9
85
  #
10
- # @param params [Hash]
11
- # @return [Hash]
12
86
  def ltp(params)
13
87
  resource.ltp(params)
14
88
  end
15
89
 
16
- # Fetches OHLC data for the requested instruments.
90
+ ##
91
+ # Fetches OHLC (Open, High, Low, Close) data along with LTP for specified instruments.
92
+ #
93
+ # Retrieves the open, high, low, and close prices along with the last traded price
94
+ # for a list of instruments. Supports up to 1000 instruments per request.
95
+ #
96
+ # @param params [Hash{String => Array<Integer>}] Request payload mapping exchange segments
97
+ # to arrays of security IDs. Exchange segments are keys (e.g., "NSE_EQ", "NSE_FNO", "BSE_EQ").
98
+ # Values are arrays of security IDs (integer identifiers for each scrip).
99
+ # Valid exchange segment values: "NSE_EQ", "NSE_FNO", "BSE_EQ", "BSE_FNO", etc.
100
+ #
101
+ # @return [HashWithIndifferentAccess] Response hash containing OHLC market data.
102
+ # Response structure:
103
+ # - **:data** [Hash{String => Hash{String => Hash}}] Market data organized by exchange segment
104
+ # and security ID. Each instrument's data contains:
105
+ # - **:last_price** [Float] Last traded price of the instrument
106
+ # - **:ohlc** [Hash{Symbol => Float}] OHLC data:
107
+ # - **:open** [Float] Market opening price of the day
108
+ # - **:close** [Float] Market closing price of the day (previous day close for current session)
109
+ # - **:high** [Float] Day high price
110
+ # - **:low** [Float] Day low price
111
+ # - **:status** [String] Response status (typically "success")
112
+ #
113
+ # @note For newly listed instruments or instruments without trading activity,
114
+ # OHLC values may be 0. The close price typically represents the previous day's
115
+ # closing price during the current trading session.
116
+ #
117
+ # @example Fetch OHLC for equity instruments
118
+ # payload = {
119
+ # "NSE_EQ" => [11536]
120
+ # }
121
+ # response = DhanHQ::Models::MarketFeed.ohlc(payload)
122
+ # tcs_data = response[:data]["NSE_EQ"]["11536"]
123
+ # puts "Open: ₹#{tcs_data[:ohlc][:open]}"
124
+ # puts "High: ₹#{tcs_data[:ohlc][:high]}"
125
+ # puts "Low: ₹#{tcs_data[:ohlc][:low]}"
126
+ # puts "Close: ₹#{tcs_data[:ohlc][:close]}"
127
+ # puts "LTP: ₹#{tcs_data[:last_price]}"
17
128
  #
18
- # @param params [Hash]
19
- # @return [Hash]
20
129
  def ohlc(params)
21
130
  resource.ohlc(params)
22
131
  end
23
132
 
24
- # Fetches full quote depth and analytics.
133
+ ##
134
+ # Fetches full market depth data including OHLC, Open Interest, Volume, and order book depth.
135
+ #
136
+ # Retrieves comprehensive market data including market depth (buy/sell orders), OHLC data,
137
+ # Open Interest (for derivatives), Volume, circuit limits, and other trading analytics
138
+ # for specified instruments. Supports up to 1000 instruments per request.
139
+ #
140
+ # @param params [Hash{String => Array<Integer>}] Request payload mapping exchange segments
141
+ # to arrays of security IDs. Exchange segments are keys (e.g., "NSE_EQ", "NSE_FNO", "BSE_EQ").
142
+ # Values are arrays of security IDs (integer identifiers for each scrip).
143
+ # Valid exchange segment values: "NSE_EQ", "NSE_FNO", "BSE_EQ", "BSE_FNO", etc.
144
+ #
145
+ # @return [HashWithIndifferentAccess] Response hash containing full quote market data.
146
+ # Response structure:
147
+ # - **:data** [Hash{String => Hash{String => Hash}}] Market data organized by exchange segment
148
+ # and security ID. Each instrument's data contains:
149
+ # - **:last_price** [Float] Last traded price of the instrument
150
+ # - **:last_quantity** [Integer] Last traded quantity
151
+ # - **:last_trade_time** [String] Timestamp of last trade in "DD/MM/YYYY HH:MM:SS" format
152
+ # - **:average_price** [Float] Volume weighted average price (VWAP) of the day
153
+ # - **:buy_quantity** [Integer] Total buy order quantity pending at the exchange
154
+ # - **:sell_quantity** [Integer] Total sell order quantity pending at the exchange
155
+ # - **:volume** [Integer] Total traded volume for the day
156
+ # - **:oi** [Integer] Open Interest in the contract (for derivatives)
157
+ # - **:oi_day_high** [Integer] Highest Open Interest for the day (only for NSE_FNO)
158
+ # - **:oi_day_low** [Integer] Lowest Open Interest for the day (only for NSE_FNO)
159
+ # - **:net_change** [Float] Absolute change in LTP from previous day closing price
160
+ # - **:upper_circuit_limit** [Float] Current upper circuit limit
161
+ # - **:lower_circuit_limit** [Float] Current lower circuit limit
162
+ # - **:ohlc** [Hash{Symbol => Float}] OHLC data:
163
+ # - **:open** [Float] Market opening price of the day
164
+ # - **:close** [Float] Market closing price of the day
165
+ # - **:high** [Float] Day high price
166
+ # - **:low** [Float] Day low price
167
+ # - **:depth** [Hash{Symbol => Array<Hash>}] Market depth (order book) data:
168
+ # - **:buy** [Array<Hash{Symbol => Integer, Float}>] Buy side depth levels (up to 5 levels):
169
+ # - **:quantity** [Integer] Number of quantity at this price depth
170
+ # - **:orders** [Integer] Number of open BUY orders at this price depth
171
+ # - **:price** [Float] Price at which the BUY depth stands
172
+ # - **:sell** [Array<Hash{Symbol => Integer, Float}>] Sell side depth levels (up to 5 levels):
173
+ # - **:quantity** [Integer] Number of quantity at this price depth
174
+ # - **:orders** [Integer] Number of open SELL orders at this price depth
175
+ # - **:price** [Float] Price at which the SELL depth stands
176
+ # - **:status** [String] Response status (typically "success")
177
+ #
178
+ # @note This endpoint uses a separate quote API with stricter rate limits (1 request per second).
179
+ # The client automatically handles rate limiting for quote requests.
180
+ #
181
+ # @example Fetch full quote for futures contract
182
+ # payload = {
183
+ # "NSE_FNO" => [49081]
184
+ # }
185
+ # response = DhanHQ::Models::MarketFeed.quote(payload)
186
+ # quote = response[:data]["NSE_FNO"]["49081"]
187
+ # puts "LTP: ₹#{quote[:last_price]}"
188
+ # puts "Volume: #{quote[:volume]}"
189
+ # puts "Open Interest: #{quote[:oi]}"
190
+ # puts "Day High OI: #{quote[:oi_day_high]}"
191
+ #
192
+ # @example Access market depth (order book)
193
+ # response = DhanHQ::Models::MarketFeed.quote("NSE_FNO" => [49081])
194
+ # quote = response[:data]["NSE_FNO"]["49081"]
195
+ # buy_depth = quote[:depth][:buy]
196
+ # puts "Best Buy Price: ₹#{buy_depth[0][:price]}"
197
+ # puts "Best Buy Quantity: #{buy_depth[0][:quantity]}"
25
198
  #
26
- # @param params [Hash]
27
- # @return [Hash]
28
199
  def quote(params)
29
200
  resource.quote(params)
30
201
  end
31
-
32
- # Shared market feed resource instance.
33
- #
34
- # @return [DhanHQ::Resources::MarketFeed]
35
- def resource
36
- @resource ||= DhanHQ::Resources::MarketFeed.new
37
- end
38
202
  end
39
203
  end
40
204
  end
@@ -4,22 +4,151 @@ require_relative "../contracts/option_chain_contract"
4
4
 
5
5
  module DhanHQ
6
6
  module Models
7
- # Model for fetching and filtering option chain snapshots.
7
+ ##
8
+ # Model for fetching option chain data for any option instrument across exchanges.
9
+ #
10
+ # The Option Chain API provides the entire option chain for any underlying instrument
11
+ # across NSE, BSE, and MCX exchanges. For each strike price, you get Open Interest (OI),
12
+ # Greeks (Delta, Theta, Gamma, Vega), Volume, Last Traded Price, Best Bid/Ask prices,
13
+ # Implied Volatility (IV), and other option analytics.
14
+ #
15
+ # @note **Rate Limits**: You can call the Option Chain API once every 3 seconds.
16
+ # This rate limit is enforced because OI data updates slowly compared to LTP or
17
+ # other data parameters. The client's internal rate limiter automatically throttles
18
+ # calls to prevent exceeding limits.
19
+ #
20
+ # @note **Data Filtering**: The model automatically filters out strikes where both
21
+ # Call (CE) and Put (PE) options have zero `last_price`, keeping the payload compact
22
+ # and focused on actively traded strikes.
23
+ #
24
+ # @example Fetch option chain for NIFTY index options
25
+ # chain = DhanHQ::Models::OptionChain.fetch(
26
+ # underlying_scrip: 13,
27
+ # underlying_seg: "IDX_I",
28
+ # expiry: "2024-10-31"
29
+ # )
30
+ # puts "Underlying LTP: ₹#{chain[:last_price]}"
31
+ # nifty_25000 = chain[:oc]["25000.000000"]
32
+ # puts "CE LTP: ₹#{nifty_25000['ce'][:last_price]}"
33
+ # puts "CE OI: #{nifty_25000['ce'][:oi]}"
34
+ #
35
+ # @example Fetch expiry list for an underlying
36
+ # expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(
37
+ # underlying_scrip: 13,
38
+ # underlying_seg: "IDX_I"
39
+ # )
40
+ # expiries.each { |expiry| puts expiry }
41
+ #
42
+ # @example Access Greeks for a strike
43
+ # chain = DhanHQ::Models::OptionChain.fetch(
44
+ # underlying_scrip: 1333,
45
+ # underlying_seg: "NSE_FNO",
46
+ # expiry: "2024-12-26"
47
+ # )
48
+ # strike_data = chain[:oc]["25000.000000"]
49
+ # ce_greeks = strike_data['ce'][:greeks]
50
+ # puts "Delta: #{ce_greeks[:delta]}"
51
+ # puts "Gamma: #{ce_greeks[:gamma]}"
52
+ # puts "Theta: #{ce_greeks[:theta]}"
53
+ # puts "Vega: #{ce_greeks[:vega]}"
54
+ #
8
55
  class OptionChain < BaseModel
9
56
  attr_reader :underlying_scrip, :underlying_seg, :expiry, :last_price, :option_data
10
57
 
11
58
  class << self
12
- # Shared resource for option chain operations.
59
+ ##
60
+ # Provides a shared instance of the OptionChain resource.
13
61
  #
14
- # @return [DhanHQ::Resources::OptionChain]
62
+ # @return [DhanHQ::Resources::OptionChain] The OptionChain resource client instance
15
63
  def resource
16
64
  @resource ||= DhanHQ::Resources::OptionChain.new
17
65
  end
18
66
 
19
- # Fetch the entire option chain for an instrument
67
+ ##
68
+ # Fetches the entire option chain for a specified underlying instrument and expiry.
20
69
  #
21
- # @param params [Hash] The request parameters (snake_case format)
22
- # @return [HashWithIndifferentAccess] The filtered option chain data
70
+ # Retrieves real-time option chain data across all strikes for the given underlying.
71
+ # The response includes Open Interest (OI), Greeks, Volume, Last Traded Price,
72
+ # Best Bid/Ask prices, Implied Volatility (IV), and other option analytics for
73
+ # both Call (CE) and Put (PE) options at each strike price.
74
+ #
75
+ # @param params [Hash{Symbol => Integer, String}] Request parameters for option chain
76
+ # @option params [Integer] :underlying_scrip (required) Security ID of the underlying
77
+ # instrument. Can be found via the Instruments API.
78
+ # @option params [String] :underlying_seg (required) Exchange and segment of underlying
79
+ # for which data is to be fetched.
80
+ # Valid values: "IDX_I" (Index), "NSE_FNO" (NSE F&O), "BSE_FNO" (BSE F&O), "MCX_FO" (MCX)
81
+ # @option params [String] :expiry (required) Expiry date of the option contract for
82
+ # which the option chain is requested. Must be in "YYYY-MM-DD" format.
83
+ # List of active expiries can be fetched using {fetch_expiry_list}.
84
+ #
85
+ # @return [HashWithIndifferentAccess] Filtered option chain data.
86
+ # Response structure:
87
+ # - **:last_price** [Float] Last Traded Price (LTP) of the underlying instrument
88
+ # - **:oc** [Hash{String => Hash}] Option chain data organized by strike price.
89
+ # Strike prices are stored as string keys (e.g., "25000.000000").
90
+ # Each strike contains:
91
+ # - **"ce"** [Hash{Symbol => Float, Integer, Hash}] Call Option data for this strike:
92
+ # - **:greeks** [Hash{Symbol => Float}] Option Greeks:
93
+ # - **:delta** [Float] Measures the change of option's premium based on
94
+ # every 1 rupee change in underlying
95
+ # - **:theta** [Float] Measures how quickly an option's value decreases over time
96
+ # - **:gamma** [Float] Rate of change in an option's delta in relation to the
97
+ # price of the underlying asset
98
+ # - **:vega** [Float] Measures the change of option's premium in response to
99
+ # a 1% change in implied volatility
100
+ # - **:implied_volatility** [Float] Value of expected volatility of a stock
101
+ # over the life of the option
102
+ # - **:last_price** [Float] Last Traded Price of the Call Option Instrument
103
+ # - **:oi** [Integer] Open Interest of the Call Option Instrument
104
+ # - **:previous_close_price** [Float] Previous day close price
105
+ # - **:previous_oi** [Integer] Previous day Open Interest
106
+ # - **:previous_volume** [Integer] Previous day volume
107
+ # - **:top_ask_price** [Float] Current best ask price available
108
+ # - **:top_ask_quantity** [Integer] Quantity available at current best ask price
109
+ # - **:top_bid_price** [Float] Current best bid price available
110
+ # - **:top_bid_quantity** [Integer] Quantity available at current best bid price
111
+ # - **:volume** [Integer] Day volume for Call Option Instrument
112
+ # - **"pe"** [Hash{Symbol => Float, Integer, Hash}] Put Option data for this strike.
113
+ # Contains the same fields as "ce" (Call Option data).
114
+ #
115
+ # @note Strikes where both CE and PE have zero `last_price` are automatically filtered out.
116
+ # This keeps the payload compact and focused on actively traded strikes.
117
+ #
118
+ # @example Fetch option chain for NIFTY index options
119
+ # chain = DhanHQ::Models::OptionChain.fetch(
120
+ # underlying_scrip: 13,
121
+ # underlying_seg: "IDX_I",
122
+ # expiry: "2024-10-31"
123
+ # )
124
+ # puts "NIFTY LTP: ₹#{chain[:last_price]}"
125
+ #
126
+ # @example Access Call and Put data for a specific strike
127
+ # chain = DhanHQ::Models::OptionChain.fetch(
128
+ # underlying_scrip: 13,
129
+ # underlying_seg: "IDX_I",
130
+ # expiry: "2024-10-31"
131
+ # )
132
+ # strike_25000 = chain[:oc]["25000.000000"]
133
+ # ce_data = strike_25000["ce"]
134
+ # pe_data = strike_25000["pe"]
135
+ # puts "CE LTP: ₹#{ce_data[:last_price]}, OI: #{ce_data[:oi]}"
136
+ # puts "PE LTP: ₹#{pe_data[:last_price]}, OI: #{pe_data[:oi]}"
137
+ #
138
+ # @example Calculate OI change and analyze Greeks
139
+ # chain = DhanHQ::Models::OptionChain.fetch(
140
+ # underlying_scrip: 1333,
141
+ # underlying_seg: "NSE_FNO",
142
+ # expiry: "2024-12-26"
143
+ # )
144
+ # strike_data = chain[:oc]["25000.000000"]
145
+ # ce = strike_data["ce"]
146
+ # oi_change = ce[:oi] - ce[:previous_oi]
147
+ # puts "OI Change: #{oi_change}"
148
+ # puts "Delta: #{ce[:greeks][:delta]}"
149
+ # puts "IV: #{ce[:implied_volatility]}%"
150
+ #
151
+ # @raise [DhanHQ::ValidationError] If validation fails for any parameter or date format
23
152
  def fetch(params)
24
153
  validate_params!(params, DhanHQ::Contracts::OptionChainContract)
25
154
 
@@ -29,10 +158,44 @@ module DhanHQ
29
158
  filter_valid_strikes(response[:data]).with_indifferent_access
30
159
  end
31
160
 
32
- # Fetch the expiry list of an underlying security
161
+ ##
162
+ # Fetches the list of active expiry dates for an underlying instrument.
33
163
  #
34
- # @param params [Hash] The request parameters (snake_case format)
35
- # @return [Array<String>] The list of expiry dates
164
+ # Retrieves all expiry dates for which option instruments are active for the given
165
+ # underlying. This list is useful for selecting valid expiry dates when fetching
166
+ # option chains.
167
+ #
168
+ # @param params [Hash{Symbol => Integer, String}] Request parameters for expiry list
169
+ # @option params [Integer] :underlying_scrip (required) Security ID of the underlying
170
+ # instrument. Can be found via the Instruments API.
171
+ # @option params [String] :underlying_seg (required) Exchange and segment of underlying
172
+ # for which expiry list is to be fetched.
173
+ # Valid values: "IDX_I" (Index), "NSE_FNO" (NSE F&O), "BSE_FNO" (BSE F&O), "MCX_FO" (MCX)
174
+ #
175
+ # @return [Array<String>] Array of expiry dates in "YYYY-MM-DD" format.
176
+ # Returns empty array if the API response status is not "success" or if no expiries are found.
177
+ #
178
+ # @example Fetch expiry list for NIFTY index
179
+ # expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(
180
+ # underlying_scrip: 13,
181
+ # underlying_seg: "IDX_I"
182
+ # )
183
+ # puts "Available expiries:"
184
+ # expiries.each { |expiry| puts " #{expiry}" }
185
+ #
186
+ # @example Use expiry list to fetch option chains
187
+ # expiries = DhanHQ::Models::OptionChain.fetch_expiry_list(
188
+ # underlying_scrip: 1333,
189
+ # underlying_seg: "NSE_FNO"
190
+ # )
191
+ # nearest_expiry = expiries.first
192
+ # chain = DhanHQ::Models::OptionChain.fetch(
193
+ # underlying_scrip: 1333,
194
+ # underlying_seg: "NSE_FNO",
195
+ # expiry: nearest_expiry
196
+ # )
197
+ #
198
+ # @raise [DhanHQ::ValidationError] If validation fails for any parameter
36
199
  def fetch_expiry_list(params)
37
200
  validate_params!(params, DhanHQ::Contracts::OptionChainExpiryListContract)
38
201
 
@@ -42,10 +205,17 @@ module DhanHQ
42
205
 
43
206
  private
44
207
 
45
- # **Filters valid strikes where `ce` or `pe` has `last_price > 0` and keeps strike prices as-is**
208
+ ##
209
+ # Filters valid strikes where at least one of CE or PE has a non-zero last_price.
210
+ #
211
+ # Removes strikes from the option chain where both Call (CE) and Put (PE) options
212
+ # have zero `last_price`, keeping only actively traded strikes. This keeps the
213
+ # payload compact and focused on relevant data.
214
+ #
215
+ # @param data [Hash] The API response data containing option chain information
216
+ # @return [Hash] The filtered option chain data with original strike price keys preserved
46
217
  #
47
- # @param data [Hash] The API response data
48
- # @return [Hash] The filtered option chain data with original strike price keys
218
+ # @api private
49
219
  def filter_valid_strikes(data)
50
220
  return {} unless data.is_a?(Hash) && data.key?(:oc)
51
221
 
@@ -63,6 +233,7 @@ module DhanHQ
63
233
  # Validation contract for option chain
64
234
  #
65
235
  # @return [DhanHQ::Contracts::OptionChainContract]
236
+ # @api private
66
237
  def validation_contract
67
238
  DhanHQ::Contracts::OptionChainContract.new
68
239
  end
@@ -73,6 +244,7 @@ module DhanHQ
73
244
  # Validation contract for option chain
74
245
  #
75
246
  # @return [DhanHQ::Contracts::OptionChainContract]
247
+ # @api private
76
248
  def validation_contract
77
249
  DhanHQ::Contracts::OptionChainContract.new
78
250
  end