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,135 @@
1
+ """
2
+ Instrument fetch function registry.
3
+
4
+ Maps instrument type strings to their fetch functions using catalog pattern.
5
+ Provides generic dispatch without hardcoded if/elif logic.
6
+ """
7
+
8
+ import logging
9
+ from collections.abc import Callable
10
+ from dataclasses import dataclass
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ @dataclass(frozen=True)
16
+ class FetchSpec:
17
+ """
18
+ Metadata for instrument fetch function.
19
+
20
+ Attributes
21
+ ----------
22
+ instrument : str
23
+ Instrument type identifier (e.g., "cdx", "vix", "etf").
24
+ fetch_fn : Callable
25
+ Fetch function for this instrument.
26
+ requires_security : bool
27
+ Whether this instrument requires security parameter.
28
+ True for CDX and ETF (multi-security), False for VIX (single-security).
29
+ """
30
+
31
+ instrument: str
32
+ fetch_fn: Callable
33
+ requires_security: bool
34
+
35
+
36
+ # Lazy-loaded registry to avoid circular imports
37
+ _FETCH_REGISTRY: dict[str, FetchSpec] | None = None
38
+
39
+
40
+ def _load_fetch_registry() -> dict[str, FetchSpec]:
41
+ """
42
+ Load fetch function registry.
43
+
44
+ Registry is populated on first access to avoid import-time side effects.
45
+
46
+ Returns
47
+ -------
48
+ dict[str, FetchSpec]
49
+ Mapping from instrument type to fetch specification.
50
+ """
51
+ global _FETCH_REGISTRY
52
+
53
+ if _FETCH_REGISTRY is not None:
54
+ return _FETCH_REGISTRY
55
+
56
+ # Import fetch functions
57
+ from .fetch import fetch_cdx, fetch_vix, fetch_etf
58
+
59
+ _FETCH_REGISTRY = {
60
+ "cdx": FetchSpec(
61
+ instrument="cdx",
62
+ fetch_fn=fetch_cdx,
63
+ requires_security=True,
64
+ ),
65
+ "vix": FetchSpec(
66
+ instrument="vix",
67
+ fetch_fn=fetch_vix,
68
+ requires_security=False,
69
+ ),
70
+ "etf": FetchSpec(
71
+ instrument="etf",
72
+ fetch_fn=fetch_etf,
73
+ requires_security=True,
74
+ ),
75
+ }
76
+
77
+ logger.debug("Loaded fetch registry: %d instruments", len(_FETCH_REGISTRY))
78
+ return _FETCH_REGISTRY
79
+
80
+
81
+ def get_fetch_spec(instrument: str) -> FetchSpec:
82
+ """
83
+ Get fetch specification for instrument type.
84
+
85
+ Parameters
86
+ ----------
87
+ instrument : str
88
+ Instrument type (e.g., "cdx", "vix", "etf").
89
+
90
+ Returns
91
+ -------
92
+ FetchSpec
93
+ Fetch specification with function and metadata.
94
+
95
+ Raises
96
+ ------
97
+ ValueError
98
+ If instrument type is not registered.
99
+
100
+ Examples
101
+ --------
102
+ >>> spec = get_fetch_spec("vix")
103
+ >>> spec.instrument
104
+ 'vix'
105
+ >>> spec.requires_security
106
+ False
107
+ >>> df = spec.fetch_fn(FileSource("data.parquet"), use_cache=True)
108
+ """
109
+ registry = _load_fetch_registry()
110
+
111
+ if instrument not in registry:
112
+ raise ValueError(
113
+ f"Unknown instrument type: '{instrument}'. "
114
+ f"Available instruments: {sorted(registry.keys())}"
115
+ )
116
+
117
+ return registry[instrument]
118
+
119
+
120
+ def list_instruments() -> list[str]:
121
+ """
122
+ List all registered instrument types.
123
+
124
+ Returns
125
+ -------
126
+ list[str]
127
+ Sorted list of instrument type identifiers.
128
+
129
+ Examples
130
+ --------
131
+ >>> list_instruments()
132
+ ['cdx', 'etf', 'vix']
133
+ """
134
+ registry = _load_fetch_registry()
135
+ return sorted(registry.keys())
aponyx/data/loaders.py ADDED
@@ -0,0 +1,205 @@
1
+ """
2
+ Data loading utilities for signal-required data aggregation.
3
+
4
+ Provides helpers for generic instrument loading without hardcoded instrument logic.
5
+ """
6
+
7
+ import logging
8
+ from collections.abc import Callable
9
+ from pathlib import Path
10
+ from typing import TYPE_CHECKING
11
+
12
+ import pandas as pd
13
+
14
+ if TYPE_CHECKING:
15
+ from .registry import DataRegistry
16
+ from ..models.registry import SignalRegistry
17
+
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+
22
+ def load_instrument_from_raw(
23
+ data_dir: Path,
24
+ instrument: str,
25
+ fetch_fn: Callable,
26
+ securities: list[str] | None = None,
27
+ ) -> pd.DataFrame:
28
+ """
29
+ Load instrument data from raw files using fetch function.
30
+
31
+ Generic loader that handles both single-security (VIX) and multi-security
32
+ (CDX, ETF) instruments. Uses FileSource with registry for security-based lookup.
33
+
34
+ Parameters
35
+ ----------
36
+ data_dir : Path
37
+ Raw data directory (e.g., data/raw/synthetic).
38
+ instrument : str
39
+ Instrument type (cdx, vix, etf).
40
+ fetch_fn : Callable
41
+ Fetch function to use for loading and validation.
42
+ Signature: fetch_fn(source, security=..., use_cache=True) -> pd.DataFrame
43
+ securities : list[str] or None
44
+ List of security identifiers for multi-security instruments.
45
+ If None, loads single security based on instrument type.
46
+
47
+ Returns
48
+ -------
49
+ pd.DataFrame
50
+ Loaded and validated instrument data with DatetimeIndex.
51
+
52
+ Raises
53
+ ------
54
+ ValueError
55
+ If registry not found or securities not in registry.
56
+ FileNotFoundError
57
+ If registry file doesn't exist.
58
+
59
+ Examples
60
+ --------
61
+ >>> from aponyx.data import fetch_vix, fetch_cdx, FileSource
62
+ >>> # Single security (VIX)
63
+ >>> df = load_instrument_from_raw(
64
+ ... Path("data/raw/synthetic"),
65
+ ... "vix",
66
+ ... fetch_vix,
67
+ ... securities=None
68
+ ... )
69
+ >>> # Multi-security (CDX)
70
+ >>> df = load_instrument_from_raw(
71
+ ... Path("data/raw/synthetic"),
72
+ ... "cdx",
73
+ ... fetch_cdx,
74
+ ... securities=["cdx_ig_5y", "cdx_hy_5y"]
75
+ ... )
76
+ """
77
+ from .sources import FileSource
78
+
79
+ # Initialize FileSource with registry
80
+ source = FileSource(data_dir)
81
+
82
+ if securities is None:
83
+ # Single-security instrument (e.g., VIX)
84
+ # Use instrument type as security ID
85
+ security = instrument
86
+ logger.debug("Loading %s from %s", instrument.upper(), data_dir)
87
+ df = fetch_fn(
88
+ source,
89
+ security=security,
90
+ use_cache=True,
91
+ )
92
+ return df
93
+
94
+ # Multi-security instrument (e.g., CDX, ETF)
95
+ dfs = []
96
+ for security in securities:
97
+ logger.debug("Loading %s from registry", security)
98
+ df_sec = fetch_fn(
99
+ source,
100
+ security=security,
101
+ use_cache=True,
102
+ )
103
+ dfs.append(df_sec)
104
+
105
+ if not dfs:
106
+ raise ValueError(
107
+ f"No {instrument.upper()} data loaded. "
108
+ f"Check that securities exist in registry: {data_dir / 'registry.json'}"
109
+ )
110
+
111
+ # Concatenate all securities
112
+ df = pd.concat(dfs, axis=0).sort_index()
113
+
114
+ # Remove duplicates if present (can occur when combining securities)
115
+ if df.index.duplicated().any():
116
+ n_dups = df.index.duplicated().sum()
117
+ logger.debug(
118
+ "Removing %d duplicate dates from %d securities",
119
+ n_dups,
120
+ len(dfs),
121
+ )
122
+ df = df[~df.index.duplicated(keep="last")]
123
+
124
+ logger.info("Loaded %s from raw files: %d rows", instrument.upper(), len(df))
125
+ return df
126
+
127
+
128
+ def load_signal_required_data(
129
+ signal_registry: "SignalRegistry",
130
+ data_registry: "DataRegistry",
131
+ security_mapping: dict[str, str] | None = None,
132
+ ) -> dict[str, pd.DataFrame]:
133
+ """
134
+ Load all market data required by enabled signals.
135
+
136
+ Collects data requirements from all enabled signals and loads
137
+ corresponding datasets from the data registry. Uses default_securities
138
+ from signal catalog unless overridden by security_mapping.
139
+
140
+ Parameters
141
+ ----------
142
+ signal_registry : SignalRegistry
143
+ Signal registry with enabled signal definitions.
144
+ data_registry : DataRegistry
145
+ Data registry for loading datasets by security ID.
146
+ security_mapping : dict[str, str] or None
147
+ Optional mapping to override default securities.
148
+ Keys are instrument types (e.g., "cdx", "etf").
149
+ Values are security IDs (e.g., "cdx_hy_5y", "hyg").
150
+
151
+ Returns
152
+ -------
153
+ dict[str, pd.DataFrame]
154
+ Market data mapping with all required instruments.
155
+ Keys are generic identifiers (e.g., "cdx", "etf", "vix").
156
+
157
+ Examples
158
+ --------
159
+ >>> from aponyx.models import SignalRegistry
160
+ >>> from aponyx.data import DataRegistry
161
+ >>> signal_reg = SignalRegistry("signal_catalog.json")
162
+ >>> data_reg = DataRegistry("registry.json", "data/")
163
+ >>> # Use default securities from catalog
164
+ >>> data = load_signal_required_data(signal_reg, data_reg)
165
+ >>> # Override with custom securities
166
+ >>> data = load_signal_required_data(
167
+ ... signal_reg,
168
+ ... data_reg,
169
+ ... security_mapping={"cdx": "cdx_hy_5y", "etf": "hyg"}
170
+ ... )
171
+
172
+ Notes
173
+ -----
174
+ For each enabled signal, uses default_securities to map
175
+ instrument types to specific security IDs. If security_mapping
176
+ is provided, it overrides the defaults for specified instruments.
177
+ """
178
+ # Build mapping from instrument type to security ID
179
+ # by collecting default_securities from all enabled signals
180
+ instrument_to_security = {}
181
+ for signal_name, metadata in signal_registry.get_enabled().items():
182
+ for inst_type, security_id in metadata.default_securities.items():
183
+ # If multiple signals specify the same instrument type,
184
+ # the last one wins (consistent behavior across codebase)
185
+ instrument_to_security[inst_type] = security_id
186
+
187
+ # Apply overrides if provided
188
+ if security_mapping:
189
+ for inst_type, security_id in security_mapping.items():
190
+ logger.info(
191
+ "Overriding default security for %s: %s -> %s",
192
+ inst_type,
193
+ instrument_to_security.get(inst_type, "N/A"),
194
+ security_id,
195
+ )
196
+ instrument_to_security[inst_type] = security_id
197
+
198
+ # Load data for each instrument type using the mapped security
199
+ market_data = {}
200
+ for inst_type, security_id in sorted(instrument_to_security.items()):
201
+ # Use registry helper to find and load dataset by security ID
202
+ df = data_registry.load_dataset_by_security(security_id)
203
+ market_data[inst_type] = df
204
+
205
+ return market_data
@@ -0,0 +1,13 @@
1
+ """
2
+ Data provider implementations for different sources.
3
+
4
+ Providers handle the specifics of fetching data from files, Bloomberg, APIs, etc.
5
+ """
6
+
7
+ from .file import fetch_from_file
8
+ from .bloomberg import fetch_from_bloomberg
9
+
10
+ __all__ = [
11
+ "fetch_from_file",
12
+ "fetch_from_bloomberg",
13
+ ]