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,272 @@
1
+ """
2
+ Generate visualization charts for backtest results.
3
+
4
+ Prerequisites
5
+ -------------
6
+ Backtest results saved from backtest execution (06_run_backtest.py):
7
+ - P&L file: data/workflows/backtests/{signal}_{strategy}_pnl.parquet
8
+ - Positions file: data/workflows/backtests/{signal}_{strategy}_positions.parquet
9
+
10
+ Outputs
11
+ -------
12
+ Three Plotly figure objects:
13
+ - Equity curve: cumulative P&L over time
14
+ - Drawdown chart: underwater equity visualization
15
+ - Signal plot: time series of signal values with thresholds
16
+
17
+ Examples
18
+ --------
19
+ Run from project root:
20
+ python -m aponyx.examples.08_visualize_results
21
+
22
+ Expected output: Three interactive Plotly charts displayed or saved.
23
+ Figures can be rendered in notebooks, Streamlit apps, or exported to HTML.
24
+ """
25
+
26
+ import pandas as pd
27
+ import plotly.graph_objects as go
28
+
29
+ from aponyx.config import DATA_WORKFLOWS_DIR
30
+ from aponyx.persistence import load_parquet
31
+ from aponyx.visualization import plot_equity_curve, plot_drawdown, plot_signal
32
+
33
+
34
+ def main() -> dict[str, go.Figure]:
35
+ """
36
+ Execute visualization workflow.
37
+
38
+ Loads backtest results and generates three key charts:
39
+ equity curve, drawdown, and signal time series.
40
+
41
+ Returns
42
+ -------
43
+ dict[str, go.Figure]
44
+ Dictionary of figure names to Plotly figure objects.
45
+
46
+ Notes
47
+ -----
48
+ Figures are returned for flexible rendering (Streamlit, Jupyter, HTML).
49
+ To display in Jupyter: fig.show()
50
+ To save to HTML: fig.write_html("output.html")
51
+ To display in Streamlit: st.plotly_chart(fig)
52
+ """
53
+ signal_name, strategy_name = define_visualization_parameters()
54
+ pnl, positions = load_backtest_data(signal_name, strategy_name)
55
+ return generate_all_charts(pnl, positions, signal_name, strategy_name)
56
+
57
+
58
+ def define_visualization_parameters() -> tuple[str, str]:
59
+ """
60
+ Define visualization parameters.
61
+
62
+ Returns
63
+ -------
64
+ tuple[str, str]
65
+ Signal name and strategy name.
66
+
67
+ Notes
68
+ -----
69
+ Must match the signal-strategy combination from backtest step.
70
+ """
71
+ signal_name = "spread_momentum"
72
+ strategy_name = "balanced"
73
+ return signal_name, strategy_name
74
+
75
+
76
+ def load_backtest_data(
77
+ signal_name: str,
78
+ strategy_name: str,
79
+ ) -> tuple[pd.DataFrame, pd.DataFrame]:
80
+ """
81
+ Load P&L and positions from backtest results.
82
+
83
+ Parameters
84
+ ----------
85
+ signal_name : str
86
+ Name of signal.
87
+ strategy_name : str
88
+ Name of strategy.
89
+
90
+ Returns
91
+ -------
92
+ tuple[pd.DataFrame, pd.DataFrame]
93
+ P&L DataFrame and positions DataFrame.
94
+
95
+ Notes
96
+ -----
97
+ Loads data saved by 06_run_backtest.py from processed directory.
98
+ P&L DataFrame contains net_pnl column for equity curve.
99
+ Positions DataFrame contains signal column for signal plot.
100
+ """
101
+ backtests_dir = DATA_WORKFLOWS_DIR / "backtests"
102
+
103
+ pnl_path = backtests_dir / f"{signal_name}_{strategy_name}_pnl.parquet"
104
+ positions_path = backtests_dir / f"{signal_name}_{strategy_name}_positions.parquet"
105
+
106
+ pnl = load_parquet(pnl_path)
107
+ positions = load_parquet(positions_path)
108
+
109
+ return pnl, positions
110
+
111
+
112
+ def generate_all_charts(
113
+ pnl: pd.DataFrame,
114
+ positions: pd.DataFrame,
115
+ signal_name: str,
116
+ strategy_name: str,
117
+ ) -> dict[str, go.Figure]:
118
+ """
119
+ Generate all visualization charts.
120
+
121
+ Parameters
122
+ ----------
123
+ pnl : pd.DataFrame
124
+ P&L data with net_pnl column.
125
+ positions : pd.DataFrame
126
+ Positions data with signal column.
127
+ signal_name : str
128
+ Signal name for titles.
129
+ strategy_name : str
130
+ Strategy name for titles.
131
+
132
+ Returns
133
+ -------
134
+ dict[str, go.Figure]
135
+ Dictionary with keys: equity_curve, drawdown, signal.
136
+
137
+ Notes
138
+ -----
139
+ Charts are configured for research presentation:
140
+ - Equity curve shows drawdown shading for regime visualization
141
+ - Drawdown uses underwater chart format (absolute dollars)
142
+ - Signal includes ±2 threshold lines for regime boundaries
143
+ """
144
+ figures = {}
145
+
146
+ figures["equity_curve"] = create_equity_curve(
147
+ pnl,
148
+ signal_name,
149
+ strategy_name,
150
+ )
151
+
152
+ figures["drawdown"] = create_drawdown_chart(
153
+ pnl,
154
+ signal_name,
155
+ strategy_name,
156
+ )
157
+
158
+ figures["signal"] = create_signal_chart(
159
+ positions,
160
+ signal_name,
161
+ )
162
+
163
+ return figures
164
+
165
+
166
+ def create_equity_curve(
167
+ pnl: pd.DataFrame,
168
+ signal_name: str,
169
+ strategy_name: str,
170
+ ) -> go.Figure:
171
+ """
172
+ Create equity curve chart with drawdown shading.
173
+
174
+ Parameters
175
+ ----------
176
+ pnl : pd.DataFrame
177
+ P&L data with net_pnl column.
178
+ signal_name : str
179
+ Signal name for title.
180
+ strategy_name : str
181
+ Strategy name for title.
182
+
183
+ Returns
184
+ -------
185
+ go.Figure
186
+ Plotly equity curve figure.
187
+
188
+ Notes
189
+ -----
190
+ Uses net_pnl column for cumulative P&L calculation.
191
+ Drawdown shading highlights underwater periods in red.
192
+ """
193
+ title = f"Equity Curve: {signal_name} ({strategy_name})"
194
+ return plot_equity_curve(
195
+ pnl["net_pnl"],
196
+ title=title,
197
+ show_drawdown_shading=True,
198
+ )
199
+
200
+
201
+ def create_drawdown_chart(
202
+ pnl: pd.DataFrame,
203
+ signal_name: str,
204
+ strategy_name: str,
205
+ ) -> go.Figure:
206
+ """
207
+ Create drawdown chart showing peak-to-trough decline.
208
+
209
+ Parameters
210
+ ----------
211
+ pnl : pd.DataFrame
212
+ P&L data with net_pnl column.
213
+ signal_name : str
214
+ Signal name for title.
215
+ strategy_name : str
216
+ Strategy name for title.
217
+
218
+ Returns
219
+ -------
220
+ go.Figure
221
+ Plotly drawdown figure.
222
+
223
+ Notes
224
+ -----
225
+ Uses underwater chart format (absolute dollars).
226
+ Drawdown is always non-positive (zero at peaks, negative otherwise).
227
+ """
228
+ title = f"Drawdown: {signal_name} ({strategy_name})"
229
+ return plot_drawdown(
230
+ pnl["net_pnl"],
231
+ title=title,
232
+ show_underwater_chart=True,
233
+ )
234
+
235
+
236
+ def create_signal_chart(
237
+ positions: pd.DataFrame,
238
+ signal_name: str,
239
+ ) -> go.Figure:
240
+ """
241
+ Create signal time series chart with threshold lines.
242
+
243
+ Parameters
244
+ ----------
245
+ positions : pd.DataFrame
246
+ Positions data with signal column.
247
+ signal_name : str
248
+ Signal name for title.
249
+
250
+ Returns
251
+ -------
252
+ go.Figure
253
+ Plotly signal figure.
254
+
255
+ Notes
256
+ -----
257
+ Threshold lines at ±2 mark typical entry/exit levels.
258
+ Signal convention: positive = long credit risk (buy CDX).
259
+ """
260
+ title = f"Signal: {signal_name}"
261
+ signal = positions["signal"]
262
+ signal.name = signal_name
263
+
264
+ return plot_signal(
265
+ signal,
266
+ title=title,
267
+ threshold_lines=[-2.0, 2.0],
268
+ )
269
+
270
+
271
+ if __name__ == "__main__":
272
+ main()
aponyx/main.py ADDED
@@ -0,0 +1,7 @@
1
+ def main() -> None:
2
+ """Main entry point."""
3
+ print("Hello from aponyx!")
4
+
5
+
6
+ if __name__ == "__main__":
7
+ main()
@@ -0,0 +1,45 @@
1
+ """
2
+ Models layer for systematic credit strategies.
3
+
4
+ This module provides signal generation via the four-stage transformation pipeline:
5
+ Security → Indicator → Score → Signal → Position
6
+
7
+ Module Organization:
8
+ -------------------
9
+ metadata.py - Metadata dataclasses (SignalMetadata, IndicatorMetadata, TransformationMetadata, SignalTransformationMetadata)
10
+ registry.py - Registry classes for catalog management (IndicatorTransformationRegistry, ScoreTransformationRegistry, SignalTransformationRegistry, SignalRegistry)
11
+ orchestrator.py - compute_registered_signals() batch computation
12
+ signal_composer.py - compose_signal() four-stage pipeline composition
13
+ indicators.py - Indicator compute functions
14
+ config.py - SignalConfig dataclass
15
+ """
16
+
17
+ from .config import SignalConfig
18
+ from .metadata import (
19
+ CatalogValidationError,
20
+ IndicatorMetadata,
21
+ SignalMetadata,
22
+ SignalTransformationMetadata,
23
+ TransformationMetadata,
24
+ )
25
+ from .orchestrator import compute_registered_signals
26
+ from .registry import (
27
+ IndicatorTransformationRegistry,
28
+ ScoreTransformationRegistry,
29
+ SignalRegistry,
30
+ SignalTransformationRegistry,
31
+ )
32
+
33
+ __all__ = [
34
+ "CatalogValidationError",
35
+ "IndicatorMetadata",
36
+ "IndicatorTransformationRegistry",
37
+ "ScoreTransformationRegistry",
38
+ "SignalConfig",
39
+ "SignalMetadata",
40
+ "SignalRegistry",
41
+ "SignalTransformationMetadata",
42
+ "SignalTransformationRegistry",
43
+ "TransformationMetadata",
44
+ "compute_registered_signals",
45
+ ]
@@ -0,0 +1,83 @@
1
+ """
2
+ Configuration dataclasses for indicator and signal generation.
3
+ """
4
+
5
+ from dataclasses import dataclass
6
+
7
+
8
+ @dataclass(frozen=True)
9
+ class IndicatorConfig:
10
+ """
11
+ Runtime configuration parameters for indicator computation.
12
+
13
+ Note: Most indicator parameters are defined at catalog-time in IndicatorMetadata.
14
+ This config is for runtime overrides and caching behavior.
15
+
16
+ Attributes
17
+ ----------
18
+ use_cache : bool
19
+ Whether to use cached indicator values if available.
20
+ force_recompute : bool
21
+ Force recomputation even if cache exists.
22
+ """
23
+
24
+ use_cache: bool = True
25
+ force_recompute: bool = False
26
+
27
+ def __post_init__(self) -> None:
28
+ """Validate configuration parameters."""
29
+ if self.use_cache and self.force_recompute:
30
+ raise ValueError("Cannot set both use_cache and force_recompute to True")
31
+
32
+
33
+ @dataclass(frozen=True)
34
+ class TransformationConfig:
35
+ """
36
+ Runtime configuration parameters for signal transformations.
37
+
38
+ Note: Most transformation parameters are defined at catalog-time in
39
+ TransformationMetadata. This config is for runtime behavior.
40
+
41
+ Attributes
42
+ ----------
43
+ min_valid_pct : float
44
+ Minimum percentage of valid (non-NaN) values required after transformation.
45
+ Values between 0.0 and 1.0. Default: 0.5 (50%)
46
+ """
47
+
48
+ min_valid_pct: float = 0.5
49
+
50
+ def __post_init__(self) -> None:
51
+ """Validate configuration parameters."""
52
+ if not 0.0 <= self.min_valid_pct <= 1.0:
53
+ raise ValueError(
54
+ f"min_valid_pct must be between 0.0 and 1.0, got {self.min_valid_pct}"
55
+ )
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class SignalConfig:
60
+ """
61
+ Configuration parameters for individual signal computation.
62
+
63
+ Attributes
64
+ ----------
65
+ lookback : int
66
+ Rolling window size for normalization and statistics.
67
+ min_periods : int
68
+ Minimum observations required for valid calculation.
69
+ """
70
+
71
+ lookback: int = 20
72
+ min_periods: int = 10
73
+
74
+ def __post_init__(self) -> None:
75
+ """Validate configuration parameters."""
76
+ if self.lookback <= 0:
77
+ raise ValueError(f"lookback must be positive, got {self.lookback}")
78
+ if self.min_periods <= 0:
79
+ raise ValueError(f"min_periods must be positive, got {self.min_periods}")
80
+ if self.min_periods > self.lookback:
81
+ raise ValueError(
82
+ f"min_periods ({self.min_periods}) cannot exceed lookback ({self.lookback})"
83
+ )
@@ -0,0 +1,52 @@
1
+ [
2
+ {
3
+ "name": "cdx_etf_spread_diff",
4
+ "description": "CDX spread minus ETF spread in basis points (raw basis without normalization)",
5
+ "compute_function_name": "compute_cdx_etf_spread_diff",
6
+ "data_requirements": {
7
+ "cdx": "spread",
8
+ "etf": "spread"
9
+ },
10
+ "default_securities": {
11
+ "cdx": "cdx_ig_5y",
12
+ "etf": "lqd"
13
+ },
14
+ "output_units": "basis_points",
15
+ "parameters": {},
16
+ "enabled": true
17
+ },
18
+ {
19
+ "name": "spread_momentum_5d",
20
+ "description": "5-day spread change in basis points (short-term momentum without volatility adjustment)",
21
+ "compute_function_name": "compute_spread_momentum",
22
+ "data_requirements": {
23
+ "cdx": "spread"
24
+ },
25
+ "default_securities": {
26
+ "cdx": "cdx_ig_5y"
27
+ },
28
+ "output_units": "basis_points",
29
+ "parameters": {
30
+ "lookback": 5
31
+ },
32
+ "enabled": true
33
+ },
34
+ {
35
+ "name": "cdx_vix_deviation_gap_20d",
36
+ "description": "Gap between CDX and VIX deviations from 20-day means (cross-asset risk sentiment)",
37
+ "compute_function_name": "compute_cdx_vix_deviation_gap",
38
+ "data_requirements": {
39
+ "cdx": "spread",
40
+ "vix": "level"
41
+ },
42
+ "default_securities": {
43
+ "cdx": "cdx_ig_5y",
44
+ "vix": "vix"
45
+ },
46
+ "output_units": "basis_points",
47
+ "parameters": {
48
+ "lookback": 20
49
+ },
50
+ "enabled": true
51
+ }
52
+ ]