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:
@@ -82,6 +82,9 @@ def _wire_market_snapshot(
|
||||
macro_events: list[dict[str, Any]] | None = None,
|
||||
eth_pct: float = 0.10,
|
||||
portfolio_eur: float | Decimal = 5000.0,
|
||||
dealer_total_net_gamma: float = 12345.6,
|
||||
liquidation_long_risk: str = "low",
|
||||
liquidation_short_risk: str = "low",
|
||||
) -> None:
|
||||
"""Stub every MCP endpoint queried during the snapshot stage."""
|
||||
httpx_mock.add_response(
|
||||
@@ -104,6 +107,29 @@ def _wire_market_snapshot(
|
||||
json={"adx": [{"value": 22.0}]},
|
||||
is_reusable=True,
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url="http://mcp-deribit:9011/tools/get_dealer_gamma_profile",
|
||||
json={
|
||||
"spot_price": spot,
|
||||
"total_net_dealer_gamma": dealer_total_net_gamma,
|
||||
"gamma_flip_level": spot * 0.99,
|
||||
"strikes_analyzed": 18,
|
||||
"by_strike": [],
|
||||
},
|
||||
is_reusable=True,
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url="http://mcp-sentiment:9014/tools/get_liquidation_heatmap",
|
||||
json={
|
||||
"asset": "ETH",
|
||||
"avg_funding_rate": funding_cross_period,
|
||||
"oi_delta_pct_4h": 1.0,
|
||||
"oi_delta_pct_24h": 1.0,
|
||||
"long_squeeze_risk": liquidation_long_risk,
|
||||
"short_squeeze_risk": liquidation_short_risk,
|
||||
},
|
||||
is_reusable=True,
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url="http://mcp-hyperliquid:9012/tools/get_funding_rate",
|
||||
json={"asset": "ETH", "current_funding_rate": funding_perp_hourly},
|
||||
@@ -504,6 +530,58 @@ async def test_broker_reject_marks_position_cancelled(
|
||||
assert ctx.kill_switch.is_armed() is True
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_dealer_short_gamma_blocks_entry(
|
||||
cfg: StrategyConfig,
|
||||
runtime_paths: tuple[Path, Path],
|
||||
now: datetime,
|
||||
httpx_mock: HTTPXMock,
|
||||
) -> None:
|
||||
_wire_market_snapshot(
|
||||
httpx_mock,
|
||||
portfolio_eur=3500,
|
||||
funding_cross_period=0.0002,
|
||||
dealer_total_net_gamma=-42000.0,
|
||||
)
|
||||
bull_cfg = golden_config(
|
||||
entry=type(cfg.entry)(
|
||||
**{**cfg.entry.model_dump(), "trend_bull_threshold_pct": Decimal("0")}
|
||||
)
|
||||
)
|
||||
ctx = _ctx(bull_cfg, runtime_paths, now)
|
||||
res = await run_entry_cycle(
|
||||
ctx, eur_to_usd_rate=Decimal("1.075"), now=now
|
||||
)
|
||||
assert res.status == "no_entry"
|
||||
assert "dealer short-gamma" in (res.reason or "")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_liquidation_high_risk_blocks_entry(
|
||||
cfg: StrategyConfig,
|
||||
runtime_paths: tuple[Path, Path],
|
||||
now: datetime,
|
||||
httpx_mock: HTTPXMock,
|
||||
) -> None:
|
||||
_wire_market_snapshot(
|
||||
httpx_mock,
|
||||
portfolio_eur=3500,
|
||||
funding_cross_period=0.0002,
|
||||
liquidation_long_risk="high",
|
||||
)
|
||||
bull_cfg = golden_config(
|
||||
entry=type(cfg.entry)(
|
||||
**{**cfg.entry.model_dump(), "trend_bull_threshold_pct": Decimal("0")}
|
||||
)
|
||||
)
|
||||
ctx = _ctx(bull_cfg, runtime_paths, now)
|
||||
res = await run_entry_cycle(
|
||||
ctx, eur_to_usd_rate=Decimal("1.075"), now=now
|
||||
)
|
||||
assert res.status == "no_entry"
|
||||
assert "liquidation squeeze" in (res.reason or "")
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_already_open_position_skips_cycle(
|
||||
cfg: StrategyConfig,
|
||||
|
||||
Reference in New Issue
Block a user