53 lines
1.8 KiB
Python
53 lines
1.8 KiB
Python
"""Distribution shapes: standardised quantiles (mean 0, sd 1) at each horizon."""
|
|||
|
|
|
||
|
|
from dataclasses import dataclass
|
||
|
|
|
||
|
|
import numpy as np
|
||
|
|
import pandas as pd
|
||
|
|
from scipy.stats import norm, t
|
||
|
|
|
||
|
|
from ..forecast import LEVELS
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class Normal:
|
||
|
|
def standard_quantiles(self, history: pd.DataFrame, horizons: np.ndarray) -> np.ndarray:
|
||
|
|
return np.broadcast_to(norm.ppf(LEVELS), (len(horizons), len(LEVELS)))
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class StudentT:
|
||
|
|
"""Student's t with `df` degrees of freedom, rescaled to unit variance."""
|
||
|
|
|
||
|
|
df: float = 4.0
|
||
|
|
|
||
|
|
def standard_quantiles(self, history: pd.DataFrame, horizons: np.ndarray) -> np.ndarray:
|
||
|
|
q = t.ppf(LEVELS, self.df) / np.sqrt(self.df / (self.df - 2))
|
||
|
|
return np.broadcast_to(q, (len(horizons), len(LEVELS)))
|
||
|
|
|
||
|
|
|
||
|
|
@dataclass(frozen=True)
|
||
|
|
class Empirical:
|
||
|
|
"""
|
||
|
|
Filtered historical simulation at the horizon level: the shape of past
|
||
|
|
h-day log returns, each divided by the trailing volatility at its start.
|
||
|
|
|
||
|
|
Overlapping h-day returns are far from independent, so a horizon falls back
|
||
|
|
to normal unless the history spans at least `min_windows` of them.
|
||
|
|
"""
|
||
|
|
|
||
|
|
vol_window: int = 365
|
||
|
|
min_windows: int = 3
|
||
|
|
|
||
|
|
def standard_quantiles(self, history: pd.DataFrame, horizons: np.ndarray) -> np.ndarray:
|
||
|
|
log_price = np.log(history["close"])
|
||
|
|
sigma = log_price.diff().rolling(self.vol_window).std()
|
||
|
|
out = np.empty((len(horizons), len(LEVELS)))
|
||
|
|
for i, h in enumerate(horizons):
|
||
|
|
z = ((log_price.shift(-h) - log_price) / (sigma * np.sqrt(h))).dropna()
|
||
|
|
if len(z) < self.min_windows * h:
|
||
|
|
out[i] = norm.ppf(LEVELS)
|
||
|
|
else:
|
||
|
|
out[i] = np.quantile((z - z.mean()) / z.std(), LEVELS)
|
||
|
|
return out
|