"""Theta Model""" __docformat__ = "numpy" import logging from typing import Optional, Tuple, Union, List import warnings from statsmodels.tools.sm_exceptions import ConvergenceWarning import numpy as np import pandas as pd from darts import TimeSeries from darts.models import Theta from darts.utils.utils import SeasonalityMode from darts.metrics import mape from openbb_terminal.decorators import log_start_end from openbb_terminal.rich_config import console from openbb_terminal.forecast import helpers warnings.simplefilter("ignore", ConvergenceWarning) SEASONS = ["N", "A", "M"] PERIODS = [4, 5, 7] logger = logging.getLogger(__name__) @log_start_end(log=logger) def get_theta_data( data: Union[pd.Series, pd.DataFrame], target_column: str = "close", seasonal: str = "M", seasonal_periods: int = 7, n_predict: int = 5, start_window: float = 0.85, forecast_horizon: int = 5, ) -> Tuple[ Optional[List[TimeSeries]], Optional[List[TimeSeries]], Optional[List[TimeSeries]], Optional[float], Optional[float], Optional[type[Theta]], ]: """Performs Theta forecasting An implementation of the 4Theta method with configurable theta parameter. https://unit8co.github.io/darts/generated_api/darts.models.forecasting.theta.html Parameters ---------- data : Union[pd.Series, np.ndarray] Input data. target_column: Optional[str]: Target column to forecast. Defaults to "close". seasonal: str Seasonal component. One of [N, A, M] Defaults to MULTIPLICATIVE. seasonal_periods: int Number of seasonal periods in a year If not set, inferred from frequency of the series. n_predict: int Number of days to forecast start_window: float Size of sliding window from start of timeseries and onwards forecast_horizon: int Number of days to forecast when backtesting and retraining historical Returns ------- Tuple[List[TimeSeries], List[TimeSeries], List[TimeSeries], float, float, type[Theta]] Adjusted Data series, Historical forecast by best theta, list of Predictions, Mean average precision error, Best Theta, Theta Model. """ use_scalers = False _, ticker_series = helpers.get_series(data, target_column, is_scaler=use_scalers) train, val = ticker_series.split_before(start_window) if seasonal == "A": seasonal = SeasonalityMode.ADDITIVE elif seasonal == "N": seasonal = SeasonalityMode.NONE else: # Default seasonal = SeasonalityMode.MULTIPLICATIVE thetas = np.linspace(-10, 10, 50) best_mape = float("inf") best_theta = 0 error = "" for theta in thetas: model = Theta( theta=theta, season_mode=seasonal, seasonality_period=seasonal_periods, ) try: model.fit(train) pred_theta = model.predict(len(val)) res = mape(val, pred_theta) if res < best_mape: best_mape = res best_theta = theta except Exception as e: # noqa error = str(e) if best_theta == 0: console.print(f"[red]{error}[/red]") return [], [], [], 0, 0, None best_theta_model = Theta( best_theta, season_mode=seasonal, seasonality_period=seasonal_periods, ) # Training model based on historical backtesting historical_fcast_theta = best_theta_model.historical_forecasts( ticker_series, start=float(start_window), forecast_horizon=int(forecast_horizon), verbose=True, ) best_theta_model_final = Theta( best_theta, season_mode=seasonal, seasonality_period=seasonal_periods, ) # fit model on entire series for final prediction best_theta_model_final.fit(ticker_series) prediction = best_theta_model_final.predict(int(n_predict)) precision = mape( actual_series=ticker_series, pred_series=historical_fcast_theta ) # mape = mean average precision error console.print(f"Theta Model obtains MAPE: {precision:.2f}% \n") return ( ticker_series, historical_fcast_theta, prediction, precision, best_theta, best_theta_model, )