"""Common interface implemented by every forecasting model, so the backtester and UI can treat them interchangeably.""" import numpy as np import pandas as pd from abc import ABC, abstractmethod def validate_history_and_horizon(history, horizon: int, model_name: str) -> None: """Enforce the BaseForecastModel input contract for every model: a horizon of at least 1, and a price history that is entirely finite. A NaN/inf close (a vendor data glitch, a forming candle read mid-write) must surface as a ValueError the caller can report per model, not leak into a fit that silently produces a number anyway. """ if horizon < 1: raise ValueError(f"{model_name}: horizon must be >= 1, got {horizon}.") values = np.asarray(pd.Series(history).astype(float), dtype=float) if not np.all(np.isfinite(values)): raise ValueError( f"{model_name}: price history contains NaN/inf. This usually means " f"a data glitch left a missing or malformed candle -- re-fetch the " f"history and try again." ) class BaseForecastModel(ABC): name = "base" @abstractmethod def predict(self, history: pd.Series, horizon: int = 1, features: pd.DataFrame = None) -> list: """Forecast `horizon` future closing prices from past closes (oldest -> newest). `features`, when given, holds one row per candle of `history`, in the same order, containing only values already known as of that candle (see features/feature_pipeline.py). `features=None` is Close-only. Raise ImportError for a missing dependency and ValueError for bad input: callers report those per model and keep the others running. """ raise NotImplementedError