FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
hedge_executor.hpp
Go to the documentation of this file.
1#pragma once
2// HedgeExecutor: keeps a strategy's exposure hedged with taker orders (docs/how-to/strategies/
3// hedge-executor.md). A strategy owns one, configures it in on_start and forwards its hooks.
4//
5// Exposure and residual, in base units (linear contracts: quantity * contract multiplier):
6//
7// residual = sum of source positions + sum of hedge positions - target
8//
9// The sources are the instruments whose position is the exposure (a maker's quote instrument);
10// the target is what the strategy wants to hold net (0 by default). The hedges are instruments
11// that can take the other side, in preference order, each with its own price tolerance.
12//
13// Sizing comes from positions only, never from a count of fills: a restart, a replayed or
14// duplicated fill and an order whose outcome arrives late all end in the same place.
15//
16// One order in flight at a time, across every hedge instrument and the de-risk orders: the
17// residual is one number, and two orders sized from it on two venues would both fill. While an
18// order is open on any hedge instrument (a previous session's included, once reconciliation
19// adopts it), nothing else goes out; its end sends the next one. No order goes out while a venue
20// reconciles (StrategyContext::reconciling): a position may still lack a replayed execution.
21//
22// A hedge is an IOC limit on the first usable hedge instrument, priced its tolerance through the
23// touch, of -residual in its contracts rounded down to the lot and capped at the instrument's
24// max_qty (the remainder follows when it ends). None goes out when that rounds under the lot,
25// min_qty or min_notional: the remainder waits for more exposure rather than being refused.
26//
27// The order is then cut to what [risk] admits now (StrategyContext::risk_headroom): max_order_qty,
28// max_order_notional at the order's price, and on its side the room of max_position and of
29// [risk.underlying] max_net. A hedge larger than one of them goes out in pieces, each after the
30// one before it ends, instead of being refused whole every time. When the limits admit nothing the
31// instrument's minimums allow (the position room is used up), the order goes out whole: the engine
32// refuses it and that is a failure of the instrument, as before. The exposure caps
33// (max_gross_notional, max_net_notional) size it the same way, through the room the engine gives
34// for one order in the instrument's own currency (RiskHeadroom::exposure_buy_notional).
35//
36// A hedge instrument is usable when its venue's order channel is up, its venue is not killed, its
37// book is valid, not older than Config::stale and not held by the feed-lag gate, and it is not
38// benched. The first usable one takes the hedge; when its balance or margin cannot cover the order
39// (StrategyContext::balance_room), the next usable one that can does. When none can, the hedge is
40// held (logged and counted once per episode) until a balance report or a fill changes that.
41//
42// Failures: a hedge that ends with nothing filled, or that the engine refuses, is a failure of its
43// instrument; the next hedge waits Config::retry. max_failures within failure_window bench the
44// instrument for Config::bench while another is not benched; when it is the last one, the executor
45// halts (no more hedges) until restart(). A hedge whose outcome the venue never reported (the
46// ack-timeout cancel, a reconciliation drop with quantity unaccounted for, a generic VenueReject
47// such as a REST timeout) holds every order for Config::uncertain_hold, so a fill still on its way
48// is booked before the positions are trusted again.
49//
50// De-risking (Config::derisk_after > 0): when no hedge instrument has taken the residual for that
51// long (unusable, balance short or halted), the executor reduces the exposure itself: reduce-only
52// IOC orders on a source whose position has the residual's sign, priced derisk_tolerance through
53// its touch, each at most derisk_step (base units), one at a time and derisk_interval apart. It
54// stops as soon as a hedge instrument can take the residual again, or the residual is gone. The
55// strategy must not quote the side a de-risk order crosses: pulling the quotes while can_hedge() is
56// false and quoting only the reducing side while held() does that.
57//
58// The executor has no timer of its own: the strategy calls on_timer from one of its timers (the
59// retry, hold and de-risk waits end at the next call after them). Every call is noexcept and
60// allocation-free. Linear instruments only: an inverse leg leaves it idle with an error.
61#include "fastmm/core/enums.hpp"
64#include "fastmm/core/log.hpp"
66#include "fastmm/core/oms.hpp"
67#include "fastmm/core/order.hpp"
69#include "fastmm/core/time.hpp"
72
73#include <algorithm>
74#include <array>
75#include <cstddef>
76#include <cstdint>
77#include <optional>
78#include <string_view>
79
80namespace fastmm {
81
82class HedgeExecutor {
83 public:
84 static constexpr std::size_t kMaxSources = 4;
85 static constexpr std::size_t kMaxHedges = 4;
86 static constexpr std::size_t kMaxFailures = 16;
88 struct Config {
89 std::string_view name = "hedge"; // log prefix
90 std::uint32_t tag = 0; // user_tag of hedge orders
91 std::uint32_t derisk_tag = 0; // user_tag of de-risk orders
92 Duration retry = milliseconds(200); // after a hedge that filled nothing
93 int max_failures = 5; // within failure_window: bench, or halt on the last one
96 Duration stale{}; // a hedge book older than this is unusable; 0: never
97 Duration bench = milliseconds(60000); // how long a failing instrument sits out
98 Duration derisk_after{}; // 0: never de-risk
99 Qty derisk_step{}; // base units per de-risk order; 0: the whole residual
102 };
104 struct Stats {
105 std::uint64_t hedges_sent = 0;
106 std::uint64_t hedge_failures = 0; // ended with nothing filled, or refused by the engine
107 std::uint64_t uncertain_ends = 0; // ended without the venue saying how
108 std::uint64_t halts = 0;
109 std::uint64_t hedges_held = 0; // episodes of a hedge no balance or margin could cover
110 std::uint64_t failovers = 0; // hedging moved to a later instrument in the list
111 std::uint64_t benches = 0; // instruments benched after max_failures
112 std::uint64_t derisk_episodes = 0;
113 std::uint64_t derisk_orders = 0;
114 };
116 // What the last evaluation found.
117 enum class State : std::uint8_t {
118 Idle, // not started, or nothing evaluated yet
119 Flat, // the residual is zero
120 Waiting, // the residual rounds under the hedge instrument's minimum
121 InFlight, // a hedge or de-risk order is open
122 Reconciling, // a venue reconciles
123 Holding, // retry backoff or uncertain hold
124 Held, // no usable instrument's balance or margin covers the hedge
125 Unavailable, // no hedge instrument is usable
126 Halted, // every hedge instrument failed max_failures times; restart() clears it
127 };
128
129 // Why a hedge instrument is not usable now.
130 enum class Reason : std::uint8_t { Usable, Down, Killed, NoBook, Stale, Gated, Benched };
131
132 struct Status {
135 std::uint8_t active = 0; // index of the hedge instrument hedges go to
136 bool in_flight = false;
137 bool held = false;
138 bool halted = false;
139 bool derisking = false;
140 Timestamp hold_until{}; // end of the last retry or uncertain hold
141 };
143 // ---- setup (on_start) ---------------------------------------------------------------------
144
145 // Forgets the legs and every state but the stats. Call before adding legs in on_start.
146 void reset() noexcept {
147 n_sources_ = 0;
148 n_hedges_ = 0;
149 ready_ = false;
150 target_ = Qty{};
151 clear_state();
152 }
153 bool add_source(InstrumentId id) noexcept {
154 if (n_sources_ == kMaxSources) return false;
155 sources_[n_sources_++] = Leg{id, Ratio{}, 0};
156 return true;
157 }
158 bool add_hedge(InstrumentId id, Ratio tolerance) noexcept {
159 if (n_hedges_ == kMaxHedges) return false;
160 hedges_[n_hedges_++] = Leg{id, tolerance, 0};
161 return true;
162 }
163 // Checks the legs against the instrument table: each is configured, linear, used once, and there
164 // is at least one of each. Logs the reason and stays idle when not.
165 template <class Ctx>
166 bool start(Ctx& ctx, const Config& cfg) noexcept {
167 cfg_ = cfg;
168 clear_state();
169 ready_ = false;
170 if (n_sources_ == 0 || n_hedges_ == 0) {
171 FASTMM_LOG_ERROR("{}: needs a source and a hedge instrument; not hedging", cfg_.name);
172 return false;
173 }
174 const auto n = ctx.instruments().size();
175 for (std::size_t i = 0; i < n_sources_ + n_hedges_; ++i) {
176 const InstrumentId id = leg(i).id;
177 if (id.value >= n) {
178 FASTMM_LOG_ERROR("{}: instrument {} is not among the {} configured; not hedging",
179 cfg_.name,
180 id.value,
181 n);
182 return false;
183 }
184 const Instrument& inst = ctx.instrument(id);
185 if (inst.inverse() || !inst.contract_multiplier.is_positive()) {
187 "{}: {} is not a linear contract; not hedging", cfg_.name, inst.symbol.view());
188 return false;
189 }
190 for (std::size_t j = 0; j < i; ++j) {
191 if (leg(j).id == id) {
192 FASTMM_LOG_ERROR("{}: {} is named twice; not hedging", cfg_.name, inst.symbol.view());
193 return false;
194 }
195 }
196 }
197 for (std::size_t i = 0; i < n_sources_ + n_hedges_; ++i)
198 venue_of_[i] = ctx.instrument(leg(i).id).venue;
199 ready_ = true;
200 return true;
201 }
202 // Parameters may change while running (on_params); the legs may not.
203 void set_config(const Config& cfg) noexcept { cfg_ = cfg; }
204 void set_tolerance(std::size_t hedge, Ratio tolerance) noexcept {
205 if (hedge < n_hedges_) hedges_[hedge].tolerance = tolerance;
206 }
207 // The net base position the strategy wants to hold; the residual is measured from it.
208 void set_target(Qty base) noexcept { target_ = base; }
209 // Clears a halt, the benches and the failure counts; hedging resumes from the positions.
210 void restart() noexcept {
211 halted_ = false;
212 next_ns_ = 0;
213 for (std::size_t i = 0; i < kMaxHedges; ++i) {
214 failures_[i] = 0;
215 bench_until_[i] = 0;
216 }
217 }
218
219 // ---- hooks: forward the strategy's -----------------------------------------------------------
220
221 // A fill of any leg: the residual moved. False for another instrument.
222 template <class Ctx>
223 bool on_fill(Ctx& ctx, const Fill& fill) noexcept {
224 if (!ready_ || !is_leg(fill.instrument)) return false;
225 update(ctx);
226 return true;
227 }
228
229 // The end of a hedge or de-risk order: counts a failure or holds, then looks again. False for
230 // anything else (another instrument, or an update that is not terminal).
231 template <class Ctx>
232 bool on_order_update(Ctx& ctx, const OmsUpdate& u) noexcept {
233 if (!ready_ || !u.terminal) return false;
234 const Order& o = u.order;
235 const std::int64_t now = ctx.now().ns;
236 if (derisk_open_ && o.cl_ord_id == derisk_id_) {
237 derisk_open_ = false;
238 if (uncertain(u)) hold_uncertain(o, now);
239 update(ctx);
240 return true;
241 }
242 const int k = hedge_index(o.instrument);
243 if (k < 0) return false;
244 if (o.cum_qty.is_zero()) failed(ctx, static_cast<std::size_t>(k), now);
245 if (uncertain(u)) hold_uncertain(o, now);
246 update(ctx);
247 return true;
248 }
249
250 // A hedge instrument's book: a new touch to price against.
251 template <class Ctx>
252 void on_book(Ctx& ctx, InstrumentId id) noexcept {
253 if (ready_ && hedge_index(id) >= 0) update(ctx);
254 }
255
256 template <class Ctx>
257 void on_timer(Ctx& ctx) noexcept {
258 update(ctx);
259 }
260
261 // Order channels (channel != 0) of the legs' venues. Market data is covered by the books'
262 // validity: the engine clears a venue's books when its channel drops, and a book resync is a
263 // Resyncing followed by snapshots with no Live. Does not send: the next event does.
264 template <class Ctx>
265 void on_connection(Ctx&, const ConnectionStateMsg& m) noexcept {
266 if (!ready_ || m.channel == 0) return;
267 const std::uint32_t bit = 1U << (m.channel & 31U);
268 for (std::size_t i = 0; i < n_sources_ + n_hedges_; ++i) {
269 Leg& l = leg(i);
270 if (venue_of_[i] != m.hdr.venue) continue;
271 if (m.state == ConnState::Live) {
272 l.down &= ~bit;
273 } else {
274 l.down |= bit;
275 }
276 }
277 }
278
279 // A balance report of a leg's venue: a held hedge may fit now. False for another venue.
280 template <class Ctx>
281 bool on_balance(Ctx& ctx, const BalanceMsg& m) noexcept {
282 if (!ready_ || !involves(m.hdr.venue)) return false;
283 update(ctx);
284 return true;
285 }
286
287 // Looks at the positions and sends what they call for; the hooks above end here.
288 template <class Ctx>
289 void update(Ctx& ctx) noexcept {
290 if (!ready_) return;
291 const std::int64_t now = ctx.now().ns;
292 if (in_flight(ctx)) {
293 state_ = State::InFlight;
294 return;
295 }
296 if (ctx.reconciling()) {
297 state_ = State::Reconciling;
298 return;
299 }
300 if (now < next_ns_) {
301 state_ = State::Holding;
302 return;
303 }
304 const Qty residual = this->residual(ctx);
305 if (halted_) {
306 state_ = State::Halted;
307 blocked(ctx, residual, now);
308 return;
309 }
310 if (residual.is_zero()) {
311 state_ = State::Flat;
312 split_ = false;
313 unblock("flat");
314 return;
315 }
316 std::size_t first = n_hedges_;
317 for (std::size_t i = 0; i < n_hedges_; ++i) {
318 if (why(ctx, i, now) == Reason::Usable) {
319 first = i;
320 break;
321 }
322 }
323 if (first == n_hedges_) {
324 if (!unavailable_) {
325 unavailable_ = true;
326 FASTMM_LOG_WARN("{}: no hedge instrument can take {} ({} is {})",
327 cfg_.name,
328 residual,
329 ctx.instrument(hedges_[0].id).symbol.view(),
330 to_string(why(ctx, 0, now)));
331 }
332 state_ = State::Unavailable;
333 blocked(ctx, residual, now);
334 return;
335 }
336 // The first usable instrument decides the size; a hedge its balance cannot cover goes to the
337 // next usable instrument that can take it.
338 bool cut = false;
339 const std::optional<NewOrderRequest> sized = order_on(ctx, first, residual, &cut);
340 if (!sized) {
341 state_ = State::Waiting;
342 unblock("the residual is under the hedge minimum");
343 return;
344 }
345 NewOrderRequest req = *sized;
346 std::size_t k = first;
347 if (!covered(ctx, req)) {
348 k = n_hedges_;
349 for (std::size_t j = first + 1; j < n_hedges_; ++j) {
350 if (why(ctx, j, now) != Reason::Usable) continue;
351 bool cut_j = false;
352 const std::optional<NewOrderRequest> r = order_on(ctx, j, residual, &cut_j);
353 if (r && covered(ctx, *r)) {
354 req = *r;
355 k = j;
356 cut = cut_j;
357 break;
358 }
359 }
360 }
361 if (k == n_hedges_) {
362 if (!held_) {
363 held_ = true;
364 ++stats_.hedges_held;
366 "{}: hedge {} {} @ {} held: no usable hedge venue's balance covers it ({} on {}); {} "
367 "unhedged until a balance report or a fill changes that",
368 cfg_.name,
369 req.side,
370 req.qty,
371 req.price,
372 ctx.balance_room(req.instrument, req.side, req.price),
373 ctx.instrument(req.instrument).symbol.view(),
374 residual);
375 }
376 state_ = State::Held;
377 blocked(ctx, residual, now);
378 return;
379 }
380 if (held_) {
381 held_ = false;
382 FASTMM_LOG_INFO("{}: the hedge venue's balance covers the hedge again", cfg_.name);
383 }
384 if (k != active_) switch_to(ctx, k, now);
385 unblock("a hedge instrument takes the residual again");
386 const auto sent = ctx.send(req);
387 if (!sent) {
388 FASTMM_LOG_WARN("{}: hedge {} {} @ {} refused: {}",
389 cfg_.name,
390 req.side,
391 req.qty,
392 req.price,
393 sent.error());
394 failed(ctx, k, now);
395 return;
396 }
397 ++stats_.hedges_sent;
398 state_ = State::InFlight;
399 if (cut && !split_) {
400 split_ = true;
402 "{}: [risk] admits {} {} @ {} of the {} to hedge on {}: it goes out in pieces",
403 cfg_.name,
404 req.side,
405 req.qty,
406 req.price,
407 residual,
408 ctx.instrument(req.instrument).symbol.view());
409 }
410 }
411
412 // ---- state -----------------------------------------------------------------------------------
414 [[nodiscard]] bool ready() const noexcept { return ready_; }
415 [[nodiscard]] bool halted() const noexcept { return halted_; }
416 // The last hedge the positions asked for fits no usable instrument's balance.
417 [[nodiscard]] bool held() const noexcept { return held_; }
418 [[nodiscard]] bool derisking() const noexcept { return derisking_; }
419 [[nodiscard]] State state() const noexcept { return state_; }
420 [[nodiscard]] const Stats& stats() const noexcept { return stats_; }
421 [[nodiscard]] std::size_t hedge_count() const noexcept { return n_hedges_; }
422 [[nodiscard]] InstrumentId hedge_id(std::size_t i) const noexcept { return hedges_[i].id; }
423 [[nodiscard]] std::size_t active() const noexcept { return active_; }
424 [[nodiscard]] Qty target() const noexcept { return target_; }
425 [[nodiscard]] bool is_hedge(InstrumentId id) const noexcept { return hedge_index(id) >= 0; }
426 [[nodiscard]] bool is_source(InstrumentId id) const noexcept {
427 for (std::size_t i = 0; i < n_sources_; ++i) {
428 if (sources_[i].id == id) return true;
429 }
430 return false;
432 [[nodiscard]] bool is_leg(InstrumentId id) const noexcept {
433 return is_source(id) || is_hedge(id);
434 }
435 // A venue of any leg.
436 [[nodiscard]] bool involves(VenueId v) const noexcept {
437 for (std::size_t i = 0; i < n_sources_ + n_hedges_; ++i) {
438 if (venue_of_[i] == v) return true;
439 }
440 return false;
441 }
442
443 // Sources plus hedges minus the target, base units.
444 template <class Ctx>
445 [[nodiscard]] Qty residual(const Ctx& ctx) const noexcept {
446 Qty r = -target_;
447 for (std::size_t i = 0; i < n_sources_ + n_hedges_; ++i) {
448 const InstrumentId id = leg(i).id;
449 r += to_base(ctx.instrument(id), ctx.position(id).qty);
450 }
451 return r;
452 }
453
454 // Why hedge instrument `i` cannot take a hedge now (Reason::Usable: it can).
455 template <class Ctx>
456 [[nodiscard]] Reason why(const Ctx& ctx, std::size_t i, std::int64_t now) const noexcept {
457 const Leg& h = hedges_[i];
458 const VenueId v = venue_of_[n_sources_ + i];
459 if (h.down != 0) return Reason::Down;
460 if (ctx.venue_killed(v)) return Reason::Killed;
461 if (now < bench_until_[i]) return Reason::Benched;
462 const auto& b = ctx.book(h.id);
463 if (!b.is_valid()) return Reason::NoBook;
464 if (cfg_.stale.ns > 0) {
465 const Timestamp t = b.last_update();
466 if (t.valid() && now - t.ns > cfg_.stale.ns) return Reason::Stale;
467 }
468 if (ctx.venue_health(v).gated) return Reason::Gated;
469 return Reason::Usable;
470 }
471
472 // Some hedge instrument can take a hedge now, and hedging is not halted. A quoting strategy pulls
473 // its quotes while this is false: a fill could not be hedged, and a de-risk order may be working.
474 template <class Ctx>
475 [[nodiscard]] bool can_hedge(const Ctx& ctx) const noexcept {
476 if (!ready_ || halted_) return false;
477 const std::int64_t now = ctx.now().ns;
478 for (std::size_t i = 0; i < n_hedges_; ++i) {
479 if (why(ctx, i, now) == Reason::Usable) return true;
480 }
481 return false;
482 }
483
484 template <class Ctx>
485 [[nodiscard]] Status status(const Ctx& ctx) const noexcept {
486 Status s;
487 s.state = state_;
488 s.residual = residual(ctx);
489 s.active = static_cast<std::uint8_t>(active_);
490 s.in_flight = in_flight(ctx);
491 s.held = held_;
492 s.halted = halted_;
493 s.derisking = derisking_;
494 s.hold_until = Timestamp{next_ns_};
495 return s;
496 }
497
498 // ---- pure functions --------------------------------------------------------------------------
500 // Contracts to base units and back (linear: qty * multiplier).
501 [[nodiscard]] static constexpr Qty to_base(const Instrument& inst, Qty contracts) noexcept {
502 return Qty::from_raw(mul_raw(contracts, inst.contract_multiplier));
503 }
504 [[nodiscard]] static constexpr Qty to_contracts(const Instrument& inst, Qty base) noexcept {
505 if (!inst.contract_multiplier.is_positive()) return Qty{};
506 return Qty::from_raw(detail::mul_div(base.raw, kFixedScale, inst.contract_multiplier.raw));
507 }
508
509 // The order on `inst` that brings `residual` (base units) back towards zero: an IOC of
510 // -residual in contracts, rounded down to the lot and capped at max_qty, priced `tolerance`
511 // through the touch and rounded towards it. None when it rounds under the lot or min_qty, its
512 // notional is under min_notional, or the touch it needs is empty: a hedge the venue (or the risk
513 // check) must refuse would only count as a failure.
514 [[nodiscard]] static std::optional<NewOrderRequest> hedge_order(const Instrument& inst,
516 Price best_bid,
517 Price best_ask,
518 Ratio tolerance,
519 std::uint32_t tag) noexcept {
520 if (residual.is_zero()) return std::nullopt;
521 const Side side = residual.is_negative() ? Side::Buy : Side::Sell;
522 Qty qty = inst.round_qty(to_contracts(inst, residual.abs()));
523 if (inst.max_qty.is_positive() && qty > inst.max_qty) qty = inst.round_qty(inst.max_qty);
524 if (!qty.is_positive() || qty < inst.min_qty) return std::nullopt;
525 Price px;
526 if (side == Side::Buy) {
527 if (!best_ask.is_positive()) return std::nullopt;
528 px = inst.round_price(best_ask + best_ask * tolerance, Side::Buy);
529 } else {
530 if (!best_bid.is_positive()) return std::nullopt;
531 px = inst.round_price(best_bid - best_bid * tolerance, Side::Sell);
532 }
533 if (!px.is_positive()) return std::nullopt;
534 if (inst.min_notional.is_positive() && inst.notional(px, qty) < inst.min_notional)
535 return std::nullopt;
536 return NewOrderRequest::limit(inst.id, side, px, qty).ioc().tag(tag);
537 }
538
539 // The largest quantity of `inst` whose notional at `price` is at most `cap`, rounded down to the
540 // lot (zero when not even one lot fits, or `price` is not positive).
541 [[nodiscard]] static constexpr Qty qty_within(const Instrument& inst,
542 Price price,
543 Notional cap) noexcept {
544 const Notional one = inst.notional(price, Qty::from_int(1));
545 if (!one.is_positive() || !cap.is_positive()) return Qty{};
546 Qty q = inst.round_qty(Qty::from_raw(detail::mul_div(cap.raw, kFixedScale, one.raw)));
547 // The notional rounds at each step: never over the cap.
548 while (q.is_positive() && inst.notional(price, q) > cap) q = q - inst.lot;
549 return q;
550 }
551
552 // The largest order of `side` on `inst` at `price` that [risk] admits now: max_order_qty,
553 // max_order_notional at that price, that side's room of max_position and of [risk.underlying]
554 // max_net, and what max_gross_notional and max_net_notional leave for it at that price.
555 // Qty::max() when none of them is set.
556 [[nodiscard]] static constexpr Qty risk_room(const RiskHeadroom& h,
557 const Instrument& inst,
558 Side side,
559 Price price) noexcept {
560 Qty room = h.max_order_qty;
561 room = std::min(room, side == Side::Buy ? h.buy_qty : h.sell_qty);
562 room = std::min(room, side == Side::Buy ? h.underlying_buy_qty : h.underlying_sell_qty);
563 Notional notional = h.max_order_notional;
564 notional =
565 std::min(notional, side == Side::Buy ? h.exposure_buy_notional : h.exposure_sell_notional);
566 if (notional != Notional::max()) room = std::min(room, qty_within(inst, price, notional));
567 return room;
568 }
569
570 // Cuts `r` to risk_room() when that is less and still an order the instrument takes (the lot,
571 // min_qty, min_notional). True when it was cut. An order the limits leave no such room for
572 // stays whole: the engine's refusal is then what the caller counts.
573 [[nodiscard]] static constexpr bool fit_risk(const RiskHeadroom& h,
574 const Instrument& inst,
575 NewOrderRequest& r) noexcept {
576 const Qty room = risk_room(h, inst, r.side, r.price);
577 if (room >= r.qty) return false;
578 const Qty q = inst.round_qty(room);
579 if (!q.is_positive() || q < inst.min_qty) return false;
580 if (inst.min_notional.is_positive() && inst.notional(r.price, q) < inst.min_notional)
581 return false;
582 r.qty = q;
583 return true;
584 }
585
586 // A terminal update whose outcome the venue did not report: the ack timeout cancelled an order
587 // the venue never acknowledged, reconciliation found it gone with quantity unaccounted for, or a
588 // connector turned a request whose outcome it does not know into a generic reject.
589 [[nodiscard]] static bool uncertain(const OmsUpdate& u) noexcept {
590 const Order& o = u.order;
591 return u.unresolved_qty.is_positive() ||
592 (o.state == OrderState::Canceled && o.venue_order_id.empty() && o.cum_qty.is_zero()) ||
595
596 [[nodiscard]] static constexpr std::string_view to_string(Reason r) noexcept {
597 switch (r) {
598 case Reason::Usable:
599 return "usable";
600 case Reason::Down:
601 return "down";
602 case Reason::Killed:
603 return "killed";
604 case Reason::NoBook:
605 return "without a book";
606 case Reason::Stale:
607 return "stale";
608 case Reason::Gated:
609 return "held by the feed-lag gate";
610 case Reason::Benched:
611 return "benched";
612 }
613 return "?";
614 }
615 [[nodiscard]] static constexpr std::string_view to_string(State s) noexcept {
616 switch (s) {
617 case State::Idle:
618 return "idle";
619 case State::Flat:
620 return "flat";
621 case State::Waiting:
622 return "waiting";
623 case State::InFlight:
624 return "in flight";
626 return "reconciling";
627 case State::Holding:
628 return "holding";
629 case State::Held:
630 return "held";
632 return "unavailable";
633 case State::Halted:
634 return "halted";
635 }
636 return "?";
637 }
638
639 private:
640 struct Leg {
641 InstrumentId id{};
642 Ratio tolerance{};
643 std::uint32_t down = 0; // bit per order channel of the venue that is not Live
644 };
645
646 [[nodiscard]] Leg& leg(std::size_t i) noexcept {
647 return i < n_sources_ ? sources_[i] : hedges_[i - n_sources_];
648 }
649 [[nodiscard]] const Leg& leg(std::size_t i) const noexcept {
650 return i < n_sources_ ? sources_[i] : hedges_[i - n_sources_];
651 }
652 [[nodiscard]] int hedge_index(InstrumentId id) const noexcept {
653 for (std::size_t i = 0; i < n_hedges_; ++i) {
654 if (hedges_[i].id == id) return static_cast<int>(i);
655 }
656 return -1;
657 }
658
659 void clear_state() noexcept {
660 for (std::size_t i = 0; i < kMaxSources + kMaxHedges; ++i) venue_of_[i] = VenueId{};
661 for (Leg& l : sources_) l.down = 0;
662 for (Leg& l : hedges_) l.down = 0;
663 next_ns_ = 0;
664 for (std::size_t i = 0; i < kMaxHedges; ++i) {
665 failures_[i] = 0;
666 bench_until_[i] = 0;
667 }
668 active_ = 0;
669 halted_ = false;
670 held_ = false;
671 unavailable_ = false;
672 state_ = State::Idle;
673 blocked_ = false;
674 blocked_ns_ = 0;
675 derisking_ = false;
676 derisk_open_ = false;
677 derisk_id_ = ClientOrderId{};
678 next_derisk_ns_ = 0;
679 derisk_stuck_ = false;
680 split_ = false;
681 }
682
683 // What [risk] admits on `id` now. A context without the risk engine (a test double) has no
684 // limit.
685 template <class Ctx>
686 [[nodiscard]] static RiskHeadroom headroom(const Ctx& ctx, InstrumentId id) noexcept {
687 if constexpr (requires { ctx.risk_headroom(id); }) {
688 return ctx.risk_headroom(id);
689 } else {
690 return RiskHeadroom{};
691 }
692 }
693
694 // The hedge of `residual` on hedge instrument `i`, priced from its book and cut to what [risk]
695 // admits (`cut` says whether it was).
696 template <class Ctx>
697 [[nodiscard]] std::optional<NewOrderRequest> order_on(const Ctx& ctx,
698 std::size_t i,
700 bool* cut) const noexcept {
701 const Leg& h = hedges_[i];
702 const auto& b = ctx.book(h.id);
703 const Instrument& inst = ctx.instrument(h.id);
704 std::optional<NewOrderRequest> r =
705 hedge_order(inst, residual, b.best_bid().price, b.best_ask().price, h.tolerance, cfg_.tag);
706 if (r) *cut = fit_risk(headroom(ctx, h.id), inst, *r);
707 return r;
708 }
709 // The venue's balance or margin covers `r` (StrategyContext::balance_room).
710 template <class Ctx>
711 [[nodiscard]] static bool covered(const Ctx& ctx, const NewOrderRequest& r) noexcept {
712 return ctx.balance_room(r.instrument, r.side, r.price) >= r.qty;
713 }
714
715 template <class Ctx>
716 [[nodiscard]] bool in_flight(const Ctx& ctx) const noexcept {
717 if (derisk_open_) return true;
718 for (std::size_t i = 0; i < n_hedges_; ++i) {
719 const InstrumentId id = hedges_[i].id;
720 if (ctx.open_qty(id, Side::Buy).is_positive() || ctx.open_qty(id, Side::Sell).is_positive())
721 return true;
722 }
723 return false;
724 }
725
726 void hold_uncertain(const Order& o, std::int64_t now) noexcept {
727 ++stats_.uncertain_ends;
728 const std::int64_t until = now + cfg_.uncertain_hold.ns;
729 if (until > next_ns_) next_ns_ = until;
731 "{}: order {} ended without the venue saying how; next order in {} ms at the earliest",
732 cfg_.name,
733 encode_cl_ord_id(o.cl_ord_id),
734 cfg_.uncertain_hold.millis());
735 }
736
737 template <class Ctx>
738 void switch_to(const Ctx& ctx, std::size_t k, std::int64_t now) noexcept {
739 const std::size_t from = active_;
740 active_ = k;
741 const InstrumentId a = hedges_[from].id;
742 const InstrumentId b = hedges_[k].id;
743 if (k > from) {
744 ++stats_.failovers;
745 const Reason r = why(ctx, from, now);
747 "{}: hedging moves from {} (instrument {}) to {} (instrument {}): the first is {}",
748 cfg_.name,
749 ctx.instrument(a).symbol.view(),
750 a.value,
751 ctx.instrument(b).symbol.view(),
752 b.value,
753 r == Reason::Usable ? std::string_view{"short of balance"} : to_string(r));
754 } else {
755 FASTMM_LOG_WARN("{}: hedging back on {} (instrument {}) from {} (instrument {})",
756 cfg_.name,
757 ctx.instrument(b).symbol.view(),
758 b.value,
759 ctx.instrument(a).symbol.view(),
760 a.value);
761 }
762 }
763
764 template <class Ctx>
765 void failed(Ctx& ctx, std::size_t k, std::int64_t now) noexcept {
766 ++stats_.hedge_failures;
767 next_ns_ = now + cfg_.retry.ns;
768 const auto n = static_cast<std::size_t>(cfg_.max_failures);
769 std::size_t& count = failures_[k];
770 fail_ns_[k][count % kMaxFailures] = now;
771 ++count;
772 if (halted_ || count < n) return;
773 const std::int64_t oldest = fail_ns_[k][(count - n) % kMaxFailures];
774 if (now - oldest > cfg_.failure_window.ns) return;
775 bool other = false;
776 for (std::size_t i = 0; i < n_hedges_; ++i) {
777 if (i != k && now >= bench_until_[i]) other = true;
778 }
779 const std::string_view sym = ctx.instrument(hedges_[k].id).symbol.view();
780 if (other) {
781 bench_until_[k] = now + cfg_.bench.ns;
782 count = 0;
783 ++stats_.benches;
785 "{}: {} hedges on {} (instrument {}) filled nothing within {} ms: it sits "
786 "out {} ms",
787 cfg_.name,
788 n,
789 sym,
790 hedges_[k].id.value,
791 cfg_.failure_window.millis(),
792 cfg_.bench.millis());
793 return;
794 }
795 halted_ = true;
796 ++stats_.halts;
798 "{}: {} hedges on {} filled nothing within {} ms: hedging stopped with {} unhedged; "
799 "restart to resume",
800 cfg_.name,
801 n,
802 sym,
803 cfg_.failure_window.millis(),
804 residual(ctx));
805 }
806
807 // ---- de-risking ----------------------------------------------------------------------------
808
809 // No hedge instrument took the residual: after derisk_after, reduce a source instead.
810 template <class Ctx>
811 void blocked(Ctx& ctx, Qty residual, std::int64_t now) noexcept {
812 if (cfg_.derisk_after.ns <= 0 || residual.is_zero()) return;
813 if (!blocked_) {
814 blocked_ = true;
815 blocked_ns_ = now;
816 }
817 if (now - blocked_ns_ < cfg_.derisk_after.ns) return;
818 if (!derisking_) {
819 derisking_ = true;
820 derisk_stuck_ = false;
821 ++stats_.derisk_episodes;
823 "{}: no hedge instrument has taken {} for {} ms: de-risking the sources in steps of at "
824 "most {}",
825 cfg_.name,
826 residual,
827 cfg_.derisk_after.millis(),
828 cfg_.derisk_step);
829 }
830 if (now < next_derisk_ns_) return;
831 derisk_step(ctx, residual, now);
832 }
833
834 // A hedge instrument takes the residual, or there is none to take.
835 void unblock(std::string_view why) noexcept {
836 unavailable_ = false;
837 blocked_ = false;
838 if (!derisking_) return;
839 derisking_ = false;
840 FASTMM_LOG_WARN("{}: de-risking ends: {}", cfg_.name, why);
841 }
842
843 template <class Ctx>
844 void derisk_step(Ctx& ctx, Qty residual, std::int64_t now) noexcept {
845 const Side side = residual.is_positive() ? Side::Sell : Side::Buy;
846 for (std::size_t i = 0; i < n_sources_; ++i) {
847 const Leg& s = sources_[i];
848 const Instrument& inst = ctx.instrument(s.id);
849 const Qty pos = to_base(inst, ctx.position(s.id).qty);
850 // Only a position on the residual's side is reduced.
851 if (pos.is_zero() || pos.is_negative() != residual.is_negative()) continue;
852 if (s.down != 0 || ctx.venue_killed(inst.venue)) continue;
853 const auto& b = ctx.book(s.id);
854 if (!b.is_valid()) continue;
855 Qty base = residual.abs();
856 if (pos.abs() < base) base = pos.abs();
857 if (cfg_.derisk_step.is_positive() && cfg_.derisk_step < base) base = cfg_.derisk_step;
858 std::optional<NewOrderRequest> r = hedge_order(inst,
859 side == Side::Sell ? base : -base,
860 b.best_bid().price,
861 b.best_ask().price,
862 cfg_.derisk_tolerance,
863 cfg_.derisk_tag);
864 // A reduce-only derivative order passes the engine's balance check; spot sells need base.
865 if (!r) continue;
866 if (!inst.is_derivative() && ctx.balance_room(s.id, r->side, r->price) < r->qty) continue;
867 NewOrderRequest req = *r;
868 req.reduce_only = true;
869 static_cast<void>(fit_risk(headroom(ctx, s.id), inst, req));
870 next_derisk_ns_ = now + cfg_.derisk_interval.ns;
871 const auto sent = ctx.send(req);
872 if (!sent) {
873 FASTMM_LOG_WARN("{}: de-risk {} {} {} @ {} refused: {}",
874 cfg_.name,
875 inst.symbol.view(),
876 req.side,
877 req.qty,
878 req.price,
879 sent.error());
880 return;
881 }
882 derisk_open_ = true;
883 derisk_id_ = *sent;
884 derisk_stuck_ = false;
885 ++stats_.derisk_orders;
886 return;
887 }
888 if (!derisk_stuck_) {
889 derisk_stuck_ = true;
890 FASTMM_LOG_WARN("{}: no source can take a de-risk order for {} now", cfg_.name, residual);
891 }
892 }
893
894 Config cfg_{};
895 std::array<Leg, kMaxSources> sources_{};
896 std::array<Leg, kMaxHedges> hedges_{};
897 std::array<VenueId, kMaxSources + kMaxHedges> venue_of_{};
898 std::size_t n_sources_ = 0;
899 std::size_t n_hedges_ = 0;
900 Qty target_{};
901 std::int64_t next_ns_ = 0; // retry backoff or uncertain hold
902 std::array<std::size_t, kMaxHedges> failures_{};
903 std::array<std::array<std::int64_t, kMaxFailures>, kMaxHedges> fail_ns_{};
904 std::array<std::int64_t, kMaxHedges> bench_until_{};
905 std::size_t active_ = 0;
906 Stats stats_{};
907 State state_ = State::Idle;
908 bool ready_ = false;
909 bool halted_ = false;
910 bool held_ = false;
911 bool unavailable_ = false; // logged once until a hedge instrument takes the residual again
912 bool split_ = false; // a hedge cut to a [risk] limit was logged; cleared when flat
913 // de-risking
914 bool blocked_ = false;
915 bool derisking_ = false;
916 bool derisk_open_ = false;
917 bool derisk_stuck_ = false;
918 std::int64_t blocked_ns_ = 0;
919 std::int64_t next_derisk_ns_ = 0;
920 ClientOrderId derisk_id_{};
921};
922
923} // namespace fastmm
bool add_hedge(InstrumentId id, Ratio tolerance) noexcept
Definition hedge_executor.hpp:159
std::size_t active() const noexcept
Definition hedge_executor.hpp:422
bool involves(VenueId v) const noexcept
A venue of any leg.
Definition hedge_executor.hpp:435
static constexpr bool fit_risk(const RiskHeadroom &h, const Instrument &inst, NewOrderRequest &r) noexcept
Cuts r to risk_room() when that is less and still an order the instrument takes (the lot,...
Definition hedge_executor.hpp:571
State state() const noexcept
Definition hedge_executor.hpp:418
void update(Ctx &ctx) noexcept
Looks at the positions and sends what they call for; the hooks above end here.
Definition hedge_executor.hpp:289
Qty residual(const Ctx &ctx) const noexcept
Sources plus hedges minus the target, base units.
Definition hedge_executor.hpp:444
bool is_leg(InstrumentId id) const noexcept
Definition hedge_executor.hpp:431
bool on_fill(Ctx &ctx, const Fill &fill) noexcept
A fill of any leg: the residual moved. False for another instrument.
Definition hedge_executor.hpp:223
static constexpr std::size_t kMaxSources
Definition hedge_executor.hpp:86
void on_timer(Ctx &ctx) noexcept
Definition hedge_executor.hpp:257
void restart() noexcept
Clears a halt, the benches and the failure counts; hedging resumes from the positions.
Definition hedge_executor.hpp:211
InstrumentId hedge_id(std::size_t i) const noexcept
Definition hedge_executor.hpp:421
bool ready() const noexcept
Definition hedge_executor.hpp:413
void set_config(const Config &cfg) noexcept
Parameters may change while running (on_params); the legs may not.
Definition hedge_executor.hpp:204
Status status(const Ctx &ctx) const noexcept
Definition hedge_executor.hpp:484
bool on_order_update(Ctx &ctx, const OmsUpdate &u) noexcept
The end of a hedge or de-risk order: counts a failure or holds, then looks again.
Definition hedge_executor.hpp:232
bool can_hedge(const Ctx &ctx) const noexcept
Some hedge instrument can take a hedge now, and hedging is not halted.
Definition hedge_executor.hpp:474
bool start(Ctx &ctx, const Config &cfg) noexcept
Checks the legs against the instrument table: each is configured, linear, used once,...
Definition hedge_executor.hpp:167
static constexpr Qty to_base(const Instrument &inst, Qty contracts) noexcept
Contracts to base units and back (linear: qty * multiplier).
Definition hedge_executor.hpp:499
Qty target() const noexcept
Definition hedge_executor.hpp:423
static constexpr Qty risk_room(const RiskHeadroom &h, const Instrument &inst, Side side, Price price) noexcept
The largest order of side on inst at price that [risk] admits now: max_order_qty, max_order_notional ...
Definition hedge_executor.hpp:554
static constexpr std::string_view to_string(Reason r) noexcept
Definition hedge_executor.hpp:594
const Stats & stats() const noexcept
Definition hedge_executor.hpp:419
bool is_source(InstrumentId id) const noexcept
Definition hedge_executor.hpp:425
bool held() const noexcept
The last hedge the positions asked for fits no usable instrument's balance.
Definition hedge_executor.hpp:416
static constexpr std::size_t kMaxHedges
Definition hedge_executor.hpp:87
static bool uncertain(const OmsUpdate &u) noexcept
A terminal update whose outcome the venue did not report: the ack timeout cancelled an order the venu...
Definition hedge_executor.hpp:587
void reset() noexcept
Forgets the legs and every state but the stats. Call before adding legs in on_start.
Definition hedge_executor.hpp:147
std::size_t hedge_count() const noexcept
Definition hedge_executor.hpp:420
void set_target(Qty base) noexcept
The net base position the strategy wants to hold; the residual is measured from it.
Definition hedge_executor.hpp:209
bool on_balance(Ctx &ctx, const BalanceMsg &m) noexcept
A balance report of a leg's venue: a held hedge may fit now. False for another venue.
Definition hedge_executor.hpp:281
bool halted() const noexcept
Definition hedge_executor.hpp:414
Reason why(const Ctx &ctx, std::size_t i, std::int64_t now) const noexcept
Why hedge instrument i cannot take a hedge now (Reason::Usable: it can).
Definition hedge_executor.hpp:455
void on_connection(Ctx &, const ConnectionStateMsg &m) noexcept
Order channels (channel != 0) of the legs' venues.
Definition hedge_executor.hpp:265
bool derisking() const noexcept
Definition hedge_executor.hpp:417
State
What the last evaluation found.
Definition hedge_executor.hpp:119
@ Holding
retry backoff or uncertain hold
@ Reconciling
a venue reconciles
@ Unavailable
no hedge instrument is usable
@ Waiting
the residual rounds under the hedge instrument's minimum
@ Halted
every hedge instrument failed max_failures times; restart() clears it
@ Flat
the residual is zero
@ InFlight
a hedge or de-risk order is open
@ Idle
not started, or nothing evaluated yet
@ Held
no usable instrument's balance or margin covers the hedge
static constexpr std::size_t kMaxFailures
Definition hedge_executor.hpp:88
bool is_hedge(InstrumentId id) const noexcept
Definition hedge_executor.hpp:424
bool add_source(InstrumentId id) noexcept
Definition hedge_executor.hpp:154
static std::optional< NewOrderRequest > hedge_order(const Instrument &inst, Qty residual, Price best_bid, Price best_ask, Ratio tolerance, std::uint32_t tag) noexcept
The order on inst that brings residual (base units) back towards zero: an IOC of -residual in contrac...
Definition hedge_executor.hpp:512
Reason
Why a hedge instrument is not usable now.
Definition hedge_executor.hpp:132
void on_book(Ctx &ctx, InstrumentId id) noexcept
A hedge instrument's book: a new touch to price against.
Definition hedge_executor.hpp:252
static constexpr Qty to_contracts(const Instrument &inst, Qty base) noexcept
Definition hedge_executor.hpp:502
static constexpr Qty qty_within(const Instrument &inst, Price price, Notional cap) noexcept
The largest quantity of inst whose notional at price is at most cap, rounded down to the lot (zero wh...
Definition hedge_executor.hpp:539
void set_tolerance(std::size_t hedge, Ratio tolerance) noexcept
Definition hedge_executor.hpp:205
All engine enumerations.
Fixed<Tag>: int64 fixed point with a global 1e-8 scale (ADR-0001).
Strategy hooks (ADR-0012): the hook table, the compile-time checker and the Fill view.
Instrument: the single definition of a tradable contract (5.3 / 5.15).
Asynchronous logging (5.10).
#define FASTMM_LOG_WARN(fmt_str,...)
Definition log.hpp:336
#define FASTMM_LOG_INFO(fmt_str,...)
Definition log.hpp:334
#define FASTMM_LOG_ERROR(fmt_str,...)
Definition log.hpp:338
Event bus message structs (5.5).
Definition strategy.hpp:16
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
constexpr Duration milliseconds(std::int64_t v) noexcept
Definition time.hpp:38
constexpr std::int64_t kFixedScale
1e-8
Definition fixed_point.hpp:23
@ VenueReject
Venue-originated.
Side
Definition enums.hpp:12
Fixed< struct QtyTag > Qty
Definition fixed_point.hpp:333
StrongId< struct InstrumentTag, std::uint32_t > InstrumentId
Dense index into the engine's instrument table.
Definition strong_id.hpp:38
FixedString< 16 > encode_cl_ord_id(ClientOrderId id) noexcept
Definition strong_id.hpp:77
constexpr std::int64_t mul_raw(Fixed< A > a, Fixed< B > b) noexcept
Generic scaled multiply for fixed*fixed in one domain (e.g. qty * multiplier).
Definition fixed_point.hpp:367
Fixed< RatioTag > Ratio
A dimensionless factor: 1.0 == 1e8 raw, 1 bp == 10'000 raw, so 0.0001 bp is the smallest step.
Definition fixed_point.hpp:336
StrongId< struct VenueTag, std::uint8_t > VenueId
Definition strong_id.hpp:39
Order management (5.8): a Pool<Order> + OpenHashMap<ClientOrderId, Handle> + a ring of recently termi...
Order: the OMS's 128-byte record for one order across its lifetime.
RiskLimits: the pre-trade limits a session runs with ([risk] in the configuration),...
The account's holding of one asset on one venue (EventType::Balance), as the venue reports it at its ...
Definition messages.hpp:315
Definition messages.hpp:452
Definition time.hpp:22
std::int64_t ns
Definition time.hpp:23
constexpr std::int64_t millis() const noexcept
Definition time.hpp:29
One execution as a strategy sees it (on_fill).
Definition hooks.hpp:56
constexpr bool is_negative() const noexcept
Definition fixed_point.hpp:303
static constexpr Fixed max() noexcept
Definition fixed_point.hpp:165
constexpr Fixed abs() const noexcept
Definition fixed_point.hpp:304
static constexpr Fixed from_raw(std::int64_t r) noexcept
Definition fixed_point.hpp:155
constexpr bool is_zero() const noexcept
Definition fixed_point.hpp:301
std::int64_t raw
Definition fixed_point.hpp:152
constexpr bool is_positive() const noexcept
Definition fixed_point.hpp:302
static constexpr Fixed from_int(std::int64_t units) noexcept
Whole units, e.g. from_int(100) == 100.00000000.
Definition fixed_point.hpp:161
Definition hedge_executor.hpp:90
Duration stale
a hedge book older than this is unusable; 0: never
Definition hedge_executor.hpp:98
Duration failure_window
Definition hedge_executor.hpp:96
Duration uncertain_hold
Definition hedge_executor.hpp:97
Qty derisk_step
base units per de-risk order; 0: the whole residual
Definition hedge_executor.hpp:101
int max_failures
within failure_window: bench, or halt on the last one
Definition hedge_executor.hpp:95
std::uint32_t tag
user_tag of hedge orders
Definition hedge_executor.hpp:92
Duration bench
how long a failing instrument sits out
Definition hedge_executor.hpp:99
std::uint32_t derisk_tag
user_tag of de-risk orders
Definition hedge_executor.hpp:93
Duration derisk_interval
Definition hedge_executor.hpp:102
Duration derisk_after
0: never de-risk
Definition hedge_executor.hpp:100
Ratio derisk_tolerance
Definition hedge_executor.hpp:103
Duration retry
after a hedge that filled nothing
Definition hedge_executor.hpp:94
std::string_view name
log prefix
Definition hedge_executor.hpp:91
Definition hedge_executor.hpp:106
std::uint64_t failovers
hedging moved to a later instrument in the list
Definition hedge_executor.hpp:112
std::uint64_t hedges_sent
Definition hedge_executor.hpp:107
std::uint64_t benches
instruments benched after max_failures
Definition hedge_executor.hpp:113
std::uint64_t derisk_orders
Definition hedge_executor.hpp:115
std::uint64_t halts
Definition hedge_executor.hpp:110
std::uint64_t hedges_held
episodes of a hedge no balance or margin could cover
Definition hedge_executor.hpp:111
std::uint64_t uncertain_ends
ended without the venue saying how
Definition hedge_executor.hpp:109
std::uint64_t derisk_episodes
Definition hedge_executor.hpp:114
std::uint64_t hedge_failures
ended with nothing filled, or refused by the engine
Definition hedge_executor.hpp:108
Definition hedge_executor.hpp:134
Timestamp hold_until
end of the last retry or uncertain hold
Definition hedge_executor.hpp:142
bool held
Definition hedge_executor.hpp:139
std::uint8_t active
index of the hedge instrument hedges go to
Definition hedge_executor.hpp:137
bool halted
Definition hedge_executor.hpp:140
bool in_flight
Definition hedge_executor.hpp:138
bool derisking
Definition hedge_executor.hpp:141
Qty residual
Definition hedge_executor.hpp:136
State state
Definition hedge_executor.hpp:135
Definition instrument.hpp:29
constexpr LimitOrder tag(std::uint32_t user_tag) const noexcept
user_tag: echoed in the order record.
Definition order.hpp:112
constexpr LimitOrder ioc() const noexcept
Definition order.hpp:105
Definition order.hpp:66
Side side
Definition order.hpp:73
Price price
Definition order.hpp:78
static constexpr LimitOrder limit(InstrumentId instrument, Side side, Price price, Qty qty) noexcept
A GTC limit order, refined with the builder's methods: ctx.send(NewOrderRequest::limit(id,...
Definition order.hpp:126
InstrumentId instrument
Definition order.hpp:67
Qty qty
Definition order.hpp:79
Definition oms.hpp:74
Definition order.hpp:15
OrderState state
1
Definition order.hpp:34
VenueOrderId venue_order_id
41 -> 49
Definition order.hpp:27
ClientOrderId cl_ord_id
8
Definition order.hpp:26
InstrumentId instrument
4 -> 60
Definition order.hpp:29
RejectReason reject_reason
1
Definition order.hpp:35
Qty cum_qty
8 -> 96
Definition order.hpp:41
How much more each limit admits for one instrument now: the largest order (or number of orders) that ...
Definition risk_limits.hpp:50
Definition time.hpp:45
std::int64_t ns
Definition time.hpp:46
constexpr bool valid() const noexcept
Definition time.hpp:55
Time primitives.
VenueHealth: per venue, how late its market data arrives and how long our orders take to be acknowled...