Source code for finstmt.forecast.models.base
import datetime
from typing import Optional, Tuple, Union
import matplotlib.pyplot as plt
import pandas as pd
from pandas import DatetimeIndex
from finstmt.exc import ForecastNotFitException, ForecastNotPredictedException
from finstmt.forecast.config import ForecastConfig, ForecastItemConfig
from finstmt.forecast.plot import plot_forecast
from finstmt.items.config import ItemConfig
[docs]class ForecastModel:
result: Optional[pd.Series] = None
result_df: Optional[pd.DataFrame] = None
last_historical_period: Optional[datetime.datetime] = None
orig_series: Optional[pd.Series] = None
[docs] def __init__(
self,
config: ForecastConfig,
item_config: ForecastItemConfig,
base_config: ItemConfig,
):
self.config = config
self.item_config = item_config
self.base_config = base_config
self.has_been_fit = False
self.has_prediction = False
[docs] def fit(self, series: pd.Series):
self.last_historical_period = series.index.max()
self.has_been_fit = True
self.orig_series = series
[docs] def predict(self) -> pd.Series:
self.has_prediction = True
return pd.Series()
[docs] def plot(
self,
ax: Optional[plt.Axes] = None,
figsize: Tuple[int, int] = (12, 5),
xlabel: Optional[str] = None,
ylabel: Optional[str] = None,
title: Optional[str] = None,
) -> plt.Figure:
if xlabel is None:
xlabel = "Time"
if title is None:
title = self.base_config.display_name
if self.orig_series is None:
raise ForecastNotPredictedException("call .fit then .predict before .plot")
return plot_forecast(
self.result_df,
self.orig_series.values,
self.orig_series.index,
ax=ax,
figsize=figsize,
xlabel=xlabel,
ylabel=ylabel,
title=title,
)
@property
def _future_date_range(self) -> DatetimeIndex:
if not self.has_been_fit:
raise ForecastNotFitException("call .fit before ._future_date_range")
return pd.date_range(
start=self.last_historical_period,
periods=self.config.periods + 1,
freq=self.config.freq,
closed="right",
)
@property
def historical_freq(self) -> str:
if self.orig_series is None:
raise ForecastNotFitException("call .fit before .historical_freq")
return pd.infer_freq(self.orig_series.index)
@property
def desired_freq_t_multiplier(self) -> float:
"""
The multiplier of the forecast frequncy versus the historical frequency. E.g.
if the forecast is annual and historical is quarterly then the multiplier is 4.
:return:
"""
if self.orig_series is None:
raise ForecastNotFitException("call .fit before .desired_freq_t_multiplier")
return compare_freq_strs(
self.config.freq, self.historical_freq, ref_date=self.orig_series.index[-1]
)
[docs]def compare_freq_strs(
freq1: str,
freq2: str,
ref_date: Union[pd.Timestamp, datetime.datetime, str] = "1/1/2000",
) -> float:
periods = 10
dates1 = pd.date_range(start=ref_date, freq=freq1, periods=periods)
td1 = dates1[-1] - dates1[0]
dates2 = pd.date_range(start=ref_date, freq=freq2, periods=periods)
td2 = dates2[-1] - dates2[0]
return td1 / td2