Files
OpenBB/openbb_platform/extensions/quantitative/tests/test_risk_decomposition.py
Danglewood cd7d602b8f [V5] openbb-quantitative: Refactor for V5 (#7506)
* refactor quantitative for v5

* add factors endpoint and add a few charts

---------

Co-authored-by: deeleeramone <>
2026-05-29 10:46:25 +03:00

93 lines
3.1 KiB
Python

"""Tests for ``openbb_quantitative.risk_decomposition``."""
import pandas as pd
import pytest
from openbb_core.app.utils import df_to_basemodel
from openbb_quantitative import risk_decomposition as rd_module
def _params(target_data, factor_data, **kwargs):
return rd_module.RiskDecompositionQueryParams(
data=target_data, factors_data=factor_data, target="close", **kwargs
)
def test_risk_decomposition_happy_path(target_returns_data, factor_matrix_data):
"""Returns rows for every factor plus a Residual row per period."""
out = rd_module.risk_decomposition(_params(target_returns_data, factor_matrix_data))
results = out.results
assert isinstance(results, list)
assert results
periods = {r.period for r in results}
for period in periods:
period_factors = [r.factor for r in results if r.period == period]
# f1, f2, rf, Residual
assert set(period_factors) == {"f1", "f2", "rf", "Residual"}
def test_risk_decomposition_shares_sum_to_one(target_returns_data, factor_matrix_data):
"""Per-period shares sum to 1.0 within floating-point tolerance."""
out = rd_module.risk_decomposition(
_params(target_returns_data, factor_matrix_data, risk_free_column="rf")
)
df = pd.DataFrame([r.model_dump() for r in out.results])
sums = df.groupby("period")["share"].sum()
for period_sum in sums:
assert period_sum == pytest.approx(1.0, abs=1e-9)
def test_risk_decomposition_with_risk_free(target_returns_data, factor_matrix_data):
"""The risk-free column is dropped from the factor list when provided."""
out = rd_module.risk_decomposition(
_params(target_returns_data, factor_matrix_data, risk_free_column="rf")
)
factors = {r.factor for r in out.results}
assert "rf" not in factors
assert {"f1", "f2", "Residual"}.issubset(factors)
def test_risk_decomposition_constant_target_skipped(
factor_matrix_df, factor_matrix_data
):
"""Periods where Var(target) is zero are skipped, not divided by zero."""
constant_target = df_to_basemodel(
pd.DataFrame(
{"date": factor_matrix_df["date"], "close": [1.0] * len(factor_matrix_df)}
)
)
out = rd_module.risk_decomposition(
_params(constant_target, factor_matrix_data, periods=["Max"])
)
assert out.results == []
def test_risk_decomposition_skips_too_short_window(
factor_matrix_df, factor_matrix_data
):
"""Windows with too few observations for OLS are skipped cleanly."""
short_dates = factor_matrix_df["date"].iloc[:2]
short_target = df_to_basemodel(
pd.DataFrame({"date": short_dates, "close": [0.01, -0.005]})
)
out = rd_module.risk_decomposition(
_params(short_target, factor_matrix_data, periods=["Max"])
)
assert out.results == []
def test_risk_decomposition_default_periods():
"""The default ``periods`` value enumerates the six named look-back windows."""
params = rd_module.RiskDecompositionQueryParams(
data=[], factors_data=[], target="close"
)
assert params.periods == [
"1 Month",
"3 Month",
"YTD",
"1 Year",
"3 Year",
"Max",
]