FastMM documentation
FastMM C++ API 0.6.0
Public headers, tier 1 and tier 2
Loading...
Searching...
No Matches
fixed_point.hpp
Go to the documentation of this file.
1#pragma once
2// Fixed<Tag>: int64 fixed point with a global 1e-8 scale (ADR-0001).
3//
4// Price, Qty and Notional are distinct types: p * q does not compile, use mul(p, q) which
5// widens to Int128. Exact decimal parsing/formatting (no double) is used for every venue
6// string; from_double/to_double exist only for config and diagnostics.
7// Range: +-92,233,720,368.54775807. Instruments outside this are rejected at load time.
8#include "fastmm/core/config_macros.hpp"
10
11#include <compare>
12#include <cstddef>
13#include <cstdint>
14#include <limits>
15#include <optional>
16#include <string_view>
17#include <type_traits>
18
19namespace fastmm {
20
21inline constexpr std::int64_t kFixedScale = 100'000'000; // 1e-8
22inline constexpr int kFixedDecimals = 8;
23// sign + 11 integer digits + '.' + 8 fraction digits + NUL
24inline constexpr std::size_t kMaxDecimalChars = 24;
25// Ratio (below): 1.0 == kFixedScale raw, one basis point == kRatioPerBp raw.
26inline constexpr std::int64_t kRatioPerBp = 10'000;
27inline constexpr int kBpsDecimals = 4;
29struct RatioTag;
30
31namespace detail {
32
33// a * b / d truncated toward zero, as through Int128. When a * b fits int64 (prices and
34// quantities in practice) the division stays 64-bit: a constant divisor becomes a multiply,
35// where an Int128 division is a call to __divti3 (~40 cycles). Same result either way.
36[[nodiscard]] FASTMM_FORCE_INLINE constexpr std::int64_t mul_div(std::int64_t a,
37 std::int64_t b,
38 std::int64_t d) noexcept {
39 std::int64_t p = 0;
40 if (FASTMM_LIKELY(!__builtin_mul_overflow(a, b, &p) && d != -1)) return p / d;
41 return static_cast<std::int64_t>(static_cast<Int128>(a) * b / d);
42}
43template <std::int64_t D>
44[[nodiscard]] FASTMM_FORCE_INLINE constexpr std::int64_t mul_div(std::int64_t a,
45 std::int64_t b) noexcept {
46 std::int64_t p = 0;
47 if (FASTMM_LIKELY(!__builtin_mul_overflow(a, b, &p))) return p / D;
48 return static_cast<std::int64_t>(static_cast<Int128>(a) * b / D);
49}
50
51// Exact parse of "[+-]digits[.digits][(e|E)[+-]digits]" into value * 10^decimals. The result must
52// be an integer that fits int64: digits left over after the scale must be zeros, so "2e-05" with 8
53// decimals is 2000 and "1.5e-8" is rejected. With `literal` (numeric literal characters) no sign is
54// accepted, digit separators (') are skipped and an octal-looking integer ("017") is rejected.
55[[nodiscard]] constexpr std::optional<std::int64_t> parse_scaled_decimal(
56 std::string_view s, int decimals, bool literal = false) noexcept {
57 constexpr std::int64_t kMax = std::numeric_limits<std::int64_t>::max();
58 std::size_t i = 0;
59 bool neg = false;
60 if (!literal && !s.empty() && (s[0] == '-' || s[0] == '+')) {
61 neg = s[0] == '-';
62 i = 1;
63 }
64 const std::size_t mant_begin = i;
65 std::int64_t n_digits = 0;
66 std::int64_t frac_digits = 0;
67 bool dot = false;
68 for (; i < s.size(); ++i) {
69 const char c = s[i];
70 if (c >= '0' && c <= '9') {
71 ++n_digits;
72 if (dot) ++frac_digits;
73 } else if (c == '.' && !dot) {
74 dot = true;
75 } else if (!literal || c != '\'') {
76 break;
77 }
78 }
79 const std::size_t mant_end = i;
80 if (n_digits == 0) return std::nullopt;
81 std::int64_t exp = 0;
82 bool has_exp = false;
83 if (i < s.size()) {
84 if (s[i] != 'e' && s[i] != 'E') return std::nullopt;
85 has_exp = true;
86 ++i;
87 bool exp_neg = false;
88 if (i < s.size() && (s[i] == '-' || s[i] == '+')) {
89 exp_neg = s[i] == '-';
90 ++i;
91 }
92 std::size_t exp_digits = 0;
93 for (; i < s.size(); ++i) {
94 const char c = s[i];
95 if (literal && c == '\'') continue;
96 if (c < '0' || c > '9') return std::nullopt;
97 if (exp < 100'000) exp = exp * 10 + (c - '0'); // larger exponents fail below anyway
98 ++exp_digits;
99 }
100 if (exp_digits == 0) return std::nullopt;
101 if (exp_neg) exp = -exp;
102 }
103 if (literal && !dot && !has_exp && n_digits > 1 && s[mant_begin] == '0') return std::nullopt;
104 // value = mantissa digits * 10^(exp - frac_digits); result = digits * 10^shift
105 const std::int64_t shift = exp - frac_digits + decimals;
106 const std::int64_t keep = n_digits + (shift < 0 ? shift : 0); // leading digits that survive
107 std::int64_t r = 0;
108 std::int64_t idx = 0;
109 for (std::size_t k = mant_begin; k < mant_end; ++k) {
110 const char c = s[k];
111 if (c < '0' || c > '9') continue;
112 const int d = c - '0';
113 if (idx < keep) {
114 if (r > (kMax - d) / 10) return std::nullopt;
115 r = r * 10 + d;
116 } else if (d != 0) {
117 return std::nullopt; // more decimals than the scale holds
118 }
119 ++idx;
120 }
121 for (std::int64_t k = 0; k < shift && r != 0; ++k) {
122 if (r > kMax / 10) return std::nullopt;
123 r *= 10;
124 }
125 return neg ? -r : r;
126}
127
128// d * scale rounded half away from zero, or nullopt when the result is not an int64: NaN and
129// +-inf fail both comparisons, and the bounds are exclusive so the cast is always defined.
130[[nodiscard]] inline std::optional<std::int64_t> scaled_round(double d, double scale) noexcept {
131 constexpr double kTwoPow63 = 9223372036854775808.0; // == -(double)INT64_MIN, exact
132 const double scaled = d * scale;
133 const double rounded = scaled < 0 ? scaled - 0.5 : scaled + 0.5;
134 if (!(rounded > -kTwoPow63 && rounded < kTwoPow63)) return std::nullopt;
135 return static_cast<std::int64_t>(rounded);
136}
137// What from_double() returns for a value scaled_round() refused: zero for NaN, the nearest bound
138// otherwise.
139template <class F>
140[[nodiscard]] inline F saturate(double d) noexcept {
141 if (d != d) return F{}; // NaN
142 return d < 0 ? F::min() : F::max();
143}
144
145} // namespace detail
146
147template <class Tag>
148struct Fixed {
149 using rep_type = std::int64_t;
150 std::int64_t raw = 0;
152 constexpr Fixed() noexcept = default;
153 [[nodiscard]] static constexpr Fixed from_raw(std::int64_t r) noexcept {
155 f.raw = r;
156 return f;
157 }
158 // Whole units, e.g. from_int(100) == 100.00000000.
159 [[nodiscard]] static constexpr Fixed from_int(std::int64_t units) noexcept {
160 return from_raw(units * kFixedScale);
162 [[nodiscard]] static constexpr Fixed zero() noexcept { return Fixed{}; }
163 [[nodiscard]] static constexpr Fixed max() noexcept {
164 return from_raw(std::numeric_limits<std::int64_t>::max());
166 [[nodiscard]] static constexpr Fixed min() noexcept {
167 return from_raw(std::numeric_limits<std::int64_t>::min());
169
170 // Exact parse of "[+-]digits[.digits]". Rejects: empty, >8 significant fraction digits,
171 // integer overflow, exponents, whitespace. Trailing zeros beyond 8 places are accepted.
172 [[nodiscard]] static constexpr std::optional<Fixed> from_decimal(std::string_view s) noexcept {
173 if (s.empty()) return std::nullopt;
174 std::size_t i = 0;
175 bool neg = false;
176 if (s[0] == '-' || s[0] == '+') {
177 neg = s[0] == '-';
178 i = 1;
179 }
180 constexpr std::int64_t kMaxInt =
181 std::numeric_limits<std::int64_t>::max() / kFixedScale; // 92233720368
182 std::int64_t ip = 0;
183 std::size_t int_digits = 0;
184 for (; i < s.size() && s[i] != '.'; ++i) {
185 const char c = s[i];
186 if (c < '0' || c > '9') return std::nullopt;
187 if (ip > (kMaxInt - (c - '0')) / 10) return std::nullopt;
188 ip = ip * 10 + (c - '0');
189 ++int_digits;
190 }
191 std::int64_t fp = 0;
192 std::size_t frac_digits = 0;
193 if (i < s.size()) { // s[i] == '.'
194 ++i;
195 for (; i < s.size(); ++i) {
196 const char c = s[i];
197 if (c < '0' || c > '9') return std::nullopt;
198 if (frac_digits < static_cast<std::size_t>(kFixedDecimals)) {
199 fp = fp * 10 + (c - '0');
200 ++frac_digits;
201 } else if (c != '0') {
202 return std::nullopt; // would lose precision
203 }
204 }
205 }
206 if (int_digits == 0 && frac_digits == 0) return std::nullopt;
207 for (std::size_t k = frac_digits; k < static_cast<std::size_t>(kFixedDecimals); ++k) fp *= 10;
208 const std::int64_t mag = ip * kFixedScale;
209 if (mag > std::numeric_limits<std::int64_t>::max() - fp) return std::nullopt;
210 const std::int64_t r = mag + fp;
211 return from_raw(neg ? -r : r);
212 }
213
214 // Exact parse of a decimal in plain or exponent notation: "0.1", "2e-05", "1.5E3", "-3". Used for
215 // configuration values (TOML floats arrive formatted by fmt, Python floats by repr). Rejects a
216 // value only if more than 8 decimals remain after applying the exponent, or it is out of range.
217 // Venue strings use the stricter from_decimal.
218 [[nodiscard]] static constexpr std::optional<Fixed> parse(std::string_view s) noexcept {
219 const std::optional<std::int64_t> r = detail::parse_scaled_decimal(s, kFixedDecimals);
220 if (!r) return std::nullopt;
221 return from_raw(*r);
222 }
223
224 // Ratio only. Startup only (double): Ratio::from_bps(2.5) == 2.5 bps, rounded to 0.0001 bp.
225 // Saturates like from_double; from_bps_checked() reports the loss instead.
226 [[nodiscard]] static Fixed from_bps(double bps) noexcept
227 requires std::is_same_v<Tag, RatioTag>
229 return from_bps_checked(bps).value_or(detail::saturate<Fixed>(bps));
230 }
231 [[nodiscard]] static std::optional<Fixed> from_bps_checked(double bps) noexcept
232 requires std::is_same_v<Tag, RatioTag>
234 const std::optional<std::int64_t> r =
235 detail::scaled_round(bps, static_cast<double>(kRatioPerBp));
236 if (!r) return std::nullopt;
237 return from_raw(*r);
238 }
239 // Ratio only. Diagnostics only.
240 [[nodiscard]] double to_bps() const noexcept
241 requires std::is_same_v<Tag, RatioTag>
243 return static_cast<double>(raw) / static_cast<double>(kRatioPerBp);
244 }
245
246 // Formats into buf (>= kMaxDecimalChars), trims trailing zeros ("1.5", "100", "0.00000001").
247 // Returns the number of chars written; no NUL is appended.
248 std::size_t to_decimal(char* buf) const noexcept {
249 char* p = buf;
250 std::uint64_t mag = 0;
251 if (raw < 0) {
252 *p++ = '-';
253 mag = raw == std::numeric_limits<std::int64_t>::min()
254 ? static_cast<std::uint64_t>(std::numeric_limits<std::int64_t>::max()) + 1U
255 : static_cast<std::uint64_t>(-raw);
256 } else {
257 mag = static_cast<std::uint64_t>(raw);
258 }
259 const std::uint64_t ip = mag / static_cast<std::uint64_t>(kFixedScale);
260 std::uint64_t fp = mag % static_cast<std::uint64_t>(kFixedScale);
261 // integer part
262 char tmp[20];
263 int n = 0;
264 std::uint64_t v = ip;
265 do {
266 tmp[n++] = static_cast<char>('0' + (v % 10));
267 v /= 10;
268 } while (v != 0);
269 while (n > 0) *p++ = tmp[--n];
270 if (fp != 0) {
271 *p++ = '.';
272 char frac[kFixedDecimals];
273 for (int k = kFixedDecimals - 1; k >= 0; --k) {
274 frac[k] = static_cast<char>('0' + (fp % 10));
275 fp /= 10;
276 }
277 int len = kFixedDecimals;
278 while (len > 0 && frac[len - 1] == '0') --len;
279 for (int k = 0; k < len; ++k) *p++ = frac[k];
280 }
281 return static_cast<std::size_t>(p - buf);
282 }
283
284 // Rounds to the 1e-8 grid. NaN becomes zero and a value outside +-92,233,720,368.54775807
285 // saturates at max()/min(); the unchecked cast this replaced made both INT64_MIN.
286 [[nodiscard]] static Fixed from_double(double d) noexcept {
287 return from_double_checked(d).value_or(detail::saturate<Fixed>(d));
289 // nullopt for NaN, +-inf and anything outside the representable range; two compares more.
290 [[nodiscard]] static std::optional<Fixed> from_double_checked(double d) noexcept {
291 const std::optional<std::int64_t> r = detail::scaled_round(d, static_cast<double>(kFixedScale));
292 if (!r) return std::nullopt;
293 return from_raw(*r);
294 }
295 [[nodiscard]] double to_double() const noexcept {
296 return static_cast<double>(raw) / static_cast<double>(kFixedScale);
298
299 [[nodiscard]] constexpr bool is_zero() const noexcept { return raw == 0; }
300 [[nodiscard]] constexpr bool is_positive() const noexcept { return raw > 0; }
301 [[nodiscard]] constexpr bool is_negative() const noexcept { return raw < 0; }
302 [[nodiscard]] constexpr Fixed abs() const noexcept { return from_raw(raw < 0 ? -raw : raw); }
303 [[nodiscard]] constexpr std::int64_t units() const noexcept { return raw / kFixedScale; }
305 constexpr auto operator<=>(const Fixed&) const noexcept = default;
306
307 constexpr Fixed operator-() const noexcept { return from_raw(-raw); }
308 friend constexpr Fixed operator+(Fixed a, Fixed b) noexcept { return from_raw(a.raw + b.raw); }
309 friend constexpr Fixed operator-(Fixed a, Fixed b) noexcept { return from_raw(a.raw - b.raw); }
310 friend constexpr Fixed operator*(Fixed a, std::int64_t k) noexcept { return from_raw(a.raw * k); }
311 friend constexpr Fixed operator*(std::int64_t k, Fixed a) noexcept { return from_raw(a.raw * k); }
312 friend constexpr Fixed operator/(Fixed a, std::int64_t k) noexcept { return from_raw(a.raw / k); }
313 // Number of whole ticks in a (truncating); result is a plain integer.
314 friend constexpr std::int64_t operator/(Fixed a, Fixed b) noexcept { return a.raw / b.raw; }
315 friend constexpr Fixed operator%(Fixed a, Fixed b) noexcept { return from_raw(a.raw % b.raw); }
316 constexpr Fixed& operator+=(Fixed o) noexcept {
317 raw += o.raw;
318 return *this;
319 }
320 constexpr Fixed& operator-=(Fixed o) noexcept {
321 raw -= o.raw;
322 return *this;
323 }
324 constexpr Fixed& operator*=(std::int64_t k) noexcept {
325 raw *= k;
326 return *this;
327 }
328};
329
330using Price = Fixed<struct PriceTag>;
331using Qty = Fixed<struct QtyTag>;
333// A dimensionless factor: 1.0 == 1e8 raw, 1 bp == 10'000 raw, so 0.0001 bp is the smallest step.
335
336static_assert(std::is_trivially_copyable_v<Price> && sizeof(Price) == 8);
337
338// value * ratio in the value's domain, through Int128 with one truncation toward zero:
339// mid * 5_bps, qty * ratio(filled, total).
340template <class T>
341[[nodiscard]] constexpr Fixed<T> operator*(Fixed<T> v, Ratio r) noexcept {
342 return Fixed<T>::from_raw(detail::mul_div<kFixedScale>(v.raw, r.raw));
344template <class T>
345[[nodiscard]] constexpr Fixed<T> operator*(Ratio r, Fixed<T> v) noexcept {
346 return v * r;
348[[nodiscard]] constexpr Ratio operator*(Ratio a, Ratio b) noexcept {
349 return Ratio::from_raw(detail::mul_div<kFixedScale>(a.raw, b.raw));
351// num / den as a Ratio, truncated toward zero; zero when den is zero. The quotient must fit
352// +-92,233,720,368.
353template <class T>
354[[nodiscard]] constexpr Ratio ratio(Fixed<T> num, Fixed<T> den) noexcept {
355 if (den.raw == 0) return Ratio{};
356 return Ratio::from_raw(detail::mul_div(num.raw, kFixedScale, den.raw));
357}
358
359// price * qty with a 128-bit intermediate, truncated toward zero to 1e-8.
360[[nodiscard]] constexpr Notional mul(Price p, Qty q) noexcept {
361 return Notional::from_raw(detail::mul_div<kFixedScale>(p.raw, q.raw));
363// Generic scaled multiply for fixed*fixed in one domain (e.g. qty * multiplier).
364template <class A, class B>
365[[nodiscard]] constexpr std::int64_t mul_raw(Fixed<A> a, Fixed<B> b) noexcept {
366 return detail::mul_div<kFixedScale>(a.raw, b.raw);
368// notional / qty -> price (used for average cost). Truncates toward zero.
369[[nodiscard]] constexpr Price div(Notional n, Qty q) noexcept {
370 if (q.raw == 0) return Price{};
371 return Price::from_raw(detail::mul_div(n.raw, kFixedScale, q.raw));
372}
373// value * bps / 10000 in one domain.
374template <class T>
375[[nodiscard]] constexpr Fixed<T> apply_bps(Fixed<T> v, std::int64_t bps) noexcept {
376 return Fixed<T>::from_raw(detail::mul_div<10'000>(v.raw, bps));
378
379// Floor division for possibly-negative numerators (prices are >= 0, but be safe).
380[[nodiscard]] constexpr std::int64_t floor_div(std::int64_t a, std::int64_t b) noexcept {
381 const std::int64_t q = a / b;
382 return (a % b != 0 && ((a < 0) != (b < 0))) ? q - 1 : q;
383}
384
385// Round to the tick grid in the passive direction: bids down, asks up. tick > 0.
386[[nodiscard]] constexpr Price round_to_tick(Price p, Price tick, Side side) noexcept {
387 FASTMM_ASSERT(tick.raw > 0);
388 const std::int64_t q = floor_div(p.raw, tick.raw);
389 const std::int64_t floored = q * tick.raw;
390 if (side == Side::Buy || floored == p.raw) return Price::from_raw(floored);
391 return Price::from_raw(floored + tick.raw);
392}
393[[nodiscard]] constexpr Price round_to_tick_nearest(Price p, Price tick) noexcept {
394 FASTMM_ASSERT(tick.raw > 0);
395 const std::int64_t half = tick.raw / 2;
396 const std::int64_t shifted = p.raw >= 0 ? p.raw + half : p.raw - half;
397 return Price::from_raw(floor_div(shifted, tick.raw) * tick.raw);
398}
399[[nodiscard]] constexpr bool on_tick(Price p, Price tick) noexcept {
400 return tick.raw > 0 && p.raw % tick.raw == 0;
402// Quantities always round down (never send more than intended).
403[[nodiscard]] constexpr Qty round_to_lot(Qty q, Qty lot) noexcept {
404 FASTMM_ASSERT(lot.raw > 0);
405 return Qty::from_raw(floor_div(q.raw, lot.raw) * lot.raw);
406}
407[[nodiscard]] constexpr bool on_lot(Qty q, Qty lot) noexcept {
408 return lot.raw > 0 && q.raw % lot.raw == 0;
410
411template <class T>
412[[nodiscard]] constexpr Fixed<T> min(Fixed<T> a, Fixed<T> b) noexcept {
413 return a.raw < b.raw ? a : b;
415template <class T>
416[[nodiscard]] constexpr Fixed<T> max(Fixed<T> a, Fixed<T> b) noexcept {
417 return a.raw < b.raw ? b : a;
419
420// Checked arithmetic: nullopt instead of a wrapped int64. The operators stay unchecked; these are
421// for the places that combine a value from outside the engine with a configured one.
422template <class T>
423[[nodiscard]] constexpr std::optional<Fixed<T>> checked_add(Fixed<T> a, Fixed<T> b) noexcept {
424 std::int64_t r = 0;
425 if (__builtin_add_overflow(a.raw, b.raw, &r)) return std::nullopt;
426 return Fixed<T>::from_raw(r);
427}
428template <class T>
429[[nodiscard]] constexpr std::optional<Fixed<T>> checked_sub(Fixed<T> a, Fixed<T> b) noexcept {
430 std::int64_t r = 0;
431 if (__builtin_sub_overflow(a.raw, b.raw, &r)) return std::nullopt;
432 return Fixed<T>::from_raw(r);
433}
434template <class T>
435[[nodiscard]] constexpr std::optional<Fixed<T>> checked_mul(Fixed<T> a, std::int64_t k) noexcept {
436 std::int64_t r = 0;
437 if (__builtin_mul_overflow(a.raw, k, &r)) return std::nullopt;
438 return Fixed<T>::from_raw(r);
439}
440
441namespace detail {
442template <char... Cs>
443inline constexpr char kLiteralChars[sizeof...(Cs)] = {Cs...};
444template <char... Cs>
445[[nodiscard]] constexpr std::optional<std::int64_t> literal_raw(int decimals) noexcept {
446 return parse_scaled_decimal(
447 std::string_view(kLiteralChars<Cs...>, sizeof...(Cs)), decimals, /*literal=*/true);
448}
449} // namespace detail
450
451// Exact literals, parsed at compile time: 100.25_px, 0.01_qty (up to 8 decimals), 5_bps, 0.25_bps
452// (up to 4 decimals). More decimals, or a value out of range, is a compile error. An inline
453// namespace, so `using namespace fastmm;` brings them in too (like std::literals).
454inline namespace literals {
455// 100_px == Price::from_int(100).
456constexpr Price operator""_px(unsigned long long v) noexcept {
457 return Price::from_int(static_cast<std::int64_t>(v));
459constexpr Qty operator""_qty(unsigned long long v) noexcept {
460 return Qty::from_int(static_cast<std::int64_t>(v));
462template <char... Cs>
463[[nodiscard]] constexpr Price operator""_px() noexcept {
464 static_assert(detail::literal_raw<Cs...>(kFixedDecimals).has_value(),
465 "fastmm: _px literal needs more than 8 decimals or is out of range");
466 return Price::from_raw(detail::literal_raw<Cs...>(kFixedDecimals)
467 .value_or(0)); // checked by the static_assert above
468}
469template <char... Cs>
470[[nodiscard]] constexpr Qty operator""_qty() noexcept {
471 static_assert(detail::literal_raw<Cs...>(kFixedDecimals).has_value(),
472 "fastmm: _qty literal needs more than 8 decimals or is out of range");
473 return Qty::from_raw(detail::literal_raw<Cs...>(kFixedDecimals)
474 .value_or(0)); // checked by the static_assert above
475}
476template <char... Cs>
477[[nodiscard]] constexpr Ratio operator""_bps() noexcept {
478 static_assert(detail::literal_raw<Cs...>(kBpsDecimals).has_value(),
479 "fastmm: _bps literal needs more than 4 decimals or is out of range");
480 return Ratio::from_raw(
481 detail::literal_raw<Cs...>(kBpsDecimals).value_or(0)); // checked by the static_assert above
482}
483} // namespace literals
484
485} // namespace fastmm
All engine enumerations.
Definition strategy.hpp:16
constexpr Fixed< T > apply_bps(Fixed< T > v, std::int64_t bps) noexcept
value * bps / 10000 in one domain.
Definition fixed_point.hpp:377
constexpr Qty round_to_lot(Qty q, Qty lot) noexcept
Quantities always round down (never send more than intended).
Definition fixed_point.hpp:405
constexpr Fixed< T > operator*(Fixed< T > v, Ratio r) noexcept
value * ratio in the value's domain, through Int128 with one truncation toward zero: mid * 5_bps,...
Definition fixed_point.hpp:343
constexpr int kFixedDecimals
Definition fixed_point.hpp:24
constexpr Price round_to_tick_nearest(Price p, Price tick) noexcept
Definition fixed_point.hpp:395
constexpr std::int64_t kFixedScale
1e-8
Definition fixed_point.hpp:23
constexpr Fixed< T > min(Fixed< T > a, Fixed< T > b) noexcept
Definition fixed_point.hpp:414
constexpr std::int64_t kRatioPerBp
Ratio (below): 1.0 == kFixedScale raw, one basis point == kRatioPerBp raw.
Definition fixed_point.hpp:28
constexpr Fixed< T > max(Fixed< T > a, Fixed< T > b) noexcept
Definition fixed_point.hpp:418
Side
Definition enums.hpp:12
constexpr Price div(Notional n, Qty q) noexcept
notional / qty -> price (used for average cost). Truncates toward zero.
Definition fixed_point.hpp:371
constexpr std::optional< Fixed< T > > checked_mul(Fixed< T > a, std::int64_t k) noexcept
Definition fixed_point.hpp:437
Fixed< struct QtyTag > Qty
Definition fixed_point.hpp:333
Fixed< struct NotionalTag > Notional
Definition fixed_point.hpp:334
constexpr std::optional< Fixed< T > > checked_sub(Fixed< T > a, Fixed< T > b) noexcept
Definition fixed_point.hpp:431
constexpr std::int64_t floor_div(std::int64_t a, std::int64_t b) noexcept
Floor division for possibly-negative numerators (prices are >= 0, but be safe).
Definition fixed_point.hpp:382
Fixed< struct PriceTag > Price
Definition fixed_point.hpp:332
constexpr std::optional< Fixed< T > > checked_add(Fixed< T > a, Fixed< T > b) noexcept
Checked arithmetic: nullopt instead of a wrapped int64. The operators stay unchecked; these are for t...
Definition fixed_point.hpp:425
constexpr std::size_t kMaxDecimalChars
sign + 11 integer digits + '.' + 8 fraction digits + NUL
Definition fixed_point.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
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
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 Ratio ratio(Fixed< T > num, Fixed< T > den) noexcept
num / den as a Ratio, truncated toward zero; zero when den is zero.
Definition fixed_point.hpp:356
constexpr int kBpsDecimals
Definition fixed_point.hpp:29
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
Definition fixed_point.hpp:150
constexpr bool is_negative() const noexcept
Definition fixed_point.hpp:303
friend constexpr Fixed operator%(Fixed a, Fixed b) noexcept
Definition fixed_point.hpp:317
constexpr Fixed & operator+=(Fixed o) noexcept
Definition fixed_point.hpp:318
static Fixed from_bps(double bps) noexcept
Ratio only.
Definition fixed_point.hpp:228
double to_double() const noexcept
Definition fixed_point.hpp:297
static constexpr Fixed max() noexcept
Definition fixed_point.hpp:165
static constexpr std::optional< Fixed > parse(std::string_view s) noexcept
Exact parse of a decimal in plain or exponent notation: "0.1", "2e-05", "1.5E3", "-3".
Definition fixed_point.hpp:220
double to_bps() const noexcept
Ratio only. Diagnostics only.
Definition fixed_point.hpp:242
constexpr Fixed abs() const noexcept
Definition fixed_point.hpp:304
constexpr auto operator<=>(const Fixed &) const noexcept=default
static constexpr Fixed min() noexcept
Definition fixed_point.hpp:168
std::size_t to_decimal(char *buf) const noexcept
Formats into buf (>= kMaxDecimalChars), trims trailing zeros ("1.5", "100", "0.00000001").
Definition fixed_point.hpp:250
static constexpr Fixed from_raw(std::int64_t r) noexcept
Definition fixed_point.hpp:155
friend constexpr Fixed operator*(Fixed a, std::int64_t k) noexcept
Definition fixed_point.hpp:312
friend constexpr Fixed operator/(Fixed a, std::int64_t k) noexcept
Definition fixed_point.hpp:314
constexpr Fixed operator-() const noexcept
Definition fixed_point.hpp:309
static constexpr Fixed zero() noexcept
Definition fixed_point.hpp:164
constexpr bool is_zero() const noexcept
Definition fixed_point.hpp:301
friend constexpr Fixed operator+(Fixed a, Fixed b) noexcept
Definition fixed_point.hpp:310
std::int64_t raw
Definition fixed_point.hpp:152
constexpr Fixed() noexcept=default
static Fixed from_double(double d) noexcept
Rounds to the 1e-8 grid.
Definition fixed_point.hpp:288
constexpr bool is_positive() const noexcept
Definition fixed_point.hpp:302
static std::optional< Fixed > from_bps_checked(double bps) noexcept
Definition fixed_point.hpp:233
std::int64_t rep_type
Definition fixed_point.hpp:151
constexpr std::int64_t units() const noexcept
Definition fixed_point.hpp:305
static std::optional< Fixed > from_double_checked(double d) noexcept
nullopt for NaN, +-inf and anything outside the representable range; two compares more.
Definition fixed_point.hpp:292
static constexpr Fixed from_int(std::int64_t units) noexcept
Whole units, e.g. from_int(100) == 100.00000000.
Definition fixed_point.hpp:161
constexpr Fixed & operator-=(Fixed o) noexcept
Definition fixed_point.hpp:322
static constexpr std::optional< Fixed > from_decimal(std::string_view s) noexcept
Exact parse of "[+-]digits[.digits]".
Definition fixed_point.hpp:174
constexpr Fixed & operator*=(std::int64_t k) noexcept
Definition fixed_point.hpp:326