"""Tests for E-CLP elliptical concentrated liquidity pool.""" import numpy as np import pytest from src.primitives.elliptical_clp import ( ECLPParams, compute_derived_params, compute_invariant, calc_out_given_in, calc_in_given_out, spot_price, virtual_offsets, ) def make_default_params(): """Standard E-CLP params for testing (near-stable pair).""" return ECLPParams( alpha=0.97, beta=1.03, c=1.0, # No rotation s=0.0, lam=10.0, # Moderate concentration ) def make_rotated_params(): """E-CLP with rotation (non-unit price target).""" phi = np.pi / 6 # 30 degrees return ECLPParams( alpha=0.8, beta=1.2, c=np.cos(phi), s=np.sin(phi), lam=5.0, ) class TestInvariant: def test_positive_invariant(self): p = make_default_params() dp = compute_derived_params(p) r = compute_invariant(1000.0, 1000.0, p, dp) assert r > 0 def test_invariant_scales(self): """Invariant should be approximately homogeneous degree 1.""" p = make_default_params() dp = compute_derived_params(p) r1 = compute_invariant(1000.0, 1000.0, p, dp) r2 = compute_invariant(2000.0, 2000.0, p, dp) # Should be approximately 2x assert abs(r2 / r1 - 2.0) < 0.1 # Allow some tolerance def test_rotated_params(self): """Rotated E-CLP should work.""" p = make_rotated_params() dp = compute_derived_params(p) r = compute_invariant(1000.0, 1000.0, p, dp) assert r > 0 class TestSwaps: def test_invariant_preserved(self): """Swap preserves invariant.""" p = make_default_params() dp = compute_derived_params(p) x, y = 1000.0, 1000.0 r_before = compute_invariant(x, y, p, dp) dy = calc_out_given_in(x, y, p, dp, 50.0, token_in=0) r_after = compute_invariant(x + 50.0, y - dy, p, dp) assert abs(r_after - r_before) / r_before < 1e-6 def test_round_trip(self): """calc_in_given_out inverts calc_out_given_in.""" p = make_default_params() dp = compute_derived_params(p) amount_in = 30.0 amount_out = calc_out_given_in(1000.0, 1000.0, p, dp, amount_in, token_in=0) recovered = calc_in_given_out(1000.0, 1000.0, p, dp, amount_out, token_out=1) assert abs(recovered - amount_in) < 1e-4 def test_lambda_changes_curve_shape(self): """Higher λ should produce different swap outputs (curve shape change).""" p_lo = ECLPParams(0.5, 2.0, 1.0, 0.0, 1.0) p_hi = ECLPParams(0.5, 2.0, 1.0, 0.0, 20.0) dp_lo = compute_derived_params(p_lo) dp_hi = compute_derived_params(p_hi) out_lo = calc_out_given_in(1000.0, 1000.0, p_lo, dp_lo, 50.0) out_hi = calc_out_given_in(1000.0, 1000.0, p_hi, dp_hi, 50.0) # λ should meaningfully change the output (different curve geometry) assert out_lo != out_hi assert abs(out_lo - out_hi) / out_lo > 0.001 # At least 0.1% difference def test_rotated_swap(self): """Swap works with rotation.""" p = make_rotated_params() dp = compute_derived_params(p) dy = calc_out_given_in(1000.0, 1000.0, p, dp, 50.0) assert dy > 0 def test_spot_price_positive(self): """Spot price should be positive.""" p = make_default_params() dp = compute_derived_params(p) sp = spot_price(1000.0, 1000.0, p, dp) assert sp > 0 class TestDerivedParams: def test_tau_normalized(self): """tau vectors should be approximately unit length.""" p = make_default_params() dp = compute_derived_params(p) assert abs(np.linalg.norm(dp.tau_alpha) - 1.0) < 1e-10 assert abs(np.linalg.norm(dp.tau_beta) - 1.0) < 1e-10 def test_dSq_near_one(self): """dSq = c² + s² should be ≈ 1.""" p = make_default_params() dp = compute_derived_params(p) assert abs(dp.dSq - 1.0) < 1e-10 def test_virtual_offsets_positive(self): """Virtual offsets should be positive for typical params.""" p = make_default_params() dp = compute_derived_params(p) r = compute_invariant(1000.0, 1000.0, p, dp) a, b = virtual_offsets(r, p, dp) assert a > 0 assert b > 0