Source code for finstmt.forecast.models.cagr

import warnings
from typing import Optional

import pandas as pd

from finstmt.exc import ForecastNotFitException
from finstmt.forecast.models.base import ForecastModel


[docs]class CAGRModel(ForecastModel): cagr: Optional[float] = None stderr: Optional[float] = None last_value: Optional[float] = None
[docs] def fit(self, series: pd.Series): y_T = series.iloc[-1] self.last_value = y_T y_0 = series.iloc[0] if y_0 <= 0 or y_T <= 0: # Invalid data for CAGR method self.cagr = 0 self.stderr = 0 specific_messages = [] if y_0 == 0: specific_messages.append("y_0 is 0") elif y_0 < 0: specific_messages.append("y_0 is negative") if y_T == 0: specific_messages.append("y_T is 0") elif y_T < 0: specific_messages.append("y_T is negative") specific_message = ", ".join(specific_messages) message = ( f"CAGR not an appropriate method for {self.base_config.display_name} " f"as {specific_message}. Setting to 0 growth (recent value forecast)" ) warnings.warn(message) else: n = len(series) self.cagr = (y_T / y_0) ** (1 / n) - 1 self.stderr = series.pct_change().std() / (n**0.5) super().fit(series)
[docs] def predict(self) -> pd.Series: if ( self.cagr is None or self.stderr is None or self.last_value is None or self.orig_series is None ): raise ForecastNotFitException("call .fit before .predict") adj_cagr = (1 + self.cagr) ** (self.desired_freq_t_multiplier) - 1 adj_stderr = (1 + self.stderr) ** (self.desired_freq_t_multiplier) - 1 cagr_dict = dict( lower=self.cagr - self.stderr * 2, upper=self.cagr + self.stderr * 2, mean=self.cagr, ) adj_cagr_dict = dict( lower=adj_cagr - adj_stderr * 2, upper=adj_cagr + adj_stderr * 2, mean=adj_cagr, ) # Start from last period, apply growth to predict into future future_df = pd.DataFrame(index=self._future_date_range) for col_name, cagr in adj_cagr_dict.items(): last_value = self.last_value future_values = [] for _ in range(self.config.periods): next_value = last_value * (1 + cagr) future_values.append(next_value) last_value = next_value future_df[col_name] = future_values self.result = future_df["mean"] # Start from last period, work back to earliest period removing growth to assess fit orig_dates = self.orig_series.index past_df = pd.DataFrame(index=reversed(orig_dates)) for col_name, cagr in cagr_dict.items(): last_value = self.last_value past_values = [self.last_value] for _ in range(len(orig_dates) - 1): next_value = last_value / (1 + cagr) past_values.append(next_value) last_value = next_value past_df[col_name] = past_values self.result_df = past_df.append(future_df).sort_index() super().predict() return self.result