Genetic-programming framework for evolving symbolic alpha signals, with expression trees, crossover, mutation, parsimony pressure, transaction costs, and out-of-sample evaluation.
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
2026-09-13 21:49:55 -04:00
diagrams initial commit 2026-09-13 21:49:55 -04:00
evolving_alpha initial commit 2026-09-13 21:49:55 -04:00
tests initial commit 2026-09-13 21:49:55 -04:00
LICENSE Initial commit 2026-09-13 21:49:01 -04:00
main.py initial commit 2026-09-13 21:49:55 -04:00
README.md initial commit 2026-09-13 21:49:55 -04:00
requirements.txt initial commit 2026-09-13 21:49:55 -04:00

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.

example

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.

For each generation:

  1. Evaluate every expression on the training data.
  2. Convert raw scores to cross-sectionally de-meaned, unit-gross portfolio weights.
  3. Lag weights by one day and deduct optional turnover-based transaction costs.
  4. Compute annualized Sharpe and subtract a node-count complexity penalty.
  5. Select parents by tournament selection.
  6. Create offspring through subtree crossover and point/subtree mutation.
  7. 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.