Class: DhanHQ::RateLimiter

Inherits:
Object
  • Object
show all
Defined in:
lib/DhanHQ/rate_limiter.rb

Overview

Coarse-grained in-memory throttler matching the platform rate limits.

Constant Summary collapse

RATE_LIMITS =

Per-interval thresholds keyed by API type.

{
  order_api: { per_second: 25, per_minute: 250, per_hour: 1000, per_day: 7000 },
  data_api: { per_second: 5, per_minute: Float::INFINITY, per_hour: Float::INFINITY, per_day: 100_000 },
  quote_api: { per_second: 1, per_minute: Float::INFINITY, per_hour: Float::INFINITY, per_day: Float::INFINITY },
  option_chain: { per_second: 1.0 / 3, per_minute: 20, per_hour: 600, per_day: 4800 },
  non_trading_api: { per_second: 20, per_minute: Float::INFINITY, per_hour: Float::INFINITY,
                     per_day: Float::INFINITY }
}.freeze

Class Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(api_type) ⇒ RateLimiter

Creates a rate limiter for a given API type. Note: For proper rate limiting coordination, use RateLimiter.for(api_type) instead of RateLimiter.new(api_type) to get a shared instance.

Parameters:

  • api_type (Symbol)

    One of the keys from RATE_LIMITS.



36
37
38
39
40
41
42
43
44
45
# File 'lib/DhanHQ/rate_limiter.rb', line 36

def initialize(api_type)
  @api_type = api_type
  @buckets = Concurrent::Hash.new
  @buckets[:last_request_time] = Time.at(0) if api_type == :option_chain
  # Track request timestamps for per-second limiting
  @request_times = []
  @window_start = Time.now
  initialize_buckets
  start_cleanup_threads
end

Class Attribute Details

.mutexesObject (readonly)

Returns the value of attribute mutexes.



23
24
25
# File 'lib/DhanHQ/rate_limiter.rb', line 23

def mutexes
  @mutexes
end

.shared_limitersObject (readonly)

Returns the value of attribute shared_limiters.



23
24
25
# File 'lib/DhanHQ/rate_limiter.rb', line 23

def shared_limiters
  @shared_limiters
end

Class Method Details

.for(api_type) ⇒ Object

Get or create a shared rate limiter instance for the given API type



26
27
28
# File 'lib/DhanHQ/rate_limiter.rb', line 26

def for(api_type)
  @shared_limiters[api_type] ||= new(api_type)
end

Instance Method Details

#throttle!void

This method returns an undefined value.

Blocks until the current request is allowed by the configured limits.



50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/DhanHQ/rate_limiter.rb', line 50

def throttle!
  mutex.synchronize do
    if @api_type == :option_chain
      last_request_time = @buckets[:last_request_time]

      sleep_time = 3 - (Time.now - last_request_time)
      if sleep_time.positive?
        if ENV["DHAN_DEBUG"] == "true"
          puts "Sleeping for #{sleep_time.round(2)} seconds due to option_chain rate limit"
        end
        sleep(sleep_time)
      end

      @buckets[:last_request_time] = Time.now
      return
    end

    # For per-second limits, use timestamp-based sliding window
    per_second_limit = RATE_LIMITS[@api_type][:per_second]
    if per_second_limit && per_second_limit != Float::INFINITY
      now = Time.now
      # Remove requests older than 1 second
      @request_times.reject! { |t| now - t >= 1.0 }

      # Check if we've hit the per-second limit
      if @request_times.size >= per_second_limit
        # Calculate how long to wait until the oldest request is 1 second old
        oldest_time = @request_times.min
        wait_time = 1.0 - (now - oldest_time)

        if wait_time.positive?
          sleep(wait_time)
          # Recalculate after sleep
          now = Time.now
          @request_times.reject! { |t| now - t >= 1.0 }
        end
      end

      # Record this request time
      @request_times << Time.now
    end

    # Check other limits (per_minute, per_hour, per_day)
    loop do
      break if allow_request?

      sleep(0.1)
    end
    record_request
  end
end