"""Full-sample and LEFT-TAIL correlation. Left-tail = correlation on the book's worst days (crisis-relevant).""" from __future__ import annotations import numpy as np from fxhnt.domain.diversification.correlation import full_correlation, left_tail_correlation def test_full_correlation_aligned() -> None: a = np.array([1.0, -1.0, 2.0, -2.0, 0.5]) assert abs(full_correlation(a, a) - 1.0) < 1e-9 assert abs(full_correlation(a, -a) + 1.0) < 1e-9 def test_left_tail_picks_book_worst_days() -> None: # book: last two days are the worst; sleeve co-moves ONLY on those days -> high left-tail corr, # near-zero full-sample corr. rng = np.random.default_rng(0) n = 200 book = rng.normal(0, 0.01, n) book[-2:] = [-0.05, -0.06] # crisis days sleeve = rng.normal(0, 0.01, n) sleeve[-2:] = [-0.05, -0.07] # sleeve crashes WITH the book lt = left_tail_correlation(sleeve, book, tail_frac=0.02) # worst ~4 days fs = full_correlation(sleeve, book) assert lt > 0.5 # co-moves in the tail assert lt > fs # tail corr exceeds full-sample (the crisis-hiding case) def test_near_constant_returns_nan() -> None: """A series with std ~1e-15 (float noise around a constant) must return NaN, not a garbage value.""" rng = np.random.default_rng(7) # near-constant: tiny float noise well below the 1e-12 epsilon near_const = np.ones(200) + rng.normal(0, 1e-15, 200) normal = rng.normal(0, 0.01, 200) result = full_correlation(near_const, normal) assert np.isnan(result), f"expected NaN for near-constant input, got {result}" def test_empty_input_is_nan_not_raise() -> None: """Empty input must return NaN without raising — _aligned's a[-0:] footgun is guarded.""" result = full_correlation(np.array([]), np.array([1.0, 2.0])) assert np.isnan(result), f"expected NaN for empty input, got {result}" def test_left_tail_unestimable_returns_nan() -> None: book = np.array([0.01, 0.02]) # too few days for a tail sleeve = np.array([0.0, 0.0]) assert np.isnan(left_tail_correlation(sleeve, book, tail_frac=0.1))