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,400 @@
1
+ """Turnover and signal stability visualization plots.
2
+
3
+ This module provides interactive Plotly visualizations for turnover analysis:
4
+ - plot_top_bottom_turnover: Turnover rates for extreme quantiles
5
+ - plot_autocorrelation: Signal rank autocorrelation by lag
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ from typing import TYPE_CHECKING
11
+
12
+ import numpy as np
13
+ import plotly.graph_objects as go
14
+
15
+ from ml4t.diagnostic.visualization.core import (
16
+ create_base_figure,
17
+ format_percentage,
18
+ get_theme_config,
19
+ validate_theme,
20
+ )
21
+
22
+ if TYPE_CHECKING:
23
+ from ml4t.diagnostic.results.signal_results import TurnoverAnalysisResult
24
+
25
+
26
+ def plot_top_bottom_turnover(
27
+ turnover_result: TurnoverAnalysisResult,
28
+ show_all_quantiles: bool = False,
29
+ theme: str | None = None,
30
+ width: int | None = None,
31
+ height: int | None = None,
32
+ ) -> go.Figure:
33
+ """Plot turnover rates for extreme (top/bottom) quantiles.
34
+
35
+ Parameters
36
+ ----------
37
+ turnover_result : TurnoverAnalysisResult
38
+ Turnover analysis result from SignalAnalysis.compute_turnover_analysis()
39
+ show_all_quantiles : bool, default False
40
+ Show all quantiles instead of just top and bottom
41
+ theme : str | None
42
+ Plot theme (default, dark, print, presentation)
43
+ width : int | None
44
+ Figure width in pixels
45
+ height : int | None
46
+ Figure height in pixels
47
+
48
+ Returns
49
+ -------
50
+ go.Figure
51
+ Interactive Plotly figure
52
+
53
+ Examples
54
+ --------
55
+ >>> turnover_result = analyzer.compute_turnover_analysis()
56
+ >>> fig = plot_top_bottom_turnover(turnover_result)
57
+ >>> fig.show()
58
+ """
59
+ theme = validate_theme(theme)
60
+ theme_config = get_theme_config(theme)
61
+
62
+ # Get periods and quantiles
63
+ periods = list(turnover_result.quantile_turnover.keys())
64
+
65
+ # Create figure
66
+ fig = create_base_figure(
67
+ title="Quantile Turnover Rates",
68
+ xaxis_title="Period",
69
+ yaxis_title="Turnover Rate",
70
+ width=width or theme_config["defaults"]["bar_height"],
71
+ height=height or theme_config["defaults"]["bar_height"],
72
+ theme=theme,
73
+ )
74
+
75
+ if show_all_quantiles:
76
+ # Show all quantiles
77
+ first_period = periods[0]
78
+ quantile_labels = list(turnover_result.quantile_turnover[first_period].keys())
79
+
80
+ # Use colorway for all quantiles
81
+ n_quantiles = len(quantile_labels)
82
+ colors = (theme_config["colorway"] * ((n_quantiles // len(theme_config["colorway"])) + 1))[
83
+ :n_quantiles
84
+ ]
85
+
86
+ for i, q_label in enumerate(quantile_labels):
87
+ turnover_values = [
88
+ turnover_result.quantile_turnover[p].get(q_label, 0) for p in periods
89
+ ]
90
+
91
+ fig.add_trace(
92
+ go.Bar(
93
+ x=periods,
94
+ y=turnover_values,
95
+ name=q_label,
96
+ marker_color=colors[i],
97
+ hovertemplate=f"{q_label}<br>Period: %{{x}}<br>Turnover: %{{y:.2%}}<extra></extra>",
98
+ )
99
+ )
100
+
101
+ fig.update_layout(barmode="group")
102
+ else:
103
+ # Just top and bottom
104
+ top_turnover = [turnover_result.top_quantile_turnover.get(p, 0) for p in periods]
105
+ bottom_turnover = [turnover_result.bottom_quantile_turnover.get(p, 0) for p in periods]
106
+ mean_turnover = [turnover_result.mean_turnover.get(p, 0) for p in periods]
107
+
108
+ # Top quantile
109
+ fig.add_trace(
110
+ go.Bar(
111
+ x=periods,
112
+ y=top_turnover,
113
+ name="Top Quantile",
114
+ marker_color=theme_config["colorway"][2], # Green
115
+ hovertemplate="Top Quantile<br>Period: %{x}<br>Turnover: %{y:.2%}<extra></extra>",
116
+ )
117
+ )
118
+
119
+ # Bottom quantile
120
+ fig.add_trace(
121
+ go.Bar(
122
+ x=periods,
123
+ y=bottom_turnover,
124
+ name="Bottom Quantile",
125
+ marker_color=theme_config["colorway"][1], # Red
126
+ hovertemplate="Bottom Quantile<br>Period: %{x}<br>Turnover: %{y:.2%}<extra></extra>",
127
+ )
128
+ )
129
+
130
+ # Mean turnover line
131
+ fig.add_trace(
132
+ go.Scatter(
133
+ x=periods,
134
+ y=mean_turnover,
135
+ mode="lines+markers",
136
+ name="Mean Turnover",
137
+ line={"color": theme_config["colorway"][0], "width": 2, "dash": "dash"},
138
+ marker={"size": 8},
139
+ hovertemplate="Mean<br>Period: %{x}<br>Turnover: %{y:.2%}<extra></extra>",
140
+ )
141
+ )
142
+
143
+ fig.update_layout(barmode="group")
144
+
145
+ # Format y-axis as percentage
146
+ fig.update_yaxes(tickformat=".0%")
147
+
148
+ # Half-life annotation
149
+ half_lives = turnover_result.half_life
150
+ if half_lives:
151
+ hl_text = "<b>Signal Half-Life:</b><br>"
152
+ for period, hl in half_lives.items():
153
+ if hl is not None:
154
+ hl_text += f"{period}: {hl:.1f} periods<br>"
155
+ else:
156
+ hl_text += f"{period}: N/A<br>"
157
+
158
+ fig.add_annotation(
159
+ text=hl_text,
160
+ xref="paper",
161
+ yref="paper",
162
+ x=0.98,
163
+ y=0.98,
164
+ showarrow=False,
165
+ bgcolor="rgba(255,255,255,0.8)" if theme != "dark" else "rgba(50,50,50,0.8)",
166
+ bordercolor="gray",
167
+ borderwidth=1,
168
+ align="left",
169
+ xanchor="right",
170
+ yanchor="top",
171
+ )
172
+
173
+ return fig
174
+
175
+
176
+ def plot_autocorrelation(
177
+ turnover_result: TurnoverAnalysisResult,
178
+ period: str | None = None,
179
+ max_lags: int | None = None,
180
+ show_significance: bool = True,
181
+ theme: str | None = None,
182
+ width: int | None = None,
183
+ height: int | None = None,
184
+ ) -> go.Figure:
185
+ """Plot signal rank autocorrelation by lag.
186
+
187
+ Parameters
188
+ ----------
189
+ turnover_result : TurnoverAnalysisResult
190
+ Turnover analysis result from SignalAnalysis.compute_turnover_analysis()
191
+ period : str | None
192
+ Period to plot. If None, uses first period.
193
+ max_lags : int | None
194
+ Maximum number of lags to show. If None, shows all available.
195
+ show_significance : bool, default True
196
+ Show significance bands (±1.96/√n for 95% CI)
197
+ theme : str | None
198
+ Plot theme
199
+ width : int | None
200
+ Figure width
201
+ height : int | None
202
+ Figure height
203
+
204
+ Returns
205
+ -------
206
+ go.Figure
207
+ Interactive Plotly figure
208
+
209
+ Examples
210
+ --------
211
+ >>> turnover_result = analyzer.compute_turnover_analysis()
212
+ >>> fig = plot_autocorrelation(turnover_result, period="5D")
213
+ >>> fig.show()
214
+ """
215
+ theme = validate_theme(theme)
216
+ theme_config = get_theme_config(theme)
217
+
218
+ # Get period data
219
+ periods = list(turnover_result.autocorrelation.keys())
220
+ if period is None:
221
+ period = periods[0]
222
+ elif period not in periods:
223
+ raise ValueError(f"Period '{period}' not found. Available: {periods}")
224
+
225
+ ac_values = turnover_result.autocorrelation[period]
226
+ lags = turnover_result.autocorrelation_lags
227
+
228
+ if max_lags is not None:
229
+ lags = lags[:max_lags]
230
+ ac_values = ac_values[:max_lags]
231
+
232
+ # Create figure
233
+ fig = create_base_figure(
234
+ title=f"Signal Rank Autocorrelation ({period})",
235
+ xaxis_title="Lag (Periods)",
236
+ yaxis_title="Autocorrelation",
237
+ width=width or theme_config["defaults"]["bar_height"],
238
+ height=height or theme_config["defaults"]["bar_height"],
239
+ theme=theme,
240
+ )
241
+
242
+ # Bar chart for autocorrelation
243
+ colors = [
244
+ theme_config["colorway"][0] if ac >= 0 else theme_config["colorway"][1] for ac in ac_values
245
+ ]
246
+
247
+ fig.add_trace(
248
+ go.Bar(
249
+ x=lags,
250
+ y=ac_values,
251
+ marker_color=colors,
252
+ hovertemplate="Lag %{x}<br>AC: %{y:.4f}<extra></extra>",
253
+ name="Autocorrelation",
254
+ )
255
+ )
256
+
257
+ # Zero line
258
+ fig.add_hline(y=0, line_dash="solid", line_color="gray", opacity=0.5)
259
+
260
+ # Significance bands
261
+ if show_significance:
262
+ # Approximate CI: ±1.96/√n where n is number of observations
263
+ # Using a reasonable default if we don't have exact n
264
+ n_obs = 252 # Approximate trading days
265
+ ci = 1.96 / np.sqrt(n_obs)
266
+
267
+ fig.add_hline(
268
+ y=ci,
269
+ line_dash="dash",
270
+ line_color="red",
271
+ opacity=0.5,
272
+ annotation_text="95% CI",
273
+ annotation_position="right",
274
+ )
275
+ fig.add_hline(
276
+ y=-ci,
277
+ line_dash="dash",
278
+ line_color="red",
279
+ opacity=0.5,
280
+ )
281
+
282
+ # Half-life annotation
283
+ half_life = turnover_result.half_life.get(period)
284
+ mean_ac = turnover_result.mean_autocorrelation.get(period, 0)
285
+
286
+ summary_text = (
287
+ f"<b>Signal Persistence:</b><br>"
288
+ f"Mean AC (Lag 1-5): {mean_ac:.4f}<br>"
289
+ f"Half-life: {half_life:.1f} periods"
290
+ if half_life
291
+ else "Half-life: N/A"
292
+ )
293
+
294
+ fig.add_annotation(
295
+ text=summary_text,
296
+ xref="paper",
297
+ yref="paper",
298
+ x=0.98,
299
+ y=0.98,
300
+ showarrow=False,
301
+ bgcolor="rgba(255,255,255,0.8)" if theme != "dark" else "rgba(50,50,50,0.8)",
302
+ bordercolor="gray",
303
+ borderwidth=1,
304
+ align="left",
305
+ xanchor="right",
306
+ yanchor="top",
307
+ )
308
+
309
+ # Decay interpretation
310
+ if len(ac_values) > 0:
311
+ ac0 = ac_values[0]
312
+ decay_rate = "fast" if ac0 < 0.5 else "moderate" if ac0 < 0.8 else "slow"
313
+
314
+ fig.add_annotation(
315
+ text=f"Signal decay: {decay_rate}",
316
+ xref="paper",
317
+ yref="paper",
318
+ x=0.5,
319
+ y=-0.15,
320
+ showarrow=False,
321
+ font={"size": 12},
322
+ )
323
+
324
+ return fig
325
+
326
+
327
+ def plot_turnover_heatmap(
328
+ turnover_result: TurnoverAnalysisResult,
329
+ theme: str | None = None,
330
+ width: int | None = None,
331
+ height: int | None = None,
332
+ ) -> go.Figure:
333
+ """Plot turnover rates as a heatmap (quantile × period).
334
+
335
+ Parameters
336
+ ----------
337
+ turnover_result : TurnoverAnalysisResult
338
+ Turnover analysis result from SignalAnalysis.compute_turnover_analysis()
339
+ theme : str | None
340
+ Plot theme
341
+ width : int | None
342
+ Figure width
343
+ height : int | None
344
+ Figure height
345
+
346
+ Returns
347
+ -------
348
+ go.Figure
349
+ Interactive Plotly heatmap
350
+ """
351
+ theme = validate_theme(theme)
352
+ theme_config = get_theme_config(theme)
353
+
354
+ # Build matrix
355
+ periods = list(turnover_result.quantile_turnover.keys())
356
+ first_period = periods[0]
357
+ quantile_labels = list(turnover_result.quantile_turnover[first_period].keys())
358
+
359
+ z_rows = []
360
+ for q_label in quantile_labels:
361
+ row = [turnover_result.quantile_turnover[p].get(q_label, 0) for p in periods]
362
+ z_rows.append(row)
363
+
364
+ z_matrix = np.array(z_rows)
365
+
366
+ # Create figure
367
+ fig = create_base_figure(
368
+ title="Turnover Rates by Quantile and Period",
369
+ xaxis_title="Period",
370
+ yaxis_title="Quantile",
371
+ width=width or theme_config["defaults"]["heatmap_height"] - 200,
372
+ height=height or 300 + 30 * len(quantile_labels),
373
+ theme=theme,
374
+ )
375
+
376
+ fig.add_trace(
377
+ go.Heatmap(
378
+ z=z_matrix,
379
+ x=periods,
380
+ y=quantile_labels,
381
+ colorscale="Oranges",
382
+ colorbar={"title": "Turnover", "tickformat": ".0%"},
383
+ hovertemplate="Quantile: %{y}<br>Period: %{x}<br>Turnover: %{z:.2%}<extra></extra>",
384
+ )
385
+ )
386
+
387
+ # Add text annotations
388
+ for i, q_label in enumerate(quantile_labels):
389
+ for j, period in enumerate(periods):
390
+ val = z_matrix[i, j]
391
+ text_color = "white" if val > 0.5 else "black"
392
+ fig.add_annotation(
393
+ x=period,
394
+ y=q_label,
395
+ text=format_percentage(val),
396
+ showarrow=False,
397
+ font={"size": 10, "color": text_color},
398
+ )
399
+
400
+ return fig
@@ -0,0 +1,90 @@
1
+ """Trade SHAP visualization components.
2
+
3
+ This package contains visualization and dashboard functions for Trade SHAP analysis.
4
+
5
+ Main Functions:
6
+ - run_diagnostics_dashboard: Run the interactive Streamlit dashboard
7
+ - run_polished_dashboard: Run the polished version of the dashboard
8
+ - extract_trade_returns: Extract trade returns from results
9
+ - extract_trade_data: Extract trade data for visualization
10
+ - export_trades_to_csv: Export trades to CSV format
11
+ - export_patterns_to_csv: Export patterns to CSV format
12
+ - export_full_report_html: Export full HTML report
13
+
14
+ Visualization Tabs:
15
+ - render_statistical_validation_tab: Statistical validation visualizations
16
+ - render_worst_trades_tab: Worst trades analysis visualizations
17
+ - render_shap_analysis_tab: SHAP analysis visualizations
18
+ - render_patterns_tab: Error pattern visualizations
19
+
20
+ Example:
21
+ >>> from ml4t.diagnostic.visualization.trade_shap import run_diagnostics_dashboard
22
+ >>> run_diagnostics_dashboard(result)
23
+
24
+ Note:
25
+ All functions in this module require Streamlit to be installed. Functions are
26
+ lazy-loaded to avoid slow import time when Streamlit is not needed.
27
+ """
28
+
29
+ # Lazy loading to avoid Streamlit import overhead (~1.3s) at module load time
30
+ # Functions are imported on first access
31
+ _dashboard_module = None
32
+
33
+ # All public names for the module
34
+ _LAZY_IMPORTS = {
35
+ "cached_extract_trade_data",
36
+ "cached_extract_trade_returns",
37
+ "display_data_summary",
38
+ "export_full_report_html",
39
+ "export_patterns_to_csv",
40
+ "export_trades_to_csv",
41
+ "extract_trade_data",
42
+ "extract_trade_returns",
43
+ "load_data_from_file",
44
+ "render_patterns_tab",
45
+ "render_shap_analysis_tab",
46
+ "render_statistical_validation_tab",
47
+ "render_worst_trades_tab",
48
+ "run_diagnostics_dashboard",
49
+ "run_polished_dashboard",
50
+ "serialize_result_for_cache",
51
+ }
52
+
53
+
54
+ def __getattr__(name: str):
55
+ """Lazy load dashboard functions to avoid importing Streamlit at module load."""
56
+ global _dashboard_module
57
+
58
+ if name in _LAZY_IMPORTS:
59
+ if _dashboard_module is None:
60
+ from ml4t.diagnostic.evaluation import trade_shap_dashboard as _mod
61
+
62
+ _dashboard_module = _mod
63
+ return getattr(_dashboard_module, name)
64
+
65
+ raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
66
+
67
+
68
+ __all__ = [
69
+ # Main dashboard functions
70
+ "run_diagnostics_dashboard",
71
+ "run_polished_dashboard",
72
+ # Data extraction
73
+ "extract_trade_returns",
74
+ "extract_trade_data",
75
+ "load_data_from_file",
76
+ "display_data_summary",
77
+ # Visualization tabs
78
+ "render_statistical_validation_tab",
79
+ "render_worst_trades_tab",
80
+ "render_shap_analysis_tab",
81
+ "render_patterns_tab",
82
+ # Export functions
83
+ "export_trades_to_csv",
84
+ "export_patterns_to_csv",
85
+ "export_full_report_html",
86
+ # Caching utilities
87
+ "serialize_result_for_cache",
88
+ "cached_extract_trade_returns",
89
+ "cached_extract_trade_data",
90
+ ]