The “Dict of Lists” Trap
Here’s how most engineers build their first multi-asset position tracker: a dict[str, list[Fill]]. Every ticker maps to a list of raw fills. Need the current position in AAPL? Loop the list, sum the signed quantities. Need the average cost? Loop again, this time weighting price by quantity. Need unrealized P&L across the whole book for the risk dashboard? Loop every ticker’s list, every fill, every time.
It works in a demo. You backtest against 50 fills across 5 tickers and it feels instant. Then you go live on paper trading with a mean-reversion strategy scalping 20 names, generating a fill every few seconds during the open. By hour two you have 4,000 fills. Your risk loop — which recomputes exposure on every tick to check margin — is now doing a full list traversal per ticker, per tick, across the whole book. That’s not O(1) work happening on a timer. That’s O(n) work per ticker multiplied by O(m) tickers, re-executed on every price update. Your event loop starts missing ticks. Your stop-loss check, which depends on knowing current exposure right now, is reading stale state because the recompute hasn’t finished. This is how naive architectures lead to account ruin — not through a bad trade, but through a risk system that can’t keep up with its own book.
There’s a second, quieter failure hiding in the same code: summing fill prices as Python float. 0.1 + 0.2 != 0.3 isn’t a curiosity when you’re averaging cost basis across 4,000 fills — it’s basis-point drift that compounds silently into your realized P&L, and you won’t notice until reconciliation against your broker statement fails by a few cents that don’t map to any single trade.
The Failure Mode, Precisely
Two independent defects stack on top of each other:
Algorithmic complexity. Recomputing position and average cost from full fill history on every query is O(n) per ticker. If your risk loop runs on every quote tick (common at 1–10Hz per symbol) across a 20-symbol book with a growing fill history, your aggregation cost grows unbounded over the trading session. This is the classic mistake of treating a streaming problem as a batch problem.
Numerical representation.
floatuses binary floating point, which cannot exactly represent most base-10 decimals. Price arithmetic (147.83 * 100 shares) accumulates representation error. Over thousands of fills, that error doesn’t cancel — it drifts, usually in one direction, because rounding in weighted-average formulas is systematically biased toward the operation order.
Both defects share a root cause: treating position state as derived on demand instead of maintained incrementally.



