24#include "fastmm/core/spin_mutex.hpp"
44 std::uint32_t
limit = 0;
54 std::uint32_t
own = 0;
57 [[nodiscard]] std::int64_t
aligned_start(std::int64_t now)
const noexcept {
61 void roll(std::int64_t now)
noexcept {
70 void realign(std::int64_t off, std::int64_t now)
noexcept {
79 [[nodiscard]]
bool would_exceed(std::uint32_t add,
double threshold)
const noexcept {
80 if (
limit == 0)
return false;
81 return static_cast<double>(
used + add) >
static_cast<double>(
limit) * threshold;
84 [[nodiscard]]
bool own_would_exceed(std::uint32_t add,
double threshold)
const noexcept {
85 if (
limit == 0)
return false;
86 return static_cast<double>(
own + add) >
static_cast<double>(
limit) * threshold;
94 std::int64_t lag)
noexcept {
97 used = std::clamp(
used, count, count + recent);
104 static constexpr std::size_t
kSlots = 32;
105 static constexpr std::int64_t
kSlotNs = 125'000'000;
106 void add(std::uint32_t n, std::int64_t now)
noexcept {
108 Slot& s = slots_[
static_cast<std::size_t
>(t) %
kSlots];
109 if (s.t != t) s = Slot{t, 0};
113 [[nodiscard]] std::uint32_t
since(std::int64_t now, std::int64_t lag)
const noexcept {
114 const std::int64_t from = (now - lag) /
kSlotNs - 1;
115 std::uint32_t sum = 0;
116 for (
const Slot& s : slots_) {
117 if (s.t >= from && s.t <= now /
kSlotNs) sum += s.n;
127 std::array<Slot, kSlots> slots_{};
159 static constexpr std::int64_t
kHeaderLagNs = 2'000'000'000;
165 void share_ip(std::shared_ptr<SharedRate> ip)
noexcept;
169 if (ip_)
return ip_add_weight_bucket(limit, window_ns);
170 return add(weight_, limit, window_ns, offset_);
172 bool add_order_bucket(std::uint32_t limit, std::int64_t window_ns)
noexcept {
173 return add(orders_, limit, window_ns, offset_);
177 void set_clock_offset(std::int64_t venue_minus_local_ns, std::int64_t now)
noexcept;
178 void set_threshold(
double t)
noexcept { threshold_ = std::clamp(t, 0.0, 1.0); }
181 void set_order_threshold(
double t)
noexcept { order_threshold_ = std::clamp(t, 0.0, 1.0); }
182 [[nodiscard]]
double order_threshold() const noexcept {
return order_threshold_; }
184 [[nodiscard]]
double threshold() const noexcept {
return threshold_; }
187 [[nodiscard]] std::int64_t
cap_of(std::uint32_t limit)
const noexcept {
188 return static_cast<std::int64_t
>(std::floor(
static_cast<double>(limit) * threshold_));
191 [[nodiscard]] std::int64_t
order_cap_of(std::uint32_t limit)
const noexcept {
192 return static_cast<std::int64_t
>(std::floor(
static_cast<double>(limit) * order_threshold_));
198 [[nodiscard]]
bool can_send(std::uint32_t weight,
200 bool is_order =
false,
201 double share = 1.0) noexcept {
202 return check(weight, now, is_order, share).
ok();
207 bool is_order =
false,
208 double share = 1.0) noexcept {
209 const RateRefusal w = ip_ ? ip_weight_fits(weight, now, share)
210 : weight_fits(weight, now, threshold_ * share, share < 1.0);
214 if (!b.active)
continue;
223 void on_sent(std::uint32_t weight, std::int64_t now,
bool is_order =
false) noexcept {
225 ip_on_sent(weight, now);
227 spend(weight_, weight, now);
228 recent_weight_.
add(weight, now);
231 spend(orders_, 1, now);
232 recent_orders_.
add(1, now);
239 std::int64_t order_count,
243 if (used_weight >= 0 && ip_) {
245 }
else if (used_weight >= 0 &&
weight_bucket < weight_.size() &&
247 weight_[
weight_bucket].calibrate(
static_cast<std::uint32_t
>(used_weight),
253 orders_[
order_bucket].calibrate(
static_cast<std::uint32_t
>(order_count),
260 void on_remaining(std::int64_t limit, std::int64_t remaining, std::int64_t now)
noexcept {
261 if (limit < 0 || remaining < 0 || weight_.empty() || !weight_[0].active)
return;
264 b.limit =
static_cast<std::uint32_t
>(limit);
265 b.used =
static_cast<std::uint32_t
>(std::max<std::int64_t>(0, limit - remaining));
271 void cooldown(std::int64_t retry_after_ns, std::int64_t now)
noexcept {
274 return ip_cooldown(retry_after_ns, now);
276 if (now >= cooldown_until_) {
277 streak_ = cooldowns_ > 0 && now - cooldown_until_ <=
kStreakWindowNs ? streak_ + 1 : 0;
279 const std::int64_t backoff =
281 cooldown_until_ = std::max(cooldown_until_, now + std::max(retry_after_ns, backoff));
286 [[nodiscard]] std::uint32_t
streak() const noexcept;
294 [[nodiscard]] std::uint64_t
cooldowns() const noexcept {
return cooldowns_; }
297 [[nodiscard]] std::optional<RateBucket>
weight_bucket(std::size_t i)
const noexcept;
298 [[nodiscard]] std::optional<RateBucket>
order_bucket(std::size_t i)
const noexcept {
299 if (i < orders_.size() && orders_[i].active)
return orders_[i];
304 static bool add(std::array<RateBucket, kMaxBuckets>& arr,
306 std::int64_t window_ns,
307 std::int64_t offset)
noexcept {
309 if (b.active)
continue;
310 b =
RateBucket{limit, window_ns, 0, 0,
true, offset, 0};
316 static void spend(std::array<RateBucket, kMaxBuckets>& arr,
318 std::int64_t now)
noexcept {
319 for (RateBucket& b : arr) {
320 if (!b.active)
continue;
333 bool paced)
noexcept {
335 for (RateBucket& b : weight_) {
336 if (!b.active)
continue;
339 if (paced && b.window_ns > 0) {
341 static_cast<double>(now - b.window_start) /
static_cast<double>(b.window_ns);
350 bool add_weight_bucket_once(std::uint32_t limit, std::int64_t window_ns)
noexcept {
351 for (
const RateBucket& b : weight_) {
352 if (b.active && b.limit == limit && b.window_ns == window_ns)
return true;
354 return add(weight_, limit, window_ns, offset_);
358 RateRefusal ip_weight_fits(std::uint32_t weight, std::int64_t now,
double share)
noexcept;
359 void ip_on_sent(std::uint32_t weight, std::int64_t now)
noexcept;
360 void ip_on_headers(std::int64_t used, std::int64_t now, std::size_t bucket)
noexcept;
361 bool ip_add_weight_bucket(std::uint32_t limit, std::int64_t window_ns)
noexcept;
362 void ip_cooldown(std::int64_t retry_after_ns, std::int64_t now)
noexcept;
365 double order_threshold_;
366 std::shared_ptr<SharedRate> ip_;
367 std::array<RateBucket, kMaxBuckets> weight_{};
368 std::array<RateBucket, kMaxBuckets> orders_{};
369 RecentSpend recent_weight_;
370 RecentSpend recent_orders_;
371 std::int64_t offset_ = 0;
372 std::int64_t cooldown_until_ = 0;
373 std::uint64_t cooldowns_ = 0;
374 std::uint32_t streak_ = 0;
375 bool hard_stopped_ =
false;
390[[nodiscard]]
inline std::string
ip_weight_key(std::string_view kind,
391 std::string_view rest_url,
392 std::string_view source) {
393 std::string key(kind);
396 if (!source.empty()) {
404inline std::shared_ptr<SharedRate>
shared_rate(std::string_view key) {
406 static std::map<std::string, std::shared_ptr<SharedRate>, std::less<>> by_key;
407 const std::lock_guard<std::mutex> lock(m);
408 auto it = by_key.find(key);
409 if (it == by_key.end())
410 it = by_key.emplace(std::string(key), std::make_shared<SharedRate>()).first;
416 const std::lock_guard<SpinMutex> lock(ip->m);
418 if (b.active)
static_cast<void>(ip->r.add_weight_bucket_once(b.limit, b.window_ns));
423inline RateRefusal RateLimiter::ip_weight_fits(std::uint32_t weight,
425 double share)
noexcept {
426 const std::lock_guard<SpinMutex> lock(ip_->m);
427 return ip_->r.weight_fits(weight, now, threshold_ * share, share < 1.0);
429inline void RateLimiter::ip_on_sent(std::uint32_t weight, std::int64_t now)
noexcept {
430 const std::lock_guard<SpinMutex> lock(ip_->m);
431 spend(ip_->r.weight_, weight, now);
432 ip_->r.recent_weight_.add(weight, now);
434inline void RateLimiter::ip_on_headers(std::int64_t used,
436 std::size_t bucket)
noexcept {
440 const std::lock_guard<SpinMutex> lock(ip_->m);
441 auto& w = ip_->r.weight_;
442 if (bucket >= w.size() || !w[bucket].active)
return;
443 w[bucket].calibrate(
static_cast<std::uint32_t
>(used),
444 ip_->r.recent_weight_.since(now, kHeaderLagNs),
449 std::int64_t now)
noexcept {
450 offset_ = venue_minus_local_ns;
452 if (b.active) b.realign(offset_, now);
456 if (b.active) b.realign(offset_, now);
460 const std::lock_guard<SpinMutex> lock(ip_->m);
461 ip_->r.offset_ = offset_;
462 for (RateBucket& b : ip_->r.weight_) {
463 if (b.active) b.realign(offset_, now);
466inline bool RateLimiter::ip_add_weight_bucket(std::uint32_t limit,
467 std::int64_t window_ns)
noexcept {
468 const std::lock_guard<SpinMutex> lock(ip_->m);
469 return ip_->r.add_weight_bucket_once(limit, window_ns);
471inline void RateLimiter::ip_cooldown(std::int64_t retry_after_ns, std::int64_t now)
noexcept {
472 const std::lock_guard<SpinMutex> lock(ip_->m);
473 ip_->r.cooldown(retry_after_ns, now);
476 if (!ip_)
return cooldown_until_;
477 const std::lock_guard<SpinMutex> lock(ip_->m);
478 return ip_->r.cooldown_until_;
482 hard_stopped_ =
true;
485 const std::lock_guard<SpinMutex> lock(ip_->m);
486 ip_->r.hard_stopped_ =
true;
490 hard_stopped_ =
false;
493 const std::lock_guard<SpinMutex> lock(ip_->m);
494 ip_->r.hard_stopped_ =
false;
498 if (i < weight_.size() && weight_[i].active)
return weight_[i];
501 const std::lock_guard<SpinMutex> lock(ip_->m);
502 return ip_->r.weight_bucket(i);
505 if (!ip_)
return streak_;
506 const std::lock_guard<SpinMutex> lock(ip_->m);
507 return ip_->r.streak_;
510 if (!ip_)
return hard_stopped_;
511 const std::lock_guard<SpinMutex> lock(ip_->m);
512 return ip_->r.hard_stopped_;
518template <
class Sleep>
520 std::uint32_t weight,
522 std::int64_t max_wait_ns,
523 Sleep&& sleep_ns)
noexcept {
524 const std::int64_t until = now_ns + max_wait_ns;
525 std::int64_t now = now_ns;
527 constexpr std::int64_t kStep = 100'000'000;
Definition rate_limiter.hpp:141
static constexpr std::size_t kMaxBuckets
Definition rate_limiter.hpp:143
void hard_stop() noexcept
418: the IP is banned; stop REST entirely until an operator clears it.
Definition rate_limiter.hpp:482
bool add_weight_bucket(std::uint32_t limit, std::int64_t window_ns) noexcept
Buckets are added at startup from exchangeInfo.rateLimits / the Bybit rate limit table.
Definition rate_limiter.hpp:170
static constexpr std::int64_t kStreakWindowNs
A pause asked (429) within this long after the last one ended is the same incident going on: each suc...
Definition rate_limiter.hpp:151
static constexpr double kBulkShare
The part of each weight window a bulk request may fill (can_send's share): with 170 symbols' snapshot...
Definition rate_limiter.hpp:147
bool can_send(std::uint32_t weight, std::int64_t now, bool is_order=false, double share=1.0) noexcept
True if a request of weight (and one order if is_order) fits under threshold * limit in every bucket ...
Definition rate_limiter.hpp:200
bool add_order_bucket(std::uint32_t limit, std::int64_t window_ns) noexcept
Definition rate_limiter.hpp:174
bool in_cooldown(std::int64_t now) const noexcept
Definition rate_limiter.hpp:289
void clear_hard_stop() noexcept
Definition rate_limiter.hpp:490
std::uint64_t cooldowns() const noexcept
Definition rate_limiter.hpp:296
void set_threshold(double t) noexcept
Definition rate_limiter.hpp:180
void on_sent(std::uint32_t weight, std::int64_t now, bool is_order=false) noexcept
Local accounting after a send.
Definition rate_limiter.hpp:225
static constexpr double kBulkPaceFloor
The part of kBulkShare this limiter's own weight in a window may fill before a bulk request grows wit...
Definition rate_limiter.hpp:158
void on_remaining(std::int64_t limit, std::int64_t remaining, std::int64_t now) noexcept
Bybit style: remaining requests for this endpoint (X-Bapi-Limit-Status) and the limit.
Definition rate_limiter.hpp:262
double threshold() const noexcept
The share of each limit can_send fills before it refuses.
Definition rate_limiter.hpp:186
std::int64_t cap_of(std::uint32_t limit) const noexcept
The count a bucket of limit admits under that share: can_send refuses the order or request that would...
Definition rate_limiter.hpp:189
std::optional< RateBucket > order_bucket(std::size_t i) const noexcept
Definition rate_limiter.hpp:300
void set_clock_offset(std::int64_t venue_minus_local_ns, std::int64_t now) noexcept
The venue's clock minus the local (now) clock, ns: every window, the IP's included,...
Definition rate_limiter.hpp:450
bool hard_stopped() const noexcept
Definition rate_limiter.hpp:511
void cooldown(std::int64_t retry_after_ns, std::int64_t now) noexcept
429 / -1003 / 10006: block sends until now + retry_after, or longer when the last pause ended less th...
Definition rate_limiter.hpp:273
std::uint32_t streak() const noexcept
Pauses in the current streak after the first.
Definition rate_limiter.hpp:506
RateCheck check(std::uint32_t weight, std::int64_t now, bool is_order=false, double share=1.0) noexcept
can_send, saying which limit refused.
Definition rate_limiter.hpp:207
static constexpr std::int64_t kBackoffBaseNs
Definition rate_limiter.hpp:152
static constexpr std::int64_t kBackoffMaxNs
Definition rate_limiter.hpp:153
std::optional< RateBucket > weight_bucket(std::size_t i) const noexcept
Copies of the i-th bucket (insertion order), empty past the last; a shared IP's is copied under its l...
Definition rate_limiter.hpp:498
static constexpr std::int64_t kHeaderLagNs
How late a venue's count (on_headers) may be: what was sent in this long before it arrived may be mis...
Definition rate_limiter.hpp:161
void on_headers(std::int64_t used_weight, std::int64_t order_count, std::int64_t now, std::size_t weight_bucket=0, std::size_t order_bucket=0) noexcept
The venue's counts from a response (RateBucket::calibrate).
Definition rate_limiter.hpp:240
void set_order_threshold(double t) noexcept
The share of each order-count limit (ORDERS buckets) can_send fills; the weight keeps threshold().
Definition rate_limiter.hpp:183
std::int64_t cooldown_until() const noexcept
Definition rate_limiter.hpp:477
double order_threshold() const noexcept
Definition rate_limiter.hpp:184
std::int64_t order_cap_of(std::uint32_t limit) const noexcept
cap_of for an order-count bucket.
Definition rate_limiter.hpp:193
void share_ip(std::shared_ptr< SharedRate > ip) noexcept
The IP's weight, cooldown and hard stop live in ip from now on (null: this limiter's own).
Definition rate_limiter.hpp:416
static constexpr std::int64_t kSlotNs
Definition rate_limiter.hpp:107
static constexpr std::size_t kSlots
Definition rate_limiter.hpp:106
std::uint32_t since(std::int64_t now, std::int64_t lag) const noexcept
Sent from now - lag on (whole slots: a little more, never less).
Definition rate_limiter.hpp:115
void add(std::uint32_t n, std::int64_t now) noexcept
Definition rate_limiter.hpp:108
std::string ip_weight_key(std::string_view kind, std::string_view rest_url, std::string_view source)
The key of one IP's weight at one venue: the connector kind, its REST host and the address its connec...
Definition rate_limiter.hpp:392
void wait_for_weight(RateLimiter &r, std::uint32_t weight, std::int64_t now_ns, std::int64_t max_wait_ns, Sleep &&sleep_ns) noexcept
A blocking start-up request (reference data, account settings, one per symbol) waits for its weight t...
Definition rate_limiter.hpp:521
RateRefusal
Why can_send refused (RateLimiter::check): the IP or account is paused (429 cooldown,...
Definition rate_limiter.hpp:134
@ None
not a multicast venue
std::shared_ptr< SharedRate > shared_rate(std::string_view key)
The SharedRate of key (ip_weight_key), made on first use; process-wide.
Definition rate_limiter.hpp:406
Definition rate_limiter.hpp:45
void calibrate(std::uint32_t count, std::uint32_t recent, std::int64_t now, std::int64_t lag) noexcept
The venue's count count for this window, recent sent in the last kHeaderLagNs: raised to the count,...
Definition rate_limiter.hpp:93
void roll(std::int64_t now) noexcept
Definition rate_limiter.hpp:63
bool would_exceed(std::uint32_t add, double threshold) const noexcept
Definition rate_limiter.hpp:81
std::uint32_t own
What this limiter sent in the current window, which used includes: the venue's counts (calibrate) mov...
Definition rate_limiter.hpp:56
std::int64_t offset
The venue's clock minus the local one (RateLimiter::set_clock_offset): windows start where now + offs...
Definition rate_limiter.hpp:53
bool active
Definition rate_limiter.hpp:50
std::int64_t window_start
ns; the window rolls when now - window_start >= window_ns
Definition rate_limiter.hpp:49
void realign(std::int64_t off, std::int64_t now) noexcept
A new clock offset: the window holding now starts where the venue's does.
Definition rate_limiter.hpp:72
bool own_would_exceed(std::uint32_t add, double threshold) const noexcept
The same for this limiter's own weight in the window.
Definition rate_limiter.hpp:86
std::uint32_t used
consumed in the current window
Definition rate_limiter.hpp:48
std::int64_t aligned_start(std::int64_t now) const noexcept
The start of the venue's window that holds now.
Definition rate_limiter.hpp:59
std::int64_t window_ns
fixed window length
Definition rate_limiter.hpp:47
std::uint32_t limit
0 = unlimited
Definition rate_limiter.hpp:46
Definition rate_limiter.hpp:135
bool ok() const noexcept
Definition rate_limiter.hpp:138
RateRefusal refusal
Definition rate_limiter.hpp:136
std::int64_t window_ns
Orders: the window at its threshold.
Definition rate_limiter.hpp:137
SpinMutex m
Every order of every account on the IP takes it twice (ip_weight_fits, ip_on_sent),...
Definition rate_limiter.hpp:385
RateLimiter r
its weight buckets, cooldown and hard stop; never shared itself
Definition rate_limiter.hpp:386