Fixed point¶
Prices, quantities and money are 64-bit integers with a fixed scale of 1e-8 (ADR-0001). The headers are include/fastmm/core/fixed_point.hpp and include/fastmm/strategies/quoting.hpp; include/fastmm/strategy.hpp includes both.
Types¶
| Type | Meaning | 1 raw unit | Range |
|---|---|---|---|
Price |
price per unit | 0.00000001 | +-92,233,720,368.54775807 |
Qty |
quantity, signed for positions | 0.00000001 | same |
Notional |
price times quantity, fees, PnL | 0.00000001 | same |
Ratio |
dimensionless factor; 1.0 is raw 100,000,000 | 0.0001 bp | +-922,337,203 x |
The types do not mix: Price * Qty and Price + Qty do not compile. .raw is the integer; from_raw, from_int, zero, max and min build values.
Arithmetic¶
| Expression | Result | Rounding |
|---|---|---|
a + b, a - b, -a, a.abs() (same type) |
same type | exact |
a * k, k * a, a / k (k is int64_t) |
same type | / truncates toward zero |
a / b (same type) |
int64_t, e.g. whole ticks |
truncates toward zero |
a % b (same type) |
same type | C++ % |
mul(Price, Qty) |
Notional |
Int128, truncates toward zero |
div(Notional, Qty) |
Price |
Int128, truncates toward zero; zero for a zero quantity |
v * r, r * v (v any fixed type, r a Ratio) |
the type of v |
Int128, one truncation toward zero |
r1 * r2 |
Ratio |
Int128, truncates toward zero |
ratio(num, den) (same type) |
Ratio |
Int128, truncates toward zero; zero for a zero den |
r * k (k is int64_t) |
Ratio |
exact; must fit int64 |
Truncation toward zero is sign-symmetric, so mid - mid * (skew * units) shifts a long and a short position by the same amount in opposite directions.
To the tick and lot grid, rounding is passive:
| Function | Rule |
|---|---|
inst.round_price(p, Side::Buy) / round_to_tick(p, tick, Buy) |
down (floor) |
inst.round_price(p, Side::Sell) |
up (ceiling) |
round_to_tick_nearest(p, tick) |
nearest, half away from zero |
inst.round_qty(q) / round_to_lot(q, lot) |
down |
inst.ticks(n) |
tick * n as a price distance |
Literals¶
using namespace fastmm::literals; // `using namespace fastmm;` includes them too
const Price p = 100.25_px; // raw 10'025'000'000
const Qty lot = 0.00000001_qty; // raw 1
const Ratio half = 0.25_bps; // raw 2'500
const Ratio cap = 5_bps; // raw 50'000
Literals are parsed exactly at compile time. _px and _qty take up to 8 decimals, _bps up to 4; more decimals, or a value out of range, is a compile error:
Digit separators (1'000.5_px) and exponents (1e-8_qty) are accepted. There is no Notional literal; use Notional::from_int(1000) or mul(price, qty).
Parsing and formatting¶
| Function | Accepts | Use |
|---|---|---|
Fixed::from_decimal(s) |
[+-]digits[.digits], up to 8 significant decimals |
venue strings (strict: no exponent, no whitespace) |
Fixed::parse(s) |
the same plus an exponent: 2e-05, 1.5E3 |
configuration values |
p.to_decimal(buf) |
exact, trailing zeros trimmed | |
Fixed::from_double(d), to_double() |
startup, diagnostics and floating-point models | |
Ratio::from_bps(double), r.to_bps() |
startup and diagnostics only |
parse rejects a value only when more decimals remain after applying the exponent than the type holds: 2e-05 is raw 2,000, 1.5e-8 is an error. TOML floats reach strategy parameters formatted by fmt (0.00002 becomes 2e-05) and Python floats through repr (Strategy API).
Quoting helpers¶
include/fastmm/strategies/quoting.hpp:
| Helper | Result |
|---|---|
mid(bid, ask) |
(bid + ask) / 2, truncated like book.mid() |
microprice(Level bid, Level ask) |
(bid * ask_qty + ask * bid_qty) / (bid_qty + ask_qty), Int128, truncated; the mid when both sizes are zero |
spread_ratio(bid, ask) |
(ask - bid) / mid as a Ratio (.to_bps() for display); zero for a non-positive mid |
away_from(ref, side, dist) |
ref - dist for a bid, ref + dist for an ask |
inventory_allows(side, position, qty, limit) |
Buy: position + qty <= limit; Sell: position - qty >= -limit; a zero limit means no cap |
keep_passive(q, best_bid, best_ask, tick) |
shifts each side's whole ladder so level 0 sits at least one tick inside the touch, keeping the spacing; bids pushed to a non-positive price are removed |
On DesiredQuotes:
| Member | Effect |
|---|---|
q.bid(px, qty), q.ask(px, qty) |
append the next level; a non-positive price or quantity, or a ninth level, is dropped and false returned |
q.uncross(tick) |
if the level-0 bid is at or above the level-0 ask, move that ask to bid + tick |
inventory_allows checks the result of the fill, not |position|: at a long limit the sell side is still allowed, and so is a reducing trade from beyond the limit when it lands inside.
Example¶
BasicMM's quoting function, from include/fastmm/strategies/basic_mm.hpp:
const BasicMMParams& p = params();
DesiredQuotes q;
if (p.quote_qty.is_zero()) return q;
const std::int64_t inventory_units = position / p.quote_qty; // signed, truncating
const Price half = mid * p.half_spread_bps;
const Price centre = mid - mid * (p.skew_bps_per_unit * inventory_units);
const Price step = inst.ticks(p.level_step_ticks);
const Qty qty = inst.round_qty(p.quote_qty);
const bool can_buy = inventory_allows(Side::Buy, position, p.quote_qty, p.max_inventory);
const bool can_sell = inventory_allows(Side::Sell, position, p.quote_qty, p.max_inventory);
for (int l = 0; l < p.levels; ++l) {
if (can_buy) q.bid(inst.round_price(centre - half - step * l, Side::Buy), qty);
if (can_sell) q.ask(inst.round_price(centre + half + step * l, Side::Sell), qty);
}
q.uncross(inst.tick);
Floating-point models¶
A model evaluated in double converts once on the way in and once on the way out, as AvellanedaStoikov::compute_quotes in include/fastmm/strategies/avellaneda_stoikov.hpp does:
| Step | Code |
|---|---|
a price or quantity to double |
mid.to_double(), inst.tick.to_double(): the value in quote currency or base units |
| a ratio of two values of one type | static_cast<double>(position.raw) / static_cast<double>(p.quote_qty.raw): the scale cancels |
a double price back to the grid |
inst.round_price(Price::from_double(r - delta), Side::Buy) |
static_cast<double>(x.raw) on its own is the value times 1e8. from_double rounds to the nearest raw unit, half away from zero, and round_price then moves bids down and asks up to the tick.
Limits¶
Ratio * kis a plain int64 multiply:skew * inventory_unitsoverflows past about 9.2e10 units at 1 bp.- A
Ratioproduct with a value truncates once.mid * a * btruncates twice; writemid * (a * b)only when the precision ofa * b(1e-8) is enough, otherwise scale in one step. - Ranges are checked where values enter (instrument load, parameter parsing, literals), not on every operation.