aponyx 0.1.18__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 (104) hide show
  1. aponyx/__init__.py +14 -0
  2. aponyx/backtest/__init__.py +31 -0
  3. aponyx/backtest/adapters.py +77 -0
  4. aponyx/backtest/config.py +84 -0
  5. aponyx/backtest/engine.py +560 -0
  6. aponyx/backtest/protocols.py +101 -0
  7. aponyx/backtest/registry.py +334 -0
  8. aponyx/backtest/strategy_catalog.json +50 -0
  9. aponyx/cli/__init__.py +5 -0
  10. aponyx/cli/commands/__init__.py +8 -0
  11. aponyx/cli/commands/clean.py +349 -0
  12. aponyx/cli/commands/list.py +302 -0
  13. aponyx/cli/commands/report.py +167 -0
  14. aponyx/cli/commands/run.py +377 -0
  15. aponyx/cli/main.py +125 -0
  16. aponyx/config/__init__.py +82 -0
  17. aponyx/data/__init__.py +99 -0
  18. aponyx/data/bloomberg_config.py +306 -0
  19. aponyx/data/bloomberg_instruments.json +26 -0
  20. aponyx/data/bloomberg_securities.json +42 -0
  21. aponyx/data/cache.py +294 -0
  22. aponyx/data/fetch.py +659 -0
  23. aponyx/data/fetch_registry.py +135 -0
  24. aponyx/data/loaders.py +205 -0
  25. aponyx/data/providers/__init__.py +13 -0
  26. aponyx/data/providers/bloomberg.py +383 -0
  27. aponyx/data/providers/file.py +111 -0
  28. aponyx/data/registry.py +500 -0
  29. aponyx/data/requirements.py +96 -0
  30. aponyx/data/sample_data.py +415 -0
  31. aponyx/data/schemas.py +60 -0
  32. aponyx/data/sources.py +171 -0
  33. aponyx/data/synthetic_params.json +46 -0
  34. aponyx/data/transforms.py +336 -0
  35. aponyx/data/validation.py +308 -0
  36. aponyx/docs/__init__.py +24 -0
  37. aponyx/docs/adding_data_providers.md +682 -0
  38. aponyx/docs/cdx_knowledge_base.md +455 -0
  39. aponyx/docs/cdx_overlay_strategy.md +135 -0
  40. aponyx/docs/cli_guide.md +607 -0
  41. aponyx/docs/governance_design.md +551 -0
  42. aponyx/docs/logging_design.md +251 -0
  43. aponyx/docs/performance_evaluation_design.md +265 -0
  44. aponyx/docs/python_guidelines.md +786 -0
  45. aponyx/docs/signal_registry_usage.md +369 -0
  46. aponyx/docs/signal_suitability_design.md +558 -0
  47. aponyx/docs/visualization_design.md +277 -0
  48. aponyx/evaluation/__init__.py +11 -0
  49. aponyx/evaluation/performance/__init__.py +24 -0
  50. aponyx/evaluation/performance/adapters.py +109 -0
  51. aponyx/evaluation/performance/analyzer.py +384 -0
  52. aponyx/evaluation/performance/config.py +320 -0
  53. aponyx/evaluation/performance/decomposition.py +304 -0
  54. aponyx/evaluation/performance/metrics.py +761 -0
  55. aponyx/evaluation/performance/registry.py +327 -0
  56. aponyx/evaluation/performance/report.py +541 -0
  57. aponyx/evaluation/suitability/__init__.py +67 -0
  58. aponyx/evaluation/suitability/config.py +143 -0
  59. aponyx/evaluation/suitability/evaluator.py +389 -0
  60. aponyx/evaluation/suitability/registry.py +328 -0
  61. aponyx/evaluation/suitability/report.py +398 -0
  62. aponyx/evaluation/suitability/scoring.py +367 -0
  63. aponyx/evaluation/suitability/tests.py +303 -0
  64. aponyx/examples/01_generate_synthetic_data.py +53 -0
  65. aponyx/examples/02_fetch_data_file.py +82 -0
  66. aponyx/examples/03_fetch_data_bloomberg.py +104 -0
  67. aponyx/examples/04_compute_signal.py +164 -0
  68. aponyx/examples/05_evaluate_suitability.py +224 -0
  69. aponyx/examples/06_run_backtest.py +242 -0
  70. aponyx/examples/07_analyze_performance.py +214 -0
  71. aponyx/examples/08_visualize_results.py +272 -0
  72. aponyx/main.py +7 -0
  73. aponyx/models/__init__.py +45 -0
  74. aponyx/models/config.py +83 -0
  75. aponyx/models/indicator_transformation.json +52 -0
  76. aponyx/models/indicators.py +292 -0
  77. aponyx/models/metadata.py +447 -0
  78. aponyx/models/orchestrator.py +213 -0
  79. aponyx/models/registry.py +860 -0
  80. aponyx/models/score_transformation.json +42 -0
  81. aponyx/models/signal_catalog.json +29 -0
  82. aponyx/models/signal_composer.py +513 -0
  83. aponyx/models/signal_transformation.json +29 -0
  84. aponyx/persistence/__init__.py +16 -0
  85. aponyx/persistence/json_io.py +132 -0
  86. aponyx/persistence/parquet_io.py +378 -0
  87. aponyx/py.typed +0 -0
  88. aponyx/reporting/__init__.py +10 -0
  89. aponyx/reporting/generator.py +517 -0
  90. aponyx/visualization/__init__.py +20 -0
  91. aponyx/visualization/app.py +37 -0
  92. aponyx/visualization/plots.py +309 -0
  93. aponyx/visualization/visualizer.py +242 -0
  94. aponyx/workflows/__init__.py +18 -0
  95. aponyx/workflows/concrete_steps.py +720 -0
  96. aponyx/workflows/config.py +122 -0
  97. aponyx/workflows/engine.py +279 -0
  98. aponyx/workflows/registry.py +116 -0
  99. aponyx/workflows/steps.py +180 -0
  100. aponyx-0.1.18.dist-info/METADATA +552 -0
  101. aponyx-0.1.18.dist-info/RECORD +104 -0
  102. aponyx-0.1.18.dist-info/WHEEL +4 -0
  103. aponyx-0.1.18.dist-info/entry_points.txt +2 -0
  104. aponyx-0.1.18.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,398 @@
1
+ """
2
+ Markdown report generation for suitability evaluation results.
3
+
4
+ Generates human-readable reports with evaluation metrics, scores,
5
+ and interpretation guidance.
6
+ """
7
+
8
+ import logging
9
+ from datetime import datetime
10
+ from pathlib import Path
11
+
12
+ from aponyx.evaluation.suitability.evaluator import SuitabilityResult
13
+
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ def generate_suitability_report(
18
+ result: SuitabilityResult,
19
+ signal_id: str,
20
+ product_id: str,
21
+ ) -> str:
22
+ """
23
+ Generate Markdown report from evaluation result.
24
+
25
+ Parameters
26
+ ----------
27
+ result : SuitabilityResult
28
+ Evaluation result to document.
29
+ signal_id : str
30
+ Signal identifier (for header).
31
+ product_id : str
32
+ Product identifier matching security_id format (e.g., 'cdx_ig_5y').
33
+
34
+ Returns
35
+ -------
36
+ str
37
+ Formatted Markdown report.
38
+
39
+ Notes
40
+ -----
41
+ Report includes:
42
+ - Header with identifiers and overall decision
43
+ - Executive summary with composite score
44
+ - Four component sections with metrics and interpretation
45
+ - Composite scoring breakdown
46
+ - Decision explanation and next steps
47
+ - Footer with metadata
48
+
49
+ Examples
50
+ --------
51
+ >>> report = generate_suitability_report(result, "cdx_etf_basis", "cdx_ig_5y")
52
+ >>> print(report[:100])
53
+ """
54
+ # Decision indicator
55
+ if result.decision == "PASS":
56
+ indicator = "[PASS]"
57
+ elif result.decision == "HOLD":
58
+ indicator = "[HOLD]"
59
+ else:
60
+ indicator = "[FAIL]"
61
+
62
+ # Interpretation text for composite score
63
+ if result.composite_score >= 0.7:
64
+ interpretation = (
65
+ "The signal demonstrates strong predictive content with good data quality. "
66
+ "Proceed to strategy design and backtesting."
67
+ )
68
+ elif result.composite_score >= 0.4:
69
+ interpretation = (
70
+ "The signal shows marginal predictive content. "
71
+ "Consider refining signal construction or gathering more data before backtesting. "
72
+ "Manual review recommended."
73
+ )
74
+ else:
75
+ interpretation = (
76
+ "The signal lacks sufficient predictive content for this product. "
77
+ "Do not proceed to backtesting. Consider alternative signal specifications."
78
+ )
79
+
80
+ # Component interpretations
81
+ data_health_interp = _interpret_data_health(result)
82
+ predictive_interp = _interpret_predictive(result)
83
+ economic_interp = _interpret_economic(result)
84
+ stability_interp = _interpret_stability(result)
85
+
86
+ # Build report
87
+ report = f"""# Signal-Product Suitability Evaluation Report
88
+
89
+ **Signal:** `{signal_id}`
90
+ **Product:** `{product_id}`
91
+ **Evaluation Date:** {result.timestamp}
92
+ **Evaluator Version:** 0.1.0
93
+
94
+ ---
95
+
96
+ ## Executive Summary
97
+
98
+ ### Overall Decision: {indicator}
99
+
100
+ **Composite Score:** {result.composite_score:.3f}
101
+
102
+ {interpretation}
103
+
104
+ ---
105
+
106
+ ## Component Analysis
107
+
108
+ ### 1. Data Health Score: {result.data_health_score:.3f}
109
+
110
+ **Metrics:**
111
+ - Valid Observations: {result.valid_obs:,}
112
+ - Missing Data: {result.missing_pct:.2f}%
113
+
114
+ **Interpretation:**
115
+ {data_health_interp}
116
+
117
+ ---
118
+
119
+ ### 2. Predictive Association Score: {result.predictive_score:.3f}
120
+
121
+ **Metrics:**
122
+
123
+ | Lag | Correlation | Beta | T-Statistic |
124
+ |-----|-------------|------|-------------|
125
+ """
126
+
127
+ # Add stats for each lag
128
+ for lag in sorted(result.correlations.keys()):
129
+ corr = result.correlations.get(lag, 0.0)
130
+ beta = result.betas.get(lag, 0.0)
131
+ tstat = result.t_stats.get(lag, 0.0)
132
+ report += f"| {lag} | {corr:.4f} | {beta:.4f} | {tstat:.4f} |\n"
133
+
134
+ report += f"""
135
+ **Interpretation:**
136
+ {predictive_interp}
137
+
138
+ ---
139
+
140
+ ### 3. Economic Relevance Score: {result.economic_score:.3f}
141
+
142
+ **Metrics:**
143
+ - Effect Size: {result.effect_size_bps:.3f} bps per 1σ signal change
144
+
145
+ **Interpretation:**
146
+ {economic_interp}
147
+
148
+ ---
149
+
150
+ ### 4. Temporal Stability Score: {result.stability_score:.3f}
151
+
152
+ **Metrics:**
153
+ - Rolling Windows: {result.n_windows} windows ({result.config.rolling_window} observations each)
154
+ - Sign Consistency Ratio: {result.sign_consistency_ratio:.1%}
155
+ - Beta Coefficient of Variation: {result.beta_cv:.3f}
156
+
157
+ **Interpretation:**
158
+ {stability_interp}
159
+
160
+ ---
161
+
162
+ ## Composite Scoring
163
+
164
+ | Component | Weight | Score | Contribution |
165
+ |-----------|--------|-------|--------------|
166
+ | Data Health | {result.config.data_health_weight:.2f} | {result.data_health_score:.3f} | {result.config.data_health_weight * result.data_health_score:.3f} |
167
+ | Predictive | {result.config.predictive_weight:.2f} | {result.predictive_score:.3f} | {result.config.predictive_weight * result.predictive_score:.3f} |
168
+ | Economic | {result.config.economic_weight:.2f} | {result.economic_score:.3f} | {result.config.economic_weight * result.economic_score:.3f} |
169
+ | Stability | {result.config.stability_weight:.2f} | {result.stability_score:.3f} | {result.config.stability_weight * result.stability_score:.3f} |
170
+ | **Total** | **1.00** | — | **{result.composite_score:.3f}** |
171
+
172
+ ---
173
+
174
+ ## Decision Criteria
175
+
176
+ - **PASS** (≥ {result.config.pass_threshold:.2f}): Proceed to backtest
177
+ - **HOLD** ({result.config.hold_threshold:.2f} - {result.config.pass_threshold:.2f}): Marginal, requires judgment
178
+ - **FAIL** (< {result.config.hold_threshold:.2f}): Do not backtest
179
+
180
+ ### Recommended Next Steps
181
+
182
+ """
183
+
184
+ if result.decision == "PASS":
185
+ report += """1. Design trading strategy with entry/exit rules
186
+ 2. Configure backtest parameters (position sizing, costs)
187
+ 3. Run historical backtest with proper risk controls
188
+ 4. Analyze performance metrics and risk-adjusted returns
189
+ """
190
+ elif result.decision == "HOLD":
191
+ report += """1. Review component scores to identify weaknesses
192
+ 2. Consider signal refinements (lookback periods, normalization)
193
+ 3. Gather additional data if sample size is limited
194
+ 4. Consult with senior researchers before proceeding
195
+ 5. Document rationale for proceed/stop decision
196
+ """
197
+ else:
198
+ report += """1. Archive evaluation for reference
199
+ 2. Document why signal failed (data, predictive, economic, or stability)
200
+ 3. Consider alternative signal specifications
201
+ 4. Do NOT proceed to backtesting with current signal
202
+ """
203
+
204
+ report += f"""
205
+ ---
206
+
207
+ ## Report Metadata
208
+
209
+ **Generated:** {datetime.now().isoformat()}
210
+ **Evaluator:** aponyx.evaluation.suitability v0.1.0
211
+ **Reproducibility:** All metrics computed from aligned signal-target pairs with deterministic methods.
212
+
213
+ ---
214
+
215
+ *This report was auto-generated from suitability evaluation results. For questions about methodology, see `docs/suitability_evaluation.md`.*
216
+ """
217
+
218
+ logger.debug(
219
+ "Generated report for %s/%s: %d characters",
220
+ signal_id,
221
+ product_id,
222
+ len(report),
223
+ )
224
+
225
+ return report
226
+
227
+
228
+ def _interpret_data_health(result: SuitabilityResult) -> str:
229
+ """Generate interpretation text for data health component."""
230
+ if result.data_health_score >= 0.8:
231
+ return (
232
+ "Excellent data quality with sufficient observations and minimal missing data. "
233
+ "Sample size supports reliable statistical inference."
234
+ )
235
+ elif result.data_health_score >= 0.5:
236
+ return (
237
+ "Acceptable data quality with some missing data. "
238
+ "Results should be interpreted with awareness of data limitations."
239
+ )
240
+ else:
241
+ return (
242
+ "Data quality concerns due to insufficient observations or high missing data rate. "
243
+ "Results may not be reliable. Consider gathering more data."
244
+ )
245
+
246
+
247
+ def _interpret_predictive(result: SuitabilityResult) -> str:
248
+ """Generate interpretation text for predictive component."""
249
+ mean_abs_tstat = (
250
+ sum(abs(t) for t in result.t_stats.values()) / len(result.t_stats)
251
+ if result.t_stats
252
+ else 0.0
253
+ )
254
+
255
+ if mean_abs_tstat >= 3.0:
256
+ return (
257
+ "Strong statistical evidence of predictive relationship. "
258
+ "T-statistics exceed conventional significance thresholds with high confidence."
259
+ )
260
+ elif mean_abs_tstat >= 2.0:
261
+ return (
262
+ "Statistically significant predictive relationship at conventional levels (95% confidence). "
263
+ "Signal contains meaningful information about target movements."
264
+ )
265
+ elif mean_abs_tstat >= 1.5:
266
+ return (
267
+ "Weak but detectable statistical relationship. "
268
+ "Signal may contain information, but evidence is marginal."
269
+ )
270
+ else:
271
+ return (
272
+ "No statistically significant predictive relationship detected. "
273
+ "Signal appears uncorrelated with target movements."
274
+ )
275
+
276
+
277
+ def _interpret_economic(result: SuitabilityResult) -> str:
278
+ """Generate interpretation text for economic component."""
279
+ if result.effect_size_bps >= 2.0:
280
+ return (
281
+ "Economically meaningful effect size. "
282
+ "A 1σ signal move is associated with substantial spread changes that could generate "
283
+ "attractive risk-adjusted returns after costs."
284
+ )
285
+ elif result.effect_size_bps >= 0.5:
286
+ return (
287
+ "Moderate economic impact. "
288
+ "Effect size is detectable but may be marginal after transaction costs. "
289
+ "Careful strategy design required."
290
+ )
291
+ else:
292
+ return (
293
+ "Negligible economic impact. "
294
+ "Even if statistically significant, the effect size is too small to generate "
295
+ "meaningful P&L after realistic transaction costs."
296
+ )
297
+
298
+
299
+ def _interpret_stability(result: SuitabilityResult) -> str:
300
+ """Generate interpretation text for stability component."""
301
+ sign_ratio = result.sign_consistency_ratio
302
+ cv = result.beta_cv
303
+ n_windows = result.n_windows
304
+
305
+ # Interpret sign consistency
306
+ if sign_ratio >= 0.8:
307
+ sign_interp = "highly consistent"
308
+ elif sign_ratio >= 0.6:
309
+ sign_interp = "moderately consistent"
310
+ else:
311
+ sign_interp = "inconsistent"
312
+
313
+ # Interpret magnitude stability
314
+ if cv < 0.5:
315
+ mag_interp = "stable magnitude"
316
+ elif cv < 1.0:
317
+ mag_interp = "moderate variation"
318
+ else:
319
+ mag_interp = "high variation"
320
+
321
+ # Overall interpretation
322
+ if result.stability_score >= 0.8:
323
+ overall = (
324
+ f"Excellent temporal stability ({sign_interp} sign, {mag_interp}). "
325
+ f"The predictive relationship maintains consistent direction and magnitude "
326
+ f"across {n_windows} rolling windows, indicating robustness across different market regimes."
327
+ )
328
+ elif result.stability_score >= 0.5:
329
+ overall = (
330
+ f"Moderate temporal stability ({sign_interp} sign, {mag_interp}). "
331
+ f"The relationship shows some consistency but exhibits regime-dependent behavior. "
332
+ f"Consider investigating the source of variation before strategy design."
333
+ )
334
+ else:
335
+ overall = (
336
+ f"Low temporal stability ({sign_interp} sign, {mag_interp}). "
337
+ f"The predictive relationship is unstable across time, suggesting strong regime "
338
+ f"dependence or non-stationarity. Use caution when designing strategies."
339
+ )
340
+
341
+ return overall
342
+
343
+
344
+ def save_report(
345
+ report: str,
346
+ signal_id: str,
347
+ product_id: str,
348
+ output_dir: Path,
349
+ timestamp: str | None = None,
350
+ ) -> Path:
351
+ """
352
+ Save report to Markdown file.
353
+
354
+ Parameters
355
+ ----------
356
+ report : str
357
+ Markdown report text.
358
+ signal_id : str
359
+ Signal identifier (for filename).
360
+ product_id : str
361
+ Product identifier matching security_id format (e.g., 'cdx_ig_5y').
362
+ output_dir : Path
363
+ Directory to save report.
364
+ timestamp : str or None, optional
365
+ Timestamp string (YYYYMMDD_HHMMSS). If None, generates new timestamp.
366
+
367
+ Returns
368
+ -------
369
+ Path
370
+ Path to saved report file.
371
+
372
+ Notes
373
+ -----
374
+ Filename format: suitability_evaluation_{YYYYMMDD_HHMMSS}.md
375
+ Creates output directory if it doesn't exist.
376
+
377
+ Examples
378
+ --------
379
+ >>> from aponyx.config import EVALUATION_DIR
380
+ >>> path = save_report(report, "cdx_etf_basis", "cdx_ig_5y", EVALUATION_DIR)
381
+ >>> print(path)
382
+ """
383
+ # Create output directory
384
+ output_dir = Path(output_dir)
385
+ output_dir.mkdir(parents=True, exist_ok=True)
386
+
387
+ # Generate or use provided timestamp
388
+ if timestamp is None:
389
+ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
390
+ filename = f"suitability_evaluation_{timestamp}.md"
391
+ output_path = output_dir / filename
392
+
393
+ # Write report
394
+ output_path.write_text(report, encoding="utf-8")
395
+
396
+ logger.info("Saved report to %s", output_path)
397
+
398
+ return output_path