mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 20:24:19 +08:00
- 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>
72 lines
2.2 KiB
Python
72 lines
2.2 KiB
Python
"""Test portfolio optimizers."""
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
import pytest
|
|
|
|
from easy_tdx.portfolio.optimizer import (
|
|
EqualWeightOptimizer,
|
|
FactorWeightedOptimizer,
|
|
RiskParityOptimizer,
|
|
get_optimizer,
|
|
)
|
|
|
|
|
|
def _make_scores(n: int = 20, seed: int = 42) -> pd.DataFrame:
|
|
rng = np.random.default_rng(seed)
|
|
return pd.DataFrame({
|
|
"code": [f"{i:06d}" for i in range(n)],
|
|
"score": rng.normal(0.02, 0.05, n),
|
|
})
|
|
|
|
|
|
class TestEqualWeight:
|
|
def test_weights_sum_to_one(self):
|
|
w = EqualWeightOptimizer().optimize(_make_scores(), n_stocks=10)
|
|
assert abs(sum(w.values()) - 1.0) < 1e-8
|
|
|
|
def test_n_stocks_selected(self):
|
|
w = EqualWeightOptimizer().optimize(_make_scores(), n_stocks=5)
|
|
assert len(w) == 5
|
|
|
|
def test_all_equal(self):
|
|
w = EqualWeightOptimizer().optimize(_make_scores(), n_stocks=10)
|
|
vals = list(w.values())
|
|
assert all(abs(v - vals[0]) < 1e-8 for v in vals)
|
|
|
|
def test_empty_input(self):
|
|
w = EqualWeightOptimizer().optimize(pd.DataFrame(columns=["code", "score"]), n_stocks=5)
|
|
assert len(w) == 0
|
|
|
|
|
|
class TestFactorWeighted:
|
|
def test_weights_sum_to_one(self):
|
|
w = FactorWeightedOptimizer().optimize(_make_scores(), n_stocks=10)
|
|
assert abs(sum(w.values()) - 1.0) < 1e-6
|
|
|
|
def test_higher_score_higher_weight(self):
|
|
scores = pd.DataFrame({"code": ["A", "B", "C"], "score": [3.0, 2.0, 1.0]})
|
|
w = FactorWeightedOptimizer().optimize(scores, n_stocks=3)
|
|
assert w["A"] > w["C"]
|
|
|
|
|
|
class TestRiskParity:
|
|
def test_weights_sum_to_one(self):
|
|
w = RiskParityOptimizer().optimize(_make_scores(), n_stocks=10)
|
|
assert abs(sum(w.values()) - 1.0) < 1e-6
|
|
|
|
def test_with_volatility_column(self):
|
|
scores = pd.DataFrame({"code": ["A", "B", "C"], "score": [1.0, 1.0, 1.0], "volatility": [0.1, 0.2, 0.4]})
|
|
w = RiskParityOptimizer().optimize(scores, n_stocks=3)
|
|
assert w["A"] > w["C"]
|
|
|
|
|
|
class TestRegistry:
|
|
def test_get_optimizer(self):
|
|
assert isinstance(get_optimizer("equal"), EqualWeightOptimizer)
|
|
|
|
def test_unknown_raises(self):
|
|
with pytest.raises(ValueError, match="未知优化器"):
|
|
get_optimizer("nonexistent")
|