Skip to content

Trade Execution: Derived Financial Quantities and Fixed-Point Arithmetic

Try it live on Compiler Explorer

Overview

This example models the execution of a buy order filled in multiple tranches and computes the average fill price (cost basis) at several settlement precisions. It demonstrates two ideas that do not appear in the currency example:

  1. Derived financial quantitiesnotional_value (price × market quantity) is a separate quantity specification, distinct from currency, enforced by the type system.
  2. Integer fixed-point arithmeticprices are scaled to USD_8 (units of $10⁻⁸) and stored as std::int64_t to avoid floating-point rounding during accumulation.

Key Concepts

Derived Quantity Specifications: market_quantity and notional_value

In finance, notional value = price × market quantity. This is not the same quantity as currency: you cannot directly add a price to a notional value, and dividing notional value by market quantity should recover currency, not notional value. The example encodes this with derived quantity specifications:

// clang-format off
inline constexpr struct dim_currency final : base_dimension<"$"> {} dim_currency;
// clang-format on

QUANTITY_SPEC(currency, dim_currency);
QUANTITY_SPEC(market_quantity, dimensionless, is_kind);
QUANTITY_SPEC(notional_value, currency, currency* market_quantity, is_kind);

inline constexpr auto Qty = market_quantity;

Both market_quantity and notional_value carry is_kind, making each the root of its own kind family:

  • market_quantity with is_kindmarket quantity is its own kind, distinct from generic dimensionless quantities (ratios, percentages, etc.). Without it, a market quantity would silently convert to a plain number.
  • notional_value with is_kindnotional value is its own kind, distinct from currency. Without it, a notional value would silently convert to a plain currency amount, defeating the type safety the derived quantity is meant to provide.

is_kind on market_quantity disables implicit construction from plain integers, so a Qty helper alias is provided: Qty(137) calls the quantity spec directly as a factory.

The relationship notional_value = currency × market_quantity is the defining equation; explicit casts still work in both directions via that equation. The static_asserts verify the intended semantics at compile time:

static_assert(!implicitly_convertible(notional_value, currency));
static_assert(!implicitly_convertible(currency, notional_value));
static_assert(explicitly_convertible(notional_value, currency));
static_assert(explicitly_convertible(currency, notional_value));

Integer Fixed-Point Units

Floating-point arithmetic accumulates rounding error across many fills. Instead, prices are stored as integers in a fine-grained unit:

// clang-format off
inline constexpr struct us_dollar   final : named_unit<"USD",   kind_of<currency>>             {} us_dollar;
inline constexpr struct us_dollar_2 final : named_unit<"USD_2", mag_power<10, -2> * us_dollar> {} us_dollar_2;
inline constexpr struct us_dollar_4 final : named_unit<"USD_4", mag_power<10, -4> * us_dollar> {} us_dollar_4;
inline constexpr struct us_dollar_6 final : named_unit<"USD_6", mag_power<10, -6> * us_dollar> {} us_dollar_6;
inline constexpr struct us_dollar_8 final : named_unit<"USD_8", mag_power<10, -8> * us_dollar> {} us_dollar_8;
// clang-format on

USD_8 represents $10⁻⁸ (one hundred-millionth of a dollar). A price of $12.95 becomes 1,295,000,000 USD_8 — an exact integer. Multiplying by a market quantity count gives an exact integer notional value. No floating-point arithmetic is involved until the final display.

Type Aliases and the Fill Struct

// Prices are affine: $0 is a meaningful absolute reference, not an arbitrary offset
using Price = quantity_point<currency[us_dollar]>;
using Shares = quantity<market_quantity[one], int>;
using Notional = quantity<notional_value[us_dollar_8], std::int64_t>;

struct Fill {
  Price price;
  Shares qty;
};
  • Price — a quantity_point (price has an absolute zero; $0 is meaningful)
  • Shares — a dimensionless market quantity count
  • Notional — the derived notional value in USD_8
  • Fill — groups a price and market quantity count together; the natural unit of trade data

Code Walkthrough

Computing Notional: force_in for Lossless Scaling

[[nodiscard]] Notional notional(const Fill& f)
{
  const quantity price_in_usd8 = f.price.quantity_from_zero().force_in<std::int64_t>(us_dollar_8);
  return price_in_usd8 * f.qty;
}

force_in<std::int64_t>(us_dollar_8) converts the price unit from USD to USD_8 and changes the representation from double to std::int64_t in one step. The product price_in_usd8 * f.qty yields a derived expression (currency × market quantity) that is implicitly convertible to notional_value; the return converts it to Notional.

Accumulating Fills

  // Three partial fills of a single buy order
  const std::array fills = {
    Fill{Price{12.95 * USD}, Qty(137)},
    Fill{Price{12.40 * USD}, Qty(126)},
    Fill{Price{12.70 * USD}, Qty(85)},
  };

  // Notional = price × shares; the derived 'notional_value' quantity spec enforces this
  // Notional bad = fills[0].price.quantity_from_zero() + fills[1].price.quantity_from_zero();  // does not
  // compile

  Notional total_notional = {};
  Shares total_qty = {};
  for (const auto& f : fills) {
    total_notional += notional(f);
    total_qty += f.qty;
  }

Each fill is constructed with an explicit Price{...} (the constructor is explicit, so direct initialization is required) and Qty(137) (the market_quantity spec called directly as a factory, required because is_kind disables implicit construction from plain integers). The accumulator variables total_notional and total_qty are zero-initialized via copy initialization from {}. The notional_value quantity specification ensures that adding two price quantities does not accidentally produce a notional value — the // does not compile comment shows what the type system prevents.

Recovering the Average Fill Price

  // Dividing notional by shares recovers currency (not notional_value), enforced at compile time
  // Notional bad = total_notional / total_qty;  // does not compile
  const quantity cost_basis_raw = currency(total_notional / total_qty);
  const Price cost_basis{cost_basis_raw};
  const Price cost_basis_r2{currency(round<USD_2>(cost_basis_raw))};
  const Price cost_basis_r4{currency(round<USD_4>(cost_basis_raw))};
  const Price cost_basis_r6{currency(round<USD_6>(cost_basis_raw))};
  const Price cost_basis_r8{currency(round<USD_8>(cost_basis_raw))};

currency(total_notional / total_qty) performs an explicit quantity cast to the currency specification — without it, the type system sees notional_value / market_quantity as an unreduced derived expression and round<USD_N> cannot match it. The cast is required.

Rounding is then applied at four different settlement precisions so the output shows how much information is retained at each level.

Example Usage

int main()
{
  using namespace unit_symbols;

  // Three partial fills of a single buy order
  const std::array fills = {
    Fill{Price{12.95 * USD}, Qty(137)},
    Fill{Price{12.40 * USD}, Qty(126)},
    Fill{Price{12.70 * USD}, Qty(85)},
  };

  // Notional = price × shares; the derived 'notional_value' quantity spec enforces this
  // Notional bad = fills[0].price.quantity_from_zero() + fills[1].price.quantity_from_zero();  // does not
  // compile

  Notional total_notional = {};
  Shares total_qty = {};
  for (const auto& f : fills) {
    total_notional += notional(f);
    total_qty += f.qty;
  }

  // Dividing notional by shares recovers currency (not notional_value), enforced at compile time
  // Notional bad = total_notional / total_qty;  // does not compile
  const quantity cost_basis_raw = currency(total_notional / total_qty);
  const Price cost_basis{cost_basis_raw};
  const Price cost_basis_r2{currency(round<USD_2>(cost_basis_raw))};
  const Price cost_basis_r4{currency(round<USD_4>(cost_basis_raw))};
  const Price cost_basis_r6{currency(round<USD_6>(cost_basis_raw))};
  const Price cost_basis_r8{currency(round<USD_8>(cost_basis_raw))};

  std::cout << "Fills:\n";
  for (const auto& f : fills) std::cout << "  " << f << "  (notional: " << notional(f) << ")\n";
  std::cout << std::setprecision(12);
  std::cout << "Total:              " << total_qty << " shares,  notional: " << total_notional << "\n";
  std::cout << "Cost basis (exact): " << cost_basis << "\n";
  std::cout << "Cost basis (USD_2): " << cost_basis_r2 << "\n";
  std::cout << "Cost basis (USD_4): " << cost_basis_r4 << "\n";
  std::cout << "Cost basis (USD_6): " << cost_basis_r6 << "\n";
  std::cout << "Cost basis (USD_8): " << cost_basis_r8 << "\n";
}

Sample Output:

Fills:
  137 @ 12.95 USD  (notional: 177415000000 USD_8)
  126 @ 12.4 USD  (notional: 156240000000 USD_8)
  85 @ 12.7 USD  (notional: 107950000000 USD_8)
Total:              348 shares,  notional: 441605000000 USD_8
Cost basis (exact): 12.68979885 USD
Cost basis (USD_2): 12.69 USD
Cost basis (USD_4): 12.6898 USD
Cost basis (USD_6): 12.689799 USD
Cost basis (USD_8): 12.68979885 USD

The notional values are large integers in USD_8; the cost basis is recovered as a human-readable USD value at varying precisions.

Why This Matters

  • Derived Quantities: The notional_value specification makes the price × market quantity algebra compile-time-safe — you cannot confuse a notional value with a price or a plain currency amount
  • Kind Safety: is_kind on both market_quantity and notional_value ensures that market quantity and notional value do not silently decay to their parent kinds (dimensionless and currency respectively)
  • Lossless Accumulation: Integer arithmetic in USD_8 avoids floating-point drift across many fills; only the final display converts to floating-point
  • Explicit Rounding: Rounding to a specific settlement precision (USD_2, USD_4, USD_6, USD_8) is an explicit, visible operation — not hidden inside a helper
  • Domain Modeling: The Fill struct groups related quantities, and operator<< provides domain-appropriate output (shares @ price)

The currency example focuses on multi-currency type safety and FX conversion. This example focuses on single-currency arithmetic precision. Together they cover the two main concerns in financial quantity modelling.