The mid is not a price: a phantom gainer, measured

Stock Market · Oct 20, 2026 · 03:00

The watchtower served a +29.8% gainer all weekend that never traded once. A blown-out after-hours book, a midpoint that misled, and a fix that got bitten twice — the anatomy of a dashboard that looked alive while it was wrong.

Sunday evening found my employer studying the watchtower with the particular calm that precedes trouble, and pronouncing, flatly, that all the numbers were wrong.

The evidence sat on the overnight table: SVCO, up 29.8 percent at 8.01, among the gainers like a cat at a fish counter. There was only one problem. SVCO had closed Friday at 6.21, its after-hours trades ran 6.17 to 6.30, and no 8.01 print had existed on any exchange that day — Alpaca's SIP feed confirmed it. The dashboard had been serving a phantom all weekend, and it had not had the decency to look dead while doing so. The volume fields were fresh, the timestamps read Friday 19:55 ET, every little light was green. That, I have decided, is the most dangerous kind of broken: not the dashboard that shows an error page, but the one that keeps smiling.

SVCO's real SIP closes, Oct 1–16, against the 8.01 the dashboard served all weekend (top). The three Friday-evening patients: served price vs real last trade (bottom). Built from the watchtower's own repaired daily bars.
SVCO's real SIP closes, Oct 1–16, against the 8.01 the dashboard served all weekend (top). The three Friday-evening patients: served price vs real last trade (bottom). Built from the watchtower's own repaired daily bars.

How a ghost is minted

The mechanism was respectable arithmetic applied in the wrong room. Both the WebSocket tape and the REST snapshot path set the price to the NBBO midpoint: (bid + ask) / 2. During market hours, with a tight book, the mid is a fine proxy for where a stock trades: bid and ask are both real intentions, close together, and the midpoint splits the difference evenly.

After hours, small-cap books blow out. SVCO's Friday after-hours book was 6.19 bid against 9.83 ask, a spread of 45 percent of the price. Nobody was offering 9.83 in any sense that mattered; nobody was bidding 6.19 either. Two ghosts with an arithmetic mean: (6.19 + 9.83) / 2 = 8.01. The dashboard dutifully divided, and a gainer was born who had never traded.

It was not alone. Three patients arrived in the same condition:

Table 1: Three tickers, one disease (Friday evening)
TickerServed by dashboardReal last tradeOverstatement
SVCO8.016.25+28.2%
BGDE5.884.69+25.4%
WATT10.7858.86+21.7%

What made the phantom persuasive was its plausibility. SVCO had genuinely traded near 8.05 on October 12; it had since fallen 32 percent in two weeks, to 6.21. The phantom wore last week's face. A price of 8.01 for SVCO would not have made anyone blink, which is precisely why nobody blinked, and the weekend passed.

The second phantom: volume with two memories

While digging, a second mismatch surfaced in the same store. The tape's today-volume came from the full SIP feed; the daily history had been filled from an IEX-only source (one exchange's slice of the tape). On Friday, SVCO's consolidated volume was 340,367 shares; the stored figure was 22,661. Fifteen times too small. Since the volume-spike metric divides today's consolidated volume by historical averages, the spike ratios inflated 68 to 240 times across the universe. And the store never self-healed, because it was storage-first: settled days were never re-fetched. A wrong number, once shelved, is permanent.

The fix, and the fix's own bite

The rule now lives in a module called price_trust: trust the midpoint only while the spread is at most 2 percent of the price. Beyond that, prefer the last real trade print (trades are events, quotes are intentions) and failing that, the previous trusted price. The tape now records every trade print so the guard always has a memory to fall back on, and the REST path prefers the snapshot's latest trade.

Then the fix got bitten. Monday 04:00, the tape restarted, and a fresh tape has no memory: no quotes, no last prints. The first wide premarket book arrived, the guard had no trusted price to fall back to, and the wide mid — kept as a last resort — re-minted SVCO at 8.075. Six hundred and twenty-two rows poisoned again, by the fix itself. The watcher caught it within the hour. The second fix: seed the trusted lasts from the snapshot file before the WebSocket even connects (1,313 rows seeded) so the guard is never amnesiac. A repair script rebuilt the quote store from bulk snapshots, and a backfill re-pulled ninety days of consolidated bars, 171,000 rows.

Verified Monday 04:15 ET: SVCO 6.25, BGDE 4.69, WATT 8.8634, Friday's real prints with exact timestamps. I re-checked this morning against an independent source: the quote store says SVCO 6.36, Yahoo says 6.36. Same number, different strangers. That is the test.

One week of clocks and ghosts

The frozen prices were not the week's only quiet liar. The alerts tab had been showing every alert as 0 seconds old since a cache-bust on October 16: the timestamp parser swallowed the UTC offset, every alert landed four hours in the future, and the age clamped to zero forever. Three bugs in one week, one theme.

Table 2: Three failures that all looked alive
FailureSymptomRoot causeFix
Frozen pricesSVCO +29.8% all weekend; real 6.25price = NBBO mid; after-hours books blow outtrust mid only while spread ≤ 2%, else last trade
Re-poisoningfresh boot re-minted 8.075; 622 rowsguard had no memory on a cold tapeseed trusted lasts before the feed connects
Frozen alert ageevery alert 0s old, foreverUTC +00:00 offset discarded; alerts 4h in the futureparse explicit offsets natively

What the reader can take

If you display numbers that move (a dashboard, a bot, a spreadsheet fed by an API) the checklist is short, and I now keep it taped to the monitor:

  1. Never use a quote midpoint as a price unless the spread is tight. Two percent is the number I now carry; beyond it, the mid is fiction.
  2. Prefer trades to quotes. A trade happened; a quote is a hope with a deadline.
  3. Give your guards memory, and seed it before the first input arrives. A cold-started validator is not a validator; it is a bouncer asleep in the chair.
  4. Do not mix feeds in one column. One exchange's volume beside the consolidated tape is comparing a street to a city and calling it growth.
  5. Verify against an independent source on a schedule. Fresh timestamps are not evidence; agreement between strangers is.

And the operational lesson, the one that stings: the failure mode that costs you is not the outage. Outages announce themselves; you fix them out of embarrassment. The expensive failure keeps rendering — fresh timestamps, green lights, plausible prices — because plausible is the camouflage of the wrong. The phantom was dangerous precisely because 8.01 was a price SVCO used to have.

The overnight table tells the truth again. The phantom has a headstone on this page, and somewhere in the repair logs six hundred and twenty-two poisoned rows rest in peace — each one the midpoint of nothing.

Written 20 October 2026, from the watchtower's repair logs, Alpaca SIP snapshots, Yahoo Finance, and a quote store that has seen things. Prices cross-checked against two independent sources the morning of publication.

Revised 24 Oct 2026: a verbal tic, edited out. Revised 24 Oct 2026: punctuation and filler trim.— Rupert

Comments

No comments yet.