8#define ATL_TRANSPORT_LAYER_PRESENT 1
96 "all_rate_limit_groups must list every RateLimitGroup"
108 return static_cast<std::size_t
>(group);
143 return budget.burst_;
149 const int reserved = budget.burst_ - budget.trading_reserve_;
150 return reserved > 0 ? reserved : 1;
163template <
typename Clock = std::chrono::steady_clock>
199 throw std::invalid_argument(
"rate limit burst must be at least one request");
202 throw std::invalid_argument(
"rate limit rate must be at least one request per second");
206 interval_ =
Duration(std::chrono::nanoseconds(std::chrono::seconds(1)).count() /
per_second);
217 return Duration::zero();
219 const auto earliest = schedule_ - tolerance(allowance);
220 if (now >= earliest) {
221 return Duration::zero();
223 return std::chrono::duration_cast<Duration>(earliest - now);
233 return delay_until(now, allowance) == Duration::zero();
244 if (!
ready(now, allowance)) {
248 schedule_ = now + interval_;
252 schedule_ = (schedule_ > now ? schedule_ : now) + interval_;
266 if (duration < Duration::zero()) {
267 duration = Duration::zero();
269 const auto held = now + duration + tolerance(burst_);
270 if (!started_ || held > schedule_) {
286 cool_down(now, refill > Duration::zero() ? refill : interval_);
299 const auto earliest = schedule_ - tolerance(allowance);
300 if (now < earliest) {
303 const auto spare = std::chrono::duration_cast<Duration>(now - earliest);
304 const auto slots = spare / interval_ + 1;
305 return slots > allowance ? allowance :
static_cast<int>(slots);
312 int burst() const noexcept {
return burst_; }
328 Duration tolerance(
int allowance)
const noexcept {
329 const int slots = allowance < 1 ? 1 : (allowance > burst_ ? burst_ : allowance);
330 return interval_ * (slots - 1);
335 Duration interval_{std::chrono::seconds(1)};
337 bool started_{
false};
419 return bucket_.delay_until(RateLimitBucket::ClockType::now(),
usable_burst(budget_, group));
428 const auto now = RateLimitBucket::ClockType::now();
429 if (!bucket_.try_acquire(now,
usable_burst(budget_, group))) {
432 ++stats_.group(group).admitted_;
433 stats_.available_ = bucket_.available(now,
usable_burst(budget_, group));
443 auto& counters = stats_.group(group);
445 counters.waited_ += waited;
463 ++stats_.broker_rejections_;
464 bucket_.cool_down(RateLimitBucket::ClockType::now(), duration);
479 bucket_.observe_limit(RateLimitBucket::ClockType::now(), burst, per_second, retry);
480 ++stats_.broker_rejections_;
481 apply_budget(burst, per_second);
498 void apply_budget(
int burst,
int per_second) {
508 RateLimitBudget budget_;
510 RateLimitStats stats_{};
A rate limiter (the generic cell rate algorithm) that computes when a request may go; it neither wait...
void observe_limit(TimePoint now, int burst, int per_second, Duration refill)
Adopts a burst and rate the broker reported, then holds requests until it refills.
int available(TimePoint now, int allowance) const
Returns how many requests could go back to back now.
Duration interval() const noexcept
Returns the spacing of the sustained rate.
std::chrono::nanoseconds Duration
The duration type of all results.
void cool_down(TimePoint now, Duration duration)
Holds every request for at least a duration (for a broker refusal that is not this client's pacing fa...
typename Clock::time_point TimePoint
The clock's instant type.
Clock ClockType
The clock type.
int burst() const noexcept
Returns the burst.
BasicRateLimitBucket(const RateLimitBudget &budget)
Constructs a bucket from a budget's burst and rate.
BasicRateLimitBucket(int burst, int per_second)
Constructs a bucket.
Duration delay_until(TimePoint now, int allowance) const
Returns how long a request must wait.
void resize(int burst, int per_second)
Changes the burst and rate, keeping the current schedule.
bool try_acquire(TimePoint now, int allowance)
Admits one request if it may go now.
int per_second() const noexcept
Returns the sustained rate.
bool ready(TimePoint now, int allowance) const
Tests whether a request may go now.
Duration delay_until(RateLimitGroup group) const
Returns how long a request of a class must wait.
const RateLimitBudget & budget() const noexcept
Returns the budget in force.
bool acquire(RateLimitGroup group)
Admits one request of a class if it may go now, and counts it.
void shrink(int burst, int per_second, Duration retry)
Counts a broker refusal, adopts the burst and rate the broker reported, and holds.
RateLimitController(RateLimitBudget budget={})
Constructs a controller.
RateLimitBucket::Duration Duration
The duration type.
const RateLimitStats & stats() const noexcept
Returns the counters.
void record_shed(RateLimitGroup group)
Records that a request was refused unsent.
void record_delay(RateLimitGroup group, Duration waited)
Records that a request waited before admission.
void hold(Duration duration)
Counts a broker refusal and holds every request for a duration, keeping the budget.
A compile-time check that a list names every value of an enum, and a parser over the list.
HTTP and WebSocket transport, OAuth tokens, and request pacing.
RateLimitGroup
The traffic class of a request, for rate limiting and queue priority. The broker adapter that builds ...
@ MARKET_DATA
Quotes, bars, order book and trades.
@ ACCOUNT
Holdings, working orders, order history and buying power.
@ TRADING
Order placement, amendment and cancellation.
@ REFERENCE
Instrument reference data and trading calendars.
@ AUTH
Access-token issue and refresh.
constexpr int rate_limit_priority(RateLimitGroup group) noexcept
Returns a traffic class's queue priority.
constexpr int usable_burst(const RateLimitBudget &budget, RateLimitGroup group) noexcept
Returns how much of the burst a traffic class may use.
constexpr std::array< RateLimitGroup, 5 > all_rate_limit_groups
Every RateLimitGroup, in declaration order.
constexpr std::size_t rate_limit_group_index(RateLimitGroup group) noexcept
Returns a traffic class's index, for per-class arrays.
constexpr std::size_t rate_limit_group_count
The number of traffic classes.
BasicRateLimitBucket<> RateLimitBucket
A rate-limit bucket on the steady clock.
constexpr std::string_view rate_limit_group_name(RateLimitGroup group) noexcept
Returns a lowercase name for a traffic class, such as "market_data", for logs.
The library: broker-neutral types (core), and the modules below.
constexpr bool lists_every_value(const std::array< Enum, N > &values, IsEnumerator is_enumerator) noexcept
Tests whether a list holds every value of an enum exactly once, in any order.
The request rate a connection may use.
int trading_reserve_
The part of burst_ only AUTH and TRADING requests may use, so polling cannot use up the capacity an o...
int per_second_
The sustained rate, in requests per second; at least 1.
int burst_
How many requests may be sent back to back after an idle period; at least 1.
A rate limiter's counters for one traffic class.
std::uint64_t delayed_
Admitted requests that had to wait first (on a polling class, often the trading reserve at work).
std::chrono::nanoseconds waited_
Total time admitted requests waited.
std::uint64_t admitted_
Requests admitted.
std::uint64_t shed_
Requests refused locally: the queue was full, or the wait would have exceeded the timeout.
A rate limiter's counters per traffic class, its current budget, and the broker's refusals (from Rate...
std::uint64_t broker_rejections_
How many rate-limit refusals the broker sent; if it keeps rising, lower the budget.
RateLimitGroupStats groups_[rate_limit_group_count]
The counters per traffic class; read them with group().
int per_second_
The sustained rate in force.
const RateLimitGroupStats & group(RateLimitGroup value) const
Returns the counters for one traffic class.
int available_
How much of the burst was unspent just after the last admitted request (for that request's class); no...
int burst_
The burst in force.
RateLimitGroupStats & group(RateLimitGroup value)
Returns the counters for one traffic class.