Python's decimal Module and Quantization Discipline
Financial arithmetic and execution-quality measurement.
§ IFrame
Today's Ops lesson named four numbers transaction-cost analysis owes the strategy: implementation shortfall, market impact, timing cost, opportunity cost. All four are computed in basis points. A basis point is one part in ten thousand. A discipline that confuses 4.21 bp with 4.21000000000001 bp will not survive review by a counterparty, an auditor, or the next morning's verdict. Python's native floating-point type, used naively, is a discipline that confuses exactly that.
The lesson is short on premise and long on consequence. The premise: monetary arithmetic in Python cannot use float without paying for it. The consequence: every TCA engine, every position ledger, every reconciliation script the operator ships must declare its precision context, perform every arithmetic operation against that context, quantize at every venue boundary, and verify quantization at every comparison. The decimal module gives the operator the tools. The operator's discipline gives the tools their meaning.
§ IILanguage Idiom — Why decimal Exists and What It Costs
Python's float is IEEE 754 double-precision binary. Sufficient for almost everything a numerical Python program ever computes. Insufficient for finance specifically because the decimal fractions money is denominated in cannot be exactly represented in binary. 0.1 + 0.2 is 0.30000000000000004 in float. A position ledger that adds 0.1 BTC and 0.2 BTC ten million times accumulates real drift the operator cannot defend.
The decimal.Decimal type is base-10. Every decimal fraction the operator can write is exactly representable. The cost is performance: Decimal arithmetic is roughly 30 to 100 times slower than float for the same operations, depending on precision context. The cost is acceptable for the analytics path the TCA engine walks; the cost is rarely acceptable for the hot inner loop of a market-data normalizer. The discipline is to choose where in the pipeline the boundary sits.
The decimal module exposes three operator-facing objects. The Decimal type itself, constructed from strings, never from float literals, which carry the float drift into the decimal. The Context object, which declares the precision and the rounding mode every operation against that context will use. The localcontext context-manager, which lets the operator scope a precision change to a block without polluting the module-level default.
The arithmetic operations the operator runs against Decimal produce Decimal results. The exceptions are operations that involve a float on one side; those raise TypeError. The discipline is to keep float out of the boundary entirely, not to convert opportunistically. Conversion is the operator's promise to surface the decision; opportunistic conversion is silent.
§ IIICode Worked Example — The TCA Engine's Decimal Discipline
The TCA engine takes fill records as input. Each fill record arrives as a dictionary from the venue API normalizer. The dictionary contains string-typed prices and quantities. The engine's first responsibility is to convert the strings to Decimal against a declared context, never through float.
Module-load-time declaration of the precision context. Twenty-eight decimal digits of significant precision is overkill for cents but cheap for the operator and protects against multi-step accumulation. Rounding mode is half-even, the banker's rounding, which removes the directional bias half-up introduces over many rounding operations.
from decimal import Decimal, Context, ROUND_HALF_EVEN, localcontext, getcontext
TCA_CONTEXT = Context(prec=28, rounding=ROUND_HALF_EVEN)
getcontext().prec = TCA_CONTEXT.prec
getcontext().rounding = TCA_CONTEXT.rounding
ZERO = Decimal("0")
BPS_PER_UNIT = Decimal("10000")
The module sets the global precision and rounding so every operation inherits them. The two named constants stand in for the magic numbers the TCA arithmetic will reach for repeatedly.
The fill-record ingest function takes the venue's string-typed dictionary and produces a typed record. Strings are converted directly. The operator never writes Decimal(record["price"]) against anything but a string-typed value the venue sent; the operator never writes Decimal(float(record["price"])), which performs the conversion to and from binary and defeats the discipline.
def ingest_fill(record: dict) -> dict:
return {
"venue_order_id": record["venue_order_id"],
"side": record["side"],
"price": Decimal(record["price"]),
"quantity": Decimal(record["quantity"]),
"fee": Decimal(record["fee"]),
"timestamp_ns": int(record["timestamp_ns"]),
}
The body has no defensive conversions. Inputs that fail the constructor raise; the upstream normalizer is responsible for ensuring string-typed inputs reach this function.
Implementation shortfall is computed against the arrival-price benchmark. Arrival price is captured at intent-receipt time and joined to the fill records by parent-intent-ID upstream. The shortfall computation operates on the joined view. The arithmetic stays in Decimal end-to-end.
def implementation_shortfall_bps(fills: list, arrival_price: Decimal, side: str) -> Decimal:
notional_filled = sum((f["price"] * f["quantity"] for f in fills), ZERO)
quantity_filled = sum((f["quantity"] for f in fills), ZERO)
if quantity_filled == ZERO:
return ZERO
vwap = notional_filled / quantity_filled
side_sign = Decimal("1") if side == "buy" else Decimal("-1")
return ((vwap - arrival_price) / arrival_price) * BPS_PER_UNIT * side_sign
The function returns a Decimal basis-point value the operator can defend. The starting ZERO in the sum calls is what prevents Python's default int(0) seed from infiltrating the accumulation and silently changing types on the first iteration. The discipline catches the most common pandas-adjacent infiltration: a sum over a generator that yields Decimal but seeds with int(0), producing Decimal results that pass type checks but have lost the operator's precision context on the first step.
Quantization is the discipline that aligns the operator's internal prices with the venue's tick convention. A venue with a tick size of 0.01 USD does not acknowledge orders priced at 4.215 USD; it rounds to 4.22 or 4.21 by the venue's own rule, which may not match the operator's. The operator's discipline is to quantize to the venue's tick before sending the order, and to verify the venue's acknowledged price matches the quantized intent on the way back.
def quantize_to_tick(price: Decimal, tick: Decimal) -> Decimal:
return (price / tick).quantize(Decimal("1"), rounding=ROUND_HALF_EVEN) * tick
def verify_tick_alignment(price: Decimal, tick: Decimal) -> bool:
remainder = (price / tick) % Decimal("1")
return remainder == ZERO
The first function maps any operator-internal price to the venue's tick grid by dividing, rounding to integer ticks, and multiplying back. The second function verifies an incoming venue acknowledgment lands cleanly on the tick grid; if it does not, the operator has either mis-modeled the venue's tick rule or the venue is acknowledging at unexpected precision. Both signals warrant alerts before the next slice fires.
Per-slice attribution decomposes the shortfall into the four named components. The function returns a typed record the analytics store can write. The arithmetic remains in Decimal; the structural fields stay typed; the operator's downstream consumers receive a record they can trust without re-validating the precision.
def attribute_slice(slice_fill: dict, arrival_price: Decimal, slice_send_price: Decimal,
side: str) -> dict:
side_sign = Decimal("1") if side == "buy" else Decimal("-1")
fill_price = slice_fill["price"]
impact_bps = ((fill_price - slice_send_price) / slice_send_price) * BPS_PER_UNIT * side_sign
timing_bps = ((slice_send_price - arrival_price) / arrival_price) * BPS_PER_UNIT * side_sign
fee_bps = (slice_fill["fee"] / (fill_price * slice_fill["quantity"])) * BPS_PER_UNIT
return {
"venue_order_id": slice_fill["venue_order_id"],
"impact_bps": impact_bps.quantize(Decimal("0.01")),
"timing_bps": timing_bps.quantize(Decimal("0.01")),
"fee_bps": fee_bps.quantize(Decimal("0.01")),
"total_bps": (impact_bps + timing_bps + fee_bps).quantize(Decimal("0.01")),
}
The output is quantized to two decimal places for the reporting layer. The two-decimal quantization is the operator's contract with the dashboard: the report reads in basis points to one hundredth. The internal arithmetic ran at twenty-eight digits; the output discipline truncates to what the operator's eye can usefully read.
Decimal is constructed from strings, never from float. Second, the precision context is declared at module-load time, not per-call. Third, sum over a Decimal generator must seed with a Decimal zero, not int(0). Fourth, quantize-to-tick before sending and verify-tick-alignment on receipt; equality replaces tolerance comparison.
§ IVConnection to Today's Ops Lesson
The Ops lesson named what TCA owes the strategy: four numbers and a verdict. Today's Dev lesson named what Python owes the TCA engine: a precision context, a tick-alignment discipline, a quantization boundary at every venue interface, a constructor discipline that keeps float out. The composition is one engine.
The Ops lesson's worked example reported shortfall numbers to one decimal place — 4.2 bp, 5.7 bp, 3.9 bp. The arithmetic that produced them ran at twenty-eight digits of precision through Decimal and quantized to two decimals at the reporting boundary. The reporter cannot defend 4.21 bp if the engine computed it through float; the report would lose the audit at the first counterparty inquiry. The Dev discipline is what permits the Ops discipline to file a verdict the operator can stand behind.
The Ops dangerous-day example caught a 4-fold rise in timing cost. The Dev lesson's precision context is what permitted the attribution to separate 3.9 bp of timing from 2.6 bp of impact at all. A float-based engine could have reported total shortfall accurately enough at the strategy level and missed the decomposition entirely, because float drift accumulates differently across the four sub-components and the residual would have absorbed the timing rise into noise.
§ VPrior-Lesson Reach
The 2026-05-18 iterator-protocol lesson named lazy evaluation as Python's discipline for streaming inference. The TCA engine reads fill records as a stream from the analytics store; the same iterator discipline that fed the streaming-inference example feeds the per-fill attribution loop. The TCA engine does not load the day's fills into memory and then iterate; it iterates as the analytics store yields, and the attribution function is called per yield.
The 2026-05-27 secrets-module lesson named module-load-time as the canonical place to declare cryptographic and credential context. The same discipline applies to decimal.Context: the precision and rounding are declared at module load, not per call. The module-load-time declaration is the operator's promise that every arithmetic operation in the module uses the same precision context, with no per-call drift.
The 2026-05-30 validator-telemetry lesson named the async-RPC polling pattern for upgrade detection and the quantized comparison the operator uses to decide whether a voting-power change crosses a threshold. The quantization there was on integer-tick basis exactly the same way the TCA engine quantizes on tick-aligned prices. The pattern travels across domains: integer-grid representation lets equality comparisons replace tolerance comparisons, which removes a class of false positives the operator would otherwise have to chase.
§ VIClosing
Decimal is the floor under every financial-arithmetic discipline Python touches. The floor is not free; it costs CPU. The cost is paid because the alternative — a float-based TCA engine producing reports the operator cannot defend — is what loses the audit the first time a counterparty asks the operator to walk through how 4.21 bp was computed. The cost of the floor is one of the cheapest insurance premiums the operator pays.
Quantization is the discipline that makes the floor structurally sound. A Decimal arithmetic without quantization to venue ticks produces prices the venue rounds away from the operator's intent. The rounding-away is silent. The TCA report attributes the cost to market impact when the cost was the operator's own quantization failure. The discipline is to quantize before sending and to verify on receipt.
The operator who reads only the Ops lesson today ships a TCA engine that computes the wrong numbers precisely. The operator who reads only the Dev lesson today ships a precision context with no verdict attached. The trio composes. The composition is the discipline.
Examine well. Reflect on this.