PlusEVData/investing

Lessons learned Β· how we audit Β· what's bullshit and what's real

Every backtest on this site goes through the same audit. This page documents what we found, what we threw away, and the rules we apply before claiming a strategy works.

πŸ”¬ What's actually predictive in our universe (Russell 3000, 2018-2026)

944 feature Γ— horizon combinations tested with 5+ years of point-in-time data. After deflating the inflation from overlapping returns, only 23 real signals survive. They cluster into 6 themes:

ThemeEffectTop featuresMean |IC|
Size premium Small caps outperform large mcap_pit, ev_pit, shares_diluted_*, raw net_income 19.6%
Value premium Cheap stocks outperform expensive ps_ttm_pit, pe_ttm_pit, sales_yield_pit 14.2%
Vol-as-risk-premium High annualised vol β†’ high return at 252d vol_252d, vol_90d 19.6%
Quality penalty High-margin stocks UNDERPERFORM net_margin_pit, gross_margin_pit, cfo_q_pct_quarters_positive_8q 10.6%
Cash penalty High cash hoards β†’ lower returns cash 5.5%
Momentum-uptrend Far above 52w low β†’ higher returns dist_52w_low, wq_alpha042 12.5%

Most-counterintuitive finding: high-margin / consistently-profitable companies systematically underperform. This is the classic value-vs-quality tension β€” quality is already priced rich. Don't naΓ―vely buy "best business" stocks expecting outperformance.

Industry-standard caveat: raw IR values look gigantic (e.g. 87 for size effect at 252d) because we sample daily with 252-day forward returns β€” consecutive observations share 251/252 of their forward window. Effective IR (deflated by √horizon) keeps the rankings honest. After deflation, the largest signal has IR = 5.5, well above Grinold's "great" threshold of 0.75.

What's NOT predictive (out of 800 noise features): most RSI variants, most chart patterns, most WorldQuant alphas, MACD, stochastic oscillators, raw EPS, raw revenue, most growth rates (YoY/QoQ), Piotroski sub-scores, Bollinger band width. Browse the full bullshit list β†’

⚠ Case study: the look-ahead bug that turned a Sharpe-3.37 winner into a -0.30 loser

During autonomous strategy research on May 3 2026, an in-house cross-asset trend-follower posted an apparent Sharpe of 3.37 over 21 years. After a mandatory honest audit, the entire result evaporated:

VersionSharpeSortinoCAGRMaxDD
Originally claimed (BUGGED)3.375.59+28.5%-4.3%
Fix 1: signal lagged 1 day0.530.69+3.8%-22.4%
Fix 2: + 10 bps round-trip cost0.170.22+0.3%–
Fix 3: + 5% annual borrow on lev>10.080.10+0.3%-29.9%
Fix 4: pessimistic 20bps + 6% borrow-0.30-0.39-2.5%-44.5%

The bug: the signal was computed at price[t] and then the strategy earned return[t] the same day β€” the strategy was already "in" the trade before the signal could possibly have been observed. After lagging the signal by 1 day so the strategy earns return[t+1] only, the edge disappears.

# BUGGY:
in_trend[i] = spy_p[i] > spy_sma200[i]   # uses TODAY's close
position[i] = (spy_r[i], tlt_r[i])        # earns TODAY's return β€” leakage

# CORRECT:
in_trend[i] = spy_p[i-1] > spy_sma200[i]  # uses YESTERDAY's close

Full forensics: reports/HONEST_AUDIT.md. The production website backtests at /strategies are not affected β€” they use a different engine that strictly enforces forward returns (audit: reports/PRODUCTION_BACKTEST_AUDIT.md).

βœ… The 10-point honest-audit checklist

Every claim of a working strategy must clear all 10:

  1. Date-permutation test β€” shuffle the order of forward returns and recompute Sharpe. If it stays positive, you have leakage.
  2. Manual signal-vs-return alignment β€” print last 5 days of signal alongside prices. Confirm signal[t] uses prices[≀ t-1] and return[t] is forward.
  3. Cost model matches rebalance frequency β€” daily-rebal needs daily turnover cost, not per-flip.
  4. Borrow cost on leverage > 1 β€” explicit annual rate Γ— fraction of time levered.
  5. Forward-return is explicit β€” shift(-1) join, not implicit time alignment.
  6. Sanity-check Sharpe > 2 β€” assume bug until proven otherwise.
  7. Random-baseline test β€” re-shuffle the SIGNAL keeping returns. Random-version Sharpe should be far below actual.
  8. Bootstrap CI lower-bound > 0.5 β€” block-bootstrap to preserve autocorrelation; lower bound > 0.5 = real edge.
  9. Out-of-sample lockbox β€” last 12-18 months never tuned on; one-shot evaluation.
  10. Survivorship + look-ahead in fundamentals β€” point-in-time membership; report-availability lag.

πŸ“Š How we decide what's predictive

The IC page labels each feature as REAL, WEAK, NOISE, or SUSPECT. The thresholds are industry-standard:

VerdictThresholdSource
βœ… REAL |IC IR| β‰₯ 0.5 ∧ |t-stat| β‰₯ 3 ∧ |mean IC| β‰₯ 2% ∧ hit rate β‰₯ 55% ∧ N dates β‰₯ 200 Grinold's fundamental law of active management (IR > 0.5 = good, > 0.75 = great)
🟑 WEAK |IC IR| β‰₯ 0.2 ∧ |t-stat| β‰₯ 1.96 ∧ hit β‰₯ 52% ∧ N dates β‰₯ 100 Statistically significant at 95% but borderline
⚠ SUSPECT |t-stat| > 5 with |IR| < 0.1 Degenerate cross-section β€” signal is constant or near-constant
βšͺ NOISE Doesn't clear weak threshold Not predictive

πŸ“š The 70-mistake catalog (stuff that wrecks backtests)

Compiled from LΓ³pez de Prado, Bailey, Harvey-Liu-Zhu, Hou-Xue-Zhang, Jensen-Kelly-Pedersen, White, Hansen, Lo, Linnainmaa-Roberts, Bessembinder. Citations verified live.

A. Look-ahead / leakage (9)
  • Same-day signal/return overlap (this caused our Sharpe 3.37 β†’ 0.53)
  • Restated fundamentals (using post-restatement values pre-restatement)
  • Survivorship in benchmarks (current S&P 500 β‰  historical)
  • Universe selection driven by current relevance
  • Fundamental data lagged less than report-available date
  • Cross-section ranking using future N
  • Vol scaling with full-sample variance
  • Calendar effects from futures-roll dates
  • Z-score normalisation across full panel
C. Selection / multiple testing (8)
  • Probability of backtest overfitting (Bailey-LdP)
  • Reporting only winning strategies (publication bias)
  • Sub-period selection (cherry-picking 2009-2019)
  • Hyperparameter optimisation without test set
  • Feature selection in-sample
  • Walking-forward without holding out final fold
  • Strategy library survivorship
  • Reality Check / SPA tests not applied
E. Cost realism (8)
  • Bid-ask spread ignored
  • Volume-weighted impact (Almgren-Chriss)
  • Borrow cost on shorts not modelled
  • Tax drag (long-term vs short-term)
  • Slippage at open/close auctions
  • Commission tiers vs flat-rate
  • Cash drag from rebalance windows
  • Cost asymmetry by side (taker vs maker)
D. Overfitting (6)
  • Hyperparameter trial count vs deflated Sharpe
  • Walk-forward parameter drift
  • Curve-fit thresholds (e.g. RSI<30 specifically)
  • Overlapping signals counted as independent
  • Implicit factor exposure not controlled
  • Train/test contamination through normalisation
I. Statistical (6)
  • Naive standard errors (assume IID returns)
  • Sharpe ratio bias from non-normal returns (Lo)
  • Multiple-testing corrections not applied (Bonferroni / BH)
  • Ignoring autocorrelation (Politis-Romano blocks)
  • Treating skew/kurt as second-order
  • Stationarity assumed without testing
K. Validation (10)
  • No probabilistic Sharpe ratio
  • No deflated Sharpe ratio
  • No PBO (probability of backtest overfit)
  • No CPCV (combinatorial purged cross-validation)
  • No paper-trading window before live
  • No out-of-sample lockbox
  • No random-baseline percentile
  • No factor decomposition (Fama-French 5)
  • No drawdown-relative-to-CAGR test (Calmar)
  • No skew/kurt reporting

Full catalog with detection tests + prevention patterns + verified citations: ~/.claude/projects/-home-miche/memory/feedback_backtest_mistakes_catalog.md (70 items, 24 sources, 11 categories).

πŸ§ͺ Even the "real" signals can be sector bets

A signal can have high IC because it accidentally aligns with sector membership. Financials skew small-cap, mega-tech skews large-cap β€” so the apparent "size effect" might just be "tech outperformed financials over 2018-2026." Test: subtract the per-(date, sector) mean from feature and forward return, then recompute IC. The retained ratio tells you what fraction is true cross-sectional alpha.

We ran this on our top REAL signals (May 3 2026 sweep). Only 4 of 6 retained >75% of their IC after sector-neutralising:

FeatureRaw ICSN ICRetainedTrue alpha?
mcap_pit (size)-19.6%-7.6%39%Mostly sector bet
sales_yield_pit (low P/S)+14.3%+12.8%89%Yes
earnings_yield_pit (low P/E)βˆ’0.3%+0.1%35%Was already noise
net_margin_pit (quality penalty)βˆ’10.2%βˆ’7.7%76%Yes
vol_252d+19.7%+17.4%88%Yes
dist_52w_low (uptrend)+11.8%+11.9%101%Pure cross-section
wq_alpha042+11.2%+9.5%85%Yes

Lesson: the size effect (smaller-cap outperforms in our universe) is mostly because financial-sector stocks happen to be smaller and underperformed mega-cap tech in this regime. Don't bet on size as a stock-picking signal β€” it's a sector bet in disguise. Do bet on dist_52w_low, sales_yield_pit, and vol_252d, which retained their predictive power even after sector-neutralisation.

πŸ”¬ What our standard validation suite catches β€” and what it missed

A surprising finding from the May 3 audit: the standard validation suite (random-baseline, OOS lockbox, walk-forward, FF5 regression, bootstrap CI) all PASSED on the bugged strategy. Every metric looked clean because the leakage was identical across train, test, and random-permutation samples β€” they all measured the same biased stream.

What caught the bug: manual code review + date-permutation test on the input signal+price stream (not just the output return stream β€” that one is invariant under permutation since it preserves mean and std).

Action taken: built skills/honest_audit.py + saved a feedback memory mandating the audit checklist before any backtest result is reported.