Commit Graph
63 Commits
Author SHA1 Message Date
dennisthiessen f714782fa4 fix: use categorical regime fundamentals
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m13s
Deploy / deploy (push) Successful in 37s
2026-07-15 10:13:22 +02:00
dennisthiessen 81c6f5fe2f fix: refresh latest regime trading session
Deploy / lint (push) Successful in 10s
Deploy / test (push) Successful in 1m14s
Deploy / deploy (push) Successful in 39s
2026-07-15 09:13:42 +02:00
dennisthiessen 1d5b1489be feat: replace regime monitor with v2 methodology 2026-07-15 09:02:56 +02:00
dennisthiessen f59c0c3484 Add system alerts log with nav badge and Admin Alerts tab.
Deploy / lint (push) Successful in 8s
Deploy / test (push) Failing after 1m4s
Deploy / deploy (push) Has been skipped
Persist job and ingestion warnings/errors for 7 days, surface a dismissible top-nav badge, treat stale OHLCV as a warning (e.g. ticker renames), and show market bar age on the ticker freshness chip.
2026-07-14 13:38:04 +02:00
dennisthiessen d5b0ebf895 Improve chart trade overlays and paper fill markers.
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 1m12s
Deploy / deploy (push) Successful in 39s
De-clutter setup labels (left roles, right prices; hide Entry near Now; S/R in-plot), and draw green/red arrows for paper trade entry and exit on the matching sessions.
2026-07-14 10:44:56 +02:00
dennisthiessen 8db535b889 Fix manual refresh dropping qualified ranks and clarify trade UI.
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 1m20s
Deploy / deploy (push) Successful in 39s
Single-ticker fetch now attaches residual-momentum ranks so setups do not silently fail the activation gate. Exit plan is a timeline, chart labels move left of the price scale, and missing ranks surface explicitly.
2026-07-14 10:10:30 +02:00
dennisthiessen 0873176f64 Show production rank below ticker chart 2026-07-13 12:35:16 +02:00
dennisthiessen 4730d19694 Add GTL price-traffic chart diagnostic 2026-07-13 12:08:10 +02:00
dennisthiessenandClaude Opus 4.8 9789e3d762 UI: frame the setup as what it is — a momentum signal with a trailing exit
The UI told a swing-trade story (entry -> target -> stop) while the engine runs
a momentum portfolio (buy strength, trail out, re-rank). The selection was
honest; everything around it was borrowed from a strategy we don't run.

The target is never an exit under `atr_trailing`: `_atr_trailing_close()` does
not even take it as a parameter. It exists only to compute the R:R and touch
odds that admit a setup through the activation gate. Backtested exit reasons for
the production strategy: 144 initial stop, 98 trailing stop, 78 max hold —
target 0. See docs/research/sr-levels-and-exits.md.

What changed:

- New ExitPlanPanel on every setup card states the rules that actually close the
  trade: initial stop (1R), the price at which the 3x ATR trail takes over from
  it, the trail width in R, and the max hold. Derived in lib/exitPlan.ts from the
  live exit policy, so it follows Admin rather than hardcoding the default.
- New BaseRatesPanel replaces per-target "probability" as the answer to "what
  usually happens": win rate, average hold, best/worst R, and how trades actually
  ended — measured under the real exit, from the backtest report.
- "Target"/"target probability" relabelled to "level"/"touch odds" and grouped as
  gate metrics, with the R:R. On the dashboard focus card, residual momentum
  (the actual signal) takes the headline stat those two used to occupy.
- The take-trade dialog no longer offers a target dropdown whose value the exit
  ignores; it states the trailing plan instead. The picker returns only when the
  live policy is mode='target', where the choice is real. The stored target is
  now the setup's own, not whichever row was last clicked while exploring.
- "Played out" is gone. A setup was declared dead once price reached the target —
  backwards under a trailing exit, where reaching a level is the good case and
  the trade keeps running. Only the stop invalidates a setup now; running past
  the entry is an "extended" warning, measured in R (you'd be chasing).

The levels ladder, the price rail and the chart overlay all stay fully
explorable — clicking a level still drives them. It is framed as overhead
structure, which is what it is, rather than a menu of exits.

Adds a parity guard: the UI recovers ATR as |entry - stop| / 1.5, so the test
fails if the scanner's stop width ever moves.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 15:29:21 +02:00
dennisthiessenandClaude Fable 5 c3dd80e203 Performance chart under positions; remove obsolete files
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m6s
Deploy / deploy (push) Successful in 35s
- PerfChart moves into the left column under Open Positions
  (half-width), balancing the radar column.

Cleanup:
- Design mockup pages (DesignMockups / DesignHorizon / DesignOrbit)
  and their routes removed - the redesign has shipped; the mockups
  live on in git history. Main bundle shrinks ~97 kB and the
  chunk-size warning goes away.
- frontend/tsconfig.tsbuildinfo untracked and gitignored (incremental
  build artifact that churned in every commit).
- Dead npm test / test:watch scripts removed - vitest was never a
  dependency; CI already skips the step via its vitest guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:07:22 +02:00
dennisthiessenandClaude Fable 5 79010a5dae Overview: restore wide focus card and half-width positions
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m13s
Deploy / deploy (push) Successful in 38s
The focus|radar 3/5-2/5 pairing made the top-pick card cramped and the
full-width positions panel stretched its trade charts. Back to the
layout that worked: focus card full width, account ribbons, then
positions | radar side by side, performance chart at the bottom.

Kept from the rework: radar rows still swap the focus card - and now
reliably, because the ticker symbol inside a row is no longer a
navigation link (clicking anywhere in the row, symbol included,
selects it; the ticker page is reachable via the focus card's Ticker
details button). Below-gate inspection, badges, and the reset link
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:47:40 +02:00
dennisthiessenandClaude Fable 5 06fdd92faa Overview: focus|radar pairing, selectable radar, performance chart
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m9s
Deploy / deploy (push) Successful in 36s
Layout regrouped by relationship, not size: the setup-in-focus card
and the radar sit side by side (they are one decision surface), the
four account ribbons move directly above the open positions they
describe, and a new performance chart closes the page.

- Radar rows are selectable: clicking one swaps the focus card to that
  setup - including below-gate rows, whose card shows a muted
  "rank N / below gate" badge and the disqualify reason in the footer,
  with a "back to top pick" reset. The row currently in focus is
  highlighted; ticker links still deep-link without selecting.
- Performance chart (the mockup's missing piece): new
  GET /paper-trades/equity-curve computes, per benchmark trading day
  since the first paper trade, the book's cumulative P&L (realized +
  mark-to-market from stored OHLCV) vs the same cost basis riding SPY
  over each trade's window (benchmark_prices). Pure curve math in
  paper_trade_service with unit tests; hidden until there are 2+
  points of data. Frontend renders both lines with crosshair readout,
  zero baseline, and direct end labels.

Backend unit suite: 501 passed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 18:09:31 +02:00
dennisthiessenandClaude Fable 5 b0b691da9c Overview: regime to the nav bar, watchlist up top
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m5s
Deploy / deploy (push) Successful in 35s
The market regime banner took a full-width row on the dashboard for
ambient context. It is now a subtle status in the top bar (colored dot
+ "neutral regime", full headline and the longs/shorts caution in the
tooltip) linking to /regime - matching the mockup's nav.

The watchlist strip moves from the bottom of the dashboard (where it
got lost) into the freed spot right under the hero: a slim chip row
with today's move per symbol and the full-watchlist link. The empty
state is a one-line hint instead of a callout box.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:31:41 +02:00
dennisthiessenandClaude Fable 5 72054d06b0 Radar axes: one canonical order everywhere
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 1m3s
Deploy / deploy (push) Successful in 35s
The dashboard fingerprints sorted score dimensions alphabetically
while the ticker page rendered them in API order, so the same ticker
drew a differently-shaped polygon on each page.

New shared helper radarAxesFromDimensions() pins the axis order to the
backend's DIMENSIONS list (technical, sr_quality, sentiment,
fundamental, momentum; unknown dimensions append alphabetically) and
centralizes the label truncation. Used by the dashboard fingerprint
strip, the ticker header radar, and ScoreCard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:25:26 +02:00
dennisthiessenandClaude Fable 5 16121042cd Target selection syncs to chart; stable rail scale; sizing top-right
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m4s
Deploy / deploy (push) Successful in 35s
- The ladder target choice is lifted to the ticker page (per
  direction), so selecting a target updates the candlestick overlay's
  target line and zone too, with the R:R for the tooltip taken from
  the ladder row.
- Price rail: the scale now always spans the entire target ladder, so
  stop / entry / now hold their positions and only the target marker
  moves when a different target is selected. The furthest target sits
  at the right edge.
- Setup card layout: position sizing (shares / value / max loss,
  details in the tooltip) and the Mark-as-taken button move to the top
  right of the card header row, next to the stats they belong with -
  the dangling bottom row is gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:52:18 +02:00
dennisthiessenandClaude Fable 5 9f56a25f2d Freshness first row; level clustering; verdict typography;
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 1m5s
Deploy / deploy (push) Successful in 34s
selectable targets drive the rail

- Data freshness is now the very first row of the ticker panel, above
  the symbol - data age is the first thing to check.
- Indicator cards: long price-level lists (pivot swing highs/lows,
  volume-profile HVN/LVN) no longer clip or overwhelm. Numeric lists
  are clustered into ranges (values within ~0.75% of price merge),
  sorted nearest-to-price first, capped at 4 chips with a "+n more"
  note; scalar values wrap instead of overflowing the card.
- Recommendation verdict: the action ("LONG (high confidence)") is set
  in the display face in accent cyan with the risk level beside it;
  the signal breakdown (technical/momentum/sentiment) drops to a small
  muted subtitle line.
- Target ladder is open by default and selectable: clicking a row
  previews that target on the price rail and updates the R:R and
  probability chips, the low-probability warning, and the prefilled
  target in Mark-as-taken (still overridable there). The scanner's
  primary stays the default; a non-primary choice is chipped as
  "custom target".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:36:39 +02:00
dennisthiessenandClaude Fable 5 03bdcbd745 Ticker panel polish: freshness up top, radar pad, indicator grid,
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 1m4s
Deploy / deploy (push) Successful in 35s
verdict de-dup, target picker on take

Five fixes from prod review:

1. Data freshness row moves from the panel foot to directly under the
   header - data age is a first-look concern.
2. Radar fingerprint labels were clipped left ("Momentum") and right
   (value digits): label padding widened to fit "momentum 100" on both
   anchored sides.
3. Indicators tab: the one-at-a-time dropdown (which also clipped) is
   gone; all indicators load in parallel as quiet outlined cards - EMA
   cross with its colored signal, RSI/ADX with a one-line human read
   (overbought / trending / ...), values formatted to 2 decimals
   instead of 4, normalized score demoted to a corner note.
4. Recommendation verdict line de-duplicated: the backend reasoning
   already starts with the action label, so it now replaces the
   headline instead of repeating under it.
5. Mark-as-taken: the take form gains a target selector listing every
   detected target with price / probability / classification (primary
   preselected), and the card warns when the primary target sits below
   15% probability. Why the scanner promotes such a target to primary
   is a separate (backend) question.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:20:23 +02:00
dennisthiessenandClaude Fable 5 d806ede07e Recommendation module in Horizon language; Standing/Dimensions as tabs
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m4s
Deploy / deploy (push) Successful in 37s
RecommendationPanel redesigned to match the rest of the redesign:

- Setup cards use the spatial price rail (stop -> entry -> now ->
  target with live R-multiples) instead of the label/value text grid.
- One quiet chip row per card (direction, preferred marker, confidence,
  R:R, target probability) replaces scattered header text.
- Position sizing is a single mono line (shares / position / max loss)
  instead of a boxed 3-column mini-table; Mark-as-taken flow kept, in
  the standard cyan button style.
- Full target ladder moves behind a disclosure (the primary target is
  already on the rail); warnings (counter-trend, stale/invalidated
  drift, earnings window, conflict flags) all kept.
- Cards are quiet outlined blocks, no glass-in-glass; the module
  renders frameless inside the unified ticker panel's Analysis tab,
  under the chart.

Ticker panel tabs now: Analysis, Standing, Dimensions, Indicators,
S/R Levels, Sentiment, Fundamentals - Standing and Dimensions were the
last below-the-fold sections; the whole page is now the single panel.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:32:03 +02:00
dennisthiessenandClaude Fable 5 fc0945367a Ticker page: one unified drill-down panel, no card salad
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m7s
Deploy / deploy (push) Successful in 36s
The previous pass kept a stack of independent glass cards (header,
recommendation, chart section, tab pills, per-tab sections), which read
as clutter. Now the page is a single casting, like the mockup:

One glass panel containing:
- header (identity, price, chips | actions + score fingerprint)
- inline hairline tab row (Analysis / Indicators / S/R Levels /
  Sentiment / Fundamentals) with the chart-overlay pills riding on the
  right of the row during Analysis
- tab body: Analysis = the candlestick chart; other tabs render their
  panel inside via .hz-frameless, which strips the nested card chrome
  (no card-in-card)
- panel foot: data freshness row (frameless now) with per-source
  refresh

Below the panel, only on Analysis: the recommendation module and a
two-column Standing | Dimensions grid. Nothing removed; the standalone
Tabs bar, the separate chart card, and the duplicated section stack are
gone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 08:17:05 +02:00
dennisthiessenandClaude Fable 5 f9e12fc78c Ticker page: mockup drill-down layout
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m7s
Deploy / deploy (push) Successful in 37s
Restructures TickerDetailPage to match the Horizon mockup's ticker
drill-down while keeping every existing feature:

- Glass header card: eyebrow + big symbol/company, price with change
  and freshness, chips (composite score, next earnings, top-pick /
  open-trade pills) on the left; watchlist + Fetch All actions and the
  score fingerprint radar on the right (hover a corner for values).
- Data freshness bar (per-source age + refresh buttons) moves to the
  foot of the chart card, like the mockup's panel footer.
- Tabs extended to five: Analysis, Indicators, S/R Levels, Sentiment,
  Fundamentals - sentiment and fundamentals no longer stack below the
  analysis content, they are one tab away.
- ScoreCard gains showRadar prop so the fingerprint is not duplicated
  in the Dimensions section.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 07:59:13 +02:00
dennisthiessenandClaude Fable 5 02cf9d1cba Match Horizon mockup structure; restore qualified-first radar
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m8s
Deploy / deploy (push) Successful in 35s
Fixes from first prod review of the redesign:

- Top navigation bar (TopBar) replaces the sidebar, like the mockup:
  wordmark + sections + ticker search + jobs/status/logout; content in
  a centered max-width column. MobileNav unchanged.
- Radar panel: qualified setups lead again (the actionable list, as
  the old Top Setups did); non-qualified sit behind an "N below the
  gate" toggle with per-rule reasons, auto-open only when nothing
  qualifies. Adds mini radar fingerprints for the top 3 qualified
  (dimension scores from the rankings endpoint, fixed axis order).
- Dashboard layout: open positions and radar side by side (xl), as in
  the mockup.
- ScoreCard: the radar fingerprint was inside the showComposite block,
  which the ticker page disables - it never rendered. Now it shows
  whenever >= 3 dimensions exist.
- TradeChart: fresh positions with < 2 bars since entry rendered no
  chart; now pads with ~10 pre-entry context bars (gray) and marks the
  entry point. First open position auto-expands so the chart is
  visible without a click.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 07:44:55 +02:00
dennisthiessenandClaude Fable 5 20f6981712 Horizon redesign: space theme, visual dashboard, chart reskin
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 1m9s
Deploy / deploy (push) Successful in 36s
Replaces the citron/green glass theme with the Horizon direction
(mockup at /design-horizon): space-void background, starfield, dim
Mars horizon, rim-cyan accent, rose for negative. Palette validated
for CVD separation and contrast on the dark surface.

- tailwind.config: gray -> cool space neutrals, blue/emerald -> cyan
  scale, red -> rose; display font Space Grotesk
- globals.css: Horizon tokens, atmosphere, denser glass, cyan
  buttons/inputs, price-rail / R-bar / radar chart CSS
- Dashboard rebuilt: verdict hero, top-pick card with spatial price
  rail, KPI tiles, open positions as diverging R-bars with expandable
  detail + trade chart (OHLCV since entry, entry/stop/target levels),
  radar list with per-setup disqualify reason, watchlist chips
- OpenTradesPanel: table replaced by R-bar rows; all fields kept in
  the drill-down (shares, P&L, alpha, trailing stop, sell)
- qualification: disqualifyReason() mirrors qualifiesSetup rule order
- CandlestickChart: canvas colors moved to Horizon palette; S/R now
  cyan/neutral so rose stays reserved for the stop level
- ScoreCard: radar score fingerprint with hover values
- Design mockup pages included (routed in App.tsx)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 07:24:17 +02:00
dennisthiessen 61156684ff Simplify top setups table
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m17s
Deploy / deploy (push) Successful in 37s
2026-07-04 13:57:42 +02:00
dennisthiessen 5f2d108227 Promote production portfolio strategy 2026-07-04 07:48:38 +02:00
dennisthiessen 14327ab25a Require aligned action for qualified setups
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m7s
Deploy / deploy (push) Successful in 39s
2026-07-03 16:13:27 +02:00
dennisthiessen 2b0068ae08 Add volume pane to ticker chart
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 58s
Deploy / deploy (push) Successful in 32s
2026-07-03 08:09:27 +02:00
dennisthiessen aadec7d403 promote residual momentum ranking
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m8s
Deploy / deploy (push) Successful in 35s
2026-07-02 21:00:39 +02:00
dennisthiessenandClaude Opus 4.8 da0bb3367e feat: company names for tickers (Alpaca backfill + subtle display)
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 55s
Deploy / deploy (push) Successful in 32s
Store an optional company name on Ticker (migration 014) and backfill it from
Alpaca's asset list in a single Trading-API call for the whole universe — no
per-ticker fetch. Runs automatically at the end of universe bootstrap and via a
manual "Backfill Names" button (admin) / POST /admin/tickers/backfill-names.

The name ships on /tickers; a shared symbol→name map (useTickerNames) lets any view
show it without its own request. Displayed subtly next to the symbol — in the global
search, the ticker header, and as a small muted line under the symbol in Top Setups
and Open Trades (no extra column, truncated so it never widens the table).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 10:50:40 +02:00
dennisthiessenandClaude Opus 4.8 94ed3207d7 feat: show composite = base + sentiment caption under the Standing matrix
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 52s
Deploy / deploy (push) Successful in 30s
The ticker page renders the composite via the Standing matrix (ScoreCard runs with
showComposite=false), so the "Base X · sentiment +Y" line in the ScoreCard header
was never visible there. Add a compact caption beneath the matrix — "Composite 83 =
Base 78 + Sentiment 5.0" — shown only when sentiment actually moves the score, so
the composition of the number has a visible home where the number lives.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-01 09:55:42 +02:00
dennisthiessenandClaude Opus 4.8 1566b84379 feat: trailing-stop auto-exit for paper trades + close/digest alerts
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 54s
Deploy / deploy (push) Successful in 33s
Applies the backtest-validated trailing stop to live paper trading, and surfaces
it transparently.

Exit (A):
- New paper-trade exit policy (paper_exit_mode=trailing, paper_trailing_pct=12),
  tunable in Admin → Paper-Trade Exit. resolve_open_trades runs a trailing stop
  (initial stop as floor, ratchets up from the peak; target ignored — the
  validated rule) and records close_reason (trailing|stop|target|manual; +migration
  013).
- list_trades enriches open trades with the live trailing-stop level + distance %.
  Open Trades panel shows the active tactic and a Trail Stop column.

Alerts (B):
- Daily digest now lists open trades with unrealized gain, trailing stop, and how
  far away it is.
- New "trade closed" alert: one summary per auto-close (trailing/target/stop, not
  manual) — direction, reason, days held, P&L abs+%/R — covering wins AND
  stop-loss losses. Deduped by trade id; toggle in Admin alerts.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 18:48:05 +02:00
dennisthiessenandClaude Opus 4.8 30effa89b7 feat: ticker search, watchlist momentum column, alpha vs S&P 500
Deploy / lint (push) Successful in 6s
Deploy / test (push) Failing after 12s
Deploy / deploy (push) Has been skipped
Three usability fixes:

1. Global ticker search in the sidebar (TickerSearch) — typeahead over the
   tracked universe that opens a ticker's detail page without adding it to the
   watchlist. Also wired into the mobile nav.

2. Watchlist table shows the ticker's 12-1 momentum percentile (the top-pick
   selector) instead of the noisy full S/R-level list. Enriched from the setup
   already loaded in watchlist_service._enrich_entry — no extra query.

3. Alpha vs the S&P 500 on paper trades (open + closed). New benchmark_prices
   table + benchmark_service store SPY daily closes (a standalone series, not a
   Ticker, so it never enters the scanner / momentum ranking / rankings) via a
   new daily-pipeline step. paper_trade_service computes per-trade
   benchmark_return / alpha_pct / alpha_usd over each holding period; the open-
   trades table, dashboard, and closed-trades panel surface per-trade and total
   alpha. The list read path never makes a provider call.

Deploy: alembic upgrade head, then run the benchmark/daily job once to populate
SPY closes (alpha shows "—" until then).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-28 08:44:40 +02:00
dennisthiessenandClaude Opus 4.8 4a96f85cd9 feat: Standing matrix on the ticker page (quality x momentum verdict)
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 42s
Deploy / deploy (push) Successful in 26s
Replace the scattered score readouts with one hero: a quality (composite) x
momentum-percentile scatter that plots this ticker against the whole field and
reads out a verdict by quadrant — Strong Buy / Momentum / Accumulate / Pass. The
dashed divider is the activation gate's momentum percentile, so "above the line =
qualifies" is visible at a glance; peers are clickable. Reuses the regime-quadrant
visual language and is lazy-loaded so recharts stays out of the main ticker chunk.

- New StandingMatrix component (composite x momentum, field cloud, verdict).
- ScoreCard gains showComposite (default true); the ticker page now renders it
  without the composite ring (composite lives in the matrix) under "Dimensions".
- Confidence + target probability stay in the recommendation panel (the trade).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 17:00:47 +02:00
dennisthiessenandClaude Opus 4.8 d15acb8741 feat: top-pick and open-trade status labels on the ticker page
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 42s
Deploy / deploy (push) Successful in 25s
Two read-only pills in the ticker header, beside the watchlist toggle:
- "Top Pick" when the ticker is the current #1 — the same ranking the dashboard
  highlights, via a shared topPickSymbol() helper so the two stay in sync.
- "Open Trade" when an open paper trade exists on the ticker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-27 16:04:55 +02:00
dennisthiessenandClaude Opus 4.8 a07bfee6e6 feat: regime quadrant plot in place of the combined gauge
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 42s
Deploy / deploy (push) Successful in 25s
The combined score collapsed two distinct signals into one not-very-meaningful
number. Replace its gauge with a quadrant scatter that shows both axes directly:
x = regime index (coincident), y = early warning (breadth divergence), with a
trail of the last 60 sessions and today highlighted.

The four quadrants make the readings legible — ① hot & brittle (narrow melt-up,
shakeout risk), ② transition, ③ healthy & broad, ④ real downturn — and the trail
surfaces the actual tell: the ①→④ move (early warning rolling over as the regime
index climbs = divergence resolving downward). Combined still shows as a line in
the score-history chart. Frontend-only; reuses the history endpoint. Lazy-loaded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 16:14:32 +02:00
dennisthiessenandClaude Opus 4.8 66444af65c feat: score-history chart on the regime tab
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 41s
Deploy / deploy (push) Successful in 25s
Plots the index, early-warning, and combined scores over time beneath the live
gauges, with a 1M/3M/6M/All range toggle and band reference lines — so the trend
and any divergence between the scores is visible, not just today's snapshot.

- Backend: GET /regime/history + get_regime_history (the three scores per
  snapshot date from regime_snapshots).
- Frontend: recharts line chart, lazy-loaded so recharts ships in its own
  regime-tab chunk instead of nearly doubling the main bundle.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:48:42 +02:00
dennisthiessenandClaude Opus 4.8 60def1155b fix: coverage-aware event-study headline instead of misleading median delta
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 39s
Deploy / deploy (push) Successful in 24s
The "warned a median -18 days later" line was the median-over-1-event trap: the
coincident baseline's 60d median is a single lucky event, while breadth warned on
7. Replace it with the honest coverage framing (7/11 vs 1/11) and flag that the
median-lead comparison is unreliable when coverage differs this much.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:36:39 +02:00
dennisthiessenandClaude Opus 4.8 613fc756ec feat: separate live early-warning + combined score on the regime tab
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 40s
Deploy / deploy (push) Successful in 24s
The event study showed the breadth-divergence signal genuinely leads (warned
before 7/11 drawdowns, ~6 weeks median, where the coincident baseline almost
never did). Surface it live to observe before deciding how to embed it — kept
separate from the index, not folded into its weights.

- regime_monitor daily job now computes breadth-divergence live and attaches a
  separate early_warning score plus a combined blend (weighted mean, default
  0.6/0.4, configurable via combined_weights) to each snapshot, including the
  backfill so the 7/30-day trends populate immediately. Stored in breakdown_json
  — no schema change. Best-effort: a breadth failure can't break the index.
- get_regime_monitor returns the index, early_warning, and combined scores each
  with 7/30-day deltas.
- Regime tab shows three gauges (generalized ScoreGauge): coincident index,
  early warning, and a compact combined blend. Stale snapshots render "—".

Note: the daily regime job now also does a universe-wide breadth scan.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:23:37 +02:00
dennisthiessenandClaude Opus 4.8 7c5fb1138d feat: sharpen the event study — more events, fair baseline, per-event view
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 41s
Deploy / deploy (push) Successful in 26s
The first run gave only 2 events (N=2 is anecdote, not evidence) and an unfairly
weak coincident baseline, so the +42d lead couldn't be trusted. This makes the
measurement meaningful:

- More, cleaner events: default drawdown threshold 15%→10%, and dedup switched
  from "recover to the high" to a rising-edge + cooldown (40d), so distinct
  drawdowns each register instead of merging.
- Fair comparison: each indicator now warns at its OWN 80th percentile instead of
  a shared absolute 60, removing the artifact that muted the coincident baseline.
- Per-event breakdown (date · depth · breadth lead · coincident lead) so a median
  over a tiny sample can't hide an apples-to-oranges comparison — you see whether
  both warned on the same drawdown.
- Surface precision/recall (best row) + base rate per indicator — the honest edge
  read, not just lead time.

Re-run the Event Study job to regenerate the cached report in the new shape.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 14:54:29 +02:00
dennisthiessenandClaude Opus 4.8 f8d62e4074 feat: show current exposure instead of lifetime stats on the overview
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 43s
Deploy / deploy (push) Successful in 25s
The overview's Hit Rate and Expectancy were static lifetime aggregates — they
barely move day to day and aren't actionable at a glance. Replace them with the
current state from open paper trades:

- Open Risk: total $ at risk to stops across open positions.
- Unrealized: summed unrealized R (mark-to-market), with $ P&L and win/loss count.

Computed in the frontend from the already-loaded open trades (tradePnl) — no
backend change. The detailed lifetime stats remain on Signals → Track Record.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 14:08:59 +02:00
dennisthiessenandClaude Opus 4.8 824c15cf69 feat: breadth-divergence early-warning indicator + event study
Adds a leading-by-construction candidate and the harness to measure whether it
actually leads regime breaks, before any of it earns weight in the live index.

- breadth_service: % of the stored universe above its own 200-DMA + a divergence
  score (benchmark price up while breadth falls, nudged by low breadth). Genuinely
  leading because it keys on divergence, not level. Not wired into the live score.
- event_study_service: detect drawdown events on the benchmark, then measure each
  indicator's median lead time (event-centered) and precision/recall vs. the base
  rate (signal-centered). Compares breadth-divergence against the deterministic
  coincident price composite (reuses the regime price sub-scores). Price/breadth
  only — reproducible, no LLM/FRED.
- Manual "Event Study" job (Admin → Jobs), GET /regime/event-study, and an
  inline early-warning panel on the Regime tab with an honest small-sample caveat.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 14:08:52 +02:00
dennisthiessenandClaude Opus 4.8 ebff19940b feat: add standalone AI/Tech regime-change monitor tab
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 46s
Deploy / deploy (push) Successful in 27s
A new /regime tab scoring how far the AI/Tech bull regime has deteriorated
toward a re-rating as a single 0-100 index with per-signal breakdown and a
7/30-day trend. Intentionally decoupled: nothing reads its output to gate or
score trades — the daily-pipeline membership is scheduling only.

- regime_monitor_service: price sub-scores (P1-P6 via Alpaca, like
  market_regime), VIX + HY credit spreads via a small FRED helper, weighted
  aggregation over available signals (missing source -> n/a, dropped from the
  denominator), one snapshot row/day, and a ~90-day history backfill by
  replaying the already-fetched series as-of each past day.
- F1/F3 fundamentals proposed by the configured grounded LLM (reuses
  sentiment_provider_service config resolution), with a manual override + lock.
- regime_snapshots table (migration 011); endpoints on the existing market
  router; admin-editable weights/threshold; standalone /regime page.

Data needs: prices via Alpaca, VIX/credit via FRED (optional key — signals show
n/a without it). No LLM needed for history.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 11:51:45 +02:00
dennisthiessenandClaude Opus 4.8 ef523474ad replace EV activation gate with cross-sectional 12-1 momentum ranking
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 41s
Deploy / deploy (push) Successful in 26s
The 5-year backtest confirmed the EV gate adds negative value (high threshold =
worst expectancy) and that 12-1 month momentum is the one price signal with a
plausible, right-signed cross-sectional IC (~0.05). So "qualified" now means:
clears the R:R + confidence floors AND the ticker ranks in the top
`min_momentum_percentile` of the universe by 12-1 momentum that week.

- qualification.py: drop expected_value_r / the EV gate; add a momentum-percentile
  gate (duck-typed `momentum_percentile`, only enforced when attached + threshold
  set, else defers to floors). Mirrored in frontend qualification.ts.
- activation config/schema: min_expected_value -> min_momentum_percentile
  (default 80 = top quintile). ActivationSettings, DashboardPage (ranks/【shows】
  momentum instead of EV), and the BacktestPanel sweep follow.
- backtest: rank each ISO week's universe by 12-1 momentum, assign a percentile,
  and qualify the top slice; the sweep now sweeps the percentile cutoff.

Also offload the backtest's per-ticker compute to a worker thread so the heavy
~5y run no longer blocks the API event loop (the "backend offline" flicker).

Production setups don't carry momentum_percentile yet — wiring the scanner to
attach it (a universe momentum-rank step) is the next step; until then the live
gate defers to floors while the backtest measures the momentum selection. 330
backend tests pass; frontend build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 22:42:24 +02:00
dennisthiessenandClaude Opus 4.8 c34f3cb1a4 redesign activation gate to expected value + make pipelines cron-configurable
Deploy / lint (push) Successful in 9s
Deploy / test (push) Successful in 46s
Deploy / deploy (push) Successful in 28s
Diagnosing "no qualified signals for 5 days": setups were generated but none
qualified. The gate required BOTH a high min_rr (2.0) AND a high
min_target_probability (60), which became contradictory after the Jun-15
probability recalibration — probability already embeds R:R via the 1/(rr+1) ruin
term, so high-R:R targets are inherently low-probability and nothing cleared both.

Gate is now expected value (R): p*rr - (1-p) from the primary target's
probability. R:R and confidence stay as floors; high-conviction / exclude-conflicts
/ min-target-probability become optional tighteners (default off). Defaults:
min_expected_value=0.15, min_rr=1.2, min_confidence=55. EV is only enforced when
computable. Migration 009 clears stored activation_* rows so the new defaults
apply. Backtest sweeps min_expected_value instead of target probability.

Scheduling: pipelines are now cron-configurable in Admin -> Jobs. daily_pipeline
(full, default 0 7 * * *) plus a new light intraday_pipeline (OHLCV + outcome eval,
default hourly US session) that keeps prices/live-R:R current without setup churn.
Fundamentals on its own early weekly cron. Timezone configurable (default
Europe/Berlin). Moving interval->CronTrigger also fixes the restart-deferral bug
where an interval job's countdown resets on every process restart.

319 backend unit tests pass; frontend tsc clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 14:46:38 +02:00
dennisthiessenandClaude Opus 4.8 7e87a15a12 admin Jobs tab: show job controls above the pipeline readiness table
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 22s
The 500+ row readiness table forced scrolling past it to reach the actual job
controls. Put JobControls first, readiness below.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 16:41:56 +02:00
dennisthiessenandClaude Opus 4.8 a69557f5d8 add paper trading: mark a setup as taken, track open P&L, sell
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 24s
New paper_trades table (migration 007) + service/router. "Mark as taken" on each
setup card (shares prefilled from position sizing, entry from current price, both
editable) records a simulated trade. Overview gains an Open Trades table that
marks each position to the latest close — P&L in $, %, and R-multiples — with a
total unrealized P&L footer and a Sell button to close at the current price.
Closed trades are retained for future realized-P&L reporting.

Deploy: alembic upgrade (new paper_trades table).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-16 06:33:56 +02:00
dennisthiessenandClaude Opus 4.8 f0b92a9718 add earnings-date guard — warn when a report falls in the target horizon
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 36s
Deploy / deploy (push) Successful in 25s
Finnhub's earnings calendar now supplies next_earnings_date through the
fundamentals chain; persisted on fundamental_data (migration 006) and exposed in
the fundamentals API. The recommendation panel warns when earnings fall within
the ~30-day target horizon (a report can gap price through stop/target) and
otherwise shows the next date. Informational only.

Deploy: run alembic upgrade (new fundamental_data.next_earnings_date column).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:44:08 +02:00
dennisthiessenandClaude Opus 4.8 c4f2673799 add market-regime guard (SPY trend) — inform + warn
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 36s
Deploy / deploy (push) Successful in 25s
New market_regime_service computes a benchmark (SPY) trend from its 50/200-day
SMAs, cached in a SystemSetting and refreshed by a nightly job; GET /market/regime
exposes it. Dashboard shows a regime banner; setup cards flag a counter-trend
caution when a setup fights the regime (LONG in a bearish market / SHORT in a
bullish one). Informational only — nothing is suppressed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-15 12:34:07 +02:00
dennisthiessenandClaude Opus 4.8 5d41ccac1c add Telegram alerts: qualified setups, S/R proximity, score drops, daily digest
Deploy / lint (push) Successful in 5s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 23s
Closes the action loop — instead of polling the dashboard, the platform pushes
actionable signals to Telegram. New hourly 'alerts' job dispatches four
toggleable triggers, deduped via a new alert_log table (cooldown-based for
qualified/S-R/digest, watermark-based for score deterioration). Admin → Settings
gains a Telegram panel (write-only bot token, chat ID, per-trigger toggles, Send
Test). Credentials follow DB > env precedence (TELEGRAM_BOT_TOKEN / _CHAT_ID).

Backend: alert_service + AlertLog model + migration 005, scheduler job, admin
endpoints/schema. Frontend: AlertSettings panel, hooks, api, types.

Deploy: run alembic upgrade (new alert_log table).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 19:42:18 +02:00
dennisthiessenandClaude Opus 4.8 6e06f51bb6 make watchlist fully manual; add price + day-change, two-block overview
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 35s
Deploy / deploy (push) Successful in 24s
Per design decision: the watchlist is now purely user-curated (no auto-seeding
of the top-10), so the auto_populate/dismissed machinery is removed and removals
are plain deletes. Each entry is enriched with latest close + day-over-day move.

Overview now shows two clear blocks: Top Setups (what to trade) and My Watchlist
(my names with current price and today's %). Market watchlist table drops the
now-meaningless auto/manual Type column in favour of Price and Day columns.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:25:04 +02:00
dennisthiessenandClaude Opus 4.8 0e9f1846f6 fix watchlist remove (was undone by auto-populate); add watch toggle on ticker page
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 34s
Deploy / deploy (push) Successful in 23s
Removing a ticker did nothing because get_watchlist re-runs auto_populate on
every read, instantly re-adding any top-ranked ticker the user had just removed.
Removals are now tombstoned as a "dismissed" entry_type: auto-population skips
them, the list hides them, and a later manual add revives the row. Also exposes
an Add/Remove-watchlist toggle in the ticker detail header.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-14 14:17:27 +02:00