Files
easy_tdx_max/tests/unit/test_portfolio_risk.py
T
GitHubandClaude e6a69d51e4 feat(portfolio): add optimizer, risk model, and rebalance engine
- WeightOptimizer base class with registry (equal, factor_weighted, risk_parity, mean_variance)
- RiskModel with shrinkage covariance estimation and portfolio risk metrics
- RebalanceEngine for multi-period backtesting with commission/slippage
- 20 unit tests covering all components

Co-Authored-By: Claude <noreply@anthropic.com>
2026-06-12 20:22:31 +08:00

45 lines
1.5 KiB
Python

"""Test RiskModel."""
from __future__ import annotations
import numpy as np
import pandas as pd
from easy_tdx.portfolio.risk import RiskModel
def _make_returns(n_dates: int = 100, n_stocks: int = 5, seed: int = 42) -> pd.DataFrame:
rng = np.random.default_rng(seed)
codes = [f"{i:06d}" for i in range(n_stocks)]
return pd.DataFrame(rng.normal(0.001, 0.02, (n_dates, n_stocks)), columns=codes)
class TestCovarianceEstimation:
def test_shape(self):
cov = RiskModel().estimate_covariance(_make_returns())
assert cov.shape == (5, 5)
def test_symmetric(self):
cov = RiskModel().estimate_covariance(_make_returns())
assert np.allclose(cov.to_numpy(), cov.to_numpy().T)
def test_shrinkage_reduces_offdiag(self):
rm = RiskModel()
ret = _make_returns()
shrunk = rm.estimate_covariance(ret, method="shrinkage")
sample = rm.estimate_covariance(ret, method="sample")
off_shrunk = shrunk.values[~np.eye(5, dtype=bool)]
off_sample = sample.values[~np.eye(5, dtype=bool)]
assert np.abs(off_shrunk).mean() <= np.abs(off_sample).mean()
class TestPortfolioRisk:
def test_total_volatility(self):
cov = RiskModel().estimate_covariance(_make_returns())
risk = RiskModel().portfolio_risk({"000000": 0.5, "000001": 0.5}, cov)
assert risk["total_volatility"] > 0
assert risk["n_positions"] == 2
def test_empty_weights(self):
risk = RiskModel().portfolio_risk({}, pd.DataFrame())
assert risk["total_volatility"] == 0.0