Design proposal · Tickerfairy

Grounding the decision in performance and news, without letting either one seduce you

Two failure modes are in scope: acting on feeling, and sounding certain without evidence. They are not the same problem. Overconfidence is cured by a gate that caps what can be claimed. Emotion is cured somewhere else entirely — because the news channel you want the narrative grounded in is precisely the channel emotion arrives through.

The trap

Sentiment scoring is an emotion amplifier

The intuitive design reads the news, scores its tone, and adds that score to the decision. That design does the opposite of what you asked for. Positive coverage clusters exactly where hype is thickest, so a tone-weighted model buys most eagerly at the moment of peak narrative.

Here is the actual SPCX feed retrieved today. Ten items, nine of them dated 2026-08-07:

Live news feed · SPCX · classified by evidentiary weight
HeadlineSourceTier
"Big week: Stock jumps despite earnings storm, lock-up expiration"Yahoo FinanceA — hard event
"So Much for Those End-of-Lockup SpaceX Fears"WSJA — hard event
"SpaceX Stock Lands An Upgrade, Rocket Lab Surges"IBDB — crowd
"Soars 13% After Analyst Upgrade Says AI Spending Could Pay Back"BenzingaB — crowd
"Gets Jaw-Dropping Street High Price Target with 560% Upside"GuruFocusB — crowd
"Top Analyst Drops Sharp Take on SpaceX Stock"GuruFocusB — crowd
"Elon Musk issues red flag warning to traders — but they keep doubling"MoneywiseC — noise
"SpaceX's influence ripples across markets: AlphaCheck"Yahoo FinanceC — noise
"SpaceX, Doximity, Trade Desk, Rigetti… Stocks That Explain Today's"Barron'sC — listicle
"Weekly Wrap: Bitcoin Hit By Coldcard Hack And Clarity Act"CryptoProwlC — unrelated
Eight of ten items carry no evidence about the company

Four are second-hand opinion about what other people think the stock is worth. Four are noise, one of which is a Bitcoin roundup that mentions SPCX nowhere. A tone model reads this feed as strongly bullish. An evidence model reads it as two facts — a lockup expired and the market absorbed it — surrounded by eight items of ambient enthusiasm.

The other trap

The same price series tells opposite true stories

Performance data is not emotionally neutral either. Window choice and reference-point choice are where feeling enters the numbers, and both are invisible in the output.

Return over 5 days
+22.8%
"Breaking out."
Return over 21 days
−12.5%
"Rolling over." Same stock, same day, opposite thesis.
Drawdown from high
−37.0%
Anchored on $211.39 set on day 4 of trading — an IPO pop, not a level the business ever justified.
Price distribution percentile
41st
The same position stated without a reference point. Unemotive, and more informative.

Every one of those four numbers is arithmetically correct. Three of them are framing devices. The harness's job is to make the framing itself a controlled variable rather than an authorial choice.

Architecture

Two channels, quarantined, then joined

Performance and news enter separately under different rules, and neither produces a conclusion alone. The decision lives in their intersection.

Channel P — price

Deterministic, multi-window, reference-free

Computed from bars, never from info fields. Three standing rules: every return is reported over all windows simultaneously, never one; position is expressed as a distribution percentile, never as distance from a high or a low; and no statistic may be emitted whose window exceeds the observation count.

Channel F — filings

Primary source, no model involved

Six months of dated SEC disclosures. A filing is primary by definition, so the form type is the classification — no LLM, and the channel keeps working when the local model is down. This is what gives the join reach: EDGAR spans 182 days where the news feed spans two.

Channel N — news

Event extraction, not sentiment

The LLM's job is to turn headlines into dated, typed, scoped events — not to score mood. Tone is extracted, recorded, and deliberately excluded from the score. Each event is tiered A, R, B or C; only A and R can explain a move, and only A can raise the verdict.

event = {
  "date": "2026-08-07",
  "type": "lockup",               # filing | earnings | contract | operational | lockup |
                                  # analyst_action | insider | regulation | geopolitics |
                                  # macro | opinion | unrelated
  "tier": "A",                    # A company-specific & primary   -> evidence
                                  # R regulation/policy reaching it -> explains, never scores
                                  # B crowd (analyst opinion)       -> never evidence
                                  # C noise                         -> dropped, counted
  "entity": "direct",             # direct | mentioned | absent
  "scope": "company",             # company | sector | market | none
  "primary": true,                # a filing, transcript or official announcement?
  "tone": "positive",             # recorded, NEVER scored
  "price_target": false,          # presence raises the hype counter
}

Tier B is the important one. An analyst upgrade is a real fact about analysts and no fact at all about the company. Filing it as crowd position rather than evidence is what stops the harness from mistaking consensus for information — and consensus is what most retail emotion is actually made of.

Tier R is the one that took a second pass to get right — see below. It carries regulation and policy that reaches the ticker or its sector, which is the only way a Chinese internet crackdown can explain a BABA move on a day BABA is never named.

The join

Match every move to an event, and report what doesn't match

This is the mechanism that grounds the narrative in both channels at once. Align each material price move to events in its window and one of three things is true — and the third is the one that prevents storytelling.

Event–response join · SPCX · moves > 9% on elevated volume
DateMoveVolumeMatching eventClassification
2026-06-15+19.6%3.1×none in feedUnexplained
2026-06-16+9.8%2.4×none in feedUnexplained
2026-06-22−16.4%2.1×none in feedUnexplained
2026-08-04+9.4%1.8×none in feedUnexplained
2026-08-05−13.6%2.6×lockup expiry approach (A)Explained — hard
2026-08-07+15.8%3.0×analyst upgrade (B)Explained — crowd only

The conclusion falls straight out of the table and requires no interpretation: four of six material moves have no identifiable cause, and the single largest recent gain is attributable only to a Tier B crowd event. That is a defensible, evidence-grounded statement about a stock currently being described in the press as soaring. It is also unavailable to any system that reads price and news in separate panels — which is what the app does today.

An honest limitation to build around

yfinance returns ten news items, nine dated today. A 39-day event history cannot be reconstructed from it, so "unexplained" for June really means "no event in the retrievable feed." The harness must either accumulate news into a local store daily, or state the news window alongside every join. Gate rule G7 below enforces the second while you build the first.

Countermeasures

One mechanism per bias

Each guard is a few lines of deterministic code, not a line in a prompt. A prompt asking a 9B model to avoid recency bias is a wish; dropping every single-window claim at the validator is a control.

Emotional failure modes and their mechanical guards
BiasIts tell in SPCX todayMechanism
Recency5d +22.8% vs 21d −12.5%All windows emitted together; a claim citing one window is dropped
Anchoring"−37% from its high" of $211.39, set on day 4High/low-water framing banned; percentile substituted
Narrative fallacy9 of 10 items published on one dayUnexplained-move slot is mandatory in the output
HerdingUpgrade → +15.8%; peers carry "Strong Buy" ratingsAnalyst actions and ratings are Tier B, excluded from score
FOMO / hype"560% upside" street-high targetPrice targets never ingested; each occurrence increments a hype counter
ConfirmationBull and bear return the same schema under the same weight cap
Fluency as confidenceConfidence is computed by the gate; the model is never asked how sure it is
The inversion that matters most

When the Tier B share is high, tone is strongly positive, and history is thin, the harness raises the abstention floor. Enthusiastic coverage of a barely-listed stock is evidence that the price is being set by mood, and mood is the thing you are trying to exclude. Positive sentiment must lower confidence, not raise it. Every off-the-shelf sentiment integration wires this backwards.

Pipeline

Where the two channels sit

0

Resolve deterministic

Query to canonical instrument, including first trade date — the field the gate depends on and the app never fetches. Separates SPCX from ARKX and UFO, which all match a "space" query but are not the same kind of object.

1

Gather P, F and N parallel

Channel P computed from bars. Channel F pulled from EDGAR, where the form type is already the classification. Channel N extracted to typed, scoped events. None returns a bare number: every value carries its window and observation count.

2

Gate deterministic · load-bearing

Emits the capability manifest — which claim types the data supports — plus a confidence ceiling and the abstention floor, adjusted upward by the hype counter.

3

Join deterministic

The event–response table above. Produces three sets: explained moves, unexplained moves, unpriced events. This is the only stage that sees both channels at once, and it is arithmetic, not judgment.

4

Analyze and challenge LLM · narrow

Analysts fill narrative slots from the join, citing evidence IDs. A bear pass returns falsifiers. Context stays small by necessity — LM Studio splits its window across four slots, leaving roughly 2048 tokens each.

5

Adjudicate and record deterministic

Weights zeroed by the gate, renormalized; below the floor the answer is abstention. Everything is written to an append-only audit row so the harness itself can be backtested later.

Stage 2 in detail

The gate

Seven rules firing on coverage metadata alone. A rule never inspects a value, only whether that value is entitled to exist.

Gate rules and their effect on SPCX
IDTriggerForbidsSPCX
G1n_obs < 252Year-scale claims: 1-year return, 52-week range, annualized volFires 39
G2n_obs < 60Beta, index correlation, any two-asset statisticFires 39
G3horizon > n_obsForecast presented as inferential rather than extrapolationFires 126>39
G4trailing_pe is None and margin < 0Earnings-multiple comparison; forces EV/S with the substitution statedFires −35.7%
G5peer_method == "sector_top_by_weight"Relative-value conclusionsFires
G6abs(mcap/(shares×price) − 1) > 0.05Per-share and size-derived claims until resolvedFires 71.3%
G7evidence_span < 0.5 × 182dCausal claims about moves outside the evidence windowFires 81d of 182d
The output

A narrative that cannot omit its own gaps

The narrative is slot-filled, not free prose. Each slot declares which channel may fill it, and two slots are mandatory — the model cannot produce a clean story by leaving them empty.

1 · What happenedChannel P
SPCX trades at $133.11, its 39th session since listing on 2026-06-12. Returns are +22.8% over 5 days, +15.7% over 10, −12.5% over 21, and −17.3% since listing. The price sits at the 41st percentile of its own short trading range.
2 · What explains itChannel N, Tier A only
One hard event is identifiable: the IPO lockup expired this week, preceded by a −13.6% session on 2.6× volume and followed by recovery. The market absorbed the supply without sustained damage.
3 · Crowd positionChannel N, Tier B — not evidence
Four of ten retrieved items are second-hand analyst opinion, including an upgrade and a street-high target implying 560% upside. These describe what analysts think, not what the company did. The 2026-08-07 gain of +15.8% coincides with the upgrade and has no Tier A event behind it.
4 · UnexplainedMandatory
Four of six material moves have no identifiable cause — +19.6%, +9.8% and −16.4% in June, +9.4% on 4 August — all on 1.8–3.1× volume. The retrievable news window is one day against a 39-day price history, so absence of cause here is absence of data, not evidence of randomness.
5 · Cannot be saidMandatory
No one-year return, 52-week range, beta or annualized volatility exists for this instrument. No earnings multiple (TTM margin −35.7%). No peer comparison — the retrieved peers are index heavyweights, not comparables. Market cap is disputed by 71% against shares × price, so no per-share claim is available.
{
  "decision": "INSUFFICIENT_EVIDENCE",
  "surviving_weight": 0.40,          # floor 0.60; raised to 0.72 by hype counter
  "hype_counter": 5,                 # 4 Tier-B items + 1 price target cited
  "emotional_risk": "elevated",
  "basis": "Price action is grounded; causation is not. The only hard event
            in the window is a lockup expiry. The largest recent move is
            attributable solely to analyst opinion.",
  "revisit_when": [
    {"trigger": "First 10-Q filed",   "unlocks": ["trend.multi_quarter"]},
    {"trigger": "n_obs >= 60",        "unlocks": ["risk.beta", "vol.annualized"]},
    {"trigger": "30d news store",     "unlocks": ["join.causal_claims"]}
  ]
}

Abstention is not indecision. Hold says you assessed the instrument and found it fairly priced. INSUFFICIENT_EVIDENCE says the instrument cannot yet be assessed by this method — and names the three events that would change that. Collapsing the two is how a tool ends up sounding most confident precisely where it knows least.

Prototype results

What happened when it was actually run

Stages 0–3 and 5 were implemented against the live endpoints and run on SPCX, with AAPL as a control. A harness that abstains on everything is worthless, so the first thing to check is that it discriminates.

Prototype output · 2026-08-08
TickerGate firedSurvivingFloorDecision
SPCX · 39 sessions G1–G7 (all)0.400.72 Insufficient evidence
AAPL · 11,505 sessions G5 only0.860.62 Rate

The floor moves too: SPCX's five Tier B items and one cited price target lift it from 0.60 to 0.72, so the abstention is driven partly by the enthusiasm surrounding the name rather than by thin data alone. That is the inversion working as designed.

1 · What happenedChannel P
SPCX trades at $133.11 on session 39 since 2026-06-12. Returns: 5d +22.8%, 10d +15.7%, 21d −12.5%, since listing −17.3%. Price sits at the 41st percentile of its trailing 39-session range.
2 · What explains itTier A only
No primary-source events in the retrievable news window.
3 · Crowd positionTier B — not evidence
5 of 10 news items are second-hand analyst opinion, 1 citing a price target. These describe what analysts think, not what the company did. Moves attributable only to crowd events: 2026-08-07 +15.8%.
4 · UnexplainedMandatory
All 4 material moves inside the evidence window map to an event — three to 8-K filings, one to an analyst upgrade. Nothing is now unassessable, but see the caveat below: filing adjacency is a weaker causal claim than it looks.
5 · Cannot be saidMandatory
G1–G7, verbatim: no year-scale claims, no beta, no inferential forecast, no earnings multiple, no relative value, no per-share claims, no causal claims outside the evidence window.

Five defects the run exposed

The deterministic stages behaved. Every defect was in the design's soft edges, and the first one mattered most because it changed the answer.

Defects found and fixed
SymptomCauseFix
"Musk issues red flag warning" typed as insider → Tier A; a market wrap typed as earnings → Tier A Tier A was reachable from a headline's topic Tier A now also requires primary=yes — a filing, transcript, or official announcement. Commentary about a real event is not the event.
AAPL: all 12 material moves printed "unexplained" News window (2 days) never overlapped the moves (2018–2020) Moves outside the news window are unassessable, a distinct class. The join reports itself unavailable rather than empty.
AAPL at the 100th percentile, +318,950% since listing Percentile over all history — a compounder is always near its own maximum Bounded to a trailing 252 sessions (AAPL → 92nd). Since-listing return is dropped once longer windows exist.
AAPL lost its entire valuation pillar despite having a real P/E G5 is about peer selection, but zeroed absolute multiples too G4 zeroes valuation; G5 alone only discounts it. AAPL moved 0.65 → 0.86.
5 of 10 AAPL items silently became Tier C as unparsed The model intermittently omits a field and emits the shorter row Labelled key=value fields instead of positional pipes. 0/10 unparsed across four trials.
The field-drop failure generalizes beyond this project

Asked for six pipe-delimited fields, the local 9B model returned six most of the time and five occasionally — dropping primary, the field added last. With positional parsing a lenient reader would have slid tone into the primary slot and mislabelled evidence tier without any error. Fail-closed parsing caught it; labelled fields removed it. Any schema an LLM fills should be order-independent for this reason.

One residual, unfixed. Across four identical runs the tier assignment moved once: an Apple–Alibaba item scored Tier A three times and Tier C once, on the primary judgment. Roughly 90% run-to-run stability is not enough for a system whose output is a decision. The mitigation is majority vote over three samples, or caching the classification per article so a given item is tiered once and reused — neither is built yet.

Second pass

The join was failing on almost every ticker

Shipped into the app, the harness abstained from explaining anything on most names. Two causes, one structural and one an outright bug, and a third source of evidence that was sitting unused in the codebase the whole time.

The bug: thresholds computed over all history

A move counted as material if it was large and on volume above 1.5× the median — but both bars were measured across the entire series. AAPL's recent volume never clears 1.5× its 45-year median, so its newest qualifying move was 2020-08-21, six years stale, and the trailing 182 days contained zero material moves. Both thresholds now come from a trailing 252 sessions, and moves are only drawn from the evidence window.

The structural fix: SEC filings as the Tier A source. yfinance returns ten news items spanning about two days. EDGAR returns six months of dated, primary-source disclosures — and filings.py was already fetching them for a different panel. A filing is primary by definition, so the form type is the classification: no LLM needed, and the channel keeps working when the local model is down.

Join coverage before and after
TickerFilingsEvidence spanJoin beforeJoin after
AAPL6166d Unavailable 12 unassessable 7 moves 2 explained
BABA27159d Unavailable 2-day window 5 moves 4 explained
SPCX1281d 1 move crowd only 4 moves 3 explained

BABA is the instructive one: it returned zero filings at first, because foreign private issuers file 20-F and 6-K rather than 10-K/10-Q/8-K. Every ADR was invisible to the channel. Administrative traffic is still skipped, and Forms 3/4 are excluded deliberately — insider paperwork is numerous enough to "explain" almost any move by coincidence.

Tier R: regulation and policy

A Chinese internet crackdown moves BABA on a day BABA is never named in a headline. The original tiering could not represent that at all — macro and entity=absent both collapsed to noise — so the harness was structurally blind to the single most important driver for a whole class of names.

Tier R covers regulatory and political news scoped to the company or its sector. The classifier now receives the company's sector, industry and home market, because it cannot judge that a Beijing tech story reaches BABA without knowing BABA is a Chinese internet company. Two guards keep it from becoming a storytelling licence:

Working, on live data: AAPL picks up "Tougher Google Antitrust Penalties Would Threaten Firefox" as regulation/sector; BABA picks up "Chinese AI Chipmakers Poised to Gain From Beijing's Tech Push"; SPCX picks up none, the Bitcoin item correctly sitting in Tier C.

G7 was a tautology. It compared the news window against the entire price history — two days against 2,988 sessions — so it fired for every mature ticker and carried no information. It now asks whether evidence covers at least half the 182-day window, and stops firing on AAPL and BABA while still firing on SPCX at 81 days.

What the fix cost: adjacency is not causation

SPCX now reports all four material moves explained, three of them by 8-K filings dated within a day. That reads better than "unexplained" and is partly an illusion. Companies file 8-Ks around events that were already public, and an active filer emits enough of them that coincidental adjacency is likely — which is exactly why Forms 3 and 4 were excluded from the channel in the first place. The same argument reaches 8-Ks for a heavy filer.

The honest reading of "explained by a filing" today is a disclosure exists near this move, not this disclosure caused this move. Closing that gap means reading the filing body to check it concerns something material, and weighting a scheduled 10-Q differently from an unscheduled 8-K. Neither is built. Until it is, the join's explanatory power is weaker than its labels suggest, and the abstention verdict — which rests on the gate, not the join — is the more trustworthy half of this system.

Sequencing

Build order

Phase 1 · shipped

Channel P and the gate

Multi-window returns, percentile framing, and rules G1–G7, live at /api/decision/{ticker}. The three mislabelled fields in finance.py — the 1-year return, the 52-week range, the disputed market cap — are still served by the older panels and remain to be fixed at source.

Phase 2 · shipped

Channels F and N, and the join

Filings, tiered event extraction and the event–response table are live. The daily news store originally planned here was largely superseded by EDGAR, which already reaches back six months — but it would still widen Tier R, where coverage remains two days deep.

Phase 3 · outstanding

Challenger, audit, calibration

The narrative slots shipped; the bear pass and append-only records did not. Still open: filing-body relevance checks, majority-vote tiering to remove run-to-run variance, and a replay to calibrate the floor and hype counter — both remain guesses until something measures them.

Deliberately excluded