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,242 @@
1
+ """
2
+ Execute backtest for signal using strategy from catalog.
3
+
4
+ Prerequisites
5
+ -------------
6
+ Signals saved from signal computation (04_compute_signal.py):
7
+ - Signal files exist in data/workflows/signals/{signal_name}.parquet
8
+ CDX spread data available from registry.
9
+ Strategy catalog configured in backtest/strategy_catalog.json.
10
+
11
+ Outputs
12
+ -------
13
+ BacktestResult with positions and P&L:
14
+ - positions: DataFrame with signal, position, days_held, spread
15
+ - pnl: DataFrame with spread_pnl, cost, net_pnl, cumulative_pnl
16
+ - metadata: Config and execution details
17
+ Backtest results saved to data/workflows/backtests/{signal_name}_{strategy}_*.parquet.
18
+
19
+ Examples
20
+ --------
21
+ Run from project root:
22
+ python -m aponyx.examples.06_run_backtest
23
+
24
+ Expected output: BacktestResult with positions and P&L history.
25
+ Results saved to data/workflows/backtests/spread_momentum_balanced_*.parquet.
26
+ """
27
+
28
+ import pandas as pd
29
+
30
+ from aponyx.config import (
31
+ REGISTRY_PATH,
32
+ DATA_DIR,
33
+ DATA_WORKFLOWS_DIR,
34
+ STRATEGY_CATALOG_PATH,
35
+ )
36
+ from aponyx.data.registry import DataRegistry
37
+ from aponyx.backtest import BacktestResult, run_backtest, StrategyRegistry
38
+ from aponyx.persistence import load_parquet, save_parquet
39
+
40
+
41
+ def main() -> BacktestResult:
42
+ """
43
+ Execute backtest workflow.
44
+
45
+ Loads signal and spread data, selects strategy from catalog,
46
+ runs backtest, and saves results.
47
+
48
+ Returns
49
+ -------
50
+ BacktestResult
51
+ Backtest result with positions and P&L.
52
+ """
53
+ signal_name, product, strategy_name = define_backtest_parameters()
54
+ signal, spread = load_backtest_data(signal_name, product)
55
+ config = load_strategy_config(strategy_name)
56
+ result = execute_backtest(signal, spread, config)
57
+ save_backtest_result(result, signal_name, strategy_name)
58
+ return result
59
+
60
+
61
+ def define_backtest_parameters() -> tuple[str, str, str]:
62
+ """
63
+ Define backtest parameters.
64
+
65
+ Returns
66
+ -------
67
+ tuple[str, str, str]
68
+ Signal name, product identifier, and strategy name.
69
+
70
+ Notes
71
+ -----
72
+ Choose one signal-product-strategy combination for demonstration.
73
+ In practice, evaluate multiple strategies per signal.
74
+ """
75
+ signal_name = "spread_momentum"
76
+ product = "cdx_ig_5y"
77
+ strategy_name = "balanced"
78
+ return signal_name, product, strategy_name
79
+
80
+
81
+ def load_backtest_data(
82
+ signal_name: str,
83
+ product: str,
84
+ ) -> tuple[pd.Series, pd.Series]:
85
+ """
86
+ Load signal and spread data for backtest.
87
+
88
+ Parameters
89
+ ----------
90
+ signal_name : str
91
+ Name of signal to load from processed directory.
92
+ product : str
93
+ Product identifier for spread data.
94
+
95
+ Returns
96
+ -------
97
+ tuple[pd.Series, pd.Series]
98
+ Signal series and spread series (aligned).
99
+
100
+ Notes
101
+ -----
102
+ Loads signal saved by previous step (04_compute_signal.py).
103
+ Spread data loaded from registry using product identifier.
104
+ """
105
+ signal = load_signal(signal_name)
106
+ spread = load_spread_data(product)
107
+
108
+ # Align signal and spread to common dates
109
+ common_idx = signal.index.intersection(spread.index)
110
+ signal = signal.loc[common_idx]
111
+ spread = spread.loc[common_idx]
112
+
113
+ return signal, spread
114
+
115
+
116
+ def load_signal(signal_name: str) -> pd.Series:
117
+ """
118
+ Load signal from processed directory.
119
+
120
+ Parameters
121
+ ----------
122
+ signal_name : str
123
+ Name of signal file (without .parquet extension).
124
+
125
+ Returns
126
+ -------
127
+ pd.Series
128
+ Signal series with DatetimeIndex.
129
+ """
130
+ signal_path = DATA_WORKFLOWS_DIR / "signals" / f"{signal_name}.parquet"
131
+ signal_df = load_parquet(signal_path)
132
+ return signal_df["value"]
133
+
134
+
135
+ def load_spread_data(product: str) -> pd.Series:
136
+ """
137
+ Load spread data for target product.
138
+
139
+ Parameters
140
+ ----------
141
+ product : str
142
+ Product identifier (e.g., "cdx_ig_5y").
143
+
144
+ Returns
145
+ -------
146
+ pd.Series
147
+ Spread series with DatetimeIndex.
148
+
149
+ Notes
150
+ -----
151
+ Uses DataRegistry.load_dataset_by_security() for efficient lookup.
152
+ """
153
+ data_registry = DataRegistry(REGISTRY_PATH, DATA_DIR)
154
+ spread_df = data_registry.load_dataset_by_security(product)
155
+ return spread_df["spread"]
156
+
157
+
158
+ def load_strategy_config(strategy_name: str):
159
+ """
160
+ Load strategy configuration from catalog.
161
+
162
+ Parameters
163
+ ----------
164
+ strategy_name : str
165
+ Name of strategy in catalog (e.g., "balanced", "conservative").
166
+
167
+ Returns
168
+ -------
169
+ BacktestConfig
170
+ Backtest configuration with strategy parameters.
171
+
172
+ Notes
173
+ -----
174
+ Reads strategy metadata from catalog and converts to BacktestConfig.
175
+ """
176
+ registry = StrategyRegistry(STRATEGY_CATALOG_PATH)
177
+ metadata = registry.get_metadata(strategy_name)
178
+ return metadata.to_config()
179
+
180
+
181
+ def execute_backtest(
182
+ signal: pd.Series,
183
+ spread: pd.Series,
184
+ config,
185
+ ) -> BacktestResult:
186
+ """
187
+ Run backtest with signal and spread data.
188
+
189
+ Parameters
190
+ ----------
191
+ signal : pd.Series
192
+ Signal series with DatetimeIndex.
193
+ spread : pd.Series
194
+ Spread series with DatetimeIndex.
195
+ config : BacktestConfig
196
+ Backtest configuration.
197
+
198
+ Returns
199
+ -------
200
+ BacktestResult
201
+ Backtest result with positions and P&L.
202
+ """
203
+ return run_backtest(signal, spread, config)
204
+
205
+
206
+ def save_backtest_result(
207
+ result: BacktestResult,
208
+ signal_name: str,
209
+ strategy_name: str,
210
+ ) -> None:
211
+ """
212
+ Save backtest results to processed directory.
213
+
214
+ Parameters
215
+ ----------
216
+ result : BacktestResult
217
+ Backtest result to save.
218
+ signal_name : str
219
+ Name of signal.
220
+ strategy_name : str
221
+ Name of strategy.
222
+
223
+ Notes
224
+ -----
225
+ Saves positions and P&L DataFrames to separate parquet files.
226
+ File naming: {signal_name}_{strategy_name}_positions.parquet
227
+ {signal_name}_{strategy_name}_pnl.parquet
228
+ """
229
+ backtests_dir = DATA_WORKFLOWS_DIR / "backtests"
230
+ backtests_dir.mkdir(parents=True, exist_ok=True)
231
+
232
+ # Save positions
233
+ positions_path = backtests_dir / f"{signal_name}_{strategy_name}_positions.parquet"
234
+ save_parquet(result.positions, positions_path)
235
+
236
+ # Save P&L
237
+ pnl_path = backtests_dir / f"{signal_name}_{strategy_name}_pnl.parquet"
238
+ save_parquet(result.pnl, pnl_path)
239
+
240
+
241
+ if __name__ == "__main__":
242
+ main()
@@ -0,0 +1,214 @@
1
+ """
2
+ Compute comprehensive performance metrics for backtest results.
3
+
4
+ Prerequisites
5
+ -------------
6
+ Backtest results saved from backtest execution (06_run_backtest.py):
7
+ - Positions file: data/workflows/backtests/{signal}_{strategy}_positions.parquet
8
+ - P&L file: data/workflows/backtests/{signal}_{strategy}_pnl.parquet
9
+
10
+ Outputs
11
+ -------
12
+ PerformanceResult with comprehensive metrics:
13
+ - All 21+ performance metrics (Sharpe, drawdown, profit factor, tail ratio, etc.)
14
+ - Subperiod stability analysis (quarterly breakdown with full metrics per period)
15
+ - Return attribution (directional, signal strength, win/loss decomposition)
16
+ - Overall stability score and interpretive summary
17
+ Performance report saved to reports/performance/{signal}_{strategy}_{timestamp}.md.
18
+
19
+ Examples
20
+ --------
21
+ Run from project root:
22
+ python -m aponyx.examples.07_analyze_performance
23
+
24
+ Expected output: PerformanceResult with stability score ~0.7, profit factor ~1.5.
25
+ Markdown report saved to reports/performance/spread_momentum_balanced_{timestamp}.md.
26
+ """
27
+
28
+ from aponyx.config import DATA_WORKFLOWS_DIR
29
+ from aponyx.backtest import BacktestResult
30
+ from aponyx.evaluation.performance import (
31
+ analyze_backtest_performance,
32
+ PerformanceConfig,
33
+ PerformanceResult,
34
+ generate_performance_report,
35
+ save_report,
36
+ )
37
+ from aponyx.persistence import load_parquet
38
+
39
+
40
+ def main() -> PerformanceResult:
41
+ """
42
+ Execute performance analysis workflow.
43
+
44
+ Loads backtest results, computes comprehensive metrics,
45
+ generates interpretive report, and saves outputs.
46
+
47
+ Returns
48
+ -------
49
+ PerformanceResult
50
+ Performance evaluation with metrics, attribution, and summary.
51
+ """
52
+ signal_name, strategy_name = define_analysis_parameters()
53
+ backtest_result = load_backtest_result(signal_name, strategy_name)
54
+ config = define_performance_config()
55
+ performance = compute_performance_metrics(backtest_result, config)
56
+ save_performance_report(performance, signal_name, strategy_name)
57
+ return performance
58
+
59
+
60
+ def define_analysis_parameters() -> tuple[str, str]:
61
+ """
62
+ Define analysis parameters.
63
+
64
+ Returns
65
+ -------
66
+ tuple[str, str]
67
+ Signal name and strategy name.
68
+
69
+ Notes
70
+ -----
71
+ Must match the signal-strategy combination from backtest step.
72
+ """
73
+ signal_name = "spread_momentum"
74
+ strategy_name = "balanced"
75
+ return signal_name, strategy_name
76
+
77
+
78
+ def load_backtest_result(
79
+ signal_name: str,
80
+ strategy_name: str,
81
+ ) -> BacktestResult:
82
+ """
83
+ Load backtest results from processed directory.
84
+
85
+ Parameters
86
+ ----------
87
+ signal_name : str
88
+ Name of signal.
89
+ strategy_name : str
90
+ Name of strategy.
91
+
92
+ Returns
93
+ -------
94
+ BacktestResult
95
+ Backtest result with positions and P&L DataFrames.
96
+
97
+ Notes
98
+ -----
99
+ Loads positions and P&L from separate parquet files saved
100
+ by previous step (06_run_backtest.py). Reconstructs BacktestResult
101
+ with minimal metadata for analysis.
102
+ """
103
+ backtests_dir = DATA_WORKFLOWS_DIR / "backtests"
104
+
105
+ positions_path = backtests_dir / f"{signal_name}_{strategy_name}_positions.parquet"
106
+ pnl_path = backtests_dir / f"{signal_name}_{strategy_name}_pnl.parquet"
107
+
108
+ positions = load_parquet(positions_path)
109
+ pnl = load_parquet(pnl_path)
110
+
111
+ metadata = {
112
+ "signal_id": signal_name,
113
+ "strategy_id": strategy_name,
114
+ }
115
+
116
+ return BacktestResult(
117
+ positions=positions,
118
+ pnl=pnl,
119
+ metadata=metadata,
120
+ )
121
+
122
+
123
+ def define_performance_config() -> PerformanceConfig:
124
+ """
125
+ Define performance evaluation configuration.
126
+
127
+ Returns
128
+ -------
129
+ PerformanceConfig
130
+ Configuration with analysis parameters.
131
+
132
+ Notes
133
+ -----
134
+ Uses quarterly subperiod analysis (n_subperiods=4) and
135
+ 3-month rolling window (rolling_window=63) for stability metrics.
136
+ Attribution uses terciles (attribution_quantiles=3) for
137
+ low/medium/high signal strength decomposition.
138
+ """
139
+ return PerformanceConfig(
140
+ min_obs=252,
141
+ n_subperiods=4,
142
+ risk_free_rate=0.0,
143
+ rolling_window=63,
144
+ report_format="markdown",
145
+ attribution_quantiles=3,
146
+ )
147
+
148
+
149
+ def compute_performance_metrics(
150
+ backtest_result: BacktestResult,
151
+ config: PerformanceConfig,
152
+ ) -> PerformanceResult:
153
+ """
154
+ Compute comprehensive performance metrics.
155
+
156
+ Parameters
157
+ ----------
158
+ backtest_result : BacktestResult
159
+ Backtest output with positions and P&L.
160
+ config : PerformanceConfig
161
+ Performance evaluation configuration.
162
+
163
+ Returns
164
+ -------
165
+ PerformanceResult
166
+ Complete performance analysis with metrics, attribution, and summary.
167
+
168
+ Notes
169
+ -----
170
+ Orchestrates all performance computations:
171
+ - Basic metrics: Sharpe, max drawdown, hit rate, trades
172
+ - Extended metrics: rolling Sharpe stability, recovery time, tail ratios
173
+ - Subperiod analysis: quarterly breakdown with full metrics per period
174
+ - Attribution: directional, signal strength, win/loss decomposition
175
+ - Stability score: consistency across subperiods
176
+ - Summary: interpretive text with key findings
177
+ """
178
+ return analyze_backtest_performance(backtest_result, config)
179
+
180
+
181
+ def save_performance_report(
182
+ performance: PerformanceResult,
183
+ signal_name: str,
184
+ strategy_name: str,
185
+ ) -> None:
186
+ """
187
+ Generate and save performance report.
188
+
189
+ Parameters
190
+ ----------
191
+ performance : PerformanceResult
192
+ Performance analysis results.
193
+ signal_name : str
194
+ Name of signal.
195
+ strategy_name : str
196
+ Name of strategy.
197
+
198
+ Notes
199
+ -----
200
+ Generates markdown report with formatted tables and saves to
201
+ reports/performance/{signal}_{strategy}_{timestamp}.md.
202
+ Report includes all metrics, attribution breakdown, and summary.
203
+ """
204
+ report = generate_performance_report(
205
+ performance,
206
+ signal_id=signal_name,
207
+ strategy_id=strategy_name,
208
+ generate_tearsheet=False,
209
+ )
210
+ save_report(report, signal_name, strategy_name, DATA_WORKFLOWS_DIR / "reports")
211
+
212
+
213
+ if __name__ == "__main__":
214
+ main()