"""Standardized result containers for wraquant.
Dataclasses that provide consistent, IDE-friendly access to results
from GARCH fitting, backtesting, forecasting, and other operations.
Each result type carries chaining methods that lazily import downstream
modules, enabling composable workflows:
garch_result.to_var(alpha=0.05) # -> risk/var
garch_result.plot() # -> viz
backtest_result.to_tearsheet() # -> backtest/tearsheet
forecast_result.plot() # -> viz
"""
from __future__ import annotations
from dataclasses import dataclass, field
from typing import Any
import numpy as np
import pandas as pd
[docs]
@dataclass
class GARCHResult:
"""Result from GARCH-family model fitting.
Attributes:
params: Fitted parameters dict (omega, alpha, beta, ...).
conditional_volatility: Time series of conditional std dev.
standardized_residuals: Residuals / conditional vol.
aic: Akaike Information Criterion.
bic: Bayesian Information Criterion.
log_likelihood: Maximized log-likelihood.
persistence: Sum of alpha + beta parameters.
half_life: Periods for shock to decay 50%.
unconditional_variance: Long-run variance.
model: Underlying fitted model object.
model_name: Name of the GARCH model variant.
"""
params: dict[str, float]
conditional_volatility: pd.Series
standardized_residuals: np.ndarray
aic: float
bic: float
log_likelihood: float
persistence: float
half_life: float
unconditional_variance: float
model: Any = None
ljung_box: dict | None = None
model_name: str = ""
[docs]
def __getitem__(self, key: str) -> Any:
"""Support dict-like access for backward compatibility."""
return getattr(self, key)
[docs]
def __contains__(self, key: object) -> bool:
"""Support ``'key' in result`` for backward compatibility."""
return hasattr(self, key) if isinstance(key, str) else False
[docs]
def get(self, key: str, default: Any = None) -> Any:
"""Support ``result.get('key')`` for backward compatibility."""
return getattr(self, key, default)
[docs]
def to_var(self, alpha: float = 0.05) -> dict:
"""Compute GARCH-based Value at Risk.
Uses the fitted conditional volatility to estimate VaR via
``wraquant.risk.var.garch_var``.
Parameters:
alpha: Significance level (default 0.05 = 95% VaR).
Returns:
Dict with VaR results from ``risk.var.garch_var``.
"""
from wraquant.risk.var import garch_var
return garch_var(
self.conditional_volatility,
alpha=alpha,
)
[docs]
def plot(self) -> Any:
"""Plot conditional volatility using viz module.
Returns:
Plotly figure object.
"""
from wraquant.viz.interactive import plotly_returns
return plotly_returns(self.conditional_volatility, title="Conditional Volatility")
[docs]
def summary(self) -> str:
"""Human-readable summary of GARCH fit.
Returns:
Multi-line string with key diagnostics.
"""
param_str = ", ".join(f"{k}={v:.6f}" for k, v in self.params.items())
lines = [
f"GARCH Result",
f" Parameters: {param_str}",
f" Persistence: {self.persistence:.4f}",
f" Half-life: {self.half_life:.1f} periods",
f" Unconditional vol: {np.sqrt(self.unconditional_variance):.4f}",
f" AIC: {self.aic:.2f} BIC: {self.bic:.2f}",
f" Log-likelihood: {self.log_likelihood:.2f}",
]
return "\n".join(lines)
[docs]
@dataclass
class BacktestResult:
"""Result from a backtest run.
Attributes:
returns: Portfolio return series.
equity_curve: Cumulative equity curve (aliased as portfolio_value).
metrics: Dict of performance metrics (Sharpe, max_dd, etc.).
trades: Trade count (int) or list of trade records.
signals: Signal series used.
positions: Position weights over time (if available).
"""
returns: pd.Series
equity_curve: pd.Series
metrics: dict[str, float]
trades: int | list[dict] = 0
signals: pd.Series | None = None
positions: pd.DataFrame | None = None
@property
def portfolio_value(self) -> pd.Series:
"""Alias for equity_curve (backward compatibility)."""
return self.equity_curve
[docs]
def __getitem__(self, key: str) -> Any:
"""Support dict-like access for backward compatibility."""
return getattr(self, key)
[docs]
def __contains__(self, key: object) -> bool:
"""Support ``'key' in result`` for backward compatibility."""
return hasattr(self, key) if isinstance(key, str) else False
[docs]
def get(self, key: str, default: Any = None) -> Any:
"""Support ``result.get('key')`` for backward compatibility."""
return getattr(self, key, default)
@property
def sharpe(self) -> float:
return self.metrics.get("sharpe_ratio", float("nan"))
@property
def max_drawdown(self) -> float:
return self.metrics.get("max_drawdown", float("nan"))
[docs]
def to_tearsheet(self, **kwargs: Any) -> dict:
"""Generate a comprehensive tearsheet.
Delegates to ``wraquant.backtest.tearsheet.comprehensive_tearsheet``.
Parameters:
**kwargs: Forwarded to ``comprehensive_tearsheet``.
Returns:
Dict with full tearsheet analysis.
"""
from wraquant.backtest.tearsheet import comprehensive_tearsheet
return comprehensive_tearsheet(self.returns, **kwargs)
[docs]
def plot(self) -> Any:
"""Plot the equity curve using viz module.
Returns:
Plotly figure object.
"""
from wraquant.viz.interactive import plotly_returns
return plotly_returns(self.equity_curve, title="Equity Curve")
[docs]
def summary(self) -> str:
"""Human-readable summary of backtest results.
Returns:
Multi-line string with key performance metrics.
"""
lines = ["Backtest Result"]
for key, value in self.metrics.items():
if isinstance(value, float):
lines.append(f" {key}: {value:.4f}")
else:
lines.append(f" {key}: {value}")
trade_count = self.trades if isinstance(self.trades, int) else len(self.trades)
lines.append(f" Trades: {trade_count}")
return "\n".join(lines)
[docs]
@dataclass
class ForecastResult:
"""Result from time series forecasting.
Attributes:
forecast: Point forecast values.
confidence_lower: Lower confidence bound.
confidence_upper: Upper confidence bound.
method: Forecasting method used.
fitted_values: In-sample fitted values.
residuals: In-sample residuals.
metrics: Fit metrics (AIC, BIC, RMSE).
"""
forecast: pd.Series | np.ndarray
confidence_lower: np.ndarray | None = None
confidence_upper: np.ndarray | None = None
method: str = ""
fitted_values: np.ndarray | None = None
residuals: np.ndarray | None = None
metrics: dict[str, float] = field(default_factory=dict)
model: Any = None
[docs]
def __getitem__(self, key: str) -> Any:
"""Support dict-like access for backward compatibility."""
return getattr(self, key)
[docs]
def __contains__(self, key: object) -> bool:
"""Support ``'key' in result`` for backward compatibility."""
return hasattr(self, key) if isinstance(key, str) else False
[docs]
def get(self, key: str, default: Any = None) -> Any:
"""Support ``result.get('key')`` for backward compatibility."""
return getattr(self, key, default)
[docs]
def plot(self) -> Any:
"""Plot forecast with confidence bounds using viz module.
Returns:
Plotly figure object.
"""
from wraquant.viz.interactive import plotly_returns
forecast_series = (
self.forecast
if isinstance(self.forecast, pd.Series)
else pd.Series(self.forecast)
)
return plotly_returns(forecast_series, title=f"Forecast ({self.method})")
[docs]
def summary(self) -> str:
"""Human-readable summary of forecast.
Returns:
Multi-line string with method and fit metrics.
"""
lines = [f"Forecast Result (method={self.method})"]
for key, value in self.metrics.items():
if isinstance(value, float):
lines.append(f" {key}: {value:.4f}")
else:
lines.append(f" {key}: {value}")
if isinstance(self.forecast, (pd.Series, np.ndarray)):
n = len(self.forecast)
lines.append(f" Horizon: {n} periods")
return "\n".join(lines)