ml4t-diagnostic 0.1.0a1__py3-none-any.whl

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 (242) hide show
  1. ml4t/diagnostic/AGENT.md +25 -0
  2. ml4t/diagnostic/__init__.py +166 -0
  3. ml4t/diagnostic/backends/__init__.py +10 -0
  4. ml4t/diagnostic/backends/adapter.py +192 -0
  5. ml4t/diagnostic/backends/polars_backend.py +899 -0
  6. ml4t/diagnostic/caching/__init__.py +40 -0
  7. ml4t/diagnostic/caching/cache.py +331 -0
  8. ml4t/diagnostic/caching/decorators.py +131 -0
  9. ml4t/diagnostic/caching/smart_cache.py +339 -0
  10. ml4t/diagnostic/config/AGENT.md +24 -0
  11. ml4t/diagnostic/config/README.md +267 -0
  12. ml4t/diagnostic/config/__init__.py +219 -0
  13. ml4t/diagnostic/config/barrier_config.py +277 -0
  14. ml4t/diagnostic/config/base.py +301 -0
  15. ml4t/diagnostic/config/event_config.py +148 -0
  16. ml4t/diagnostic/config/feature_config.py +404 -0
  17. ml4t/diagnostic/config/multi_signal_config.py +55 -0
  18. ml4t/diagnostic/config/portfolio_config.py +215 -0
  19. ml4t/diagnostic/config/report_config.py +391 -0
  20. ml4t/diagnostic/config/sharpe_config.py +202 -0
  21. ml4t/diagnostic/config/signal_config.py +206 -0
  22. ml4t/diagnostic/config/trade_analysis_config.py +310 -0
  23. ml4t/diagnostic/config/validation.py +279 -0
  24. ml4t/diagnostic/core/__init__.py +29 -0
  25. ml4t/diagnostic/core/numba_utils.py +315 -0
  26. ml4t/diagnostic/core/purging.py +372 -0
  27. ml4t/diagnostic/core/sampling.py +471 -0
  28. ml4t/diagnostic/errors/__init__.py +205 -0
  29. ml4t/diagnostic/evaluation/AGENT.md +26 -0
  30. ml4t/diagnostic/evaluation/__init__.py +437 -0
  31. ml4t/diagnostic/evaluation/autocorrelation.py +531 -0
  32. ml4t/diagnostic/evaluation/barrier_analysis.py +1050 -0
  33. ml4t/diagnostic/evaluation/binary_metrics.py +910 -0
  34. ml4t/diagnostic/evaluation/dashboard.py +715 -0
  35. ml4t/diagnostic/evaluation/diagnostic_plots.py +1037 -0
  36. ml4t/diagnostic/evaluation/distribution/__init__.py +499 -0
  37. ml4t/diagnostic/evaluation/distribution/moments.py +299 -0
  38. ml4t/diagnostic/evaluation/distribution/tails.py +777 -0
  39. ml4t/diagnostic/evaluation/distribution/tests.py +470 -0
  40. ml4t/diagnostic/evaluation/drift/__init__.py +139 -0
  41. ml4t/diagnostic/evaluation/drift/analysis.py +432 -0
  42. ml4t/diagnostic/evaluation/drift/domain_classifier.py +517 -0
  43. ml4t/diagnostic/evaluation/drift/population_stability_index.py +310 -0
  44. ml4t/diagnostic/evaluation/drift/wasserstein.py +388 -0
  45. ml4t/diagnostic/evaluation/event_analysis.py +647 -0
  46. ml4t/diagnostic/evaluation/excursion.py +390 -0
  47. ml4t/diagnostic/evaluation/feature_diagnostics.py +873 -0
  48. ml4t/diagnostic/evaluation/feature_outcome.py +666 -0
  49. ml4t/diagnostic/evaluation/framework.py +935 -0
  50. ml4t/diagnostic/evaluation/metric_registry.py +255 -0
  51. ml4t/diagnostic/evaluation/metrics/AGENT.md +23 -0
  52. ml4t/diagnostic/evaluation/metrics/__init__.py +133 -0
  53. ml4t/diagnostic/evaluation/metrics/basic.py +160 -0
  54. ml4t/diagnostic/evaluation/metrics/conditional_ic.py +469 -0
  55. ml4t/diagnostic/evaluation/metrics/feature_outcome.py +475 -0
  56. ml4t/diagnostic/evaluation/metrics/ic_statistics.py +446 -0
  57. ml4t/diagnostic/evaluation/metrics/importance_analysis.py +338 -0
  58. ml4t/diagnostic/evaluation/metrics/importance_classical.py +375 -0
  59. ml4t/diagnostic/evaluation/metrics/importance_mda.py +371 -0
  60. ml4t/diagnostic/evaluation/metrics/importance_shap.py +715 -0
  61. ml4t/diagnostic/evaluation/metrics/information_coefficient.py +527 -0
  62. ml4t/diagnostic/evaluation/metrics/interactions.py +772 -0
  63. ml4t/diagnostic/evaluation/metrics/monotonicity.py +226 -0
  64. ml4t/diagnostic/evaluation/metrics/risk_adjusted.py +324 -0
  65. ml4t/diagnostic/evaluation/multi_signal.py +550 -0
  66. ml4t/diagnostic/evaluation/portfolio_analysis/__init__.py +83 -0
  67. ml4t/diagnostic/evaluation/portfolio_analysis/analysis.py +734 -0
  68. ml4t/diagnostic/evaluation/portfolio_analysis/metrics.py +589 -0
  69. ml4t/diagnostic/evaluation/portfolio_analysis/results.py +334 -0
  70. ml4t/diagnostic/evaluation/report_generation.py +824 -0
  71. ml4t/diagnostic/evaluation/signal_selector.py +452 -0
  72. ml4t/diagnostic/evaluation/stat_registry.py +139 -0
  73. ml4t/diagnostic/evaluation/stationarity/__init__.py +97 -0
  74. ml4t/diagnostic/evaluation/stationarity/analysis.py +518 -0
  75. ml4t/diagnostic/evaluation/stationarity/augmented_dickey_fuller.py +296 -0
  76. ml4t/diagnostic/evaluation/stationarity/kpss_test.py +308 -0
  77. ml4t/diagnostic/evaluation/stationarity/phillips_perron.py +365 -0
  78. ml4t/diagnostic/evaluation/stats/AGENT.md +43 -0
  79. ml4t/diagnostic/evaluation/stats/__init__.py +191 -0
  80. ml4t/diagnostic/evaluation/stats/backtest_overfitting.py +219 -0
  81. ml4t/diagnostic/evaluation/stats/bootstrap.py +228 -0
  82. ml4t/diagnostic/evaluation/stats/deflated_sharpe_ratio.py +591 -0
  83. ml4t/diagnostic/evaluation/stats/false_discovery_rate.py +295 -0
  84. ml4t/diagnostic/evaluation/stats/hac_standard_errors.py +108 -0
  85. ml4t/diagnostic/evaluation/stats/minimum_track_record.py +408 -0
  86. ml4t/diagnostic/evaluation/stats/moments.py +164 -0
  87. ml4t/diagnostic/evaluation/stats/rademacher_adjustment.py +436 -0
  88. ml4t/diagnostic/evaluation/stats/reality_check.py +155 -0
  89. ml4t/diagnostic/evaluation/stats/sharpe_inference.py +219 -0
  90. ml4t/diagnostic/evaluation/themes.py +330 -0
  91. ml4t/diagnostic/evaluation/threshold_analysis.py +957 -0
  92. ml4t/diagnostic/evaluation/trade_analysis.py +1136 -0
  93. ml4t/diagnostic/evaluation/trade_dashboard/__init__.py +32 -0
  94. ml4t/diagnostic/evaluation/trade_dashboard/app.py +315 -0
  95. ml4t/diagnostic/evaluation/trade_dashboard/export/__init__.py +18 -0
  96. ml4t/diagnostic/evaluation/trade_dashboard/export/csv.py +82 -0
  97. ml4t/diagnostic/evaluation/trade_dashboard/export/html.py +276 -0
  98. ml4t/diagnostic/evaluation/trade_dashboard/io.py +166 -0
  99. ml4t/diagnostic/evaluation/trade_dashboard/normalize.py +304 -0
  100. ml4t/diagnostic/evaluation/trade_dashboard/stats.py +386 -0
  101. ml4t/diagnostic/evaluation/trade_dashboard/style.py +79 -0
  102. ml4t/diagnostic/evaluation/trade_dashboard/tabs/__init__.py +21 -0
  103. ml4t/diagnostic/evaluation/trade_dashboard/tabs/patterns.py +354 -0
  104. ml4t/diagnostic/evaluation/trade_dashboard/tabs/shap_analysis.py +280 -0
  105. ml4t/diagnostic/evaluation/trade_dashboard/tabs/stat_validation.py +186 -0
  106. ml4t/diagnostic/evaluation/trade_dashboard/tabs/worst_trades.py +236 -0
  107. ml4t/diagnostic/evaluation/trade_dashboard/types.py +129 -0
  108. ml4t/diagnostic/evaluation/trade_shap/__init__.py +102 -0
  109. ml4t/diagnostic/evaluation/trade_shap/alignment.py +188 -0
  110. ml4t/diagnostic/evaluation/trade_shap/characterize.py +413 -0
  111. ml4t/diagnostic/evaluation/trade_shap/cluster.py +302 -0
  112. ml4t/diagnostic/evaluation/trade_shap/explain.py +208 -0
  113. ml4t/diagnostic/evaluation/trade_shap/hypotheses/__init__.py +23 -0
  114. ml4t/diagnostic/evaluation/trade_shap/hypotheses/generator.py +290 -0
  115. ml4t/diagnostic/evaluation/trade_shap/hypotheses/matcher.py +251 -0
  116. ml4t/diagnostic/evaluation/trade_shap/hypotheses/templates.yaml +467 -0
  117. ml4t/diagnostic/evaluation/trade_shap/models.py +386 -0
  118. ml4t/diagnostic/evaluation/trade_shap/normalize.py +116 -0
  119. ml4t/diagnostic/evaluation/trade_shap/pipeline.py +263 -0
  120. ml4t/diagnostic/evaluation/trade_shap_dashboard.py +283 -0
  121. ml4t/diagnostic/evaluation/trade_shap_diagnostics.py +588 -0
  122. ml4t/diagnostic/evaluation/validated_cv.py +535 -0
  123. ml4t/diagnostic/evaluation/visualization.py +1050 -0
  124. ml4t/diagnostic/evaluation/volatility/__init__.py +45 -0
  125. ml4t/diagnostic/evaluation/volatility/analysis.py +351 -0
  126. ml4t/diagnostic/evaluation/volatility/arch.py +258 -0
  127. ml4t/diagnostic/evaluation/volatility/garch.py +460 -0
  128. ml4t/diagnostic/integration/__init__.py +48 -0
  129. ml4t/diagnostic/integration/backtest_contract.py +671 -0
  130. ml4t/diagnostic/integration/data_contract.py +316 -0
  131. ml4t/diagnostic/integration/engineer_contract.py +226 -0
  132. ml4t/diagnostic/logging/__init__.py +77 -0
  133. ml4t/diagnostic/logging/logger.py +245 -0
  134. ml4t/diagnostic/logging/performance.py +234 -0
  135. ml4t/diagnostic/logging/progress.py +234 -0
  136. ml4t/diagnostic/logging/wandb.py +412 -0
  137. ml4t/diagnostic/metrics/__init__.py +9 -0
  138. ml4t/diagnostic/metrics/percentiles.py +128 -0
  139. ml4t/diagnostic/py.typed +1 -0
  140. ml4t/diagnostic/reporting/__init__.py +43 -0
  141. ml4t/diagnostic/reporting/base.py +130 -0
  142. ml4t/diagnostic/reporting/html_renderer.py +275 -0
  143. ml4t/diagnostic/reporting/json_renderer.py +51 -0
  144. ml4t/diagnostic/reporting/markdown_renderer.py +117 -0
  145. ml4t/diagnostic/results/AGENT.md +24 -0
  146. ml4t/diagnostic/results/__init__.py +105 -0
  147. ml4t/diagnostic/results/barrier_results/__init__.py +36 -0
  148. ml4t/diagnostic/results/barrier_results/hit_rate.py +304 -0
  149. ml4t/diagnostic/results/barrier_results/precision_recall.py +266 -0
  150. ml4t/diagnostic/results/barrier_results/profit_factor.py +297 -0
  151. ml4t/diagnostic/results/barrier_results/tearsheet.py +397 -0
  152. ml4t/diagnostic/results/barrier_results/time_to_target.py +305 -0
  153. ml4t/diagnostic/results/barrier_results/validation.py +38 -0
  154. ml4t/diagnostic/results/base.py +177 -0
  155. ml4t/diagnostic/results/event_results.py +349 -0
  156. ml4t/diagnostic/results/feature_results.py +787 -0
  157. ml4t/diagnostic/results/multi_signal_results.py +431 -0
  158. ml4t/diagnostic/results/portfolio_results.py +281 -0
  159. ml4t/diagnostic/results/sharpe_results.py +448 -0
  160. ml4t/diagnostic/results/signal_results/__init__.py +74 -0
  161. ml4t/diagnostic/results/signal_results/ic.py +581 -0
  162. ml4t/diagnostic/results/signal_results/irtc.py +110 -0
  163. ml4t/diagnostic/results/signal_results/quantile.py +392 -0
  164. ml4t/diagnostic/results/signal_results/tearsheet.py +456 -0
  165. ml4t/diagnostic/results/signal_results/turnover.py +213 -0
  166. ml4t/diagnostic/results/signal_results/validation.py +147 -0
  167. ml4t/diagnostic/signal/AGENT.md +17 -0
  168. ml4t/diagnostic/signal/__init__.py +69 -0
  169. ml4t/diagnostic/signal/_report.py +152 -0
  170. ml4t/diagnostic/signal/_utils.py +261 -0
  171. ml4t/diagnostic/signal/core.py +275 -0
  172. ml4t/diagnostic/signal/quantile.py +148 -0
  173. ml4t/diagnostic/signal/result.py +214 -0
  174. ml4t/diagnostic/signal/signal_ic.py +129 -0
  175. ml4t/diagnostic/signal/turnover.py +182 -0
  176. ml4t/diagnostic/splitters/AGENT.md +19 -0
  177. ml4t/diagnostic/splitters/__init__.py +36 -0
  178. ml4t/diagnostic/splitters/base.py +501 -0
  179. ml4t/diagnostic/splitters/calendar.py +421 -0
  180. ml4t/diagnostic/splitters/calendar_config.py +91 -0
  181. ml4t/diagnostic/splitters/combinatorial.py +1064 -0
  182. ml4t/diagnostic/splitters/config.py +322 -0
  183. ml4t/diagnostic/splitters/cpcv/__init__.py +57 -0
  184. ml4t/diagnostic/splitters/cpcv/combinations.py +119 -0
  185. ml4t/diagnostic/splitters/cpcv/partitioning.py +263 -0
  186. ml4t/diagnostic/splitters/cpcv/purge_engine.py +379 -0
  187. ml4t/diagnostic/splitters/cpcv/windows.py +190 -0
  188. ml4t/diagnostic/splitters/group_isolation.py +329 -0
  189. ml4t/diagnostic/splitters/persistence.py +316 -0
  190. ml4t/diagnostic/splitters/utils.py +207 -0
  191. ml4t/diagnostic/splitters/walk_forward.py +757 -0
  192. ml4t/diagnostic/utils/__init__.py +42 -0
  193. ml4t/diagnostic/utils/config.py +542 -0
  194. ml4t/diagnostic/utils/dependencies.py +318 -0
  195. ml4t/diagnostic/utils/sessions.py +127 -0
  196. ml4t/diagnostic/validation/__init__.py +54 -0
  197. ml4t/diagnostic/validation/dataframe.py +274 -0
  198. ml4t/diagnostic/validation/returns.py +280 -0
  199. ml4t/diagnostic/validation/timeseries.py +299 -0
  200. ml4t/diagnostic/visualization/AGENT.md +19 -0
  201. ml4t/diagnostic/visualization/__init__.py +223 -0
  202. ml4t/diagnostic/visualization/backtest/__init__.py +98 -0
  203. ml4t/diagnostic/visualization/backtest/cost_attribution.py +762 -0
  204. ml4t/diagnostic/visualization/backtest/executive_summary.py +895 -0
  205. ml4t/diagnostic/visualization/backtest/interactive_controls.py +673 -0
  206. ml4t/diagnostic/visualization/backtest/statistical_validity.py +874 -0
  207. ml4t/diagnostic/visualization/backtest/tearsheet.py +565 -0
  208. ml4t/diagnostic/visualization/backtest/template_system.py +373 -0
  209. ml4t/diagnostic/visualization/backtest/trade_plots.py +1172 -0
  210. ml4t/diagnostic/visualization/barrier_plots.py +782 -0
  211. ml4t/diagnostic/visualization/core.py +1060 -0
  212. ml4t/diagnostic/visualization/dashboards/__init__.py +36 -0
  213. ml4t/diagnostic/visualization/dashboards/base.py +582 -0
  214. ml4t/diagnostic/visualization/dashboards/importance.py +801 -0
  215. ml4t/diagnostic/visualization/dashboards/interaction.py +263 -0
  216. ml4t/diagnostic/visualization/dashboards.py +43 -0
  217. ml4t/diagnostic/visualization/data_extraction/__init__.py +48 -0
  218. ml4t/diagnostic/visualization/data_extraction/importance.py +649 -0
  219. ml4t/diagnostic/visualization/data_extraction/interaction.py +504 -0
  220. ml4t/diagnostic/visualization/data_extraction/types.py +113 -0
  221. ml4t/diagnostic/visualization/data_extraction/validation.py +66 -0
  222. ml4t/diagnostic/visualization/feature_plots.py +888 -0
  223. ml4t/diagnostic/visualization/interaction_plots.py +618 -0
  224. ml4t/diagnostic/visualization/portfolio/__init__.py +41 -0
  225. ml4t/diagnostic/visualization/portfolio/dashboard.py +514 -0
  226. ml4t/diagnostic/visualization/portfolio/drawdown_plots.py +341 -0
  227. ml4t/diagnostic/visualization/portfolio/returns_plots.py +487 -0
  228. ml4t/diagnostic/visualization/portfolio/risk_plots.py +301 -0
  229. ml4t/diagnostic/visualization/report_generation.py +1343 -0
  230. ml4t/diagnostic/visualization/signal/__init__.py +103 -0
  231. ml4t/diagnostic/visualization/signal/dashboard.py +911 -0
  232. ml4t/diagnostic/visualization/signal/event_plots.py +514 -0
  233. ml4t/diagnostic/visualization/signal/ic_plots.py +635 -0
  234. ml4t/diagnostic/visualization/signal/multi_signal_dashboard.py +974 -0
  235. ml4t/diagnostic/visualization/signal/multi_signal_plots.py +603 -0
  236. ml4t/diagnostic/visualization/signal/quantile_plots.py +625 -0
  237. ml4t/diagnostic/visualization/signal/turnover_plots.py +400 -0
  238. ml4t/diagnostic/visualization/trade_shap/__init__.py +90 -0
  239. ml4t_diagnostic-0.1.0a1.dist-info/METADATA +1044 -0
  240. ml4t_diagnostic-0.1.0a1.dist-info/RECORD +242 -0
  241. ml4t_diagnostic-0.1.0a1.dist-info/WHEEL +4 -0
  242. ml4t_diagnostic-0.1.0a1.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,219 @@
1
+ """Sharpe ratio inference: variance estimation and multiple testing adjustment.
2
+
3
+ This module implements the statistical inference framework for Sharpe ratios:
4
+
5
+ - Variance of Sharpe ratio estimator (2025 formula with autocorrelation)
6
+ - Expected maximum Sharpe under null (for multiple testing)
7
+ - Variance rescaling factors for selection bias
8
+
9
+ References
10
+ ----------
11
+ López de Prado, M., Lipton, A., & Zoonekynd, V. (2025).
12
+ "How to Use the Sharpe Ratio." ADIA Lab Research Paper Series, No. 19.
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import numpy as np
18
+ from scipy.stats import norm
19
+
20
+ # Euler-Mascheroni constant for E[max{Z}] calculation
21
+ EULER_GAMMA = 0.5772156649015329
22
+
23
+ # Standard deviation rescaling factors for maximum of K standard normals
24
+ # Source: López de Prado et al. (2025), Exhibit 3, page 13
25
+ # These are √V[max{X_k}] values for DSR variance adjustment
26
+ VARIANCE_RESCALING_FACTORS: dict[int, float] = {
27
+ 1: 1.00000,
28
+ 2: 0.82565,
29
+ 3: 0.74798,
30
+ 4: 0.70122,
31
+ 5: 0.66898,
32
+ 6: 0.64492,
33
+ 7: 0.62603,
34
+ 8: 0.61065,
35
+ 9: 0.59779,
36
+ 10: 0.58681,
37
+ 20: 0.52131,
38
+ 30: 0.49364,
39
+ 40: 0.47599,
40
+ 50: 0.46334,
41
+ 60: 0.45361,
42
+ 70: 0.44579,
43
+ 80: 0.43929,
44
+ 90: 0.43376,
45
+ 100: 0.42942,
46
+ }
47
+
48
+
49
+ def get_variance_rescaling_factor(k: int) -> float:
50
+ """Get variance rescaling factor √V[max{X_k}] for K trials.
51
+
52
+ Source: López de Prado et al. (2025), Exhibit 3, page 13.
53
+
54
+ Parameters
55
+ ----------
56
+ k : int
57
+ Number of independent trials
58
+
59
+ Returns
60
+ -------
61
+ float
62
+ Rescaling factor (uses linear interpolation for unlisted values)
63
+ """
64
+ if k in VARIANCE_RESCALING_FACTORS:
65
+ return VARIANCE_RESCALING_FACTORS[k]
66
+
67
+ keys = sorted(VARIANCE_RESCALING_FACTORS.keys())
68
+ if k < keys[0]:
69
+ return VARIANCE_RESCALING_FACTORS[keys[0]]
70
+ if k > keys[-1]:
71
+ return VARIANCE_RESCALING_FACTORS[keys[-1]]
72
+
73
+ # Linear interpolation
74
+ for i in range(len(keys) - 1):
75
+ if keys[i] <= k <= keys[i + 1]:
76
+ k1, k2 = keys[i], keys[i + 1]
77
+ v1, v2 = VARIANCE_RESCALING_FACTORS[k1], VARIANCE_RESCALING_FACTORS[k2]
78
+ return v1 + (v2 - v1) * (k - k1) / (k2 - k1)
79
+
80
+ return VARIANCE_RESCALING_FACTORS[keys[-1]]
81
+
82
+
83
+ def compute_sharpe_variance(
84
+ sharpe: float,
85
+ n_samples: int,
86
+ skewness: float,
87
+ kurtosis: float,
88
+ autocorrelation: float,
89
+ n_trials: int = 1,
90
+ ) -> float:
91
+ """Compute variance of Sharpe ratio estimator.
92
+
93
+ Implements the full 2025 formula with autocorrelation correction:
94
+
95
+ .. math::
96
+
97
+ \\sigma^2[\\widehat{SR}] = \\frac{1}{T} \\left[
98
+ \\frac{1+\\rho}{1-\\rho}
99
+ - \\left(1 + \\frac{\\rho}{1-\\rho} + \\frac{\\rho^2}{1-\\rho^2}\\right) \\gamma_3 SR
100
+ + \\frac{1+\\rho^2}{1-\\rho^2} \\frac{\\gamma_4 - 1}{4} SR^2
101
+ \\right] \\times V[\\max_k\\{X_k\\}]
102
+
103
+ When ρ=0 (i.i.d. assumption), this reduces to the 2014 formula:
104
+
105
+ .. math::
106
+
107
+ \\sigma^2[\\widehat{SR}] = \\frac{1}{T} \\left[
108
+ 1 - \\gamma_3 SR + \\frac{\\gamma_4 - 1}{4} SR^2
109
+ \\right]
110
+
111
+ Parameters
112
+ ----------
113
+ sharpe : float
114
+ Sharpe ratio (at native frequency)
115
+ n_samples : int
116
+ Number of observations (T)
117
+ skewness : float
118
+ Return skewness (γ₃)
119
+ kurtosis : float
120
+ Return kurtosis (γ₄), Pearson convention (normal = 3)
121
+ autocorrelation : float
122
+ First-order autocorrelation (ρ), must be in (-1, 1)
123
+ n_trials : int, default 1
124
+ Number of strategies (K) for variance rescaling
125
+
126
+ Returns
127
+ -------
128
+ float
129
+ Variance of Sharpe ratio estimator
130
+
131
+ Raises
132
+ ------
133
+ ValueError
134
+ If autocorrelation is not in (-1, 1).
135
+
136
+ References
137
+ ----------
138
+ López de Prado et al. (2025), Equations 2-5, pages 5-7.
139
+ """
140
+ rho = autocorrelation
141
+
142
+ # Validate autocorrelation
143
+ if abs(rho) >= 1.0:
144
+ raise ValueError(f"Autocorrelation must be in (-1, 1), got {rho}")
145
+
146
+ # Compute coefficients (from reference implementation)
147
+ # When rho=0: coef_a=1, coef_b=0, coef_c=0, so a=1, b=1, c=1 (reduces to 2014 formula)
148
+ coef_a = 1.0
149
+ if rho != 0:
150
+ coef_b = rho / (1 - rho)
151
+ coef_c = rho**2 / (1 - rho**2)
152
+ else:
153
+ coef_b = 0.0
154
+ coef_c = 0.0
155
+
156
+ a = coef_a + 2 * coef_b # = (1+ρ)/(1-ρ) - base term coefficient
157
+ b = coef_a + coef_b + coef_c # = 1 + ρ/(1-ρ) + ρ²/(1-ρ²) - skewness coefficient
158
+ c = coef_a + 2 * coef_c # = (1+ρ²)/(1-ρ²) - kurtosis coefficient
159
+
160
+ # Variance formula (Equation 5)
161
+ variance = (a - b * skewness * sharpe + c * (kurtosis - 1) / 4 * sharpe**2) / n_samples
162
+
163
+ # Apply variance rescaling for multiple testing (Equation 29)
164
+ if n_trials > 1:
165
+ rescaling_factor = get_variance_rescaling_factor(n_trials)
166
+ variance *= rescaling_factor**2
167
+
168
+ return max(variance, 0.0) # Ensure non-negative
169
+
170
+
171
+ def compute_expected_max_sharpe(n_trials: int, variance_trials: float) -> float:
172
+ """Compute expected maximum Sharpe ratio under null hypothesis.
173
+
174
+ .. math::
175
+
176
+ E[\\max_k\\{\\widehat{SR}_k\\}] \\approx \\sqrt{V[\\{\\widehat{SR}_k\\}]}
177
+ \\left((1-\\gamma) \\Phi^{-1}(1-1/K) + \\gamma \\Phi^{-1}(1-1/(Ke))\\right)
178
+
179
+ where γ is the Euler-Mascheroni constant ≈ 0.5772.
180
+
181
+ Parameters
182
+ ----------
183
+ n_trials : int
184
+ Number of strategies tested (K)
185
+ variance_trials : float
186
+ Cross-sectional variance of Sharpe ratios: Var[{SR_1, ..., SR_K}]
187
+
188
+ Returns
189
+ -------
190
+ float
191
+ Expected maximum Sharpe ratio E[max{SR}]
192
+
193
+ References
194
+ ----------
195
+ López de Prado et al. (2025), Equation 26, page 13.
196
+ Bailey & López de Prado (2014), Appendix A.1, Equation 6.
197
+ """
198
+ if n_trials <= 1:
199
+ return 0.0
200
+
201
+ if variance_trials <= 0:
202
+ return 0.0
203
+
204
+ # E[max{Z}] for K i.i.d. standard normals (Equation 26)
205
+ quantile_1 = norm.ppf(1 - 1 / n_trials)
206
+ quantile_2 = norm.ppf(1 - 1 / (n_trials * np.e))
207
+ e_max_z = (1 - EULER_GAMMA) * quantile_1 + EULER_GAMMA * quantile_2
208
+
209
+ # Scale by standard deviation of Sharpe ratios across trials
210
+ return float(np.sqrt(variance_trials) * e_max_z)
211
+
212
+
213
+ __all__ = [
214
+ "EULER_GAMMA",
215
+ "VARIANCE_RESCALING_FACTORS",
216
+ "get_variance_rescaling_factor",
217
+ "compute_sharpe_variance",
218
+ "compute_expected_max_sharpe",
219
+ ]
@@ -0,0 +1,330 @@
1
+ """Plotly themes and styling for ml4t-diagnostic visualizations.
2
+
3
+ This module provides consistent theming across all ml4t-diagnostic visualizations,
4
+ including color schemes, layout templates, and accessibility options.
5
+ """
6
+
7
+ from typing import Any
8
+
9
+ # Financial color schemes
10
+ FINANCIAL_COLORS = {
11
+ # Returns and performance
12
+ "positive": "#00CC88", # Green for gains
13
+ "negative": "#FF4444", # Red for losses
14
+ "neutral": "#888888", # Gray for neutral
15
+ # Data series
16
+ "primary": "#3366CC", # Blue
17
+ "secondary": "#FF9900", # Orange
18
+ "tertiary": "#109618", # Dark green
19
+ "quaternary": "#990099", # Purple
20
+ # UI elements
21
+ "background": "#F8F9FA", # Light gray
22
+ "paper": "#FFFFFF", # White
23
+ "grid": "#E0E0E0", # Grid lines
24
+ "text": "#333333", # Dark gray text
25
+ "subtitle": "#666666", # Medium gray
26
+ # Quantiles (5-level)
27
+ "q1": "#D32F2F", # Dark red (lowest)
28
+ "q2": "#F57C00", # Orange
29
+ "q3": "#FBC02D", # Yellow
30
+ "q4": "#689F38", # Light green
31
+ "q5": "#388E3C", # Dark green (highest)
32
+ }
33
+
34
+ # Colorblind-friendly palette
35
+ COLORBLIND_SAFE = {
36
+ "blue": "#0173B2",
37
+ "orange": "#DE8F05",
38
+ "green": "#029E73",
39
+ "red": "#CC78BC",
40
+ "purple": "#5B4E96",
41
+ "brown": "#A65628",
42
+ "pink": "#F0E442",
43
+ "gray": "#999999",
44
+ }
45
+
46
+ # Layout templates
47
+ DEFAULT_TEMPLATE = {
48
+ "layout": {
49
+ # Typography
50
+ "font": {
51
+ "family": "Arial, Helvetica, sans-serif",
52
+ "size": 12,
53
+ "color": FINANCIAL_COLORS["text"],
54
+ },
55
+ "title": {
56
+ "font": {"size": 16, "color": FINANCIAL_COLORS["text"]},
57
+ "x": 0.5,
58
+ "xanchor": "center",
59
+ },
60
+ # Colors
61
+ "plot_bgcolor": FINANCIAL_COLORS["background"],
62
+ "paper_bgcolor": FINANCIAL_COLORS["paper"],
63
+ # Margins
64
+ "margin": {"l": 60, "r": 30, "t": 60, "b": 60},
65
+ # Hover
66
+ "hovermode": "closest",
67
+ "hoverlabel": {"bgcolor": "white", "font_size": 12, "font_family": "Arial"},
68
+ # Grid and axes
69
+ "xaxis": {
70
+ "gridcolor": FINANCIAL_COLORS["grid"],
71
+ "zeroline": False,
72
+ "showgrid": True,
73
+ "showline": True,
74
+ "linecolor": FINANCIAL_COLORS["grid"],
75
+ "tickfont": {"size": 11},
76
+ },
77
+ "yaxis": {
78
+ "gridcolor": FINANCIAL_COLORS["grid"],
79
+ "zeroline": True,
80
+ "zerolinecolor": FINANCIAL_COLORS["grid"],
81
+ "showgrid": True,
82
+ "showline": True,
83
+ "linecolor": FINANCIAL_COLORS["grid"],
84
+ "tickfont": {"size": 11},
85
+ },
86
+ # Legend
87
+ "legend": {
88
+ "bgcolor": "rgba(255,255,255,0.9)",
89
+ "bordercolor": FINANCIAL_COLORS["grid"],
90
+ "borderwidth": 1,
91
+ },
92
+ },
93
+ }
94
+
95
+ # Dark theme for dashboards
96
+ DARK_TEMPLATE = {
97
+ "layout": {
98
+ # Typography
99
+ "font": {"family": "Arial, Helvetica, sans-serif", "size": 12, "color": "#E0E0E0"},
100
+ "title": {"font": {"size": 16, "color": "#FFFFFF"}, "x": 0.5, "xanchor": "center"},
101
+ # Colors
102
+ "plot_bgcolor": "#1E1E1E",
103
+ "paper_bgcolor": "#121212",
104
+ # Grid and axes
105
+ "xaxis": {
106
+ "gridcolor": "#333333",
107
+ "zeroline": False,
108
+ "showgrid": True,
109
+ "showline": True,
110
+ "linecolor": "#444444",
111
+ "tickfont": {"color": "#B0B0B0"},
112
+ },
113
+ "yaxis": {
114
+ "gridcolor": "#333333",
115
+ "zeroline": True,
116
+ "zerolinecolor": "#444444",
117
+ "showgrid": True,
118
+ "showline": True,
119
+ "linecolor": "#444444",
120
+ "tickfont": {"color": "#B0B0B0"},
121
+ },
122
+ # Legend
123
+ "legend": {
124
+ "bgcolor": "rgba(30,30,30,0.9)",
125
+ "bordercolor": "#444444",
126
+ "borderwidth": 1,
127
+ "font": {"color": "#E0E0E0"},
128
+ },
129
+ },
130
+ }
131
+
132
+ # Print-friendly template
133
+ PRINT_TEMPLATE = {
134
+ "layout": {
135
+ # Black and white only
136
+ "font": {"family": "Times New Roman, serif", "size": 10, "color": "black"},
137
+ "title": {"font": {"size": 14, "color": "black"}, "x": 0.5, "xanchor": "center"},
138
+ # White background
139
+ "plot_bgcolor": "white",
140
+ "paper_bgcolor": "white",
141
+ # Minimal margins for printing
142
+ "margin": {"l": 40, "r": 20, "t": 40, "b": 40},
143
+ # High contrast grid
144
+ "xaxis": {
145
+ "gridcolor": "#CCCCCC",
146
+ "zeroline": True,
147
+ "zerolinecolor": "black",
148
+ "showgrid": True,
149
+ "showline": True,
150
+ "linecolor": "black",
151
+ "linewidth": 1,
152
+ },
153
+ "yaxis": {
154
+ "gridcolor": "#CCCCCC",
155
+ "zeroline": True,
156
+ "zerolinecolor": "black",
157
+ "showgrid": True,
158
+ "showline": True,
159
+ "linecolor": "black",
160
+ "linewidth": 1,
161
+ },
162
+ # No shadows or effects
163
+ "legend": {"bgcolor": "white", "bordercolor": "black", "borderwidth": 1},
164
+ },
165
+ }
166
+
167
+
168
+ def get_color_scale(n_colors: int, scheme: str = "diverging") -> list[str]:
169
+ """Get a color scale for visualizations.
170
+
171
+ Parameters
172
+ ----------
173
+ n_colors : int
174
+ Number of colors needed
175
+ scheme : str, default "diverging"
176
+ Color scheme type: "diverging", "sequential", "quantile", "colorblind"
177
+
178
+ Returns:
179
+ -------
180
+ list[str]
181
+ List of hex color codes
182
+ """
183
+ if scheme == "diverging":
184
+ # Red to green through white
185
+ if n_colors == 2:
186
+ return [FINANCIAL_COLORS["negative"], FINANCIAL_COLORS["positive"]]
187
+ if n_colors == 3:
188
+ return [
189
+ FINANCIAL_COLORS["negative"],
190
+ FINANCIAL_COLORS["neutral"],
191
+ FINANCIAL_COLORS["positive"],
192
+ ]
193
+ # Use plotly's RdYlGn scale
194
+ import plotly.colors as pc
195
+
196
+ return pc.sample_colorscale("RdYlGn", n_colors)
197
+
198
+ if scheme == "sequential":
199
+ # Blue gradient
200
+ if n_colors <= 5:
201
+ return ["#E3F2FD", "#90CAF9", "#42A5F5", "#1E88E5", "#0D47A1"][:n_colors]
202
+ import plotly.colors as pc
203
+
204
+ return pc.sample_colorscale("Blues", n_colors)
205
+
206
+ if scheme == "quantile":
207
+ # Specific colors for quantiles
208
+ quantile_colors = [
209
+ FINANCIAL_COLORS["q1"],
210
+ FINANCIAL_COLORS["q2"],
211
+ FINANCIAL_COLORS["q3"],
212
+ FINANCIAL_COLORS["q4"],
213
+ FINANCIAL_COLORS["q5"],
214
+ ]
215
+ if n_colors <= 5:
216
+ return quantile_colors[:n_colors]
217
+ # Interpolate if more than 5
218
+ import plotly.colors as pc
219
+
220
+ return pc.sample_colorscale("RdYlGn", n_colors)
221
+
222
+ if scheme == "colorblind":
223
+ # Colorblind-safe palette
224
+ colors = list(COLORBLIND_SAFE.values())
225
+ if n_colors <= len(colors):
226
+ return colors[:n_colors]
227
+ # Cycle through if need more
228
+ return (colors * (n_colors // len(colors) + 1))[:n_colors]
229
+
230
+ # Default categorical
231
+ import plotly.express as px
232
+
233
+ return px.colors.qualitative.Set3[:n_colors]
234
+
235
+
236
+ def apply_theme(fig: Any, theme: str = "default") -> Any:
237
+ """Apply a theme to a Plotly figure.
238
+
239
+ Parameters
240
+ ----------
241
+ fig : plotly.graph_objects.Figure
242
+ Figure to apply theme to
243
+ theme : str, default "default"
244
+ Theme name: "default", "dark", "print", "colorblind"
245
+
246
+ Returns:
247
+ -------
248
+ plotly.graph_objects.Figure
249
+ Figure with theme applied
250
+ """
251
+ if theme == "default":
252
+ template = DEFAULT_TEMPLATE
253
+ elif theme == "dark":
254
+ template = DARK_TEMPLATE
255
+ elif theme == "print":
256
+ template = PRINT_TEMPLATE
257
+ elif theme == "colorblind":
258
+ # Apply colorblind-safe colors to existing theme
259
+ template = DEFAULT_TEMPLATE.copy()
260
+ # Would need to update trace colors here
261
+ else:
262
+ raise ValueError(f"Unknown theme: {theme}")
263
+
264
+ # Apply template
265
+ fig.update_layout(template["layout"])
266
+
267
+ return fig
268
+
269
+
270
+ def format_percentage(value: float, decimals: int = 1) -> str:
271
+ """Format a value as percentage for display.
272
+
273
+ Parameters
274
+ ----------
275
+ value : float
276
+ Value to format (0.05 = 5%)
277
+ decimals : int, default 1
278
+ Number of decimal places
279
+
280
+ Returns:
281
+ -------
282
+ str
283
+ Formatted percentage string
284
+ """
285
+ return f"{value * 100:.{decimals}f}%"
286
+
287
+
288
+ def format_currency(value: float, currency: str = "$", decimals: int = 0) -> str:
289
+ """Format a value as currency for display.
290
+
291
+ Parameters
292
+ ----------
293
+ value : float
294
+ Value to format
295
+ currency : str, default "$"
296
+ Currency symbol
297
+ decimals : int, default 0
298
+ Number of decimal places
299
+
300
+ Returns:
301
+ -------
302
+ str
303
+ Formatted currency string
304
+ """
305
+ if decimals == 0:
306
+ return f"{currency}{value:,.0f}"
307
+ return f"{currency}{value:,.{decimals}f}"
308
+
309
+
310
+ # Accessibility helpers
311
+ def add_pattern_overlay(fig: Any, _trace_index: int, _pattern: str = "diagonal") -> Any:
312
+ """Add pattern overlay for better accessibility.
313
+
314
+ Parameters
315
+ ----------
316
+ fig : plotly.graph_objects.Figure
317
+ Figure to modify
318
+ trace_index : int
319
+ Index of trace to add pattern to
320
+ pattern : str, default "diagonal"
321
+ Pattern type: "diagonal", "vertical", "horizontal", "dot"
322
+
323
+ Returns:
324
+ -------
325
+ plotly.graph_objects.Figure
326
+ Modified figure
327
+ """
328
+ # This would add SVG patterns for accessibility
329
+ # Implementation depends on Plotly version and trace type
330
+ return fig