from fxhnt.application.funding_panel import basis_change_by_symbol, delta_neutral_return_panel


def test_dn_is_basis_plus_funding():
    spot = {"A": {1: 100.0, 2: 110.0}}; perp = {"A": {1: 100.0, 2: 121.0}}; funding = {"A": {2: 0.001}}
    dn = delta_neutral_return_panel(spot, perp, funding)
    # day2: spot_ret 0.10, perp_ret 0.21 -> basis -0.11 + funding 0.001 = -0.109
    assert abs(dn["A"][2] - (-0.109)) < 1e-9
    assert 1 not in dn["A"]                          # no prior close for day1 -> no dn


def test_dn_funding_only_fallback_when_no_spot():
    perp = {"A": {1: 100.0, 2: 110.0}}; funding = {"A": {2: 0.02}}
    dn = delta_neutral_return_panel({}, perp, funding)
    assert abs(dn["A"][2] - 0.02) < 1e-9             # no spot -> funding-only


def test_dn_carry_moves_with_basis_not_just_funding():
    # constant funding, but basis varies -> dn varies (proves the variance fix)
    spot = {"A": {1: 100.0, 2: 102.0, 3: 100.0}}; perp = {"A": {1: 100.0, 2: 100.0, 3: 100.0}}
    funding = {"A": {2: 0.001, 3: 0.001}}
    dn = delta_neutral_return_panel(spot, perp, funding)
    assert dn["A"][2] != dn["A"][3]                  # basis differs day2 vs day3 despite equal funding


def test_basis_change_is_spot_ret_minus_perp_ret():
    spot = {"A": {1: 100.0, 2: 110.0}}; perp = {"A": {1: 100.0, 2: 121.0}}
    b = basis_change_by_symbol(spot, perp, 2)
    # spot_ret 0.10, perp_ret 0.21 -> basis change -0.11
    assert abs(b["A"] - (-0.11)) < 1e-12


def test_basis_change_omits_symbol_when_no_spot_leg():
    # no spot pair -> the symbol is omitted (0 basis contribution; funding-only fallback documented)
    perp = {"A": {1: 100.0, 2: 110.0}}
    assert basis_change_by_symbol({}, perp, 2) == {}


def test_basis_change_omits_symbol_when_no_value_on_day():
    spot = {"A": {1: 100.0}}; perp = {"A": {1: 100.0}}
    assert basis_change_by_symbol(spot, perp, 2) == {}   # nothing on day 2


def test_basis_change_uses_nearest_prior_within_gap():
    # day 1 has data, days 2/3 are gaps, day 4 has data; with max_gap>=3 the nearest prior (day 1) is used.
    spot = {"A": {1: 100.0, 4: 110.0}}; perp = {"A": {1: 100.0, 4: 105.0}}
    b = basis_change_by_symbol(spot, perp, 4, max_gap=3)
    # spot_ret 0.10, perp_ret 0.05 -> basis change 0.05
    assert abs(b["A"] - 0.05) < 1e-12
    # with a tighter window the prior is out of reach -> omitted
    assert basis_change_by_symbol(spot, perp, 4, max_gap=2) == {}


def test_basis_change_loss_is_negative_when_spot_lags_perp():
    # spot falls less than perp rises against the book -> basis change negative (the tail term)
    spot = {"A": {1: 100.0, 2: 98.0}}; perp = {"A": {1: 100.0, 2: 110.0}}
    b = basis_change_by_symbol(spot, perp, 2)
    assert b["A"] < 0.0
    assert abs(b["A"] - (-0.02 - 0.10)) < 1e-12       # spot_ret -0.02, perp_ret +0.10
