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.
- aponyx/__init__.py +14 -0
- aponyx/backtest/__init__.py +31 -0
- aponyx/backtest/adapters.py +77 -0
- aponyx/backtest/config.py +84 -0
- aponyx/backtest/engine.py +560 -0
- aponyx/backtest/protocols.py +101 -0
- aponyx/backtest/registry.py +334 -0
- aponyx/backtest/strategy_catalog.json +50 -0
- aponyx/cli/__init__.py +5 -0
- aponyx/cli/commands/__init__.py +8 -0
- aponyx/cli/commands/clean.py +349 -0
- aponyx/cli/commands/list.py +302 -0
- aponyx/cli/commands/report.py +167 -0
- aponyx/cli/commands/run.py +377 -0
- aponyx/cli/main.py +125 -0
- aponyx/config/__init__.py +82 -0
- aponyx/data/__init__.py +99 -0
- aponyx/data/bloomberg_config.py +306 -0
- aponyx/data/bloomberg_instruments.json +26 -0
- aponyx/data/bloomberg_securities.json +42 -0
- aponyx/data/cache.py +294 -0
- aponyx/data/fetch.py +659 -0
- aponyx/data/fetch_registry.py +135 -0
- aponyx/data/loaders.py +205 -0
- aponyx/data/providers/__init__.py +13 -0
- aponyx/data/providers/bloomberg.py +383 -0
- aponyx/data/providers/file.py +111 -0
- aponyx/data/registry.py +500 -0
- aponyx/data/requirements.py +96 -0
- aponyx/data/sample_data.py +415 -0
- aponyx/data/schemas.py +60 -0
- aponyx/data/sources.py +171 -0
- aponyx/data/synthetic_params.json +46 -0
- aponyx/data/transforms.py +336 -0
- aponyx/data/validation.py +308 -0
- aponyx/docs/__init__.py +24 -0
- aponyx/docs/adding_data_providers.md +682 -0
- aponyx/docs/cdx_knowledge_base.md +455 -0
- aponyx/docs/cdx_overlay_strategy.md +135 -0
- aponyx/docs/cli_guide.md +607 -0
- aponyx/docs/governance_design.md +551 -0
- aponyx/docs/logging_design.md +251 -0
- aponyx/docs/performance_evaluation_design.md +265 -0
- aponyx/docs/python_guidelines.md +786 -0
- aponyx/docs/signal_registry_usage.md +369 -0
- aponyx/docs/signal_suitability_design.md +558 -0
- aponyx/docs/visualization_design.md +277 -0
- aponyx/evaluation/__init__.py +11 -0
- aponyx/evaluation/performance/__init__.py +24 -0
- aponyx/evaluation/performance/adapters.py +109 -0
- aponyx/evaluation/performance/analyzer.py +384 -0
- aponyx/evaluation/performance/config.py +320 -0
- aponyx/evaluation/performance/decomposition.py +304 -0
- aponyx/evaluation/performance/metrics.py +761 -0
- aponyx/evaluation/performance/registry.py +327 -0
- aponyx/evaluation/performance/report.py +541 -0
- aponyx/evaluation/suitability/__init__.py +67 -0
- aponyx/evaluation/suitability/config.py +143 -0
- aponyx/evaluation/suitability/evaluator.py +389 -0
- aponyx/evaluation/suitability/registry.py +328 -0
- aponyx/evaluation/suitability/report.py +398 -0
- aponyx/evaluation/suitability/scoring.py +367 -0
- aponyx/evaluation/suitability/tests.py +303 -0
- aponyx/examples/01_generate_synthetic_data.py +53 -0
- aponyx/examples/02_fetch_data_file.py +82 -0
- aponyx/examples/03_fetch_data_bloomberg.py +104 -0
- aponyx/examples/04_compute_signal.py +164 -0
- aponyx/examples/05_evaluate_suitability.py +224 -0
- aponyx/examples/06_run_backtest.py +242 -0
- aponyx/examples/07_analyze_performance.py +214 -0
- aponyx/examples/08_visualize_results.py +272 -0
- aponyx/main.py +7 -0
- aponyx/models/__init__.py +45 -0
- aponyx/models/config.py +83 -0
- aponyx/models/indicator_transformation.json +52 -0
- aponyx/models/indicators.py +292 -0
- aponyx/models/metadata.py +447 -0
- aponyx/models/orchestrator.py +213 -0
- aponyx/models/registry.py +860 -0
- aponyx/models/score_transformation.json +42 -0
- aponyx/models/signal_catalog.json +29 -0
- aponyx/models/signal_composer.py +513 -0
- aponyx/models/signal_transformation.json +29 -0
- aponyx/persistence/__init__.py +16 -0
- aponyx/persistence/json_io.py +132 -0
- aponyx/persistence/parquet_io.py +378 -0
- aponyx/py.typed +0 -0
- aponyx/reporting/__init__.py +10 -0
- aponyx/reporting/generator.py +517 -0
- aponyx/visualization/__init__.py +20 -0
- aponyx/visualization/app.py +37 -0
- aponyx/visualization/plots.py +309 -0
- aponyx/visualization/visualizer.py +242 -0
- aponyx/workflows/__init__.py +18 -0
- aponyx/workflows/concrete_steps.py +720 -0
- aponyx/workflows/config.py +122 -0
- aponyx/workflows/engine.py +279 -0
- aponyx/workflows/registry.py +116 -0
- aponyx/workflows/steps.py +180 -0
- aponyx-0.1.18.dist-info/METADATA +552 -0
- aponyx-0.1.18.dist-info/RECORD +104 -0
- aponyx-0.1.18.dist-info/WHEEL +4 -0
- aponyx-0.1.18.dist-info/entry_points.txt +2 -0
- 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
|
+
]
|