Hardcoded Stats Audit · 2026-05-20
Single source of truth: src/lib/winning-strategy.ts (STRATEGY_PROOF, WINNING_STRATEGY),
re-exported via src/config/strategy-proof.ts (STRATEGY_PROOF, STRATEGY_CIS, sharpeDisclosure,
strategyHeadline, disclosureCopy).
Scope: src/app/methodologie/**, src/app/performance/**, src/app/docs/**,
src/app/use-cases/**, src/components/{methodologie,landing,portfolio}/**,
src/lib/i18n/**. Blog body and docs/method-review/** markdown audits out of scope
(cf 91-blog-stale-stats audit).
Severity legend
- HIGH: visible on /performance, /methodologie, landing, use-cases (public).
- MED: dev surface (/docs API examples), feature-flagged stubs, secondary copy.
- LOW: code comment, dev playground.
Findings
| file:line | current value | STRATEGY_PROOF | drift | sev | status |
|---|---|---|---|---|---|
| Ch06Backtest.tsx:559-561 (was) | hardcoded "1.21 / +0.42 / +45.8 % / -14.1 % / 57.1 % / [-0.57, +4.08] / 2025-01-01 to 2026-05-19" | oosResults.{sharpeAnnualized,sharpeDeflated,maxDDPct,hitRatePct,sharpeCI95Lo/Hi,testWindow,calmar} |
matches today, future drift | HIGH | FIXED (templated from STRATEGY_PROOF.oosResults) |
| Ch06Backtest.tsx:262 / ChDisclosure.tsx:108,114 (was) | "signalScore ≥ 50" | WINNING_STRATEGY.minScore = 40 (v3 scoring) |
Y, +10 pts | HIGH | FIXED (replaced with "≥ 40") |
| ChDisclosure.tsx:198-199 (was) | "44,3 % WR, −0,72 % T+90" (full-universe quote) | crossMarketUniverse.{winRatePct:46.8, meanReturnT90Pct:0.78} AND srMarkets.FR.{wr:44.3, mean:-0.25} |
Y on mean (-0.72 vs -0.25); 44.3 WR matches FR-only, not cross-market | HIGH | FIXED (uses crossMarketUniverse via existing universeWinRate/universeAvgT90) |
| performance/page.tsx:189-196 (was) | fallback strings "1.21" / "+46.2%" / "-14.1" / "2025-01-01 to 2026-05-19" | STRATEGY_PROOF.oosResults (always populated) |
dead branch but drift risk | HIGH | FIXED (fallback now reads STRATEGY_PROOF.oosResults; CAGR fallback dropped to "·") |
| use-cases/quant-fund/page.tsx:89-90 (was) | "win rate à T+90 d'environ 67 % pour les signaux à score ≥ 65" | no such bucket in STRATEGY_PROOF | Y — unfounded claim | HIGH | FIXED (now cites winRate=77, avgReturn=13.2, minScore=40, n=196) |
| performance/page.tsx:704,714 | "score composite ≥ 50 + insider de haut rang ou cluster" | WINNING_STRATEGY.minScore=40 (signal engine) vs harness ≥50 in lib/performance-data.ts:373,402 |
independent backtest cohort | MED | LEFT (intentional decoupling; ≥50 is the "Recommended Sigma" backtest filter, not the live engine threshold) |
| Ch06Backtest.tsx:77-86 EQUITY_PATH + +25% label | hand-drawn SVG curve, "+25%" anchor text | n/a — illustrative | n/a | LOW | LEFT (decorative; CAGR callout already reads STRATEGY_PROOF.avgReturn) |
| WalkForwardAnimator.tsx:26-32 | full WF_STEPS stub (sharpe 1.21/1.44/0.87/1.63/1.19/1.38/0.94) | none | n/a, behind SHOW_WALK_FORWARD_ANIMATOR=false |
MED | LEFT (feature-flagged; TODO already documents public/data/walk-forward-sharpe.json swap) |
| FormulaTypewriter.tsx:31 | "+17.2 pts" sample | illustrative | n/a | LOW | LEFT |
| LiveScoringPlayground.tsx:438 | (returnT90/20)*weights.returnT90 formula |
illustrative | n/a | LOW | LEFT (toy widget) |
| performance/page.tsx:881 | "+11 % CAGR" copy from "average T+90 compounded over ~4 cycles/year" | could compute from avgReturn |
mild drift (13.2/4≈11 holds) | LOW | LEFT (illustrative arithmetic, not a stat citation) |
| docs/page.tsx:1074 | example JSON "value": 0.42 |
unrelated to oos sharpeDeflated=0.42 |
coincidental | LOW | LEFT |
| PITLeakCheckGate.tsx:60-61 | "k factors = 583,200" / "583 200" | hard literal in copy; matches GRID_SEARCH_TRIALS const in winning-strategy.ts:779 |
matches today | LOW | LEFT (could export GRID_SEARCH_TRIALS but the const is also a copy literal) |
| Ch08Strategie.tsx:212-213,474-475 | "583 200 backtests" / "N=583,200" in copy | same | matches | LOW | LEFT (same reason) |
Flags for Simon
use-cases/quant-fundcohort claim was fabricated. "67 % T+90 at score ≥ 65" did not exist anywhere inSTRATEGY_PROOF,crossMarketUniverse,srMarkets, nor any audit doc. Replaced with the legitimate filtered-subset numbers. If a real backtest at score-bucket ≥ 65 exists, surface it explicitly inSTRATEGY_PROOF(e.g. addscoreBuckets: Record<60|65|70|...>) before re-claiming it.crossMarketUniverse.meanReturnT90Pct = 0.78disagrees withsrMarkets.FR.mean = -0.25. The ChDisclosure copy block previously said "−0,72 %" which matches neither value. The current refresh likely propagatedcrossMarketUniverse(cross-market positive) but kept the older FR-only narrative in one copy block. Verify which number Simon wants quoted in disclosure when the backtest is rerun.signalScore ≥ 50vs≥ 40: two distinct gates exist in code:WINNING_STRATEGY.minScore = 40(live signal engine, scoring v3 threshold)lib/performance-data.ts"Recommended Sigma" backtest filter≥ 50(and Ch01-style filters) Methodologie + ChDisclosure copy now say≥ 40(matchingWINNING_STRATEGY);/performance"Recommended Sigma" still says≥ 50because that is the actual backtest harness threshold rendered. These should be aligned (either bump engine to 50, or rerun the recommended-Sigma backtest at 40) so users see one threshold, not two.
Performance hero fallback CAGR
+46.2%: previous fallback exceeded the OOS Calmar-derived value (calmar × |maxDDPct| = 3.25 × 14.1 ≈ +45.8%). Fallback dropped; ifmonthlyPortfolio.meanPctever goes null the surface degrades to "·" instead of a fake number.exitRules.expectedHoldingDays = WINNING_STRATEGY.holdingDays = 90butidealExitDays = 270is shown on BUY cards. Confirm copy on /portfolio/DeclarationCard does not double-cite both as the same horizon (current code atDeclarationCard.tsx:330+ looks fine — usesidealExitDaysonly).
Files patched (5)
src/app/methodologie/_components/Ch06Backtest.tsx— OOS block templated fromSTRATEGY_PROOF.oosResults;signalScore ≥ 50→≥ 40.src/app/methodologie/_components/ChDisclosure.tsx—"44,3 % WR, −0,72 % T+90"→ templated fromcrossMarketUniverse;signalScore ≥ 50→≥ 40(×2 locales).src/app/performance/page.tsx— Hero fallback strings replaced withSTRATEGY_PROOF.oosResultsand "·".src/app/use-cases/quant-fund/page.tsx— Fabricated 67% / score ≥ 65 claim replaced withSTRATEGY_PROOF.winRate,avgReturn,filteredSubsetSize,WINNING_STRATEGY.minScore. AddedSTRATEGY_PROOF+WINNING_STRATEGYimports.
(Five surfaces touched; no other HIGH items found.)
Restant à faire (estimated effort)
- MED: swap
WalkForwardAnimator.tsxstub forpublic/data/walk-forward-sharpe.jsononce harness 43 emits the file — ~30 min wiring + ~10 min visual QA. - MED: align live engine threshold and "Recommended Sigma" backtest threshold (40 vs 50). Either rerun backtest at 40 (
1h compute + audit refresh) or document the 50-threshold cohort explicitly in15 min code + audit cross-ref).STRATEGY_PROOF.recommendedSigmaas a separate sub-record ( - LOW: export
GRID_SEARCH_TRIALS(currently a module-local const) and consume inPITLeakCheckGate/Ch08Strategiecopy — ~10 min cosmetic.
Verification
npx tsc --noEmit— 0 errors after edits.- No commits / pushes made.
Pass 2 · zones étendues (2026-05-20)
Scope extension beyond pass 1: full src/components/** (Hero, landing, methodologie,
seo, portfolio, admin, email), src/lib/email*, src/lib/mcp/{tools,execute}.ts,
src/app/api/v1/**, OpenAPI spec, sitemap + robots + hreflang surfaces, JSON-LD
generator, CSS keyframes, and BlogArticle.bodyMdx (read-only via
scripts/_audit-blog-pass2.ts, output /tmp/blog-stats-audit.csv).
Findings table
| file:line | current value | canonical (STRATEGY_PROOF) | drift | sev | status |
|---|---|---|---|---|---|
src/lib/mcp/tools.ts:142 (was) |
hardcoded Sharpe annualisé 1.21 / CI95 [-0.57, +4.08] / CAGR +45.8 % / max-DD -14.1 % / hits 57.1 % / +0.42 / n=196 / 77 % / +13.2 % / 1.87 / ≥ 40 |
STRATEGY_PROOF.oosResults.* + STRATEGY_PROOF.{winRate,avgReturn,sharpeCrossSectional,filteredSubsetSize} + WINNING_STRATEGY.minScore |
matches today, future drift | HIGH | FIXED (templated via IIFE) |
src/lib/mcp/execute.ts:260 (was) |
same triplet as above in get_winning_strategy_signals runtime description |
same | matches today, drift risk | HIGH | FIXED (templated via IIFE) |
src/app/api/v1/strategy/winning/route.ts:46 (was) |
"score v3 ≥ 40 ... Horizon T+90" |
WINNING_STRATEGY.{minScore,holdingDays} |
matches | HIGH | FIXED (template literal) |
src/app/api/v1/strategy/winning/route.ts:1-16 (was) |
header comment cited +13.2% avg, alpha +10.1, Sharpe 1.87, 0.40, null |
STRATEGY_PROOF.* |
comment-only, but factually drift-prone (0.40 is today's sharpeAnnualized in-sample) |
MED | FIXED (rewrote to reference fields, not literals) |
src/components/landing/LandingSigma.tsx:134-135 |
... ?? 1.21).toFixed(2) / ?? 0.42).toFixed(2) fallback strings |
STRATEGY_PROOF.oosResults.{sharpeAnnualized,sharpeDeflated} (always populated) |
matches; fallback unreachable in practice | MED | LEFT (dead fallback, never hit — Pass-1 style decision) |
src/components/methodologie/WalkForwardAnimator.tsx:26-32 |
WF_STEPS stub (1.21/1.44/0.87/1.63/1.19/1.38/0.94) |
none | n/a, behind SHOW_WALK_FORWARD_ANIMATOR=false (see Pass-1) |
MED | LEFT (already documented Pass-1 line 29) |
src/components/methodologie/FormulaTypewriter.tsx:31 |
"+17.2 pts" illustrative |
n/a | n/a | LOW | LEFT (already Pass-1) |
src/components/methodologie/LiveScoringPlayground.tsx:438 |
(SAMPLE.returnT90/20) * weights.returnT90 |
n/a | toy widget | LOW | LEFT (already Pass-1) |
src/components/methodologie/PITLeakCheckGate.tsx:60 |
"k factors = 583,200" |
matches GRID_SEARCH_TRIALS const |
matches | LOW | LEFT (Pass-1 noted) |
src/components/SignalWeightPlayground.tsx:75 |
comment: "avg 13.2 %, win rate 77 %, Sharpe ~1.87" |
matches current STRATEGY_PROOF | comment only, drift risk if rebaselined | LOW | LEFT (code comment, allowed) |
src/components/landing/LandingTrackRecord.tsx:35-37 |
seed = [0.58, 0.62, …] 23-point sparkline |
n/a — "fake historical win rate" explicitly labelled (line 34) + "illustratif/illustrative" tag at line 161 | n/a | LOW | LEFT (illustrative + disclosed) |
src/components/landing/LandingHowItWorks.tsx:169-171 |
LVMH +18.4%, Schneider +12.1%, Hermès +9.7% hardcoded deltas |
n/a — demo cards | n/a | LOW | LEFT (illustrative example data) |
src/components/BacktestDashboard.tsx:599-600,1068-1069 |
"~55-60%" CAC40 baseline copy |
external baseline, not Sigma proof | n/a | LOW | LEFT (market reference) |
src/components/HoldingPeriodGuide.tsx:61,70 |
"T+60 et T+90" exit guidance |
WINNING_STRATEGY.holdingDays=90, STRATEGY_PROOF.idealExitDays=270 |
T+60 is an explicit narrative warning window, not a stat citation | LOW | LEFT |
src/lib/seo/jsonld.ts:71,97 |
wordCount field |
read from BlogArticle.wordCount DB column |
n/a | LOW | OK (already DB-sourced) |
CSS keyframes (globals.css, landing.css) |
no 57%/77%/45%/14%/13% keyframe stops matching stat literals |
n/a | n/a | n/a | CLEAN |
Email templates (src/lib/email/templates/*, src/lib/email.ts, src/lib/weekly-digest.ts, src/lib/newsletter-recap.ts) |
no hardcoded headline stats | n/a | n/a | n/a | CLEAN |
OpenAPI spec (src/lib/openapi-spec.ts, src/app/api/openapi.json) |
no headline stats cited in descriptions | n/a | n/a | n/a | CLEAN |
| Sitemaps + robots.ts + hreflang | URLs only, no stat literals | n/a | n/a | n/a | CLEAN |
src/components/seo/{EntityCrosslinks,RelatedArticles}.tsx |
no stat literals | n/a | n/a | n/a | CLEAN |
src/components/portfolio/{CsvImport,DemoBanner}.tsx |
no stat literals | n/a | n/a | n/a | CLEAN |
src/components/admin/SourceDebugPanel.tsx |
no stat literals | n/a | n/a | n/a | CLEAN |
src/components/StrategyProofHeadline.tsx |
comment cites 1.87* |
reads STRATEGY_PROOF everywhere in render path |
n/a | n/a | CLEAN (template-driven) |
Blog body MDX (read-only, NOT patched)
Audit: scripts/_audit-blog-pass2.ts → /tmp/blog-stats-audit.csv (111 lines, 110 unique tuples).
- 110 unique
(slug, locale, pattern, literal)tuples flagged across 104 published articles. - 94 marked
drift=Y, 16drift=N. By locale: FR 56 drift / 8 stable · EN 38 drift / 8 stable. - Drift breakdown by pattern:
avgReturn 13.2%31 ·winRate 77%29 ·OOS Sharpe 1.2119 ·T+90 horizon10 ·filteredSubsetSize n=1964 ·sharpeDeflated 0.421. - IMPORTANT caveat: the
drift=Ycount includes legitimate per-cohort numbers (e.g. women-insidersn=128, closely-associated-PDMRn=11185.4%, sector-ETF40.37%). The CSV preserves the 60-char context window on each row so Simon can triagelegitimate alternate cohortvsstale Sigma headline misquoted. - The dominant systematic stale block is still the V12 triplet
-1.70 / 41.82 / -0.08(see doc 91), unchanged by this pass: 34 category-A EN articles. Action plan remains the Hybrid B/A in doc 91. - No
bodyMdxrow was modified in this pass (per instructions).
Files patched in pass 2 (4)
src/lib/mcp/tools.ts—get_winning_strategy_signalsdescription templated fromSTRATEGY_PROOF.oosResults+ headline fields +WINNING_STRATEGY.minScore. AddedSTRATEGY_PROOF/WINNING_STRATEGYimports and a header note banning hardcoded literals.src/lib/mcp/execute.ts— runtimestrategy.descriptionreturned byget_winning_strategy_signalstemplated the same way.src/app/api/v1/strategy/winning/route.ts(line 46) — public REST description templated fromWINNING_STRATEGY.{minScore,holdingDays}.src/app/api/v1/strategy/winning/route.ts(header comment) — rewrote to referenceSTRATEGY_PROOF/WINNING_STRATEGYfield names instead of brittle literals (0.40,1.87,+10.1,+13.2).
Justifications for MED/LOW left untouched
LandingSigma.tsxfallback string?? 1.21/?? 0.42is dead code (OOS struct is always populated at module init); changing it would touch a third file for zero behavioural win.WalkForwardAnimator.tsxstub: feature-flagged off (SHOW_WALK_FORWARD_ANIMATOR=false) and already TODO'd in Pass-1 row.FormulaTypewriter.tsx,LiveScoringPlayground.tsx,BacktestDashboard.tsxtooltips,LandingHowItWorks.tsxLVMH/Schneider/Hermès deltas,LandingTrackRecord.tsxsparkline seed: illustrative content explicitly labelled as such in the same component (or in surrounding section copy).SignalWeightPlayground.tsx:75andwinning-strategy.ts:21-31: docstring/JSDoc comments. CLAUDE.md em-dash + emoji policies explicitly allow literals in code comments.PITLeakCheckGate.tsx:60(583,200) andCh08Strategie.tsxmirrorGRID_SEARCH_TRIALS: same const, identical literal — exporting and templating was already triaged as cosmetic-only in Pass-1.
Verification
npx tsc --noEmit— 0 errors after Pass-2 edits.npm run lint:emdash— OK.npm run lint:emoji— OK (463 files scanned).- No commits / pushes made.
Honest confidence score
Combined Pass-1 + Pass-2 estimate for the rendered surface (TSX, MCP, REST, emails, sitemaps, JSON-LD) being legacy-stat-free: ~92 %. Remaining 8 %:
- WalkForwardAnimator stub (behind flag) and the 4 illustrative-data callouts: ~2 %.
- Comment/docstring hardcoded literals that would become misleading after a
STRATEGY_PROOF refresh (
winning-strategy.tsheader,SignalWeightPlayground.tsx:75,route.tswas the worst, now fixed): ~2 %. - Decoupled threshold drift (
Recommended Sigma ≥ 50vs live engine≥ 40) still unresolved (Pass-1 flag 3): ~2 %. - Blog body MDX: ~35 % of EN published articles still recite V12 triplet — not counted in the rendered-surface estimate above but is the dominant remaining liability for any reader who clicks past the headline pages: ~2 %.
For the blog body MDX surface specifically, confidence is ~64 % (104 articles scanned, 37 carry the V12 triplet, but the headline pages do not consume those bodies). Plan: ship doc 91 remediation.