Weekly Monitor

Weekly Monitor — Methodology (Audit Trail)

*Last updated: 2026-07-27 (universe 1,000; honest caveats added on the two factors that behave

differently on the live keyless path — see §3). The scoring engine lives in lib/scoring.js;

its core math is pinned by known-answer tests in tests/scoring-unit.mjs.*

Every number the app shows is computed deterministically from data — no hidden judgment.

This document states the exact weights, formulas, and thresholds behind each score, cross-referenced

to the functions in lib/scoring.js and server.js. Change a number in the code and the output changes; that is the point.

Guiding principle: our own calculations lead; analyst consensus is a supplement, never the driver.

Analyst price targets are consensus, so leaning on them can't surface mispricings — they only confirm.


1. Monitor grade (Best Overall / Quality Value / Parabolic) → scoreTicker

A name's grade is a weighted blend of five component scores, each 0–100. Missing inputs are dropped,

the remaining weights renormalized, and the result is then **shrunk toward neutral 50 in proportion to the

missing weight** (50 + (raw−50) × (0.5 + 0.5×coverage), 2026-07-15) — so a name graded off two components

can no longer outrank one graded off five. The coverage field (formerly mislabeled "confidence") is the

% of input weight present: data coverage, not conviction.

ComponentFunctionHow it's scored
ValuevalueScoreBlend 0.6 × our DCF upside + 0.4 × analyst-target upside when both exist (the monitor computes the same WACC-discounted computeDCF as the screener, 2026-07-15). Analyst-only haircut: when the target is the sole input its upside is cut 15% (TARGET_HAIRCUT) — sell-side targets run systematically optimistic. Mapped 35 + blend×160, clamped 0–100 (−20%→0, 0%→35, +25%→75, +50%+→100). Fallback if neither exists: 60% of the gap to the 52-week high, score capped at 60 — the falling-knife guard: a big gap-to-high must never outrank a target/DCF-sourced value case.
TrendtrendScoreAbove 200-DMA → base 70, else 25. Golden cross (50>200) +15 (else −10). Above 50-DMA +10 (else −5). −4 if only a 50-DMA proxy is available.
Entry/timingentryScoreBase 50. RSI ≤35 +25 / ≤45 +12 / ≥60 −10 / ≥70 −25. At support (≤2.5%) +18, ≤6% +8, below support −20, >20% above support −8. Extended above 50-DMA: ≥8% −7, ≥15% −15.
RatingratingScoreAnalyst consensus skew net = (buy−sell)/total; base 50 + net×45. Small nudge for target upside, capped at [−15, +20].
QualityqualityScoreWeighted: ROIC−WACC value-creation spread 40% (2026-07-17: ROIC [ROE proxy] minus the name's own Blume-CAPM cost of capital — the same rate the DCF discounts at; spread 0→50, +10pp→75, −10pp→25. Previously an absolute ROIC curve, 20%→85 / 10%→50, which gave every name the same hurdle), net debt/EBITDA 35% (net cash→100, banded down to 10 at ≥5×), FCF yield 25% (5%→80).

Per-list weights & curve (LIST_PROFILE) — each list grades on its own curve on purpose:

Listvaluetrendentryratingqualitycurve
Own0.250.220.180.180.170
Quality Value ('deep')0.400.180.120.150.15+3
LEAPS0.280.280.220.120.100
Parabolic0.380.180.180.160.10+8

Composite → letter (letter, applied to composite + curve):

A ≥86 · A− ≥80 · B+ ≥74 · B ≥66 · B− ≥60 · C+ ≥54 · C ≥46 · else C−.

Signal (signal) — the rule the backtest said mattered: never buy support in a downtrend.

Sizing (sizing): Parabolic → Starter/Lottery. Below 200-DMA → Starter. Large-cap (≥$200B) & composite ≥78 → Core. Mid (≥$20B) & ≥66 → Standard. Else Starter.

Reward:risk (rewardRisk): value-blend upside ÷ risk, where risk = max(distance-to-support, 8%) in a clean uptrend (RISK_FLOOR, 2026-07-15 — support breaks, so sitting 1.5% above a floor is not 1.5% of risk and can no longer print 20:1 ratios), else a 12% (uptrend) / 20% (downtrend) band.


1b. Sector profiles → SECTOR_PROFILES, resolveSectorProfile (2026-07-23)

The same five assumptions don't fit a bank, a miner, and a SaaS company — so every name is scored

through a sector profile that adjusts three things, each with a stated rationale shown on the

card ("How this grade works"):

1. DCF assumptions — the growth cap (a trough-year memory rebound or peak-cycle oil FCF must not

extrapolate for 5 years), the terminal growth rate, an optional discount floor, or a full veto

(dcf: false) where an FCF DCF is not meaningful.

2. Monitor component-weight tilts — multipliers (0.8–1.25 by design) composed onto the list

weights (LIST_PROFILE), then renormalized to sum 1. The sector shades the lens; it never

replaces the engine.

3. Quality lens shape — the ROIC/debt/FCF part weights, plus debtShift, which moves the

net-debt/EBITDA thresholds right where structural leverage is normal.

4. Screener factor tilts — same multiplier treatment on SCREEN_W, renormalized.

ProfileDCF growth cap / terminalKey tiltsQuality lens
Semiconductors20% / 2.5%trend ×1.15, entry ×1.10standard
Software30% / 3.0%quality ×1.15ROIC-tilted (.45/.25/.30)
Internet platforms30% / 3.0%quality ×1.15, rating ×1.05ROIC-tilted
AI infrastructure30% / 2.5%, r ≥ 9%trend ×1.20, quality ×0.85standard
Payments25% / 3.0%quality ×1.15ROIC-tilted
Healthcare25% / 2.5%rating ×1.20standard
Industrials15% / 2.5%quality ×1.10, value ×1.05standard
Consumer15% / 2.5%entry ×1.10standard
Energy8% / 2.0%quality ×1.20, trend ×1.15, value ×0.80debt-tilted (.35/.40/.25)
Materials10% / 2.0%quality ×1.15, trend ×1.10debt-tilted
FinancialsDCF vetoedrating ×1.15, value ×0.85ROE vs cost of equity only (debt/FCF parts dropped — meaningless for balance-sheet businesses)
REITs6% / 2.0%entry ×1.05debtShift +3 (5× EBITDA is normal), FCF-tilted
Telecom & media8% / 2.0%value ×1.05debtShift +1.5, FCF-tilted (.30/.30/.40)
Autos10% / 2.0%trend ×1.10, quality ×1.10debt-tilted
Utilities6% / 2.0%quality ×1.10, entry ×1.05debtShift +2
Generalist (default)30% / 2.5%nonestandard (.40/.35/.25)

Resolution order: curated SECTOR_BY_TICKER (the hand-assigned SCREEN_LIST sector) → row's own

sector → Yahoo assetProfile sector folded through YAHOO_SECTOR_MAPdefault (neutral,

identical to pre-profile behavior). When the Financials veto suppresses the DCF, the value component

falls back to the analyst target with the standard 15% haircut, and the card says so explicitly

(detail.sectorProfile.dcfNote). The effective post-tilt weights are published per card in

detail.sectorProfile.weights — the numbers on screen are the numbers used.


2. DCF intrinsic value → computeDCF

Our own 5-year discounted-cash-flow model — this, not FMP, is what the app's "DCF" means.

5 years — the cap and terminal rate come from the sector profile (§1b); the generalist default is 30% → 2.5%.

r = clamp(8%…12%, 4.3% + β* × 4.7%) where β* = 0.33 + 0.67β (Blume shrinkage — raw historical

betas overshoot). Names with no beta use the old flat 10%. Why: the 2026-07-10 parallel

analysis showed flat-10% treats a β0.25 utility and a β2.0 speculative name as equally risky,

while raw (un-shrunk) CAPM over-tilted rankings toward low-beta defensives (+42pp median upside);

the Blume + 8–12% clamp variant kept rank correlation ≈0.975 with sensible risk adjustment.

local-currency financials against a USD market cap: TSM/PDD/JD…; float-inflated insurer FCF:

ALL/TRV/AIG), not an opportunity. dcfFactor then falls back to the analyst target.

The full year-by-year waterfall (incl. the discount source) is shown in the Screener card so it can be checked by hand.

Reverse DCF (impliedGrowth, 2026-07-15): the same machinery run backwards — bisection solves for

the year-1 FCF growth rate the current market price implies (searched over −60%…+150%; null when the

price sits outside that band). A stock isn't cheap because a DCF says so; it's cheap when the market's

implied growth is mathematically below what the company delivers. Shown on screener rows and monitor

tear sheets next to the forward-growth estimate, so the disagreement — the edge, or the error — is explicit.

The two lenses run on different growth premises, and the card now says so (2026-07-30). The forward

DCF credits at most the sector growth cap (20% for Semiconductors, 30% default); the reverse DCF is

uncapped across its −60%…+150% search band. On a name growing far above its cap both figures are

correct and they read as a contradiction: NVDA showed "DCF fair value $32 (−83% vs price)" directly

beside "the market is pricing less growth than the numbers show." The first credits 20% growth, the

second credits 214%. Where the cap binds, the fair-value sentence now names it — *"deliberately

conservative: the model credits at most 20% year-1 growth (the Semiconductors cap), not the >+200% the

data shows, so read it as a floor rather than a target."* No score changed; the sector A/B contract is

byte-identical. This is a disclosure fix, and it is pinned in tests/scoring-unit.mjs in both

directions — capped names must carry the note, uncapped names must not.

Value-creation spread (2026-07-17): every card surfaces ROIC vs WACC — return on capital

(Yahoo ROE, an admitted proxy) against the same CAPM discount rate the DCF uses — as a signed

spread in points. Positive spread + growth compounds value; negative spread means growth destroys

it. Shown in the tear-sheet valuation summary, the screener Value line, the financials blurb, and

Decision Queue evidence; recorded in every ledger snapshot. The monitor tear sheet's Fundamentals

tab now renders the full DCF waterfall (same DcfBlock glass box as the screener — assumptions,

per-year projection, terminal value, EV→equity→per-share, input provenance).

Context comparators (2026-07-15): sector medians of P/E and RSI (robust median over the

screener's sector distributions) accompany the raw values on cards — a multiple without its sector curve

is noise, and an oversold RSI in a firm sector is a stronger signal than one falling with its group.

FCF yield is shown against the 4.3% risk-free rate (the spread is the equity risk premium actually

on offer). The Black–Scholes blurb now surfaces the tension when a target implies ≥25% upside but

the name's own volatility prices those odds under 25% within a year.


3. Screener composite (1,000-name universe) → scoreScreen, weights SCREEN_W

FactorWeightSource
DCF upside0.18computeDCF (ours, CAPM-discounted §2)
Technicals0.11trend/RSI/support
Revenue growth0.12Yahoo — 60% absolute + 40% sector-relative
Market/forward growth0.09Yahoo — 60% absolute + 40% sector-relative. ⚠ Honest caveat (2026-07-27): on the keyless Yahoo-only crawl (the live default), the "forward" input is Yahoo's earningsGrowth, a trailing figure used as a proxy — so this factor is correlated with Revenue growth and Growth quality's EPS component rather than independently forward-looking. True forward estimates require the FMP path.
Balance sheet0.12FCF yield 0.35 · leverage 0.30 · EBITDA margin 0.20 (60/40 blend) · gross margin 0.15 (sector-relative)
Growth quality0.12margin trend (0.20 of this factor) is structurally null in live production — it needs opIncomeGrowth, which only the FMP branch sets and the keyless screener crawl skips; it is also absent from the warm-start fields, so nothing backfills it. The remaining three parts renormalize.EPS growth 0.35 · margin trend 0.20 · ROIC−WACC spread 0.25 (2026-07-17: vs each name's own Blume-CAPM cost of capital; was a flat 9% hurdle) · Rule of 40 0.20 (sector-relative, clamped inputs)
Est. momentum0.08analyst revisions. ⚠ Honest caveat (2026-07-27): structurally null in live production — the input comes only from the Finnhub branch, which the Yahoo-only screener crawl skips, so this 8% is renormalized across the other factors for every screener name. The weight is published because the factor fires when keys/paths allow; the renormalization is the standard null-factor mechanic (§3 note below).
Valuation0.09PEG first → sector-relative EV/EBITDA (no-PEG fallback) → P/E band
Price momentum (12-1)0.09momentumFactor — trailing-year return excl. the last month, ranked cross-sectionally (robust z vs the whole universe). Adopted 2026-07-14 from the Factor Lab's walk-forward evidence (§7d: 6-month IC 0.043, t = 1.98, 42 rebalances); weight trimmed pro-rata from the other factors (prior weights: .20/.12/.13/.10/.13/.13/.09/.10).

Analyst price targets are not in this composite — only displayed alongside.

Sector-relative scoring (adopted 2026-07-11, → buildSectorStats/sectorZ/blendZ): sector-structural

metrics are judged against the name's own sector — 30% revenue growth is mediocre for software but elite

for industrials; 40× EV/EBITDA is normal for semis, absurd for telecom. Mechanics: robust z-score within

sector (median + MAD, so one outlier can't warp the curve), mapped to 0–100 via 50 + z×20; sectors with

fewer than 8 usable values fall back to the whole-universe distribution. Growth/margin factors **blend 60%

absolute + 40% relative** so a shrinking sector can't mint winners purely by curve ("best house in a bad

neighborhood" guard); Rule of 40 (inputs clamped: growth ±150pp, margin ±100pp), EV/EBITDA and gross margin

enter purely sector-relative. Each card's "Model context" line shows the metric next to its sector median.


4. LEAPS opportunity score → scoreLeap, weights LEAP_W

Hunts the whole universe for a strong, favorably-priced, paying-off option. Scored on the balanced (~ATM) contract. Two structural rules (2026-07-12): contracts whose ATM implied vol is < 12% are rejected as stale quotes on illiquid chains (not bargains), and the top 15 carries max 3 names per sector — the upside components saturate above ~+78%, so a single hot theme (e.g. peak-margin commodity DCFs) would otherwise sweep every slot:

ComponentWeightMeaning
Conviction0.18the stock's screener composite
Upside0.15stock's distance to our DCF (analyst target only as fallback)
Value edge0.24implied vol vs the stock's realized vol — implied below realized = a cheap option (mispricing)
Option return0.23the contract's % return if the stock reaches fair value
Odds of profit0.12model-implied chance of finishing above break-even
Liquidity0.08open interest

Contracts, greeks, and probabilities come from live CBOE data + Black-Scholes (below). Strikes are chosen by delta (~0.75 ITM / ~0.55 ATM / ~0.35 OTM).


5. Most Upside (opportunity ceiling) → scoreUpsideName

Model-first ceiling: max(DCF upside, revenue-growth proxy, distance-to-52-wk-high) + volatility×0.5,

then blended 70% our model + 30% analyst target (analyst is a supplement, never the driver). Capped at +500%.

Risk = max(annualized volatility, max-drawdown×0.8), ×1.2 when below the 200-DMA. Reward:risk = ceiling ÷ risk.


6. ETF score (75-fund pool) → scoreETFRow

Weights: Growth 0.28 · Value 0.27 · Technicals 0.25 · Fund financials 0.20.

Growth/value come from the holdings' aggregate fundamentals (Yahoo topHoldings); financials = the fund's P/CF, expense ratio, yield. Only equity-holding ETFs (bond/physical-commodity funds are excluded — no holdings fundamentals to score).


7. Black–Scholes projection (every stock card) → blackScholesBlurb, bsCall, _normCdf

From each name's own annualized volatility (σ, from price history) over a 1-year horizon, risk-free rate RF_RATE = 4.3%:

Labeled a model estimate, not a forecast — risk-neutral, so it's the honest/conservative reading.


7b. Radar (overlooked opportunities) → buildRadar, weights RADAR_W

Reuses the screener's already-scored rows (zero extra fetching). Eligibility gates: screener composite ≥ 55 (quality first — our calculated factors only), market cap ≤ $50B, ≤ 8 covering analysts (numberOfAnalystOpinions), and not currently trending on StockTwits.

Radar score = 0.55 × composite + 0.25 × coverage gap + 0.20 × ownership gap, where

coverage gap = clamp(100 − analysts × 11) and ownership gap = clamp(110 − instOwn% × 1.4) (unknown ownership scores a neutral 50). Analyst targets play no part — coverage count is used only as a neglect signal (fewer = more overlooked). Top 15 shown.

Why institutional ownership is a score input but NOT a gate (revised 2026-07-14): live data showed

heldPercentInstitutions is unreliable at the small end — 164 of 600 names reported >100% (float vs

shares-outstanding and double-counted 13F artifacts) — and in the passive-index era even $2B companies are

~90% "institution-owned" mechanically, so a ≤70% gate matched almost nothing (4 names in a 651-name

universe). Sell-side analyst coverage is the honest neglect signal. The bounded ownership component

is artifact-safe: anything ≥79% already scores zero on that leg.


7c. Model-context line → precisionAnalysis

Born 2026-07-10 as the read-only "parallel analysis" used to trial sharper methodology before adoption. Everything it trialed has since graduated into the scored model: the WACC-DCF on 2026-07-11 morning (§2), and Rule of 40 / EV-EBITDA / gross margin later that day as sector-relative inputs (§3). The line now provides transparency instead: each adopted metric shown next to its sector median (the yardstick it's scored against), plus two references that remain informational only — the flat-10% DCF (pre-adoption model, for continuity) and EV/Sales.


7d. Factor Lab (walk-forward factor validation) → runFactorLab

The evidence layer the composite weights answer to. Monthly walk-forward across the whole screening

universe (live: 42 rebalances over ~3.5 years, 545 names): at each date, price-derived factors are

computed only from data available then (12-1 momentum, trend vs 200-DMA, RSI-14, 63-day volatility,

52-week-high proximity, and a blended price composite), then forward 1/3/6-month returns are measured

across the cross-section. Reported per factor × horizon: mean Spearman IC, a t-statistic

(mean/std × √n), % of dates positive, and the average top-minus-bottom quintile spread.

Honesty constraints (also shown in the UI): fundamental factors (DCF, growth, margins, valuation)

are not tested — only today's fundamentals exist, and applying them to past prices is look-ahead

bias; the universe is today's list (survivorship bias); results are gross of costs. First live run

(2026-07-14): trend vs 200-DMA was the strongest validated factor (t = 3.64, 70% of months positive) —

supporting the composite's Technicals weight and the REVIEW rule — and 12-1 momentum cleared

significance (t = 1.98), leading to its adoption at 9% (§3). The high-volatility "signal" (t = 2.98)

was deliberately not adopted: a bull-regime + survivorship artifact.


7e. Market Pulse (Home) → computePulse, endpoint /api/pulse

The macro monitor that fronts the Home view (2026-07-15): sector breadth heat (per sector, % of

screened names above their 200-day average + median composite + median 12-1 momentum — derived from the

screener's already-scored rows, zero extra fetching), overall breadth (% of the whole screened

universe above the 200-day), and the Treasury yield curve — official **FRED H.15 constant-maturity

yields** (keyless CSV, series DGS3MO/DGS2/DGS5/DGS10/DGS30; ~1-business-day lag, as-of date shown on

screen) with the 10y−3m spread and an inversion flag. Yahoo index tickers (^IRX/^FVX/^TNX/^TYX)

remain only as a labeled fallback — ^IRX is a 13-week discount rate, ~10–15bp under the published 3M

CMT, which is why the Yahoo-sourced curve read slightly off. Cached 30 min; an empty-breadth pulse

(screener still crawling) expires in ~2 min instead.

The screener composite carries the same coverage shrink as the monitor (missing factor weight pulls

the composite toward 50), and dcfFactor's analyst-target fallback gets the same 15% haircut as

valueScore — the sell-side number is never taken at face value when it's the sole input.

7f. Signal Ledger → buildSnapshot / computeLedger, endpoints /api/snapshot /api/ledger

The accountability layer (2026-07-16): the engine's opinions are recorded, then graded.

price, grade, composite, signal, sizing, and the fundamentals — FCF yield fy, ROIC ro,

WACC wa, ROIC−WACC value spread vs, reverse-DCF implied growth ig, modeled growth mg,

P/E pe, revenue growth rg (added 2026-07-17); plus an SPY benchmark quote and the average

data coverage) and commits it to the repo's ledger branch. The fundamentals make each

snapshot a genuine point-in-time record, which is what will let the Factor Lab

walk-forward test fundamental factors without look-ahead bias once history accrues (snapshots/YYYY-MM-DD.json +

index.json). The branch is append-only in practice and public — a track record nobody can

quietly rewrite. Render's free disk is ephemeral; the repo is the durable store. Validation:

never commits mock data, near-empty reads, or (after 3 retries) low-coverage reads.

(with 5- and 21-snapshot forward returns, absolute and minus SPY), BUY-flip hit rate, and

hit rate by grade bucket (every daily observation's ~1-month forward return, pooled by

A/B/C — the test of whether grades mean anything).

rolling hit rate; below 45% the app says, on screen, *"the strategy is out of regime — trust

the signals less."* Needs ≥8 measured flips before it speaks.

returns; grade buckets overweight names that stay in the universe.

The calibration scorecard (2026-07-23, → lib/scorecard.js, rendered in the Signal Ledger):

three questions the ledger now answers rigorously, each activating only when the sample supports it:

1. Do the signals differ the way they claim to? Per-signal (BUY/WAIT/REVIEW/VERIFY) pooled

~1-month forward stats — BUY must out-return WAIT must out-return REVIEW, or the rule is noise.

2. Does a higher composite predict a higher return? Per-snapshot-date Spearman rank IC

between composite and measured 21-session forward return (cohorts need ≥8 scored names);

reported as mean IC, t-statistic, and % of dates positive — pooled and split at 2026-07-23,

the sector-profile regime change, so the post-split series is the live verdict on sector-aware

scoring. ~0.05 mean IC is respectable for a live monthly-horizon signal.

3. When the model states odds, do they happen at that rate? Every snapshot records the

volatility model's own probabilities at observation time — p21 = P(higher in 21 sessions)

and pt = the card's published "chance of reaching the analyst target within a year" (same

Black–Scholes machinery, same numbers the user sees). Realized outcomes are binned against

predicted odds (40–45 / 45–50 / 50–55 / 55–60%): the predicted and realized columns should

match, bin by bin. The 21-session panel forms at 40 scored pairs; the 1-year target-odds

panel matures on a 1-year clock by construction and reports its accrual until then. Odds are

recorded at snapshot time, never backfilled — pre-2026-07-23 snapshots simply lack them.

Since the repo went private (2026-07-23) the ledger/warm-start reads authenticate with a

GITHUB_TOKEN (fine-grained PAT, Contents: read) in the server environment; the daily snapshot

Action needs no change (it writes with the workflow's own token).

7g. Exposure, catalysts, and the decision ritual (2026-07-16)

concentration, regression beta vs SPY (1y daily, current weights held constant — labeled an

approximation), annualized σ, 1-y max drawdown, and the correlated-cluster callout (≥50% of the

book in one sector across ≥3 names → "functionally one bet"). Position discipline: engine

sizing implies a max weight (Core ≤~8% · Standard ≤~5% · Starter ≤~2.5% · Lottery ≤~1%);

holdings past 1.25× the cap are flagged — "engine says Starter, you're holding 11%".

→ next-earnings date on every monitor/screener card ("the signal predates the print"), a

"Reporting this week" strip on Home, and a decide-before-the-print Weekly Review item.

decision (logged with a timestamp in the browser's review history); the review reads

"N of M decided" until every line has one.

a returning visit (≥6h gap) opens with "since your last visit: X signal changes, Y downgrades".

before it touches the tape (UI falls back to the engine price; the next poll self-heals).

grades/composites/signals exactly — a refactor can never silently change what an "A" means.

Intentional methodology changes re-baseline with --update and say so in the commit.

7h. Decisions, theses, and portfolio layers (2026-07-17 — all client-side, stored in the browser)

benchmark, and the QUALITY of the result (largest contributor's share of the move; ≥60% is

called out as narrow). Labeled "interpretation — moderate confidence" with its basis.

sector cap (40%), and per-sizing weight caps are user-editable — on the Exposure page, or

via the CHANGE POLICY action on a decision card (old → new value logged with a required

rationale). Every consumer (review triggers, discipline flags, holdings warnings) reads the

same store, and each rule displays its provenance ("your policy, set DATE" vs "app default").

Cause (appreciation vs trading, attributed from the prior review's stored prices+shares —

recorded from 2026-07-16 onward) / estimated Impact (20%-decline scenario × weight) /

Evidence / a decide-by date. Actions: KEEP · TRIM · ADD · SNOOZE (+ CHANGE POLICY on policy-backed items). Everything except snoozing

or dismissing an informational note requires a written rationale, logged with a timestamp.

assumptions** the engine re-checks from live detail — demand growth (≥ baseline−5pp), margin

durability (≥ baseline−3pp), valuation support (price below modeled fair value), price trend

(200-day), return on capital (≥9%). Missing data = "not measurable", never a strike.

Weakening always decomposes ("2 of 5 weakened: margin durability, valuation support"),

confidence = intact share (High/Moderate/Watch/Weak), evidence history appends weekly,

invalidation = kill criterion + ≤2 intact at two consecutive reviews.

(pp), dollar value, and per-holding contribution (thesis-tagged); gold baseline ticks mark

weeks with logged decisions or holdings changes. Constant current shares throughout —

return-excluding-deposits is NOT computable (no dated cash flows) and the page says so.

actual vs a gold target tick, drift in points, change since the last review, the dollar amount

a rebalance moves, and cause attribution ("~71% of this week's movement came from NVDA

appreciation — holdings unchanged → drift is price, not trading").

card (with a callout when ≥25% of capital has no written thesis) and a Return by thesis

rollup under the contribution view — is the return coming from the ideas you believe in?

5-session return) / Thesis / Conviction (grade + coverage) / Fair-value gap / Next catalyst /

Status (reason on hover + in the expanded row); price/day/sector demoted behind column

presets (Analysis / Trading / Everything) and per-column checkboxes (persisted); text +

status filters saveable as named chips; sticky header + first column; persisted density;

expandable rows; keyboard navigation (↑↓ move, Enter expands, Esc collapses).

(CAPM discount) vs model default (terminal rate, horizon); generated insights carry a

confidence tag and the data they're based on. Currency: all figures USD as reported —

no FX conversion is performed (disclosed in the data-trust line; ADR home-currency artifacts

are guarded in the DCF rather than converted).

7i. The review-centered structure (2026-07-17)

The Weekly Review is the product's spine, not a Portfolio tab. Navigation: **Overview → Review

(Weekly Review · Decision History) → Portfolio (Holdings · Exposure · Theses) → Research (all

ranked lenses + ETFs) → Models (Options · Sandbox) → Briefing → System** — one nav system; old

bookmarked groups migrate automatically. The Overview orders sections by decision priority:

weekly conclusion → decisions requiring review (with a start/continue call-to-action) →

portfolio condition → market context (breadth and the yield curve deliberately do not outrank

policy violations). The decision queue is a guided wizard — one consolidated decision at a

time ("Decision 2 of 6"), related flags on the same name merged into a single resolution, the

remaining queue in a compact sidebar, "Show all" for the full-list view. Sizing alerts are

tiered (sizingTier): Critical policy breach (>3× the cap) / Review sizing (>1.5×) /

Monitor (>1.15×) / silence when in line — the full explanation lives behind the tooltip, so red

is reserved for what's actually critical. Theses open with a guided first-thesis onboarding

(argument → drivers → risks → assumptions → invalidation → cadence) instead of empty cards; the

empty Signal Ledger shows an illustrative example table plus the next snapshot time. Privacy

is stated unmistakably where it matters: *personal holdings and decisions stay local to the

browser; the public ledger contains only the engine's signals — never portfolio data.* Type

scale: body ~14px, decisions 14–15px, controls ≥32px tall, secondary-text contrast raised.

7j. Portfolio loading & security (2026-07-17)

Two ways in, both engineered so holdings never leave the device — the security is

structural, not a promise:

(Fidelity / Schwab / Vanguard / IBKR / Robinhood / Webull); the file is read via FileReader

and parsed in the browser — there is no upload endpoint. Header-alias detection (not

per-broker templates), quoted-cell CSV parsing, cash/money-market rows skipped (specific

patterns — Fidelity's account-type "Cash" column famously false-positives naive filters),

BRK.B → BRK-B normalization, duplicate lots merged share-weighted, preview with per-row

checkboxes, then merge or replace.

passphrase-encrypted — PBKDF2-SHA256 (200k iterations, random salt) → AES-256-GCM (random

IV), WebCrypto in-browser. GCM authenticates, so a wrong passphrase or tampered code fails

loudly instead of loading garbage. Format WMPF2.b64(salt|iv|ct); legacy plaintext WMPF1

still imports. Passphrases are never stored and not recoverable.

holdings through a server and third party, breaking the local-only guarantee, and adds

accounts + per-connection cost. It's a product decision, not a default.

8. Data sources (all best-effort, gracefully degrading)

SourceUsed forKey
Yahoo Financeprices, history, fundamentals, ETF holdingskeyless (cookie/crumb)
CBOE (delayed)option chains + greekskeyless CDN
Finnhubanalyst ratings/consensusfree tier
StockTwitssocial sentimentkeyless
Anthropic ClaudeMacro / This Week / Hype editorial, one-line thesesANTHROPIC_API_KEY

Grades/scores work fully on the keyless sources; the Claude layer is editorial only. Nothing is invented — a

missing input drops out of the average rather than being guessed.

Data-quality guards (2026-07-21, → sanitizeFundamentals, bounds in Q_BOUNDS): a field can be present

but wrong — unit confusion (a percent read as a fraction is 100× too big), stale pre/post-split analyst

targets (rejected when outside ⅛×–8× of price), float-inflated insurer FCF yields (>60% of market cap),

negative-EBITDA leverage ratios (|x|>50), >100% institutional "ownership" from double-counted 13Fs (>150%),

NaN/∞, negative share counts. Impossible values are rejected at ingest against deliberately wide bounds —

unusual-but-real readings (a trough-base +1369% EPS rebound) pass. Order is the contract: **sanitize →

record → backfill** — a rejected value can never enter the last-known-good cache, and the two-tier warm-fill

replaces it with the last good value (the card then carries the gold "fundamentals as of… · last-good"

freshness stamp). Reject counts are public on /api/health (quality.rejects, per field).


Kept in sync with server.js. If a weight/threshold here disagrees with the code, the code is the source of truth — update this file.

See it run on live stocks →Every grade in the app links back to the section that defines it. Nothing here is advice.

← Plain-English Learn libraryTerms · Privacy →