FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
order_commands.hpp
Go to the documentation of this file.
1#pragma once
2// OrderCommand: a uniform, non-owning view over the three outbound engine messages
3// (OutNewOrderMsg / OutCancelMsg / OutReplaceMsg) so encoders switch on one enum instead of
4// three message types. Built from the ring pointer with no copy.
6
7#include <cstddef>
8#include <cstdint>
9#include <optional>
10
11namespace fastmm::venues {
12
13enum class OrderCommandKind : std::uint8_t { New = 0, Cancel = 1, Replace = 2 };
14[[nodiscard]] constexpr std::string_view to_string(OrderCommandKind k) noexcept {
15 switch (k) {
17 return "New";
19 return "Cancel";
21 return "Replace";
22 }
23 return "?";
24}
25
26struct OrderCommand {
30 ClientOrderId cl_ord_id{}; // New: the order; Cancel: order to cancel; Replace: new id
31 ClientOrderId orig_cl_ord_id{}; // Replace only
32 const VenueOrderId* venue_order_id = nullptr; // Cancel/Replace: may be empty
36 bool reduce_only = false;
39 const EventHeader* header = nullptr;
41 // Receive stamp of the inbound event that triggered the order (0 when none, e.g. timers).
42 [[nodiscard]] Cycles t0_cycles() const noexcept {
43 return header != nullptr ? header->t0_cycles : Cycles{};
44 }
45
46 // nullopt for any non-outbound message type.
47 [[nodiscard]] static std::optional<OrderCommand> from(const EventHeader& h) noexcept {
49 c.header = &h;
50 c.instrument = h.instrument;
51 c.venue = h.venue;
52 switch (h.type) {
54 const auto& m = msg_cast<OutNewOrderMsg>(&h);
55 c.kind = OrderCommandKind::New;
56 c.cl_ord_id = m.cl_ord_id;
57 c.side = m.side;
58 c.type = m.type;
59 c.tif = m.tif;
60 c.reduce_only = m.reduce_only != 0;
61 c.price = m.price;
62 c.qty = m.qty;
63 return c;
64 }
66 const auto& m = msg_cast<OutCancelMsg>(&h);
68 c.cl_ord_id = m.cl_ord_id;
69 c.venue_order_id = &m.venue_order_id;
70 return c;
71 }
73 const auto& m = msg_cast<OutReplaceMsg>(&h);
75 c.cl_ord_id = m.cl_ord_id;
76 c.orig_cl_ord_id = m.orig_cl_ord_id;
77 c.venue_order_id = &m.venue_order_id;
78 c.price = m.price;
79 c.qty = m.qty;
80 return c;
81 }
82 default:
83 return std::nullopt;
84 }
85 }
86};
87
88// The watermark stamped into an open-order snapshot's ReconcileMsg Begin: the engine's orders at
89// or below it can be judged by the snapshot, the ones above it cannot. It is the last client order
90// id (New or Replace) the venue sent before the first one the venue has not answered yet (ack,
91// reject, fill or cancel), in send order: an order still in flight when the snapshot is asked for
92// may reach the matching engine, or the venue's open-order view, after the snapshot is taken
93// (Binance Spot, 2026-09-26: an order sent 1 ms before openOrders.status and accepted by the
94// matching engine was missing from the reply, and the engine cancelled it as gone). An order with
95// no answer after kUnansweredNs no longer holds the watermark back, and neither does one sent on a
96// WebSocket connection that is gone (it will not be answered on it); one sent over REST does, as
97// its reply still comes.
98//
99// One engine allocates its ids in the order it sends them; fastmm-gateway interleaves several
100// engines' ids on one venue and finds the point the snapshot was taken at from the id itself
101// (live/gateway.hpp), which is why the watermark is always an id that was sent.
102//
103// The connector itself compares by send sequence: every New and Replace noted takes the next
104// number (last_seq()), in send order over every engine behind the connector, and the watermark's
105// sequence (Mark::seq) is that of the id it names. An order shadow stamped with its sequence is
106// judged by the snapshot when its sequence is at or below the mark's, whichever engine sent it.
107class SentWatermark {
108 public:
109 static constexpr std::int64_t kUnansweredNs = 10'000'000'000;
110 static constexpr std::size_t kMaxInFlight = 256;
112 // The watermark for a snapshot: the id, for the engine, and its send sequence (0: none sent).
113 struct Mark {
115 std::uint64_t seq = 0;
116 };
118 void note(const OrderCommand& c, std::int64_t now_ns) noexcept {
119 if (c.kind == OrderCommandKind::Cancel) return;
120 prune(now_ns);
121 if (n_ == kMaxInFlight) pop(); // treat the oldest as answered: never blocks new orders
122 ++seq_;
123 in_flight_[(head_ + n_) % kMaxInFlight] =
124 InFlight{c.cl_ord_id, high_, seq_, now_ns, false, false};
125 ++n_;
126 high_ = c.cl_ord_id;
127 }
128 // The venue said something about order `id`.
129 void answered(ClientOrderId id) noexcept {
130 for (std::size_t k = 0; k < n_; ++k) {
131 InFlight& f = in_flight_[(head_ + k) % kMaxInFlight];
132 if (f.id == id) {
133 f.answered = true;
134 break;
135 }
136 }
137 }
138 // answered() for the order an event from the venue is about.
139 void answered(const EventHeader& h) noexcept {
140 switch (h.type) {
142 answered(msg_cast<OrderAckMsg>(&h).cl_ord_id);
143 break;
145 answered(msg_cast<OrderRejectMsg>(&h).cl_ord_id);
146 break;
148 answered(msg_cast<OrderFillMsg>(&h).cl_ord_id);
149 break;
151 answered(msg_cast<OrderCancelAckMsg>(&h).cl_ord_id);
152 break;
154 answered(msg_cast<OrderExpiredMsg>(&h).cl_ord_id);
155 break;
156 default:
157 break;
158 }
159 }
160 // Order `id` (noted last, or nearly) went out over REST rather than on the WebSocket order
161 // connection: losing that connection does not settle it.
162 void sent_over_rest(ClientOrderId id) noexcept {
163 for (std::size_t k = n_; k > 0; --k) {
164 InFlight& f = in_flight_[(head_ + k - 1) % kMaxInFlight];
165 if (f.id == id) {
166 f.rest = true;
167 break;
168 }
169 }
170 }
171 // The WebSocket order connection is gone: the requests sent on it will not be answered on it.
172 // REST requests still will, and keep holding the watermark back.
173 void connection_lost() noexcept {
174 for (std::size_t k = 0; k < n_; ++k) {
175 InFlight& f = in_flight_[(head_ + k) % kMaxInFlight];
176 if (!f.rest) f.answered = true;
177 }
178 }
179 // The watermark for a snapshot requested now.
180 [[nodiscard]] ClientOrderId value(std::int64_t now_ns) noexcept { return mark(now_ns).id; }
181 [[nodiscard]] Mark mark(std::int64_t now_ns) noexcept {
182 prune(now_ns);
183 if (n_ == 0) return Mark{high_, seq_};
184 const InFlight& f = in_flight_[head_];
185 return Mark{f.prev, f.seq - 1};
186 }
187 // The last id sent, answered or not, and its send sequence (the order being sent, inside the
188 // connector's send path).
189 [[nodiscard]] ClientOrderId last_sent() const noexcept { return high_; }
190 [[nodiscard]] std::uint64_t last_seq() const noexcept { return seq_; }
191 static void stamp(ReconcileMsg& begin, ClientOrderId watermark) noexcept {
192 begin.sent_watermark = watermark;
194 }
195
196 private:
197 struct InFlight {
198 ClientOrderId id;
199 ClientOrderId prev; // the id sent just before it
200 std::uint64_t seq; // its send sequence
201 std::int64_t sent_ns;
202 bool answered;
203 bool rest; // sent over REST, not on the WebSocket order connection
204 };
205 void pop() noexcept {
206 head_ = (head_ + 1) % kMaxInFlight;
207 --n_;
208 }
209 void prune(std::int64_t now_ns) noexcept {
210 while (n_ > 0) {
211 const InFlight& f = in_flight_[head_];
212 if (!f.answered && now_ns - f.sent_ns < kUnansweredNs) break;
213 pop();
214 }
215 }
216
217 InFlight in_flight_[kMaxInFlight] = {};
218 std::size_t head_ = 0;
219 std::size_t n_ = 0;
220 ClientOrderId high_{};
221 std::uint64_t seq_ = 0; // send sequence of high_
222};
223
224// The engine asks for a reconciliation (ControlCommand::Reconcile on the outbound ring).
225[[nodiscard]] inline bool is_reconcile_request(const EventHeader& h) noexcept {
226 return h.type == EventType::Control &&
227 msg_cast<ControlMsg>(&h).command == ControlCommand::Reconcile;
228}
229
230} // namespace fastmm::venues
ClientOrderId last_sent() const noexcept
The last id sent, answered or not, and its send sequence (the order being sent, inside the connector'...
Definition order_commands.hpp:191
static constexpr std::int64_t kUnansweredNs
Definition order_commands.hpp:111
void connection_lost() noexcept
The WebSocket order connection is gone: the requests sent on it will not be answered on it.
Definition order_commands.hpp:175
Mark mark(std::int64_t now_ns) noexcept
Definition order_commands.hpp:183
static constexpr std::size_t kMaxInFlight
Definition order_commands.hpp:112
void answered(ClientOrderId id) noexcept
The venue said something about order id.
Definition order_commands.hpp:131
void sent_over_rest(ClientOrderId id) noexcept
Order id (noted last, or nearly) went out over REST rather than on the WebSocket order connection: lo...
Definition order_commands.hpp:164
std::uint64_t last_seq() const noexcept
Definition order_commands.hpp:192
static void stamp(ReconcileMsg &begin, ClientOrderId watermark) noexcept
Definition order_commands.hpp:193
ClientOrderId value(std::int64_t now_ns) noexcept
The watermark for a snapshot requested now.
Definition order_commands.hpp:182
void note(const OrderCommand &c, std::int64_t now_ns) noexcept
Definition order_commands.hpp:120
Event bus message structs (5.5).
Definition venue.hpp:38
bool is_reconcile_request(const EventHeader &h) noexcept
The engine asks for a reconciliation (ControlCommand::Reconcile on the outbound ring).
Definition order_commands.hpp:227
constexpr std::string_view to_string(ChannelState s) noexcept
Definition venue.hpp:51
OrderCommandKind
Definition order_commands.hpp:15
OrderType
Definition enums.hpp:23
StrongId< struct ClientOrderTag, std::uint64_t, 0 > ClientOrderId
48-bit: session_epoch(16) << 32 | seq(32). 0 is never issued.
Definition strong_id.hpp:41
FixedString< 40 > VenueOrderId
Definition strong_id.hpp:42
Side
Definition enums.hpp:12
TimeInForce
Definition enums.hpp:36
@ Reconcile
Engine -> venue, on the outbound ring (hdr.venue: the venue): the engine and the venue disagree about...
Definition time.hpp:59
Definition messages.hpp:37
Cycles t0_cycles
T0 raw TSC at recv (48)
Definition messages.hpp:65
Control thread injects the venue's view after a reconnect (5.8).
Definition messages.hpp:469
static constexpr std::uint8_t kSentWatermark
Definition messages.hpp:471
Definition order_commands.hpp:28
const EventHeader * header
Definition order_commands.hpp:41
OrderType type
Definition order_commands.hpp:36
Qty qty
Definition order_commands.hpp:40
Price price
Definition order_commands.hpp:39
Side side
Definition order_commands.hpp:35
ClientOrderId cl_ord_id
New: the order; Cancel: order to cancel; Replace: new id.
Definition order_commands.hpp:32
static std::optional< OrderCommand > from(const EventHeader &h) noexcept
nullopt for any non-outbound message type.
Definition order_commands.hpp:49
OrderCommandKind kind
Definition order_commands.hpp:29
TimeInForce tif
Definition order_commands.hpp:37
InstrumentId instrument
Definition order_commands.hpp:30
const VenueOrderId * venue_order_id
Cancel/Replace: may be empty.
Definition order_commands.hpp:34
ClientOrderId orig_cl_ord_id
Replace only.
Definition order_commands.hpp:33
Cycles t0_cycles() const noexcept
Receive stamp of the inbound event that triggered the order (0 when none, e.g. timers).
Definition order_commands.hpp:44
bool reduce_only
Definition order_commands.hpp:38
VenueId venue
Definition order_commands.hpp:31
The watermark for a snapshot: the id, for the engine, and its send sequence (0: none sent).
Definition order_commands.hpp:115
std::uint64_t seq
Definition order_commands.hpp:117
ClientOrderId id
Definition order_commands.hpp:116