Reading the Chart Without Reading Tea Leaves
Technical analysis has a credibility problem: draw enough lines on a price chart and you will always find a head and shoulders. Wave 28's Sentiment Center puts double tops, support and resistance, breakouts and news tone on one zoomable chart per stock, so the pattern engine underneath it had to earn trust the hard way. This page is the set of honesty rules it lives under, and the decisions behind each one.
WAVE 28 · OCTOBER 2026 In development, not shipped ClientWeb · AdminWeb · MAUIThe Problem: Patterns Are Easy to See and Hard to Trust
Allan's brief was short: "sentiment is the price chart." Show what the stock did, mark the earnings reports, put the news on the timeline, and say when it double topped. The risk was just as clear. Every charting site will tell you a stock "formed a cup and handle," and almost none of them say how the shape was found, when it could first have been known, or how often it was followed by anything at all.
GMI's positioning is for investors who read the numbers, not the headlines. A pattern overlay that can't be checked would undercut that on the very page meant to show it. So the Sentiment Center inherits the same rules as the Earnings Setup page and adds three of its own. Each one below is a decision, the alternative that was turned down, and the reason.
flowchart LR
PX[Adjusted daily closes<br/>price cache] --> SW[Zigzag swings<br/>max 5 pct or 1.5 x ATR]
SW --> PE[SentimentPatternMath<br/>pure, unit-tested]
PE --> PAT[Patterns with<br/>formed and confirmed dates]
PE --> LV[Support and resistance<br/>at most 3 each way]
PAT --> TR[Track record<br/>counts, floor of 3]
NEWS[News archive] --> TONE[Tone classifier<br/>lexicon plus Claude]
TONE --> GATE{200-label<br/>validation passed?}
GATE -->|no| GRAY[Neutral dots,<br/>mood without tone]
GATE -->|yes| COLOR[Tone colors, mood,<br/>drift, tone alerts]
PAT --> PAGE[Chart, board, reports,<br/>alerts]
LV --> PAGE
TR --> PAGE
GRAY --> PAGE
COLOR --> PAGE
PAGE --> CL[Claude writes sentences<br/>over computed facts only]1. Computed, Not Model-Written
The tempting build was to hand a chart image or a price series to a multimodal model and ask it what it sees. It would have shipped in a week and demoed beautifully. I turned it down because a model's answer is neither reproducible nor checkable: ask twice and get two different "patterns," and there is no recipe to publish.
Instead, every pattern comes from SentimentPatternMath, one pure, deterministic class in
the shared models library. The API, ClientWeb, AdminWeb, MAUI, the report generator and the public
page builder all call it, so the same symbol and window can't show a double top on the phone and
nothing on the web. Claude does have a job on this page (the history narrative, story-chapter
blurbs, "ask about a range"), but it only writes sentences over facts the code already computed.
Claude never looks at a chart and "sees" a pattern.
Every parameter is published on the page's help panel, generated from the same constants the engine uses, so the documentation can't drift from the code.
2. One Swing Rule That Scales With the Stock
Every pattern is defined on swing highs and lows, found by a zigzag filter over adjusted closes. The design question was the reversal size that makes an extreme count as a swing. A fixed 5% treats a utility and a biotech the same: the utility rarely produces a swing at all, and the biotech produces dozens of meaningless ones. A pure volatility rule fixes that but turns a calm stock's 2% wiggle into a "swing."
The rule takes the larger of the two:
swing threshold = max( 5% , 1.5 × ATR(14) ÷ price )
The floor stops noise on quiet stocks and the ATR term scales with each stock's own volatility. The threshold is measured at the reversal bar, so the engine can never "know" a swing before the market did, and the trailing, still-unconfirmed extreme is never returned.
| Pattern | Definition (v1) |
|---|---|
| Double top | Two swing highs within 3% of each other, 15 to 120 sessions apart, with a swing low between them at least 8% below the lower peak. Confirmed when a close breaks that low (the neckline); failed if a close lands 3% above the higher peak first. Double bottom is the mirror image. |
| Support and resistance | Clusters of at least 3 swing points inside a 2% band; strength is touch count plus recency; at most 3 levels drawn each way, so the chart doesn't fill with lines. |
| Breakout and breakdown | A close more than 2% beyond a level that holds for 2 sessions. |
| 50/200-day cross | Simple averages of closes. Exact, so it needs no tuning. |
| Head and shoulders, channels | Phase 3: a middle peak at least 3% above two shoulders within 5% of each other; least-squares channels over 60+ sessions with slopes within 25%. |
Allan accepted these parameters with one condition (decision #6): they are tuned once against 20 real charts in Phase 1, and any change is a published, versioned change, not a quiet tweak.
3. Formed Is Not Confirmed, and Hindsight Is Disclosed
The most common dishonesty in charting is drawing the pattern at the moment it started, as if it had been visible then. A double top's second peak looks like a peak only after price has fallen away from it, and the pattern is only a double top once the neckline breaks, often weeks later.
So every pattern in the engine carries two dates: FormedDate, when the
shape began, and ConfirmedDate, the first close on which the rule was satisfied. The chart
draws the gap between them instead of hiding it, and wherever a pattern or its track record appears,
the page says that patterns are only confirmed after the fact.
The same rule carried into the Phase 4 alerts: a "pattern confirmed" alert fires only for a confirmation dated after the last session the detector saw, so a pattern that becomes the active one today but was confirmed three weeks ago is history, not news.
4. Counts, Not Probabilities, With a Floor of 3
Once patterns exist, the next request writes itself: "how often does this work?" The easy answer is a percentage ("double tops lead to drops 75% of the time"). A percentage built from four occurrences looks exactly as authoritative as one built from four thousand.
The pattern track record uses only the stock's own history and reports raw counts with the sample size in the same sentence: "4 confirmed double tops since 2015. After 3 of them the stock fell 10% or more within 60 sessions." Below 3 occurrences the page lists them and writes no summary sentence at all. Using the stock's own history also sidesteps the survivorship bias of cross-sectional pattern studies, which quietly drop the companies that no longer exist.
The same thinking shaped the Mood vs Grade read. An early draft said a gloomy mood next to a strong grade "has often faded." That is a prediction nobody had measured, so it was rewritten to state the disagreement and its parts, and a measured track record for divergences waits on a walk-forward study.
5. Descriptive Voice, Enforced by Tests
The page says what happened and what history shows, never what to do: no "buy," "sell," "should," "will," "likely" or "expect," and no arrows or verdicts. A rule in a style guide erodes; a failing build doesn't. The voice check that started with Earnings Setup now runs over every templated and Claude-written sentence produced from fixtures on this page, and over the sentiment alert templates.
| Rejected | Shipped |
|---|---|
| "Bearish double top: the stock is likely to fall." | "Double top confirmed on the Oct 27 close." |
| "Moods like this have often faded." | "Mood and grade disagree: mood −58 while the grade is A- and improving." |
| "Watch for a breakdown below support." | "Closed at $142.10, 3.1% below the $146.65 support level." |
Why the Tone Classifier Waits for 200 Hand Labels
News dots on the chart can be colored by tone: good, bad or neutral for this company. The classifier behind that is a finance lexicon for the clear cases plus a batched Claude pass for the ambiguous middle, with every label stored per classifier version and never recomputed on read. It was built first, as its own wave, because three other features depend on it.
Building it was not the same as trusting it. Before any color appears, Allan hand-labels
200 random articles on an AdminWeb validation page, and the classifier must agree on
at least 80% of the clear-label ones. The pass button refuses below that line, and the
accuracy gets published. Until it passes, a single switch (tone-colors-enabled) keeps the
whole downstream chain neutral:
- news dots render gray and the Mood score uses only price momentum and trend, and says so;
- the Earnings Setup page's "sentiment drift since the last print" stays off;
- the "tone flipped" alert detector does nothing, and starts from a fresh baseline the night tone is switched on, so a sign change during the gated period can never arrive as an alert;
- the negative-news alerts keep making their own Claude call instead of reusing the classifier's verdict.
The scores themselves are still recorded from day one and exported to Plutus, GMI's sister trading
system, through a key-only, version-pinnable API route that carries a validated flag.
A backtest consumer can study an unvalidated classifier; a reader on the chart shouldn't have to.
The cost is capped too: a $15-a-month hard budget enforced from per-batch accounting rows, so a
restarted worker can't spend twice.
Why the Public Chart Waits on the Data Provider's Terms
The public /grade/{TICKER} pages get a reduced, static version of the chart: two years of
weekly closes, the 50/200-day averages and earnings markers, with no news, no tone and no patterns.
It reuses the static SVG generator the HTML report already uses, so there is no new chart code and
the pages stay prerendered with no WebAssembly.
It is built and switched off behind SeoPages:SentimentChartEnabled.
GMI's market-data contract requires provider-approved terms for public display, and anonymous
viewers count toward the plan's tier. A crawlable, chart-rich page per ticker is good for search,
but showing licensed prices to the open web before the provider confirms it would be the kind of
shortcut that costs more than it earns. The code is ready; the switch waits for a signature.
The Same Rules, Applied to Alerts
Phase 4 adds four paid alert types to the alerts spine: pattern confirmed, tone flipped (30-day tone crosses zero with at least 10 scored articles on both sides), attention spike (at least 3 times the stock's normal weekly article count) and key level broken (a close more than 2% beyond the previous session's nearest support or resistance). They carry the lessons of the September 29 alert flood, when a detector replayed history into inboxes:
- every type starts off, appears in no preset, and ships dark behind its kill switch;
- the nightly snapshot row is overwritten in place, so the detector keeps its own memory per symbol, and a symbol's first observation is a baseline that emits nothing;
- two observations more than five days apart are never compared, and tone and attention alerts have a seven-day quiet period so a value hovering at the line can't ping every night;
- tests drive the detector end to end: first run is a baseline, a real change is exactly one event, and the next night is zero.
The Decisions, in One Table
| Decision | Alternative turned down | Why |
|---|---|---|
| Deterministic pattern engine, shared by every surface | Ask a model what it sees on the chart | Reproducible, publishable and testable; the phone and the web can't disagree |
| Swing threshold max(5%, 1.5 × ATR) | A fixed percentage, or volatility alone | Sensible swings on a utility and a biotech with one rule |
| Formed and confirmed dates on every pattern | Draw the pattern where it started | A pattern is only knowable after the fact, and the chart should say so |
| Counts with a floor of 3, own history only | Percentages and cross-sectional studies | Sample size stays visible; no survivorship bias |
| Tone hidden until 200 hand labels pass at 80% | Ship the colors with the classifier | A colored dot reads as a fact; it has to be measured first |
| Public chart behind a setting, off | Turn it on for the SEO win | The data contract comes first |
Full design: docs/gmi-sentiment-center-design.md in the GMI repository. Related pages:
Earnings Setup, News Wire Poller,
Alerts, Plutus Integration.