boruta-quant 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 (64) hide show
  1. boruta_quant-0.1.0/.gitignore +58 -0
  2. boruta_quant-0.1.0/.pre-commit-config.yaml +27 -0
  3. boruta_quant-0.1.0/.python-version +1 -0
  4. boruta_quant-0.1.0/CHANGELOG.md +27 -0
  5. boruta_quant-0.1.0/LICENSE +21 -0
  6. boruta_quant-0.1.0/PKG-INFO +203 -0
  7. boruta_quant-0.1.0/README.md +151 -0
  8. boruta_quant-0.1.0/domains/PORTFOLIO.md +169 -0
  9. boruta_quant-0.1.0/domains/PREDICTION.md +119 -0
  10. boruta_quant-0.1.0/domains/TRADING.md +80 -0
  11. boruta_quant-0.1.0/pyproject.toml +142 -0
  12. boruta_quant-0.1.0/src/boruta_quant/__init__.py +29 -0
  13. boruta_quant-0.1.0/src/boruta_quant/_version.py +3 -0
  14. boruta_quant-0.1.0/src/boruta_quant/metrics/__init__.py +31 -0
  15. boruta_quant-0.1.0/src/boruta_quant/metrics/auc.py +83 -0
  16. boruta_quant-0.1.0/src/boruta_quant/metrics/directional_accuracy.py +67 -0
  17. boruta_quant-0.1.0/src/boruta_quant/metrics/rank_ic.py +72 -0
  18. boruta_quant-0.1.0/src/boruta_quant/oracle/__init__.py +32 -0
  19. boruta_quant-0.1.0/src/boruta_quant/oracle/base.py +124 -0
  20. boruta_quant-0.1.0/src/boruta_quant/oracle/block_permutation.py +165 -0
  21. boruta_quant-0.1.0/src/boruta_quant/oracle/drop_column.py +99 -0
  22. boruta_quant-0.1.0/src/boruta_quant/oracle/permutation.py +85 -0
  23. boruta_quant-0.1.0/src/boruta_quant/profiling/__init__.py +18 -0
  24. boruta_quant-0.1.0/src/boruta_quant/profiling/helpers.py +40 -0
  25. boruta_quant-0.1.0/src/boruta_quant/profiling/results.py +37 -0
  26. boruta_quant-0.1.0/src/boruta_quant/profiling/session.py +128 -0
  27. boruta_quant-0.1.0/src/boruta_quant/profiling/timer.py +58 -0
  28. boruta_quant-0.1.0/src/boruta_quant/py.typed +0 -0
  29. boruta_quant-0.1.0/src/boruta_quant/selector/__init__.py +47 -0
  30. boruta_quant-0.1.0/src/boruta_quant/selector/config.py +56 -0
  31. boruta_quant-0.1.0/src/boruta_quant/selector/hypothesis.py +132 -0
  32. boruta_quant-0.1.0/src/boruta_quant/selector/results.py +44 -0
  33. boruta_quant-0.1.0/src/boruta_quant/selector/selector.py +353 -0
  34. boruta_quant-0.1.0/src/boruta_quant/selector/shadow.py +100 -0
  35. boruta_quant-0.1.0/src/boruta_quant/selector/shuffle.py +53 -0
  36. boruta_quant-0.1.0/src/boruta_quant/temporal/__init__.py +40 -0
  37. boruta_quant-0.1.0/src/boruta_quant/temporal/config.py +37 -0
  38. boruta_quant-0.1.0/src/boruta_quant/temporal/cv.py +74 -0
  39. boruta_quant-0.1.0/src/boruta_quant/temporal/purged_cv.py +147 -0
  40. boruta_quant-0.1.0/src/boruta_quant/temporal/split.py +28 -0
  41. boruta_quant-0.1.0/tests/__init__.py +1 -0
  42. boruta_quant-0.1.0/tests/fixtures/__init__.py +0 -0
  43. boruta_quant-0.1.0/tests/test_metrics/__init__.py +1 -0
  44. boruta_quant-0.1.0/tests/test_metrics/test_auc.py +104 -0
  45. boruta_quant-0.1.0/tests/test_metrics/test_directional_accuracy.py +128 -0
  46. boruta_quant-0.1.0/tests/test_metrics/test_rank_ic.py +89 -0
  47. boruta_quant-0.1.0/tests/test_oracle/__init__.py +1 -0
  48. boruta_quant-0.1.0/tests/test_oracle/conftest.py +92 -0
  49. boruta_quant-0.1.0/tests/test_oracle/test_block_permutation.py +404 -0
  50. boruta_quant-0.1.0/tests/test_oracle/test_drop_column.py +224 -0
  51. boruta_quant-0.1.0/tests/test_oracle/test_permutation.py +267 -0
  52. boruta_quant-0.1.0/tests/test_profiling/__init__.py +1 -0
  53. boruta_quant-0.1.0/tests/test_profiling/test_helpers.py +63 -0
  54. boruta_quant-0.1.0/tests/test_profiling/test_session.py +166 -0
  55. boruta_quant-0.1.0/tests/test_profiling/test_timer.py +80 -0
  56. boruta_quant-0.1.0/tests/test_selector/__init__.py +1 -0
  57. boruta_quant-0.1.0/tests/test_selector/conftest.py +120 -0
  58. boruta_quant-0.1.0/tests/test_selector/test_config.py +307 -0
  59. boruta_quant-0.1.0/tests/test_selector/test_hypothesis.py +265 -0
  60. boruta_quant-0.1.0/tests/test_selector/test_selector.py +573 -0
  61. boruta_quant-0.1.0/tests/test_selector/test_shadow.py +331 -0
  62. boruta_quant-0.1.0/tests/test_selector/test_shuffle.py +223 -0
  63. boruta_quant-0.1.0/tests/test_temporal/__init__.py +1 -0
  64. boruta_quant-0.1.0/tests/test_temporal/test_purged_cv.py +466 -0
@@ -0,0 +1,58 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # IDE
13
+ .idea/
14
+ .vscode/
15
+ *.swp
16
+ *.swo
17
+ *~
18
+
19
+ # Testing
20
+ .pytest_cache/
21
+ .coverage
22
+ .coverage.*
23
+ htmlcov/
24
+ .tox/
25
+ .nox/
26
+
27
+ # Type checking
28
+ .mypy_cache/
29
+ .pyright/
30
+
31
+ # Distribution
32
+ *.whl
33
+ *.tar.gz
34
+
35
+ # Jupyter
36
+ .ipynb_checkpoints/
37
+
38
+ # OS
39
+ .DS_Store
40
+ Thumbs.db
41
+
42
+ # UV
43
+ uv.lock
44
+
45
+ # Claude Code (imported from .ai_workspace_template, not tracked)
46
+ .claude/
47
+ .ai_workspace/
48
+ CLAUDE.md
49
+ CLAUDE_TEMPLATE.md
50
+ CLAUDE_DOMAIN.md
51
+ CLAUDE_REPO.md
52
+ .claude_config.yaml
53
+
54
+ # Internal development infrastructure (not for public)
55
+ hooks/
56
+ templates/
57
+ STANDARDS.md
58
+ docs/research/
@@ -0,0 +1,27 @@
1
+ # Pre-commit hooks configuration for boruta-quant
2
+ # Install: pre-commit install
3
+ # Run manually: pre-commit run --all-files
4
+
5
+ repos:
6
+ # Linting, import sorting, and formatting with Ruff (fast Rust-based tool)
7
+ - repo: https://github.com/astral-sh/ruff-pre-commit
8
+ rev: v0.12.9
9
+ hooks:
10
+ - id: ruff
11
+ name: Ruff linter (includes import sorting)
12
+ args: [--fix, --exit-non-zero-on-fix]
13
+ - id: ruff-format
14
+ name: Ruff formatter
15
+
16
+ # Type checking with Pyright (3-10x faster than mypy)
17
+ - repo: local
18
+ hooks:
19
+ - id: pyright
20
+ name: Pyright type checker
21
+ entry: pyright
22
+ language: node
23
+ types: [python]
24
+ pass_filenames: false
25
+ additional_dependencies: ['pyright@1.1.380']
26
+ files: '\.py$'
27
+ exclude: ^tests/fixtures/
@@ -0,0 +1 @@
1
+ 3.12
@@ -0,0 +1,27 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.1.0] - 2026-02-26
9
+
10
+ ### Added
11
+ - **BorutaSelector**: Main feature selection class with configurable trials, alpha, percentile, and two-step tentative resolution
12
+ - **Importance Oracles**: Pluggable `ImportanceOracle` protocol with three implementations:
13
+ - `PermutationOracle` (default) — OOS-safe permutation importance
14
+ - `DropColumnOracle` — refit-based importance via column removal
15
+ - `BlockPermutationOracle` — block-preserving permutation for autocorrelated data
16
+ - **Purged Temporal CV**: `PurgedTemporalCV` with configurable purge window, embargo window, and min train size to prevent lookahead bias
17
+ - **Shadow Features**: Shadow feature generation with three shuffle modes:
18
+ - `RANDOM` — standard i.i.d. permutation (default)
19
+ - `BLOCK` — block-preserving shuffle for serial correlation
20
+ - `ERA` — within-era-only shuffle for regime-aware selection
21
+ - **Era Support**: `boundaries_to_eras()` utility for converting date boundaries to era labels
22
+ - **Statistical Testing**: Binomial hypothesis testing with Bonferroni correction and optional two-step rough fix for tentative features
23
+ - **Metrics**: `rank_ic` (Spearman correlation) and `auc_scorer` for alpha research evaluation
24
+ - **Profiling**: Built-in `ProfilingSession` integration for timing and memory tracking during `fit()`
25
+ - **Type Safety**: Full type hints with beartype runtime enforcement and Pyright strict mode
26
+ - **Pydantic Configs**: `BorutaSelectorConfig` and `PurgedCVConfig` with fail-fast validation
27
+ - 229 tests with 92% statement coverage
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 BlackArbsCEO
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,203 @@
1
+ Metadata-Version: 2.4
2
+ Name: boruta-quant
3
+ Version: 0.1.0
4
+ Summary: Temporal-aware Boruta feature selection for quantitative finance. OOS-only importance with purged cross-validation.
5
+ Project-URL: Homepage, https://github.com/BlackArbsCEO/boruta-quant
6
+ Project-URL: Documentation, https://github.com/BlackArbsCEO/boruta-quant#readme
7
+ Project-URL: Repository, https://github.com/BlackArbsCEO/boruta-quant
8
+ Project-URL: Issues, https://github.com/BlackArbsCEO/boruta-quant/issues
9
+ Project-URL: Changelog, https://github.com/BlackArbsCEO/boruta-quant/blob/main/CHANGELOG.md
10
+ Author-email: BlackArbsCEO <bcr@blackarbs.com>
11
+ Maintainer-email: BlackArbsCEO <bcr@blackarbs.com>
12
+ License: MIT
13
+ License-File: LICENSE
14
+ Keywords: boruta,cross-validation,feature-selection,machine-learning,out-of-sample,permutation-importance,quantitative-finance,temporal-cv
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Intended Audience :: Financial and Insurance Industry
18
+ Classifier: Intended Audience :: Science/Research
19
+ Classifier: License :: OSI Approved :: MIT License
20
+ Classifier: Operating System :: OS Independent
21
+ Classifier: Programming Language :: Python :: 3
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business :: Financial :: Investment
25
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
26
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
27
+ Classifier: Typing :: Typed
28
+ Requires-Python: >=3.12
29
+ Requires-Dist: beartype>=0.18.0
30
+ Requires-Dist: numpy>=2.0.0
31
+ Requires-Dist: pandas>=2.0.0
32
+ Requires-Dist: psutil>=5.9.0
33
+ Requires-Dist: pydantic>=2.0.0
34
+ Requires-Dist: scikit-learn>=1.3.0
35
+ Requires-Dist: scipy>=1.11.0
36
+ Provides-Extra: all
37
+ Requires-Dist: lightgbm>=4.0.0; extra == 'all'
38
+ Requires-Dist: matplotlib>=3.7.0; extra == 'all'
39
+ Requires-Dist: seaborn>=0.13.0; extra == 'all'
40
+ Requires-Dist: shap>=0.48.0; extra == 'all'
41
+ Requires-Dist: xgboost>=2.0.0; extra == 'all'
42
+ Provides-Extra: lightgbm
43
+ Requires-Dist: lightgbm>=4.0.0; extra == 'lightgbm'
44
+ Provides-Extra: shap
45
+ Requires-Dist: shap>=0.48.0; extra == 'shap'
46
+ Provides-Extra: viz
47
+ Requires-Dist: matplotlib>=3.7.0; extra == 'viz'
48
+ Requires-Dist: seaborn>=0.13.0; extra == 'viz'
49
+ Provides-Extra: xgboost
50
+ Requires-Dist: xgboost>=2.0.0; extra == 'xgboost'
51
+ Description-Content-Type: text/markdown
52
+
53
+ # boruta-quant
54
+
55
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
56
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
57
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
58
+
59
+ **Temporal-aware Boruta feature selection for quantitative finance.**
60
+
61
+ `boruta-quant` computes feature importance on validation data only, using purged cross-validation to prevent lookahead bias. Built for financial time series where temporal integrity matters.
62
+
63
+ ## Why boruta-quant?
64
+
65
+ Standard feature selection (SHAP, sklearn permutation importance) computes importance on training data. In financial time series, this leaks future information into feature rankings. `boruta-quant` fixes this:
66
+
67
+ - **OOS-Only Importance**: Importance computed exclusively on validation folds
68
+ - **Purged Cross-Validation**: Train/test gap with purge and embargo windows
69
+ - **Shadow Features**: Boruta's all-relevant selection via shadow comparison
70
+
71
+ ## Installation
72
+
73
+ ```bash
74
+ # Basic (permutation importance only)
75
+ pip install boruta-quant
76
+
77
+ # With LightGBM
78
+ pip install boruta-quant[lightgbm]
79
+
80
+ # With SHAP support
81
+ pip install boruta-quant[shap]
82
+
83
+ # Everything
84
+ pip install boruta-quant[all]
85
+ ```
86
+
87
+ ### Development
88
+
89
+ ```bash
90
+ git clone https://github.com/BlackArbsCEO/boruta-quant.git
91
+ cd boruta-quant
92
+ uv sync --all-extras --dev
93
+ ```
94
+
95
+ ## Quick Start
96
+
97
+ ```python
98
+ from boruta_quant import BorutaSelector, BorutaSelectorConfig
99
+ from boruta_quant.oracle import PermutationImportanceOracle
100
+ from boruta_quant.temporal import PurgedTemporalCV, PurgedCVConfig
101
+ from boruta_quant.metrics import rank_ic_scorer
102
+ from lightgbm import LGBMRegressor
103
+
104
+ # 1. Configure purged temporal CV
105
+ cv = PurgedTemporalCV(PurgedCVConfig(
106
+ n_splits=5,
107
+ purge_window_days=5, # gap before validation fold
108
+ embargo_window_days=5, # gap after validation fold
109
+ min_train_size=100,
110
+ test_size_ratio=0.2,
111
+ ))
112
+
113
+ # 2. Configure importance oracle (OOS-only)
114
+ oracle = PermutationImportanceOracle(
115
+ scoring=rank_ic_scorer, # Spearman rank correlation
116
+ n_repeats=10,
117
+ random_state=42,
118
+ )
119
+
120
+ # 3. Configure Boruta selector
121
+ selector = BorutaSelector(
122
+ config=BorutaSelectorConfig(
123
+ n_trials=20, # Boruta iterations
124
+ percentile=100, # shadow threshold percentile
125
+ alpha=0.05, # significance level
126
+ two_step=True, # resolve tentative features
127
+ random_state=42,
128
+ ),
129
+ oracle=oracle,
130
+ cv=cv,
131
+ )
132
+
133
+ # 4. Fit — model goes here, not in the constructor
134
+ result = selector.fit(
135
+ X, y,
136
+ timestamps=timestamps, # must be timezone-aware
137
+ model=LGBMRegressor(n_estimators=100, random_state=42),
138
+ )
139
+
140
+ # 5. Results
141
+ print(result.accepted_features) # confirmed important
142
+ print(result.rejected_features) # confirmed unimportant
143
+ print(result.tentative_features) # borderline (resolved if two_step=True)
144
+ ```
145
+
146
+ ## Importance Oracles
147
+
148
+ All oracles fit the model on training data but measure importance on validation data only.
149
+
150
+ | Oracle | How it works | When to use |
151
+ |--------|-------------|-------------|
152
+ | `PermutationImportanceOracle` | Shuffles one feature in validation set, measures prediction drop | Default — reliable, no refit needed |
153
+ | `DropColumnImportanceOracle` | Removes feature, refits model, measures prediction drop | When refit cost is acceptable |
154
+ | `BlockPermutationImportanceOracle` | Block-shuffles feature (preserves autocorrelation structure) | Autocorrelated time series |
155
+
156
+ ## Temporal Cross-Validation
157
+
158
+ ```
159
+ Training Purge Validation Embargo
160
+ |--------------| |-------| |----------| |-------|
161
+ ^ ^
162
+ train_start embargo_end
163
+
164
+ - Purge: removes observations that could leak into validation
165
+ - Embargo: prevents information from validation bleeding forward
166
+ ```
167
+
168
+ ## Shadow Shuffle Modes
169
+
170
+ Shadow features are shuffled copies of real features. The shuffle mode controls how temporal structure is handled:
171
+
172
+ | Mode | Description | Use case |
173
+ |------|-------------|----------|
174
+ | `ShuffleMode.RANDOM` | Standard i.i.d. permutation | Default — i.i.d. data |
175
+ | `ShuffleMode.BLOCK` | Block-preserving shuffle | Autocorrelated features |
176
+ | `ShuffleMode.ERA` | Shuffle within eras only | Regime-aware selection |
177
+
178
+ ## Metrics
179
+
180
+ | Function | Description |
181
+ |----------|-------------|
182
+ | `rank_ic` | Spearman correlation between predictions and actuals |
183
+ | `rank_ic_scorer` | sklearn-compatible scorer wrapping `rank_ic` |
184
+ | `directional_accuracy` | Fraction of correct sign predictions (up vs down) |
185
+ | `directional_accuracy_scorer` | sklearn-compatible scorer wrapping `directional_accuracy` |
186
+ | `auc_score` | Area under ROC curve |
187
+ | `auc_scorer` | sklearn-compatible scorer wrapping `auc_score` |
188
+
189
+ ## Design Principles
190
+
191
+ 1. **OOS-Only**: Importance never computed on training data
192
+ 2. **Fail-Fast**: Invalid temporal data (naive timestamps, unsorted) raises immediately
193
+ 3. **Type-Safe**: Runtime enforcement with beartype, strict Pyright
194
+ 4. **Explicit**: All config parameters required — no hidden defaults
195
+
196
+ ## References
197
+
198
+ - [Boruta Algorithm](https://www.jstatsoft.org/article/view/v036i11) — Kursa & Rudnicki (2010)
199
+ - [Advances in Financial Machine Learning](https://www.wiley.com/en-us/Advances+in+Financial+Machine+Learning-p-9781119482086) — Lopez de Prado (2018), Ch. 7 (purged CV)
200
+
201
+ ## License
202
+
203
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,151 @@
1
+ # boruta-quant
2
+
3
+ [![Python 3.12+](https://img.shields.io/badge/python-3.12+-blue.svg)](https://www.python.org/downloads/)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
5
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
6
+
7
+ **Temporal-aware Boruta feature selection for quantitative finance.**
8
+
9
+ `boruta-quant` computes feature importance on validation data only, using purged cross-validation to prevent lookahead bias. Built for financial time series where temporal integrity matters.
10
+
11
+ ## Why boruta-quant?
12
+
13
+ Standard feature selection (SHAP, sklearn permutation importance) computes importance on training data. In financial time series, this leaks future information into feature rankings. `boruta-quant` fixes this:
14
+
15
+ - **OOS-Only Importance**: Importance computed exclusively on validation folds
16
+ - **Purged Cross-Validation**: Train/test gap with purge and embargo windows
17
+ - **Shadow Features**: Boruta's all-relevant selection via shadow comparison
18
+
19
+ ## Installation
20
+
21
+ ```bash
22
+ # Basic (permutation importance only)
23
+ pip install boruta-quant
24
+
25
+ # With LightGBM
26
+ pip install boruta-quant[lightgbm]
27
+
28
+ # With SHAP support
29
+ pip install boruta-quant[shap]
30
+
31
+ # Everything
32
+ pip install boruta-quant[all]
33
+ ```
34
+
35
+ ### Development
36
+
37
+ ```bash
38
+ git clone https://github.com/BlackArbsCEO/boruta-quant.git
39
+ cd boruta-quant
40
+ uv sync --all-extras --dev
41
+ ```
42
+
43
+ ## Quick Start
44
+
45
+ ```python
46
+ from boruta_quant import BorutaSelector, BorutaSelectorConfig
47
+ from boruta_quant.oracle import PermutationImportanceOracle
48
+ from boruta_quant.temporal import PurgedTemporalCV, PurgedCVConfig
49
+ from boruta_quant.metrics import rank_ic_scorer
50
+ from lightgbm import LGBMRegressor
51
+
52
+ # 1. Configure purged temporal CV
53
+ cv = PurgedTemporalCV(PurgedCVConfig(
54
+ n_splits=5,
55
+ purge_window_days=5, # gap before validation fold
56
+ embargo_window_days=5, # gap after validation fold
57
+ min_train_size=100,
58
+ test_size_ratio=0.2,
59
+ ))
60
+
61
+ # 2. Configure importance oracle (OOS-only)
62
+ oracle = PermutationImportanceOracle(
63
+ scoring=rank_ic_scorer, # Spearman rank correlation
64
+ n_repeats=10,
65
+ random_state=42,
66
+ )
67
+
68
+ # 3. Configure Boruta selector
69
+ selector = BorutaSelector(
70
+ config=BorutaSelectorConfig(
71
+ n_trials=20, # Boruta iterations
72
+ percentile=100, # shadow threshold percentile
73
+ alpha=0.05, # significance level
74
+ two_step=True, # resolve tentative features
75
+ random_state=42,
76
+ ),
77
+ oracle=oracle,
78
+ cv=cv,
79
+ )
80
+
81
+ # 4. Fit — model goes here, not in the constructor
82
+ result = selector.fit(
83
+ X, y,
84
+ timestamps=timestamps, # must be timezone-aware
85
+ model=LGBMRegressor(n_estimators=100, random_state=42),
86
+ )
87
+
88
+ # 5. Results
89
+ print(result.accepted_features) # confirmed important
90
+ print(result.rejected_features) # confirmed unimportant
91
+ print(result.tentative_features) # borderline (resolved if two_step=True)
92
+ ```
93
+
94
+ ## Importance Oracles
95
+
96
+ All oracles fit the model on training data but measure importance on validation data only.
97
+
98
+ | Oracle | How it works | When to use |
99
+ |--------|-------------|-------------|
100
+ | `PermutationImportanceOracle` | Shuffles one feature in validation set, measures prediction drop | Default — reliable, no refit needed |
101
+ | `DropColumnImportanceOracle` | Removes feature, refits model, measures prediction drop | When refit cost is acceptable |
102
+ | `BlockPermutationImportanceOracle` | Block-shuffles feature (preserves autocorrelation structure) | Autocorrelated time series |
103
+
104
+ ## Temporal Cross-Validation
105
+
106
+ ```
107
+ Training Purge Validation Embargo
108
+ |--------------| |-------| |----------| |-------|
109
+ ^ ^
110
+ train_start embargo_end
111
+
112
+ - Purge: removes observations that could leak into validation
113
+ - Embargo: prevents information from validation bleeding forward
114
+ ```
115
+
116
+ ## Shadow Shuffle Modes
117
+
118
+ Shadow features are shuffled copies of real features. The shuffle mode controls how temporal structure is handled:
119
+
120
+ | Mode | Description | Use case |
121
+ |------|-------------|----------|
122
+ | `ShuffleMode.RANDOM` | Standard i.i.d. permutation | Default — i.i.d. data |
123
+ | `ShuffleMode.BLOCK` | Block-preserving shuffle | Autocorrelated features |
124
+ | `ShuffleMode.ERA` | Shuffle within eras only | Regime-aware selection |
125
+
126
+ ## Metrics
127
+
128
+ | Function | Description |
129
+ |----------|-------------|
130
+ | `rank_ic` | Spearman correlation between predictions and actuals |
131
+ | `rank_ic_scorer` | sklearn-compatible scorer wrapping `rank_ic` |
132
+ | `directional_accuracy` | Fraction of correct sign predictions (up vs down) |
133
+ | `directional_accuracy_scorer` | sklearn-compatible scorer wrapping `directional_accuracy` |
134
+ | `auc_score` | Area under ROC curve |
135
+ | `auc_scorer` | sklearn-compatible scorer wrapping `auc_score` |
136
+
137
+ ## Design Principles
138
+
139
+ 1. **OOS-Only**: Importance never computed on training data
140
+ 2. **Fail-Fast**: Invalid temporal data (naive timestamps, unsorted) raises immediately
141
+ 3. **Type-Safe**: Runtime enforcement with beartype, strict Pyright
142
+ 4. **Explicit**: All config parameters required — no hidden defaults
143
+
144
+ ## References
145
+
146
+ - [Boruta Algorithm](https://www.jstatsoft.org/article/view/v036i11) — Kursa & Rudnicki (2010)
147
+ - [Advances in Financial Machine Learning](https://www.wiley.com/en-us/Advances+in+Financial+Machine+Learning-p-9781119482086) — Lopez de Prado (2018), Ch. 7 (purged CV)
148
+
149
+ ## License
150
+
151
+ MIT — see [LICENSE](LICENSE).
@@ -0,0 +1,169 @@
1
+ # PORTFOLIO DOMAIN RULES
2
+
3
+ ## 💼 Portfolio Management Requirements
4
+
5
+ ### Core Portfolio Rules
6
+ 1. **NEVER exceed position limits** - Risk management is paramount
7
+ 2. **ALWAYS maintain audit trail** - Every rebalance must be traceable
8
+ 3. **ENFORCE factor attribution** - Know what drives returns
9
+ 4. **REQUIRE transaction cost modeling** - Real costs in optimization
10
+
11
+ ### Portfolio Construction Requirements
12
+
13
+ ```python
14
+ # ✅ REQUIRED - Position limits enforcement
15
+ @beartype
16
+ def validate_weights(weights: np.ndarray, limits: PositionLimits):
17
+ assert abs(weights.sum() - 1.0) < 1e-6, "Weights must sum to 1"
18
+ assert (weights >= limits.min_weight).all(), f"Below minimum: {weights.min()}"
19
+ assert (weights <= limits.max_weight).all(), f"Above maximum: {weights.max()}"
20
+ assert weights[weights != 0].shape[0] <= limits.max_positions, "Too many positions"
21
+ ```
22
+
23
+ ### Risk Management Rules
24
+
25
+ | Metric | Required Check | Failure Action |
26
+ |--------|---------------|----------------|
27
+ | Portfolio VaR | < Risk budget | Scale positions down |
28
+ | Concentration | < 25% per position | Rebalance |
29
+ | Correlation | < 0.8 between positions | Diversify |
30
+ | Leverage | < Maximum allowed | Reduce exposure |
31
+
32
+ ### Rebalancing Requirements
33
+
34
+ ```python
35
+ # Event-driven rebalancing triggers
36
+ RebalanceTriggers = {
37
+ "drift": 5.0, # % drift from target
38
+ "vol_spike": 2.0, # Volatility multiplier
39
+ "correlation": 0.85, # Correlation threshold
40
+ "calendar": 30, # Days since last rebalance
41
+ }
42
+
43
+ # REQUIRED: Rebalancing audit
44
+ RebalanceAudit = {
45
+ "timestamp": datetime.utcnow(),
46
+ "trigger": trigger_reason,
47
+ "weights_before": current_weights.copy(),
48
+ "weights_after": new_weights.copy(),
49
+ "turnover": calculate_turnover(current, new),
50
+ "expected_cost": transaction_costs,
51
+ "risk_metrics": calculate_risk_metrics(new)
52
+ }
53
+ ```
54
+
55
+ ### Factor Attribution Requirements
56
+
57
+ ```python
58
+ # MANDATORY - Track factor exposures
59
+ @dataclass
60
+ class FactorExposure:
61
+ market_beta: float
62
+ size: float
63
+ value: float
64
+ momentum: float
65
+ quality: float
66
+ volatility: float
67
+
68
+ def validate(self):
69
+ assert -3 <= self.market_beta <= 3, "Unrealistic beta"
70
+ total_exposure = abs(self.size) + abs(self.value) + abs(self.momentum)
71
+ assert total_exposure > 0, "No factor exposure"
72
+ ```
73
+
74
+ ### Optimization Requirements
75
+
76
+ ```python
77
+ # REQUIRED: Robust optimization
78
+ from scipy.optimize import minimize
79
+
80
+ def optimize_portfolio(
81
+ expected_returns: np.ndarray,
82
+ covariance: np.ndarray,
83
+ constraints: list
84
+ ):
85
+ # MANDATORY: Regularization for numerical stability
86
+ cov_regularized = covariance + np.eye(len(covariance)) * 1e-8
87
+
88
+ # REQUIRED: Multiple objectives
89
+ objectives = {
90
+ "return": expected_returns,
91
+ "risk": cov_regularized,
92
+ "transaction_costs": cost_model
93
+ }
94
+
95
+ # FORBIDDEN: Unconstrained optimization
96
+ assert len(constraints) > 0, "Must have constraints"
97
+ ```
98
+
99
+ ### Execution Requirements
100
+
101
+ ```python
102
+ # Saga pattern for multi-step execution
103
+ @dataclass
104
+ class RebalanceSaga:
105
+ saga_id: str
106
+ steps: list[RebalanceStep]
107
+ state: SagaState
108
+
109
+ def execute(self):
110
+ for step in self.steps:
111
+ try:
112
+ step.execute()
113
+ self.checkpoint(step)
114
+ except Exception as e:
115
+ self.rollback(step)
116
+ raise SagaFailure(f"Failed at {step}: {e}")
117
+ ```
118
+
119
+ ### Performance Attribution
120
+
121
+ | Component | Calculation | Required |
122
+ |-----------|------------|----------|
123
+ | Asset allocation | Benchmark vs actual weights | Yes |
124
+ | Security selection | Within-sector returns | Yes |
125
+ | Factor contribution | Factor return × exposure | Yes |
126
+ | Transaction costs | Explicit tracking | Yes |
127
+ | Timing | Entry/exit vs benchmark | Optional |
128
+
129
+ ### Data Management
130
+
131
+ ```python
132
+ # Zero-copy optimization for large portfolios
133
+ import pyarrow as pa
134
+
135
+ # REQUIRED: Efficient data structures
136
+ prices_table = pa.Table.from_pandas(prices_df)
137
+ returns_array = pa.compute.diff(prices_table['close'])
138
+
139
+ # FORBIDDEN: Repeated DataFrame copies
140
+ # ❌ df2 = df.copy(); df3 = df2.copy()
141
+ # ✅ Use views or Arrow tables
142
+ ```
143
+
144
+ ### Forbidden Patterns
145
+
146
+ | ❌ Forbidden | ✅ Required | Why |
147
+ |--------------|-------------|-----|
148
+ | Optimize without constraints | Constrained optimization | Prevents unrealistic portfolios |
149
+ | Ignore transaction costs | Explicit cost modeling | Real-world performance |
150
+ | Single-period optimization | Multi-period planning | Path dependency |
151
+ | Static risk limits | Dynamic risk budgeting | Market regime changes |
152
+ | Rebalance without audit | Complete audit trail | Regulatory compliance |
153
+
154
+ ### Production Safety
155
+
156
+ ```python
157
+ # MANDATORY: Pre-trade compliance checks
158
+ def pre_trade_compliance(orders: list[Order]) -> list[Order]:
159
+ validated = []
160
+ for order in orders:
161
+ # Position limits
162
+ assert check_position_limits(order), f"Position limit breach: {order}"
163
+ # Risk limits
164
+ assert check_risk_limits(order), f"Risk limit breach: {order}"
165
+ # Regulatory
166
+ assert check_regulatory(order), f"Regulatory violation: {order}"
167
+ validated.append(order)
168
+ return validated
169
+ ```