FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
instrument.hpp
Go to the documentation of this file.
1#pragma once
2// Instrument: the single definition of a tradable contract (5.3 / 5.15). 128 bytes: the
3// first cache line holds everything the hot path reads (tick, lot, limits, multiplier),
4// the second holds cold reference data (symbol, expiry, strike).
5//
6// All asset-class differences live here (contract_multiplier, option fields) so strategies
7// never branch on asset class; they call inst.notional()/pnl_per_tick().
8#include "fastmm/core/config_macros.hpp"
9#include "fastmm/core/containers/flat_map.hpp"
10#include "fastmm/core/containers/static_vector.hpp"
11#include "fastmm/core/enums.hpp"
13#include "fastmm/core/fixed_string.hpp"
16
17#include <cstddef>
18#include <cstdint>
19#include <string_view>
20#include <type_traits>
21
22namespace fastmm {
23
24inline constexpr std::size_t kMaxInstruments = 256;
25using Symbol = FixedString<20>;
27struct alignas(kCacheLine) Instrument {
28 enum Flags : std::uint8_t {
29 kEnabled = 1U << 0,
30 // Priced in one currency and settled in another (Deribit's BTC perpetual: USD against BTC):
31 // notional is qty * multiplier / price and PnL moves with 1 / price, in the base coin.
32 kInverse = 1U << 1,
33 kReduceOnlySupported = 1U << 2,
34 // An option whose premium is in its underlying coin (Deribit's BTC options, 0.0065 BTC): its
35 // model value is the USD price divided by the forward. Valued linearly in that premium; not
36 // kInverse.
37 kCoinQuoted = 1U << 3,
38 };
40 // ---- hot line (64 bytes) -------------------------------------------------------------
41 InstrumentId id{}; // 4
42 VenueId venue{}; // 1
43 AssetClass asset_class{}; // 1
44 std::uint8_t flags = 0; // 1
45 std::uint8_t price_decimals = 8; // 1 venue formatting hint
46 Price tick{}; // 8
47 Qty lot{}; // 8
48 Qty min_qty{}; // 8
49 Qty max_qty{}; // 8
50 Notional min_notional{}; // 8
51 Qty contract_multiplier = Qty::from_int(1); // 8 units of underlying per contract
52 Notional max_notional{}; // 8 -> 64
53 // ---- cold line (64 bytes) ------------------------------------------------------------
54 std::int64_t expiry_ns = 0; // 8 (0 = perpetual / spot)
55 Price strike{}; // 8 -> 16
56 Symbol symbol{}; // 21
57 FixedString<8> base{}; // 9
58 FixedString<8> quote{}; // 9 -> 55
59 OptionType option_type = OptionType::None; // 1 -> 56
60 std::uint8_t cold_pad_[8] = {}; // -> 64
61
62 [[nodiscard]] constexpr bool enabled() const noexcept { return (flags & kEnabled) != 0; }
63 [[nodiscard]] constexpr bool inverse() const noexcept { return (flags & kInverse) != 0; }
64 [[nodiscard]] constexpr bool coin_quoted() const noexcept { return (flags & kCoinQuoted) != 0; }
65
66 // Passive rounding: bids down, asks up.
67 [[nodiscard]] constexpr Price round_price(Price p, Side side) const noexcept {
68 return round_to_tick(p, tick, side);
69 }
70 [[nodiscard]] constexpr Qty round_qty(Qty q) const noexcept { return round_to_lot(q, lot); }
71 // n ticks as a price distance (tick * n).
72 [[nodiscard]] constexpr Price ticks(std::int64_t n) const noexcept { return tick * n; }
73 [[nodiscard]] constexpr bool valid_price(Price p) const noexcept {
74 return p.is_positive() && on_tick(p, tick);
75 }
76 [[nodiscard]] constexpr bool valid_qty(Qty q) const noexcept {
77 return q.is_positive() && on_lot(q, lot) && q >= min_qty && (max_qty.is_zero() || q <= max_qty);
78 }
79 // Notional in the settlement currency: price * qty * multiplier for a linear contract, and
80 // qty * multiplier / price (base coin) for an inverse one, whose contract size is quoted in the
81 // quote currency. Zero for an inverse contract at a non-positive price.
82 [[nodiscard]] constexpr Notional notional(Price p, Qty q) const noexcept {
83 if (inverse()) {
84 if (!p.is_positive()) return Notional{};
85 return Notional::from_raw(
86 static_cast<std::int64_t>(static_cast<Int128>(q.raw) * contract_multiplier.raw / p.raw));
87 }
88 const Notional n = mul(p, q);
89 if (contract_multiplier == Qty::from_int(1)) return n;
90 return Notional::from_raw(mul_raw(n, contract_multiplier));
91 }
92 // PnL change (settlement ccy) for one tick move on one contract. An inverse contract's tick
93 // value depends on the price (multiplier * tick / price^2), so this is zero for one: use
94 // inverse_pnl() instead.
95 [[nodiscard]] constexpr Notional pnl_per_tick() const noexcept {
96 if (inverse()) return Notional{};
97 return Notional::from_raw(mul_raw(tick, contract_multiplier));
98 }
99 // PnL in the base coin of moving `qty` contracts from `from` to `to`:
100 // qty * multiplier * (1/from - 1/to). Zero unless both prices are positive.
101 [[nodiscard]] constexpr Notional inverse_pnl(Price from, Price to, Qty qty) const noexcept {
102 if (!from.is_positive() || !to.is_positive()) return Notional{};
103 const Int128 num = static_cast<Int128>(qty.raw) * contract_multiplier.raw * (to.raw - from.raw);
104 const Int128 den = static_cast<Int128>(from.raw) * to.raw;
105 return Notional::from_raw(static_cast<std::int64_t>(num / den));
106 }
107 // The currency PnL, fees and notional are denominated in: the base coin for an inverse
108 // contract, the quote currency otherwise. Empty when the instrument does not name it.
109 [[nodiscard]] constexpr std::string_view settlement_ccy() const noexcept {
110 return inverse() ? base.view() : quote.view();
111 }
112 [[nodiscard]] constexpr bool is_derivative() const noexcept {
113 return asset_class == AssetClass::Perpetual || asset_class == AssetClass::Future ||
114 asset_class == AssetClass::Option;
115 }
116};
117static_assert(sizeof(Instrument) == 128);
118static_assert(alignof(Instrument) == 64);
119static_assert(std::is_trivially_copyable_v<Instrument>);
120
121// Instruments do not all settle in the same currency: an inverse contract settles in its base
122// coin, a linear one in its quote currency. PnL totals and [risk] max_loss are one currency-less
123// Notional, so a table that mixes them adds unrelated numbers; callers refuse such a table (see
124// docs/explanation/risk-model.md).
126 const Instrument* first = nullptr; // first enabled instrument
127 const Instrument* other = nullptr; // first enabled instrument that settles in another currency
128 [[nodiscard]] bool mixed() const noexcept { return other != nullptr; }
129};
130
131enum class InstrumentError : std::uint8_t {
132 TableFull,
137};
138
139// Dense-by-id table built at startup; symbol lookup is a sorted FlatMap keyed by
140// (venue, symbol). Ids are the insertion index.
142 public:
143 struct Key {
146 constexpr auto operator<=>(const Key&) const noexcept = default;
147 };
148
150 if (inst.symbol.empty()) return fail(InstrumentError::EmptySymbol);
151 if (!inst.tick.is_positive()) return fail(InstrumentError::InvalidTick);
152 if (!inst.lot.is_positive()) return fail(InstrumentError::InvalidLot);
153 if (by_id_.full()) return fail(InstrumentError::TableFull);
154 const InstrumentId id{static_cast<std::uint32_t>(by_id_.size())};
155 auto [p, inserted] = by_symbol_.insert(Key{inst.venue, inst.symbol}, id);
156 if (!inserted) return fail(InstrumentError::DuplicateSymbol);
157 by_id_.push_back(inst);
158 by_id_[id.value].id = id;
159 return id;
160 }
161
162 [[nodiscard]] std::size_t size() const noexcept { return by_id_.size(); }
163 [[nodiscard]] bool contains(InstrumentId id) const noexcept { return id.value < by_id_.size(); }
164 [[nodiscard]] const Instrument& get(InstrumentId id) const noexcept {
165 FASTMM_ASSERT(contains(id));
166 return by_id_[id.value];
167 }
168 [[nodiscard]] Instrument& get(InstrumentId id) noexcept {
169 FASTMM_ASSERT(contains(id));
170 return by_id_[id.value];
171 }
172 [[nodiscard]] const Instrument& operator[](InstrumentId id) const noexcept { return get(id); }
173 [[nodiscard]] const Instrument* find(VenueId venue, std::string_view symbol) const noexcept {
174 const InstrumentId* id = by_symbol_.find(Key{venue, Symbol(symbol)});
175 return id == nullptr ? nullptr : &by_id_[id->value];
176 }
177 [[nodiscard]] const Instrument* begin() const noexcept { return by_id_.begin(); }
178 [[nodiscard]] const Instrument* end() const noexcept { return by_id_.end(); }
179 [[nodiscard]] const Instrument* data() const noexcept { return by_id_.data(); }
180 // Two instruments settle alike when they name the same settlement currency, linear or inverse
181 // (Deribit's inverse BTC-PERPETUAL and its BTC-quoted options both settle in BTC); an unnamed
182 // currency only matches another unnamed one of the same kind.
183 [[nodiscard]] SettlementMix settlement_mix() const noexcept {
185 for (const Instrument& inst : *this) {
186 if (!inst.enabled()) continue;
187 if (m.first == nullptr) {
188 m.first = &inst;
189 } else if (inst.settlement_ccy() != m.first->settlement_ccy() ||
190 (inst.settlement_ccy().empty() && inst.inverse() != m.first->inverse())) {
191 m.other = &inst;
192 break;
193 }
194 }
195 return m;
196 }
197 void clear() noexcept {
198 by_id_.clear();
199 by_symbol_.clear();
200 }
201
202 private:
203 StaticVector<Instrument, kMaxInstruments> by_id_;
204 FlatMap<Key, InstrumentId, kMaxInstruments> by_symbol_;
205};
206
207} // namespace fastmm
Dense-by-id table built at startup; symbol lookup is a sorted FlatMap keyed by (venue,...
Definition instrument.hpp:141
bool contains(InstrumentId id) const noexcept
Definition instrument.hpp:163
SettlementMix settlement_mix() const noexcept
Two instruments settle alike when they name the same settlement currency, linear or inverse (Deribit'...
Definition instrument.hpp:183
const Instrument * find(VenueId venue, std::string_view symbol) const noexcept
Definition instrument.hpp:173
std::size_t size() const noexcept
Definition instrument.hpp:162
const Instrument & operator[](InstrumentId id) const noexcept
Definition instrument.hpp:172
Instrument & get(InstrumentId id) noexcept
Definition instrument.hpp:168
const Instrument & get(InstrumentId id) const noexcept
Definition instrument.hpp:164
void clear() noexcept
Definition instrument.hpp:197
Result< InstrumentId, InstrumentError > add(const Instrument &inst) noexcept
Definition instrument.hpp:149
const Instrument * begin() const noexcept
Definition instrument.hpp:177
const Instrument * end() const noexcept
Definition instrument.hpp:178
const Instrument * data() const noexcept
Definition instrument.hpp:179
Definition result.hpp:30
Minimal expected<T, E>: the hot path never throws, it returns Result.
All engine enumerations.
Fixed<Tag>: int64 fixed point with a global 1e-8 scale (ADR-0001).
Definition strategy.hpp:16
constexpr Qty round_to_lot(Qty q, Qty lot) noexcept
Quantities always round down (never send more than intended).
Definition fixed_point.hpp:405
AssetClass
Definition enums.hpp:51
Side
Definition enums.hpp:12
constexpr Unexpected< std::decay_t< E > > fail(E &&e) noexcept
Definition result.hpp:25
constexpr std::size_t kMaxInstruments
Definition instrument.hpp:26
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
constexpr bool on_lot(Qty q, Qty lot) noexcept
Definition fixed_point.hpp:409
InstrumentError
Definition instrument.hpp:131
OptionType
Definition enums.hpp:77
FixedString< 20 > Symbol
Definition instrument.hpp:27
constexpr Notional mul(Price p, Qty q) noexcept
price * qty with a 128-bit intermediate, truncated toward zero to 1e-8.
Definition fixed_point.hpp:362
constexpr Price round_to_tick(Price p, Price tick, Side side) noexcept
Round to the tick grid in the passive direction: bids down, asks up. tick > 0.
Definition fixed_point.hpp:388
constexpr bool on_tick(Price p, Price tick) noexcept
Definition fixed_point.hpp:401
Strongly typed identifiers.
std::int64_t raw
Definition fixed_point.hpp:152
Definition instrument.hpp:143
Symbol symbol
Definition instrument.hpp:145
VenueId venue
Definition instrument.hpp:144
constexpr auto operator<=>(const Key &) const noexcept=default
Definition instrument.hpp:29
constexpr bool inverse() const noexcept
Definition instrument.hpp:63
constexpr std::string_view settlement_ccy() const noexcept
The currency PnL, fees and notional are denominated in: the base coin for an inverse contract,...
Definition instrument.hpp:109
constexpr Qty round_qty(Qty q) const noexcept
Definition instrument.hpp:70
constexpr bool valid_qty(Qty q) const noexcept
Definition instrument.hpp:76
constexpr Notional notional(Price p, Qty q) const noexcept
Notional in the settlement currency: price * qty * multiplier for a linear contract,...
Definition instrument.hpp:82
constexpr Price ticks(std::int64_t n) const noexcept
n ticks as a price distance (tick * n).
Definition instrument.hpp:72
constexpr bool is_derivative() const noexcept
Definition instrument.hpp:112
constexpr bool coin_quoted() const noexcept
Definition instrument.hpp:64
constexpr bool enabled() const noexcept
Definition instrument.hpp:62
constexpr Notional inverse_pnl(Price from, Price to, Qty qty) const noexcept
PnL in the base coin of moving qty contracts from from to to: qty * multiplier * (1/from - 1/to).
Definition instrument.hpp:101
constexpr Price round_price(Price p, Side side) const noexcept
Passive rounding: bids down, asks up.
Definition instrument.hpp:67
constexpr Notional pnl_per_tick() const noexcept
PnL change (settlement ccy) for one tick move on one contract.
Definition instrument.hpp:95
constexpr bool valid_price(Price p) const noexcept
Definition instrument.hpp:73
Instruments do not all settle in the same currency: an inverse contract settles in its base coin,...
Definition instrument.hpp:125
bool mixed() const noexcept
Definition instrument.hpp:128
const Instrument * first
first enabled instrument
Definition instrument.hpp:126
const Instrument * other
first enabled instrument that settles in another currency
Definition instrument.hpp:127