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
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1ffcb72b2997f7841ef6c872ad71748c36f4305f9e746a5d4d8f2a3b9429c8da
4
- data.tar.gz: 5a72519746d9ca58f6cb295855f9065ccc3b9059c2684347b58c3e998ad8668b
3
+ metadata.gz: 300207afdb6b293a9b73b5cf6d2bb11da0eb8251bed6ab489c115287ab201051
4
+ data.tar.gz: 49fde0cde20215c959d167860529ccaa2e984b1aa46653f8024a463a8ff8db53
5
5
  SHA512:
6
- metadata.gz: 397d51627f7049470332e23926ca7d00d78aad94d88eb0939d25164dc74330d68014a5ad6593453dfedb2da51c4f2a810ecc7e971d5f1735b3b21e96d3d068df
7
- data.tar.gz: 2092c00ab1903f18d7718bd27125236251d2129cd536a071b506c3a53d2826a66eb5c9ba737ab89eff52b125e864138b507e577616ffc9e0a034da0a4eb8e37a
6
+ metadata.gz: eb75dbe33b1c937cb825dde8f8a2e86748f1fce2f230a52190ca5862a900f40b64d7f17319cf34c6847253d5c98ab42396e6ee5d46b9ae66c26c34c473d7fb69
7
+ data.tar.gz: d491b32c1ec671414d2d1c7bdef765cde05631bca5be3c41ff1df66d7060901b6e58b5612092891e25b3d06b7b920525ef69097a91b3104cdd2364b8c97c3a6f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,63 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [2.1.10] - 2025-11-11
4
+
5
+ ### Fixed
6
+ - Expired Options Data routing: send `client-id` header for `/v2/charts/rollingoption` by adding `/v2/charts/` to data API prefixes.
7
+ - Correct `HTTP_PATH` for `ExpiredOptionsData` resource to `/v2/charts`.
8
+ - Prevent false validation failures by allowing up to 31-day ranges (to_date non-inclusive).
9
+
10
+ ### Changed
11
+ - Align `ExpiredOptionsData` contract with broker docs:
12
+ - `interval` accepted as String (e.g., "1", "5", "15", "25", "60").
13
+ - `security_id` validated as Integer.
14
+ - Input normalization for `ExpiredOptionsData.fetch`:
15
+ - Coerce convertible types (`interval`, `security_id`, `expiry_code`).
16
+ - Uppercase enums and `strike`, normalize `required_data` to downcased unique list.
17
+ - Improved examples and YARD docs to reflect the above.
18
+
19
+ ## [2.1.9] - 2025-01-31
20
+
21
+ ### Added
22
+ - **Comprehensive YARD documentation**: Added complete YARD documentation across all model classes with detailed parameter specifications, return types, and examples:
23
+ - `DhanHQ::Models::Edis` - EDIS form, bulk form, TPIN, and inquiry methods documented
24
+ - `DhanHQ::Models::ExpiredOptionsData` - Expired options data fetching with strike analysis helpers
25
+ - `DhanHQ::Models::ForeverOrder` - Forever Order (GTT) creation, modification, and cancellation
26
+ - `DhanHQ::Models::Funds` - Account fund information retrieval
27
+ - `DhanHQ::Models::HistoricalData` - Daily and intraday historical candle data fetching
28
+ - `DhanHQ::Models::Holding` - Portfolio holdings retrieval
29
+ - `DhanHQ::Models::Margin` - Margin calculation for orders
30
+ - `DhanHQ::Models::MarketFeed` - LTP, OHLC, and quote data fetching
31
+ - `DhanHQ::Models::OptionChain` - Option chain data and expiry list fetching
32
+ - `DhanHQ::Models::Order` - Order placement, modification, cancellation, and slicing
33
+ - `DhanHQ::Models::Position` - Position management and conversion
34
+ - `DhanHQ::Models::SuperOrder` - Multi-leg super order management
35
+ - `DhanHQ::Models::Trade` - Trade book, order trades, and historical trades
36
+ - `DhanHQ::Models::Profile` - User profile and account information
37
+ - `DhanHQ::Models::KillSwitch` - Kill switch activation and deactivation
38
+ - All documentation includes:
39
+ - Complete parameter documentation with types, descriptions, and valid values
40
+ - Comprehensive return type specifications with response structure details
41
+ - Multiple practical examples for each method
42
+ - Response field normalization (snake_case) documentation
43
+ - Error handling documentation with `@raise` tags
44
+ - Special notes and prerequisites where applicable
45
+
46
+ ### Changed
47
+ - **Documentation standards**: All model documentation now follows YARD best practices with:
48
+ - Properly indented `@option` tags for better readability
49
+ - Consistent use of YARD hash syntax for parameter and return types
50
+ - Detailed response structure documentation with field types and descriptions
51
+ - Clarified that `dhan_client_id` must be explicitly provided (not auto-injected) where applicable
52
+
53
+ ## [2.1.8] - 2025-10-30
54
+
55
+ ### Fixed
56
+ - Correctly map `underlying_seg` for option chain APIs:
57
+ - Index instruments use `IDX_I`.
58
+ - Stocks map to `NSE_FNO` or `BSE_FNO` based on the instrument's exchange.
59
+ - Implemented via `underlying_segment_for_options` in `DhanHQ::Models::InstrumentHelpers` and applied to `expiry_list` and `option_chain`.
60
+
3
61
  ## [2.1.7] - 2025-01-28
4
62
 
5
63
  ### Added
File without changes
File without changes
@@ -101,7 +101,7 @@ summary_timer = Thread.new do
101
101
  end
102
102
  end
103
103
 
104
- puts "\n" + ("=" * 50)
104
+ puts "\n#{"=" * 50}"
105
105
  end
106
106
  end
107
107
 
@@ -74,14 +74,14 @@ depth_client = DhanHQ::WS::MarketDepth.connect(symbols: symbols) do |depth_data|
74
74
  puts " Ask Levels: #{depth_data[:asks].size}"
75
75
 
76
76
  # Show top 3 bid/ask levels if available
77
- if depth_data[:bids] && depth_data[:bids].size > 0
77
+ if depth_data[:bids]&.size&.positive?
78
78
  puts " Top Bids:"
79
79
  depth_data[:bids].first(3).each_with_index do |bid, i|
80
80
  puts " #{i + 1}. Price: #{bid[:price]}, Qty: #{bid[:quantity]}"
81
81
  end
82
82
  end
83
83
 
84
- if depth_data[:asks] && depth_data[:asks].size > 0
84
+ if depth_data[:asks]&.size&.positive?
85
85
  puts " Top Asks:"
86
86
  depth_data[:asks].first(3).each_with_index do |ask, i|
87
87
  puts " #{i + 1}. Price: #{ask[:price]}, Qty: #{ask[:quantity]}"
File without changes
@@ -109,7 +109,7 @@ def calculate_margin_requirements(instrument, name, quantity, price)
109
109
  puts " Sell CO Margin Required: ₹#{sell_margin}"
110
110
 
111
111
  # Calculate MTF leverage
112
- if instrument.mtf_leverage > 0
112
+ if instrument.mtf_leverage.positive?
113
113
  mtf_value = total_value * instrument.mtf_leverage
114
114
  puts " MTF Leverage: #{instrument.mtf_leverage}x"
115
115
  puts " MTF Value: ₹#{mtf_value}"
data/lib/DhanHQ/client.rb CHANGED
@@ -38,7 +38,8 @@ module DhanHQ
38
38
  # @return [DhanHQ::Client] A new client instance.
39
39
  def initialize(api_type:)
40
40
  DhanHQ.configure_with_env if ENV.fetch("CLIENT_ID", nil)
41
- @rate_limiter = RateLimiter.new(api_type)
41
+ # Use shared rate limiter instance per API type to ensure proper coordination
42
+ @rate_limiter = RateLimiter.for(api_type)
42
43
 
43
44
  raise "RateLimiter initialization failed" unless @rate_limiter
44
45
 
@@ -10,8 +10,8 @@ module DhanHQ
10
10
  class ExpiredOptionsDataContract < BaseContract
11
11
  params do
12
12
  required(:exchange_segment).filled(:string)
13
- required(:interval).filled(:integer)
14
- required(:security_id).filled(:string)
13
+ required(:interval).filled(:string)
14
+ required(:security_id).filled(:integer)
15
15
  required(:instrument).filled(:string)
16
16
  required(:expiry_flag).filled(:string)
17
17
  required(:expiry_code).filled(:integer)
@@ -28,7 +28,7 @@ module DhanHQ
28
28
  end
29
29
 
30
30
  rule(:interval) do
31
- valid_intervals = [1, 5, 15, 25, 60]
31
+ valid_intervals = %w[1 5 15 25 60]
32
32
  key.failure("must be one of: #{valid_intervals.join(", ")}") unless valid_intervals.include?(value)
33
33
  end
34
34
 
@@ -69,10 +69,10 @@ module DhanHQ
69
69
  from_date = Date.parse(values[:from_date])
70
70
  to_date = Date.parse(values[:to_date])
71
71
 
72
- key.failure("from_date must be before to_date") if from_date >= to_date
72
+ key.failure("from_date must be on or before to_date") if from_date > to_date
73
73
 
74
- # Check if date range is not too large (max 30 days)
75
- key.failure("date range cannot exceed 30 days") if (to_date - from_date).to_i > 30
74
+ # Check if date range is not too large (max 31 days; to_date is non-inclusive)
75
+ key.failure("date range cannot exceed 31 days") if (to_date - from_date).to_i > 31
76
76
 
77
77
  # Check if from_date is not too far in the past (max 5 years)
78
78
  five_years_ago = Date.today - (5 * 365)
@@ -222,9 +222,17 @@ module DhanHQ
222
222
 
223
223
  # Format request parameters before sending to API
224
224
  #
225
+ # Auto-injects dhan_client_id from configuration if not present in attributes
226
+ # and the model requires it (has dhan_client_id as an attribute).
227
+ #
225
228
  # @return [Hash] The camelCased attributes
226
229
  def to_request_params
227
- optionchain_api? ? titleize_keys(@attributes) : camelize_keys(@attributes)
230
+ attrs = @attributes.dup
231
+ # Auto-inject dhan_client_id from configuration if not provided and model supports it
232
+ if self.class.defined_attributes&.include?("dhan_client_id") && !attrs[:dhan_client_id] && DhanHQ.configuration.client_id
233
+ attrs[:dhan_client_id] = DhanHQ.configuration.client_id
234
+ end
235
+ optionchain_api? ? titleize_keys(attrs) : camelize_keys(attrs)
228
236
  end
229
237
 
230
238
  # Identifier inferred from the loaded attributes.
@@ -2,54 +2,190 @@
2
2
 
3
3
  module DhanHQ
4
4
  module Models
5
- # Model wrapper for electronic DIS flows.
5
+ ##
6
+ # Model wrapper for electronic DIS (Delivery Instruction Slip) flows.
7
+ #
8
+ # To sell holding stocks, one needs to complete the CDSL eDIS flow:
9
+ # 1. Generate T-PIN using {tpin}
10
+ # 2. Retrieve escaped HTML form using {form} and enter T-PIN to mark stock for EDIS approval
11
+ # 3. Check status using {inquire} to verify if stock is approved and marked for sell action
12
+ #
13
+ # You can get ISIN (International Securities Identification Number) of portfolio stocks
14
+ # from the holdings API response.
15
+ #
16
+ # @example Generate T-PIN
17
+ # response = DhanHQ::Models::Edis.tpin
18
+ # # Returns 202 Accepted status
19
+ #
20
+ # @example Generate eDIS form for a single stock
21
+ # response = DhanHQ::Models::Edis.form(
22
+ # isin: "INE733E01010",
23
+ # qty: 1,
24
+ # exchange: "NSE",
25
+ # segment: "EQ",
26
+ # bulk: false
27
+ # )
28
+ # # => {
29
+ # # dhan_client_id: "1000000401",
30
+ # # edis_form_html: "<!DOCTYPE html>..."
31
+ # # }
32
+ #
33
+ # @example Check EDIS status for an ISIN
34
+ # status = DhanHQ::Models::Edis.inquire("INE00IN01015")
35
+ # # => {
36
+ # # client_id: "1000000401",
37
+ # # isin: "INE00IN01015",
38
+ # # total_qty: "10",
39
+ # # aprvd_qty: "4",
40
+ # # status: "SUCCESS",
41
+ # # remarks: "eDIS transaction done successfully"
42
+ # # }
43
+ #
6
44
  class Edis < BaseModel
7
45
  # Base path backing the model operations.
8
46
  HTTP_PATH = "/v2/edis"
9
47
 
10
48
  class << self
49
+ ##
11
50
  # Shared resource client used by the model helpers.
12
51
  #
13
- # @return [DhanHQ::Resources::Edis]
52
+ # @return [DhanHQ::Resources::Edis] The EDIS resource client instance
14
53
  def resource
15
54
  @resource ||= DhanHQ::Resources::Edis.new
16
55
  end
17
56
 
18
- # Submits an EDIS form request.
57
+ ##
58
+ # Retrieves escaped HTML form of CDSL and enters T-PIN to mark the stock for EDIS approval.
19
59
  #
20
- # @param params [Hash]
21
- # @return [Hash]
60
+ # User has to render this form at their end to unescape. The form contains hidden fields
61
+ # that will automatically submit to CDSL's eDIS verification endpoint.
62
+ #
63
+ # @param params [Hash{Symbol => String, Integer, Boolean}] The EDIS form request parameters
64
+ # @option params [String] :isin (required) International Securities Identification Number
65
+ # (12-digit alphanumeric code). You can get ISIN from the holdings API response.
66
+ # @option params [Integer] :qty (required) Number of shares to mark for EDIS transaction
67
+ # @option params [String] :exchange (required) Exchange identifier. Must be either "NSE" or "BSE"
68
+ # @option params [String] :segment (required) Segment identifier. Must be "EQ"
69
+ # @option params [Boolean] :bulk (optional, default: false) Set to true to mark EDIS for
70
+ # all stocks in portfolio. When true, other parameters may be ignored.
71
+ #
72
+ # @return [Hash{Symbol => String}] The EDIS form response containing escaped HTML form.
73
+ # Response keys are normalized to snake_case:
74
+ # - **:dhan_client_id** [String] User-specific identification generated by Dhan
75
+ # - **:edis_form_html** [String] Escaped HTML form that needs to be rendered and unescaped
76
+ #
77
+ # @example Generate form for a single stock
78
+ # response = DhanHQ::Models::Edis.form(
79
+ # isin: "INE733E01010",
80
+ # qty: 1,
81
+ # exchange: "NSE",
82
+ # segment: "EQ",
83
+ # bulk: false
84
+ # )
85
+ # html_form = response[:edis_form_html]
86
+ # # Render and unescape the HTML form to complete EDIS approval
87
+ #
88
+ # @example Generate form for bulk EDIS
89
+ # response = DhanHQ::Models::Edis.form(
90
+ # isin: "",
91
+ # qty: 0,
92
+ # exchange: "NSE",
93
+ # segment: "EQ",
94
+ # bulk: true
95
+ # )
22
96
  def form(params)
23
97
  resource.form(params)
24
98
  end
25
99
 
26
- # Submits a bulk EDIS form request.
100
+ ##
101
+ # Submits a bulk EDIS form request for multiple stocks.
27
102
  #
28
- # @param params [Hash]
29
- # @return [Hash]
103
+ # This is an alternative to using {form} with `bulk: true`. The exact parameter
104
+ # structure may differ from the single form request.
105
+ #
106
+ # @param params [Hash{Symbol => String, Integer, Boolean}] The bulk EDIS form request parameters
107
+ # @option params [String] :isin (optional) International Securities Identification Number.
108
+ # May be ignored for bulk requests
109
+ # @option params [Integer] :qty (optional) Number of shares. May be ignored for bulk requests
110
+ # @option params [String] :exchange (required) Exchange identifier ("NSE" or "BSE")
111
+ # @option params [String] :segment (required) Segment identifier ("EQ")
112
+ # @option params [Boolean] :bulk (optional, default: true) Set to true for bulk operations
113
+ #
114
+ # @return [Hash{Symbol => String}] The bulk EDIS form response containing escaped HTML form.
115
+ # Response keys are normalized to snake_case:
116
+ # - **:dhan_client_id** [String] User-specific identification generated by Dhan
117
+ # - **:edis_form_html** [String] Escaped HTML form that needs to be rendered and unescaped
118
+ #
119
+ # @example Bulk EDIS form request
120
+ # response = DhanHQ::Models::Edis.bulk_form(
121
+ # exchange: "NSE",
122
+ # segment: "EQ",
123
+ # bulk: true
124
+ # )
30
125
  def bulk_form(params)
31
126
  resource.bulk_form(params)
32
127
  end
33
128
 
34
- # Requests a TPIN for the configured client.
129
+ ##
130
+ # Generates a T-PIN (Transaction PIN) for the configured client.
131
+ #
132
+ # T-PIN is sent to the user's registered mobile number. This T-PIN is required
133
+ # when submitting the EDIS form to mark stocks for sell action.
35
134
  #
36
- # @return [Hash]
135
+ # @return [Hash{Symbol => String}] The T-PIN generation response. The T-PIN itself is sent
136
+ # to the registered mobile number and not returned in the response. Response typically
137
+ # contains a status indicating successful generation (e.g., "202 Accepted").
138
+ #
139
+ # @example Generate T-PIN
140
+ # response = DhanHQ::Models::Edis.tpin
141
+ # # T-PIN will be sent to registered mobile number
142
+ # # Check your mobile for the T-PIN before submitting the EDIS form
143
+ #
144
+ # @note This is a GET request with no body parameters required
37
145
  def tpin
38
146
  resource.tpin
39
147
  end
40
148
 
41
- # Inquires EDIS status for a specific ISIN.
149
+ ##
150
+ # Inquires the EDIS status for a specific ISIN to check if stock is approved and marked for sell action.
151
+ #
152
+ # You can check whether a stock has been approved through the EDIS process and is ready
153
+ # for sell action. Alternatively, you can pass "ALL" instead of an ISIN to get EDIS status
154
+ # of all holdings in your portfolio.
155
+ #
156
+ # @param isin [String] International Securities Identification Number (12-digit alphanumeric code).
157
+ # You can get ISIN from the holdings API response. Alternatively, pass "ALL" to get status
158
+ # for all holdings in your portfolio.
159
+ #
160
+ # @return [Hash{Symbol => String}] The EDIS inquiry response containing status information.
161
+ # Response keys are normalized to snake_case:
162
+ # - **:client_id** [String] User-specific identification
163
+ # - **:isin** [String] International Securities Identification Number that was queried
164
+ # - **:total_qty** [String] Total number of shares for the given stock
165
+ # - **:aprvd_qty** [String] Number of approved stocks that are marked for EDIS
166
+ # - **:status** [String] Status of the EDIS order (e.g., "SUCCESS", "PENDING", "FAILED")
167
+ # - **:remarks** [String] Remarks about the order status (e.g., "eDIS transaction done successfully")
168
+ #
169
+ # @example Check status for a specific ISIN
170
+ # status = DhanHQ::Models::Edis.inquire("INE00IN01015")
171
+ # puts status[:status] # => "SUCCESS"
172
+ # puts status[:aprvd_qty] # => "4"
173
+ # puts status[:total_qty] # => "10"
174
+ #
175
+ # @example Check status for all holdings
176
+ # all_status = DhanHQ::Models::Edis.inquire("ALL")
177
+ # # Returns status for all holdings in portfolio
42
178
  #
43
- # @param isin [String]
44
- # @return [Hash]
179
+ # @note This is a GET request with no body parameters. The ISIN is passed in the URL path.
45
180
  def inquire(isin)
46
181
  resource.inquire(isin)
47
182
  end
48
183
  end
49
184
 
185
+ ##
50
186
  # EDIS payloads are validated upstream so no contract is applied.
51
187
  #
52
- # @return [nil]
188
+ # @return [nil] No validation contract is needed for EDIS operations
53
189
  def validation_contract
54
190
  nil
55
191
  end