Skip to content

Repository files navigation

quant-prep

A self-directed study project building the quantitative and statistical foundations for quant research, worked through from first principles. Work in progress.

Scope

This covers the analysis side — estimator correctness and the conditioning of a least-squares problem, plus a first look-ahead audit. All data here is synthetic (random walks and generated matrices); it does not use real market data or machine learning. Where a piece is a planned next step rather than finished work, it says so.

This work was done with an LLM (Claude) as a tutor and reviewer; the full session logs are in prompts/ (see below).

Contents

Least squares from scratch

  • ols_two_factor.py — two-factor OLS via the normal equations, solved by hand: de-mean the response and the factors (which decouples the intercept), solve the resulting 2×2 system by Cramer's rule, then recover the intercept. The solution path is deliberately independent of any library solver, so it can be checked against one.
  • test_ols_two_factor.py — checks the coefficients, residuals summing to zero, residuals orthogonal to each factor, and agreement with NumPy's lstsq. (A test that only checked "residuals sum to zero" would miss an intercept-absorbs-the-mean bug, since those residuals are y - mean(y) and always sum to zero; the orthogonality check is what catches it.)
  • ols_general.py — the general p-factor version, solving the normal equations solve(X.T @ X, X.T @ r) on the centered design.
  • test_ols_general.py — checks it against NumPy's lstsq, the first-order conditions, and exact recovery of planted coefficients on noise-free data.

Near-collinearity

  • collineardata.py — a generator that produces near-collinear design matrices with a provable spectral contract, for use as known-answer test data. Given a set of collinear groups, each with a collinearity parameter η, the standardized correlation matrix has exactly one small eigenvalue per group, and each equals 1 − √(1 − η²) (matched to rtol=1e-6). The factor background is built by QR-orthogonalizing a random matrix, so the contract holds exactly: a plain random background carries roughly 1/√T of spurious correlation between columns, which on a borderline test produces a high false-pass rate, whereas the QR-orthogonalized background starts every eigenvalue at exactly 1. Ships with fail-loud input validation and a 1,000-spec random stress test.

A look-ahead audit

  • buggy_backtest.py — an intentional known-answer test, not a working strategy. The prices are a pure random walk, so the true edge is exactly zero and any profit the backtest reports must come from a look-ahead bug. The bugs are planted on purpose; the file is a substrate for practicing timeline audits — finding where future information leaks into past decisions.

Notes and logs

  • experiment_log.md — a running log of every attempt (in Traditional Chinese), kept so the number of "asks of the data" (N) can be counted honestly — the multiple-comparison discipline behind "a good backtest is not a real edge". It also holds the derivations (why the least-squares objective is convex, population vs sample covariance) and the overfitting / √(2 ln N) experiments.
  • prompts/ — detailed reconstructions of the sessions behind this code (also Traditional Chinese): the reasoning, the design decisions, and an honest account of what worked and what didn't.

Upcoming

The next concrete exercise, and the broader direction beyond it.

  • A local backtester with known-answer tests (next up) — a minimal engine: signal → positions → P&L, with an explicit decision clock (for every variable, the timestamp at which it becomes available in the real world, taken recursively over its upstream inputs). Correctness is established by three tests whose right answers are known in advance, not by any performance number:

    • zero-edge — prices are a pure random walk, so true predictability is identically zero; realised performance must land inside a pre-computed acceptance band.
    • planted-signal — returns are made to depend on a factor with a coefficient I set; the engine must recover that known performance. (Zero-edge alone is passed trivially by an engine that always returns zero positions; this is the test that catches it.)
    • look-ahead probe — the signal is deliberately given one extra day of knowledge. If the honest version performs about as well as the peeking version, the honest version is already peeking.

    Scope is deliberately narrow: synthetic panels only. Ingesting real market data — delistings, actual filing dates, split and dividend adjustment, identifier mapping — is not part of this exercise and has not been done.

  • ridge_derivation_exercise.md — a problem sheet for a symbolic derivation of ridge regression: the closed form, why the penalty restores invertibility, the SVD shrinkage view, and the bias-variance trade-off. In Traditional Chinese; not yet worked through. Deferred behind the backtester: the penalty addresses coefficient instability for a given set of factors, whereas the risk that actually bites in my own work is picking the best of many searched candidates — which the penalty does not touch.

Planned directions after that:

  • Real-data pipelines — moving from synthetic arrays to time-series and panel data (many assets observed over many dates), and the data handling that requires.
  • Macroeconomic drivers of asset returns.
  • Model regularization with out-of-sample validation — building on the ridge exercise toward the discipline that keeps a model generalizing rather than overfitting on the training window.

Running

python -m pytest              # all OLS tests (two-factor and general-p)
python collineardata.py       # generator contract and stress-test self-checks

Pure NumPy; no network, no data downloads, no API keys.

About

Quant research fundamentals rebuilt from scratch, validated against synthetic data with known answers. Development record with Claude in prompts/.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages