GLM / models /base_model.py
Kashaf1's picture
Upload KLO contents
5e31ef5
Raw
History Blame Contribute Delete
3.47 kB
"""
Common interface every forecasting model implements, so the rest of the app
(backtester, Gradio UI) can treat ARIMA / Auto-ARIMA / ARIMA-GARCH / Moirai /
TimesFM interchangeably without knowing their internal details.
"""
from abc import ABC, abstractmethod
import pandas as pd
class BaseForecastModel(ABC):
name = "base"
@abstractmethod
def predict(self, history: pd.Series, horizon: int = 1, features: pd.DataFrame = None) -> list:
"""
Given a 1-D series of past closing prices (oldest -> newest), return
a list of `horizon` forecasted future closing prices.
`features`, if given, is a DataFrame of extra covariates (OHLCV +
technical indicators β€” see features/feature_pipeline.py's
FEATURE_COLUMNS) covering the SAME candles as `history`, in the SAME
order, one row per candle (len(features) == len(history)). Every
value in it was already known as of its own candle β€” callers (the
backtester, the UI, live_runner.py) are responsible for never
building a `features` window that reaches past the last candle in
`history`; see features/feature_pipeline.py for how that's kept
leak-free, and its carry_forward_future() for how each model asks
for covariate values over the FORECAST horizon (where real future
values don't exist yet).
`features=None` (the default) means Close-price-only β€” every
model's original behavior, completely unchanged.
Implementations should raise a clear, specific exception (ImportError
for a missing dependency, ValueError for bad input) rather than
silently returning a wrong/empty result β€” the backtester and UI both
catch and display these per-model, without crashing other models.
"""
raise NotImplementedError
def is_available(self) -> bool:
"""True if this model's dependencies are installed and importable."""
try:
self._check_dependencies()
return True
except Exception:
return False
def _check_dependencies(self):
"""Override to raise ImportError if a required package is missing."""
return None
def describe(self) -> dict:
"""Model identity metadata for logging/reproducibility (P0 item:
"log exact model identity β€” model name, checkpoint, package
version, backend, device, context length, normalization, seed,
preprocessing version"). Returns a flat dict; a field this model
doesn't have (e.g. "checkpoint" for ARIMA, which has none) is
simply omitted rather than filled with a placeholder. Override in
subclasses to add model-specific fields β€” always start from
`super().describe()` and update it, so every model at minimum
reports its name and this project's feature/preprocessing version
(features/feature_pipeline.py's FEATURE_PIPELINE_VERSION), which
matters here specifically because a fitted (p,d,q) order or a
cached foundation-model instance is only valid for the exact
feature definitions it was produced under β€” see
models/registry.py's cache-key docstring.
"""
from features.feature_pipeline import FEATURE_PIPELINE_VERSION
return {
"model": self.name,
"class": type(self).__name__,
"preprocessing_version": FEATURE_PIPELINE_VERSION,
}