FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
venue_health.hpp
Go to the documentation of this file.
1#pragma once
2// VenueHealth: per venue, how late its market data arrives and how long our orders take to be
3// acknowledged, from the events the engine consumes (engine thread only).
4//
5// feed lag recv_ts - exch_ts of every market-data message that carries a venue time (book
6// deltas, trades, tickers; snapshots excluded). The host and the venue clocks differ
7// by a constant offset, so the lag is compared with its baseline: the minimum over
8// the last kWindow (kBuckets buckets of kBucket, rolled on the engine clock).
9// ack RTT engine time from sending a new order to consuming its first ack, last and smoothed
10// (srtt += (rtt - srtt) / 8, as TCP's SRTT).
11//
12// Every input is in the journal (the message headers and the engine clock), so a replay computes
13// the same values. The feed-lag gate ([risk] max_feed_lag_ms) holds a venue for kGateHold after the
14// last message whose excess lag was over the limit.
15#include "fastmm/core/config_macros.hpp"
17#include "fastmm/core/time.hpp"
18
19#include <array>
20#include <cstddef>
21#include <cstdint>
22#include <limits>
23
24namespace fastmm {
25
26// What StrategyContext::venue_health returns. Durations are zero until the first sample.
27struct VenueHealthView {
28 Duration feed_lag{}; // recv_ts - exch_ts of the latest market-data message
29 Duration feed_lag_base{}; // its minimum over the last kWindow
30 Duration feed_lag_excess{}; // feed_lag - feed_lag_base
31 Duration ack_rtt{}; // latest send -> first ack
33 Timestamp md_updated{}; // engine time of the latest feed-lag sample
34 Timestamp ack_updated{}; // ... and of the latest ack sample
35 std::uint64_t md_samples = 0;
36 std::uint64_t ack_samples = 0;
37 std::uint64_t gate_engagements = 0; // times the feed-lag gate engaged
38 bool gated = false; // the feed-lag gate holds this venue now
39};
41class VenueHealth {
42 public:
43 static constexpr std::size_t kVenues = 8; // kMaxVenues
44 static constexpr std::size_t kBuckets = 8;
45 static constexpr Duration kBucket = seconds(1);
46 static constexpr Duration kWindow = Duration{kBucket.ns * static_cast<std::int64_t>(kBuckets)};
47 static constexpr Duration kGateHold = milliseconds(100);
49 // A market-data message of venue `v` with `lag` = recv_ts - exch_ts, consumed at `now`. With
50 // `limit` > 0, returns true when the message engages the gate (it was not holding the venue).
51 FASTMM_FORCE_INLINE bool on_md(VenueId v,
52 std::int64_t lag,
54 Duration limit) noexcept {
55 if (FASTMM_UNLIKELY(v.value >= kVenues)) return false;
56 Slot& s = slots_[v.value];
57 if (FASTMM_UNLIKELY(now >= s.bucket_end)) roll(s, now);
58 if (lag < s.cur_min) s.cur_min = lag;
59 s.lag = lag;
60 s.md_ts = now;
61 ++s.md_samples;
62 if (FASTMM_LIKELY(limit.ns <= 0)) return false;
63 return over_limit(s, lag, now, limit);
64 }
65 void on_ack(VenueId v, Duration rtt, Timestamp now) noexcept {
66 if (v.value >= kVenues || rtt.ns < 0) return;
67 Slot& s = slots_[v.value];
68 s.rtt = rtt.ns;
69 s.srtt = s.ack_samples == 0 ? rtt.ns : s.srtt + (rtt.ns - s.srtt) / 8;
70 s.ack_ts = now;
71 ++s.ack_samples;
72 }
73 [[nodiscard]] FASTMM_FORCE_INLINE bool gated(VenueId v, Timestamp now) const noexcept {
74 return v.value < kVenues && now < slots_[v.value].gate_until;
75 }
76 [[nodiscard]] VenueHealthView view(VenueId v, Timestamp now) const noexcept {
78 if (v.value >= kVenues) return out;
79 const Slot& s = slots_[v.value];
80 if (s.md_samples != 0) {
81 const std::int64_t base = s.cur_min < s.done_min ? s.cur_min : s.done_min;
82 out.feed_lag = Duration{s.lag};
83 out.feed_lag_base = Duration{base};
84 out.feed_lag_excess = Duration{s.lag - base};
85 }
86 out.ack_rtt = Duration{s.rtt};
87 out.ack_rtt_smoothed = Duration{s.srtt};
88 out.md_updated = s.md_ts;
89 out.ack_updated = s.ack_ts;
90 out.md_samples = s.md_samples;
91 out.ack_samples = s.ack_samples;
92 out.gate_engagements = s.gate_engagements;
93 out.gated = now < s.gate_until;
94 return out;
95 }
96
97 private:
98 static constexpr std::int64_t kNone = std::numeric_limits<std::int64_t>::max();
99 struct Slot {
100 Timestamp bucket_end{}; // the current bucket ends here (engine time)
101 std::int64_t cur_min = kNone; // of the current bucket
102 std::int64_t done_min = kNone; // of the completed buckets of the window
103 std::int64_t lag = 0;
104 Timestamp md_ts{};
105 Timestamp gate_until{};
106 std::uint64_t md_samples = 0;
107 std::uint64_t gate_engagements = 0;
108 std::array<std::int64_t, kBuckets - 1> done{}; // minima of the completed buckets
109 std::uint32_t next = 0; // slot in `done` the next roll writes
110 std::int64_t rtt = 0;
111 std::int64_t srtt = 0;
112 Timestamp ack_ts{};
113 std::uint64_t ack_samples = 0;
114 };
115
116 static bool over_limit(Slot& s, std::int64_t lag, Timestamp now, Duration limit) noexcept {
117 const std::int64_t base = s.cur_min < s.done_min ? s.cur_min : s.done_min;
118 if (lag - base <= limit.ns) return false;
119 const bool engaged = now >= s.gate_until;
120 s.gate_until = now + kGateHold;
121 if (engaged) ++s.gate_engagements;
122 return engaged;
123 }
124 // Closes the buckets that ended before `now`; a gap of a whole window or more clears them all.
125 FASTMM_NOINLINE static void roll(Slot& s, Timestamp now) noexcept {
126 if (!s.bucket_end.valid() || now - s.bucket_end >= kWindow) {
127 s.done.fill(kNone);
128 s.next = 0;
129 s.cur_min = kNone;
130 s.bucket_end = now + kBucket;
131 } else {
132 while (now >= s.bucket_end) {
133 s.done[s.next] = s.cur_min;
134 s.next = (s.next + 1) % static_cast<std::uint32_t>(s.done.size());
135 s.cur_min = kNone;
136 s.bucket_end = s.bucket_end + kBucket;
137 }
138 }
139 s.done_min = kNone;
140 for (const std::int64_t m : s.done)
141 if (m < s.done_min) s.done_min = m;
142 }
143
144 std::array<Slot, kVenues> slots_{};
145};
146
147} // namespace fastmm
Definition venue_health.hpp:43
static constexpr Duration kWindow
Definition venue_health.hpp:48
VenueHealthView view(VenueId v, Timestamp now) const noexcept
Definition venue_health.hpp:78
static constexpr std::size_t kBuckets
Definition venue_health.hpp:46
static constexpr Duration kGateHold
Definition venue_health.hpp:49
FASTMM_FORCE_INLINE bool gated(VenueId v, Timestamp now) const noexcept
Definition venue_health.hpp:75
static constexpr Duration kBucket
Definition venue_health.hpp:47
FASTMM_FORCE_INLINE bool on_md(VenueId v, std::int64_t lag, Timestamp now, Duration limit) noexcept
A market-data message of venue v with lag = recv_ts - exch_ts, consumed at now.
Definition venue_health.hpp:53
void on_ack(VenueId v, Duration rtt, Timestamp now) noexcept
Definition venue_health.hpp:67
static constexpr std::size_t kVenues
kMaxVenues
Definition venue_health.hpp:45
Definition strategy.hpp:16
constexpr Duration milliseconds(std::int64_t v) noexcept
Definition time.hpp:38
constexpr Duration seconds(std::int64_t v) noexcept
Definition time.hpp:41
Strongly typed identifiers.
Definition time.hpp:22
std::int64_t ns
Definition time.hpp:23
Definition time.hpp:45
What StrategyContext::venue_health returns. Durations are zero until the first sample.
Definition venue_health.hpp:29
std::uint64_t ack_samples
Definition venue_health.hpp:38
std::uint64_t gate_engagements
times the feed-lag gate engaged
Definition venue_health.hpp:39
bool gated
the feed-lag gate holds this venue now
Definition venue_health.hpp:40
std::uint64_t md_samples
Definition venue_health.hpp:37
Duration feed_lag
recv_ts - exch_ts of the latest market-data message
Definition venue_health.hpp:30
Duration ack_rtt_smoothed
Definition venue_health.hpp:34
Timestamp ack_updated
... and of the latest ack sample
Definition venue_health.hpp:36
Duration ack_rtt
latest send -> first ack
Definition venue_health.hpp:33
Duration feed_lag_base
its minimum over the last kWindow
Definition venue_health.hpp:31
Duration feed_lag_excess
feed_lag - feed_lag_base
Definition venue_health.hpp:32
Timestamp md_updated
engine time of the latest feed-lag sample
Definition venue_health.hpp:35
Time primitives.