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
@@ -3,11 +3,35 @@
3
3
  module DhanHQ
4
4
  module Models
5
5
  ##
6
- # Represents a single trade.
7
- # Supports three main API endpoints:
8
- # 1. GET /v2/trades - Current day trades
9
- # 2. GET /v2/trades/{order-id} - Trades for specific order
10
- # 3. GET /v2/trades/{from-date}/{to-date}/{page} - Historical trades
6
+ # Model for retrieving trade execution details.
7
+ #
8
+ # The Trade Book API lets you retrieve an array of all trades executed in a day.
9
+ # You can also fetch trade details for a specific order ID, which is useful during
10
+ # partial trades or Bracket/Cover Orders. Additionally, you can retrieve detailed
11
+ # trade history for all orders within a particular time frame.
12
+ #
13
+ # @example Fetch all trades for today
14
+ # trades = DhanHQ::Models::Trade.today
15
+ # trades.each do |trade|
16
+ # puts "#{trade.trading_symbol}: #{trade.traded_quantity} @ ₹#{trade.traded_price}"
17
+ # puts "Total Value: ₹#{trade.total_value}, Charges: ₹#{trade.total_charges}"
18
+ # end
19
+ #
20
+ # @example Fetch trades for a specific order
21
+ # trade = DhanHQ::Models::Trade.find_by_order_id("112111182045")
22
+ # if trade
23
+ # puts "Traded: #{trade.traded_quantity} @ ₹#{trade.traded_price}"
24
+ # end
25
+ #
26
+ # @example Fetch historical trades
27
+ # trades = DhanHQ::Models::Trade.history(
28
+ # from_date: "2022-12-01",
29
+ # to_date: "2022-12-31",
30
+ # page: 0
31
+ # )
32
+ # total_value = trades.sum(&:total_value)
33
+ # puts "Total traded value: ₹#{total_value}"
34
+ #
11
35
  class Trade < BaseModel
12
36
  HTTP_PATH = "/v2/trades"
13
37
 
@@ -22,22 +46,66 @@ module DhanHQ
22
46
 
23
47
  class << self
24
48
  ##
25
- # Resource for current day tradebook APIs
49
+ # Provides a shared instance of the Trades resource for current day tradebook APIs.
50
+ #
51
+ # @return [DhanHQ::Resources::Trades] The Trades resource client instance
26
52
  def tradebook_resource
27
53
  @tradebook_resource ||= DhanHQ::Resources::Trades.new
28
54
  end
29
55
 
30
56
  ##
31
- # Resource for historical trade data
57
+ # Provides a shared instance of the Statements resource for historical trade data.
58
+ #
59
+ # @return [DhanHQ::Resources::Statements] The Statements resource client instance
32
60
  def statements_resource
33
61
  @statements_resource ||= DhanHQ::Resources::Statements.new
34
62
  end
35
63
 
36
64
  ##
37
- # Fetch current day trades
38
- # GET /v2/trades
65
+ # Retrieves all trades executed during the current trading day.
66
+ #
67
+ # Fetches an array of all trades executed in the day. This is useful for
68
+ # tracking daily execution activity and analyzing trade performance.
69
+ #
70
+ # @return [Array<Trade>] Array of Trade objects. Returns empty array if no trades exist.
71
+ # Each Trade object contains (keys normalized to snake_case):
72
+ # - **:dhan_client_id** [String] User-specific identification generated by Dhan
73
+ # - **:order_id** [String] Order-specific identification generated by Dhan
74
+ # - **:exchange_order_id** [String] Order-specific identification generated by exchange
75
+ # - **:exchange_trade_id** [String] Trade-specific identification generated by exchange
76
+ # - **:transaction_type** [String] The trading side of transaction. "BUY" or "SELL"
77
+ # - **:exchange_segment** [String] Exchange segment of instrument
78
+ # - **:product_type** [String] Product type. Valid values: "CNC", "INTRADAY",
79
+ # "MARGIN", "MTF", "CO", "BO"
80
+ # - **:order_type** [String] Order type. Valid values: "LIMIT", "MARKET",
81
+ # "STOP_LOSS", "STOP_LOSS_MARKET"
82
+ # - **:trading_symbol** [String] Trading symbol of the instrument
83
+ # - **:security_id** [String] Exchange standard ID for each scrip
84
+ # - **:traded_quantity** [Integer] Number of shares executed
85
+ # - **:traded_price** [Float] Price at which trade is executed
86
+ # - **:create_time** [String] Time at which the order is created
87
+ # - **:update_time** [String] Time at which the last activity happened
88
+ # - **:exchange_time** [String] Time at which order reached at exchange
89
+ # - **:drv_expiry_date** [String, nil] For F&O, expiry date of contract
90
+ # - **:drv_option_type** [String, nil] Type of Option. "CALL" or "PUT"
91
+ # - **:drv_strike_price** [Float] For Options, Strike Price
92
+ #
93
+ # @example Fetch and analyze today's trades
94
+ # trades = DhanHQ::Models::Trade.today
95
+ # buy_trades = trades.select(&:buy?)
96
+ # sell_trades = trades.select(&:sell?)
97
+ # puts "Buy trades: #{buy_trades.count}, Sell trades: #{sell_trades.count}"
98
+ # total_value = trades.sum(&:total_value)
99
+ # puts "Total traded value: ₹#{total_value}"
100
+ #
101
+ # @example Calculate P&L for today
102
+ # trades = DhanHQ::Models::Trade.today
103
+ # buy_value = trades.select(&:buy?).sum(&:total_value)
104
+ # sell_value = trades.select(&:sell?).sum(&:total_value)
105
+ # total_charges = trades.sum(&:total_charges)
106
+ # pnl = sell_value - buy_value - total_charges
107
+ # puts "P&L: ₹#{pnl}"
39
108
  #
40
- # @return [Array<Trade>] Array of trades executed today
41
109
  def today
42
110
  response = tradebook_resource.all
43
111
  return [] unless response.is_a?(Array)
@@ -46,11 +114,28 @@ module DhanHQ
46
114
  end
47
115
 
48
116
  ##
49
- # Fetch trades for a specific order ID (current day)
50
- # GET /v2/trades/{order-id}
117
+ # Retrieves trade details for a specific order ID (current day).
118
+ #
119
+ # Fetches all trades generated for a particular order ID. This is especially useful
120
+ # during partial trades or Bracket/Cover Orders where traders may get confused
121
+ # reading trades from the tradebook. The response includes all trades generated
122
+ # for the specified order ID.
123
+ #
124
+ # @param order_id [String] Order-specific identification generated by Dhan
51
125
  #
52
- # @param order_id [String] The order ID to fetch trades for
53
- # @return [Trade, nil] Trade object or nil if not found
126
+ # @return [Trade, nil] Trade object with trade details if found, nil otherwise.
127
+ # Response structure is the same as {today}.
128
+ #
129
+ # @example Fetch trade for an order
130
+ # trade = DhanHQ::Models::Trade.find_by_order_id("112111182045")
131
+ # if trade
132
+ # puts "Order: #{trade.order_id}"
133
+ # puts "Traded: #{trade.traded_quantity} @ ₹#{trade.traded_price}"
134
+ # puts "Symbol: #{trade.trading_symbol}"
135
+ # puts "Time: #{trade.exchange_time}"
136
+ # end
137
+ #
138
+ # @raise [DhanHQ::ValidationError] If order_id validation fails
54
139
  def find_by_order_id(order_id)
55
140
  # Validate input
56
141
  contract = DhanHQ::Contracts::TradeByOrderIdContract.new
@@ -68,13 +153,61 @@ module DhanHQ
68
153
  end
69
154
 
70
155
  ##
71
- # Fetch historical trades within the given date range and page
72
- # GET /v2/trades/{from-date}/{to-date}/{page}
156
+ # Retrieves detailed trade history for all orders within a specified time frame.
73
157
  #
74
- # @param from_date [String] Start date in YYYY-MM-DD format
75
- # @param to_date [String] End date in YYYY-MM-DD format
76
- # @param page [Integer] Page number (default: 0)
77
- # @return [Array<Trade>] Array of historical trades
158
+ # Fetches paginated trade history data with comprehensive charge breakdowns.
159
+ # The response includes detailed information about SEBI tax, STT, brokerage charges,
160
+ # service tax, exchange transaction charges, and stamp duty for each trade.
161
+ #
162
+ # @param from_date [String] (required) Start date in "YYYY-MM-DD" format
163
+ # @param to_date [String] (required) End date in "YYYY-MM-DD" format
164
+ # @param page [Integer] (default: 0) Page number for paginated results. Pass 0 for first page
165
+ #
166
+ # @return [Array<Trade>] Array of historical Trade objects. Returns empty array if no trades found.
167
+ # Each Trade object contains all fields from {today} plus additional charge details:
168
+ # - **:custom_symbol** [String] Trading Symbol as per Dhan
169
+ # - **:isin** [String] Universal standard ID for each scrip (International Securities Identification Number)
170
+ # - **:instrument** [String] Type of Instrument. "EQUITY" or "DERIVATIVES"
171
+ # - **:sebi_tax** [String] SEBI Turnover Charges
172
+ # - **:stt** [String] Securities Transactions Tax
173
+ # - **:brokerage_charges** [String] Brokerage charges by Dhan
174
+ # - **:service_tax** [String] Applicable Service Tax
175
+ # - **:exchange_transaction_charges** [String] Exchange Transaction Charge
176
+ # - **:stamp_duty** [String] Stamp Duty Charges
177
+ #
178
+ # @example Fetch historical trades for a month
179
+ # trades = DhanHQ::Models::Trade.history(
180
+ # from_date: "2022-12-01",
181
+ # to_date: "2022-12-31",
182
+ # page: 0
183
+ # )
184
+ # puts "Total trades: #{trades.count}"
185
+ #
186
+ # @example Calculate total charges for historical period
187
+ # trades = DhanHQ::Models::Trade.history(
188
+ # from_date: "2022-12-01",
189
+ # to_date: "2022-12-31"
190
+ # )
191
+ # total_charges = trades.sum(&:total_charges)
192
+ # total_brokerage = trades.sum { |t| t.brokerage_charges.to_f }
193
+ # puts "Total Charges: ₹#{total_charges}"
194
+ # puts "Total Brokerage: ₹#{total_brokerage}"
195
+ #
196
+ # @example Paginate through historical trades
197
+ # page = 0
198
+ # loop do
199
+ # trades = DhanHQ::Models::Trade.history(
200
+ # from_date: "2022-12-01",
201
+ # to_date: "2022-12-31",
202
+ # page: page
203
+ # )
204
+ # break if trades.empty?
205
+ #
206
+ # puts "Page #{page}: #{trades.count} trades"
207
+ # page += 1
208
+ # end
209
+ #
210
+ # @raise [DhanHQ::ValidationError] If date format or page validation fails
78
211
  def history(from_date:, to_date:, page: 0)
79
212
  validate_history_params(from_date, to_date, page)
80
213
 
@@ -91,6 +224,16 @@ module DhanHQ
91
224
 
92
225
  private
93
226
 
227
+ ##
228
+ # Validates parameters for historical trade queries.
229
+ #
230
+ # @param from_date [String] Start date in YYYY-MM-DD format
231
+ # @param to_date [String] End date in YYYY-MM-DD format
232
+ # @param page [Integer] Page number
233
+ #
234
+ # @raise [DhanHQ::ValidationError] If validation fails
235
+ #
236
+ # @api private
94
237
  def validate_history_params(from_date, to_date, page)
95
238
  contract = DhanHQ::Contracts::TradeHistoryContract.new
96
239
  validation_result = contract.call(from_date: from_date, to_date: to_date, page: page)
@@ -100,48 +243,131 @@ module DhanHQ
100
243
  raise DhanHQ::ValidationError, "Invalid parameters: #{validation_result.errors.to_h}"
101
244
  end
102
245
 
103
- # Alias for backward compatibility
246
+ # Alias for backward compatibility with historical trades
247
+ # @api private
104
248
  alias all history
105
249
  end
106
250
 
107
251
  ##
108
- # Trade objects are read-only, so no validation contract needed
252
+ # Trade objects are read-only, so no validation contract needed.
253
+ #
254
+ # @return [nil] Always returns nil as trades are read-only
255
+ #
256
+ # @api private
109
257
  def validation_contract
110
258
  nil
111
259
  end
112
260
 
113
261
  ##
114
- # Helper methods for trade data
262
+ # Checks if the trade is a BUY transaction.
263
+ #
264
+ # @return [Boolean] true if transaction_type is "BUY", false otherwise
265
+ #
266
+ # @example
267
+ # trade = DhanHQ::Models::Trade.today.first
268
+ # if trade.buy?
269
+ # puts "This is a buy trade"
270
+ # end
271
+ #
115
272
  def buy?
116
273
  transaction_type == "BUY"
117
274
  end
118
275
 
276
+ ##
277
+ # Checks if the trade is a SELL transaction.
278
+ #
279
+ # @return [Boolean] true if transaction_type is "SELL", false otherwise
280
+ #
281
+ # @example
282
+ # trade = DhanHQ::Models::Trade.today.first
283
+ # if trade.sell?
284
+ # puts "This is a sell trade"
285
+ # end
286
+ #
119
287
  def sell?
120
288
  transaction_type == "SELL"
121
289
  end
122
290
 
291
+ ##
292
+ # Checks if the trade instrument is EQUITY.
293
+ #
294
+ # @return [Boolean] true if instrument is "EQUITY", false otherwise
295
+ #
296
+ # @example
297
+ # trades = DhanHQ::Models::Trade.today
298
+ # equity_trades = trades.select(&:equity?)
299
+ # puts "Equity trades: #{equity_trades.count}"
300
+ #
123
301
  def equity?
124
302
  instrument == "EQUITY"
125
303
  end
126
304
 
305
+ ##
306
+ # Checks if the trade instrument is DERIVATIVES.
307
+ #
308
+ # @return [Boolean] true if instrument is "DERIVATIVES", false otherwise
309
+ #
310
+ # @example
311
+ # trades = DhanHQ::Models::Trade.today
312
+ # derivative_trades = trades.select(&:derivative?)
313
+ # puts "Derivative trades: #{derivative_trades.count}"
314
+ #
127
315
  def derivative?
128
316
  instrument == "DERIVATIVES"
129
317
  end
130
318
 
319
+ ##
320
+ # Checks if the trade is an option (CALL or PUT).
321
+ #
322
+ # @return [Boolean] true if drv_option_type is "CALL" or "PUT", false otherwise
323
+ #
324
+ # @example
325
+ # trades = DhanHQ::Models::Trade.today
326
+ # option_trades = trades.select(&:option?)
327
+ # puts "Option trades: #{option_trades.count}"
328
+ #
131
329
  def option?
132
330
  %w[CALL PUT].include?(drv_option_type)
133
331
  end
134
332
 
333
+ ##
334
+ # Checks if the trade is a CALL option.
335
+ #
336
+ # @return [Boolean] true if drv_option_type is "CALL", false otherwise
337
+ #
338
+ # @example
339
+ # trades = DhanHQ::Models::Trade.today
340
+ # call_trades = trades.select(&:call_option?)
341
+ # puts "Call option trades: #{call_trades.count}"
342
+ #
135
343
  def call_option?
136
344
  drv_option_type == "CALL"
137
345
  end
138
346
 
347
+ ##
348
+ # Checks if the trade is a PUT option.
349
+ #
350
+ # @return [Boolean] true if drv_option_type is "PUT", false otherwise
351
+ #
352
+ # @example
353
+ # trades = DhanHQ::Models::Trade.today
354
+ # put_trades = trades.select(&:put_option?)
355
+ # puts "Put option trades: #{put_trades.count}"
356
+ #
139
357
  def put_option?
140
358
  drv_option_type == "PUT"
141
359
  end
142
360
 
143
361
  ##
144
- # Calculate total trade value
362
+ # Calculates the total trade value (quantity × price).
363
+ #
364
+ # @return [Float] Total trade value. Returns 0 if traded_quantity or traded_price is missing
365
+ #
366
+ # @example
367
+ # trade = DhanHQ::Models::Trade.today.first
368
+ # puts "Trade Value: ₹#{trade.total_value}"
369
+ # # => Trade Value: ₹133832.0
370
+ #
145
371
  def total_value
146
372
  return 0 unless traded_quantity && traded_price
147
373
 
@@ -149,7 +375,22 @@ module DhanHQ
149
375
  end
150
376
 
151
377
  ##
152
- # Calculate total charges
378
+ # Calculates the total charges for the trade.
379
+ #
380
+ # Sums all applicable charges including SEBI tax, STT, brokerage charges,
381
+ # service tax, exchange transaction charges, and stamp duty.
382
+ #
383
+ # @return [Float] Total charges amount. Returns 0 if no charges are present
384
+ #
385
+ # @example
386
+ # trade = DhanHQ::Models::Trade.history(
387
+ # from_date: "2022-12-01",
388
+ # to_date: "2022-12-31"
389
+ # ).first
390
+ # puts "Total Charges: ₹#{trade.total_charges}"
391
+ # puts "Brokerage: ₹#{trade.brokerage_charges}"
392
+ # puts "STT: ₹#{trade.stt}"
393
+ #
153
394
  def total_charges
154
395
  charges = [sebi_tax, stt, brokerage_charges, service_tax,
155
396
  exchange_transaction_charges, stamp_duty].compact
@@ -157,7 +398,19 @@ module DhanHQ
157
398
  end
158
399
 
159
400
  ##
160
- # Net trade value after charges
401
+ # Calculates the net trade value after deducting all charges.
402
+ #
403
+ # @return [Float] Net trade value (total_value - total_charges)
404
+ #
405
+ # @example
406
+ # trade = DhanHQ::Models::Trade.history(
407
+ # from_date: "2022-12-01",
408
+ # to_date: "2022-12-31"
409
+ # ).first
410
+ # puts "Gross Value: ₹#{trade.total_value}"
411
+ # puts "Charges: ₹#{trade.total_charges}"
412
+ # puts "Net Value: ₹#{trade.net_value}"
413
+ #
161
414
  def net_value
162
415
  total_value - total_charges
163
416
  end
@@ -15,13 +15,31 @@ module DhanHQ
15
15
  per_day: Float::INFINITY }
16
16
  }.freeze
17
17
 
18
+ # Thread-safe shared rate limiters per API type
19
+ @shared_limiters = Concurrent::Map.new
20
+ @mutexes = Concurrent::Map.new
21
+
22
+ class << self
23
+ attr_reader :shared_limiters, :mutexes
24
+
25
+ # Get or create a shared rate limiter instance for the given API type
26
+ def for(api_type)
27
+ @shared_limiters[api_type] ||= new(api_type)
28
+ end
29
+ end
30
+
18
31
  # Creates a rate limiter for a given API type.
32
+ # Note: For proper rate limiting coordination, use RateLimiter.for(api_type) instead
33
+ # of RateLimiter.new(api_type) to get a shared instance.
19
34
  #
20
35
  # @param api_type [Symbol] One of the keys from {RATE_LIMITS}.
21
36
  def initialize(api_type)
22
37
  @api_type = api_type
23
38
  @buckets = Concurrent::Hash.new
24
39
  @buckets[:last_request_time] = Time.at(0) if api_type == :option_chain
40
+ # Track request timestamps for per-second limiting
41
+ @request_times = []
42
+ @window_start = Time.now
25
43
  initialize_buckets
26
44
  start_cleanup_threads
27
45
  end
@@ -30,34 +48,70 @@ module DhanHQ
30
48
  #
31
49
  # @return [void]
32
50
  def throttle!
33
- if @api_type == :option_chain
34
- last_request_time = @buckets[:last_request_time]
51
+ mutex.synchronize do
52
+ if @api_type == :option_chain
53
+ last_request_time = @buckets[:last_request_time]
35
54
 
36
- sleep_time = 3 - (Time.now - last_request_time)
37
- if sleep_time.positive?
38
- if ENV["DHAN_DEBUG"] == "true"
39
- puts "Sleeping for #{sleep_time.round(2)} seconds due to option_chain rate limit"
55
+ sleep_time = 3 - (Time.now - last_request_time)
56
+ if sleep_time.positive?
57
+ if ENV["DHAN_DEBUG"] == "true"
58
+ puts "Sleeping for #{sleep_time.round(2)} seconds due to option_chain rate limit"
59
+ end
60
+ sleep(sleep_time)
40
61
  end
41
- sleep(sleep_time)
62
+
63
+ @buckets[:last_request_time] = Time.now
64
+ return
42
65
  end
43
66
 
44
- @buckets[:last_request_time] = Time.now
45
- return
46
- end
67
+ # For per-second limits, use timestamp-based sliding window
68
+ per_second_limit = RATE_LIMITS[@api_type][:per_second]
69
+ if per_second_limit && per_second_limit != Float::INFINITY
70
+ now = Time.now
71
+ # Remove requests older than 1 second
72
+ @request_times.reject! { |t| now - t >= 1.0 }
73
+
74
+ # Check if we've hit the per-second limit
75
+ if @request_times.size >= per_second_limit
76
+ # Calculate how long to wait until the oldest request is 1 second old
77
+ oldest_time = @request_times.min
78
+ wait_time = 1.0 - (now - oldest_time)
79
+
80
+ if wait_time.positive?
81
+ sleep(wait_time)
82
+ # Recalculate after sleep
83
+ now = Time.now
84
+ @request_times.reject! { |t| now - t >= 1.0 }
85
+ end
86
+ end
47
87
 
48
- loop do
49
- break if allow_request?
88
+ # Record this request time
89
+ @request_times << Time.now
90
+ end
50
91
 
51
- sleep(0.1)
92
+ # Check other limits (per_minute, per_hour, per_day)
93
+ loop do
94
+ break if allow_request?
95
+
96
+ sleep(0.1)
97
+ end
98
+ record_request
52
99
  end
53
- record_request
54
100
  end
55
101
 
56
102
  private
57
103
 
104
+ # Gets or creates a mutex for this API type for thread-safe throttling
105
+ def mutex
106
+ self.class.mutexes[@api_type] ||= Mutex.new
107
+ end
108
+
58
109
  # Prepares the counters used for each interval in {RATE_LIMITS}.
59
110
  def initialize_buckets
60
111
  RATE_LIMITS[@api_type].each_key do |interval|
112
+ # Skip per_second as we handle it with timestamps
113
+ next if interval == :per_second
114
+
61
115
  @buckets[interval] = Concurrent::AtomicFixnum.new(0)
62
116
  end
63
117
  end
@@ -67,6 +121,9 @@ module DhanHQ
67
121
  # @return [Boolean]
68
122
  def allow_request?
69
123
  RATE_LIMITS[@api_type].all? do |interval, limit|
124
+ # Skip per_second check as it's handled in throttle! with timestamps
125
+ next true if interval == :per_second
126
+
70
127
  @buckets[interval].value < limit
71
128
  end
72
129
  end
@@ -74,34 +131,32 @@ module DhanHQ
74
131
  # Increments the counters for each time window once a request is made.
75
132
  def record_request
76
133
  RATE_LIMITS[@api_type].each_key do |interval|
134
+ # Skip per_second as it's handled with timestamps
135
+ next if interval == :per_second
136
+
77
137
  @buckets[interval].increment
78
138
  end
79
139
  end
80
140
 
81
141
  # Spawns background threads to reset counters after each interval elapses.
82
142
  def start_cleanup_threads
83
- Thread.new do
84
- loop do
85
- sleep(1)
86
- @buckets[:per_second].value = 0
87
- end
88
- end
143
+ # Don't create per_second cleanup thread - we handle it with timestamps
89
144
  Thread.new do
90
145
  loop do
91
146
  sleep(60)
92
- @buckets[:per_minute].value = 0
147
+ @buckets[:per_minute]&.value = 0
93
148
  end
94
149
  end
95
150
  Thread.new do
96
151
  loop do
97
152
  sleep(3600)
98
- @buckets[:per_hour].value = 0
153
+ @buckets[:per_hour]&.value = 0
99
154
  end
100
155
  end
101
156
  Thread.new do
102
157
  loop do
103
158
  sleep(86_400)
104
- @buckets[:per_day].value = 0
159
+ @buckets[:per_day]&.value = 0
105
160
  end
106
161
  end
107
162
  end
@@ -6,7 +6,7 @@ module DhanHQ
6
6
  # Resource for expired options data API endpoints
7
7
  class ExpiredOptionsData < BaseAPI
8
8
  API_TYPE = :data_api
9
- HTTP_PATH = "/charts"
9
+ HTTP_PATH = "/v2/charts"
10
10
 
11
11
  ##
12
12
  # Fetch expired options data for rolling contracts
@@ -2,5 +2,5 @@
2
2
 
3
3
  module DhanHQ
4
4
  # Semantic version of the DhanHQ client gem.
5
- VERSION = "2.1.7"
5
+ VERSION = "2.1.10"
6
6
  end
@@ -213,7 +213,9 @@ module DhanHQ
213
213
 
214
214
  instrument = find_instrument(symbol_code, segment_hint)
215
215
  unless instrument
216
- DhanHQ.logger&.warn("[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code} (segment hint: #{segment_hint || 'AUTO'})")
216
+ DhanHQ.logger&.warn(
217
+ "[DhanHQ::WS::MarketDepth] Unable to locate instrument for #{symbol_code} (segment hint: #{segment_hint || "AUTO"})"
218
+ )
217
219
  return nil
218
220
  end
219
221
 
@@ -184,8 +184,9 @@ module DhanHQ
184
184
 
185
185
  ##
186
186
  # Emit status-specific events
187
+ #
187
188
  # @param order_update [OrderUpdate] Current order update
188
- # @param previous_state [OrderUpdate, nil] Previous order state
189
+ # @param _previous_state [OrderUpdate, nil] Previous order state (unused parameter)
189
190
  # rubocop:disable Metrics/MethodLength
190
191
  def emit_status_specific_events(order_update, _previous_state)
191
192
  case order_update.status
@@ -13,9 +13,6 @@ module DhanHQ
13
13
  # Initialize Orders WebSocket connection
14
14
  # @param url [String] WebSocket endpoint URL
15
15
  # @param options [Hash] Connection options
16
- def initialize(url:, **options)
17
- super
18
- end
19
16
 
20
17
  private
21
18
 
@@ -10,7 +10,8 @@ module DhanHQ
10
10
  module Orders
11
11
  ##
12
12
  # Connect to order updates WebSocket with a simple callback
13
- # @param block [Proc] Callback for order updates
13
+ #
14
+ # @yield [OrderUpdate] Yields order update objects when received
14
15
  # @return [Client] WebSocket client instance
15
16
  def self.connect(&)
16
17
  Client.new.start.on(:update, &)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: DhanHQ
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.7
4
+ version: 2.1.10
5
5
  platform: ruby
6
6
  authors:
7
7
  - Shubham Taywade