fbb7753cc6
Implementa i sette algoritmi puri di docs/03-algorithms.md con disciplina TDD: 112 test, copertura statement+branch al 100% su core/ e config/, mypy --strict pulito, ruff pulito. Moduli: - config/schema.py: StrategyConfig Pydantic v2 con validatori di consistenza (kelly, delta, OTM, spread width, profit/stop). - core/types.py: OptionQuote e OptionLeg condivisi. - core/entry_validator.py: validate_entry (accumula motivi) e compute_bias (bull_put/bear_call/iron_condor/None). - core/liquidity_gate.py: check OI/volume/spread/depth + slippage stimato in % del credito. - core/sizing_engine.py: Quarter Kelly con cap 200/1000 EUR e bande DVOL. - core/combo_builder.py: select_strikes (DTE/OTM/delta/width/credit) e build (ComboProposal con credit/max_loss/breakeven). - core/greeks_aggregator.py: somma firmata BUY/SELL, theta in USD. - core/exit_decision.py: 6 trigger ordinati con eccezione skip-time vicino a profit (mark in (50%,70%] credito). - core/kelly_recalibration.py: full/quarter Kelly, confidence per sample size, blend medio in fascia 30-99 trade. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
101 lines
3.2 KiB
Python
101 lines
3.2 KiB
Python
"""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")
|