FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
strategy_context.hpp
Go to the documentation of this file.
1#pragma once
2// StrategyContext: the API a strategy sees (ADR-0012, section 2). A thin, non-owning facade over
3// the Engine, so strategies never include engine.hpp and cannot reach into internals. Every
4// order-API call marks the strategy's decision time for the serialize latency interval.
5#include "fastmm/core/account_pool.hpp"
6#include "fastmm/core/balance_book.hpp"
8#include "fastmm/core/config_macros.hpp"
10#include "fastmm/core/fees.hpp"
13#include "fastmm/core/oms.hpp"
14#include "fastmm/core/order.hpp"
15#include "fastmm/core/order_budget.hpp"
16#include "fastmm/core/perp_book.hpp"
21#include "fastmm/core/rng.hpp"
23#include "fastmm/core/time.hpp"
25
26#include <cstdint>
27#include <optional>
28#include <string_view>
29
30namespace fastmm {
31
32template <class Engine>
33class StrategyContext {
34 public:
35 using Book = typename Engine::Book;
36
37 explicit StrategyContext(Engine* e) noexcept : e_(e) {}
38
39 // ---- time and reference data --------------------------------------------------------------
40
41 [[nodiscard]] Timestamp now() const noexcept { return e_->now(); }
42 [[nodiscard]] const Instrument& instrument(InstrumentId id) const noexcept {
43 return e_->instrument(id);
44 }
45 [[nodiscard]] const InstrumentTable& instruments() const noexcept { return e_->instruments(); }
46 [[nodiscard]] bool contains(InstrumentId id) const noexcept {
47 return e_->instruments().contains(id);
48 }
49
50 // ---- market data --------------------------------------------------------------------------
51
52 [[nodiscard]] const Book& book(InstrumentId id) const noexcept { return e_->book(id); }
53
54 // Our resting quantity that the venue's feed shows at (id, side, px): the book and the tickers of
55 // a live venue include our own orders. As of the book's last update, or of venue time `at` (a
56 // BookTicker's exch_ts). An order counts from its ack's venue time to its end's, less its fills,
57 // so a depth update stamped before our cancel still includes it. Zero in the simulator, whose
58 // feed does not contain our orders.
59 [[nodiscard]] Qty own_qty(InstrumentId id, Side side, Price px) const noexcept {
60 return e_->own_qty(id, side, px);
61 }
62 [[nodiscard]] Qty own_qty(InstrumentId id, Side side, Price px, Timestamp at) const noexcept {
63 return e_->own_qty(id, side, px, at);
64 }
65 // The book's best level on one side with own_qty taken out; a level that was only ours is
66 // skipped. Level{} when none is left.
67 [[nodiscard]] Level best_ex_self(InstrumentId id, Side side) const noexcept {
68 return e_->best_ex_self(id, side);
69 }
70
71 // ---- portfolio ----------------------------------------------------------------------------
73 [[nodiscard]] const Position& position(InstrumentId id) const noexcept {
74 return e_->position(id);
75 }
76 // PnL totals over every instrument (a loop over all positions; not for every event).
77 [[nodiscard]] Portfolio portfolio() const noexcept { return e_->positions().portfolio(); }
78
79 // ---- quoting: the QuoteManager diffs the desired ladder against working orders --------------
80
81 // Returns false when the quotes were ignored: quoting is disabled (control pull, kill switch,
82 // reconciliation; on_quoting reports the changes) or the instrument is not in the table.
83 bool set_quotes(InstrumentId id, const DesiredQuotes& q) noexcept {
84 e_->mark_decision();
85 return e_->set_quotes(id, q);
86 }
87 void pull_quotes(InstrumentId id) noexcept {
88 e_->mark_decision();
89 e_->pull_quotes(id);
90 }
91 // Why the strategy asks what it asks on a side, for the operator: a short reason
92 // ("inventory_cap", "model_off"; empty clears it, at most 23 bytes kept) and the budget it gave
93 // the side in the settlement currency. The status file shows it next to what the engine finds
94 // in the way (core/quote_diag.hpp); the store records each change. No allocation.
96 Side side,
97 std::string_view reason,
98 Notional budget = {}) noexcept {
99 e_->note_quote(id, side, reason, budget);
100 }
101 // A number the strategy publishes by name, for the status file (a budget split, a model's
102 // state): the first 32 names are kept, a name at most 23 bytes. No allocation.
103 void metric(std::string_view name, double value) noexcept { e_->metric(name, value); }
104 void pull_all_quotes() noexcept {
105 e_->mark_decision();
106 e_->pull_all_quotes();
108 // The open order in a quote slot (level 0 is closest to the mid), or nullptr.
109 [[nodiscard]] const Order* working_quote(InstrumentId id,
110 Side side,
111 std::uint32_t level = 0) const noexcept {
112 if (!contains(id) || level >= kMaxQuoteLevels) return nullptr;
113 const Handle<Order> h = e_->quote_manager().slot_handle(id, side, level);
114 const Oms& oms = e_->oms();
115 return h.valid() && oms.is_live(h) ? &oms.get(h) : nullptr;
116 }
117
118 // ---- direct orders (risk-checked, journaled) ------------------------------------------------
119
120 // RejectReason::InvalidTag: user_tag lies in the QuoteManager's tag range.
121 [[nodiscard]] Result<ClientOrderId, RejectReason> send(const NewOrderRequest& req) noexcept {
122 e_->mark_decision();
123 if (FASTMM_UNLIKELY(QuoteManager::is_quote_tag(req.user_tag))) {
126 return e_->send_order(req);
127 }
128 [[nodiscard]] Result<void, RejectReason> cancel(ClientOrderId id) noexcept {
129 e_->mark_decision();
130 return e_->cancel_order(id);
131 }
132 [[nodiscard]] Result<void, RejectReason> replace(ClientOrderId id, Price px, Qty qty) noexcept {
133 e_->mark_decision();
134 return e_->replace_order(id, px, qty);
135 }
136 // An open order by id, or nullptr once it is terminal. Valid until the next context call.
137 [[nodiscard]] const Order* order(ClientOrderId id) const noexcept {
138 const Oms& oms = e_->oms();
139 const Handle<Order> h = oms.find(id);
140 return h.valid() ? &oms.get(h) : nullptr;
141 }
142 // Estimated quantity resting ahead of an open order at its price: the l2_queue fill model's
143 // queue model on the market data the strategy sees, with [engine] queue_conservatism, from the
144 // order's ack. nullopt while it is not acknowledged or once it is terminal. The engine tracks
145 // queues from the first call on (call it in on_start to cover every order from its ack); an
146 // order already resting at the first call starts at the back of its level as the book shows it.
147 [[nodiscard]] std::optional<Qty> queue_ahead(ClientOrderId id) const noexcept {
148 return e_->queue_ahead(id);
150 // Send and ack times of an open order, or nullptr (OmsUpdate::times has them in on_order_update
151 // and, through Fill::update, in on_fill).
152 [[nodiscard]] const OrderTimes* order_times(ClientOrderId id) const noexcept {
153 const Oms& oms = e_->oms();
154 const Handle<Order> h = oms.find(id);
155 return h.valid() ? &oms.times(h) : nullptr;
156 }
157 // Unfilled quantity of our open orders (quotes included) on one side.
158 [[nodiscard]] Qty open_qty(InstrumentId id, Side side) const noexcept {
159 return e_->oms().open_qty(id, side);
160 }
161 [[nodiscard]] const Oms& oms() const noexcept { return e_->oms(); }
162
163 // ---- timers: journaled when they fire, so replay reproduces them ---------------------------
165 [[nodiscard]] TimerId every(Duration period, std::uint64_t tag = 0) noexcept {
166 return e_->add_timer(period, true, tag);
168 [[nodiscard]] TimerId once(Duration delay, std::uint64_t tag = 0) noexcept {
169 return e_->add_timer(delay, false, tag);
171 bool cancel_timer(TimerId id) noexcept { return e_->cancel_timer(id); }
172
173 // ---- control --------------------------------------------------------------------------------
175 [[nodiscard]] bool quoting_enabled() const noexcept { return e_->quoting_enabled(); }
176 // A venue's orders and position are being reconciled, or have not been yet since the session
177 // started (a live session waits for every venue that replays executions; new orders are refused
178 // with NotReconciled until then). Positions may be missing fills while this holds.
179 [[nodiscard]] bool reconciling() const noexcept { return e_->reconciling(); }
180 [[nodiscard]] bool killed() const noexcept { return e_->risk().killed(); }
181 // One venue's kill switch: new orders to it are refused and set_quotes ignores its instruments.
182 [[nodiscard]] bool venue_killed(VenueId v) const noexcept { return e_->risk().venue_killed(v); }
183 void request_stop() noexcept { e_->stop(); }
184 // Asks for one on_batch_end call once the engine has handled every event that is waiting now
185 // (the feed is empty or the step's max_events_per_step is used up). Work a burst of events would
186 // each trigger (a requote after every balance report) is done once, after the burst. Repeated
187 // requests before the call make one call.
188 void request_batch_end() noexcept { e_->request_batch_end(); }
189 // Trips the global kill switch: quoting stops, quotes are pulled and every working order is
190 // cancelled. The adapter for Python hot hooks uses KillReason::StrategyError.
191 void trip_kill(KillReason reason) noexcept { e_->trip_kill(reason); }
193 // ---- venue state: fees, risk headroom, venue health -------------------------------------------
194
195 // Maker and taker rates of the instrument, in cbps of the notional (positive: a fee). The
196 // configuration's ([[instruments]] maker_bps / taker_bps, else [venues.<x>.fees]), or the
197 // account's own where the connector fetched them (fetch_fees). A backtest charges these.
198 [[nodiscard]] const FeeRates& fees(InstrumentId id) const noexcept { return e_->fees(id); }
199 // What each [risk] limit still admits on the instrument now: the next check uses the same
200 // inputs, so an order of exactly a room passes that limit. RiskHeadroom::kUnlimited (or the
201 // type's max()) where the limit is off.
202 [[nodiscard]] RiskHeadroom risk_headroom(InstrumentId id) const noexcept {
203 return e_->risk_headroom(id);
204 }
205 // Feed lag and order round trip of a venue (core/venue_health.hpp), and whether the feed-lag
206 // gate ([risk] max_feed_lag_ms) holds it now.
207 [[nodiscard]] VenueHealthView venue_health(VenueId v) const noexcept {
208 return e_->venue_health(v);
209 }
210 // What the [risk] rate limit and the venue's own limits still admit (core/order_budget.hpp):
211 // the local token bucket, and the venue's order counts and request weight by window as its
212 // connector last published them (live only; venue_known is false elsewhere).
213 [[nodiscard]] OrderBudget order_budget(VenueId v) const noexcept { return e_->order_budget(v); }
214
215 // ---- balances (core/balance_book.hpp) ---------------------------------------------------------
216
217 // One asset of a venue's account: the venue's last report (on_balance) less what this engine's
218 // orders hold and its fills moved since. Balance::known is false until the venue reports it, and
219 // for an asset no instrument of the venue names.
220 [[nodiscard]] Balance balance(VenueId v, std::string_view asset) const noexcept {
221 return e_->balance(v, asset);
222 }
223 // The venue's margin: its account-wide margin where it reports one, else the settlement asset of
224 // its first derivative.
225 [[nodiscard]] Margin margin(VenueId v) const noexcept { return e_->margin(v); }
226 // A venue has reported balances (before that, balance() is unknown everywhere).
227 [[nodiscard]] bool balances_live() const noexcept { return e_->balances_live(); }
228 // Largest quantity of `id` on `side` at `px` the balance covers (a spot buy's quote with the
229 // taker fee, a spot sell's base, a derivative's initial margin), rounded down to the lot.
230 // Qty::max() while the venue has not reported the balance that side draws on.
231 [[nodiscard]] Qty balance_room(InstrumentId id, Side side, Price px) const noexcept {
232 return e_->balance_room(id, side, px);
233 }
234 // ... on one account of the instrument's pool (its own venue when `account` is not one).
235 [[nodiscard]] Qty balance_room(InstrumentId id,
236 Side side,
237 Price px,
238 VenueId account) const noexcept {
239 return e_->balance_room(id, side, px, account);
241
242 // ---- account pools (core/account_pool.hpp) ---------------------------------------------------
243
244 // The accounts that take orders for a venue's instruments ([venues.<x>] pool_of), the primary
245 // first; just the venue itself without a pool. Each has its own balance(), margin() and
246 // order_budget(); an order goes to the one NewOrderRequest::account names, else to the usable
247 // member whose balance covers it with the most order window left, and Order::venue keeps it.
248 [[nodiscard]] PoolMembers pool(VenueId primary) const noexcept { return e_->pool(primary); }
249 // The venue whose instruments `v` takes orders for: its primary, or `v` itself.
250 [[nodiscard]] VenueId pool_primary(VenueId v) const noexcept { return e_->pool_primary(v); }
251 // The automatic routing would send to `v` now: its order link is live and its kill switch is
252 // not engaged (its balance and window are the order's to decide).
253 [[nodiscard]] bool account_usable(VenueId v) const noexcept { return e_->account_usable(v); }
255 // ---- perpetuals (core/perp_book.hpp)
256 // -----------------------------------------------------------
257
258 // The venue's mark and index price of a derivative (on_perp_state), with the time each arrived.
259 // `stale`: older than [accounting] stale_mark_ms, or never reported; usable() is neither.
260 [[nodiscard]] RefPrice mark(InstrumentId id) const noexcept { return e_->mark(id); }
261 [[nodiscard]] RefPrice index(InstrumentId id) const noexcept { return e_->index(id); }
262 // The venue's funding of a perpetual: the rate it will apply at `next`, per `interval`, and
263 // over(d), the rate over a holding time d. `stale` against [accounting] stale_funding_ms.
264 [[nodiscard]] FundingView funding(InstrumentId id) const noexcept { return e_->funding(id); }
265 // Every field as last reported, open interest included, without the staleness applied.
266 [[nodiscard]] const PerpRow& perp_state(InstrumentId id) const noexcept {
267 return e_->perps().row(id);
268 }
269
270 // ---- randomness: seeded from EngineConfig::rng_seed, replay-deterministic --------------------
271
272 [[nodiscard]] Xoshiro256ss& rng() noexcept { return e_->rng(); }
273
274 private:
275 Engine* e_;
276};
277
278} // namespace fastmm
Dense-by-id table built at startup; symbol lookup is a sorted FlatMap keyed by (venue,...
Definition instrument.hpp:141
Definition oms.hpp:137
const OrderTimes & times(Handle< Order > h) const noexcept
Send and ack times of an open order.
Definition oms.hpp:586
bool is_live(Handle< Order > h) const noexcept
Definition oms.hpp:584
Handle< Order > find(ClientOrderId id) const noexcept
Definition oms.hpp:587
const Order & get(Handle< Order > h) const noexcept
Definition oms.hpp:582
static constexpr bool is_quote_tag(std::uint32_t tag) noexcept
Definition quote_manager.hpp:167
Definition result.hpp:30
Definition strategy_context.hpp:35
void request_stop() noexcept
Definition strategy_context.hpp:178
RiskHeadroom risk_headroom(InstrumentId id) const noexcept
What each [risk] limit still admits on the instrument now: the next check uses the same inputs,...
Definition strategy_context.hpp:196
TimerId every(Duration period, std::uint64_t tag=0) noexcept
Definition strategy_context.hpp:161
void pull_quotes(InstrumentId id) noexcept
Definition strategy_context.hpp:85
const Position & position(InstrumentId id) const noexcept
Definition strategy_context.hpp:72
Xoshiro256ss & rng() noexcept
Definition strategy_context.hpp:261
bool killed() const noexcept
Definition strategy_context.hpp:175
Level best_ex_self(InstrumentId id, Side side) const noexcept
The book's best level on one side with own_qty taken out; a level that was only ours is skipped.
Definition strategy_context.hpp:67
const FeeRates & fees(InstrumentId id) const noexcept
Maker and taker rates of the instrument, in cbps of the notional (positive: a fee).
Definition strategy_context.hpp:192
Qty own_qty(InstrumentId id, Side side, Price px) const noexcept
Our resting quantity that the venue's feed shows at (id, side, px): the book and the tickers of a liv...
Definition strategy_context.hpp:59
Qty balance_room(InstrumentId id, Side side, Price px) const noexcept
Largest quantity of id on side at px the balance covers (a spot buy's quote with the taker fee,...
Definition strategy_context.hpp:224
bool balances_live() const noexcept
A venue has reported balances (before that, balance() is unknown everywhere).
Definition strategy_context.hpp:220
Result< ClientOrderId, RejectReason > send(const NewOrderRequest &req) noexcept
RejectReason::InvalidTag: user_tag lies in the QuoteManager's tag range.
Definition strategy_context.hpp:118
const Order * order(ClientOrderId id) const noexcept
An open order by id, or nullptr once it is terminal. Valid until the next context call.
Definition strategy_context.hpp:134
Balance balance(VenueId v, std::string_view asset) const noexcept
One asset of a venue's account: the venue's last report (on_balance) less what this engine's orders h...
Definition strategy_context.hpp:213
VenueId pool_primary(VenueId v) const noexcept
The venue whose instruments v takes orders for: its primary, or v itself.
Definition strategy_context.hpp:242
typename Engine::Book Book
Definition strategy_context.hpp:37
Result< void, RejectReason > replace(ClientOrderId id, Price px, Qty qty) noexcept
Definition strategy_context.hpp:129
void note_quote(InstrumentId id, Side side, std::string_view reason, Notional budget={}) noexcept
Why the strategy asks what it asks on a side, for the operator: a short reason ("inventory_cap",...
Definition strategy_context.hpp:93
std::optional< Qty > queue_ahead(ClientOrderId id) const noexcept
Estimated quantity resting ahead of an open order at its price: the l2_queue fill model's queue model...
Definition strategy_context.hpp:144
const Order * working_quote(InstrumentId id, Side side, std::uint32_t level=0) const noexcept
The open order in a quote slot (level 0 is closest to the mid), or nullptr.
Definition strategy_context.hpp:107
OrderBudget order_budget(VenueId v) const noexcept
What the [risk] rate limit and the venue's own limits still admit (core/order_budget....
Definition strategy_context.hpp:207
bool account_usable(VenueId v) const noexcept
The automatic routing would send to v now: its order link is live and its kill switch is not engaged ...
Definition strategy_context.hpp:245
Qty open_qty(InstrumentId id, Side side) const noexcept
Unfilled quantity of our open orders (quotes included) on one side.
Definition strategy_context.hpp:155
bool set_quotes(InstrumentId id, const DesiredQuotes &q) noexcept
Returns false when the quotes were ignored: quoting is disabled (control pull, kill switch,...
Definition strategy_context.hpp:81
void pull_all_quotes() noexcept
Definition strategy_context.hpp:102
bool contains(InstrumentId id) const noexcept
Definition strategy_context.hpp:47
const Book & book(InstrumentId id) const noexcept
Definition strategy_context.hpp:52
bool quoting_enabled() const noexcept
Definition strategy_context.hpp:170
const InstrumentTable & instruments() const noexcept
Definition strategy_context.hpp:46
Portfolio portfolio() const noexcept
PnL totals over every instrument (a loop over all positions; not for every event).
Definition strategy_context.hpp:76
VenueHealthView venue_health(VenueId v) const noexcept
Feed lag and order round trip of a venue (core/venue_health.hpp), and whether the feed-lag gate ([ris...
Definition strategy_context.hpp:201
FundingView funding(InstrumentId id) const noexcept
The venue's funding of a perpetual: the rate it will apply at next, per interval, and over(d),...
Definition strategy_context.hpp:254
PoolMembers pool(VenueId primary) const noexcept
The accounts that take orders for a venue's instruments ([venues.
Definition strategy_context.hpp:240
bool venue_killed(VenueId v) const noexcept
One venue's kill switch: new orders to it are refused and set_quotes ignores its instruments.
Definition strategy_context.hpp:177
void metric(std::string_view name, double value) noexcept
A number the strategy publishes by name, for the status file (a budget split, a model's state): the f...
Definition strategy_context.hpp:101
Qty own_qty(InstrumentId id, Side side, Price px, Timestamp at) const noexcept
Definition strategy_context.hpp:62
RefPrice index(InstrumentId id) const noexcept
Definition strategy_context.hpp:251
const Instrument & instrument(InstrumentId id) const noexcept
Definition strategy_context.hpp:43
void trip_kill(KillReason reason) noexcept
Trips the global kill switch: quoting stops, quotes are pulled and every working order is cancelled.
Definition strategy_context.hpp:186
Timestamp now() const noexcept
Definition strategy_context.hpp:42
void request_batch_end() noexcept
Asks for one on_batch_end call once the engine has handled every event that is waiting now (the feed ...
Definition strategy_context.hpp:183
bool reconciling() const noexcept
A venue's orders and position are being reconciled, or have not been yet since the session started (a...
Definition strategy_context.hpp:174
RefPrice mark(InstrumentId id) const noexcept
The venue's mark and index price of a derivative (on_perp_state), with the time each arrived.
Definition strategy_context.hpp:250
const OrderTimes * order_times(ClientOrderId id) const noexcept
Send and ack times of an open order, or nullptr (OmsUpdate::times has them in on_order_update and,...
Definition strategy_context.hpp:149
TimerId once(Duration delay, std::uint64_t tag=0) noexcept
Definition strategy_context.hpp:164
const PerpRow & perp_state(InstrumentId id) const noexcept
Every field as last reported, open interest included, without the staleness applied.
Definition strategy_context.hpp:256
const Oms & oms() const noexcept
Definition strategy_context.hpp:158
bool cancel_timer(TimerId id) noexcept
Definition strategy_context.hpp:167
Margin margin(VenueId v) const noexcept
The venue's margin: its account-wide margin where it reports one, else the settlement asset of its fi...
Definition strategy_context.hpp:218
Result< void, RejectReason > cancel(ClientOrderId id) noexcept
Definition strategy_context.hpp:125
Minimal expected<T, E>: the hot path never throws, it returns Result.
All engine enumerations.
Fee rates per instrument, in centi-basis-points (integer) of the fill notional.
Fixed<Tag>: int64 fixed point with a global 1e-8 scale (ADR-0001).
Instrument: the single definition of a tradable contract (5.3 / 5.15).
L2Book<kMaxDepth>: price-aggregated book for crypto feeds (5.4).
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
KillReason
Why a kill switch (global or one venue's) was tripped.
Definition enums.hpp:523
@ Balance
the account's holding of one asset on one venue (BalanceMsg)
@ InvalidTag
a direct order used a user_tag in the QuoteManager's range
Side
Definition enums.hpp:12
constexpr Unexpected< std::decay_t< E > > fail(E &&e) noexcept
Definition result.hpp:25
StrongId< struct InstrumentTag, std::uint32_t > InstrumentId
Dense index into the engine's instrument table.
Definition strong_id.hpp:38
constexpr std::size_t kMaxQuoteLevels
Definition quote_manager.hpp:54
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.
Position tracking, average-cost method (5.8).
QuoteManager (5.6): diffs the strategy's DesiredQuotes against the orders resting at the venue and em...
RiskLimits: the pre-trade limits a session runs with ([risk] in the configuration),...
xoshiro256** - the engine's only source of randomness; seeded from config and written to the journal ...
Strongly typed identifiers.
Definition quote_manager.hpp:56
Definition time.hpp:22
One maker/taker pair. 1 cbps == 0.01 bps == 1e-6 of the notional.
Definition fees.hpp:26
Definition strong_id.hpp:54
constexpr bool valid() const noexcept
Definition strong_id.hpp:58
Definition instrument.hpp:29
One price level. Shared by book messages and the L2 book.
Definition messages.hpp:30
Definition order.hpp:66
When the order's working leg (the order, or its latest replacement once acknowledged) was sent and ac...
Definition order.hpp:58
Definition order.hpp:15
PnL totals over every instrument (StrategyContext::portfolio()), in the reporting currency when the t...
Definition position.hpp:39
Definition position.hpp:20
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
What StrategyContext::venue_health returns. Durations are zero until the first sample.
Definition venue_health.hpp:29
Time primitives.
VenueHealth: per venue, how late its market data arrives and how long our orders take to be acknowled...