"""TDD for :mod:`cerbero_bite.core.greeks_aggregator`. Spec: ``docs/03-algorithms.md §5``. """ from __future__ import annotations from datetime import UTC, datetime from decimal import Decimal from cerbero_bite.core.greeks_aggregator import aggregate from cerbero_bite.core.types import OptionLeg def _leg( *, side: str, size: int = 1, delta: str, gamma: str = "0.001", theta: str = "-0.0005", vega: str = "0.10", strike: str = "2400", option_type: str = "P", instrument: str = "ETH-X-2400-P", ) -> OptionLeg: return OptionLeg( instrument=instrument, side=side, # type: ignore[arg-type] strike=Decimal(strike), expiry=datetime(2026, 5, 15, 8, 0, tzinfo=UTC), type=option_type, # type: ignore[arg-type] size=size, mid_price_eth=Decimal("0.012"), delta=Decimal(delta), gamma=Decimal(gamma), theta=Decimal(theta), vega=Decimal(vega), ) def test_bull_put_spread_net_delta_long_positive() -> None: """SELL put (negative delta) + BUY further-OTM put → net delta positive.""" legs = [ _leg(side="SELL", delta="-0.12"), _leg(side="BUY", delta="-0.08", strike="2300"), ] res = aggregate(legs=legs, eth_price_usd=Decimal("3000")) # delta_net = -1*-0.12 + +1*-0.08 = 0.12 - 0.08 = +0.04 assert res.delta_net == Decimal("0.04") def test_bear_call_spread_net_delta_short_negative() -> None: legs = [ _leg(side="SELL", delta="0.12", option_type="C"), _leg(side="BUY", delta="0.08", option_type="C", strike="3650"), ] res = aggregate(legs=legs, eth_price_usd=Decimal("3000")) # delta_net = -1*0.12 + +1*0.08 = -0.04 assert res.delta_net == Decimal("-0.04") def test_size_scales_each_leg() -> None: legs = [ _leg(side="SELL", size=3, delta="-0.12"), _leg(side="BUY", size=3, delta="-0.08", strike="2300"), ] res = aggregate(legs=legs, eth_price_usd=Decimal("3000")) assert res.delta_net == Decimal("0.12") # 3 × 0.04 def test_theta_converted_to_usd_per_day() -> None: legs = [ _leg(side="SELL", delta="-0.12", theta="-0.0005"), # selling → +0.0005 ETH theta _leg(side="BUY", delta="-0.08", theta="-0.0003", strike="2300"), # +0.0003 cost ] res = aggregate(legs=legs, eth_price_usd=Decimal("3000")) # theta_eth = -1*(-0.0005) + +1*(-0.0003) = 0.0005 - 0.0003 = 0.0002 # theta_usd = 0.0002 × 3000 = 0.60 assert res.theta_net == Decimal("0.60") def test_gamma_and_vega_summed_with_sign() -> None: legs = [ _leg(side="SELL", delta="-0.12", gamma="0.0020", vega="0.30"), _leg(side="BUY", delta="-0.08", gamma="0.0015", vega="0.20", strike="2300"), ] res = aggregate(legs=legs, eth_price_usd=Decimal("3000")) # gamma: -1*0.0020 + 1*0.0015 = -0.0005 # vega: -1*0.30 + 1*0.20 = -0.10 assert res.gamma_net == Decimal("-0.0005") assert res.vega_net == Decimal("-0.10") def test_empty_legs_returns_zero_greeks() -> None: res = aggregate(legs=[], eth_price_usd=Decimal("3000")) assert res.delta_net == Decimal("0") assert res.gamma_net == Decimal("0") assert res.theta_net == Decimal("0") assert res.vega_net == Decimal("0")