16 September 2026
Brokers in PixelTrader name their own candidates. Each scouting round, a model covering a sector is asked for four to eight ticker symbols, and whatever comes back gets checked against the price feed. Anything that does not price is dropped.
Reading a real session log, this is what was being dropped:
PEP. QCOM. XLY. GME. BMY. RIO. BRK.B.
Every one of those is a real, liquid, tradable stock. They were discarded because the model had finished a sentence and the parser took the full stop as part of the symbol.
candidates = [t.strip().upper() for t in raw.split(",") if t.strip()]
strip() with no argument removes whitespace. Not periods. So
"PEP." arrives, is uppercased to "PEP.", and the feed
is asked for a symbol that has never existed on any exchange.
It fails silently and correctly. The log line reads
discarded PEP. — no live price, which is true, and which reads
exactly like every genuinely dead symbol in the same list. Sitting alongside
UTX, RIMM and a dozen invented tickers, a real one
with a period on the end is invisible.
Why it hid so well: the failure was indistinguishable from the expected failure. A model naming stale tickers is a known, accepted cost of letting it choose. Once you have decided some proportion of the list will be junk, you stop reading the junk — and the bug lives in it.
The obvious fix is to strip periods. The obvious fix is wrong, because
BRK.B is a real ticker and the dot is load-bearing.
So: strip punctuation from the ends only, never the middle, then check the result is actually shaped like a US symbol:
_SYMBOL_SHAPE = re.compile(r"^[A-Z]{1,5}([.-][A-Z]{1,2})?$")
One to five letters, optionally a one or two letter class suffix. That keeps
BRK.B and RDS-A, and it rejects JD.COM
— three letters after the dot — before it costs a network round
trip.
Two more shapes turned up in the logs and got handled: $NVDA,
and numbered lists like 1. F where the marker is not part of the
symbol. A company name in brackets after the symbol is also unwrapped, but only
when a bracket is actually present. A bare Nu Holdings is still
rejected rather than guessed at from its first word — guessing there would
turn Bank of America into BANK, which is a real and
completely unrelated ticker.
The same symbols came back round after round. FB,
UTX, SQ, RIMM, SNE,
ECA — all real companies whose tickers changed after the
model's training data was collected. Facebook became META in 2022. The model
cannot know that, and asking it more firmly does not help.
What helps is telling it. Symbols that fail to price are now remembered for the session and named in the next scouting prompt as already checked and unavailable.
Behind that is a two-strike rule rather than one. A single price miss can be a network blip, and a symbol wrongly blacklisted for a whole session is a worse outcome than one extra wasted lookup. Two misses and it stops being offered.
The exception is the brokerage's own asset filter: when the broker says an asset is not tradable, that is an authoritative answer rather than a failed lookup, so it skips the strike count entirely.
Deliberately not persisted. The dead list is rebuilt each session, which costs one round of re-checking and means a symbol that was merely halted, or caught during a feed problem, gets a clean look next launch instead of being condemned forever by one bad afternoon.
In the six rounds of log I worked from, 73 distinct symbols were discarded. Sixteen of them were recoverable purely by stripping punctuation. One more was rejectable on shape before spending a request.
The rest were genuinely dead — and those were being re-requested every round by eight brokers, against a live API, for the entire session.