This file gives an AI coding assistant (Claude, Copilot, etc.) the
context to make safe, idiomatic changes to alpha-lens. Read it
before suggesting edits.
src/alpha_lens/
__init__.py # Public API.
core/
types.py # Pydantic models for every result type.
config.py # Frozen dataclass configs.
validator.py # Input validation and cleaning.
analysis/ # Pure functions, no I/O, fully tested.
statistics.py # Sharpe, drawdown, IC, etc.
regime.py # Rule-based and HMM regime detection.
drawdown.py # Drawdown event detection + regime tagging.
attribution.py # OLS factor attribution.
decay.py # IC decay and half-life estimation.
correlation.py # Factor correlation and VIF.
overfitting.py # DSR, PBO, MinBTL.
validation.py # IS/OOS split, walk-forward.
robustness.py # Bootstrap CI, subsample stability.
cost_analysis.py # Cost sensitivity sweep and break-even.
scoring.py # Production Readiness Score aggregator.
report/
generator.py # The `autopsy()` orchestrator.
html_report.py # Standalone HTML renderer.
sections.py # Per-tab content builders.
template.py # HTML/CSS/JS template (one string).
viz/
charts.py # 11 Plotly chart builders.
styles.py # Color tokens, fonts, base_layout dict.
tests/ # 85 tests, all under 6 seconds.
examples/ # 3 runnable examples.
docs/ # concepts.md, api.md, img/
-
Decimal returns only.
0.01means 1%, not 1. The validator warns on percent-encoded input; do not "fix" the warning by accepting both. -
Always √252 annualization. Sharpe ratios use
np.sqrt(252), notnp.sqrt(365). If a function needs a different period count, it accepts aperiods_per_yearargument with the default 252. -
Forward-shifted returns for IC. The factor at time
tshould correlate with returns att + h, NEVER with returns att.rank_information_coefficientenforces this with aforward_periodsargument; the tests include a regression check. -
No interpolation. NaNs in returns are forward-filled briefly and then dropped. We never invent return values.
-
All analysis functions are pure. No I/O, no randomness without a seed, no logging side effects, no global state. The boundary between pure and impure code is the
report/anddata/modules;analysis/is always pure. -
Pydantic v2 models. All result types are pydantic v2 models with
model_config = ConfigDict(arbitrary_types_allowed=True). Adding a new result type? Inherit fromAlphaLensModelincore/types.pyso the config is uniform. -
No re-introduction of look-ahead. Any new diagnostic that uses forward returns must explicitly shift them and have a test that fails if look-ahead is present (use the "factor = today's return" trick from
test_statistics.py). -
Configurable, opinionated. Default weights and thresholds are defensible choices documented in
docs/concepts.md. Override viaScoringConfig. Do not soften the defaults to make scores easier to pass; that defeats the purpose of the library.
- Add a result type in
core/types.py. - Add the analysis function in
analysis/<module>.py— pure, typed, no I/O. Add toanalysis/__init__.pyexports. - Add tests with synthetic data of known ground truth. The
pattern: construct data with a property you can verify (known IC,
known half-life, known regime structure), then check the estimator
recovers it within a tolerance. Look at
conftest.pyfor generators. - If it should affect the score, add a scorer function in
analysis/scoring.pyand a weight incore/config.ScoringConfig. Weights must sum to 1.0; the dataclass validates this in__post_init__. - If it should appear in the report, add a chart in
viz/charts.pyand surface it in the appropriate_*_sectionfunction inreport/sections.py.
cd alpha-lens
pip install -e ".[dev]"
pytest # 85 tests, ~6 seconds
pytest --cov=alpha_lens # coverage reportTests live in tests/ and are organized one file per module. The
conftest.py fixtures are the canonical way to generate synthetic
data. If you add a fixture, document its ground truth in the docstring.
- Do not add new dependencies without compelling reason. Current core
deps: numpy, pandas, scipy, scikit-learn, plotly, statsmodels,
pydantic. Anything beyond these goes behind an optional extra
(
[hmm],[data],[llm]). - Do not call
np.randomdirectly. Always use aGeneratorfromnp.random.default_rng(seed)so tests are reproducible. - Do not reach for matplotlib. The viz layer is Plotly; mixing introduces font and theme inconsistency.
- Do not "improve" the scoring weights to make a particular strategy pass. The weights are the product, not a parameter to be tuned.
- Do not introduce a HTTP/network call in
analysis/. Network calls go indata/(and are opt-in via an optional extra). - Do not store secrets, API keys, or large data files in the repo.