marketlab 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.
Files changed (47) hide show
  1. marketlab-0.1.0/LICENSE +21 -0
  2. marketlab-0.1.0/PKG-INFO +210 -0
  3. marketlab-0.1.0/README.md +176 -0
  4. marketlab-0.1.0/pyproject.toml +86 -0
  5. marketlab-0.1.0/setup.cfg +4 -0
  6. marketlab-0.1.0/src/marketlab/__init__.py +5 -0
  7. marketlab-0.1.0/src/marketlab/_version.py +16 -0
  8. marketlab-0.1.0/src/marketlab/backtest/__init__.py +1 -0
  9. marketlab-0.1.0/src/marketlab/backtest/engine.py +61 -0
  10. marketlab-0.1.0/src/marketlab/backtest/metrics.py +44 -0
  11. marketlab-0.1.0/src/marketlab/cli.py +76 -0
  12. marketlab-0.1.0/src/marketlab/config.py +181 -0
  13. marketlab-0.1.0/src/marketlab/data/__init__.py +1 -0
  14. marketlab-0.1.0/src/marketlab/data/market.py +99 -0
  15. marketlab-0.1.0/src/marketlab/data/panel.py +124 -0
  16. marketlab-0.1.0/src/marketlab/evaluation/__init__.py +13 -0
  17. marketlab-0.1.0/src/marketlab/evaluation/walk_forward.py +173 -0
  18. marketlab-0.1.0/src/marketlab/features/__init__.py +1 -0
  19. marketlab-0.1.0/src/marketlab/features/engineering.py +42 -0
  20. marketlab-0.1.0/src/marketlab/log.py +10 -0
  21. marketlab-0.1.0/src/marketlab/models/__init__.py +16 -0
  22. marketlab-0.1.0/src/marketlab/models/registry.py +98 -0
  23. marketlab-0.1.0/src/marketlab/models/training.py +197 -0
  24. marketlab-0.1.0/src/marketlab/pipeline.py +444 -0
  25. marketlab-0.1.0/src/marketlab/rebalance.py +54 -0
  26. marketlab-0.1.0/src/marketlab/reports/__init__.py +1 -0
  27. marketlab-0.1.0/src/marketlab/reports/analytics.py +146 -0
  28. marketlab-0.1.0/src/marketlab/reports/markdown.py +158 -0
  29. marketlab-0.1.0/src/marketlab/reports/plots.py +68 -0
  30. marketlab-0.1.0/src/marketlab/reports/summary.py +215 -0
  31. marketlab-0.1.0/src/marketlab/resources/__init__.py +13 -0
  32. marketlab-0.1.0/src/marketlab/resources/config_templates/__init__.py +1 -0
  33. marketlab-0.1.0/src/marketlab/resources/config_templates/weekly_rank.yaml +53 -0
  34. marketlab-0.1.0/src/marketlab/resources/config_templates/weekly_rank_smoke.yaml +53 -0
  35. marketlab-0.1.0/src/marketlab/resources/templates.py +34 -0
  36. marketlab-0.1.0/src/marketlab/strategies/__init__.py +1 -0
  37. marketlab-0.1.0/src/marketlab/strategies/buy_hold.py +17 -0
  38. marketlab-0.1.0/src/marketlab/strategies/ranking.py +184 -0
  39. marketlab-0.1.0/src/marketlab/strategies/sma.py +50 -0
  40. marketlab-0.1.0/src/marketlab/targets/__init__.py +11 -0
  41. marketlab-0.1.0/src/marketlab/targets/weekly.py +143 -0
  42. marketlab-0.1.0/src/marketlab.egg-info/PKG-INFO +210 -0
  43. marketlab-0.1.0/src/marketlab.egg-info/SOURCES.txt +45 -0
  44. marketlab-0.1.0/src/marketlab.egg-info/dependency_links.txt +1 -0
  45. marketlab-0.1.0/src/marketlab.egg-info/entry_points.txt +2 -0
  46. marketlab-0.1.0/src/marketlab.egg-info/requires.txt +13 -0
  47. marketlab-0.1.0/src/marketlab.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Ricardogr07
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,210 @@
1
+ Metadata-Version: 2.4
2
+ Name: marketlab
3
+ Version: 0.1.0
4
+ Summary: Reproducible market research toolkit for weekly modeling, walk-forward evaluation, and baseline-plus-ML experiments.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/ricardogr07/market-lab
7
+ Project-URL: Repository, https://github.com/ricardogr07/market-lab
8
+ Project-URL: Issues, https://github.com/ricardogr07/market-lab/issues
9
+ Keywords: market-research,backtesting,walk-forward,machine-learning,portfolio
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: Intended Audience :: Financial and Insurance Industry
13
+ Classifier: Operating System :: OS Independent
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.12
16
+ Classifier: Topic :: Office/Business :: Financial :: Investment
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.12
19
+ Description-Content-Type: text/markdown
20
+ License-File: LICENSE
21
+ Requires-Dist: matplotlib>=3.8
22
+ Requires-Dist: pandas>=2.2
23
+ Requires-Dist: PyYAML>=6.0
24
+ Requires-Dist: yfinance>=0.2
25
+ Requires-Dist: scikit-learn>=1.4
26
+ Provides-Extra: dev
27
+ Requires-Dist: build>=1.2; extra == "dev"
28
+ Requires-Dist: mkdocs>=1.6; extra == "dev"
29
+ Requires-Dist: mkdocs-include-markdown-plugin>=7.1; extra == "dev"
30
+ Requires-Dist: pytest>=8.0; extra == "dev"
31
+ Requires-Dist: ruff>=0.11; extra == "dev"
32
+ Requires-Dist: tox>=4.0; extra == "dev"
33
+ Dynamic: license-file
34
+
35
+ # MarketLab
36
+
37
+ MarketLab is a package-first research toolkit for reproducible market experiments over a fixed ETF universe. The current implementation includes a working baseline-plus-ML workflow: weekly supervised modeling rows, walk-forward folds, trained models, rank-based ML strategies, shared out-of-sample experiments, and reviewable artifact summaries.
38
+
39
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for the system map, data contracts, execution flow, and extension rules.
40
+
41
+ ## Current Commands
42
+
43
+ ```bash
44
+ python scripts/run_marketlab.py prepare-data --config configs/experiment.weekly_rank.yaml
45
+ python scripts/run_marketlab.py backtest --config configs/experiment.weekly_rank.yaml
46
+ python scripts/run_marketlab.py train-models --config configs/experiment.weekly_rank.yaml
47
+ python scripts/run_marketlab.py run-experiment --config configs/experiment.weekly_rank.yaml
48
+ ```
49
+
50
+ `python scripts/run_marketlab.py ...` is the canonical local invocation path because it always resolves to the source tree under `src/`.
51
+
52
+ ## What Each Command Does
53
+
54
+ - `prepare-data`: build or reuse the cached prepared panel.
55
+ - `backtest`: run the rule baselines only (`buy_hold` and `sma`) and write performance, analytics summaries, report, and plots.
56
+ - `train-models`: fit the configured models across walk-forward folds and write raw training artifacts plus fold/model summary CSVs.
57
+ - `run-experiment`: run baselines and ML strategies together on the shared out-of-sample window and write the experiment outputs, analytics summaries, and ML summary CSVs.
58
+
59
+ ## Artifact Outputs
60
+
61
+ ### `train-models`
62
+
63
+ Writes a timestamped folder under `artifacts/runs/<experiment_name>/` containing:
64
+
65
+ - `folds.csv`
66
+ - `model_manifest.csv`
67
+ - `model_metrics.csv`
68
+ - `predictions.csv`
69
+ - `model_summary.csv`
70
+ - `fold_summary.csv`
71
+ - per-fold model pickles under `models/`
72
+
73
+ ### `backtest`
74
+
75
+ Writes a timestamped folder under `artifacts/runs/<experiment_name>/` containing:
76
+
77
+ - `metrics.csv`
78
+ - `performance.csv`
79
+ - `strategy_summary.csv`
80
+ - `monthly_returns.csv`
81
+ - `turnover_costs.csv`
82
+ - `report.md`
83
+ - `cumulative_returns.png`
84
+ - `drawdown.png`
85
+ - `turnover.png`
86
+
87
+ ### `run-experiment`
88
+
89
+ Writes a timestamped folder under `artifacts/runs/<experiment_name>/` containing:
90
+
91
+ - `metrics.csv`
92
+ - `performance.csv`
93
+ - `strategy_summary.csv`
94
+ - `monthly_returns.csv`
95
+ - `turnover_costs.csv`
96
+ - `report.md`
97
+ - `cumulative_returns.png`
98
+ - `drawdown.png`
99
+ - `turnover.png`
100
+ - `model_summary.csv`
101
+ - `fold_summary.csv`
102
+ - optional per-fold model pickles under `models/`
103
+
104
+ ## Environment
105
+
106
+ - Python 3.12+
107
+ - Installed packages:
108
+ - `pandas`
109
+ - `PyYAML`
110
+ - `matplotlib`
111
+ - `yfinance`
112
+ - `scikit-learn`
113
+
114
+ ## Quickstart
115
+
116
+ ```bash
117
+ python -m pip install -e .[dev]
118
+ python scripts/run_marketlab.py run-experiment --config configs/experiment.weekly_rank.yaml
119
+ ```
120
+
121
+ If `artifacts/data/panel.csv` already exists, the pipeline uses it and does not attempt a network download.
122
+
123
+ ## Installed Package Quickstart
124
+
125
+ If you install MarketLab from PyPI or a built wheel, use the packaged CLI bootstrap flow instead of the repo launcher:
126
+
127
+ ```bash
128
+ marketlab --version
129
+ marketlab list-configs
130
+ marketlab write-config --name weekly_rank --output weekly_rank.yaml
131
+ marketlab run-experiment --config weekly_rank.yaml
132
+ ```
133
+
134
+ `list-configs` shows the bundled example templates, and `write-config` exports one of those templates into your working directory. That keeps the installed package self-contained without requiring a checkout of this repository.
135
+
136
+ ## Local Validation
137
+
138
+ ```bash
139
+ python -m pytest -q --basetemp .pytest_tmp
140
+ powershell -ExecutionPolicy Bypass -File scripts/run-e2e.ps1
141
+ ```
142
+
143
+ ## Local CI Entry Points
144
+
145
+ ```bash
146
+ python -m uv sync --group dev
147
+ python -m uv run tox -e lint
148
+ python -m uv run tox -e docs
149
+ python -m uv run tox -e package
150
+ python -m uv run tox -e py312
151
+ python -m tox -e integration
152
+ python -m tox -e preflight
153
+ ```
154
+
155
+ Use `python -m tox -e preflight` as the canonical local pre-push gate. It runs the same lint, docs, packaging, unit-test, and offline integration checks that Phase 3 CI expects through one local entrypoint after the dev dependencies are installed.
156
+
157
+ The MkDocs site renders the current root Markdown docs through `mkdocs-include-markdown-plugin`, so the documentation build stays aligned with `README.md`, `ARCHITECTURE.md`, `Phase2-results.md`, and `PLAN.md`.
158
+
159
+ ## Contribution Workflow
160
+
161
+ - Branch from a refreshed `master` instead of working directly on the default branch.
162
+ - Keep changes in small intentional commits so review scope stays clear.
163
+ - Run `python -m tox -e preflight` before pushing.
164
+ - Open a pull request for review instead of pushing directly to `master`.
165
+ - Treat the `Docker Runner` workflow as an optional manual smoke path, not as a required pre-push step.
166
+ - Keep Codex skills and other personal automation assets in the user-local Codex home rather than in the public repository or package surface.
167
+ - Expect `master` to move ahead of the last public release between monthly release batches.
168
+
169
+ ## Dockerized CLI
170
+
171
+ ```bash
172
+ docker build -t marketlab-cli .
173
+ docker run --rm marketlab-cli --help
174
+ docker run --rm marketlab-cli backtest --config configs/experiment.weekly_rank.smoke.yaml
175
+ ```
176
+
177
+ The container uses the installed `marketlab` console script as its entrypoint. Keep using `python scripts/run_marketlab.py ...` for local source-tree development; the Docker image exists to validate the installed package path and to support manual GitHub Actions runs.
178
+
179
+ ## Manual Docker Runner Workflow
180
+
181
+ GitHub Actions now includes a manual workflow named `Docker Runner` with these inputs:
182
+
183
+ - `command`: `backtest`, `train-models`, or `run-experiment`
184
+ - `config_path`: repo-relative config path inside the image, defaulting to `configs/experiment.weekly_rank.smoke.yaml`
185
+
186
+ The workflow defaults to `backtest`, builds the Docker image, runs the selected command inside the container, writes the resolved run directory into the job summary, and uploads the copied `artifacts/` tree as an Actions artifact.
187
+
188
+ This workflow is not part of the required PR CI checks. It is a manual historical real-data smoke runner around the checked-in smoke config, not a rolling weekly market automation job.
189
+
190
+ ## Release Automation
191
+
192
+ GitHub Actions now includes a release workflow at `.github/workflows/release.yml`.
193
+
194
+ - Normal feature PRs still merge to `master` in sequence.
195
+ - Each merge to `master` updates the open Release PR managed by release-please.
196
+ - The Release PR accumulates the unreleased monthly or feature batch over time.
197
+ - Nothing is tagged or published when a normal feature PR lands on `master`.
198
+ - The actual Git tag, GitHub Release, and PyPI publish path run only when you merge the Release PR.
199
+
200
+ This means `master` can intentionally contain unreleased work while you continue merging feature PRs. The Release PR is the public-release gate.
201
+
202
+ ### Manual Prerequisites
203
+
204
+ Before the first automated public release:
205
+
206
+ - verify that the `marketlab` package name is available on PyPI
207
+ - configure PyPI Trusted Publishing for this repository and the `pypi` environment
208
+ - create the GitHub Actions environment named `pypi`
209
+
210
+ The first automated public release target remains `v0.1.0`.
@@ -0,0 +1,176 @@
1
+ # MarketLab
2
+
3
+ MarketLab is a package-first research toolkit for reproducible market experiments over a fixed ETF universe. The current implementation includes a working baseline-plus-ML workflow: weekly supervised modeling rows, walk-forward folds, trained models, rank-based ML strategies, shared out-of-sample experiments, and reviewable artifact summaries.
4
+
5
+ See [ARCHITECTURE.md](ARCHITECTURE.md) for the system map, data contracts, execution flow, and extension rules.
6
+
7
+ ## Current Commands
8
+
9
+ ```bash
10
+ python scripts/run_marketlab.py prepare-data --config configs/experiment.weekly_rank.yaml
11
+ python scripts/run_marketlab.py backtest --config configs/experiment.weekly_rank.yaml
12
+ python scripts/run_marketlab.py train-models --config configs/experiment.weekly_rank.yaml
13
+ python scripts/run_marketlab.py run-experiment --config configs/experiment.weekly_rank.yaml
14
+ ```
15
+
16
+ `python scripts/run_marketlab.py ...` is the canonical local invocation path because it always resolves to the source tree under `src/`.
17
+
18
+ ## What Each Command Does
19
+
20
+ - `prepare-data`: build or reuse the cached prepared panel.
21
+ - `backtest`: run the rule baselines only (`buy_hold` and `sma`) and write performance, analytics summaries, report, and plots.
22
+ - `train-models`: fit the configured models across walk-forward folds and write raw training artifacts plus fold/model summary CSVs.
23
+ - `run-experiment`: run baselines and ML strategies together on the shared out-of-sample window and write the experiment outputs, analytics summaries, and ML summary CSVs.
24
+
25
+ ## Artifact Outputs
26
+
27
+ ### `train-models`
28
+
29
+ Writes a timestamped folder under `artifacts/runs/<experiment_name>/` containing:
30
+
31
+ - `folds.csv`
32
+ - `model_manifest.csv`
33
+ - `model_metrics.csv`
34
+ - `predictions.csv`
35
+ - `model_summary.csv`
36
+ - `fold_summary.csv`
37
+ - per-fold model pickles under `models/`
38
+
39
+ ### `backtest`
40
+
41
+ Writes a timestamped folder under `artifacts/runs/<experiment_name>/` containing:
42
+
43
+ - `metrics.csv`
44
+ - `performance.csv`
45
+ - `strategy_summary.csv`
46
+ - `monthly_returns.csv`
47
+ - `turnover_costs.csv`
48
+ - `report.md`
49
+ - `cumulative_returns.png`
50
+ - `drawdown.png`
51
+ - `turnover.png`
52
+
53
+ ### `run-experiment`
54
+
55
+ Writes a timestamped folder under `artifacts/runs/<experiment_name>/` containing:
56
+
57
+ - `metrics.csv`
58
+ - `performance.csv`
59
+ - `strategy_summary.csv`
60
+ - `monthly_returns.csv`
61
+ - `turnover_costs.csv`
62
+ - `report.md`
63
+ - `cumulative_returns.png`
64
+ - `drawdown.png`
65
+ - `turnover.png`
66
+ - `model_summary.csv`
67
+ - `fold_summary.csv`
68
+ - optional per-fold model pickles under `models/`
69
+
70
+ ## Environment
71
+
72
+ - Python 3.12+
73
+ - Installed packages:
74
+ - `pandas`
75
+ - `PyYAML`
76
+ - `matplotlib`
77
+ - `yfinance`
78
+ - `scikit-learn`
79
+
80
+ ## Quickstart
81
+
82
+ ```bash
83
+ python -m pip install -e .[dev]
84
+ python scripts/run_marketlab.py run-experiment --config configs/experiment.weekly_rank.yaml
85
+ ```
86
+
87
+ If `artifacts/data/panel.csv` already exists, the pipeline uses it and does not attempt a network download.
88
+
89
+ ## Installed Package Quickstart
90
+
91
+ If you install MarketLab from PyPI or a built wheel, use the packaged CLI bootstrap flow instead of the repo launcher:
92
+
93
+ ```bash
94
+ marketlab --version
95
+ marketlab list-configs
96
+ marketlab write-config --name weekly_rank --output weekly_rank.yaml
97
+ marketlab run-experiment --config weekly_rank.yaml
98
+ ```
99
+
100
+ `list-configs` shows the bundled example templates, and `write-config` exports one of those templates into your working directory. That keeps the installed package self-contained without requiring a checkout of this repository.
101
+
102
+ ## Local Validation
103
+
104
+ ```bash
105
+ python -m pytest -q --basetemp .pytest_tmp
106
+ powershell -ExecutionPolicy Bypass -File scripts/run-e2e.ps1
107
+ ```
108
+
109
+ ## Local CI Entry Points
110
+
111
+ ```bash
112
+ python -m uv sync --group dev
113
+ python -m uv run tox -e lint
114
+ python -m uv run tox -e docs
115
+ python -m uv run tox -e package
116
+ python -m uv run tox -e py312
117
+ python -m tox -e integration
118
+ python -m tox -e preflight
119
+ ```
120
+
121
+ Use `python -m tox -e preflight` as the canonical local pre-push gate. It runs the same lint, docs, packaging, unit-test, and offline integration checks that Phase 3 CI expects through one local entrypoint after the dev dependencies are installed.
122
+
123
+ The MkDocs site renders the current root Markdown docs through `mkdocs-include-markdown-plugin`, so the documentation build stays aligned with `README.md`, `ARCHITECTURE.md`, `Phase2-results.md`, and `PLAN.md`.
124
+
125
+ ## Contribution Workflow
126
+
127
+ - Branch from a refreshed `master` instead of working directly on the default branch.
128
+ - Keep changes in small intentional commits so review scope stays clear.
129
+ - Run `python -m tox -e preflight` before pushing.
130
+ - Open a pull request for review instead of pushing directly to `master`.
131
+ - Treat the `Docker Runner` workflow as an optional manual smoke path, not as a required pre-push step.
132
+ - Keep Codex skills and other personal automation assets in the user-local Codex home rather than in the public repository or package surface.
133
+ - Expect `master` to move ahead of the last public release between monthly release batches.
134
+
135
+ ## Dockerized CLI
136
+
137
+ ```bash
138
+ docker build -t marketlab-cli .
139
+ docker run --rm marketlab-cli --help
140
+ docker run --rm marketlab-cli backtest --config configs/experiment.weekly_rank.smoke.yaml
141
+ ```
142
+
143
+ The container uses the installed `marketlab` console script as its entrypoint. Keep using `python scripts/run_marketlab.py ...` for local source-tree development; the Docker image exists to validate the installed package path and to support manual GitHub Actions runs.
144
+
145
+ ## Manual Docker Runner Workflow
146
+
147
+ GitHub Actions now includes a manual workflow named `Docker Runner` with these inputs:
148
+
149
+ - `command`: `backtest`, `train-models`, or `run-experiment`
150
+ - `config_path`: repo-relative config path inside the image, defaulting to `configs/experiment.weekly_rank.smoke.yaml`
151
+
152
+ The workflow defaults to `backtest`, builds the Docker image, runs the selected command inside the container, writes the resolved run directory into the job summary, and uploads the copied `artifacts/` tree as an Actions artifact.
153
+
154
+ This workflow is not part of the required PR CI checks. It is a manual historical real-data smoke runner around the checked-in smoke config, not a rolling weekly market automation job.
155
+
156
+ ## Release Automation
157
+
158
+ GitHub Actions now includes a release workflow at `.github/workflows/release.yml`.
159
+
160
+ - Normal feature PRs still merge to `master` in sequence.
161
+ - Each merge to `master` updates the open Release PR managed by release-please.
162
+ - The Release PR accumulates the unreleased monthly or feature batch over time.
163
+ - Nothing is tagged or published when a normal feature PR lands on `master`.
164
+ - The actual Git tag, GitHub Release, and PyPI publish path run only when you merge the Release PR.
165
+
166
+ This means `master` can intentionally contain unreleased work while you continue merging feature PRs. The Release PR is the public-release gate.
167
+
168
+ ### Manual Prerequisites
169
+
170
+ Before the first automated public release:
171
+
172
+ - verify that the `marketlab` package name is available on PyPI
173
+ - configure PyPI Trusted Publishing for this repository and the `pypi` environment
174
+ - create the GitHub Actions environment named `pypi`
175
+
176
+ The first automated public release target remains `v0.1.0`.
@@ -0,0 +1,86 @@
1
+ [build-system]
2
+ requires = ["setuptools>=69", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "marketlab"
7
+ version = "0.1.0"
8
+ description = "Reproducible market research toolkit for weekly modeling, walk-forward evaluation, and baseline-plus-ML experiments."
9
+ readme = "README.md"
10
+ requires-python = ">=3.12"
11
+ license = "MIT"
12
+ keywords = ["market-research", "backtesting", "walk-forward", "machine-learning", "portfolio"]
13
+ classifiers = [
14
+ "Development Status :: 3 - Alpha",
15
+ "Intended Audience :: Developers",
16
+ "Intended Audience :: Financial and Insurance Industry",
17
+ "Operating System :: OS Independent",
18
+ "Programming Language :: Python :: 3",
19
+ "Programming Language :: Python :: 3.12",
20
+ "Topic :: Office/Business :: Financial :: Investment",
21
+ "Topic :: Software Development :: Libraries :: Python Modules",
22
+ ]
23
+ dependencies = [
24
+ "matplotlib>=3.8",
25
+ "pandas>=2.2",
26
+ "PyYAML>=6.0",
27
+ "yfinance>=0.2",
28
+ "scikit-learn>=1.4",
29
+ ]
30
+
31
+ [project.optional-dependencies]
32
+ dev = [
33
+ "build>=1.2",
34
+ "mkdocs>=1.6",
35
+ "mkdocs-include-markdown-plugin>=7.1",
36
+ "pytest>=8.0",
37
+ "ruff>=0.11",
38
+ "tox>=4.0",
39
+ ]
40
+
41
+ [project.urls]
42
+ Homepage = "https://github.com/ricardogr07/market-lab"
43
+ Repository = "https://github.com/ricardogr07/market-lab"
44
+ Issues = "https://github.com/ricardogr07/market-lab/issues"
45
+
46
+ [dependency-groups]
47
+ dev = [
48
+ "build>=1.2",
49
+ "mkdocs>=1.6",
50
+ "mkdocs-include-markdown-plugin>=7.1",
51
+ "pytest>=8.0",
52
+ "ruff>=0.11",
53
+ "tox>=4.0",
54
+ ]
55
+
56
+ [project.scripts]
57
+ marketlab = "marketlab.cli:main"
58
+
59
+ [tool.setuptools]
60
+ package-dir = {"" = "src"}
61
+ include-package-data = true
62
+
63
+ [tool.setuptools.package-data]
64
+ "marketlab.resources.config_templates" = ["*.yaml"]
65
+
66
+ [tool.setuptools.packages.find]
67
+ where = ["src"]
68
+
69
+ [tool.uv]
70
+ default-groups = ["dev"]
71
+
72
+ [tool.ruff]
73
+ target-version = "py312"
74
+ src = ["src", "tests", "scripts"]
75
+ extend-exclude = ["artifacts", "build", "dist", "site", ".pytest_tmp*", "tmpk_x17fq8"]
76
+
77
+ [tool.ruff.lint]
78
+ select = ["E4", "E7", "E9", "F", "I"]
79
+
80
+ [tool.pytest.ini_options]
81
+ testpaths = ["tests"]
82
+ addopts = "--basetemp=.pytest_tmp -p no:cacheprovider"
83
+ markers = [
84
+ "real_data: opt-in tests that download real market data.",
85
+ "network: tests that depend on external network access.",
86
+ ]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """MarketLab package for reproducible market experiments."""
2
+
3
+ from marketlab._version import __version__
4
+
5
+ __all__ = ["__version__"]
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from importlib import metadata
4
+
5
+ FALLBACK_VERSION = "0.0.0+local"
6
+ DIST_NAME = "marketlab"
7
+
8
+
9
+ def get_version(dist_name: str = DIST_NAME, fallback: str = FALLBACK_VERSION) -> str:
10
+ try:
11
+ return metadata.version(dist_name)
12
+ except metadata.PackageNotFoundError:
13
+ return fallback
14
+
15
+
16
+ __version__ = get_version()
@@ -0,0 +1 @@
1
+ """Backtest engine and metrics."""
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import pandas as pd
4
+
5
+
6
+ def run_backtest(
7
+ panel: pd.DataFrame,
8
+ weights: pd.DataFrame,
9
+ cost_bps: float,
10
+ ) -> pd.DataFrame:
11
+ required_weight_columns = {"strategy", "effective_date", "symbol", "weight"}
12
+ missing = required_weight_columns - set(weights.columns)
13
+ if missing:
14
+ joined = ", ".join(sorted(missing))
15
+ raise ValueError(f"Weights frame is missing required columns: {joined}")
16
+
17
+ strategy_names = weights["strategy"].drop_duplicates().tolist()
18
+ if len(strategy_names) != 1:
19
+ raise ValueError("Backtest engine expects weights for exactly one strategy.")
20
+ strategy_name = strategy_names[0]
21
+
22
+ working_panel = panel.sort_values(["timestamp", "symbol"]).copy()
23
+ unique_dates = pd.Index(sorted(working_panel["timestamp"].drop_duplicates()))
24
+ symbols = sorted(working_panel["symbol"].unique())
25
+
26
+ adj_close = working_panel.pivot(index="timestamp", columns="symbol", values="adj_close")
27
+ adj_open = working_panel.pivot(index="timestamp", columns="symbol", values="adj_open")
28
+
29
+ overnight_returns = (adj_open / adj_close.shift(1)) - 1.0
30
+ intraday_returns = (adj_close / adj_open) - 1.0
31
+
32
+ weights_pivot = (
33
+ weights.copy()
34
+ .assign(effective_date=lambda frame: pd.to_datetime(frame["effective_date"]))
35
+ .pivot(index="effective_date", columns="symbol", values="weight")
36
+ .reindex(columns=symbols, fill_value=0.0)
37
+ )
38
+ weights_pivot = weights_pivot.reindex(unique_dates).ffill().fillna(0.0)
39
+
40
+ post_open_weights = weights_pivot
41
+ pre_open_weights = post_open_weights.shift(1).fillna(0.0)
42
+ turnover = (post_open_weights - pre_open_weights).abs().sum(axis=1)
43
+
44
+ gross_returns = (
45
+ (pre_open_weights * overnight_returns.fillna(0.0)).sum(axis=1)
46
+ + (post_open_weights * intraday_returns.fillna(0.0)).sum(axis=1)
47
+ )
48
+ costs = turnover * (cost_bps / 10_000.0)
49
+ net_returns = gross_returns - costs
50
+ equity = (1.0 + net_returns).cumprod()
51
+
52
+ return pd.DataFrame(
53
+ {
54
+ "date": unique_dates,
55
+ "strategy": strategy_name,
56
+ "gross_return": gross_returns.values,
57
+ "net_return": net_returns.values,
58
+ "turnover": turnover.values,
59
+ "equity": equity.values,
60
+ }
61
+ )
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ import math
4
+
5
+ import pandas as pd
6
+
7
+
8
+ def compute_strategy_metrics(performance: pd.DataFrame) -> pd.DataFrame:
9
+ rows: list[dict[str, float | str]] = []
10
+ for strategy, frame in performance.groupby("strategy", sort=False):
11
+ ordered = frame.sort_values("date").reset_index(drop=True)
12
+ returns = ordered["net_return"]
13
+ equity = ordered["equity"]
14
+ periods = len(ordered)
15
+
16
+ cumulative_return = float(equity.iloc[-1] - 1.0) if periods else 0.0
17
+ annualized_return = (
18
+ float((equity.iloc[-1] ** (252.0 / periods)) - 1.0) if periods else 0.0
19
+ )
20
+ annualized_volatility = float(returns.std(ddof=0) * math.sqrt(252.0))
21
+ sharpe_like = (
22
+ annualized_return / annualized_volatility if annualized_volatility else 0.0
23
+ )
24
+ drawdown = (equity / equity.cummax()) - 1.0
25
+ max_drawdown = float(drawdown.min()) if not drawdown.empty else 0.0
26
+ hit_rate = float((returns > 0.0).mean()) if periods else 0.0
27
+ avg_turnover = float(ordered["turnover"].mean()) if periods else 0.0
28
+ total_turnover = float(ordered["turnover"].sum()) if periods else 0.0
29
+
30
+ rows.append(
31
+ {
32
+ "strategy": strategy,
33
+ "cumulative_return": cumulative_return,
34
+ "annualized_return": annualized_return,
35
+ "annualized_volatility": annualized_volatility,
36
+ "sharpe_like": sharpe_like,
37
+ "max_drawdown": max_drawdown,
38
+ "hit_rate": hit_rate,
39
+ "avg_turnover": avg_turnover,
40
+ "total_turnover": total_turnover,
41
+ }
42
+ )
43
+
44
+ return pd.DataFrame(rows)