The “Just Recompute It” Trap
Every engineer who’s touched pandas.DataFrame.ewm() reaches for the same first draft of a live MACD system: keep a growing list of prices, and on every new tick call .ewm(span=12).mean() and .ewm(span=26).mean() over the whole thing, subtract, then .ewm(span=9).mean() the result for the signal line. It’s four lines. It matches every textbook formula. It passes in a notebook against historical data instantly.
It also puts an O(n) operation — actually two O(n) operations plus a third over the derived series — on the critical path of every single tick, for the entire lifetime of the process. A backtest calls this once per bar over a fixed, bounded dataset. A live system calls it once per tick, forever, against a series that only grows. That’s not a performance nitpick; it’s a structural mismatch between what .ewm() is designed for (batch, vectorized, one-shot) and what a tick handler needs (incremental, O(1), millions of calls per session).
The Failure Mode
Run the numbers and the shape of the problem is obvious. Benchmarking NaiveRecomputeMACD (full .ewm() recompute per tick) against a correctly-implemented O(1) streaming engine on the same synthetic tick stream:
ticks streaming (s) naive (s) speedup 500 0.0018 0.1793 101x 1,000 0.0028 0.3742 135x 2,000 0.0058 0.9303 160x 4,000 0.0112 1.9219 172x 8,000 0.0236 4.8588 206x
The streaming engine is flat. The naive recompute grows — and the speedup ratio itself keeps climbing, because the naive path is doing strictly more work per tick as history accumulates while the streaming path never does more than three scalar multiply-adds. At market open volumes this is the difference between a handler that keeps up with the tape and one that silently falls behind, queues ticks, and starts trading on stale state without ever raising an exception.



