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,551 @@
|
|
|
1
|
+
# Governance Design — aponyx Framework
|
|
2
|
+
|
|
3
|
+
**Purpose:**
|
|
4
|
+
This document describes the governance architecture for the *aponyx* research framework. It explains the design principles, patterns, and implementation examples that maintain consistency and modularity across the system.
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## 1. Design Overview
|
|
9
|
+
|
|
10
|
+
The governance system exists to:
|
|
11
|
+
- Provide **structure without friction** for researchers.
|
|
12
|
+
- Keep **layers isolated yet interoperable** through clear conventions.
|
|
13
|
+
- Ensure **consistency and reproducibility** across data, models, and backtests.
|
|
14
|
+
|
|
15
|
+
The framework organizes governance around three pillars:
|
|
16
|
+
|
|
17
|
+
| Pillar | Scope | Persistence | Description |
|
|
18
|
+
|--------|--------|--------------|--------------|
|
|
19
|
+
| **Config** | Paths, constants, defaults | Hardcoded in `config/` | Declares static project settings and directory locations. |
|
|
20
|
+
| **Registry** | Data and metadata tracking | JSON file | Tracks datasets and their lineage. |
|
|
21
|
+
| **Catalog** | Model and signal definitions | JSON file | Declares signals and computational assets available for research. |
|
|
22
|
+
|
|
23
|
+
Each pillar owns a single JSON or constant source of truth.
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## 2. Design Principles
|
|
28
|
+
|
|
29
|
+
1. **Single Source of Truth per Concern**
|
|
30
|
+
Each governance domain (config, registry, catalog) has exactly one canonical file.
|
|
31
|
+
|
|
32
|
+
2. **Flat, Readable Metadata**
|
|
33
|
+
All metadata should be inspectable and editable by hand in JSON format.
|
|
34
|
+
|
|
35
|
+
3. **Minimal State**
|
|
36
|
+
Governance components prefer functional patterns where practical. Class-based registries are acceptable when they provide clear lifecycle management (load → validate → query → save). State should be immutable after initialization (except DataRegistry which supports CRUD operations).
|
|
37
|
+
|
|
38
|
+
4. **Pure Dependencies**
|
|
39
|
+
Governance modules never import from higher layers (e.g., models or backtest).
|
|
40
|
+
|
|
41
|
+
5. **Determinism**
|
|
42
|
+
Loading a governance object from disk must always yield the same in-memory representation.
|
|
43
|
+
|
|
44
|
+
6. **Replaceability**
|
|
45
|
+
Governance constructs are designed to be replaceable without rewriting other layers.
|
|
46
|
+
|
|
47
|
+
7. **Convention over Abstraction**
|
|
48
|
+
Uses naming and directory conventions instead of frameworks or inheritance.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## 3. Governance Pillars
|
|
53
|
+
|
|
54
|
+
### 3.1 Configuration (`config/`)
|
|
55
|
+
|
|
56
|
+
- Declares constant paths, project root, and cache directories.
|
|
57
|
+
- Must be deterministic at import time.
|
|
58
|
+
- No dynamic configuration or environment-variable logic.
|
|
59
|
+
- Used by all layers for locating data, cache, and registries.
|
|
60
|
+
|
|
61
|
+
### 3.2 Registry (`data/registry.py`)
|
|
62
|
+
|
|
63
|
+
- Tracks datasets produced or consumed by the framework.
|
|
64
|
+
- Maintains lightweight metadata such as instrument, source, and date range.
|
|
65
|
+
- Each dataset is uniquely identified by name and stored in a single JSON registry.
|
|
66
|
+
- Supports basic operations: register, lookup, list.
|
|
67
|
+
|
|
68
|
+
### 3.3 Catalog (`models/registry.py`, `models/signal_catalog.json`)
|
|
69
|
+
|
|
70
|
+
- Enumerates all available signal definitions.
|
|
71
|
+
- Each entry specifies: name, description, data requirements, and whether it is enabled.
|
|
72
|
+
- The catalog acts as the research “menu” from which signals are selected for computation.
|
|
73
|
+
- Catalog edits are manual and version-controlled.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## 4. Governance Lifecycle Pattern
|
|
78
|
+
|
|
79
|
+
1. **Load** from a static source (constants or JSON file).
|
|
80
|
+
2. **Inspect** or query (e.g., list enabled signals).
|
|
81
|
+
3. **Use** in downstream processes (fetching data, computing signals).
|
|
82
|
+
4. **Optionally Save** if new metadata is produced.
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## 5. Layer Boundaries
|
|
87
|
+
|
|
88
|
+
| Layer | May Import From | Must Not Import From |
|
|
89
|
+
|-------|-----------------|----------------------|
|
|
90
|
+
| `config/` | None | All others |
|
|
91
|
+
| `persistence/` | `config` | `data`, `models`, `backtest` |
|
|
92
|
+
| `data/` | `config`, `persistence`, `data` (own modules) | `models`, `backtest`, `visualization` |
|
|
93
|
+
| `models/` | `config`, `data` (schemas only) | `backtest`, `visualization` |
|
|
94
|
+
| `backtest/` | `config`, `models` | `data`, `visualization` |
|
|
95
|
+
| `visualization/` | None | All others |
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## 6. Implementation Patterns
|
|
100
|
+
|
|
101
|
+
### 6.1 Config — Import-Time Constants
|
|
102
|
+
|
|
103
|
+
**File:** `src/aponyx/config/__init__.py`
|
|
104
|
+
|
|
105
|
+
```python
|
|
106
|
+
from pathlib import Path
|
|
107
|
+
from typing import Final
|
|
108
|
+
|
|
109
|
+
# Project root and data directories
|
|
110
|
+
PROJECT_ROOT: Final[Path] = Path(__file__).parent.parent.parent.parent
|
|
111
|
+
DATA_DIR: Final[Path] = PROJECT_ROOT / "data"
|
|
112
|
+
LOGS_DIR: Final[Path] = PROJECT_ROOT / "logs"
|
|
113
|
+
|
|
114
|
+
# Cache configuration
|
|
115
|
+
CACHE_ENABLED: Final[bool] = True
|
|
116
|
+
CACHE_TTL_DAYS: Final[int] = 1
|
|
117
|
+
CACHE_DIR: Final[Path] = DATA_DIR / "cache"
|
|
118
|
+
|
|
119
|
+
# Catalog paths
|
|
120
|
+
INDICATOR_TRANSFORMATION_PATH: Final[Path] = PROJECT_ROOT / "src/aponyx/models/indicator_transformation.json"
|
|
121
|
+
SCORE_TRANSFORMATION_PATH: Final[Path] = PROJECT_ROOT / "src/aponyx/models/score_transformation.json"
|
|
122
|
+
SIGNAL_CATALOG_PATH: Final[Path] = PROJECT_ROOT / "src/aponyx/models/signal_catalog.json"
|
|
123
|
+
STRATEGY_CATALOG_PATH: Final[Path] = PROJECT_ROOT / "src/aponyx/backtest/strategy_catalog.json"
|
|
124
|
+
|
|
125
|
+
# Bloomberg configuration paths
|
|
126
|
+
BLOOMBERG_SECURITIES_PATH: Final[Path] = PROJECT_ROOT / "src/aponyx/data/bloomberg_securities.json"
|
|
127
|
+
BLOOMBERG_INSTRUMENTS_PATH: Final[Path] = PROJECT_ROOT / "src/aponyx/data/bloomberg_instruments.json"
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
**Usage:**
|
|
131
|
+
```python
|
|
132
|
+
from aponyx.config import SIGNAL_CATALOG_PATH, DATA_DIR, CACHE_ENABLED
|
|
133
|
+
|
|
134
|
+
# Config values are available immediately at import time
|
|
135
|
+
if CACHE_ENABLED:
|
|
136
|
+
cache_path = DATA_DIR / "cache" / "cdx_data.parquet"
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
**Pattern:** Import-time constants with `Final` type hints. No class instantiation required.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
### 6.2 DataRegistry — Class-Based Registry
|
|
144
|
+
|
|
145
|
+
**File:** `src/aponyx/data/registry.py`
|
|
146
|
+
|
|
147
|
+
**Lifecycle:**
|
|
148
|
+
|
|
149
|
+
```python
|
|
150
|
+
from aponyx.config import REGISTRY_PATH, DATA_DIR
|
|
151
|
+
from aponyx.data.registry import DataRegistry
|
|
152
|
+
|
|
153
|
+
# 1. LOAD: Instantiate registry (loads JSON)
|
|
154
|
+
registry = DataRegistry(REGISTRY_PATH, DATA_DIR)
|
|
155
|
+
|
|
156
|
+
# 2. INSPECT: Query registered datasets
|
|
157
|
+
all_datasets = registry.list_datasets()
|
|
158
|
+
cdx_datasets = registry.list_datasets(instrument="CDX.NA.IG")
|
|
159
|
+
|
|
160
|
+
# 3. USE: Retrieve metadata for data loading
|
|
161
|
+
info = registry.get_dataset_info("cdx_ig_5y")
|
|
162
|
+
file_path = info["file_path"]
|
|
163
|
+
start_date = info["start_date"]
|
|
164
|
+
|
|
165
|
+
# 4. SAVE: Register new dataset (auto-saves)
|
|
166
|
+
registry.register_dataset(
|
|
167
|
+
name="new_cdx_data",
|
|
168
|
+
file_path="data/raw/cdx_new.parquet",
|
|
169
|
+
instrument="CDX.NA.IG",
|
|
170
|
+
)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
**Pattern:** Class-based registry with state (`self._catalog`). JSON persistence via `_save()` method. Supports CRUD operations.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
### 6.3 IndicatorTransformationRegistry — Class-Based Catalog for Market Indicators
|
|
178
|
+
|
|
179
|
+
**Files:**
|
|
180
|
+
- `src/aponyx/models/metadata.py` — IndicatorMetadata dataclass
|
|
181
|
+
- `src/aponyx/models/registry.py` — IndicatorTransformationRegistry class
|
|
182
|
+
- `src/aponyx/models/indicators.py` — Indicator compute functions
|
|
183
|
+
|
|
184
|
+
**Lifecycle:**
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
from aponyx.config import INDICATOR_TRANSFORMATION_PATH
|
|
188
|
+
from aponyx.models import IndicatorTransformationRegistry, compute_indicator
|
|
189
|
+
|
|
190
|
+
# 1. LOAD: Instantiate registry (loads + validates JSON)
|
|
191
|
+
registry = IndicatorTransformationRegistry(INDICATOR_TRANSFORMATION_PATH)
|
|
192
|
+
# Validation happens automatically in __init__:
|
|
193
|
+
# - Checks all compute functions exist in indicators module
|
|
194
|
+
# - Validates output_units and data_requirements
|
|
195
|
+
# - Raises ValueError if function missing or validation fails
|
|
196
|
+
|
|
197
|
+
# 2. INSPECT: Query indicator metadata
|
|
198
|
+
enabled = registry.get_enabled() # Only enabled indicators
|
|
199
|
+
all_indicators = registry.list_all() # All indicators
|
|
200
|
+
metadata = registry.get_metadata("cdx_etf_spread_diff")
|
|
201
|
+
|
|
202
|
+
# 3. USE: Compute indicator with caching
|
|
203
|
+
market_data = {"cdx": cdx_df, "etf": etf_df}
|
|
204
|
+
indicator = compute_indicator(
|
|
205
|
+
indicator_metadata=metadata,
|
|
206
|
+
market_data=market_data,
|
|
207
|
+
use_cache=True # Cached at data/cache/indicators/
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
# 4. DEPENDENCY TRACKING: Query which signals use an indicator
|
|
211
|
+
dependent_signals = registry.get_dependent_signals("cdx_etf_spread_diff")
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
**Pattern:**
|
|
215
|
+
- Class-based registry with fail-fast validation
|
|
216
|
+
- Indicators output economically interpretable values (basis_points, ratio, percentage)
|
|
217
|
+
- Caching via hash-based file names in `data/cache/indicators/`
|
|
218
|
+
- Dependency tracking for impact analysis
|
|
219
|
+
|
|
220
|
+
---
|
|
221
|
+
|
|
222
|
+
### 6.4 ScoreTransformationRegistry — Class-Based Catalog for Signal Transformations
|
|
223
|
+
|
|
224
|
+
**Files:**
|
|
225
|
+
- `src/aponyx/models/metadata.py` — TransformationMetadata dataclass
|
|
226
|
+
- `src/aponyx/models/registry.py` — ScoreTransformationRegistry class
|
|
227
|
+
- `src/aponyx/models/transformations.py` — Transformation functions
|
|
228
|
+
|
|
229
|
+
**Lifecycle:**
|
|
230
|
+
|
|
231
|
+
```python
|
|
232
|
+
from aponyx.config import SCORE_TRANSFORMATION_PATH
|
|
233
|
+
from aponyx.models import ScoreTransformationRegistry, apply_signal_transformation
|
|
234
|
+
|
|
235
|
+
# 1. LOAD: Instantiate registry (loads + validates JSON)
|
|
236
|
+
registry = ScoreTransformationRegistry(SCORE_TRANSFORMATION_PATH)
|
|
237
|
+
|
|
238
|
+
# 2. INSPECT: Query transformation metadata
|
|
239
|
+
all_transforms = registry.list_all()
|
|
240
|
+
metadata = registry.get_metadata("z_score_20d")
|
|
241
|
+
|
|
242
|
+
# 3. USE: Apply transformation to indicator
|
|
243
|
+
transformed = apply_signal_transformation(
|
|
244
|
+
data=indicator,
|
|
245
|
+
transformation_metadata=metadata
|
|
246
|
+
)
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
**Pattern:**
|
|
250
|
+
- Class-based registry for reusable transformations
|
|
251
|
+
- Transformations are pure functions (z-score, volatility adjustment, etc.)
|
|
252
|
+
- Parameters defined in catalog (window, min_periods, etc.)
|
|
253
|
+
- Applied sequentially in signal composition
|
|
254
|
+
|
|
255
|
+
---
|
|
256
|
+
|
|
257
|
+
### 6.5 SignalRegistry — Class-Based Catalog with Indicator Dependencies
|
|
258
|
+
|
|
259
|
+
**Files:**
|
|
260
|
+
- `src/aponyx/models/metadata.py` — SignalMetadata dataclass
|
|
261
|
+
- `src/aponyx/models/registry.py` — SignalRegistry class
|
|
262
|
+
- `src/aponyx/models/signal_composer.py` — compose_signal() function
|
|
263
|
+
- `src/aponyx/models/orchestrator.py` — compute_registered_signals() function
|
|
264
|
+
|
|
265
|
+
**Lifecycle:**
|
|
266
|
+
|
|
267
|
+
```python
|
|
268
|
+
from aponyx.config import SIGNAL_CATALOG_PATH, INDICATOR_TRANSFORMATION_PATH, SCORE_TRANSFORMATION_PATH
|
|
269
|
+
from aponyx.models import SignalRegistry, IndicatorTransformationRegistry, ScoreTransformationRegistry
|
|
270
|
+
from aponyx.models import compose_signal, compute_registered_signals
|
|
271
|
+
|
|
272
|
+
# 1. LOAD: Instantiate registries (loads + validates JSON)
|
|
273
|
+
signal_registry = SignalRegistry(SIGNAL_CATALOG_PATH)
|
|
274
|
+
indicator_registry = IndicatorTransformationRegistry(INDICATOR_TRANSFORMATION_PATH)
|
|
275
|
+
transformation_registry = ScoreTransformationRegistry(SCORE_TRANSFORMATION_PATH)
|
|
276
|
+
# Validation happens automatically in __init__:
|
|
277
|
+
# - Signals MUST have indicator_dependencies and transformations fields
|
|
278
|
+
# - No compute_function_name, data_requirements, or arg_mapping allowed
|
|
279
|
+
# - Raises ValueError if schema violated
|
|
280
|
+
|
|
281
|
+
# 2. INSPECT: Query signal metadata
|
|
282
|
+
enabled = signal_registry.get_enabled() # Only enabled signals
|
|
283
|
+
all_signals = signal_registry.list_all() # All signals
|
|
284
|
+
metadata = signal_registry.get_metadata("cdx_etf_basis")
|
|
285
|
+
|
|
286
|
+
# 3. USE: Compose signal from indicator + transformation
|
|
287
|
+
market_data = {"cdx": cdx_df, "etf": etf_df}
|
|
288
|
+
signal = compose_signal(
|
|
289
|
+
signal_metadata=metadata,
|
|
290
|
+
market_data=market_data,
|
|
291
|
+
indicator_registry=indicator_registry,
|
|
292
|
+
transformation_registry=transformation_registry
|
|
293
|
+
)
|
|
294
|
+
|
|
295
|
+
# Batch computation of all enabled signals
|
|
296
|
+
signals = compute_registered_signals(
|
|
297
|
+
signal_registry, market_data, indicator_registry, transformation_registry
|
|
298
|
+
)
|
|
299
|
+
|
|
300
|
+
# 4. SAVE: Update catalog (optional)
|
|
301
|
+
# registry.save_catalog() # Overwrites original JSON
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
**Pattern:**
|
|
305
|
+
- Class-based registry with fail-fast validation
|
|
306
|
+
- Signals reference indicators (no embedded computation logic)
|
|
307
|
+
- Signals reference transformations (applied sequentially)
|
|
308
|
+
- Schema enforcement via SignalMetadata.__post_init__
|
|
309
|
+
- Orchestration bridges three registries
|
|
310
|
+
|
|
311
|
+
**Module Organization:**
|
|
312
|
+
```
|
|
313
|
+
models/
|
|
314
|
+
metadata.py # IndicatorMetadata, TransformationMetadata, SignalMetadata
|
|
315
|
+
registry.py # IndicatorTransformationRegistry, ScoreTransformationRegistry, SignalRegistry
|
|
316
|
+
indicators.py # Indicator compute functions
|
|
317
|
+
transformations.py # Transformation functions
|
|
318
|
+
signal_composer.py # compose_signal() - combines indicators + transformations
|
|
319
|
+
orchestrator.py # compute_registered_signals() - batch computation
|
|
320
|
+
config.py # IndicatorConfig, TransformationConfig
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
This separation clarifies responsibilities:
|
|
324
|
+
- `metadata.py` = data structure definitions
|
|
325
|
+
- `registry.py` = catalog lifecycle management for all three layers
|
|
326
|
+
- `indicators.py` = economically interpretable market metrics
|
|
327
|
+
- `transformations.py` = signal processing operations
|
|
328
|
+
- `signal_composer.py` = signal composition logic
|
|
329
|
+
- `orchestrator.py` = batch orchestration
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
### 6.6 PerformanceRegistry — Class-Based Catalog for Performance Evaluations
|
|
334
|
+
|
|
335
|
+
**File:** `src/aponyx/evaluation/performance/registry.py`
|
|
336
|
+
|
|
337
|
+
**Lifecycle:**
|
|
338
|
+
|
|
339
|
+
```python
|
|
340
|
+
from aponyx.config import PERFORMANCE_REGISTRY_PATH
|
|
341
|
+
from aponyx.evaluation.performance import PerformanceRegistry
|
|
342
|
+
|
|
343
|
+
# 1. LOAD: Instantiate registry (loads JSON)
|
|
344
|
+
registry = PerformanceRegistry(PERFORMANCE_REGISTRY_PATH)
|
|
345
|
+
|
|
346
|
+
# 2. INSPECT: Query evaluation metadata
|
|
347
|
+
all_evals = registry.list_evaluations()
|
|
348
|
+
cdx_evals = registry.list_evaluations(signal_id="cdx_etf_basis")
|
|
349
|
+
balanced_evals = registry.list_evaluations(strategy_id="balanced")
|
|
350
|
+
|
|
351
|
+
# 3. USE: Register new performance evaluation
|
|
352
|
+
eval_id = registry.register_evaluation(
|
|
353
|
+
performance_result=perf_result,
|
|
354
|
+
signal_id="cdx_etf_basis",
|
|
355
|
+
strategy_id="balanced",
|
|
356
|
+
report_path=Path("data/workflows/cdx_etf_basis_balanced_20251109_120000/reports/cdx_etf_basis_balanced_20251109.md"),
|
|
357
|
+
)
|
|
358
|
+
|
|
359
|
+
# 4. RETRIEVE: Get specific evaluation metadata
|
|
360
|
+
metadata = registry.get_evaluation(eval_id)
|
|
361
|
+
|
|
362
|
+
# 5. SAVE: Auto-saves on register (manual save also available)
|
|
363
|
+
registry.save_catalog()
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
**Pattern:** Class-based registry similar to SuitabilityRegistry. Tracks performance evaluation runs with comprehensive metadata including signal, strategy, stability score, and report paths.
|
|
367
|
+
|
|
368
|
+
---
|
|
369
|
+
|
|
370
|
+
### 6.7 StrategyRegistry — Class-Based Catalog for Backtest Strategies
|
|
371
|
+
|
|
372
|
+
**File:** `src/aponyx/backtest/registry.py`
|
|
373
|
+
|
|
374
|
+
**Lifecycle:**
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
from aponyx.config import STRATEGY_CATALOG_PATH
|
|
378
|
+
from aponyx.backtest import StrategyRegistry, run_backtest
|
|
379
|
+
|
|
380
|
+
# 1. LOAD: Instantiate registry (loads + validates JSON)
|
|
381
|
+
registry = StrategyRegistry(STRATEGY_CATALOG_PATH)
|
|
382
|
+
# Validation happens automatically in __init__:
|
|
383
|
+
# - Validates position_size_mm > 0
|
|
384
|
+
# - Validates sizing_mode is 'binary' or 'proportional'
|
|
385
|
+
# - Validates stop_loss_pct/take_profit_pct in (0, 100] if set
|
|
386
|
+
|
|
387
|
+
# 2. INSPECT: Query strategy metadata
|
|
388
|
+
enabled = registry.get_enabled() # Only enabled strategies
|
|
389
|
+
metadata = registry.get_metadata("balanced")
|
|
390
|
+
|
|
391
|
+
# 3. USE: Convert strategy to BacktestConfig
|
|
392
|
+
config = metadata.to_config(
|
|
393
|
+
position_size_mm_override=20.0, # Override default
|
|
394
|
+
)
|
|
395
|
+
|
|
396
|
+
# Run backtest with strategy config
|
|
397
|
+
result = run_backtest(signal_series, cdx_spread, config)
|
|
398
|
+
|
|
399
|
+
# 4. SAVE: Update catalog (optional, not typical)
|
|
400
|
+
# registry.save_catalog()
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
**Pattern:** Class-based registry similar to SignalRegistry. `StrategyMetadata.to_config()` bridges catalog to runtime config dataclass.
|
|
404
|
+
|
|
405
|
+
---
|
|
406
|
+
|
|
407
|
+
### 6.6 Bloomberg Config — Functional Pattern with Module-Level Caching
|
|
408
|
+
|
|
409
|
+
**File:** `src/aponyx/data/bloomberg_config.py`
|
|
410
|
+
|
|
411
|
+
**Lifecycle:**
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
from aponyx.data.bloomberg_config import (
|
|
415
|
+
get_instrument_spec,
|
|
416
|
+
get_security_spec,
|
|
417
|
+
get_bloomberg_ticker,
|
|
418
|
+
)
|
|
419
|
+
|
|
420
|
+
# 1. LOAD: Automatic lazy-loading on first access
|
|
421
|
+
# JSON files loaded into module-level variables on first function call
|
|
422
|
+
|
|
423
|
+
# 2. INSPECT: Query specifications
|
|
424
|
+
instrument_spec = get_instrument_spec("CDX")
|
|
425
|
+
security_spec = get_security_spec("CDX.NA.IG", "5Y")
|
|
426
|
+
|
|
427
|
+
# 3. USE: Resolve ticker for data fetching
|
|
428
|
+
ticker = get_bloomberg_ticker("CDX.NA.IG", "5Y")
|
|
429
|
+
# Returns: "CDX IG CDSI 5Y Corp"
|
|
430
|
+
|
|
431
|
+
# 4. SAVE: Read-only (no save operation)
|
|
432
|
+
# Bloomberg config is managed manually via JSON files
|
|
433
|
+
```
|
|
434
|
+
|
|
435
|
+
**Pattern:** Functional pattern with module-level caching (`_INSTRUMENTS_CATALOG`, `_SECURITIES_CATALOG`). Pure functions, no class instantiation. Lazy-loads JSON on first access.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
### 6.10 Pattern Comparison
|
|
440
|
+
|
|
441
|
+
| Pillar | Implementation | State | Validation | Save Support |
|
|
442
|
+
|--------|---------------|-------|------------|--------------|
|
|
443
|
+
| **Config** | Import-time constants | None | N/A | No |
|
|
444
|
+
| **DataRegistry** | Class-based | Mutable (`self._catalog`) | On save | Yes |
|
|
445
|
+
| **IndicatorTransformationRegistry** | Class-based | Immutable (frozen dataclass) | Fail-fast (load time) | Yes |
|
|
446
|
+
| **ScoreTransformationRegistry** | Class-based | Immutable (frozen dataclass) | Fail-fast (load time) | Yes |
|
|
447
|
+
| **SignalRegistry** | Class-based | Immutable (frozen dataclass) | Fail-fast (load time) | Yes |
|
|
448
|
+
| **StrategyRegistry** | Class-based | Immutable (frozen dataclass) | Fail-fast (load time) | Yes |
|
|
449
|
+
| **SuitabilityRegistry** | Class-based | Mutable (`self._evaluations`) | On register | Yes |
|
|
450
|
+
| **PerformanceRegistry** | Class-based | Mutable (`self._evaluations`) | On register | Yes |
|
|
451
|
+
| **Bloomberg Config** | Functional | Module-level cache | On access | No |
|
|
452
|
+
|
|
453
|
+
**When to use each pattern:**
|
|
454
|
+
|
|
455
|
+
- **Import-time constants:** Static configuration that never changes (paths, flags)
|
|
456
|
+
- **Class-based registry:** Needs CRUD operations or mutable state (DataRegistry)
|
|
457
|
+
- **Class-based catalog:** Needs validation + orchestration (IndicatorTransformationRegistry, ScoreTransformationRegistry, SignalRegistry, StrategyRegistry)
|
|
458
|
+
- **Functional pattern:** Read-only lookup with lazy loading (Bloomberg config)
|
|
459
|
+
|
|
460
|
+
**Key insight:** Both class-based and functional patterns satisfy the governance spine. Choose based on:
|
|
461
|
+
1. **Mutability needs:** Mutable state → class-based
|
|
462
|
+
2. **Validation complexity:** Fail-fast validation → class-based with `__post_init__`
|
|
463
|
+
3. **Simplicity:** Read-only lookups → functional
|
|
464
|
+
|
|
465
|
+
---
|
|
466
|
+
|
|
467
|
+
## 7. Fail-Fast Validation
|
|
468
|
+
|
|
469
|
+
### IndicatorTransformationRegistry Validation
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
def _validate_catalog(self) -> None:
|
|
473
|
+
"""Validate that all indicator compute functions exist in indicators module."""
|
|
474
|
+
for name, metadata in self._indicators.items():
|
|
475
|
+
if not hasattr(indicators, metadata.compute_function_name):
|
|
476
|
+
raise ValueError(
|
|
477
|
+
f"Indicator '{name}' references non-existent compute function: "
|
|
478
|
+
f"{metadata.compute_function_name}"
|
|
479
|
+
)
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
**Timing:** Called at end of `_load_catalog()` before registry initialization completes.
|
|
483
|
+
|
|
484
|
+
**Benefits:**
|
|
485
|
+
- Catches typos in function names immediately
|
|
486
|
+
- Prevents runtime failures during indicator computation
|
|
487
|
+
- Clear error messages with indicator name and missing function
|
|
488
|
+
|
|
489
|
+
### SignalRegistry Validation
|
|
490
|
+
|
|
491
|
+
```python
|
|
492
|
+
def _validate_catalog(self) -> None:
|
|
493
|
+
"""Validate that all signals have required fields and reference valid indicators."""
|
|
494
|
+
for name, metadata in self._signals.items():
|
|
495
|
+
# Enforce schema: signals MUST have indicator_dependencies and transformations
|
|
496
|
+
if not metadata.indicator_dependencies:
|
|
497
|
+
raise ValueError(
|
|
498
|
+
f"Signal '{name}' missing required field: indicator_dependencies"
|
|
499
|
+
)
|
|
500
|
+
if not metadata.transformations:
|
|
501
|
+
raise ValueError(
|
|
502
|
+
f"Signal '{name}' missing required field: transformations"
|
|
503
|
+
)
|
|
504
|
+
|
|
505
|
+
# Reject legacy fields
|
|
506
|
+
if hasattr(metadata, 'compute_function_name'):
|
|
507
|
+
raise ValueError(
|
|
508
|
+
f"Signal '{name}' uses deprecated field 'compute_function_name'. "
|
|
509
|
+
"Signals must reference indicators via indicator_dependencies."
|
|
510
|
+
)
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
**Timing:** Called during SignalMetadata.__post_init__() and at end of `_load_catalog()`.
|
|
514
|
+
|
|
515
|
+
**Benefits:**
|
|
516
|
+
- Enforces new architecture (no embedded computation in signals)
|
|
517
|
+
- Prevents mixing of old and new patterns
|
|
518
|
+
- Clear migration path for deprecated fields
|
|
519
|
+
|
|
520
|
+
### StrategyRegistry Validation
|
|
521
|
+
|
|
522
|
+
```python
|
|
523
|
+
@dataclass(frozen=True)
|
|
524
|
+
class StrategyMetadata:
|
|
525
|
+
# ... fields ...
|
|
526
|
+
|
|
527
|
+
def __post_init__(self) -> None:
|
|
528
|
+
"""Validate strategy metadata."""
|
|
529
|
+
if self.position_size_mm <= 0:
|
|
530
|
+
raise ValueError(
|
|
531
|
+
f"Strategy '{self.name}': position_size_mm must be positive, "
|
|
532
|
+
f"got {self.position_size_mm}"
|
|
533
|
+
)
|
|
534
|
+
if self.sizing_mode not in {"binary", "proportional"}:
|
|
535
|
+
raise ValueError(
|
|
536
|
+
f"Strategy '{self.name}': sizing_mode must be 'binary' or 'proportional', "
|
|
537
|
+
f"got '{self.sizing_mode}'"
|
|
538
|
+
)
|
|
539
|
+
```
|
|
540
|
+
|
|
541
|
+
**Timing:** Runs during dataclass instantiation (in `_load_catalog()`).
|
|
542
|
+
|
|
543
|
+
**Benefits:**
|
|
544
|
+
- Enforces valid position sizing parameters
|
|
545
|
+
- Prevents invalid BacktestConfig creation
|
|
546
|
+
- No need for separate validation step
|
|
547
|
+
|
|
548
|
+
---
|
|
549
|
+
|
|
550
|
+
**End of Document**
|
|
551
|
+
|