57 lines
2.2 KiB
Python
57 lines
2.2 KiB
Python
"""Pure equity-factor math: winsorized z-score, family/composite scores, 3 construction weightings, book return."""
|
|
from __future__ import annotations
|
|
|
|
import pytest
|
|
|
|
from fxhnt.domain.strategies import equity_factor as ef
|
|
|
|
|
|
def test_robust_z_winsorizes_and_standardizes() -> None:
|
|
z = ef.robust_z([1.0, 2.0, 3.0, 4.0, 100.0], k=2.0) # 100 is an outlier
|
|
assert max(z) == pytest.approx(2.0) # winsorized at +k
|
|
assert all(-2.0 - 1e-9 <= v <= 2.0 + 1e-9 for v in z)
|
|
|
|
|
|
def test_robust_z_missing_is_neutral_zero() -> None:
|
|
z = ef.robust_z([1.0, None, 3.0])
|
|
assert z[1] == 0.0 # missing -> neutral
|
|
|
|
|
|
def test_composite_is_equal_weight_of_families() -> None:
|
|
c = ef.composite(value_z=[1.0, -1.0], quality_z=[1.0, -1.0], momentum_z=[1.0, -1.0])
|
|
assert c == pytest.approx([1.0, -1.0])
|
|
|
|
|
|
def test_long_only_weights_top_quintile_sum_to_one() -> None:
|
|
scores = [float(i) for i in range(10)] # 0..9; top quintile = top 2 (8,9)
|
|
w = ef.construction_weights(scores, "long", quantile=0.2)
|
|
assert sum(w) == pytest.approx(1.0)
|
|
assert w[9] == pytest.approx(0.5) and w[8] == pytest.approx(0.5)
|
|
assert all(w[i] == 0.0 for i in range(8))
|
|
|
|
|
|
def test_long_short_is_market_neutral_gross_two() -> None:
|
|
scores = [float(i) for i in range(10)]
|
|
w = ef.construction_weights(scores, "ls", quantile=0.2)
|
|
assert sum(w) == pytest.approx(0.0)
|
|
assert sum(abs(x) for x in w) == pytest.approx(2.0)
|
|
assert w[9] == pytest.approx(0.5) and w[0] == pytest.approx(-0.5)
|
|
|
|
|
|
def test_tilt_is_long_only_rank_weighted_sum_one() -> None:
|
|
scores = [float(i) for i in range(10)]
|
|
w = ef.construction_weights(scores, "tilt")
|
|
assert sum(w) == pytest.approx(1.0)
|
|
assert all(x >= 0.0 for x in w)
|
|
assert w[9] > w[5] >= 0.0
|
|
|
|
|
|
def test_book_return_weighted_minus_short_borrow() -> None:
|
|
prev_w = {"A": 0.5, "B": -0.5}
|
|
prev_p = {"A": 100.0, "B": 100.0}
|
|
cur_p = {"A": 110.0, "B": 90.0}
|
|
r = ef.book_return(prev_w, prev_p, cur_p, borrow_daily=0.0)
|
|
assert r == pytest.approx(0.5 * 0.10 + (-0.5) * (-0.10)) # +0.10
|
|
r2 = ef.book_return(prev_w, prev_p, cur_p, borrow_daily=0.001)
|
|
assert r2 == pytest.approx(r - 0.001 * 0.5)
|