quant-pml 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 (178) hide show
  1. quant_pml-0.1.0/LICENSE +21 -0
  2. quant_pml-0.1.0/PKG-INFO +172 -0
  3. quant_pml-0.1.0/README.md +146 -0
  4. quant_pml-0.1.0/pyproject.toml +179 -0
  5. quant_pml-0.1.0/setup.cfg +7 -0
  6. quant_pml-0.1.0/setup.py +6 -0
  7. quant_pml-0.1.0/src/quant_pml/__init__.py +1 -0
  8. quant_pml-0.1.0/src/quant_pml/ap/__init__.py +0 -0
  9. quant_pml-0.1.0/src/quant_pml/ap/base_asset_pricer.py +58 -0
  10. quant_pml-0.1.0/src/quant_pml/ap/ipca_factor_model.py +104 -0
  11. quant_pml-0.1.0/src/quant_pml/ap/kns_factor_model.py +90 -0
  12. quant_pml-0.1.0/src/quant_pml/ap/uncond_factor_model.py +72 -0
  13. quant_pml-0.1.0/src/quant_pml/backtest/__init__.py +0 -0
  14. quant_pml-0.1.0/src/quant_pml/backtest/assessor.py +214 -0
  15. quant_pml-0.1.0/src/quant_pml/backtest/backtester.py +647 -0
  16. quant_pml-0.1.0/src/quant_pml/backtest/plot.py +140 -0
  17. quant_pml-0.1.0/src/quant_pml/backtest/research_us_backtest.py +54 -0
  18. quant_pml-0.1.0/src/quant_pml/backtest/russell3000_backtest.py +54 -0
  19. quant_pml-0.1.0/src/quant_pml/backtest/transaction_costs_charger.py +84 -0
  20. quant_pml-0.1.0/src/quant_pml/base/__init__.py +0 -0
  21. quant_pml-0.1.0/src/quant_pml/base/currencies.py +29 -0
  22. quant_pml-0.1.0/src/quant_pml/base/prices.py +31 -0
  23. quant_pml-0.1.0/src/quant_pml/base/returns.py +40 -0
  24. quant_pml-0.1.0/src/quant_pml/config/__init__.py +0 -0
  25. quant_pml-0.1.0/src/quant_pml/config/base_experiment_config.py +219 -0
  26. quant_pml-0.1.0/src/quant_pml/config/base_experiment_config_v2.py +178 -0
  27. quant_pml-0.1.0/src/quant_pml/config/experiment_config.py +96 -0
  28. quant_pml-0.1.0/src/quant_pml/config/jkp_experiment_config.py +15 -0
  29. quant_pml-0.1.0/src/quant_pml/config/research_us_experiment_config.py +15 -0
  30. quant_pml-0.1.0/src/quant_pml/config/research_us_trading_config.py +17 -0
  31. quant_pml-0.1.0/src/quant_pml/config/russell_3000_experiment_config.py +15 -0
  32. quant_pml-0.1.0/src/quant_pml/config/russia_experiment_config.py +112 -0
  33. quant_pml-0.1.0/src/quant_pml/config/spx_experiment_config.py +15 -0
  34. quant_pml-0.1.0/src/quant_pml/config/topn_experiment_config.py +19 -0
  35. quant_pml-0.1.0/src/quant_pml/config/trading_config.py +25 -0
  36. quant_pml-0.1.0/src/quant_pml/config/us_experiment_config.py +104 -0
  37. quant_pml-0.1.0/src/quant_pml/data_handlers/__init__.py +0 -0
  38. quant_pml-0.1.0/src/quant_pml/data_handlers/additional_data.py +54 -0
  39. quant_pml-0.1.0/src/quant_pml/data_handlers/create_compustat_dataset.py +129 -0
  40. quant_pml-0.1.0/src/quant_pml/data_handlers/create_crsp_dataset.py +142 -0
  41. quant_pml-0.1.0/src/quant_pml/data_handlers/create_spx_dataset.py +35 -0
  42. quant_pml-0.1.0/src/quant_pml/data_handlers/dataset.py +18 -0
  43. quant_pml-0.1.0/src/quant_pml/data_handlers/dataset_builder_functions.py +194 -0
  44. quant_pml-0.1.0/src/quant_pml/data_handlers/prepare_data.py +94 -0
  45. quant_pml-0.1.0/src/quant_pml/data_handlers/universe_builder_functions.py +73 -0
  46. quant_pml-0.1.0/src/quant_pml/dataset/__init__.py +0 -0
  47. quant_pml-0.1.0/src/quant_pml/dataset/dataset_builder.py +428 -0
  48. quant_pml-0.1.0/src/quant_pml/dataset/dnk_features_targets.py +0 -0
  49. quant_pml-0.1.0/src/quant_pml/date/__init__.py +0 -0
  50. quant_pml-0.1.0/src/quant_pml/date/pd_date.py +17 -0
  51. quant_pml-0.1.0/src/quant_pml/estimation/__init__.py +0 -0
  52. quant_pml-0.1.0/src/quant_pml/estimation/base_estimator.py +45 -0
  53. quant_pml-0.1.0/src/quant_pml/estimation/covariance/__init__.py +0 -0
  54. quant_pml-0.1.0/src/quant_pml/estimation/covariance/base_cov_estimator.py +10 -0
  55. quant_pml-0.1.0/src/quant_pml/estimation/covariance/factor/__init__.py +0 -0
  56. quant_pml-0.1.0/src/quant_pml/estimation/covariance/factor/factor_cov_estimator.py +78 -0
  57. quant_pml-0.1.0/src/quant_pml/estimation/covariance/heuristic/__init__.py +0 -0
  58. quant_pml-0.1.0/src/quant_pml/estimation/covariance/heuristic/hist_cov_estimator.py +23 -0
  59. quant_pml-0.1.0/src/quant_pml/estimation/covariance/ml/__init__.py +0 -0
  60. quant_pml-0.1.0/src/quant_pml/estimation/covariance/ml/glasso_estimator.py +52 -0
  61. quant_pml-0.1.0/src/quant_pml/estimation/covariance/ml/glasso_tscv_estimator.py +39 -0
  62. quant_pml-0.1.0/src/quant_pml/estimation/covariance/ml/predictors/__init__.py +0 -0
  63. quant_pml-0.1.0/src/quant_pml/estimation/covariance/ml/predictors/sklearn_ml_predictor.py +68 -0
  64. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/__init__.py +0 -0
  65. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/base_rl_estimator.py +199 -0
  66. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/__init__.py +0 -0
  67. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/ar_estimator.py +38 -0
  68. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/average_estimator.py +22 -0
  69. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/bound_estimator.py +21 -0
  70. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/dl/__init__.py +0 -0
  71. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/dl/dl_model.py +139 -0
  72. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/dl/models/__init__.py +0 -0
  73. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/dl/models/mlp.py +26 -0
  74. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/dl_estimator.py +36 -0
  75. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/dnk_estimator.py +61 -0
  76. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/gp_estimator.py +46 -0
  77. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/gpr_estimator.py +48 -0
  78. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/lasso_estimator.py +50 -0
  79. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/last_optimal_estimator.py +30 -0
  80. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/ma_estimator.py +30 -0
  81. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/ols_estimator.py +45 -0
  82. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/pretrained_estimator.py +63 -0
  83. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/rf_estimator.py +43 -0
  84. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/rf_xiu_estimator.py +50 -0
  85. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/ridge_estimator.py +49 -0
  86. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/ridge_prior_estimator.py +57 -0
  87. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/uncert_ensemble_estimator.py +79 -0
  88. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/behavioral_cloning/xgb_estimator.py +68 -0
  89. quant_pml-0.1.0/src/quant_pml/estimation/covariance/rl/env.py +174 -0
  90. quant_pml-0.1.0/src/quant_pml/estimation/covariance/sample_cov_estimator.py +23 -0
  91. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/__init__.py +0 -0
  92. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/base_cross_val_cov_estimator.py +105 -0
  93. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/de_nard_cov_estimator.py +48 -0
  94. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/diag_hist_cov_estimator.py +24 -0
  95. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/identity_based_cov_estimator.py +36 -0
  96. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/lw_cv_cov_estimator.py +92 -0
  97. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/lw_linear_estimator.py +36 -0
  98. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/pca_cov_estimator.py +51 -0
  99. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/qis.py +169 -0
  100. quant_pml-0.1.0/src/quant_pml/estimation/covariance/shrinkage/rp_cov_estimator.py +42 -0
  101. quant_pml-0.1.0/src/quant_pml/estimation/mean/__init__.py +0 -0
  102. quant_pml-0.1.0/src/quant_pml/estimation/mean/base_mu_estimator.py +10 -0
  103. quant_pml-0.1.0/src/quant_pml/estimation/mean/sample_mu_estimator.py +23 -0
  104. quant_pml-0.1.0/src/quant_pml/feature_processor.py +24 -0
  105. quant_pml-0.1.0/src/quant_pml/features/__init__.py +0 -0
  106. quant_pml-0.1.0/src/quant_pml/features/base_preprocessor.py +24 -0
  107. quant_pml-0.1.0/src/quant_pml/features/ols_betas.py +102 -0
  108. quant_pml-0.1.0/src/quant_pml/features/online_ols_betas.py +194 -0
  109. quant_pml-0.1.0/src/quant_pml/features/preprocessor.py +33 -0
  110. quant_pml-0.1.0/src/quant_pml/hedge/__init__.py +0 -0
  111. quant_pml-0.1.0/src/quant_pml/hedge/base_hedger.py +71 -0
  112. quant_pml-0.1.0/src/quant_pml/hedge/constant_hedge.py +31 -0
  113. quant_pml-0.1.0/src/quant_pml/hedge/hedger.py +30 -0
  114. quant_pml-0.1.0/src/quant_pml/hedge/market_futures_hedge.py +53 -0
  115. quant_pml-0.1.0/src/quant_pml/market_data/__init__.py +0 -0
  116. quant_pml-0.1.0/src/quant_pml/market_data/market_data.py +175 -0
  117. quant_pml-0.1.0/src/quant_pml/market_data/risk_free_conventions.py +11 -0
  118. quant_pml-0.1.0/src/quant_pml/market_data/ticker.py +10 -0
  119. quant_pml-0.1.0/src/quant_pml/market_data/tickers.py +57 -0
  120. quant_pml-0.1.0/src/quant_pml/meta_portfolio/__init__.py +0 -0
  121. quant_pml-0.1.0/src/quant_pml/optimization/__init__.py +0 -0
  122. quant_pml-0.1.0/src/quant_pml/optimization/constraints.py +201 -0
  123. quant_pml-0.1.0/src/quant_pml/optimization/helper_functions.py +57 -0
  124. quant_pml-0.1.0/src/quant_pml/optimization/optimization.py +254 -0
  125. quant_pml-0.1.0/src/quant_pml/optimization/quadratic_program.py +271 -0
  126. quant_pml-0.1.0/src/quant_pml/py.typed +0 -0
  127. quant_pml-0.1.0/src/quant_pml/runner.py +417 -0
  128. quant_pml-0.1.0/src/quant_pml/stats/__init__.py +0 -0
  129. quant_pml-0.1.0/src/quant_pml/stats/helpers/__init__.py +0 -0
  130. quant_pml-0.1.0/src/quant_pml/stats/helpers/linalg.py +14 -0
  131. quant_pml-0.1.0/src/quant_pml/stats/helpers/max_drawdown.py +11 -0
  132. quant_pml-0.1.0/src/quant_pml/stats/stats_pack.py +9 -0
  133. quant_pml-0.1.0/src/quant_pml/strategies/__init__.py +0 -0
  134. quant_pml-0.1.0/src/quant_pml/strategies/base_strategy.py +142 -0
  135. quant_pml-0.1.0/src/quant_pml/strategies/factors/__init__.py +0 -0
  136. quant_pml-0.1.0/src/quant_pml/strategies/factors/high_beta.py +33 -0
  137. quant_pml-0.1.0/src/quant_pml/strategies/factors/low_beta.py +64 -0
  138. quant_pml-0.1.0/src/quant_pml/strategies/factors/momentum.py +50 -0
  139. quant_pml-0.1.0/src/quant_pml/strategies/factors/momentum_sign.py +46 -0
  140. quant_pml-0.1.0/src/quant_pml/strategies/factors/size.py +53 -0
  141. quant_pml-0.1.0/src/quant_pml/strategies/factors/sorting_strategy.py +92 -0
  142. quant_pml-0.1.0/src/quant_pml/strategies/heuristics/__init__.py +0 -0
  143. quant_pml-0.1.0/src/quant_pml/strategies/heuristics/capitalization_weighted.py +29 -0
  144. quant_pml-0.1.0/src/quant_pml/strategies/heuristics/equally_weighted.py +25 -0
  145. quant_pml-0.1.0/src/quant_pml/strategies/heuristics/target_strategy.py +26 -0
  146. quant_pml-0.1.0/src/quant_pml/strategies/ml/__init__.py +0 -0
  147. quant_pml-0.1.0/src/quant_pml/strategies/ml/ret_clf/__init__.py +0 -0
  148. quant_pml-0.1.0/src/quant_pml/strategies/ml/ret_clf/fractional_momentum_sample_mean.py +69 -0
  149. quant_pml-0.1.0/src/quant_pml/strategies/ml/ret_clf/fractional_momentum_sklearn.py +74 -0
  150. quant_pml-0.1.0/src/quant_pml/strategies/ml/scoring/__init__.py +0 -0
  151. quant_pml-0.1.0/src/quant_pml/strategies/ml/scoring/base_ml_scoring_strategy.py +66 -0
  152. quant_pml-0.1.0/src/quant_pml/strategies/optimization_data.py +83 -0
  153. quant_pml-0.1.0/src/quant_pml/strategies/optimized/__init__.py +0 -0
  154. quant_pml-0.1.0/src/quant_pml/strategies/optimized/base_estimated_strategy.py +66 -0
  155. quant_pml-0.1.0/src/quant_pml/strategies/optimized/long_short_min_var.py +47 -0
  156. quant_pml-0.1.0/src/quant_pml/strategies/optimized/max_decorr.py +62 -0
  157. quant_pml-0.1.0/src/quant_pml/strategies/optimized/mean_var.py +62 -0
  158. quant_pml-0.1.0/src/quant_pml/strategies/optimized/min_var.py +67 -0
  159. quant_pml-0.1.0/src/quant_pml/strategies/optimized/resid_min_var.py +62 -0
  160. quant_pml-0.1.0/src/quant_pml/strategies/optimized/risk_parity.py +40 -0
  161. quant_pml-0.1.0/src/quant_pml/strategies/optimized/timed_risk_parity.py +56 -0
  162. quant_pml-0.1.0/src/quant_pml/strategies/optimized/unconditional_mean_var.py +56 -0
  163. quant_pml-0.1.0/src/quant_pml/strategies/scaling/__init__.py +0 -0
  164. quant_pml-0.1.0/src/quant_pml/strategies/scaling/volatility_targeting.py +74 -0
  165. quant_pml-0.1.0/src/quant_pml/strategies/timing/__init__.py +0 -0
  166. quant_pml-0.1.0/src/quant_pml/strategies/timing/vol_timing.py +66 -0
  167. quant_pml-0.1.0/src/quant_pml/strategies/timing/vol_timing_vs_bonds.py +37 -0
  168. quant_pml-0.1.0/src/quant_pml/strategies/weighting/__init__.py +0 -0
  169. quant_pml-0.1.0/src/quant_pml/strategies/weighting/weighting_mixin.py +82 -0
  170. quant_pml-0.1.0/src/quant_pml/utils/__init__.py +0 -0
  171. quant_pml-0.1.0/src/quant_pml/utils/data.py +76 -0
  172. quant_pml-0.1.0/src/quant_pml/utils/linalg.py +41 -0
  173. quant_pml-0.1.0/src/quant_pml/utils/pd_date.py +31 -0
  174. quant_pml-0.1.0/src/quant_pml.egg-info/PKG-INFO +172 -0
  175. quant_pml-0.1.0/src/quant_pml.egg-info/SOURCES.txt +177 -0
  176. quant_pml-0.1.0/src/quant_pml.egg-info/dependency_links.txt +1 -0
  177. quant_pml-0.1.0/src/quant_pml.egg-info/requires.txt +15 -0
  178. quant_pml-0.1.0/src/quant_pml.egg-info/top_level.txt +1 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Viacheslav Buchkov
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,172 @@
1
+ Metadata-Version: 2.4
2
+ Name: quant-pml
3
+ Version: 0.1.0
4
+ Author: Viacheslav Buchkov
5
+ License-Expression: MIT
6
+ Keywords: quant,quantitative finance,machine learning
7
+ Requires-Python: ==3.12.5
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: statsmodels>=0.14.4
11
+ Requires-Dist: scikit-learn>=1.5.2
12
+ Requires-Dist: numpy>=2.2.4
13
+ Requires-Dist: pandas>=2.3.1
14
+ Requires-Dist: matplotlib>=3.10.1
15
+ Requires-Dist: qpsolvers>=4.5.0
16
+ Requires-Dist: torch==2.8.0
17
+ Requires-Dist: pyarrow==20.0.0
18
+ Requires-Dist: fastparquet==2024.11.0
19
+ Requires-Dist: tqdm>=4.67.1
20
+ Requires-Dist: scipy>=1.16.1
21
+ Requires-Dist: optuna==4.4.0
22
+ Requires-Dist: plotly>=6.2.0
23
+ Requires-Dist: openpyxl>=3.1.5
24
+ Requires-Dist: polars==1.34.0
25
+ Dynamic: license-file
26
+
27
+ # Quantitative Portfolio Machine Learning Library
28
+
29
+ _Viacheslav Buchkov_ \
30
+ ETH Zürich
31
+
32
+ **Keywords**: quant; quantitative finance; portfolio management; machine learning; reinforcement learning.
33
+
34
+ ## Install
35
+
36
+ ```
37
+ pip install quant-pml
38
+ ```
39
+
40
+
41
+ ## How To Run A Backtest
42
+
43
+ ```
44
+ from quant_pml.config.trading_config import TradingConfig
45
+ from quant_pml.config.russell_3000_experiment_config import Russell3000ExperimentConfig
46
+ from quant_pml.hedge.market_futures_hedge import MarketFuturesHedge
47
+ from quant_pml.runner import build_backtest
48
+ from quant_pml.strategies.factors.momentum import Momentum
49
+
50
+ # Specify the model hyperparameters
51
+ REBAL_FREQ = "D" # Rebalance frequency (Month End)
52
+ QUANTILE = 0.1 # Sorted portfolio quantile
53
+ MODE = "long_short" # Trading mode
54
+
55
+ # Specify the trading configurations
56
+ trading_config = TradingConfig(
57
+ broker_fee=0, # Broker commission in decimals
58
+ bid_ask_spread=0, # Bid-ask spread in decimals
59
+ total_exposure=1, # Budget constraint
60
+ max_exposure=None, # Maximum weight constraint
61
+ min_exposure=None, # Minimum weight constraint
62
+ trading_lag_days=1, # Trading Lag
63
+ )
64
+
65
+ # Create a strategy with logic for weights selection
66
+ strategy = Momentum(
67
+ mode=MODE,
68
+ quantile=QUANTILE,
69
+ )
70
+
71
+ # Initialize the hedger (can be set to `None` if no hedging is required)
72
+ hedger = MarketFuturesHedge(market_name="spx")
73
+
74
+ # Build the backtest pipeline
75
+ preprocessor, runner = build_backtest(
76
+ experiment_config=Russell3000ExperimentConfig(),
77
+ trading_config=trading_config,
78
+ rebal_freq=REBAL_FREQ,
79
+ )
80
+
81
+ # Run the backtest
82
+ res = runner(
83
+ feature_processor=preprocessor,
84
+ strategy=strategy,
85
+ hedger=hedger,
86
+ )
87
+ ```
88
+
89
+ ## How To Create A New Strategy
90
+
91
+ ```
92
+ import pandas as pd
93
+ from quant_pml.strategies.base_strategy import BaseStrategy
94
+ from quant_pml.strategies.optimization_data import TrainingData, PredictionData
95
+
96
+
97
+ # Create a blueprint for your strategy
98
+ class NewStrategy(BaseStrategy):
99
+ def __init__(self) -> None:
100
+ super().__init__()
101
+
102
+ # Store the data across time, if needed
103
+
104
+ def _fit(self, training_data: TrainingData) -> None:
105
+ # Specify here your fitting logic
106
+ pass
107
+
108
+ def _get_weights(self, prediction_data: PredictionData, weights_: pd.DataFrame) -> pd.DataFrame:
109
+ # Specify here your prediction logic
110
+
111
+ # Update the weights DataFrame by modifying the respective columns of stock_ids
112
+ return weights_
113
+ ```
114
+
115
+ ## How To Create A New Covariance Estimator
116
+
117
+ ```
118
+ from __future__ import annotations
119
+
120
+ from typing import TYPE_CHECKING
121
+
122
+ if TYPE_CHECKING:
123
+ import pandas as pd
124
+
125
+ from quant_pml.strategies.optimization_data import PredictionData, TrainingData
126
+ from quant_pml.cov_estimators.base_cov_estimator import BaseCovEstimator
127
+
128
+
129
+ class NewCovEstimator(BaseCovEstimator):
130
+ def __init__(self) -> None:
131
+ super().__init__()
132
+
133
+ self._fitted_cov = None
134
+
135
+ def _fit(self, training_data: TrainingData) -> None:
136
+ # Specify here your fitting logic (e.g., store a historical covmat)
137
+ ...
138
+
139
+ def _predict(self, prediction_data: PredictionData) -> pd.DataFrame:
140
+ # Specify here your prediction logic (e.g., get a stored estimate)
141
+ ...
142
+ ```
143
+
144
+ ## How To Create A New Data-Driven Covariance Estimator
145
+
146
+ ```
147
+ from __future__ import annotations
148
+
149
+ from typing import TYPE_CHECKING
150
+
151
+ if TYPE_CHECKING:
152
+ import pandas as pd
153
+
154
+ from quant_pml.cov_estimators.rl.base_rl_estimator import BaseRLCovEstimator
155
+
156
+
157
+ class NewRLCovEstimator(BaseRLCovEstimator):
158
+ def __init__(self, shrinkage_type: str, window_size: int | None = None) -> None:
159
+ super().__init__(shrinkage_type=shrinkage_type, window_size=window_size)
160
+
161
+ def _fit_shrinkage(
162
+ self, features: pd.DataFrame, shrinkage_target: pd.Series
163
+ ) -> None:
164
+ # Specify here your model (might be classical ML / RL / etc.)
165
+ self.model = ...
166
+ self.model.fit(X=features, y=shrinkage_target)
167
+
168
+ def _predict_shrinkage(self, features: pd.DataFrame) -> float:
169
+ pred = self.model.predict(features).item()
170
+
171
+ return pred
172
+ ```
@@ -0,0 +1,146 @@
1
+ # Quantitative Portfolio Machine Learning Library
2
+
3
+ _Viacheslav Buchkov_ \
4
+ ETH Zürich
5
+
6
+ **Keywords**: quant; quantitative finance; portfolio management; machine learning; reinforcement learning.
7
+
8
+ ## Install
9
+
10
+ ```
11
+ pip install quant-pml
12
+ ```
13
+
14
+
15
+ ## How To Run A Backtest
16
+
17
+ ```
18
+ from quant_pml.config.trading_config import TradingConfig
19
+ from quant_pml.config.russell_3000_experiment_config import Russell3000ExperimentConfig
20
+ from quant_pml.hedge.market_futures_hedge import MarketFuturesHedge
21
+ from quant_pml.runner import build_backtest
22
+ from quant_pml.strategies.factors.momentum import Momentum
23
+
24
+ # Specify the model hyperparameters
25
+ REBAL_FREQ = "D" # Rebalance frequency (Month End)
26
+ QUANTILE = 0.1 # Sorted portfolio quantile
27
+ MODE = "long_short" # Trading mode
28
+
29
+ # Specify the trading configurations
30
+ trading_config = TradingConfig(
31
+ broker_fee=0, # Broker commission in decimals
32
+ bid_ask_spread=0, # Bid-ask spread in decimals
33
+ total_exposure=1, # Budget constraint
34
+ max_exposure=None, # Maximum weight constraint
35
+ min_exposure=None, # Minimum weight constraint
36
+ trading_lag_days=1, # Trading Lag
37
+ )
38
+
39
+ # Create a strategy with logic for weights selection
40
+ strategy = Momentum(
41
+ mode=MODE,
42
+ quantile=QUANTILE,
43
+ )
44
+
45
+ # Initialize the hedger (can be set to `None` if no hedging is required)
46
+ hedger = MarketFuturesHedge(market_name="spx")
47
+
48
+ # Build the backtest pipeline
49
+ preprocessor, runner = build_backtest(
50
+ experiment_config=Russell3000ExperimentConfig(),
51
+ trading_config=trading_config,
52
+ rebal_freq=REBAL_FREQ,
53
+ )
54
+
55
+ # Run the backtest
56
+ res = runner(
57
+ feature_processor=preprocessor,
58
+ strategy=strategy,
59
+ hedger=hedger,
60
+ )
61
+ ```
62
+
63
+ ## How To Create A New Strategy
64
+
65
+ ```
66
+ import pandas as pd
67
+ from quant_pml.strategies.base_strategy import BaseStrategy
68
+ from quant_pml.strategies.optimization_data import TrainingData, PredictionData
69
+
70
+
71
+ # Create a blueprint for your strategy
72
+ class NewStrategy(BaseStrategy):
73
+ def __init__(self) -> None:
74
+ super().__init__()
75
+
76
+ # Store the data across time, if needed
77
+
78
+ def _fit(self, training_data: TrainingData) -> None:
79
+ # Specify here your fitting logic
80
+ pass
81
+
82
+ def _get_weights(self, prediction_data: PredictionData, weights_: pd.DataFrame) -> pd.DataFrame:
83
+ # Specify here your prediction logic
84
+
85
+ # Update the weights DataFrame by modifying the respective columns of stock_ids
86
+ return weights_
87
+ ```
88
+
89
+ ## How To Create A New Covariance Estimator
90
+
91
+ ```
92
+ from __future__ import annotations
93
+
94
+ from typing import TYPE_CHECKING
95
+
96
+ if TYPE_CHECKING:
97
+ import pandas as pd
98
+
99
+ from quant_pml.strategies.optimization_data import PredictionData, TrainingData
100
+ from quant_pml.cov_estimators.base_cov_estimator import BaseCovEstimator
101
+
102
+
103
+ class NewCovEstimator(BaseCovEstimator):
104
+ def __init__(self) -> None:
105
+ super().__init__()
106
+
107
+ self._fitted_cov = None
108
+
109
+ def _fit(self, training_data: TrainingData) -> None:
110
+ # Specify here your fitting logic (e.g., store a historical covmat)
111
+ ...
112
+
113
+ def _predict(self, prediction_data: PredictionData) -> pd.DataFrame:
114
+ # Specify here your prediction logic (e.g., get a stored estimate)
115
+ ...
116
+ ```
117
+
118
+ ## How To Create A New Data-Driven Covariance Estimator
119
+
120
+ ```
121
+ from __future__ import annotations
122
+
123
+ from typing import TYPE_CHECKING
124
+
125
+ if TYPE_CHECKING:
126
+ import pandas as pd
127
+
128
+ from quant_pml.cov_estimators.rl.base_rl_estimator import BaseRLCovEstimator
129
+
130
+
131
+ class NewRLCovEstimator(BaseRLCovEstimator):
132
+ def __init__(self, shrinkage_type: str, window_size: int | None = None) -> None:
133
+ super().__init__(shrinkage_type=shrinkage_type, window_size=window_size)
134
+
135
+ def _fit_shrinkage(
136
+ self, features: pd.DataFrame, shrinkage_target: pd.Series
137
+ ) -> None:
138
+ # Specify here your model (might be classical ML / RL / etc.)
139
+ self.model = ...
140
+ self.model.fit(X=features, y=shrinkage_target)
141
+
142
+ def _predict_shrinkage(self, features: pd.DataFrame) -> float:
143
+ pred = self.model.predict(features).item()
144
+
145
+ return pred
146
+ ```
@@ -0,0 +1,179 @@
1
+ [project]
2
+ name = "quant-pml"
3
+ version = "0.1.0"
4
+ description = ""
5
+ requires-python = "==3.12.5"
6
+ readme = "README.md"
7
+ dependencies = [
8
+ "statsmodels>=0.14.4",
9
+ "scikit-learn>=1.5.2",
10
+ "numpy>=2.2.4",
11
+ "pandas>=2.3.1",
12
+ "matplotlib>=3.10.1",
13
+ "qpsolvers>=4.5.0",
14
+ "torch==2.8.0",
15
+ "pyarrow==20.0.0",
16
+ "fastparquet==2024.11.0",
17
+ "tqdm>=4.67.1",
18
+ "scipy>=1.16.1",
19
+ "optuna==4.4.0",
20
+ "plotly>=6.2.0",
21
+ "openpyxl>=3.1.5",
22
+ "polars==1.34.0",
23
+ ]
24
+ authors = [{name = "Viacheslav Buchkov"}]
25
+ keywords = ["quant", "quantitative finance", "machine learning"]
26
+ license = "MIT"
27
+
28
+ [tool.uv.workspace]
29
+ members = [
30
+ "src/",
31
+ "backtesting_checks/dummy",
32
+ "backtesting_checks/timing_strategy",
33
+ "backtesting_checks/low_beta",
34
+ "backtesting_checks/spx_reconstr",
35
+ "backtests/enhanced_momentum",
36
+ "backtests/fractional_momentum",
37
+ ]
38
+
39
+ [dependency-groups]
40
+ dev = [
41
+ "basedpyright>=1.31.6",
42
+ "deptry>=0.23.1",
43
+ "ipykernel>=6.30.1",
44
+ "ruff>=0.13.3",
45
+ ]
46
+
47
+
48
+ [tool.ruff]
49
+ line-length = 130
50
+ fix = true
51
+
52
+ exclude = [
53
+ ".bzr",
54
+ ".direnv",
55
+ ".eggs",
56
+ ".git",
57
+ ".git-rewrite",
58
+ ".hg",
59
+ ".ipynb_checkpoints",
60
+ ".mypy_cache",
61
+ ".nox",
62
+ ".pants.d",
63
+ ".pyenv",
64
+ ".pytest_cache",
65
+ ".pytype",
66
+ ".ruff_cache",
67
+ ".svn",
68
+ ".tox",
69
+ ".venv",
70
+ ".vscode",
71
+ "__pypackages__",
72
+ "_build",
73
+ "buck-out",
74
+ "build",
75
+ "dist",
76
+ "node_modules",
77
+ "site-packages",
78
+ "venv",
79
+ "__init__.py",
80
+ "libraries/pmplib/pmplib/optimization", # V added this but (?)
81
+ "check_dependencies.py" # scripts, maybe should move to tools later
82
+ ]
83
+
84
+ target-version = "py312"
85
+
86
+ extend-exclude = [".venv"]
87
+
88
+ # See: https://github.com/charliermarsh/ruff#supported-rules
89
+ [tool.ruff.lint]
90
+ select = [
91
+ "F", # Pyflakes
92
+ "E", # pycodestyle Error
93
+ "W", # pycodestyle Warning
94
+ "C90", # mccabe
95
+ "I", # isort
96
+ "D", # pydocstyle
97
+ "UP", # pyupgrade
98
+ "N", # pep8-naming
99
+ "YTT", # flake8-2020
100
+ "ASYNC", # flake8-async
101
+ "ANN", # flake8-annotations
102
+ "S", # flake8-bandit
103
+ "BLE", # flake8-blind-except
104
+ "FBT", # flake8-boolean-trap
105
+ "B", # flake8-bugbear
106
+ "A", # flake8-builtins
107
+ "C4", # flake8-comprehensions
108
+ "T10", # flake8-debugger
109
+ "DJ", # flake8-django
110
+ "EM", # flake8-errmsg
111
+ "ISC", # flake8-implicit-str-concat
112
+ "ICN", # flake8-import-conventions
113
+ "T20", # flake8-print
114
+ "PYI", # flake8-pyi
115
+ "R", # refactor
116
+ "Q", # flake8-quotes
117
+ "RET", # flake8-return
118
+ "SLF", # flake8-self
119
+ "PT", # flake8-pytest-style
120
+ "SIM", # flake8-simplify
121
+ "TID", # flake8-tidy-imports
122
+ "ARG", # flake8-unused-arguments
123
+ "DTZ", # flake8-datetimez
124
+ "ERA", # eradicate
125
+ "PD", # pandas-vet
126
+ "PGH", # pygrep-hooks
127
+ "PLC", # Pylint Convention
128
+ "PLE", # Pylint Error
129
+ "PLR", # Pylint Refactor
130
+ "PLW", # Pylint Warning
131
+ "PIE", # flake8-pie
132
+ "COM", # flake8-commas
133
+ "INP", # flake8-no-pep420
134
+ "EXE", # flake8-executable
135
+ "TCH", # flake8-type-checking
136
+ "ARG", # flake8-unused-arguments
137
+ "TRY", # tryceratops
138
+ "PTH", # flake8-use-pathlib
139
+ "RUF", # Ruff-specific rules
140
+ "RSE", # flake8-raise
141
+ "SLOT", # flake8-slots
142
+ "TD", # flake8-todos
143
+ "FLY", # flynt
144
+ "NPY", # NumPy-specific rules
145
+ "PERF", # Perflint
146
+ "G", # flake8-logging-format
147
+ "PL", # Pylint
148
+ "FURB", # refurb
149
+ ]
150
+
151
+ ignore = [
152
+ "D100", # Missing docstring in public module
153
+ "D101", # Missing docstring in public class
154
+ "D102", # Missing docstring in public method
155
+ "D103", # Missing docstring in public function
156
+ "D104", # Missing docstring in public package
157
+ "D105", # Missing docstring in magic method
158
+ "D106", # Missing docstring in public nested class
159
+ "D107", # Missing docstring in __init__
160
+ "TD003", # Missing issue link on the line following this TODORuffTD003
161
+ "D211", # No blank lines allowed before class docstring
162
+ "D213", # Multi-line docstring summary should start at the second line
163
+ "G004", # Logging statement uses string formatting
164
+ "D203", # 1 blank line required before class docstring
165
+ "COM812", # Trailing comma missing (conflicts with formatter)
166
+ "DTZ011", # Use `datetime.datetime.now(tz=...).date()` instead
167
+ "DTZ005", # datetime.datetime.now()` called without a `tz` argument
168
+ "S101", #Use of `assert` detected
169
+ "PD010", #`.pivot_table` is preferred to `.pivot` or `.unstack`; provides same functionality
170
+ ]
171
+
172
+ unfixable = ["F401"]
173
+
174
+ [tool.ruff.lint.per-file-ignores]
175
+ "**/*.ipynb" = [
176
+ "E501", # ignore line-length in all notebooks
177
+ "F404", # ignore `from __future__` imports must occur at the beginning of the file
178
+ "T201" # ignore print statements
179
+ ]
@@ -0,0 +1,7 @@
1
+ [metadata]
2
+ description-file = README.md
3
+
4
+ [egg_info]
5
+ tag_build =
6
+ tag_date = 0
7
+
@@ -0,0 +1,6 @@
1
+ from setuptools import find_packages, setup
2
+
3
+ setup(
4
+ package_dir={"": "src"},
5
+ packages=find_packages(where="src"),
6
+ )
@@ -0,0 +1 @@
1
+ from __future__ import annotations
File without changes
@@ -0,0 +1,58 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING
5
+
6
+ import numpy as np
7
+
8
+ if TYPE_CHECKING:
9
+ import pandas as pd
10
+
11
+
12
+ class BaseAssetPricer(ABC):
13
+ def __init__(self) -> None:
14
+ super().__init__()
15
+
16
+ @abstractmethod
17
+ def fit(self, test_assets_xs_r: pd.DataFrame, factors: pd.DataFrame) -> pd.DataFrame:
18
+ raise NotImplementedError
19
+
20
+ @abstractmethod
21
+ def predict(self, factors: pd.DataFrame) -> pd.DataFrame:
22
+ raise NotImplementedError
23
+
24
+ def _get_deviations(self, test_assets_xs_r: pd.DataFrame, factors: pd.DataFrame) -> tuple[pd.Series, pd.Series]:
25
+ pred_xs_r = self.predict(factors)
26
+
27
+ ts_average = test_assets_xs_r.mean(axis=0)
28
+
29
+ model_deviation = ts_average - pred_xs_r.mean(axis=1)
30
+ baseline_deviation = ts_average - ts_average.mean() * np.ones(len(ts_average))
31
+
32
+ return model_deviation, baseline_deviation
33
+
34
+ def r2_score(self, test_assets_xs_r: pd.DataFrame, factors: pd.DataFrame) -> float:
35
+ model_deviation, baseline_deviation = self._get_deviations(test_assets_xs_r, factors)
36
+
37
+ mse_model = model_deviation.T @ model_deviation
38
+ mse_baseline = baseline_deviation.T @ baseline_deviation
39
+
40
+ return 1 - mse_model / mse_baseline
41
+
42
+ def r2_gls_score(self, test_assets_xs_r: pd.DataFrame, factors: pd.DataFrame) -> float:
43
+ model_deviation, baseline_deviation = self._get_deviations(test_assets_xs_r, factors)
44
+
45
+ var_r_inv = np.linalg.inv(test_assets_xs_r.cov())
46
+
47
+ mse_model = model_deviation.T @ var_r_inv @ model_deviation
48
+ mse_baseline = baseline_deviation.T @ var_r_inv @ baseline_deviation
49
+
50
+ return 1 - mse_model / mse_baseline
51
+
52
+ def implied_sharpe_ratio(self, test_assets_xs_r: pd.DataFrame, factors: pd.DataFrame) -> float:
53
+ raise NotImplementedError
54
+
55
+ def rmse_score(self, test_assets_xs_r: pd.DataFrame, factors: pd.DataFrame) -> float:
56
+ model_deviation, _baseline_deviation = self._get_deviations(test_assets_xs_r, factors)
57
+
58
+ return np.sqrt(model_deviation.T @ model_deviation / len(model_deviation))
@@ -0,0 +1,104 @@
1
+ from __future__ import annotations
2
+
3
+ import warnings
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from ipca import InstrumentedPCA
8
+
9
+ from quant_pml.ap.base_asset_pricer import BaseAssetPricer
10
+
11
+ warnings.filterwarnings("ignore")
12
+
13
+
14
+ class IPCAFactorModel(BaseAssetPricer):
15
+ def __init__(
16
+ self,
17
+ n_factors: int = 4,
18
+ *,
19
+ fit_alpha: bool = True,
20
+ ) -> None:
21
+ super().__init__()
22
+
23
+ self.n_factors = n_factors
24
+ self.fit_alpha = fit_alpha
25
+
26
+ self.ipca = None
27
+ self.gamma = None
28
+ self.factors = None
29
+
30
+ def fit(self, test_assets_xs_r: pd.Series, ranks: pd.DataFrame) -> None:
31
+ self.ipca = InstrumentedPCA(
32
+ n_factors=self.n_factors,
33
+ intercept=self.fit_alpha,
34
+ max_iter=10_000,
35
+ )
36
+ self.ipca = self.ipca.fit(X=ranks, y=test_assets_xs_r)
37
+ self.gamma, self.factors = self.ipca.get_factors(label_ind=True)
38
+
39
+ def predict(self, ranks: pd.DataFrame) -> pd.DataFrame:
40
+ pred = self.ipca.predict(X=ranks)
41
+ pred = pd.DataFrame(pred, index=ranks.index, columns=["pred"])
42
+ return pd.pivot_table(pred, index="date", columns="portfolio", values="pred")
43
+
44
+ def _get_deviations(self, test_assets_xs_r: pd.DataFrame, ranks: pd.DataFrame) -> tuple[pd.Series, pd.Series]:
45
+ pred_xs_r = self.predict(ranks)
46
+
47
+ ts_average = test_assets_xs_r.mean(axis=0)
48
+
49
+ model_deviation = ts_average - pred_xs_r.mean(axis=0)
50
+ baseline_deviation = ts_average - ts_average.mean() * np.ones(len(ts_average))
51
+
52
+ return model_deviation, baseline_deviation
53
+
54
+ def r2_score(self, test_assets_xs_r: pd.DataFrame, ranks: pd.DataFrame) -> float:
55
+ model_deviation, baseline_deviation = self._get_deviations(test_assets_xs_r, ranks)
56
+
57
+ mse_model = model_deviation.T @ model_deviation
58
+ mse_baseline = baseline_deviation.T @ baseline_deviation
59
+
60
+ return 1 - mse_model / mse_baseline
61
+
62
+ def r2_gls_score(self, test_assets_xs_r: pd.DataFrame, ranks: pd.DataFrame) -> float:
63
+ model_deviation, baseline_deviation = self._get_deviations(test_assets_xs_r, ranks)
64
+
65
+ var_r_inv = np.linalg.inv(test_assets_xs_r.cov())
66
+
67
+ mse_model = model_deviation.T @ var_r_inv @ model_deviation
68
+ mse_baseline = baseline_deviation.T @ var_r_inv @ baseline_deviation
69
+
70
+ return 1 - mse_model / mse_baseline
71
+
72
+ def get_mv_weights(self, test_assets_xs_r: pd.DataFrame, ranks: pd.DataFrame) -> float:
73
+ z_t = ranks
74
+ gamma_b = self.gamma.iloc[:, :-1] if self.fit_alpha else self.gamma
75
+ var = gamma_b.T @ z_t.T @ z_t @ gamma_b
76
+ var_inv = np.linalg.inv(var)
77
+
78
+ weights = (var_inv @ gamma_b.T @ z_t.T).T
79
+
80
+ factors_mu = self.factors.mean(axis=1)
81
+ factors_cov = self.factors.T.cov()
82
+
83
+ mv_weights = np.linalg.inv(factors_cov) @ factors_mu
84
+
85
+ factor_rets = pd.DataFrame(
86
+ index=test_assets_xs_r.index,
87
+ columns=[f"Factor{k}" for k in range(1, self.n_factors + 1)],
88
+ )
89
+ for factor in range(self.n_factors):
90
+ factor_weights = pd.pivot_table(
91
+ weights.iloc[:, factor].to_frame("weights"),
92
+ index="date",
93
+ columns="portfolio",
94
+ values="weights",
95
+ )
96
+ factor_ret = (factor_weights.to_numpy() * test_assets_xs_r.to_numpy()).sum(axis=1)
97
+ factor_rets[f"Factor{factor + 1}"] = factor_ret
98
+
99
+ return factor_rets @ mv_weights
100
+
101
+ def rmse_score(self, test_assets_xs_r: pd.DataFrame, ranks: pd.DataFrame) -> float:
102
+ model_deviation, _baseline_deviation = self._get_deviations(test_assets_xs_r, ranks)
103
+
104
+ return np.sqrt(model_deviation.T @ model_deviation / len(model_deviation))