Files
Cerbero-Bite/tests/unit/test_config_schema.py
Adriano fbb7753cc6 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>
2026-04-27 10:14:06 +02:00

105 lines
3.2 KiB
Python

"""Unit tests for :mod:`cerbero_bite.config.schema`."""
from __future__ import annotations
from decimal import Decimal
import pytest
from cerbero_bite.config import (
EntryConfig,
ExitConfig,
ShortStrikeSpec,
SizingConfig,
SpreadWidthSpec,
StrategyConfig,
StructureConfig,
golden_config,
)
def test_golden_config_builds_with_defaults() -> None:
cfg = golden_config()
assert cfg.config_version == "1.0.0-test"
assert cfg.sizing.kelly_fraction == Decimal("0.13")
assert cfg.exit.profit_take_pct_of_credit == Decimal("0.50")
assert cfg.entry.dvol_min == Decimal("35")
assert cfg.entry.dvol_max == Decimal("90")
def test_dvol_adjustment_default_bands_ordered() -> None:
cfg = golden_config()
bands = cfg.sizing.dvol_adjustment
assert [b.dvol_under for b in bands] == [Decimal("45"), Decimal("60"), Decimal("80")]
assert [b.multiplier for b in bands] == [
Decimal("1.00"),
Decimal("0.85"),
Decimal("0.65"),
]
def test_validator_rejects_kelly_out_of_safe_range() -> None:
with pytest.raises(ValueError, match="kelly_fraction"):
golden_config(sizing=SizingConfig(kelly_fraction=Decimal("0.6")))
def test_validator_rejects_delta_target_outside_band() -> None:
bad = ShortStrikeSpec(
delta_target=Decimal("0.20"),
delta_min=Decimal("0.10"),
delta_max=Decimal("0.15"),
)
with pytest.raises(ValueError, match="delta_target"):
golden_config(structure=StructureConfig(short_strike=bad))
def test_validator_rejects_inverted_otm_range() -> None:
bad = ShortStrikeSpec(
distance_otm_pct_min=Decimal("0.30"),
distance_otm_pct_max=Decimal("0.20"),
)
with pytest.raises(ValueError, match="OTM range"):
golden_config(structure=StructureConfig(short_strike=bad))
def test_validator_rejects_inverted_spread_width() -> None:
bad = SpreadWidthSpec(
target_pct_of_spot=Decimal("0.06"),
min_pct_of_spot=Decimal("0.03"),
max_pct_of_spot=Decimal("0.05"),
)
with pytest.raises(ValueError, match="spread_width"):
golden_config(structure=StructureConfig(spread_width=bad))
def test_validator_rejects_profit_take_ge_100pct() -> None:
with pytest.raises(ValueError, match="profit_take"):
golden_config(exit=ExitConfig(profit_take_pct_of_credit=Decimal("1.0")))
def test_validator_rejects_stop_loss_le_1x() -> None:
with pytest.raises(ValueError, match="stop_loss"):
golden_config(exit=ExitConfig(stop_loss_mark_x_credit=Decimal("1.0")))
def test_validator_rejects_inverted_dvol_range() -> None:
with pytest.raises(ValueError, match="dvol_min"):
golden_config(entry=EntryConfig(dvol_min=Decimal("90"), dvol_max=Decimal("35")))
def test_strategy_config_is_immutable() -> None:
cfg = golden_config()
with pytest.raises(Exception): # pydantic raises ValidationError on frozen
cfg.entry = EntryConfig() # type: ignore[misc]
def test_extra_fields_rejected_on_root() -> None:
with pytest.raises(ValueError):
StrategyConfig(
config_version="x",
config_hash="0" * 64,
last_review="2026-04-26",
last_reviewer="test",
unknown_field=42, # type: ignore[call-arg]
)