Commit Graph
236 Commits
Author SHA1 Message Date
dennisthiessen f7c2e35e29 Isolate legacy range-grid features 2026-07-13 07:57:52 +02:00
Dennis Thiessen 37836f8824 done 2026-07-13 07:31:45 +02:00
dennisthiessen b891122936 Fix post-cluster neutral strength ablation 2026-07-12 23:51:28 +02:00
Dennis Thiessen 8df76d3288 traffic done 2026-07-12 23:45:20 +02:00
dennisthiessen 93403b4d3a Document S/R findings and isolate legacy gate features 2026-07-12 23:38:09 +02:00
Dennis Thiessen ce0df6a126 test done 2026-07-12 23:28:44 +02:00
dennisthiessen bd72fa75f9 Separate S/R detector tests from primary R:R policy 2026-07-12 23:18:38 +02:00
Dennis Thiessen 178abd4b2d validation completed 2026-07-12 23:09:55 +02:00
Dennis Thiessen 0ace776367 train completed 2026-07-12 23:01:15 +02:00
dennisthiessen cb64f7bf65 Fix bounded S/R training portfolio calendars 2026-07-12 22:45:05 +02:00
Dennis Thiessen 4ca48ac590 backtest done 2026-07-12 21:58:10 +02:00
dennisthiessen 681f0f95da Make S/R matrix runner cross-platform 2026-07-12 21:27:13 +02:00
dennisthiessen 19b81c169d Add S/R v2 research and validation harness 2026-07-12 21:15:18 +02:00
dennisthiessen 57ac1d2cdd Replace compare_reports with a full backtest report explorer.
Browse, drill, and compare all report sections (not just four tables), overlay equity curves, and sort reports by generated_at so the newest run is always on top.
2026-07-12 19:27:02 +02:00
dennisthiessenandClaude Opus 4.8 ea11efe3d1 Sweep the R:R floor; fix a holdout metric artifact
Deploy / lint (push) Successful in 10s
Deploy / test (push) Successful in 1m9s
Deploy / deploy (push) Successful in 37s
min_rr = 2.0 was hand-set in Admin (2026-06-24) and never swept — the gate
ablation only tested the floor on-vs-off, never its level. It was the last
un-swept knob in the live gate.

Swept against portfolio Sharpe under the real exit, with a parity self-check
(reproduces_production_gate: the row at the live floor must rebuild production's
exact 1,089-setup qualified set — it does).

  min_rr   qualified   in-sample Sh/CAGR   OOS Sh/CAGR (entries >= 2024-07)
  0.0        6636      1.98 / 58.5%        2.02 / 66.2%
  1.2        3897      1.34 / 33.9%        1.12 / 28.8%
  1.5        3127      1.20 / 29.6%        1.12 / 28.8%
  1.75       1974      1.64 / 44.5%        1.15 / 27.4%
  2.0 (live) 1089      2.04 / 50.4%        2.78 / 73.3%
  2.25        577      1.64 / 31.8%        1.71 / 31.9%
  2.5         286      1.67 / 29.0%        0.68 /  8.7%

KEEP 2.0. It is the optimum in both windows, and a peak that reproduces in data
it was never fitted to is real evidence. But treat it as fragile: unlike the ATR
trail (a plateau), this is a spike with a trough beside it — +/-0.25 costs ~0.4
Sharpe in-sample and ~1.6 out-of-sample — and the curve is bimodal (floor-off is
good, 1.2-1.75 is bad, 2.0 is good). The hand-set value landed on the peak by
luck, not by tuning. Do not nudge it.

Worth knowing: turning the floor OFF entirely is the second-best row in both
windows, with substantially higher CAGR (58.5% / 66.2%) and more trades. If CAGR
ever outranks Sharpe here, "no R:R floor" is a live option — and it would sever
the gate's last dependency on the weak S/R detector.

Also fixes a metric artifact in the holdout harness. The train book's equity curve
ran to the end of the data while its entries stopped at the split, so it sat in
flat cash for two years and deflated its own CAGR/Sharpe (reported 0.95 / 14.6%;
actually 1.31 / 29.6%). _simulate_portfolio now truncates the calendar to
hold_days after the last entry when end_date is set — it only triggers on the
holdout train window, so no other number moves. The clear-air OOS verdict is
unaffected: it rests on the test row, whose entries and curve both start at the
split and were always clean. Both holdout reports regenerated.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 16:45:59 +02:00
dennisthiessenandClaude Opus 4.8 906d1db7d1 Docs: state the actual strategy, add an end-to-end diagram, add a research log
The README opened with "find the path of least resistance, key S/R zones, and
asymmetric R:R setups" — a description of a strategy we do not run. What we run
is a long-only cross-sectional momentum book with a trailing exit. The S/R
engine, the composite score, sentiment and fundamentals are screening and
display; none has a measured edge.

- Rewrites the intro/philosophy around the real strategy, and says plainly what
  is NOT the edge.
- Adds a mermaid decision graph, universe -> qualified -> ranked -> opened ->
  closed, with the real exit distribution on the terminal nodes: initial stop 45%,
  trailing stop 31%, max hold 24%, S/R target 0%. Validated against the mermaid
  parser, not eyeballed.
- Documents that the R:R and touch-probability are GATE INPUTS, not forecasts of
  the trade — the single easiest way to misread this app.
- Adds win rate, best/worst R and the exit-reason split to the production
  baseline table.
- New docs/research/README.md: every strategy tested, the result, the decision,
  and why we stay with the current one. 12 rejected ideas (take-profit exits,
  clear-air gate relaxation, EV gate, regime overlay, inverse-vol sizing, shorts,
  standalone vol, FIP, ...), the confirmed tuning knobs, the open leads, and the
  method rules we learned the hard way (nested lookbacks are not out-of-sample; a
  rising win rate is a warning, not a win).
- Documents the research flags and the holdout harness, and warns that the
  portfolio_monitor lookbacks are nested windows, NOT a holdout.
- Notes the snapshot must copy paper_% settings or it silently diverges from prod.

All baseline numbers re-verified against reports/backtest-20260711-prod-baseline.json
(506 tickers, 1,089 qualified, CAGR 50.4%, +413.8% vs SPY +95.7%, DD -21.4%,
Sharpe 2.04, 320 trades, 15.3d avg hold, and all five promotion contenders). No
corrections were needed — the numbers were right, the framing was not.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 15:40:34 +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 Opus 4.8 85b3ef618f Research: S/R levels, the target exit, and the entry gate
Investigated whether our support/resistance detection follows best practice
and whether we actually use it that way. Three findings, all backed by runs
against the prod snapshot and written up in docs/research/sr-levels-and-exits.md:

- The S/R target must NOT become an exit. Honoring it as a take-profit on top
  of the 3x ATR trail drops Sharpe 2.04 -> 1.47 and halves CAGR. Win rate rises
  (37.5% -> 40.0%), which is the tell: it truncates the right tail where
  momentum's edge lives.
- The clear-air fallback (synthesize a 3xATR target where no resistance exists,
  so 52-week-high breakouts stop being vetoed) looked strictly better in-sample
  (Sharpe 2.04 -> 2.07, CAGR 50.4% -> 62.3%, DD 21.4% -> 20.1%) but FAILED a
  real out-of-sample holdout: on entries after 2024-07-01 it is worse on Sharpe
  (2.78 -> 2.45) and Calmar, better only on raw CAGR. Not shipped.
- The detector itself is weak vs best practice (POC/VAH/VAL computed then
  discarded, HVN = any above-mean bin, 1.48x volume double-counting, "touch"
  counts pass-throughs, no round numbers), but its only causal path to P&L is
  the entry gate. Fix it for the displayed levels, not for returns.

Method note: nested lookback windows are NOT out-of-sample. The in-sample result
was clean, large, and consistent across five windows, and still did not survive
a proper entry-date split.

All research paths are off by default and the default report is unchanged:
  BACKTEST_RESEARCH_EXITS=1        take-profit exit rows
  BACKTEST_ATR_TARGET_FALLBACK=k   synthetic k*ATR target when S/R offers none
  BACKTEST_FALLBACK_CLEAR_AIR_ONLY=1  restrict that to genuinely clear air
  BACKTEST_HOLDOUT_SPLIT=YYYY-MM-DD   train/test split by entry date

Also fixes two reproducibility holes found while reconciling our local baseline
against the live report:

- create_backtest_snapshot.py now copies paper_% settings. The production
  monitor row replays the runtime exit policy via get_exit_policy(); without
  those keys a snapshot silently falls back to code defaults, so a live-tuned
  exit would never be reflected.
- Migration 020 drops activation_min_expected_value and
  activation_min_target_probability. Both are orphans of the June EV-gate
  redesign, read by no code path, but prod carries min_target_probability = 50.0
  which implies a probability floor that is not enforced (the real floor is the
  20% constant in qualification.py).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 15:05:34 +02:00
dennisthiessenandClaude Opus 4.8 fa26ec3ec4 Track backtest reports in git; add a report comparison tool
The reports are the evidence behind the production baseline, so they belong
next to the README that quotes them rather than living only on one machine.
Un-ignores reports/*.json (~2.6 MB compressed for all 11); the snapshot DBs
they run against stay ignored.

Renames the reports to a single dated scheme so they sort chronologically and
say what they measured. Each name is derived from the report's own contents
(the atr_trail_sweep / regime_overlay / blue_sky_projected / sizing_test
sections, and the qualified counts that identify the A/B arms), not from the
ad-hoc slugs they carried before. The run the README quotes is now
backtest-20260711-prod-baseline.json.

reports/compare_reports.py loads every report into one sortable table
(portfolio monitor, entry variants, exit policies, portfolio sim), filters by
report and lookback, and highlights the best row for a chosen metric — max
drawdown correctly ranking lowest-as-best. Stdlib tkinter, no dependencies.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 10:04:13 +02:00
dennisthiessenandClaude Opus 4.8 88527f39b6 Refresh production baseline numbers in the README
The baseline table and promotion evidence still carried pre-primary-target-floor
figures. Re-derived every number from the 2026-07-11 run, the first baseline
measured after the 20% probability floor pruned lottery targets (1,428 -> 1,089
qualified).

The promotion evidence table also claimed the promoted book beat legacy on
"CAGR, Sharpe, and drawdown". That no longer holds: legacy residual 80 + hold
now has the shallowest drawdown (-15.8% vs -21.4%). Production still wins on
Sharpe, so the promotion stands, but the text now says so honestly rather than
implying a clean sweep.

Also documents the primary-target reach-probability floor in the gate
description, which shipped in c7a198b/8f41143 but never reached the README.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 10:03:47 +02:00
dennisthiessen cb215e2595 Merge branch 'main' of ssh://git.thiessen.io:2266/dennisthiessen/signal-platform
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m12s
Deploy / deploy (push) Successful in 37s
# Conflicts:
#	app/services/fundamental_service.py
#	app/services/rr_scanner_service.py
#	app/services/scoring_service.py
2026-07-11 17:03:25 +02:00
dennisthiessen 727b147c81 Withhold stale-score trade recommendations 2026-07-11 16:56:52 +02:00
dennisthiessenandClaude Fable 5 292b9934b1 Scan survives score-refresh failures; fix expired-ORM crash after rollback
A scoring error no longer skips setup detection for the ticker: the
rollback already restores a clean transaction, and qualification
re-gates on live scores at alert time, so a stale score is recoverable
but a skipped scan is not.

Iterating symbol strings instead of Ticker instances fixes a latent
crash the new regression test caught: rollback() expires ORM objects
regardless of expire_on_commit, so touching ticker.symbol in the except
handler triggered sync lazy-loading, which raises on an AsyncSession
and killed the whole scan on the first per-ticker error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 16:29:06 +02:00
dennisthiessen 9450831ef3 Optimize signal read paths and enforce score invariants 2026-07-11 16:04:13 +02:00
dennisthiessen 25364f8e99 Optimize signal read paths and enforce score invariants
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m11s
Deploy / deploy (push) Successful in 37s
2026-07-11 13:36:59 +02:00
dennisthiessenandClaude Fable 5 fdc49d0e28 Setup views: primary-target column, floor-target prune, liveness cutoff
Three follow-ups to the gate probability floor (8f41143):

- Signals table shows the starred primary target (shared primaryTarget
  helper) instead of an independently computed max-probability best,
  so Overview, Signals and ticker details agree by construction.
- Targets pinned at the 3% probability clamp floor collapse to the
  nearest one (enhance_trade_setup + backtest candidates in parity):
  floor-pinned levels are indistinguishable to the model, so farther
  ones were duplicate 3% rows inviting lottery headlines.
- get_trade_setups only returns setups re-emitted within
  LIVE_SETUP_MAX_AGE_DAYS (3): an older latest row means the daily
  scan no longer confirms the setup, and such rows otherwise surface
  forever on Overview/Signals/ticker/alerts. History endpoints keep
  full history.

Backtest on the Jul-3 snapshot is metric-identical to the gate-floor
run on all qualified stats (1089 qualified, Sharpe 2.02, CAGR +49.6%,
DD -15.8%): the prune only removes noise the gate already rejected.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 10:06:34 +02:00
dennisthiessenandClaude Fable 5 8f411435ee Activation gate: primary target probability floor (>= 20%)
A qualified setup's primary target must now clear MIN_TARGET_PROBABILITY
(20%), shared with the primary-selection floor in recommendation_service
and mirrored in the frontend gate. Closes the read-time hole where a
stale pre-c7a198b row starring a far lottery target (probability pinned
at the 3% clamp floor, R:R inflated by the same distance) qualified
forever: the scanner emits no replacement row and live R:R never decays.

A/B backtest vs c7a198b baseline (same July-3 snapshot): 7 of 1096
qualified setups removed; qualified net avg R 0.202 -> 0.207, hold
Sharpe 2.00 -> 2.02, CAGR +48.8% -> +49.6%, max DD unchanged at -15.8%.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 09:31:28 +02:00
dennisthiessenandClaude Fable 5 924c474624 Track Record: Strategy/Lookback selects use the shared Dropdown
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m3s
Deploy / deploy (push) Successful in 34s
The portfolio-monitor filters were native <select> elements with a
hardcoded bg-slate-950 - a look from before the design system. They
now use the shared Dropdown (same as the Setups filters), which also
gives them the dark themed menu and the portal fix from 6464fdd.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:23:09 +02:00
dennisthiessenandClaude Fable 5 6464fdd4af Dropdown menus paint under later glass panels - portal them out
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m6s
Deploy / deploy (push) Successful in 35s
The Signals > Setups filter dropdowns (Direction, Recommended Action)
opened UNDER the panels below: glass containers use backdrop-filter,
which creates a stacking context, so the menu's z-50 could never
compete with later sibling panels regardless of value.

The menu now portals to document.body with fixed positioning tracked
against the trigger (re-measured on scroll/resize), and click-outside
accounts for the portaled node. Applies to every Dropdown usage.
Menu background also updated from the old green-tinted #151911 to the
Horizon #14161f.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 23:20:04 +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 c7a198ba8e Primary target: probability floor stops lottery headlines
Deploy / lint (push) Successful in 7s
Deploy / test (push) Successful in 1m4s
Deploy / deploy (push) Successful in 34s
A setup's primary target could carry a ~3% probability: the picker
chose the most likely target among those with R:R >= 1.5, and after a
run-up that pool can contain only far "lottery" levels (the near,
likely levels fail the R:R floor). The lottery target's inflated R:R
then became the setup's headline and passed the activation gate's
min_rr floor - the gate's probability check only requires a value to
exist.

Fix, no new tuning knobs: the primary must clear BOTH floors
(R:R >= 1.5 AND probability >= 20%). When nothing does, fall back to
the most likely target overall, so the headline carries an honest low
R:R and the gate rejects the setup on real numbers instead of being
gamed by an unreachable target.

Deliberately NOT pure EV-maximization (p*RR): the probability model
adds strength/alignment bonuses as flat percentage points, so EV
arithmetic would scale those bonuses by (RR+1) and systematically
favor far targets on aligned setups - the same lottery bias through
the back door.

Shared by production (enhance_trade_setup) and the backtest simulator,
so backtest comparisons stay apples-to-apples. Unit tests pin the
degenerate case; full unit suite green (497 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:31:35 +02:00
dennisthiessenandClaude Fable 5 6e33897c1a Mark-as-taken opens a modal dialog instead of expanding inline
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 1m1s
Deploy / deploy (push) Successful in 35s
The inline take form under the price rail pushed the card apart. It is
now a focused overlay dialog (portaled to body - the unified panel's
backdrop-filter/overflow would trap a fixed element):

- symbol + direction header, stop/target/suggested-size summary line
- shares (autofocused), entry, and target selector
- Confirm trade / Cancel; closes on success, Escape, or backdrop click
- the card and panel never move

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:10:13 +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
dennisthiessenandClaude Fable 5 744ea4ddc4 Document July 2026 tuning conclusions in Strategy Status
Deploy / lint (push) Successful in 10s
Deploy / test (push) Successful in 1m22s
Deploy / deploy (push) Successful in 39s
Record every tested-and-confirmed knob (ATR trail, regime overlay, lookback,
cutoff x book, sizing, FIP tie-breaker) so the sweep is not repeated on the
same snapshot, including the inverse-vol mis-attribution warning and the
universe-level fip_id lead. Prune the done items from the next-experiments
list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:59:52 +02:00
dennisthiessenandClaude Fable 5 ae1aeb3c84 Align backtest production sim with live runtime config
The portfolio monitor's Production row now replays the live qualification
flag and the Admin exit policy (mode/ATR multiplier/hold days) instead of a
frozen research-variant gate, so Admin tuning is reflected in the next run.
Single-source the 80/20 strategy_rank weights in momentum_service and pin
every dual-defined constant with a parity test. Behavior-preserving today:
the production sim reproduces the README baseline exactly.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 14:28:50 +02:00
dennisthiessenandClaude Opus 4.8 65d2dae62a Revert blue-sky projection; keep played-out setup UX
A local backtest (offline prod snapshot, 506 tickers) evaluated blue-sky
projected targets under the PRODUCTION exit (3x ATR trailing + 30d max hold,
paper_trade_service DEFAULT_EXIT_MODE="atr_trailing"). Blue-sky setups are
dilutive: the qualified book scored 328% return / Sharpe 1.84 / DD -21.0%
WITHOUT them vs 300% / 1.58 / -18.7% WITH them. They rank high on momentum by
construction, so they grab slots from S/R setups that catch bigger runs under
a trailing-stop exit (only ~2pp worse drawdown doesn't justify the lost return
and Sharpe).

Reverts the scanner/TargetGenerator measured-move projection, the stricter
projected activation gate, the frontend qualification mirror, the `projected`
type field, and the projected tests -- all backend files are now byte-identical
to the pre-blue-sky commit.

Keeps the played-out "No current setup" UX (RecommendationPanel): when price
has run past the target (played out) or through the stop (invalidated), the
panel shows a plain no-setup state instead of a stale actionable card. This is
frontend-only (reads last close + existing setup fields) and is what actually
fixes the reported stale-below-price bug -- no backend change or rescan needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 22:03:25 +02:00
dennisthiessenandClaude Opus 4.8 294d935030 Add blue-sky projected targets and played-out setup UX
Fixes stale below-price setups showing as current recommendations. Three
distinct causes share the symptom (get_trade_setups returns the latest stored
setup per direction and never expires it):

- Genuine blue-sky (no overhead S/R): scanner + TargetGenerator now project a
  measured-move target (entry +/- 3*ATR, ~2:1 R:R), flagged projected with a
  low sr_strength probability haircut. Overhead check keys on level tag OR price
  so it never projects through a straddling resistance cluster.
- Projected targets clear a stricter activation bar (long-only, momentum >= 90,
  confidence >= min+10), independent of the general momentum gate. Mirrored in
  frontend qualification.ts.
- Played-out UX (fixes the reported TTWO case, which is R:R-starved under a
  resistance cluster, not blue-sky): when price is at/past target or through the
  stop, RecommendationPanel shows a "No current setup" state and softens the
  stale ticker-level header/reasoning, instead of a stale actionable card.

No migration: the projected flag rides in existing targets_json. 504 backend
unit tests pass; frontend typechecks.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-08 19:30:28 +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 65335cf1f3 Tighten qualified signal gate
Deploy / lint (push) Successful in 8s
Deploy / test (push) Successful in 1m18s
Deploy / deploy (push) Successful in 40s
2026-07-04 13:51:44 +02:00