strategy-genome 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- strategy_genome-0.1.0/.gitignore +9 -0
- strategy_genome-0.1.0/CHANGELOG.md +62 -0
- strategy_genome-0.1.0/LICENSE +21 -0
- strategy_genome-0.1.0/NOTICE +11 -0
- strategy_genome-0.1.0/PKG-INFO +163 -0
- strategy_genome-0.1.0/contrib/optunahub/strategy_genome/LICENSE +21 -0
- strategy_genome-0.1.0/docs/api.md +109 -0
- strategy_genome-0.1.0/docs/ecosystem.md +29 -0
- strategy_genome-0.1.0/docs/gp.md +52 -0
- strategy_genome-0.1.0/docs/index.md +122 -0
- strategy_genome-0.1.0/docs/optuna-and-freqtrade.md +123 -0
- strategy_genome-0.1.0/docs/provenance.md +71 -0
- strategy_genome-0.1.0/docs/trading-examples.md +36 -0
- strategy_genome-0.1.0/examples/freqtrade_strategy.py +55 -0
- strategy_genome-0.1.0/examples/gp_signal.py +25 -0
- strategy_genome-0.1.0/examples/multi_objective.py +48 -0
- strategy_genome-0.1.0/examples/optuna_study.py +26 -0
- strategy_genome-0.1.0/examples/quickstart.py +22 -0
- strategy_genome-0.1.0/examples/toy_backtest.py +9 -0
- strategy_genome-0.1.0/pyproject.toml +71 -0
- strategy_genome-0.1.0/src/strategy_genome/__init__.py +60 -0
- strategy_genome-0.1.0/src/strategy_genome/__main__.py +5 -0
- strategy_genome-0.1.0/src/strategy_genome/cli.py +92 -0
- strategy_genome-0.1.0/src/strategy_genome/evolution.py +751 -0
- strategy_genome-0.1.0/src/strategy_genome/fitness.py +145 -0
- strategy_genome-0.1.0/src/strategy_genome/genome.py +1076 -0
- strategy_genome-0.1.0/src/strategy_genome/gp/__init__.py +48 -0
- strategy_genome-0.1.0/src/strategy_genome/gp/expression_tree.py +749 -0
- strategy_genome-0.1.0/src/strategy_genome/gp/gp_engine.py +610 -0
- strategy_genome-0.1.0/src/strategy_genome/gp/gp_operators.py +323 -0
- strategy_genome-0.1.0/src/strategy_genome/gp/signal_validator.py +528 -0
- strategy_genome-0.1.0/src/strategy_genome/metrics.py +149 -0
- strategy_genome-0.1.0/src/strategy_genome/operators.py +827 -0
- strategy_genome-0.1.0/src/strategy_genome/optuna.py +300 -0
- strategy_genome-0.1.0/src/strategy_genome/pareto.py +159 -0
- strategy_genome-0.1.0/src/strategy_genome/population.py +769 -0
- strategy_genome-0.1.0/src/strategy_genome/py.typed +0 -0
- strategy_genome-0.1.0/src/strategy_genome/space.py +124 -0
- strategy_genome-0.1.0/src/strategy_genome/trading/__init__.py +71 -0
- strategy_genome-0.1.0/src/strategy_genome/trading/backtest.py +536 -0
- strategy_genome-0.1.0/src/strategy_genome/trading/coevolution.py +887 -0
- strategy_genome-0.1.0/src/strategy_genome/trading/meta_reward.py +845 -0
- strategy_genome-0.1.0/tests/test_core.py +292 -0
- strategy_genome-0.1.0/tests/test_gp.py +1204 -0
- strategy_genome-0.1.0/tests/test_metrics.py +98 -0
- strategy_genome-0.1.0/tests/test_optuna.py +166 -0
- strategy_genome-0.1.0/tests/test_trading.py +103 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-11-15
|
|
8
|
+
|
|
9
|
+
First release. Carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab)
|
|
10
|
+
`ml/genetic` at commit `8cbfcb0` (MIT). See `docs/provenance.md`.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `evolve(fitness, space, population, generations, seed, ...)`: GA over a
|
|
14
|
+
user-supplied fitness function returning a float or a tuple of floats,
|
|
15
|
+
with `direction` / `directions` (maximize or minimize per objective).
|
|
16
|
+
- `strategy_genome.space`: `Float`, `Int`, `LogFloat`, `Categorical`, `Bool`
|
|
17
|
+
and shorthand dict spaces.
|
|
18
|
+
- `EvolutionResult` gains `best_params`, `best_value(s)`,
|
|
19
|
+
`diversity_history`, `best_fitness_history`, `n_failed_evaluations`,
|
|
20
|
+
`directions` and `to_dict()`.
|
|
21
|
+
- `CallableFitness`: failed or NaN evaluations score `-inf` and are counted
|
|
22
|
+
instead of aborting the run (opt out with `raise_errors=True`).
|
|
23
|
+
- `strategy_genome.optuna.GeneticStrategySampler`: steady-state GA as an
|
|
24
|
+
`optuna.samplers.BaseSampler` (single and multi-objective), plus
|
|
25
|
+
`space_to_distributions` / `distribution_to_param_range`. Works with
|
|
26
|
+
freqtrade hyperopt via `HyperOpt.generate_estimator`.
|
|
27
|
+
- `n_jobs` thread-pool evaluation; `callback` can stop a run early;
|
|
28
|
+
`initial` seeds generation 0; `multi_run` repeats a run over seeds.
|
|
29
|
+
- `GPEngine(terminal_names=..., function_names=...)`.
|
|
30
|
+
- `strategy-genome demo` CLI.
|
|
31
|
+
- Tests: numeric reference checks for metrics and hypervolume, GA
|
|
32
|
+
convergence and reproducibility, Optuna integration (tested on Optuna 3.4
|
|
33
|
+
and 5.0), trading examples, CLI; plus the 108 upstream GP tests.
|
|
34
|
+
- `contrib/optunahub/strategy_genome`: OptunaHub registry wrapper (not yet
|
|
35
|
+
submitted).
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
- Fitness is user-supplied. The hard-wired toy backtester, co-evolution and
|
|
39
|
+
meta-reward code moved to the opt-in `strategy_genome.trading` package.
|
|
40
|
+
- Return-series metrics moved to `strategy_genome.metrics`, Pareto indicators
|
|
41
|
+
to `strategy_genome.pareto`, GP to `strategy_genome.gp`.
|
|
42
|
+
- Checkpoint files are opt-in (`checkpoint_interval=0` by default) and
|
|
43
|
+
`verbose` defaults to `False`.
|
|
44
|
+
- Multi-objective parent selection uses NSGA-II crowded comparison.
|
|
45
|
+
|
|
46
|
+
### Fixed (relative to srfm-lab)
|
|
47
|
+
- `StrategyGenome.clone()` was called but never defined, so the GA loop
|
|
48
|
+
raised `AttributeError`.
|
|
49
|
+
- Seeded runs were not reproducible (unseeded diversity sampler and operator
|
|
50
|
+
selection; genome ids drawn from the global RNG).
|
|
51
|
+
- `Population.__repr__` raised `ValueError`.
|
|
52
|
+
- Two-point crossover raised `IndexError` on single-gene chromosomes.
|
|
53
|
+
- `ParetoAnalysis.knee_point` returned an extreme point instead of the knee.
|
|
54
|
+
- `ParamRange.step` was ignored.
|
|
55
|
+
- Island mode did not advance the generation counter and left padded
|
|
56
|
+
members unevaluated.
|
|
57
|
+
|
|
58
|
+
### Removed
|
|
59
|
+
- `visualization.py` (matplotlib dashboards) and the multiprocessing worker
|
|
60
|
+
that never ran in parallel.
|
|
61
|
+
|
|
62
|
+
[0.1.0]: https://github.com/Mattbusel/strategy-genome/releases/tag/v0.1.0
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Busel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
strategy-genome
|
|
2
|
+
Copyright (c) 2026 Matthew Busel
|
|
3
|
+
|
|
4
|
+
This package was carved out of srfm-lab (https://github.com/Mattbusel/srfm-lab),
|
|
5
|
+
directory ml/genetic, at commit 8cbfcb0551d44e3ed1c20bdae9dc805d61521c62.
|
|
6
|
+
srfm-lab is released under the MIT License, copyright (c) 2026 Matthew Busel.
|
|
7
|
+
|
|
8
|
+
Most modules under src/strategy_genome/ (genome, population, operators,
|
|
9
|
+
evolution, gp/, trading/) are derived from that code; docs/provenance.md maps
|
|
10
|
+
each file to its origin and lists the changes made. tests/test_gp.py is the
|
|
11
|
+
upstream test suite with only its import paths changed.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: strategy-genome
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Typed genetic-algorithm / genetic-programming optimizer (NSGA-II, island model, adaptive operators) with an Optuna sampler. Carved out of srfm-lab.
|
|
5
|
+
Project-URL: Homepage, https://github.com/Mattbusel/strategy-genome
|
|
6
|
+
Project-URL: Repository, https://github.com/Mattbusel/strategy-genome
|
|
7
|
+
Project-URL: Documentation, https://github.com/Mattbusel/strategy-genome/tree/main/docs
|
|
8
|
+
Project-URL: Changelog, https://github.com/Mattbusel/strategy-genome/blob/main/CHANGELOG.md
|
|
9
|
+
Project-URL: Issues, https://github.com/Mattbusel/strategy-genome/issues
|
|
10
|
+
Project-URL: Upstream (srfm-lab), https://github.com/Mattbusel/srfm-lab
|
|
11
|
+
Author: Matthew Busel
|
|
12
|
+
License-Expression: MIT
|
|
13
|
+
License-File: LICENSE
|
|
14
|
+
License-File: NOTICE
|
|
15
|
+
Keywords: evolutionary-algorithm,freqtrade,genetic-algorithm,genetic-programming,hyperopt,hyperparameter-optimization,multi-objective-optimization,nsga-ii,optuna,quantitative-finance,sampler,trading-strategy
|
|
16
|
+
Classifier: Development Status :: 4 - Beta
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
19
|
+
Classifier: Intended Audience :: Science/Research
|
|
20
|
+
Classifier: Operating System :: OS Independent
|
|
21
|
+
Classifier: Programming Language :: Python :: 3
|
|
22
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
28
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
29
|
+
Classifier: Topic :: Scientific/Engineering :: Mathematics
|
|
30
|
+
Classifier: Typing :: Typed
|
|
31
|
+
Requires-Python: >=3.10
|
|
32
|
+
Requires-Dist: numpy>=1.22
|
|
33
|
+
Provides-Extra: optuna
|
|
34
|
+
Requires-Dist: optuna>=3.4; extra == 'optuna'
|
|
35
|
+
Provides-Extra: scipy
|
|
36
|
+
Requires-Dist: scipy>=1.8; extra == 'scipy'
|
|
37
|
+
Provides-Extra: test
|
|
38
|
+
Requires-Dist: optuna>=3.4; extra == 'test'
|
|
39
|
+
Requires-Dist: pytest>=7; extra == 'test'
|
|
40
|
+
Description-Content-Type: text/markdown
|
|
41
|
+
|
|
42
|
+
# strategy-genome
|
|
43
|
+
|
|
44
|
+
A typed genetic algorithm (GA) and genetic programming (GP) optimizer for
|
|
45
|
+
Python, with an Optuna sampler so any Optuna study can use it.
|
|
46
|
+
|
|
47
|
+
- **You bring the fitness function.** `evolve(fitness, space)` calls
|
|
48
|
+
`fitness(genome)` and expects a float, or a tuple of floats for
|
|
49
|
+
multi-objective runs. Nothing about trading is built in.
|
|
50
|
+
- **Mixed search spaces:** floats (optionally on a step grid), integers,
|
|
51
|
+
log-scale floats, categoricals and booleans.
|
|
52
|
+
- **Real GA machinery:** elitism, tournament selection, SBX / arithmetic /
|
|
53
|
+
uniform / one- and two-point crossover, gaussian / polynomial / creep /
|
|
54
|
+
boundary mutation, adaptive mutation rate and adaptive operator choice,
|
|
55
|
+
NSGA-II for multiple objectives, an island model, stagnation restarts, and a
|
|
56
|
+
hall of fame.
|
|
57
|
+
- **Reproducible:** the same seed and the same fitness give the same run.
|
|
58
|
+
- **Light:** the GA core is pure standard-library Python. numpy is used by
|
|
59
|
+
the GP submodule and the trading examples. Optuna is an optional extra.
|
|
60
|
+
|
|
61
|
+
Carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab)
|
|
62
|
+
(`ml/genetic`), MIT licensed. See [provenance](https://github.com/Mattbusel/strategy-genome/blob/main/docs/provenance.md) for what was
|
|
63
|
+
changed and which upstream bugs were fixed on the way out.
|
|
64
|
+
|
|
65
|
+
## Install
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
pip install strategy-genome # GA core + GP (numpy)
|
|
69
|
+
pip install "strategy-genome[optuna]" # adds the Optuna sampler
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Python 3.10 or newer.
|
|
73
|
+
|
|
74
|
+
## Quickstart
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
from strategy_genome import evolve, space as sp
|
|
78
|
+
|
|
79
|
+
def fitness(g):
|
|
80
|
+
# g["name"] reads one parameter; g.params is the whole dict
|
|
81
|
+
return (g["x"] - 3.0) ** 2 + (g["y"] + 1.0) ** 2
|
|
82
|
+
|
|
83
|
+
result = evolve(
|
|
84
|
+
fitness,
|
|
85
|
+
{"x": sp.Float(-10, 10), "y": sp.Float(-10, 10)},
|
|
86
|
+
population=40,
|
|
87
|
+
generations=80,
|
|
88
|
+
seed=7,
|
|
89
|
+
direction="minimize",
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
result.best_params # {'x': 3.00..., 'y': -0.99...}
|
|
93
|
+
result.best_value # what your function returned for the best genome
|
|
94
|
+
result.hall_of_fame # best unique genomes seen, best first
|
|
95
|
+
result.diversity_history # mean pairwise parameter distance per generation
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Search spaces
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
space = {
|
|
102
|
+
"fast": sp.Int(2, 50),
|
|
103
|
+
"slow": sp.Int(10, 200, step=5),
|
|
104
|
+
"stop": sp.Float(0.005, 0.10),
|
|
105
|
+
"lr": sp.LogFloat(1e-5, 1e-1),
|
|
106
|
+
"kind": sp.Categorical(["ema", "sma", "wma"]),
|
|
107
|
+
"short": sp.Bool(),
|
|
108
|
+
}
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Shorthand also works: `(0, 10)` is an integer range, `(0.0, 1.0)` a float
|
|
112
|
+
range, and a list is a categorical.
|
|
113
|
+
|
|
114
|
+
### Several objectives
|
|
115
|
+
|
|
116
|
+
Return a tuple and give one direction per objective. The run switches to
|
|
117
|
+
NSGA-II and `result.pareto_front` holds the non-dominated genomes.
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
def fitness(g):
|
|
121
|
+
returns = backtest(g.params)
|
|
122
|
+
return sharpe(returns), max_drawdown(returns)
|
|
123
|
+
|
|
124
|
+
result = evolve(fitness, space, population=60, generations=100, seed=1,
|
|
125
|
+
directions=["maximize", "minimize"])
|
|
126
|
+
for g in result.pareto_front:
|
|
127
|
+
print(g.values, g.params)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Failures
|
|
131
|
+
|
|
132
|
+
If `fitness` raises or returns NaN, that genome scores `-inf` and the run
|
|
133
|
+
continues. `result.n_failed_evaluations` counts them. Pass
|
|
134
|
+
`raise_errors=True` to stop on the first error instead.
|
|
135
|
+
|
|
136
|
+
## Use it from Optuna (and freqtrade)
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
import optuna
|
|
140
|
+
from strategy_genome.optuna import GeneticStrategySampler
|
|
141
|
+
|
|
142
|
+
study = optuna.create_study(sampler=GeneticStrategySampler(population_size=30, seed=1))
|
|
143
|
+
study.optimize(objective, n_trials=500)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
freqtrade's hyperopt runs on Optuna and lets a strategy return any
|
|
147
|
+
`optuna.samplers.BaseSampler` from `HyperOpt.generate_estimator`, so the
|
|
148
|
+
sampler drops straight in. See [Optuna and freqtrade](https://github.com/Mattbusel/strategy-genome/blob/main/docs/optuna-and-freqtrade.md),
|
|
149
|
+
including a benchmark against Random, NSGA-II and TPE.
|
|
150
|
+
|
|
151
|
+
## More
|
|
152
|
+
|
|
153
|
+
- [API reference](https://github.com/Mattbusel/strategy-genome/blob/main/docs/api.md)
|
|
154
|
+
- [Optuna and freqtrade](https://github.com/Mattbusel/strategy-genome/blob/main/docs/optuna-and-freqtrade.md)
|
|
155
|
+
- [Genetic programming (`strategy_genome.gp`)](https://github.com/Mattbusel/strategy-genome/blob/main/docs/gp.md)
|
|
156
|
+
- [Trading examples (`strategy_genome.trading`)](https://github.com/Mattbusel/strategy-genome/blob/main/docs/trading-examples.md)
|
|
157
|
+
- [Provenance and upstream fixes](https://github.com/Mattbusel/strategy-genome/blob/main/docs/provenance.md)
|
|
158
|
+
- [Ecosystem plan (OptunaHub)](https://github.com/Mattbusel/strategy-genome/blob/main/docs/ecosystem.md)
|
|
159
|
+
- Examples: [`examples/`](https://github.com/Mattbusel/strategy-genome/tree/main/examples)
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT. Copyright (c) 2026 Matthew Busel. See `LICENSE` and `NOTICE`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Matthew Busel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# API reference
|
|
2
|
+
|
|
3
|
+
Everything below is importable from `strategy_genome` unless a submodule is
|
|
4
|
+
named.
|
|
5
|
+
|
|
6
|
+
## `evolve`
|
|
7
|
+
|
|
8
|
+
```python
|
|
9
|
+
evolve(
|
|
10
|
+
fitness, # fn(genome) -> float | tuple[float, ...]
|
|
11
|
+
space, # {name: ParamRange} | shorthand dict | [ParamRange]
|
|
12
|
+
population=50,
|
|
13
|
+
generations=100,
|
|
14
|
+
seed=None,
|
|
15
|
+
*,
|
|
16
|
+
direction=None, # "maximize" (default) | "minimize"
|
|
17
|
+
directions=None, # one per objective, for multi-objective runs
|
|
18
|
+
initial=None, # list of param dicts to seed generation 0
|
|
19
|
+
n_jobs=1, # threads for fitness evaluation
|
|
20
|
+
callback=None, # callback(gen, population, stats); return False to stop
|
|
21
|
+
raise_errors=False,
|
|
22
|
+
verbose=False,
|
|
23
|
+
**config, # any EvolutionConfig field
|
|
24
|
+
) -> EvolutionResult
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Useful `**config` fields (see `EvolutionConfig` for all of them):
|
|
28
|
+
|
|
29
|
+
| field | default | meaning |
|
|
30
|
+
|---|---|---|
|
|
31
|
+
| `elite_fraction` | 0.10 | share of the population copied unchanged each generation |
|
|
32
|
+
| `tournament_size` | 5 | tournament size for parent selection |
|
|
33
|
+
| `crossover_rate` | 0.80 | starting crossover probability (cosine-annealed) |
|
|
34
|
+
| `initial_mutation_rate` | 0.10 | starting per-gene mutation probability |
|
|
35
|
+
| `use_adaptive_mutation` | True | adapt the mutation rate to progress and diversity |
|
|
36
|
+
| `use_adaptive_operators` | True | sample crossover/mutation operators by weight |
|
|
37
|
+
| `mutation_method` / `crossover_method` | gaussian / uniform | used when adaptive operators are off |
|
|
38
|
+
| `use_island_model`, `n_islands`, `migration_interval` | False, 4, 20 | island model (single objective) |
|
|
39
|
+
| `stagnation_patience`, `max_restarts`, `restart_method` | 30, 5, partial | restarts when progress stalls |
|
|
40
|
+
| `convergence_patience` | 50 | generations without improvement before stopping |
|
|
41
|
+
| `use_fitness_sharing`, `sharing_sigma` | False, 0.15 | niching |
|
|
42
|
+
| `checkpoint_interval`, `checkpoint_dir` | 0, ./checkpoints | write best-genome JSON every N generations (0 = off) |
|
|
43
|
+
|
|
44
|
+
## `EvolutionResult`
|
|
45
|
+
|
|
46
|
+
| attribute | meaning |
|
|
47
|
+
|---|---|
|
|
48
|
+
| `best_genome` | best genome seen (hall-of-fame head) |
|
|
49
|
+
| `best_params` | its parameters as a dict |
|
|
50
|
+
| `best_value` / `best_values` | what your fitness returned for it |
|
|
51
|
+
| `hall_of_fame` | best unique genomes, best first |
|
|
52
|
+
| `pareto_front` | non-dominated genomes (multi-objective runs) |
|
|
53
|
+
| `stats_history` | `PopulationStats` per generation |
|
|
54
|
+
| `diversity_history` | mean pairwise distance in normalized parameter space, per generation |
|
|
55
|
+
| `best_fitness_history` | best internal fitness per generation |
|
|
56
|
+
| `n_generations_run`, `n_evaluations`, `n_failed_evaluations`, `n_restarts` | counters |
|
|
57
|
+
| `converged`, `convergence_reason` | why the run stopped |
|
|
58
|
+
| `directions` | the direction of each objective |
|
|
59
|
+
| `to_dict()` | JSON-friendly summary |
|
|
60
|
+
| `summary()` | printable summary |
|
|
61
|
+
|
|
62
|
+
Internally every objective is maximized: `genome.fitness` and
|
|
63
|
+
`genome.objectives` are sign-adjusted. `genome.value` / `genome.values` are
|
|
64
|
+
always your function's raw output.
|
|
65
|
+
|
|
66
|
+
## Genomes
|
|
67
|
+
|
|
68
|
+
`Genome` (alias of `StrategyGenome`) is what your fitness function receives.
|
|
69
|
+
|
|
70
|
+
- `g["name"]`, `g.get("name", default)`, `g.params`
|
|
71
|
+
- `g.value`, `g.values`, `g.fitness`, `g.objectives`
|
|
72
|
+
- `g.clone()`, `g.mutate(rate, method=...)`, `StrategyGenome.crossover(a, b, method=...)`
|
|
73
|
+
- `g.dominates(other)` (Pareto dominance on `objectives`)
|
|
74
|
+
|
|
75
|
+
`GenomeFactory.from_space(space, seed=...)` builds random, default or
|
|
76
|
+
dict-specified genomes and serializes them to JSON-compatible dicts.
|
|
77
|
+
|
|
78
|
+
## Search space (`strategy_genome.space`)
|
|
79
|
+
|
|
80
|
+
`Float(low, high, step=None)`, `Int(low, high, step=1)`,
|
|
81
|
+
`LogFloat(low, high)`, `Categorical(choices)`, `Bool()`, and
|
|
82
|
+
`normalize_space(space)` which turns any accepted form into a list of named
|
|
83
|
+
`ParamRange`.
|
|
84
|
+
|
|
85
|
+
## Lower-level pieces
|
|
86
|
+
|
|
87
|
+
- `GeneticAlgorithm(config, factory, evaluator)`: the loop itself. The
|
|
88
|
+
evaluator is any object with `evaluate(genome) -> genome` that sets
|
|
89
|
+
`genome.fitness` (higher is better). `CallableFitness` wraps a function.
|
|
90
|
+
- `Population`, `HallOfFame`, `SelectionOperator` (tournament, roulette,
|
|
91
|
+
rank, SUS, lexicase), `FitnessSharing`, `DiversityMetrics`.
|
|
92
|
+
- `MutationOperator`, `CrossoverOperator`: the operators, usable on their own.
|
|
93
|
+
- `AdaptiveMutationRate`, `AdaptiveOperatorSelection`, `NichePreservation`
|
|
94
|
+
(deterministic crowding, clearing, restricted mating), `IslandModel`,
|
|
95
|
+
`RestartStrategy`.
|
|
96
|
+
- `ParetoAnalysis`: hypervolume (exact in 2-D, Monte Carlo above), spacing,
|
|
97
|
+
knee point.
|
|
98
|
+
- `metrics`: `compute_sharpe`, `compute_sortino`, `compute_calmar`,
|
|
99
|
+
`compute_max_drawdown`, `compute_profit_factor`, `compute_win_rate`,
|
|
100
|
+
`compute_omega_ratio`, `compute_var`, `compute_cvar`,
|
|
101
|
+
`compute_information_ratio`. Ratios with a zero denominator return capped
|
|
102
|
+
sentinels (for example 10.0) rather than infinity, as upstream did.
|
|
103
|
+
- `multi_run(fitness, space, n_runs=5, ...)`: repeat a run over seeds to see
|
|
104
|
+
whether a result is robust or a lucky seed.
|
|
105
|
+
|
|
106
|
+
## CLI
|
|
107
|
+
|
|
108
|
+
`strategy-genome demo [--strategy momentum] [--prices prices.csv] [--json out.json]`
|
|
109
|
+
runs the GA against the toy backtester so you can watch it work.
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# Ecosystem plan
|
|
2
|
+
|
|
3
|
+
Where this package can plug into existing tools, and the status of each.
|
|
4
|
+
|
|
5
|
+
## freqtrade: no merge needed
|
|
6
|
+
|
|
7
|
+
freqtrade hyperopt runs on Optuna and accepts any `BaseSampler` instance
|
|
8
|
+
from a strategy's `HyperOpt.generate_estimator`. The integration is a
|
|
9
|
+
user-side snippet, documented in [Optuna and freqtrade](optuna-and-freqtrade.md)
|
|
10
|
+
and `examples/freqtrade_strategy.py`. Verified against freqtrade's
|
|
11
|
+
`docs/advanced-hyperopt.md` and `freqtrade/optimize/hyperopt/hyperopt_optimizer.py`
|
|
12
|
+
on `develop` (release 2026.10).
|
|
13
|
+
|
|
14
|
+
## OptunaHub sampler: prepared, not submitted
|
|
15
|
+
|
|
16
|
+
OptunaHub (`optuna/optunahub-registry`) hosts community samplers under
|
|
17
|
+
`package/samplers/<name>/`. A package there is a thin wrapper that imports
|
|
18
|
+
this library. `contrib/optunahub/strategy_genome/` holds the wrapper
|
|
19
|
+
(`__init__.py`, `_sampler.py`, `example.py`, `LICENSE`, `requirements.txt`), matching the layout of existing registry packages such as `auto_sampler`.
|
|
20
|
+
|
|
21
|
+
Not submitted yet: the registry requires a `README.md` inside the package
|
|
22
|
+
directory, and that file is not written here.
|
|
23
|
+
|
|
24
|
+
## OptunaHub benchmark: proposed
|
|
25
|
+
|
|
26
|
+
OptunaHub's benchmark list has no financial problem. A walk-forward
|
|
27
|
+
strategy-overfitting benchmark (optimize on one window, score on the next,
|
|
28
|
+
report the gap) would be a new category. It would be built on this package
|
|
29
|
+
and a walk-forward splitter, and has the same `README.md` requirement.
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Genetic programming: `strategy_genome.gp`
|
|
2
|
+
|
|
3
|
+
An opt-in submodule that evolves symbolic expression trees, for example
|
|
4
|
+
candidate alpha signals built from feature arrays. Needs numpy. scipy is used
|
|
5
|
+
for Spearman rank correlation when installed, with a numpy fallback.
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
import numpy as np
|
|
9
|
+
from strategy_genome.gp import GPConfig, GPEngine, SignalValidator
|
|
10
|
+
|
|
11
|
+
rng = np.random.default_rng(0)
|
|
12
|
+
n = 500
|
|
13
|
+
data = {
|
|
14
|
+
"close": rng.standard_normal(n).cumsum() + 100.0,
|
|
15
|
+
"volume": rng.uniform(1e5, 1e7, n),
|
|
16
|
+
"mom_5d": rng.standard_normal(n),
|
|
17
|
+
"mom_20d": rng.standard_normal(n),
|
|
18
|
+
}
|
|
19
|
+
forward_returns = rng.standard_normal(n) * 0.01
|
|
20
|
+
|
|
21
|
+
engine = GPEngine(seed=1, terminal_names=list(data))
|
|
22
|
+
pareto = engine.run(data, forward_returns,
|
|
23
|
+
GPConfig(population_size=200, max_generations=20, verbosity=0))
|
|
24
|
+
|
|
25
|
+
best = pareto[0]
|
|
26
|
+
print(engine.export_best(best)["expression"], best.fitness_scores)
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
What is inside:
|
|
30
|
+
|
|
31
|
+
- `ExpressionTree`, `ExpressionNode`, `TreeGenerator`: trees over unary and
|
|
32
|
+
binary functions (arithmetic with protected division, EMA, rolling
|
|
33
|
+
statistics, ranks, lags and more; see `ALL_FUNCTION_NAMES`), simplification,
|
|
34
|
+
depth and size introspection.
|
|
35
|
+
- `gp_operators`: subtree crossover, point / subtree / hoist mutation,
|
|
36
|
+
tournament, fitness-proportional and lexicase selection.
|
|
37
|
+
- `GPEngine`: ramped half-and-half initialization, tournament + lexicase
|
|
38
|
+
parent selection, elitism, parsimony pressure, early stopping, and a Pareto
|
|
39
|
+
front over information coefficient (IC) and ICIR.
|
|
40
|
+
- `SignalValidator`: IC and ICIR checks, a look-ahead test, stability across
|
|
41
|
+
sub-periods, correlation against an existing signal library, and
|
|
42
|
+
out-of-sample validation.
|
|
43
|
+
|
|
44
|
+
Notes:
|
|
45
|
+
|
|
46
|
+
- `terminal_names` defaults to the srfm-lab signal library
|
|
47
|
+
(`ALL_TERMINALS`). Pass the keys of your own data dict, as above, or trees
|
|
48
|
+
will reference names you do not have (those evaluate to 0).
|
|
49
|
+
- `GPEngine(seed=...)` seeds Python's and numpy's global RNGs, as upstream
|
|
50
|
+
did.
|
|
51
|
+
- The 108 upstream tests for this subsystem ship unchanged in
|
|
52
|
+
`tests/test_gp.py` and all pass.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# strategy-genome
|
|
2
|
+
|
|
3
|
+
A typed genetic algorithm (GA) and genetic programming (GP) optimizer for
|
|
4
|
+
Python, with an Optuna sampler so any Optuna study can use it.
|
|
5
|
+
|
|
6
|
+
- **You bring the fitness function.** `evolve(fitness, space)` calls
|
|
7
|
+
`fitness(genome)` and expects a float, or a tuple of floats for
|
|
8
|
+
multi-objective runs. Nothing about trading is built in.
|
|
9
|
+
- **Mixed search spaces:** floats (optionally on a step grid), integers,
|
|
10
|
+
log-scale floats, categoricals and booleans.
|
|
11
|
+
- **Real GA machinery:** elitism, tournament selection, SBX / arithmetic /
|
|
12
|
+
uniform / one- and two-point crossover, gaussian / polynomial / creep /
|
|
13
|
+
boundary mutation, adaptive mutation rate and adaptive operator choice,
|
|
14
|
+
NSGA-II for multiple objectives, an island model, stagnation restarts, and a
|
|
15
|
+
hall of fame.
|
|
16
|
+
- **Reproducible:** the same seed and the same fitness give the same run.
|
|
17
|
+
- **Light:** the GA core is pure standard-library Python. numpy is used by
|
|
18
|
+
the GP submodule and the trading examples. Optuna is an optional extra.
|
|
19
|
+
|
|
20
|
+
Carved out of [srfm-lab](https://github.com/Mattbusel/srfm-lab)
|
|
21
|
+
(`ml/genetic`), MIT licensed. See [provenance](https://github.com/Mattbusel/strategy-genome/blob/main/docs/provenance.md) for what was
|
|
22
|
+
changed and which upstream bugs were fixed on the way out.
|
|
23
|
+
|
|
24
|
+
## Install
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
pip install strategy-genome # GA core + GP (numpy)
|
|
28
|
+
pip install "strategy-genome[optuna]" # adds the Optuna sampler
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Python 3.10 or newer.
|
|
32
|
+
|
|
33
|
+
## Quickstart
|
|
34
|
+
|
|
35
|
+
```python
|
|
36
|
+
from strategy_genome import evolve, space as sp
|
|
37
|
+
|
|
38
|
+
def fitness(g):
|
|
39
|
+
# g["name"] reads one parameter; g.params is the whole dict
|
|
40
|
+
return (g["x"] - 3.0) ** 2 + (g["y"] + 1.0) ** 2
|
|
41
|
+
|
|
42
|
+
result = evolve(
|
|
43
|
+
fitness,
|
|
44
|
+
{"x": sp.Float(-10, 10), "y": sp.Float(-10, 10)},
|
|
45
|
+
population=40,
|
|
46
|
+
generations=80,
|
|
47
|
+
seed=7,
|
|
48
|
+
direction="minimize",
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
result.best_params # {'x': 3.00..., 'y': -0.99...}
|
|
52
|
+
result.best_value # what your function returned for the best genome
|
|
53
|
+
result.hall_of_fame # best unique genomes seen, best first
|
|
54
|
+
result.diversity_history # mean pairwise parameter distance per generation
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Search spaces
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
space = {
|
|
61
|
+
"fast": sp.Int(2, 50),
|
|
62
|
+
"slow": sp.Int(10, 200, step=5),
|
|
63
|
+
"stop": sp.Float(0.005, 0.10),
|
|
64
|
+
"lr": sp.LogFloat(1e-5, 1e-1),
|
|
65
|
+
"kind": sp.Categorical(["ema", "sma", "wma"]),
|
|
66
|
+
"short": sp.Bool(),
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Shorthand also works: `(0, 10)` is an integer range, `(0.0, 1.0)` a float
|
|
71
|
+
range, and a list is a categorical.
|
|
72
|
+
|
|
73
|
+
### Several objectives
|
|
74
|
+
|
|
75
|
+
Return a tuple and give one direction per objective. The run switches to
|
|
76
|
+
NSGA-II and `result.pareto_front` holds the non-dominated genomes.
|
|
77
|
+
|
|
78
|
+
```python
|
|
79
|
+
def fitness(g):
|
|
80
|
+
returns = backtest(g.params)
|
|
81
|
+
return sharpe(returns), max_drawdown(returns)
|
|
82
|
+
|
|
83
|
+
result = evolve(fitness, space, population=60, generations=100, seed=1,
|
|
84
|
+
directions=["maximize", "minimize"])
|
|
85
|
+
for g in result.pareto_front:
|
|
86
|
+
print(g.values, g.params)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Failures
|
|
90
|
+
|
|
91
|
+
If `fitness` raises or returns NaN, that genome scores `-inf` and the run
|
|
92
|
+
continues. `result.n_failed_evaluations` counts them. Pass
|
|
93
|
+
`raise_errors=True` to stop on the first error instead.
|
|
94
|
+
|
|
95
|
+
## Use it from Optuna (and freqtrade)
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import optuna
|
|
99
|
+
from strategy_genome.optuna import GeneticStrategySampler
|
|
100
|
+
|
|
101
|
+
study = optuna.create_study(sampler=GeneticStrategySampler(population_size=30, seed=1))
|
|
102
|
+
study.optimize(objective, n_trials=500)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
freqtrade's hyperopt runs on Optuna and lets a strategy return any
|
|
106
|
+
`optuna.samplers.BaseSampler` from `HyperOpt.generate_estimator`, so the
|
|
107
|
+
sampler drops straight in. See [Optuna and freqtrade](https://github.com/Mattbusel/strategy-genome/blob/main/docs/optuna-and-freqtrade.md),
|
|
108
|
+
including a benchmark against Random, NSGA-II and TPE.
|
|
109
|
+
|
|
110
|
+
## More
|
|
111
|
+
|
|
112
|
+
- [API reference](https://github.com/Mattbusel/strategy-genome/blob/main/docs/api.md)
|
|
113
|
+
- [Optuna and freqtrade](https://github.com/Mattbusel/strategy-genome/blob/main/docs/optuna-and-freqtrade.md)
|
|
114
|
+
- [Genetic programming (`strategy_genome.gp`)](https://github.com/Mattbusel/strategy-genome/blob/main/docs/gp.md)
|
|
115
|
+
- [Trading examples (`strategy_genome.trading`)](https://github.com/Mattbusel/strategy-genome/blob/main/docs/trading-examples.md)
|
|
116
|
+
- [Provenance and upstream fixes](https://github.com/Mattbusel/strategy-genome/blob/main/docs/provenance.md)
|
|
117
|
+
- [Ecosystem plan (OptunaHub)](https://github.com/Mattbusel/strategy-genome/blob/main/docs/ecosystem.md)
|
|
118
|
+
- Examples: [`examples/`](https://github.com/Mattbusel/strategy-genome/tree/main/examples)
|
|
119
|
+
|
|
120
|
+
## License
|
|
121
|
+
|
|
122
|
+
MIT. Copyright (c) 2026 Matthew Busel. See `LICENSE` and `NOTICE`.
|