Phase 1: core algorithms

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>
This commit is contained in:
2026-04-27 10:14:06 +02:00
parent 881bc8a1bf
commit fbb7753cc6
20 changed files with 3090 additions and 1 deletions
+100
View File
@@ -0,0 +1,100 @@
"""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")