← Back to selected work

Quantitative research / point-in-time validation

Point-in-Time S&P 500 Return Modeling

I built a point-in-time stock panel to test whether a small set of price and risk features could rank stocks out of sample. The page shows both the promising part of the result and the checks that made the conclusion weaker.

Research question

Can a stock-ranking model beat a simple baseline after costs when membership and dates are frozen?

I started with historical prices, built risk and return features, and kept tightening the evaluation design. The final version uses historical S&P 500 membership, a 12-month feature window, non-overlapping quarterly targets, an eight-quarter holdout, five-stock portfolios, and a 0.5% transaction cost per turnover.

End-to-end process

From raw prices to a defensible conclusion

  1. A

    1. Static price data

    Use a static, reproducible price source so the experiment is not dependent on a live API or rate limits.

  2. B

    2. Data cleaning and outlier detection

    Align dates, repair missing values, flag implausible price jumps, and preserve an audit trail.

  3. C

    3. Descriptive analysis and visualization

    Inspect normalized price paths, annual returns, correlations, and distributional behavior.

  4. D

    4. Risk/return features and peer groups

    Construct return, volatility, drawdown, persistence, and PCA-based company profiles.

  5. E

    5. Rolling clustering and market regime

    Re-estimate peer groups through time and add point-in-time volatility/regime information.

  6. F

    6. Prediction models and strategy backtest

    Compare frozen model families and evaluate top-N portfolios after turnover costs.

  7. G

    7. Point-in-time and historical membership

    Use historical index membership and only information available at each evaluation date.

  8. H

    8. Placebo, bootstrap, and Reality Check

    Test whether the observed result survives random portfolios, dependence-aware resampling, and model selection.

  9. I

    9. Locked out-of-sample holdout

    Evaluate the frozen specification on eight quarterly periods that were not used for development.

  10. J

    10. Advanced Gradient Boosting

    Use the selected nonlinear model only after the validation design is fixed.

  11. K

    11. Fair comparison of new factors

    Add momentum and risk blocks without changing the baseline universe or model parameters.

  12. L

    12. Final report and conclusion

    Separate exploratory evidence from confirmatory evidence and document limitations.

Visual evidence

What the data and selected portfolios look like

Normalized S&P 500 company price trends
Normalized company price paths reveal strong heterogeneity and structural breaks that motivate robust cleaning and time-aware evaluation.
S&P 500 company peer segments
PCA-based peer segments summarize differences in historical risk, return, drawdown, and persistence features.
Rolling 60-month peer group assignments
Rolling clustering shows that peer-group membership is not static across market periods.
Advanced model comparison development and holdout
Model selection is separated from the locked holdout; the holdout is evaluated only after the specification is frozen.
Advanced statistical validation results
Placebo, block bootstrap, Reality Check, and cost/top-N sensitivity provide a more conservative interpretation of the observed result.
Locked out-of-sample confirmatory holdout
The final confirmatory holdout keeps the selected specification fixed and reports performance on eight previously locked quarterly periods.
Factor expansion development and holdout
The factor expansion is an apples-to-apples test: additional price factors did not improve the frozen baseline on the holdout.

Model comparison

Locked holdout performance

All rows below use the same historical-membership holdout, eight quarterly evaluation periods, Top-5 selection, and 0.5% transaction cost.

ModelOOS R²DirectionRank corr.Mean q. net excessTerminal wealth
Gradient Boosting0.04260.1%0.03158.25%1.765
Elastic Net-0.02350.4%-0.01562.40%1.161
Huber-0.05548.4%-0.02050.10%0.980
Ridge-0.04948.7%-0.0211-3.31%0.754

Robustness and inference

The result changes after stricter checks

Exploratory alpha p = 0.0395. Before dependence and model-selection corrections, the Gradient Boosting alpha looks significant. After moving-block bootstrap and four-model Reality Check, the p-values are 0.1237 and 0.1361. With only eight quarterly observations, the evidence is promising but not confirmatory proof of persistent alpha.
Test / quantityValueInterpretation
Raw alpha p-value0.0395Nominal significance before corrections
Random Top-5 placebo p-value0.0002Observed portfolio beats random selection
Moving-block bootstrap p-value0.1237Not significant after dependence-aware resampling
Reality Check p-value0.1361Not significant after four-model selection
Bootstrap 95% interval[-1.26%, 21.83%]Wide interval due to only eight quarters

Implementation sensitivity

Transaction costs and portfolio breadth

Cost per turnoverTop-5 mean q. net excessTop-10 mean q. net excess
0%8.98%4.54%
0.5%8.25%3.94%
1%7.51%3.34%
2%6.03%2.14%

The signal remains positive in this sample at 2% cost, but portfolio performance is not the same as statistical proof. Costs, turnover, liquidity, and the small number of holdout periods remain central limitations.

Factor expansion

More features did not automatically improve the model

Factor blockMean q. net excessTerminal wealthAlpha p
Baseline8.25%1.7650.0395
Baseline + momentum-1.13%0.8860.2220
Baseline + momentum/risk-5.39%0.6120.0009
Full factor block-2.83%0.7670.0083

The baseline is intentionally retained because it performs better on the locked holdout. This is a useful negative result: complexity was treated as a hypothesis to test, not as an automatic upgrade.

Final takeaway

The useful conclusion is also a limitation

The model looks interesting in the first comparison, but the locked holdout and multiple-testing checks make a strong alpha claim hard to defend. That is the conclusion I would carry forward: the pipeline is reusable, while this particular signal still needs more data before it deserves to be called persistent.

Read the code and reports on GitHub