myco-bonding-curve/tests/test_elliptical_clp.py

137 lines
4.3 KiB
Python

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