"""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)
