trade-study 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.
- trade_study-0.1.0/LICENSE +21 -0
- trade_study-0.1.0/PKG-INFO +279 -0
- trade_study-0.1.0/README.md +218 -0
- trade_study-0.1.0/pyproject.toml +132 -0
- trade_study-0.1.0/setup.cfg +4 -0
- trade_study-0.1.0/src/trade_study/__init__.py +54 -0
- trade_study-0.1.0/src/trade_study/_pareto.py +128 -0
- trade_study-0.1.0/src/trade_study/_scoring.py +213 -0
- trade_study-0.1.0/src/trade_study/_version.py +1 -0
- trade_study-0.1.0/src/trade_study/design.py +309 -0
- trade_study-0.1.0/src/trade_study/io.py +67 -0
- trade_study-0.1.0/src/trade_study/protocols.py +130 -0
- trade_study-0.1.0/src/trade_study/py.typed +0 -0
- trade_study-0.1.0/src/trade_study/runner.py +170 -0
- trade_study-0.1.0/src/trade_study/stacking.py +100 -0
- trade_study-0.1.0/src/trade_study/study.py +211 -0
- trade_study-0.1.0/src/trade_study.egg-info/PKG-INFO +279 -0
- trade_study-0.1.0/src/trade_study.egg-info/SOURCES.txt +29 -0
- trade_study-0.1.0/src/trade_study.egg-info/dependency_links.txt +1 -0
- trade_study-0.1.0/src/trade_study.egg-info/requires.txt +48 -0
- trade_study-0.1.0/src/trade_study.egg-info/top_level.txt +1 -0
- trade_study-0.1.0/tests/test_api.py +67 -0
- trade_study-0.1.0/tests/test_design.py +374 -0
- trade_study-0.1.0/tests/test_io.py +208 -0
- trade_study-0.1.0/tests/test_pareto.py +275 -0
- trade_study-0.1.0/tests/test_protocols.py +300 -0
- trade_study-0.1.0/tests/test_runner.py +322 -0
- trade_study-0.1.0/tests/test_scoring.py +170 -0
- trade_study-0.1.0/tests/test_smoke.py +8 -0
- trade_study-0.1.0/tests/test_stacking.py +179 -0
- trade_study-0.1.0/tests/test_study.py +528 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Joshua C. Macdonald
|
|
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,279 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: trade-study
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Multi-objective trade-study orchestration: scoring, Pareto optimization, and Bayesian stacking
|
|
5
|
+
Author-email: "Joshua C. Macdonald" <jmacdo16@jh.edu>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/jcm-sci/trade-study
|
|
8
|
+
Project-URL: Documentation, https://jcm-sci.github.io/trade-study/
|
|
9
|
+
Project-URL: Repository, https://github.com/jcm-sci/trade-study
|
|
10
|
+
Project-URL: Changelog, https://github.com/jcm-sci/trade-study/blob/main/CHANGELOG.md
|
|
11
|
+
Project-URL: Issues, https://github.com/jcm-sci/trade-study/issues
|
|
12
|
+
Keywords: trade-study,model-selection,scoring-rules,pareto,bayesian-stacking,design-of-experiments,sensitivity-analysis,multi-objective-optimization
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Intended Audience :: Science/Research
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering
|
|
23
|
+
Classifier: Typing :: Typed
|
|
24
|
+
Requires-Python: >=3.10
|
|
25
|
+
Description-Content-Type: text/markdown
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: numpy>=1.24
|
|
28
|
+
Provides-Extra: scoring
|
|
29
|
+
Requires-Dist: scoringrules>=0.8; extra == "scoring"
|
|
30
|
+
Provides-Extra: pareto
|
|
31
|
+
Requires-Dist: pymoo>=0.6; extra == "pareto"
|
|
32
|
+
Provides-Extra: stacking
|
|
33
|
+
Requires-Dist: arviz>=0.20; python_version < "3.12" and extra == "stacking"
|
|
34
|
+
Requires-Dist: arviz>=1.0; python_version >= "3.12" and extra == "stacking"
|
|
35
|
+
Requires-Dist: scipy>=1.10; extra == "stacking"
|
|
36
|
+
Provides-Extra: design
|
|
37
|
+
Requires-Dist: pyDOE3>=1.5; extra == "design"
|
|
38
|
+
Requires-Dist: SALib>=1.5; extra == "design"
|
|
39
|
+
Requires-Dist: scipy>=1.10; extra == "design"
|
|
40
|
+
Provides-Extra: adaptive
|
|
41
|
+
Requires-Dist: optuna>=4.0; extra == "adaptive"
|
|
42
|
+
Provides-Extra: parallel
|
|
43
|
+
Requires-Dist: joblib>=1.3; extra == "parallel"
|
|
44
|
+
Provides-Extra: all
|
|
45
|
+
Requires-Dist: trade-study[adaptive,design,parallel,pareto,scoring,stacking]; extra == "all"
|
|
46
|
+
Provides-Extra: examples
|
|
47
|
+
Requires-Dist: scikit-learn>=1.3; extra == "examples"
|
|
48
|
+
Requires-Dist: trade-study[all]; extra == "examples"
|
|
49
|
+
Provides-Extra: test
|
|
50
|
+
Requires-Dist: pytest>=8.1; extra == "test"
|
|
51
|
+
Requires-Dist: pytest-cov>=6.0; extra == "test"
|
|
52
|
+
Requires-Dist: trade-study[all]; extra == "test"
|
|
53
|
+
Provides-Extra: docs
|
|
54
|
+
Requires-Dist: mkdocs-material>=9.5; extra == "docs"
|
|
55
|
+
Requires-Dist: mkdocstrings[python]>=0.27; extra == "docs"
|
|
56
|
+
Provides-Extra: dev
|
|
57
|
+
Requires-Dist: mypy>=1.18; extra == "dev"
|
|
58
|
+
Requires-Dist: ruff>=0.13; extra == "dev"
|
|
59
|
+
Requires-Dist: trade-study[docs,test]; extra == "dev"
|
|
60
|
+
Dynamic: license-file
|
|
61
|
+
|
|
62
|
+
# trade-study
|
|
63
|
+
|
|
64
|
+
[](https://github.com/jcm-sci/trade-study/actions/workflows/ci.yml)
|
|
65
|
+
[](https://opensource.org/licenses/MIT)
|
|
66
|
+
[](https://www.python.org/downloads/)
|
|
67
|
+
[](https://github.com/astral-sh/ruff)
|
|
68
|
+
[](https://jcmacdonald.dev/software/)
|
|
69
|
+
|
|
70
|
+
Multi-objective trade-study orchestration: define factors, build
|
|
71
|
+
parameter grids, run hierarchical study phases, and extract Pareto
|
|
72
|
+
fronts — for any domain where you compare alternatives against
|
|
73
|
+
competing objectives.
|
|
74
|
+
|
|
75
|
+
## Statement of need
|
|
76
|
+
|
|
77
|
+
Comparing design alternatives against multiple objectives is a common
|
|
78
|
+
task across scientific simulation, engineering trade-offs, and ML
|
|
79
|
+
hyperparameter tuning. Researchers typically glue together separate
|
|
80
|
+
tools for grid construction, execution, scoring, and Pareto analysis,
|
|
81
|
+
writing ad-hoc scripts that are hard to reproduce or extend to
|
|
82
|
+
multi-phase studies (screening → refinement → benchmark).
|
|
83
|
+
|
|
84
|
+
`trade-study` provides a single orchestration layer that composes these
|
|
85
|
+
steps into a reproducible, protocol-driven workflow. Users supply a
|
|
86
|
+
`Simulator` (generates data) and a `Scorer` (evaluates it); the
|
|
87
|
+
framework handles grid construction, parallel execution, Pareto
|
|
88
|
+
extraction, and phase chaining. All components are modular and
|
|
89
|
+
optional — use only what you need.
|
|
90
|
+
|
|
91
|
+
This package targets researchers and practitioners who need:
|
|
92
|
+
|
|
93
|
+
- structured multi-phase experimental design (screen → refine → benchmark),
|
|
94
|
+
- multi-objective Pareto analysis across heterogeneous factors, and
|
|
95
|
+
- a reproducible Python API that separates domain logic from study orchestration.
|
|
96
|
+
|
|
97
|
+
## Why trade-study?
|
|
98
|
+
|
|
99
|
+
| Need | Without trade-study | With trade-study |
|
|
100
|
+
|------|---------------------|------------------|
|
|
101
|
+
| Parameter grid | Manual `itertools.product` or one-off scripts | `build_grid(factors, method="sobol")` — full factorial, LHS, Sobol, Halton |
|
|
102
|
+
| Multi-objective ranking | Call pymoo directly, handle direction normalization | `extract_front(scores, directions)` — direction-aware |
|
|
103
|
+
| Phased studies | Custom loop with manual filtering between stages | `Study(phases=[Phase(..., filter_fn=top_k_pareto_filter(k=20)), ...])` |
|
|
104
|
+
| Adaptive search | Set up optuna study from scratch | `run_adaptive(world, scorer, factors, observables, n_trials=600)` |
|
|
105
|
+
| Reproducibility | Scattered scripts, no standard protocol | `Simulator` / `Scorer` protocols + `save_results()` / `load_results()` |
|
|
106
|
+
|
|
107
|
+
Existing tools solve pieces of this problem — [optuna](https://optuna.org/) for adaptive optimization, [pymoo](https://pymoo.org/) for multi-objective solvers, [SALib](https://salib.readthedocs.io/) for sensitivity analysis — but none provide the **hierarchical phase orchestration** that connects them into a single study.
|
|
108
|
+
|
|
109
|
+
## Quick start
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
from trade_study import (
|
|
113
|
+
Direction,
|
|
114
|
+
Factor,
|
|
115
|
+
FactorType,
|
|
116
|
+
Observable,
|
|
117
|
+
Phase,
|
|
118
|
+
Study,
|
|
119
|
+
build_grid,
|
|
120
|
+
top_k_pareto_filter,
|
|
121
|
+
)
|
|
122
|
+
|
|
123
|
+
# 1. Define objectives
|
|
124
|
+
accuracy = Observable("accuracy", Direction.MAXIMIZE)
|
|
125
|
+
latency = Observable("latency_ms", Direction.MINIMIZE)
|
|
126
|
+
cost = Observable("cost_usd", Direction.MINIMIZE)
|
|
127
|
+
|
|
128
|
+
# 2. Define factors and build a design grid
|
|
129
|
+
factors = [
|
|
130
|
+
Factor("learning_rate", FactorType.CONTINUOUS, bounds=(1e-4, 1e-1)),
|
|
131
|
+
Factor("backend", FactorType.CATEGORICAL, levels=["A", "B", "C"]),
|
|
132
|
+
]
|
|
133
|
+
grid = build_grid(factors, method="lhs", n_samples=500)
|
|
134
|
+
|
|
135
|
+
# 3. Run a hierarchical study
|
|
136
|
+
study = Study(
|
|
137
|
+
world=MySimulator(), # implements Simulator protocol
|
|
138
|
+
scorer=MyScorer(), # implements Scorer protocol
|
|
139
|
+
observables=[accuracy, latency, cost],
|
|
140
|
+
phases=[
|
|
141
|
+
Phase(
|
|
142
|
+
"screening",
|
|
143
|
+
grid=grid,
|
|
144
|
+
filter_fn=top_k_pareto_filter(k=20),
|
|
145
|
+
),
|
|
146
|
+
Phase("benchmark", grid="carry", filter_fn=None),
|
|
147
|
+
],
|
|
148
|
+
)
|
|
149
|
+
study.run(n_jobs=-1)
|
|
150
|
+
|
|
151
|
+
# 4. Inspect results
|
|
152
|
+
print(study.summary())
|
|
153
|
+
front = study.front() # non-dominated configs
|
|
154
|
+
hv = study.front_hypervolume(ref=...) # hypervolume indicator
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Protocols
|
|
158
|
+
|
|
159
|
+
Users implement two protocols to plug in their domain:
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
from trade_study import Scorer, Simulator
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
class MySimulator:
|
|
166
|
+
"""Implements the Simulator protocol."""
|
|
167
|
+
|
|
168
|
+
def generate(self, config: dict) -> tuple:
|
|
169
|
+
"""Return (truth, observations) for a given config."""
|
|
170
|
+
...
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
class MyScorer:
|
|
174
|
+
"""Implements the Scorer protocol."""
|
|
175
|
+
|
|
176
|
+
def score(self, truth, observations, config: dict) -> dict[str, float]:
|
|
177
|
+
"""Return {observable_name: value} for a single trial."""
|
|
178
|
+
...
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Installation
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pip install trade-study[all]
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Or install only the extras you need:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
pip install trade-study[design,pareto]
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Extra | Packages | Purpose |
|
|
194
|
+
|-------|----------|---------|
|
|
195
|
+
| `design` | [pyDOE3](https://github.com/relf/pyDOE3), [SALib](https://github.com/SALib/SALib), [scipy](https://scipy.org/) | Grid construction and sensitivity screening |
|
|
196
|
+
| `pareto` | [pymoo](https://pymoo.org/) | Non-dominated sorting and indicators |
|
|
197
|
+
| `scoring` | [scoringrules](https://github.com/frazane/scoringrules) | Proper scoring rules (CRPS, WIS, etc.) |
|
|
198
|
+
| `stacking` | [arviz](https://github.com/arviz-devs/arviz), scipy | Bayesian and score-based ensemble weights |
|
|
199
|
+
| `adaptive` | [optuna](https://optuna.org/) | Multi-objective Bayesian optimization |
|
|
200
|
+
| `parallel` | joblib | Parallel grid execution |
|
|
201
|
+
| `all` | All of the above | |
|
|
202
|
+
|
|
203
|
+
**Core dependency**: numpy only.
|
|
204
|
+
|
|
205
|
+
## API overview
|
|
206
|
+
|
|
207
|
+
### Design
|
|
208
|
+
|
|
209
|
+
```python
|
|
210
|
+
from trade_study import Factor, FactorType, build_grid, screen
|
|
211
|
+
|
|
212
|
+
factors = [
|
|
213
|
+
Factor("x", FactorType.CONTINUOUS, bounds=(0.0, 1.0)),
|
|
214
|
+
Factor("method", FactorType.CATEGORICAL, levels=["a", "b", "c"]),
|
|
215
|
+
]
|
|
216
|
+
|
|
217
|
+
grid = build_grid(factors, method="sobol", n_samples=1024)
|
|
218
|
+
si = screen(run_fn, factors, method="morris", n_eval=3000)
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### Execution
|
|
222
|
+
|
|
223
|
+
```python
|
|
224
|
+
from trade_study import run_grid, run_adaptive
|
|
225
|
+
|
|
226
|
+
# Grid mode: evaluate all configs (optional parallelism)
|
|
227
|
+
results = run_grid(world, scorer, grid, observables, n_jobs=-1)
|
|
228
|
+
|
|
229
|
+
# Adaptive mode: multi-objective Bayesian optimization (NSGA-II)
|
|
230
|
+
results = run_adaptive(world, scorer, factors, observables, n_trials=600)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Pareto analysis
|
|
234
|
+
|
|
235
|
+
```python
|
|
236
|
+
from trade_study import extract_front, hypervolume, pareto_rank
|
|
237
|
+
|
|
238
|
+
front_mask = extract_front(results.scores, directions)
|
|
239
|
+
ranks = pareto_rank(results.scores, directions)
|
|
240
|
+
hv = hypervolume(results.scores[front_mask], directions, ref=ref_point)
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Stacking
|
|
244
|
+
|
|
245
|
+
```python
|
|
246
|
+
from trade_study import stack_scores, stack_bayesian, ensemble_predict
|
|
247
|
+
|
|
248
|
+
weights = stack_scores(score_matrix) # simplex-constrained optimization
|
|
249
|
+
weights = stack_bayesian(idata_dict) # arviz stacking (Yao et al. 2018)
|
|
250
|
+
combined = ensemble_predict(predictions, weights)
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
### I/O
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
from trade_study import save_results, load_results
|
|
257
|
+
|
|
258
|
+
save_results(results, "study_results")
|
|
259
|
+
results = load_results("study_results")
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Related packages
|
|
263
|
+
|
|
264
|
+
| Package | Description |
|
|
265
|
+
|---------|-------------|
|
|
266
|
+
| [TradeStudy.jl](https://github.com/jcm-sci/TradeStudy.jl) | Julia implementation of the same framework |
|
|
267
|
+
|
|
268
|
+
## Development
|
|
269
|
+
|
|
270
|
+
```bash
|
|
271
|
+
uv sync --extra dev
|
|
272
|
+
just ci # lint → mypy --strict → pytest with coverage
|
|
273
|
+
just format # auto-format
|
|
274
|
+
just check # auto-fix lint
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
## License
|
|
278
|
+
|
|
279
|
+
MIT
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
# trade-study
|
|
2
|
+
|
|
3
|
+
[](https://github.com/jcm-sci/trade-study/actions/workflows/ci.yml)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
[](https://www.python.org/downloads/)
|
|
6
|
+
[](https://github.com/astral-sh/ruff)
|
|
7
|
+
[](https://jcmacdonald.dev/software/)
|
|
8
|
+
|
|
9
|
+
Multi-objective trade-study orchestration: define factors, build
|
|
10
|
+
parameter grids, run hierarchical study phases, and extract Pareto
|
|
11
|
+
fronts — for any domain where you compare alternatives against
|
|
12
|
+
competing objectives.
|
|
13
|
+
|
|
14
|
+
## Statement of need
|
|
15
|
+
|
|
16
|
+
Comparing design alternatives against multiple objectives is a common
|
|
17
|
+
task across scientific simulation, engineering trade-offs, and ML
|
|
18
|
+
hyperparameter tuning. Researchers typically glue together separate
|
|
19
|
+
tools for grid construction, execution, scoring, and Pareto analysis,
|
|
20
|
+
writing ad-hoc scripts that are hard to reproduce or extend to
|
|
21
|
+
multi-phase studies (screening → refinement → benchmark).
|
|
22
|
+
|
|
23
|
+
`trade-study` provides a single orchestration layer that composes these
|
|
24
|
+
steps into a reproducible, protocol-driven workflow. Users supply a
|
|
25
|
+
`Simulator` (generates data) and a `Scorer` (evaluates it); the
|
|
26
|
+
framework handles grid construction, parallel execution, Pareto
|
|
27
|
+
extraction, and phase chaining. All components are modular and
|
|
28
|
+
optional — use only what you need.
|
|
29
|
+
|
|
30
|
+
This package targets researchers and practitioners who need:
|
|
31
|
+
|
|
32
|
+
- structured multi-phase experimental design (screen → refine → benchmark),
|
|
33
|
+
- multi-objective Pareto analysis across heterogeneous factors, and
|
|
34
|
+
- a reproducible Python API that separates domain logic from study orchestration.
|
|
35
|
+
|
|
36
|
+
## Why trade-study?
|
|
37
|
+
|
|
38
|
+
| Need | Without trade-study | With trade-study |
|
|
39
|
+
|------|---------------------|------------------|
|
|
40
|
+
| Parameter grid | Manual `itertools.product` or one-off scripts | `build_grid(factors, method="sobol")` — full factorial, LHS, Sobol, Halton |
|
|
41
|
+
| Multi-objective ranking | Call pymoo directly, handle direction normalization | `extract_front(scores, directions)` — direction-aware |
|
|
42
|
+
| Phased studies | Custom loop with manual filtering between stages | `Study(phases=[Phase(..., filter_fn=top_k_pareto_filter(k=20)), ...])` |
|
|
43
|
+
| Adaptive search | Set up optuna study from scratch | `run_adaptive(world, scorer, factors, observables, n_trials=600)` |
|
|
44
|
+
| Reproducibility | Scattered scripts, no standard protocol | `Simulator` / `Scorer` protocols + `save_results()` / `load_results()` |
|
|
45
|
+
|
|
46
|
+
Existing tools solve pieces of this problem — [optuna](https://optuna.org/) for adaptive optimization, [pymoo](https://pymoo.org/) for multi-objective solvers, [SALib](https://salib.readthedocs.io/) for sensitivity analysis — but none provide the **hierarchical phase orchestration** that connects them into a single study.
|
|
47
|
+
|
|
48
|
+
## Quick start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from trade_study import (
|
|
52
|
+
Direction,
|
|
53
|
+
Factor,
|
|
54
|
+
FactorType,
|
|
55
|
+
Observable,
|
|
56
|
+
Phase,
|
|
57
|
+
Study,
|
|
58
|
+
build_grid,
|
|
59
|
+
top_k_pareto_filter,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
# 1. Define objectives
|
|
63
|
+
accuracy = Observable("accuracy", Direction.MAXIMIZE)
|
|
64
|
+
latency = Observable("latency_ms", Direction.MINIMIZE)
|
|
65
|
+
cost = Observable("cost_usd", Direction.MINIMIZE)
|
|
66
|
+
|
|
67
|
+
# 2. Define factors and build a design grid
|
|
68
|
+
factors = [
|
|
69
|
+
Factor("learning_rate", FactorType.CONTINUOUS, bounds=(1e-4, 1e-1)),
|
|
70
|
+
Factor("backend", FactorType.CATEGORICAL, levels=["A", "B", "C"]),
|
|
71
|
+
]
|
|
72
|
+
grid = build_grid(factors, method="lhs", n_samples=500)
|
|
73
|
+
|
|
74
|
+
# 3. Run a hierarchical study
|
|
75
|
+
study = Study(
|
|
76
|
+
world=MySimulator(), # implements Simulator protocol
|
|
77
|
+
scorer=MyScorer(), # implements Scorer protocol
|
|
78
|
+
observables=[accuracy, latency, cost],
|
|
79
|
+
phases=[
|
|
80
|
+
Phase(
|
|
81
|
+
"screening",
|
|
82
|
+
grid=grid,
|
|
83
|
+
filter_fn=top_k_pareto_filter(k=20),
|
|
84
|
+
),
|
|
85
|
+
Phase("benchmark", grid="carry", filter_fn=None),
|
|
86
|
+
],
|
|
87
|
+
)
|
|
88
|
+
study.run(n_jobs=-1)
|
|
89
|
+
|
|
90
|
+
# 4. Inspect results
|
|
91
|
+
print(study.summary())
|
|
92
|
+
front = study.front() # non-dominated configs
|
|
93
|
+
hv = study.front_hypervolume(ref=...) # hypervolume indicator
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Protocols
|
|
97
|
+
|
|
98
|
+
Users implement two protocols to plug in their domain:
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
from trade_study import Scorer, Simulator
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
class MySimulator:
|
|
105
|
+
"""Implements the Simulator protocol."""
|
|
106
|
+
|
|
107
|
+
def generate(self, config: dict) -> tuple:
|
|
108
|
+
"""Return (truth, observations) for a given config."""
|
|
109
|
+
...
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class MyScorer:
|
|
113
|
+
"""Implements the Scorer protocol."""
|
|
114
|
+
|
|
115
|
+
def score(self, truth, observations, config: dict) -> dict[str, float]:
|
|
116
|
+
"""Return {observable_name: value} for a single trial."""
|
|
117
|
+
...
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
## Installation
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
pip install trade-study[all]
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Or install only the extras you need:
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
pip install trade-study[design,pareto]
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
| Extra | Packages | Purpose |
|
|
133
|
+
|-------|----------|---------|
|
|
134
|
+
| `design` | [pyDOE3](https://github.com/relf/pyDOE3), [SALib](https://github.com/SALib/SALib), [scipy](https://scipy.org/) | Grid construction and sensitivity screening |
|
|
135
|
+
| `pareto` | [pymoo](https://pymoo.org/) | Non-dominated sorting and indicators |
|
|
136
|
+
| `scoring` | [scoringrules](https://github.com/frazane/scoringrules) | Proper scoring rules (CRPS, WIS, etc.) |
|
|
137
|
+
| `stacking` | [arviz](https://github.com/arviz-devs/arviz), scipy | Bayesian and score-based ensemble weights |
|
|
138
|
+
| `adaptive` | [optuna](https://optuna.org/) | Multi-objective Bayesian optimization |
|
|
139
|
+
| `parallel` | joblib | Parallel grid execution |
|
|
140
|
+
| `all` | All of the above | |
|
|
141
|
+
|
|
142
|
+
**Core dependency**: numpy only.
|
|
143
|
+
|
|
144
|
+
## API overview
|
|
145
|
+
|
|
146
|
+
### Design
|
|
147
|
+
|
|
148
|
+
```python
|
|
149
|
+
from trade_study import Factor, FactorType, build_grid, screen
|
|
150
|
+
|
|
151
|
+
factors = [
|
|
152
|
+
Factor("x", FactorType.CONTINUOUS, bounds=(0.0, 1.0)),
|
|
153
|
+
Factor("method", FactorType.CATEGORICAL, levels=["a", "b", "c"]),
|
|
154
|
+
]
|
|
155
|
+
|
|
156
|
+
grid = build_grid(factors, method="sobol", n_samples=1024)
|
|
157
|
+
si = screen(run_fn, factors, method="morris", n_eval=3000)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Execution
|
|
161
|
+
|
|
162
|
+
```python
|
|
163
|
+
from trade_study import run_grid, run_adaptive
|
|
164
|
+
|
|
165
|
+
# Grid mode: evaluate all configs (optional parallelism)
|
|
166
|
+
results = run_grid(world, scorer, grid, observables, n_jobs=-1)
|
|
167
|
+
|
|
168
|
+
# Adaptive mode: multi-objective Bayesian optimization (NSGA-II)
|
|
169
|
+
results = run_adaptive(world, scorer, factors, observables, n_trials=600)
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Pareto analysis
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
from trade_study import extract_front, hypervolume, pareto_rank
|
|
176
|
+
|
|
177
|
+
front_mask = extract_front(results.scores, directions)
|
|
178
|
+
ranks = pareto_rank(results.scores, directions)
|
|
179
|
+
hv = hypervolume(results.scores[front_mask], directions, ref=ref_point)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Stacking
|
|
183
|
+
|
|
184
|
+
```python
|
|
185
|
+
from trade_study import stack_scores, stack_bayesian, ensemble_predict
|
|
186
|
+
|
|
187
|
+
weights = stack_scores(score_matrix) # simplex-constrained optimization
|
|
188
|
+
weights = stack_bayesian(idata_dict) # arviz stacking (Yao et al. 2018)
|
|
189
|
+
combined = ensemble_predict(predictions, weights)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### I/O
|
|
193
|
+
|
|
194
|
+
```python
|
|
195
|
+
from trade_study import save_results, load_results
|
|
196
|
+
|
|
197
|
+
save_results(results, "study_results")
|
|
198
|
+
results = load_results("study_results")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Related packages
|
|
202
|
+
|
|
203
|
+
| Package | Description |
|
|
204
|
+
|---------|-------------|
|
|
205
|
+
| [TradeStudy.jl](https://github.com/jcm-sci/TradeStudy.jl) | Julia implementation of the same framework |
|
|
206
|
+
|
|
207
|
+
## Development
|
|
208
|
+
|
|
209
|
+
```bash
|
|
210
|
+
uv sync --extra dev
|
|
211
|
+
just ci # lint → mypy --strict → pytest with coverage
|
|
212
|
+
just format # auto-format
|
|
213
|
+
just check # auto-fix lint
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## License
|
|
217
|
+
|
|
218
|
+
MIT
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=64", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "trade-study"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Multi-objective trade-study orchestration: scoring, Pareto optimization, and Bayesian stacking"
|
|
9
|
+
authors = [
|
|
10
|
+
{ name = "Joshua C. Macdonald", email = "jmacdo16@jh.edu" },
|
|
11
|
+
]
|
|
12
|
+
readme = "README.md"
|
|
13
|
+
license = "MIT"
|
|
14
|
+
license-files = ["LICENSE"]
|
|
15
|
+
requires-python = ">=3.10"
|
|
16
|
+
keywords = [
|
|
17
|
+
"trade-study",
|
|
18
|
+
"model-selection",
|
|
19
|
+
"scoring-rules",
|
|
20
|
+
"pareto",
|
|
21
|
+
"bayesian-stacking",
|
|
22
|
+
"design-of-experiments",
|
|
23
|
+
"sensitivity-analysis",
|
|
24
|
+
"multi-objective-optimization",
|
|
25
|
+
]
|
|
26
|
+
classifiers = [
|
|
27
|
+
"Development Status :: 3 - Alpha",
|
|
28
|
+
"Intended Audience :: Science/Research",
|
|
29
|
+
"Operating System :: OS Independent",
|
|
30
|
+
"Programming Language :: Python :: 3",
|
|
31
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
32
|
+
"Programming Language :: Python :: 3.10",
|
|
33
|
+
"Programming Language :: Python :: 3.11",
|
|
34
|
+
"Programming Language :: Python :: 3.12",
|
|
35
|
+
"Programming Language :: Python :: 3.13",
|
|
36
|
+
"Topic :: Scientific/Engineering",
|
|
37
|
+
"Typing :: Typed",
|
|
38
|
+
]
|
|
39
|
+
dependencies = [
|
|
40
|
+
"numpy>=1.24",
|
|
41
|
+
]
|
|
42
|
+
|
|
43
|
+
[project.urls]
|
|
44
|
+
Homepage = "https://github.com/jcm-sci/trade-study"
|
|
45
|
+
Documentation = "https://jcm-sci.github.io/trade-study/"
|
|
46
|
+
Repository = "https://github.com/jcm-sci/trade-study"
|
|
47
|
+
Changelog = "https://github.com/jcm-sci/trade-study/blob/main/CHANGELOG.md"
|
|
48
|
+
Issues = "https://github.com/jcm-sci/trade-study/issues"
|
|
49
|
+
|
|
50
|
+
[project.optional-dependencies]
|
|
51
|
+
scoring = [
|
|
52
|
+
"scoringrules>=0.8",
|
|
53
|
+
]
|
|
54
|
+
pareto = [
|
|
55
|
+
"pymoo>=0.6",
|
|
56
|
+
]
|
|
57
|
+
stacking = [
|
|
58
|
+
"arviz>=0.20; python_version < '3.12'",
|
|
59
|
+
"arviz>=1.0; python_version >= '3.12'",
|
|
60
|
+
"scipy>=1.10",
|
|
61
|
+
]
|
|
62
|
+
design = [
|
|
63
|
+
"pyDOE3>=1.5",
|
|
64
|
+
"SALib>=1.5",
|
|
65
|
+
"scipy>=1.10",
|
|
66
|
+
]
|
|
67
|
+
adaptive = [
|
|
68
|
+
"optuna>=4.0",
|
|
69
|
+
]
|
|
70
|
+
parallel = [
|
|
71
|
+
"joblib>=1.3",
|
|
72
|
+
]
|
|
73
|
+
all = [
|
|
74
|
+
"trade-study[scoring,pareto,stacking,design,adaptive,parallel]",
|
|
75
|
+
]
|
|
76
|
+
examples = [
|
|
77
|
+
"scikit-learn>=1.3",
|
|
78
|
+
"trade-study[all]",
|
|
79
|
+
]
|
|
80
|
+
test = [
|
|
81
|
+
"pytest>=8.1",
|
|
82
|
+
"pytest-cov>=6.0",
|
|
83
|
+
"trade-study[all]",
|
|
84
|
+
]
|
|
85
|
+
docs = [
|
|
86
|
+
"mkdocs-material>=9.5",
|
|
87
|
+
"mkdocstrings[python]>=0.27",
|
|
88
|
+
]
|
|
89
|
+
dev = [
|
|
90
|
+
"mypy>=1.18",
|
|
91
|
+
"ruff>=0.13",
|
|
92
|
+
"trade-study[test,docs]",
|
|
93
|
+
]
|
|
94
|
+
|
|
95
|
+
[tool.setuptools]
|
|
96
|
+
package-dir = {"" = "src"}
|
|
97
|
+
|
|
98
|
+
[tool.setuptools.packages.find]
|
|
99
|
+
where = ["src"]
|
|
100
|
+
|
|
101
|
+
[tool.ruff.format]
|
|
102
|
+
docstring-code-format = true
|
|
103
|
+
docstring-code-line-length = 72
|
|
104
|
+
|
|
105
|
+
[tool.ruff.lint]
|
|
106
|
+
select = ["ALL"]
|
|
107
|
+
ignore = [
|
|
108
|
+
"COM812",
|
|
109
|
+
"CPY001",
|
|
110
|
+
"D203",
|
|
111
|
+
"D212",
|
|
112
|
+
"PLC0415",
|
|
113
|
+
"PLR2004",
|
|
114
|
+
]
|
|
115
|
+
|
|
116
|
+
[tool.ruff.lint.per-file-ignores]
|
|
117
|
+
"tests/**/*" = ["ANN201", "ANN401", "ARG002", "D103", "INP001", "PLR6301", "S101"]
|
|
118
|
+
"examples/**/*" = ["ANN401", "ARG002", "ERA001", "INP001", "PLR6301", "T201"]
|
|
119
|
+
"src/trade_study/protocols.py" = ["ANN401"]
|
|
120
|
+
|
|
121
|
+
[tool.ruff.lint.pylint]
|
|
122
|
+
max-args = 7
|
|
123
|
+
|
|
124
|
+
[tool.ruff.lint.pydocstyle]
|
|
125
|
+
convention = "google"
|
|
126
|
+
|
|
127
|
+
[tool.mypy]
|
|
128
|
+
strict = true
|
|
129
|
+
|
|
130
|
+
[tool.pytest.ini_options]
|
|
131
|
+
testpaths = ["tests"]
|
|
132
|
+
addopts = "-q"
|