AlphaWinnow — Formulaic Alpha Mining in Rust
AlphaWinnow is an open-source Rust CLI and library for formulaic alpha mining, quantitative factor research, symbolic expression search, and local factor evaluation. It uses a typed expression grammar, deterministic genetic programming, semantic deduplication, structural novelty search, Rayon parallelism, and optional causal evaluation on your own market data.
Give AlphaWinnow a field/operator catalog and an optional archive of measured expressions. It explores a large space of valid quantitative formulas, removes equivalent or trivial signals, and writes a small, diverse, reproducible shortlist for downstream research.
AlphaWinnow is not a trading bot, broker integration, or hosted alpha database. It runs locally, performs no network requests, and never claims that a generated formula is profitable.
What's new in 0.2.0
This checkout prepares 0.2.0; publication is a separate release step. The release adds a bounded ask/tell library API for caller-measured search, opt-in feedback-guided search policies, and an expanded causal numeric operator backend. It also changes semantic identity for nested cross-sectional ranks and upgrades search artifact schemas. See the changelog and migration notes before resuming old work.
These are toolkit capabilities, not a claim of better trading performance or superiority over AlphaGen. Research adapters, private datasets, and experiment outputs are not part of the published crate.
Why AlphaWinnow?
Most alpha factor generators can produce thousands of formulas. The difficult part is keeping the search typed, bounded, reproducible, and diverse while not wasting evaluation time on duplicate expressions. AlphaWinnow focuses on that selection problem.
- Strongly typed formula generation: invalid fields, operators, argument types, windows, and keyword parameters are rejected before search.
- Genetic programming for alpha factors: seeded generation, mutation, and crossover explore a configurable quantitative-expression DSL.
- Semantic alpha deduplication: exact duplicates, provably trivial signals, and positive final-scale equivalents collapse into one family.
- Diversity-aware factor mining: structural descriptors discourage a shortlist filled with cosmetic variations of the same idea.
- Fast Rust execution: a bounded Rayon worker pool evaluates search work in parallel with explicit CPU and memory limits.
- Local factor evaluation: optional train, validation, and holdout evidence includes Pearson signal/return correlation, gross and net returns, turnover, costs, Sharpe, drawdown, and auditable daily results.
- Reproducible quant research: deterministic seeds, checksums, immutable JSONL artifacts, atomic writes, manifests, and checkpoint/resume.
- Bring your own data: use a custom field/operator catalog, immutable columnar JSON, or the optional local Sharadar Parquet adapter.
Common use cases
AlphaWinnow is designed for quantitative researchers, systematic traders, financial machine-learning engineers, and developers building alpha research pipelines. Typical workflows include:
- generating formulaic alpha-factor candidates from OHLCV or fundamental data;
- building a strongly typed genetic-programming search for trading signals;
- deduplicating large collections of WorldQuant-style expressions;
- evaluating factors locally with causal train/validation/holdout splits;
- ranking candidates by structural novelty before an expensive backtest;
- creating reproducible JSONL datasets for symbolic regression or ML research;
- benchmarking parallel quantitative-expression search in Rust.
Install
Every prebuilt channel below serves the same platform binaries that
release.yml built. A source installation
instead compiles the same version from its locked dependencies.
Homebrew (macOS and Linux, Apple silicon and x86-64):
Scoop (Windows):
scoop install https://raw.githubusercontent.com/eslazarev/alphawinnow/main/packaging/scoop/alphawinnow.json
Debian, Ubuntu, Fedora, RHEL — download the package for your architecture from the latest release and install it:
Prebuilt binary through Cargo, without compiling:
From source, which needs Rust 1.92 or newer:
Or download an archive for your platform straight from the
latest release.
Each one carries its own .sha256, the README, and the licence.
The .deb and .rpm are built from the very binary the tarball ships, not from
a separate compilation, and they are not stripped. Each release archive and
native package has its own SHA-256 checksum because the surrounding package
formats have different bytes.
The explicit tap URL is needed because this repository is not named
homebrew-alphawinnow; the formula lives in Formula/ and is
regenerated from the release archives by
scripts/update-packaging.sh, never by hand.
Quick start
Rust 1.92 or newer is required to build from source.
Build it from a checkout:
The search writes five durable artifacts:
candidates.jsonl: the diverse expression shortlist;candidates.jsonl.manifest.json: configuration, counters, checksums, limits, and termination evidence;candidates.jsonl.current.json: the publication pointer naming the last complete candidate/manifest pair;.candidates.jsonl.runs/: that pair, written immutably before the pointer is replaced;search.checkpoint.json: resumable deterministic search state.
Read candidates.jsonl.current.json rather than the two adjacent aliases when
you need a guarantee that the candidates and the manifest describe the same
run. Artifact schemas documents the publication protocol.
Generated candidates are research hypotheses, not trading recommendations.
structural_score measures expression structure and novelty; it is not Sharpe,
Fitness, expected return, or pass probability.
Use as a library
The same crate is the library. The binary and its argument parser live behind
the default cli feature, so turn defaults off to embed the engine without
pulling in a command-line stack:
[]
= { = "0.2", = false }
# With optional causal numeric evaluation and local Parquet preparation:
# alphawinnow = { version = "0.2", default-features = false, features = ["parquet-input"] }
use ;
Drive search with your own evaluator
alphawinnow::measured_search::MeasuredSearchSession provides a separate,
bounded ask/tell loop, usable without the cli or numeric-evidence features:
ask()emits typed, semantically unique proposals with lineage.- Evaluate all
score_requests()under your declared evaluator context. tell()accepts one complete finite-outcome snapshot; larger is better.- Save
checkpoint_json()and restore with the identical config and catalog.
The default Score policy requests fresh scores for retained parents as well
as the pending batch. LatestBatchScoreV1 instead ranks the latest cohort and
retires incumbents; its outcomes can represent sequential admission events,
not simultaneous marginal contributions. LatestBatchUniformV1 is a
score-blind same-cohort control, not independent grammar-random search.
Other policies and constraints are documented in the module's Rust API.
Try the complete synthetic example:
It rewards smaller expressions, supplies no market data, and verifies
checkpoint restoration after each batch. For another-language evaluator, the
stdio example exposes
the same public API over local JSONL; it is an example, not a hosted service.
The session bounds attempts, emitted proposals, batch/parent counts, and AST
complexity. It does not time-limit the caller's evaluator. Budget rescoring
and evaluation time explicitly; max_evaluations counts new proposals only.
How it works
typed catalog + optional measured archive
|
v
generate / mutate / crossover
|
v
parse + type-check + enforce limits
|
v
canonicalize + semantic deduplicate
|
v
structural novelty + diverse top-k
|
v
JSONL shortlist + manifest
|
v
optional local factor evaluation on your data
The default search is structural and data-independent. Market evidence is an explicit optional stage, stored separately from structural ranking so a local metric can never silently masquerade as a remote-platform result.
CLI commands
| Command | Purpose |
|---|---|
doctor |
Verify the parser, hashing, and local runtime |
inspect |
Parse and explain canonical and semantic identity |
dedup |
Remove exact and positive-root-scale duplicates |
search |
Run bounded parallel expression discovery |
benchmark |
Compare reproducible one/many-thread workloads |
prioritize |
Re-rank candidates using immutable measured feedback |
audit-feedback |
Audit feedback guidance with leave-one-out evaluation |
compile |
Lower and lint an expression against a target dialect |
evaluate |
Evaluate one expression on local data (optional feature) |
evaluate-batch |
Evaluate candidate JSONL in bounded batches (optional) |
prepare-sharadar |
Build a causal local panel from Sharadar Parquet (optional) |
Search a custom alpha-expression space
--archive is optional. When present, AlphaWinnow partitions it into accepted,
semantic-duplicate, and provably trivial records. The valid portion supplies
search context without being silently treated as proof of future performance.
Resume an interrupted run with the same catalog, archive, limits, and seed:
Deduplicate alpha formulas
Use measured feedback without mixing it into structural score
Run local factor evaluation
The evaluator loads and validates the dataset once, preserves causal warm-up, keeps future targets outside the expression inputs, and records failed expressions without discarding successful rows.
Custom catalogs and quantitative-expression DSLs
--catalog activates a schema-1 user-supplied catalog for the complete run.
Fields, groups, operator availability, generation weights, window domains, and
scalar domains are data rather than hard-coded search assumptions. The catalog
checksum becomes part of the manifest and checkpoint compatibility contract.
This makes AlphaWinnow useful as a generic quantitative factor-mining engine: you can model an OHLCV formula language, a fundamental-factor DSL, or a restricted expression dialect supported by another research system. External catalogs are local files and are never downloaded or embedded into output beyond the symbols used by an expression.
Expression subset
The embedded, license-safe catalog is
public-v1.json. It declares
operators, typed arguments, arity, commutativity/associativity, generation
weights, window/non-zero-scalar/keyword domains, keyword relations, fields and
public family labels, field roles, groups, and the scalar domain. Fields are
the catalog identifiers close, open, high, low, volume, and
returns. The parser also accepts finite scalar literals, true/false, and
catalog group literals such as group("sector"); unknown fields and groups are
rejected.
Parsing, validation, generation, compatible-operator replacement, field/group/
scalar/window/keyword mutation, tree-slot typing, novelty families, checkpoint
compatibility, and manifest identity all consume this catalog. Every
generation-enabled operator is reachable in a fixed coverage test, including
clip, divide, and negate. Catalog::from_json,
parse_expression_with_catalog, generate_with_catalog, and
validate_transformed_with_catalog form the public import boundary. Adding a
synthetic unary operator or field is covered by tests without adding generator
match arms. External catalogs must be license-safe and are never fetched over
the network.
Supported signal operators are:
- arithmetic:
add(a, b, ...),subtract(a, b),multiply(signal, scalar, ...),divide(signal, nonzero_scalar),negate(a),abs(a), andsign(a); - time series:
ts_delay(a, window),ts_rank(a, window),ts_mean(a, window),ts_sum(a, window),ts_std_dev(a, window),ts_var(a, window),ts_skew(a, window),ts_kurt(a, window),ts_min(a, window),ts_max(a, window),ts_median(a, window),ts_mad(a, window),ts_wma(a, window),ts_ema(a, window),ts_zscore(a, window), andts_delta(a, window), with integer windows in2..=512; - pairwise time series:
ts_cov(a, b, window)andts_corr(a, b, window); - cross sectional:
rank(a),zscore(a),group_rank(a, group("sector")); - conditions:
greater(signal, scalar)andif_else(boolean, then_signal, else_signal); - nonlinear bounds:
winsorize(a, std=2)wherestdis in0.1..=10, andclip(a, lower=-2, upper=2)where both bounds are in-100..=100andlower < upper.
Unknown operators, incorrect arity or kinds, invalid windows, non-finite scalars, unsupported keywords, non-literal keyword values, and out-of-range operator parameters are rejected before search or deduplication.
With the numeric-evidence feature, every generation-enabled embedded
operator has a causal local numeric backend. Evaluator construction fails
closed for a custom catalog that enables generation for an operator without a
backend, preventing search and evaluation spaces from silently diverging.
Formatting normalizes numeric spelling and keyword order. add and
multiply operands are sorted because those registry entries are explicitly
commutative. Exact fingerprints are SHA-256 hashes of exact canonical text.
Semantic analysis flattens associative multiplication only along the final
root, combines finite scalar factors, removes positive magnitude, and retains
a normalized negative direction. Thus close, multiply(close, 6),
multiply(close, -2, -3), and add(close, close) share a family, while
multiply(close, -2) remains directionally distinct. Scaling inside a
nonlinear operator, condition, clipping/winsorization step, or one composite
sleeve is never moved across that boundary.
Starting in 0.2.0, nested cross-sectional rank(rank(x)) also shares a
semantic family with rank(x). This narrow identity does not merge time-series
or grouped ranks, and does not remove scaling inside rank. Exact canonical
fingerprints are unchanged by this semantic rule; affected semantic fingerprints
must be recomputed when migrating an archive.
The same conservative pass rejects roots proven to be zero or constant, such
as subtract(close, close), multiplication by zero, or rank of a proven-zero
subtree. Uncertain cases remain eligible. inspect exposes rejection_reason
as null, provably_zero, or provably_constant. dedup writes all accepted
records while separately reporting deterministic duplicate and rejection
counts, so one old trivial record cannot abort the remaining valid stream.
Search and structural score
The seeded generator, mutation, and typed crossover enforce configured depth, node, operator, window, scalar, and operator-parameter limits. Immutable paths identify the root, every argument, and every keyword slot together with its required kind. Mutation covers fields, windows, ordinary scalars, groups, bounded keywords, boolean literals, compatible operators, and generated typed subtrees. Crossover replaces a receiver subtree only with a compatible donor kind. Every result round-trips through the public parser and passes complete limits validation.
Retries are capped at 16. Successful lineage records the affected path,
old/new exact subtree fingerprints, retries, and semantic parent fingerprints.
If retries cannot produce a valid child, the actual operation is
fallback_generation; its parent list is empty and the requested transform is
recorded separately. A bounded Rayon pool executes indexed offspring
generation, validation, semantic analysis, fingerprinting, and structural
feature computation. Results merge in ordinal order; scored parent selection
and family-balanced elitism bound the population at 512 expressions. The
resident elite, semantic-hash, descriptor, population, and parent structures
have explicit capacities in search-spec schema 2. Elite retention is a
streaming deterministic top-k operation rather than an unbounded list of every
accepted AST. Final descriptor-aware shortlist selection compares bounded
32-record candidate and selected-reference samples and checks the run deadline
between selections. A wide shortlist therefore remains bounded instead of
degrading into a quadratic all-pairs finalization tail.
structural_score schema 2 is explicitly non-financial:
0.20 * simplicity
+ 0.15 * archive_descriptor_novelty
+ 0.10 * root_family_diversity
+ 0.10 * lineage_diversity
+ 0.35 * structural_descriptor_novelty
+ 0.10 * transform_diversity
The versioned weights, expression limits, and resource capacities are embedded in the manifest. Search reserves a finalization interval, checks the wall clock between bounded generation/scoring/merge stages, stops at the hard duration deadline, and serializes completed work. A versioned checkpoint written after each completed generation contains the exact population, bounded archives, counters, next generation, deterministic seed state, configuration checksum, and operator-catalog checksum. Resume rejects a changed configuration, archive, or operator catalog. With a candidate ceiling reached before the deadline, candidate bytes are deterministic for identical input, seed, configuration, and thread count. Deadline-bound run counts can reflect host scheduling; the manifest records the actual termination reason and elapsed time. The current indexed computation and stable tie breaking also produce matching candidate checksums across thread counts in the integration benchmark, but callers should rely only on the fixed-thread contract.
Artifact schemas
Each newly written candidate JSONL line has schema: 4; readers also accept
legacy schema 1–3 records. Schema 4 adds the immutable run_id; schema 3 added
the score schema, every component, normalized weights, and complete structural
descriptor:
The adjacent compatibility file OUTPUT.manifest.json has schema: 8,
candidate_schema: 4, tool
version, complete search specification and scoring weights, configuration
checksum, archive and candidate counters, rejected_trivial_candidates, a
machine-readable rejection_reasons map, archive accepted/duplicate/rejected
counters and reasons, invalid-transform count, accepted/retained transform-kind
counts, completed generations, all resident capacities and peaks, elapsed
milliseconds, termination reason, and SHA-256 of the exact candidate JSONL
content. It also records measured-feedback identity, optional operator-policy
weights and provenance, archive-seeding controls, and the catalog-scaffold policy.
Candidate and manifest bytes are first written and synced as an immutable pair
under .OUTPUT.runs/. Only then is OUTPUT.current.json atomically replaced.
Readers following that pointer cannot pair a new candidate file with a stale
manifest. The adjacent candidate and manifest paths remain compatibility
aliases and are refreshed after publication; damaging either alias does not
damage the last complete pointed-to run. Structural-search checkpoints use
schema 2 and the same
temporary-file, sync_all, atomic-rename protocol.
Optional numeric evidence
The numeric-evidence feature adds a pure-Rust evaluator and the evaluate
command; it is absent from the default binary. Input is immutable schema-1
JSON with increasing timestamps, an assets vector, one row-major
NumericColumn per field, a separate row-major realized_returns column,
optional static group labels, a source label, and preprocessing notes. Each
column contains exactly timestamps.len() * assets.len() numbers or nulls.
The evaluator supports every operator in the embedded catalog. Time-series
windows read the current and previous rows only. Signal row t is paired with
the additive realized return from rows t+1..=t+horizon; a target is discarded
if it would cross its train, validation, or holdout boundary. The output records
the dataset ID and checksum, source, canonical expression/fingerprint, split
ranges, horizon, costs, alignment rule, user preprocessing, evaluator
assumptions, observation counts, measured Pearson correlation, measured
per-row gross/net return, and turnover.
evaluate-batch accepts the candidate JSONL produced by search, loads and
validates the immutable dataset once, evaluates bounded chunks with
--threads N, preserves deterministic input order, and atomically publishes
one evidence JSONL. At most one chunk of expression artifacts is materialized
at a time, so increasing workers has an explicit memory tradeoff.
Individual expression failures are recorded without discarding successful
rows. --include-daily is optional because daily evidence can materially
increase artifact size.
evaluation_start changes only the first scored row: earlier rows remain
available to rolling operators and provide causal warm-up without entering
reported PnL. overall is evaluated continuously without resetting positions
at diagnostic split boundaries. daily is emitted only with
--include-daily; every row records signal and target timestamps, gross and
cost-adjusted returns, and turnover. --signal-decay N applies one causal
linear decay at the final signal root, with the current row receiving weight
N and the oldest row weight 1; zero disables the setting.
--truncation X caps absolute normalized position weights and deterministically
redistributes remaining gross exposure; zero disables it.
brain-proxy-v1 is a transparent local convention, not a platform result. It
uses population-volatility annualized gross Sharpe, twice the annualized
unit-gross return, mean unit-gross turnover, twice the maximum drawdown of the
additive gross-PnL curve, mean gross return divided by turnover for margin, and
Sharpe * sqrt(abs(Returns) / max(Turnover, 0.125)) for Fitness. Gross proxy
metrics never deduct the user-selected transaction cost; measured net metrics
remain separate. Universe and source-data differences can dominate the error,
so proxy values still require out-of-sample calibration.
Optional Sharadar Parquet preparation
The parquet-input feature adds a local, read-only adapter for partitioned
Sharadar daily and stocks tables. It selects a fresh positive-finite
market-cap universe independently on every date, emits adjusted open/high/low/
close, raw volume, and closeadj returns, keeps realized returns outside
current membership for future targets, and atomically writes the same schema-1
JSON consumed by evaluate:
The command prints the exact row indices to pass to evaluate. Loaded dates
before evaluation_start are warm-up only. The adapter never contacts a data
vendor or platform, never writes into the source lake, and refuses to
overwrite an existing output file.
Target-dialect compile and lint
compile lowers an expression against an immutable, user-supplied dialect
artifact before an external consumer sees it. A dialect contains explicit
field/operator allowlists and conservative unary-to-scalar lowerings. For
example, a target that lacks negate can declare a structural lowering to
multiply(-1, x):
The command emits source and compiled canonical expressions, symbols used, and every lowering applied. It fails closed on an unavailable field/operator and contains no credentials, network, simulation, or submission path.
Numeric evidence is a separate schema-1 artifact. It never enters
structural_score (structural_score_used is explicitly false), is never
represented as measured WorldQuant Fitness/Sharpe, and makes no pass-probability
or future-return claim. Optional brain_proxy values remain explicitly local
estimates with remote_platform_result: false. The evaluator uses deterministic ordinal tie-breaking for ranks,
listwise missing-value exclusion, additive horizon returns, cross-sectional
demeaning plus unit-gross normalization, and turnover costs; all are repeated
in each artifact for auditability.
Measured-feedback prioritization
prioritize consumes candidate-schema JSONL and one immutable schema-1
feedback dataset. A dataset declares its ID, one context checksum, a descriptive
outcome label, positive scales for an exact structural-feature set, and bounded
outcomes in [-1, 1] with confidence in (0, 1]. The included
public-feedback-v1.json is synthetic and
contains no market or platform data.
Supported features are field-name-independent: nodes, depth, operator count, field/family/group counts, short/medium/long window counts, nonlinear and conditional flags, and lineage-parent count. Deterministic nearest-neighbor guidance reports dataset/context/checksum, neighbor count, nearest distance, weighted outcome, dispersion, and a conservative outcome. Dataset checksums are invariant to record order.
The output nests each original candidate byte-for-byte at the semantic-field
level and adds measured_guidance; candidate.structural_score is not changed
and every estimate records structural_score_used: false. Guided candidates
sort by conservative measured outcome, then measured mean, original structural
score, and semantic fingerprint. Records without enough comparable neighbors
remain explicit and sort after guided records. The outcome is user-defined and
must not be called expected return, Sharpe, Fitness, correlation, or a pass
probability unless the supplied immutable dataset itself establishes that
meaning.
audit-feedback applies the identical neighbor rule while excluding each
target record from its own evidence. It reports coverage, MAE, RMSE, sign
accuracy, balanced sign accuracy, and rank AUC (only when both non-zero outcome
signs exist). This is a deterministic leakage check for comparing feature sets
or neighbor settings, not evidence that the supplied outcomes generalize to a
new dataset or context.
Measured evidence can also guide the structural search's parent selection:
--seed-from-archive initializes parents from accepted --archive records;
--freeze-seed-parents additionally requires feedback and keeps that seeded
parent set fixed. --feedback-elite-parent-count reserves parent slots for the
measured-feedback ranking. All are explicit opt-ins, and guidance remains
separate from structural_score. The public feedback fixture is synthetic.
search --feedback-operator-policy-strength 0.5 additionally opts into an
outcome-aware generation policy when the feedback contains
operator_<name>_count features. It converts confidence-weighted operator
outcomes into bounded catalog weights, preserves positive exploration for every
enabled operator, and records the configuration checksum, base and adjusted
catalog checksums, and exact weights in the run manifest. Without this flag,
search does not apply feedback-derived operator weights. This is not a
cross-version byte-compatibility guarantee: the 0.2 catalog and semantic rules
also changed. --catalog-scaffold-policy separately selects fixed,
seeded-diverse, or motif-diverse initial coverage; --seeded-catalog-scaffold
remains an alias for seeded-diverse coverage.
Reproducible benchmark
Command (structural-search throughput, not predictive performance):
For repeated measurements use --seeds 7,15,42 --repetitions 3.
The 0.2.0 preparation measurement used 20,000 candidates, seed 20260828, 1/4 threads and three repetitions on 2026-09-30 (Apple M1 Pro, macOS arm64, Rust 1.98.1). All six trials produced the same candidate checksum and 17,185 semantic families, with zero invalid emissions. Median wall time was 5,105 ms with one thread and 2,155 ms with four. These are local observations, not portable speed guarantees or market results. The historical rows below describe earlier trees; catalog and identity changes can alter counts and checksums across versions.
The phase rows were measured 2026-08-28, with later current-tree measurements on 2026-09-04 and 2026-09-08, all on the same Apple M1 Pro, Darwin arm64. The review baseline preceded the Phase 1 semantic/triviality pass:
| Version | Threads | Generated | Semantic families | Trivial rejects | Duplicate rejects | Retained | Wall time | Candidate SHA-256 |
|---|---|---|---|---|---|---|---|---|
| Review baseline | 1 | 20,000 | 15,787 | — | 4,213 | 100 | ~359 ms | b0134b7ba5c3e29b96b660a616e8e305041cd55c4884c5cd3de9db2fa39c3b95 |
| Review baseline | 4 | 20,000 | 15,787 | — | 4,213 | 100 | ~271 ms | b0134b7ba5c3e29b96b660a616e8e305041cd55c4884c5cd3de9db2fa39c3b95 |
| Phase 1 | 1 | 20,000 | 15,633 | 52 | 4,315 | 100 | 519 ms | 2b9fe09fda351a8495dd17fcbb38886fbf3867f9d331927390455c595976abc4 |
| Phase 1 | 4 | 20,000 | 15,633 | 52 | 4,315 | 100 | 442 ms | 2b9fe09fda351a8495dd17fcbb38886fbf3867f9d331927390455c595976abc4 |
| Phase 2 | 1 | 20,000 | 18,056 | 49 | 1,895 | 100 | 813 ms | 8bd2a3e1764c240dc1c64db537942de1b0ce79045364e95910c8a0446fed87df |
| Phase 2 | 4 | 20,000 | 18,056 | 49 | 1,895 | 100 | 712 ms | 8bd2a3e1764c240dc1c64db537942de1b0ce79045364e95910c8a0446fed87df |
| Phase 2 corrected | 1 | 20,000 | 17,997 | 64 | 1,939 | 100 | 906 ms | 77cd80a7104b8ec849422464695759fd5f8176bc81f149ff658a540344092666 |
| Phase 2 corrected | 4 | 20,000 | 17,997 | 64 | 1,939 | 100 | 912 ms | 77cd80a7104b8ec849422464695759fd5f8176bc81f149ff658a540344092666 |
| Phase 3 | 1 | 20,000 | 7,008 | 132 | 12,860 | 100 | 697 ms | 6a22cccc07c2e6583c67a250986303559162776389abe00b437b2c711b49e2fe |
| Phase 3 | 4 | 20,000 | 7,008 | 132 | 12,860 | 100 | 361 ms | 6a22cccc07c2e6583c67a250986303559162776389abe00b437b2c711b49e2fe |
| Phase 4 | 1 | 20,000 | 14,815 | 92 | 5,093 | 100 | 3,289 ms | 180d296a48f9668d96b91db0340e746dd4973f3d1799665e99e5bc8e53a53cab |
| Phase 4 | 4 | 20,000 | 14,815 | 92 | 5,093 | 100 | 1,268 ms | 180d296a48f9668d96b91db0340e746dd4973f3d1799665e99e5bc8e53a53cab |
| Phase 5 | 1 | 20,000 | 14,815 | 92 | 5,093 | 100 | 3,479 ms | 5a322ca6d4a5ecfc42ae28ae510ccf46b3ea1f63665e23cb842dc1733b17d97e |
| Phase 5 | 4 | 20,000 | 14,815 | 92 | 5,093 | 100 | 1,713 ms | 5a322ca6d4a5ecfc42ae28ae510ccf46b3ea1f63665e23cb842dc1733b17d97e |
| Phase 6 | 1 | 20,000 | 15,135 | 72 | 4,793 | 100 | 3,373 ms | 8b4a83c552ab96f1382b9a9c4b7f5455bcd0d60225750c6f36f7a34395476dfb |
| Phase 6 | 4 | 20,000 | 15,135 | 72 | 4,793 | 100 | 1,576 ms | 8b4a83c552ab96f1382b9a9c4b7f5455bcd0d60225750c6f36f7a34395476dfb |
| Phase 9 | 1 | 20,000 | 16,587 | 49 | 3,364 | 100 | 5,042 ms | af46848c82daf83d57a1a111301e63e4ca99cd7b3f0f0513a1a5aab972e5d8f8 |
| Phase 9 | 4 | 20,000 | 16,587 | 49 | 3,364 | 100 | 2,433 ms | af46848c82daf83d57a1a111301e63e4ca99cd7b3f0f0513a1a5aab972e5d8f8 |
| Re-measured 2026-09-04 | 1 | 20,000 | 15,850 | 38 | 4,112 | 100 | 4,725 ms | 66df757199e55116f283747f74bdb917c59c1a50ea4f813a66182feb92a40e12 |
| Re-measured 2026-09-04 | 4 | 20,000 | 15,850 | 38 | 4,112 | 100 | 2,046 ms | 66df757199e55116f283747f74bdb917c59c1a50ea4f813a66182feb92a40e12 |
| Re-measured 2026-09-08 | 1 | 20,000 | 16,968 | 70 | 2,962 | 100 | 4,905 ms | 61d9fefe25bdd491524bc1e313a6dca14c5d24199a885b1f40e2a88711e0b378 |
| Re-measured 2026-09-08 | 4 | 20,000 | 16,968 | 70 | 2,962 | 100 | 2,500 ms | 61d9fefe25bdd491524bc1e313a6dca14c5d24199a885b1f40e2a88711e0b378 |
Phase 1 rejected 23 proven-zero and 29 proven-constant expressions. The semantic-family count fell by 154: 52 explicit trivial rejections plus 102 additional forms merged by the strengthened final-root equivalence rules. The composite-add and tolerant-archive corrections did not change these fixed workload counts or checksum.
Phase 2 accepted 64 initial, 4,150 mutation, 12,666 crossover, and 1,176 truthfully labelled fallback-generation records. The retained shortlist still contains 96 crossover and four initial records; changing selection pressure belongs to the generation-based engine and diversity phases, not to the lineage implementation.
After correcting context-aware multiply slot compatibility, the same
workload accepted 64 initial, 4,171 mutation, 12,585 crossover, and 1,177
fallback-generation records. It retained the same 96 crossover and four
initial records. One- and four-thread candidate checksums remain identical;
the changed checksum and rejection counts are the expected result of the
expanded typed crossover search space.
Phase 3 completed 157 deterministic batches with a peak population of 512 and zero invalid emitted expressions. Accepted candidates comprised 45 initial, 915 mutation, 3,927 crossover, and 2,121 truthful fallback-generation records. The higher duplicate count reflects actual evolutionary convergence under the still-coarse novelty measure; Phase 4 addresses it with layered structural descriptors rather than hiding it with random generation.
Phase 4 recovered 14,815 semantic families using deterministic staged descriptor comparisons. The retained 100 are balanced 25 each across initial, mutation, crossover, and fallback-generation provenance. Descriptor scoring is more expensive than root-only balancing, but the four-thread workload remained byte-identical and reduced measured wall time from 3,289 ms to 1,268 ms.
Phase 5 retains the same accepted/rejected counts while bounding the persistent population at 512, elite archive at 4,096, semantic index at 100,000, and imported descriptor archive at 4,096 entries. Its changed checksum is expected: the bounded archive uses deterministic family-balanced top-k retention and schema 4 includes the run identifier. A pause/resume regression reaches the same fixed-budget candidate checksum as an uninterrupted run.
Phase 6 replaces the 13-way generator match with weighted catalog traversal and makes all 15 parser-supported operators generation-reachable. The workload found 15,135 semantic families with zero invalid emitted expressions. The candidate checksum changed because catalog weights/domains now define the search distribution; one- and four-thread bytes still match.
Phase 9 preserves geometric node-count buckets independently within transform and root families in the population, elite archive, and shortlist. The fixed workload retains 16,587 semantic families and a mix of 31 mutation, 28 crossover, 24 fallback-generation, and 17 initial records. A separate bounded 100,000-candidate run with limits 20/192/96 retained structures from 1 through 159 nodes, demonstrating that the wider CLI envelope is operational. The additional diversity bookkeeping increases wall time; one- and four-thread candidate checksums remain identical.
The 2026-09-04 rows were a re-run of the identical fixed workload after the Phase 9 work. The 2026-09-08 rows describe the expanded built-in catalog: they retain 16,968 semantic families, reject 70 trivial expressions and 2,962 duplicates, and produce the same candidate checksum with one and four threads. The changed counts and checksum are expected because the generation-enabled operator set changed. Wall times are local observations and are not a portable speedup claim.
These are observations from one workload and environment, not a hard-coded speedup claim. The command reports full configuration, counts, checksums, thread counts, and wall time so results can be compared honestly elsewhere.
FAQ
What is formulaic alpha mining?
Formulaic alpha mining searches a domain-specific language of mathematical expressions for candidate signals or quantitative factors. AlphaWinnow builds only well-typed expressions, removes known duplicates, and preserves a diverse shortlist. A separate measured evaluation is still required to determine whether any candidate contains useful market information.
Is AlphaWinnow a symbolic regression or genetic programming library?
AlphaWinnow uses strongly typed genetic-programming techniques—generation, mutation, crossover, selection, and novelty—but specializes them for quantitative expressions. The default structural search does not fit a numeric regression target. You can provide measured feedback or evaluate candidates on your own panel as explicit later stages.
Is AlphaWinnow a backtesting engine?
It includes an optional causal factor evaluator with transaction costs, turnover, split diagnostics, and daily long-short evidence. It is not an order execution simulator: there is no broker, exchange, fill, latency, or live trading model.
Does it support WorldQuant BRAIN or FASTEXPR formulas?
The bundled DSL contains familiar WorldQuant-style time-series, cross-sectional, arithmetic, group, and conditional operators. AlphaWinnow has no WorldQuant BRAIN API, authentication, simulation, or submission integration. Use a local catalog and target-dialect file to describe the exact syntax an external system accepts; always verify compatibility independently.
AlphaWinnow is an independent open-source project and is not affiliated with or endorsed by WorldQuant.
Can I use Sharadar market data?
Yes. The optional parquet-input feature reads local Sharadar-compatible
Parquet files and prepares a causal dynamic-universe panel. No market data is
bundled, downloaded, or redistributed; you must supply data you are licensed
to use.
Does AlphaWinnow require Python?
No. The CLI, parser, search engine, semantic deduplication, numeric evaluator, and Parquet adapter are implemented in Rust.
Will it discover profitable trading strategies?
There is no such guarantee. Large factor searches are vulnerable to overfitting, selection bias, regime change, and transaction costs. Treat every output as a hypothesis requiring independent point-in-time data, out-of-sample testing, and risk review.
Security and scope
AlphaWinnow is local-only by design:
- no network client or telemetry;
- no credentials, cookies, or API keys;
- no broker, exchange, or remote research-platform integration;
- no database server or background service;
- no live simulation or alpha submission path.
Commands read explicit immutable inputs and write versioned local artifacts. The optional Sharadar adapter is also read-only with respect to the source dataset.
Development
Two documentation surfaces are published automatically and need no manual
step. docs.rs/alphawinnow is built by crates.io
for every released version. eslazarev.github.io/alphawinnow
is the same cargo doc reference built from main, so it covers unreleased
work; both are generated from the /// comments in the source, never
hand-edited.
Run the complete quality gates before opening a pull request:
AlphaWinnow is a single crate with a library target and a binary target.
Reusable search and evaluation logic belongs in the library; src/main.rs
should remain a thin argument-parsing and serialization layer that only calls
the public API. See the public ROADMAP.md for planned work and
docs/radiate-decision.md for the reproducible
multi-objective-search dependency evaluation.
Continuous integration
Every push and pull request against main runs the same gates on GitHub
Actions, plus a few that are impractical to run locally on every change:
| Workflow | What it enforces |
|---|---|
ci.yml |
formatting, Clippy with warnings denied, tests on Linux/macOS/Windows, every feature combination, the declared MSRV, cargo doc with warnings denied, a cargo publish dry run, and the standalone benchmark workspace |
ci.yml → library-graph |
that no CLI-only dependency is reachable without the cli feature, so default-features = false stays genuinely lean |
audit.yml |
cargo audit and cargo deny over advisories, licenses, sources, and the banned-dependency policy in deny.toml |
codeql.yml, secrets.yml, scorecard.yml |
static analysis, verified-secret scanning, and OpenSSF Scorecard |
release.yml |
verifies first, preserves a prepared version or raises it from Conventional Commits, then tags, builds, and publishes; see docs/releasing.md |
docs.yml |
rustdoc for main, published to GitHub Pages |
deny.toml also encodes the local-only product boundary: no HTTP or TLS crate
may enter the dependency graph, so the "performs no network requests" claim is
checked mechanically rather than by review alone.
License
AlphaWinnow is available under the MIT License.
Limitations and next milestone
- The public catalog is deliberately small and synthetic; there is no bundled private field catalog or automatic catalog download.
- Admission and deterministic archive merge remain serial by design; indexed generation, validation, analysis, and scoring run in the bounded Rayon pool.
- Structural novelty is an auditable AST descriptor distance, not numeric correlation or measured market evidence.
- The generic numeric backend reads the documented JSON columnar schema. The optional Sharadar adapter reads Parquet but intentionally materializes that portable JSON boundary; it is not yet a general streaming Parquet evaluator.
- Static group membership is supported; point-in-time changing classifications require a future schema version.
- Persistent search memory is bounded by explicit resource capacities; each parallel generation additionally holds one fixed 128-candidate work batch.
- The manifest contains measured elapsed time and therefore is auditable rather than byte-identical between repeated runs; candidate JSONL is the reproducible content artifact.
Measured guidance and caller-supplied ask/tell outcomes remain separate from structural scores. Their availability is not evidence of predictive superiority. Streaming Arrow/Parquet evaluation and stronger independent validation remain future work; see the roadmap. Neither requires remote platform integration.