- Python 100%
| Filename | Latest commit message | Latest commit date |
|---|---|---|
| diagrams | ||
| evolving_alpha | ||
| tests | ||
| LICENSE | ||
| main.py | ||
| README.md | ||
| requirements.txt | ||
Evolving Alpha
Evolving Alpha is a compact quantitative-research framework that evolves interpretable trading signals from OHLCV market data. Candidate signals are represented as expression trees and optimized through tournament selection, subtree crossover, mutation, elitism, and complexity-aware Sharpe fitness.
The project is designed to make the research mechanics explicit: signal construction, cross-sectional portfolio formation, execution lag, turnover costs, chronological holdout evaluation, and the evolutionary search itself are implemented in readable Python rather than hidden behind a strategy-optimization library.
Research pipeline
Highlights
- Symbolic genetic programming — alpha candidates are inspectable expression trees, not opaque model weights.
- Vectorized cross-sectional evaluation — each expression produces signals across all dates and assets simultaneously.
- Look-ahead protection — portfolio weights are lagged one trading day before returns are realized.
- Parsimony pressure — larger trees receive a complexity penalty to discourage expression bloat.
- Transaction-cost modeling — optional basis-point costs are charged against daily portfolio turnover.
- Chronological holdout — the example workflow evolves on the training period and evaluates the selected expression on a later, untouched test period.
- Reproducible search — a random seed can be supplied to the evolutionary engine.
- Research diagnostics — annualized return, volatility, Sharpe ratio, maximum drawdown, positive-day rate, and turnover are reported.
Genetic representation
Each candidate is a tree. Leaves are market features or constants; internal nodes are functions from a small primitive registry.
The default primitive set includes arithmetic, absolute value, a five-day delay, a ten-day rolling mean, and a ten-day rolling standard deviation. The registry is intentionally small so evolved expressions remain interpretable and easy to extend.
Evolutionary search
For each generation:
- Evaluate every expression on the training data.
- Convert raw scores to cross-sectionally de-meaned, unit-gross portfolio weights.
- Lag weights by one day and deduct optional turnover-based transaction costs.
- Compute annualized Sharpe and subtract a node-count complexity penalty.
- Select parents by tournament selection.
- Create offspring through subtree crossover and point/subtree mutation.
- Preserve the best-so-far expression through elitism.
The search objective is intentionally in-sample. The selected expression is then evaluated separately on the chronological holdout; test performance never participates in evolution.
Project structure
evolving-alpha/
├── main.py # End-to-end research example
├── requirements.txt
├── README.md
├── LICENSE
├── tests/
│ └── test_core.py # Portfolio/evaluation sanity tests
└── evolving_alpha/
├── __init__.py
├── data.py # OHLCV acquisition and preparation
├── node.py # GP expression-tree representation
├── functions.py # Primitive function registry
├── genetic_ops.py # Tree generation, crossover, mutation
├── fitness.py # Sharpe + parsimony search objective
├── evaluation.py # Portfolio construction and diagnostics
└── engine.py # Evolutionary search loop
Installation
python -m venv .venv
source .venv/bin/activate # Windows: .venv\Scripts\activate
pip install -r requirements.txt
Requires Python 3.9+.
Quick start
python main.py
The example downloads daily data for a small technology-stock universe, uses the first 70% of observations for evolution, and reserves the final 30% for chronological out-of-sample evaluation. A fixed random seed makes the evolutionary run reproducible.
Example output structure:
Evolved expression: (Close - mean10(Open))
Training period
---------------
annualized_return: ...
annualized_volatility: ...
sharpe: ...
max_drawdown: ...
positive_day_rate: ...
average_daily_turnover: ...
Out-of-sample test period
-------------------------
...
Results vary with the data, universe, search parameters, and seed; the example above is illustrative rather than a performance claim.
Library usage
from evolving_alpha import (
backtest_expression,
chronological_split,
load_market_data,
performance_metrics,
run_quant_gp,
)
features, returns = load_market_data(
tickers=["AAPL", "MSFT", "GOOGL", "AMZN", "META", "NVDA"],
start="2022-01-01",
end="2025-12-31",
)
train_x, train_r, test_x, test_r = chronological_split(features, returns, 0.70)
alpha = run_quant_gp(
train_x,
train_r,
generations=20,
max_population=200,
max_depth=4,
mutation_rate=0.25,
transaction_cost_bps=2.0,
seed=42,
)
test_returns, _, turnover = backtest_expression(
alpha, test_x, test_r, transaction_cost_bps=2.0
)
print(alpha.to_infix())
print(performance_metrics(test_returns, turnover))
Primitive operators
| Operator | Arity | Meaning |
|---|---|---|
+, -, *, / |
2 | Element-wise arithmetic; division is zero-safe |
abs |
1 | Absolute value |
delay5 |
1 | Five-period time delay |
mean10 |
1 | Ten-period rolling mean |
std10 |
1 | Ten-period rolling standard deviation |
Additional primitives can be registered in evolving_alpha/functions.py by supplying a callable and its arity.
Methodological scope
This repository is a research implementation, not a production trading system. The chronological holdout is a stronger test than reporting the optimization window alone, but it does not eliminate multiple-testing or data-mining risk. The demonstration universe is small and fixed, and the framework does not model market impact, borrow constraints, execution latency, changing index membership, or survivorship effects.
Accordingly, evolved expressions should be treated as research hypotheses, not as evidence of persistent alpha or as ready-to-trade strategies. A more rigorous research program would add walk-forward evaluation, broader universes, benchmark comparisons, repeated trials across seeds, and robustness tests across market regimes and cost assumptions.
Tests
The included tests use synthetic data and require no market-data download:
python -m pytest -q
License
This project is licensed under the MIT License. See LICENSE for details.
