diff --git a/CHANGELOG.md b/CHANGELOG.md index 6df677f..1c8d34e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,15 @@ ## Unreleased +- Added PIT release rules (`alphaforge.pit.release_rules`): `NthBusinessDay`, `NthWeekday`, `CalendarDay`, `FixedLagMonths`, `QuarterlyRelease`, `WeeklyRelease`, and `CustomRule` with a tagged-union registry for YAML round-trips. +- Added vintage resolvers (`alphaforge.pit.resolvers`): `RealtimeResolver`, `LatestResolver`, and `FrozenResolver` implementing the `VintageResolver` protocol for point-in-time backtesting views. +- Added `VintageView` value object (`alphaforge.pit.views`) to declare realtime / latest / frozen vintage strategies without coupling to resolution logic. +- Added PIT panel builder utilities (`alphaforge.pit.panel`): `build_pit_panel` and `long_to_wide` for assembling aligned panels from PIT snapshots. +- Added missingness taxonomy (`alphaforge.pit.missingness`) for classifying NaN cells in nowcasting panels by cause. +- Added vintage selection and lookahead validation utilities (`alphaforge.pit.vintage`): `select_vintage_for_asof` and `validate_no_lookahead`. +- Fixed CI linting errors: removed unused imports and sorted import blocks across new modules and test files. +- Fixed mypy type errors: tightened `_register` signature in `pit/release_rules.py`, added `name: str` to `Parametric` protocol in `pipeline/protocols.py`, and corrected `Mapping[Any, str]` annotation in `data/short_rates.py`. + - Added `alphaforge.evaluation` package with pluggable metric infrastructure: - `MetricFn` protocol (runtime-checkable) for composable forecast accuracy metrics. - Built-in implementations: `RMSE`, `MAE`, `DirectionalAccuracy`, `MAPE`, `MeanError`. diff --git a/alphaforge/__init__.py b/alphaforge/__init__.py index 5d3a92c..394f82f 100644 --- a/alphaforge/__init__.py +++ b/alphaforge/__init__.py @@ -4,13 +4,6 @@ from .data.fred_source import FREDDataSource from .data.panel import PanelFrame from .data.pit_source import PITDataSource -from .data.short_rates import ( - ShortRateDataset, - build_duan_weekly_dataset, - build_kim_orphanides_dataset, - build_macro_finance_dataset, - build_policy_rule_dataset, -) from .data.public_web import ( ANPFuelPricesDataSource, B3HistoricalQuotesDataSource, @@ -35,6 +28,13 @@ ) from .data.query import Query from .data.schema import TableSchema +from .data.short_rates import ( + ShortRateDataset, + build_duan_weekly_dataset, + build_kim_orphanides_dataset, + build_macro_finance_dataset, + build_policy_rule_dataset, +) from .data.universe import EntityMetadata, Universe from .features.frame import Artifact, FeatureFrame from .features.ops import join_feature_frames, materialize @@ -74,7 +74,6 @@ coerce_pipeline_spec, ) from .pit.ref_entity import make_ref_entity_id, parse_ref_entity_id -from .registry import EntityEntry, EntityRegistry from .pit.tasks import ( build_snapshot_tape, first_vintage_snapshot, @@ -93,6 +92,7 @@ ) from .pit.transforms import PITTransformResult, PITTransformSpec from .pit.validation import PITValidationReport, validate_pit_observations +from .registry import EntityEntry, EntityRegistry from .store.cache import MaterializationPolicy from .store.duckdb_parquet import DuckDBParquetStore from .time.align import AlignedPanel, AlignSpec, AvailabilityState, align_panel diff --git a/alphaforge/data/short_rates.py b/alphaforge/data/short_rates.py index e2e5e43..c5af127 100644 --- a/alphaforge/data/short_rates.py +++ b/alphaforge/data/short_rates.py @@ -3,7 +3,7 @@ from __future__ import annotations from dataclasses import dataclass, field -from typing import Mapping +from typing import Any, Mapping import pandas as pd @@ -105,7 +105,7 @@ def _fetch_fred_panel( ctx: DataContext, *, source: str, - series_map: Mapping[object, str], + series_map: Mapping[Any, str], start: pd.Timestamp, end: pd.Timestamp, sort_labels: bool = True, diff --git a/alphaforge/evaluation/__init__.py b/alphaforge/evaluation/__init__.py index bb11212..a607243 100644 --- a/alphaforge/evaluation/__init__.py +++ b/alphaforge/evaluation/__init__.py @@ -28,12 +28,12 @@ from .metrics import ( BENCHMARK_METRICS, DEFAULT_METRICS, - MAPE, MAE, - MeanError, - MetricFn, + MAPE, RMSE, DirectionalAccuracy, + MeanError, + MetricFn, ) __all__ = [ diff --git a/alphaforge/evaluation/metrics.py b/alphaforge/evaluation/metrics.py index 31b1cac..43bdaa8 100644 --- a/alphaforge/evaluation/metrics.py +++ b/alphaforge/evaluation/metrics.py @@ -81,7 +81,6 @@ def __call__(self, y_pred, y_true): import numpy as np - # --------------------------------------------------------------------------- # Protocol # --------------------------------------------------------------------------- diff --git a/alphaforge/pipeline/protocols.py b/alphaforge/pipeline/protocols.py index 03ecec1..3eeb297 100644 --- a/alphaforge/pipeline/protocols.py +++ b/alphaforge/pipeline/protocols.py @@ -64,6 +64,8 @@ class Parametric(Protocol): Transformers and Signals may implement this. Filters do NOT. """ + name: str + def get_params(self) -> dict[str, float]: ... def set_params(self, params: dict[str, float]) -> None: ... diff --git a/alphaforge/pipeline/tracker.py b/alphaforge/pipeline/tracker.py index 47af96d..7efd0d1 100644 --- a/alphaforge/pipeline/tracker.py +++ b/alphaforge/pipeline/tracker.py @@ -5,7 +5,7 @@ import pandas as pd -from alphaforge.pit.accessor import PITAccessor, to_utc_aware +from alphaforge.pit.accessor import PITAccessor from .health import SourceHealthPolicy, SourceHealthStatus, assess_source_health @@ -28,7 +28,6 @@ def _prefix(self, source_name: str) -> str: return source_name.replace("_", ".") def _latest_obs_date(self, source_name: str) -> pd.Timestamp | None: - prefix = self._prefix(source_name) try: result = self.pit.conn.execute( "SELECT MAX(obs_date) FROM pit_observations WHERE source = ?", diff --git a/alphaforge/pit/__init__.py b/alphaforge/pit/__init__.py index da69d2c..4e0fdec 100644 --- a/alphaforge/pit/__init__.py +++ b/alphaforge/pit/__init__.py @@ -1,12 +1,5 @@ from .accessor import PITAccessor, ensure_pit_table from .contract import PIT_CONTRACT_VERSION, PITContractVersion, get_pit_contract_version -from .resolvers import ( - FrozenResolver, - LatestResolver, - RealtimeResolver, - VintageResolver, -) -from .views import VintageView from .exceptions import ( PITCausalityError, PITContractError, @@ -49,6 +42,12 @@ coerce_pipeline_spec, ) from .ref_entity import make_ref_entity_id, parse_ref_entity_id +from .resolvers import ( + FrozenResolver, + LatestResolver, + RealtimeResolver, + VintageResolver, +) from .tasks import ( build_snapshot_tape, first_vintage_snapshot, @@ -67,6 +66,7 @@ ) from .transforms import PITTransformResult, PITTransformSpec from .validation import PITValidationReport, validate_pit_observations +from .views import VintageView __all__ = [ "PITAccessor", diff --git a/alphaforge/pit/release_rules.py b/alphaforge/pit/release_rules.py index 15bc545..ff8e30b 100644 --- a/alphaforge/pit/release_rules.py +++ b/alphaforge/pit/release_rules.py @@ -28,12 +28,6 @@ RULE_REGISTRY: dict[str, type] = {} -def _register(cls: type) -> type: - """Decorator that adds a ReleaseRule subclass to the registry.""" - RULE_REGISTRY[cls.rule_type] = cls - return cls - - # --------------------------------------------------------------------------- # Base # --------------------------------------------------------------------------- @@ -85,6 +79,12 @@ def from_dict(d: dict[str, Any]) -> ReleaseRule: return cls(**kwargs) +def _register(cls: type[ReleaseRule]) -> type[ReleaseRule]: + """Decorator that adds a ReleaseRule subclass to the registry.""" + RULE_REGISTRY[cls.rule_type] = cls + return cls + + # --------------------------------------------------------------------------- # Concrete rule classes # --------------------------------------------------------------------------- diff --git a/alphaforge/pit/resolvers.py b/alphaforge/pit/resolvers.py index 28a3302..034c027 100644 --- a/alphaforge/pit/resolvers.py +++ b/alphaforge/pit/resolvers.py @@ -14,7 +14,6 @@ from alphaforge.pit.views import VintageView - # --------------------------------------------------------------------------- # Protocol # --------------------------------------------------------------------------- diff --git a/docs/api/pit-missingness.md b/docs/api/pit-missingness.md new file mode 100644 index 0000000..ea3438c --- /dev/null +++ b/docs/api/pit-missingness.md @@ -0,0 +1,3 @@ +# PIT Missingness + +::: alphaforge.pit.missingness diff --git a/docs/api/pit-panel.md b/docs/api/pit-panel.md new file mode 100644 index 0000000..d8a8c35 --- /dev/null +++ b/docs/api/pit-panel.md @@ -0,0 +1,3 @@ +# PIT Panel Builder + +::: alphaforge.pit.panel diff --git a/docs/api/pit-release-rules.md b/docs/api/pit-release-rules.md new file mode 100644 index 0000000..df1b9a3 --- /dev/null +++ b/docs/api/pit-release-rules.md @@ -0,0 +1,3 @@ +# PIT Release Rules + +::: alphaforge.pit.release_rules diff --git a/docs/api/pit-resolvers.md b/docs/api/pit-resolvers.md new file mode 100644 index 0000000..66a1e9b --- /dev/null +++ b/docs/api/pit-resolvers.md @@ -0,0 +1,3 @@ +# PIT Vintage Resolvers + +::: alphaforge.pit.resolvers diff --git a/docs/api/pit-views.md b/docs/api/pit-views.md new file mode 100644 index 0000000..4e56467 --- /dev/null +++ b/docs/api/pit-views.md @@ -0,0 +1,3 @@ +# PIT Vintage Views + +::: alphaforge.pit.views diff --git a/docs/api/pit-vintage.md b/docs/api/pit-vintage.md new file mode 100644 index 0000000..efaa6e3 --- /dev/null +++ b/docs/api/pit-vintage.md @@ -0,0 +1,3 @@ +# PIT Vintage Utilities + +::: alphaforge.pit.vintage diff --git a/examples/short_rate_kim_dataset.py b/examples/short_rate_kim_dataset.py index 887f092..a719db3 100644 --- a/examples/short_rate_kim_dataset.py +++ b/examples/short_rate_kim_dataset.py @@ -10,8 +10,8 @@ from alphaforge import ( DataContext, DuckDBParquetStore, - FREDDataSource, FRBTermStructureBenchmarkSource, + FREDDataSource, PhiladelphiaSPFMeanLevelSource, TradingCalendar, build_kim_orphanides_dataset, diff --git a/mkdocs.yml b/mkdocs.yml index dfb2a5c..1d79985 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -52,6 +52,12 @@ nav: - Dataset Builder: api/dataset-builder.md - Dataset Spec: api/dataset-spec.md - PIT Accessor: api/pit-accessor.md + - PIT Release Rules: api/pit-release-rules.md + - PIT Vintage Views: api/pit-views.md + - PIT Vintage Resolvers: api/pit-resolvers.md + - PIT Panel Builder: api/pit-panel.md + - PIT Missingness: api/pit-missingness.md + - PIT Vintage Utilities: api/pit-vintage.md - PIT Transforms: api/pit-transforms.md - PIT Pipelines: api/pit-pipelines.md - PIT Models: api/pit-models.md diff --git a/tests/test_logging.py b/tests/test_logging.py index 428ffca..e952748 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -1,12 +1,9 @@ """Tests for alphaforge.logging module.""" from __future__ import annotations -import json import logging -import tempfile from pathlib import Path -import structlog from structlog.testing import capture_logs from alphaforge.logging import configure_logging, get_logger diff --git a/tests/test_pipeline_protocols.py b/tests/test_pipeline_protocols.py index e93444c..5cb8c17 100644 --- a/tests/test_pipeline_protocols.py +++ b/tests/test_pipeline_protocols.py @@ -10,14 +10,12 @@ from alphaforge.pipeline.protocols import ( Filter, Parametric, - Pipeline, PipelineVariant, Signal, SimplePipeline, Transformer, ) - # --- Stub implementations --- diff --git a/tests/test_pit_panel_builder.py b/tests/test_pit_panel_builder.py index 829aa88..a1796f6 100644 --- a/tests/test_pit_panel_builder.py +++ b/tests/test_pit_panel_builder.py @@ -4,7 +4,6 @@ from datetime import date import pandas as pd -import pytest from alphaforge.pit.vintage import select_vintage_for_asof, validate_no_lookahead diff --git a/tests/test_release_rules.py b/tests/test_release_rules.py index 173cd28..a7c555c 100644 --- a/tests/test_release_rules.py +++ b/tests/test_release_rules.py @@ -6,11 +6,8 @@ from alphaforge.pit.release_rules import ( RULE_REGISTRY, - CalendarDay, CustomRule, - FixedLagMonths, NthBusinessDay, - NthWeekday, QuarterlyRelease, ReleaseRule, WeeklyRelease, diff --git a/tests/test_torch_protocols.py b/tests/test_torch_protocols.py index b02d9eb..d066280 100644 --- a/tests/test_torch_protocols.py +++ b/tests/test_torch_protocols.py @@ -7,7 +7,7 @@ torch = pytest.importorskip("torch") nn = torch.nn -from alphaforge.pipeline.torch_protocols import ( +from alphaforge.pipeline.torch_protocols import ( # noqa: E402 DifferentiableSignalModule, NumpyDifferentiableWrapper, TorchParametric, diff --git a/tests/test_vintage_resolvers.py b/tests/test_vintage_resolvers.py index 7c7aaf8..1282eb2 100644 --- a/tests/test_vintage_resolvers.py +++ b/tests/test_vintage_resolvers.py @@ -14,7 +14,6 @@ ) from alphaforge.pit.views import VintageView - # --------------------------------------------------------------------------- # VintageView value object # ---------------------------------------------------------------------------