Phase 4 hardening: dealer-gamma + liquidation-heatmap entry filters
Integra due nuovi filtri dal pacchetto quant indicators rilasciato in Cerbero_mcp (commit a13e3fe). 335 test pass, mypy strict pulito, ruff clean. Filtri (§2.8 — nuovo): - dealer-gamma: blocca entry quando total_net_dealer_gamma < dealer_gamma_min (default 0). Long-gamma regime favorisce credit spread (vol-suppressing dealer flow); short-gamma flow lo amplifica ed è da evitare. - liquidation-heatmap: blocca entry quando il segnale euristico di cerbero-sentiment riporta long o short squeeze risk = "high" (cluster di liquidations imminenti entro 24h). Entrambi sono best-effort: se il tool MCP fallisce o restituisce dati anomali l'entry_cycle popola EntryContext con None e validate_entry salta il gate per non bloccare entry su problemi infrastrutturali. Wrapper: - DeribitClient.dealer_gamma_profile_eth → DealerGammaSnapshot. - SentimentClient.liquidation_heatmap → LiquidationHeatmap con property has_high_squeeze_risk. Schema: - EntryConfig.dealer_gamma_min, dealer_gamma_filter_enabled, liquidation_filter_enabled. - EntryContext.dealer_net_gamma, liquidation_squeeze_risk_high opzionali. - strategy.yaml: nuovi campi documentati con commento + hash ricalcolato (4c2be4c5...). Documentazione: - docs/04-mcp-integration.md riscritto al modello attuale (HTTP REST, no mcp SDK, no memory/brain-bridge, place_combo_order documentato, environment_info al boot). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -333,6 +333,35 @@ async def test_get_positions_returns_list(httpx_mock: HTTPXMock) -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dealer_gamma_profile_eth_parses_payload(httpx_mock: HTTPXMock) -> None:
|
||||
httpx_mock.add_response(
|
||||
url="http://mcp-deribit:9011/tools/get_dealer_gamma_profile",
|
||||
json={
|
||||
"currency": "ETH",
|
||||
"spot_price": 3000.0,
|
||||
"by_strike": [],
|
||||
"total_net_dealer_gamma": 12345.6,
|
||||
"gamma_flip_level": 2950.5,
|
||||
"strikes_analyzed": 18,
|
||||
},
|
||||
)
|
||||
snap = await _client().dealer_gamma_profile_eth()
|
||||
assert snap.spot_price == Decimal("3000.0")
|
||||
assert snap.total_net_dealer_gamma == Decimal("12345.6")
|
||||
assert snap.gamma_flip_level == Decimal("2950.5")
|
||||
assert snap.strikes_analyzed == 18
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dealer_gamma_profile_anomaly_when_total_missing(
|
||||
httpx_mock: HTTPXMock,
|
||||
) -> None:
|
||||
httpx_mock.add_response(json={"spot_price": 3000.0})
|
||||
with pytest.raises(McpDataAnomalyError, match="missing spot_price or total"):
|
||||
await _client().dealer_gamma_profile_eth()
|
||||
|
||||
|
||||
def test_deribit_client_rejects_wrong_service() -> None:
|
||||
bad = HttpToolClient(
|
||||
service="macro", base_url="http://x:1", token="t", retry_max=1
|
||||
|
||||
@@ -99,6 +99,43 @@ def test_periods_table_covers_documented_venues() -> None:
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_liquidation_heatmap_parses_high_risk(httpx_mock: HTTPXMock) -> None:
|
||||
httpx_mock.add_response(
|
||||
url="http://mcp-sentiment:9014/tools/get_liquidation_heatmap",
|
||||
json={
|
||||
"asset": "ETH",
|
||||
"avg_funding_rate": 0.00012,
|
||||
"oi_delta_pct_4h": 6.5,
|
||||
"oi_delta_pct_24h": 8.2,
|
||||
"long_squeeze_risk": "high",
|
||||
"short_squeeze_risk": "low",
|
||||
},
|
||||
)
|
||||
out = await _client().liquidation_heatmap("eth")
|
||||
assert out.asset == "ETH"
|
||||
assert out.avg_funding_rate == Decimal("0.00012")
|
||||
assert out.long_squeeze_risk == "high"
|
||||
assert out.has_high_squeeze_risk is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_liquidation_heatmap_unknown_risk_levels_default_to_low(
|
||||
httpx_mock: HTTPXMock,
|
||||
) -> None:
|
||||
httpx_mock.add_response(
|
||||
json={
|
||||
"asset": "ETH",
|
||||
"long_squeeze_risk": "extreme",
|
||||
"short_squeeze_risk": None,
|
||||
}
|
||||
)
|
||||
out = await _client().liquidation_heatmap("ETH")
|
||||
assert out.long_squeeze_risk == "low"
|
||||
assert out.short_squeeze_risk == "low"
|
||||
assert out.has_high_squeeze_risk is False
|
||||
|
||||
|
||||
def test_sentiment_client_rejects_wrong_service() -> None:
|
||||
bad = HttpToolClient(
|
||||
service="macro",
|
||||
|
||||
@@ -9,7 +9,7 @@ from decimal import Decimal
|
||||
|
||||
import pytest
|
||||
|
||||
from cerbero_bite.config import StrategyConfig, golden_config
|
||||
from cerbero_bite.config import EntryConfig, StrategyConfig, golden_config
|
||||
from cerbero_bite.core.entry_validator import (
|
||||
EntryContext,
|
||||
TrendContext,
|
||||
@@ -144,6 +144,56 @@ def test_eth_holdings_at_cap_is_accepted(cfg: StrategyConfig) -> None:
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
def test_dealer_short_gamma_blocks_entry(cfg: StrategyConfig) -> None:
|
||||
decision = validate_entry(_good_ctx(dealer_net_gamma=Decimal("-5")), cfg)
|
||||
assert decision.accepted is False
|
||||
assert any("dealer short-gamma" in r for r in decision.reasons)
|
||||
|
||||
|
||||
def test_dealer_long_gamma_passes(cfg: StrategyConfig) -> None:
|
||||
decision = validate_entry(_good_ctx(dealer_net_gamma=Decimal("100")), cfg)
|
||||
assert decision.accepted is True
|
||||
|
||||
|
||||
def test_dealer_gamma_none_skips_filter(cfg: StrategyConfig) -> None:
|
||||
decision = validate_entry(_good_ctx(dealer_net_gamma=None), cfg)
|
||||
assert decision.accepted is True
|
||||
|
||||
|
||||
def test_liquidation_squeeze_high_blocks_entry(cfg: StrategyConfig) -> None:
|
||||
decision = validate_entry(
|
||||
_good_ctx(liquidation_squeeze_risk_high=True), cfg
|
||||
)
|
||||
assert decision.accepted is False
|
||||
assert any("liquidation squeeze" in r for r in decision.reasons)
|
||||
|
||||
|
||||
def test_liquidation_squeeze_filter_disabled_in_config(
|
||||
cfg: StrategyConfig,
|
||||
) -> None:
|
||||
permissive = golden_config(
|
||||
entry=EntryConfig(
|
||||
**{**cfg.entry.model_dump(), "liquidation_filter_enabled": False}
|
||||
)
|
||||
)
|
||||
decision = validate_entry(
|
||||
_good_ctx(liquidation_squeeze_risk_high=True), permissive
|
||||
)
|
||||
assert decision.accepted is True
|
||||
|
||||
|
||||
def test_dealer_gamma_filter_disabled_in_config(cfg: StrategyConfig) -> None:
|
||||
permissive = golden_config(
|
||||
entry=EntryConfig(
|
||||
**{**cfg.entry.model_dump(), "dealer_gamma_filter_enabled": False}
|
||||
)
|
||||
)
|
||||
decision = validate_entry(
|
||||
_good_ctx(dealer_net_gamma=Decimal("-1000")), permissive
|
||||
)
|
||||
assert decision.accepted is True
|
||||
|
||||
|
||||
def test_validate_entry_accumulates_all_reasons(cfg: StrategyConfig) -> None:
|
||||
decision = validate_entry(
|
||||
_good_ctx(
|
||||
|
||||
Reference in New Issue
Block a user