d9454fc996
Sistema dedicato di raccolta dati per scegliere le soglie dei filtri sui percentili reali invece di valori a istinto. Nuovi componenti: * state/migrations/0003_market_snapshots.sql — tabella + index, PK composta (timestamp, asset). Ogni colonna numerica è NULL-able per preservare la continuità della serie quando un singolo MCP fallisce. * state/models.py — MarketSnapshotRecord Pydantic. * state/repository.py — record_market_snapshot, list_market_snapshots, _row_to_market_snapshot. * runtime/market_snapshot_cycle.py — collettore best-effort che chiama spot/dvol/realized_vol/dealer_gamma/funding_perp/funding_cross/ liquidation_heatmap/macro per ogni asset; raccoglie gli errori in fetch_errors_json e segna fetch_ok=false ma persiste comunque la riga. * clients/deribit.py — generalizzati dealer_gamma_profile(currency), realized_vol(currency), spot_perp_price(asset). dealer_gamma_profile_eth resta come alias per la chiamata dell'entry cycle. * runtime/orchestrator.py — nuovo job APScheduler `market_snapshot` cron */15 con assets configurabili (default ETH+BTC); il consumer manual_actions ora dispatcha anche kind=run_cycle cycle=market_snapshot per la GUI. * gui/data_layer.py — load_market_snapshots, enqueue_run_cycle accetta market_snapshot; tipo MarketSnapshotRecord esposto. * gui/pages/6_📐_Calibrazione.py — selezione asset+finestra, conteggio fetch_ok, per ogni metrica: istogramma, soglia da strategy.yaml come vline rossa, percentili P5/P10/P25/P50/P75/P90/P95, % di tick che la soglia avrebbe filtrato. * gui/pages/1_📊_Status.py — bottone "📐 Forza snapshot" (4° del pannello Forza ciclo) per popolare la tabella senza aspettare il cron. 5 nuovi test sul collector (happy, fault tolerance, asset switch, macro fail, empty assets); test_orchestrator job set aggiornato. 368/368 tests pass; ruff clean; mypy strict src clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
187 lines
4.8 KiB
Python
187 lines
4.8 KiB
Python
"""Pydantic record types mirroring the SQLite tables.
|
|
|
|
Every numeric column documented as ``NUMERIC`` in
|
|
``state/migrations/0001_init.sql`` is exposed as :class:`decimal.Decimal`
|
|
on the Python side. The repository layer is responsible for serialising
|
|
to ``TEXT`` (using ``str``) when writing and parsing back when reading,
|
|
so precision is never lost via ``float`` coercion.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime
|
|
from decimal import Decimal
|
|
from typing import Literal
|
|
from uuid import UUID
|
|
|
|
from pydantic import BaseModel, ConfigDict, Field
|
|
|
|
__all__ = [
|
|
"DecisionRecord",
|
|
"DvolSnapshot",
|
|
"InstructionRecord",
|
|
"ManualAction",
|
|
"MarketSnapshotRecord",
|
|
"PositionRecord",
|
|
"PositionStatus",
|
|
"SystemStateRecord",
|
|
]
|
|
|
|
|
|
PositionStatus = Literal[
|
|
"proposed",
|
|
"awaiting_fill",
|
|
"open",
|
|
"closing",
|
|
"closed",
|
|
"cancelled",
|
|
]
|
|
|
|
|
|
class PositionRecord(BaseModel):
|
|
"""Row of the ``positions`` table."""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
proposal_id: UUID
|
|
spread_type: str
|
|
asset: str = "ETH"
|
|
expiry: datetime
|
|
short_strike: Decimal
|
|
long_strike: Decimal
|
|
short_instrument: str
|
|
long_instrument: str
|
|
n_contracts: int
|
|
spread_width_usd: Decimal
|
|
spread_width_pct: Decimal
|
|
credit_eth: Decimal
|
|
credit_usd: Decimal
|
|
max_loss_usd: Decimal
|
|
spot_at_entry: Decimal
|
|
dvol_at_entry: Decimal
|
|
delta_at_entry: Decimal
|
|
eth_price_at_entry: Decimal
|
|
proposed_at: datetime
|
|
opened_at: datetime | None = None
|
|
closed_at: datetime | None = None
|
|
close_reason: str | None = None
|
|
debit_paid_eth: Decimal | None = None
|
|
pnl_eth: Decimal | None = None
|
|
pnl_usd: Decimal | None = None
|
|
status: PositionStatus
|
|
created_at: datetime
|
|
updated_at: datetime
|
|
|
|
|
|
class InstructionRecord(BaseModel):
|
|
"""Row of the ``instructions`` table."""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
instruction_id: UUID
|
|
proposal_id: UUID
|
|
kind: Literal["open_combo", "close_combo"]
|
|
payload_json: str
|
|
sent_at: datetime
|
|
acknowledged_at: datetime | None = None
|
|
filled_at: datetime | None = None
|
|
cancelled_at: datetime | None = None
|
|
actual_fill_eth: Decimal | None = None
|
|
actual_fees_eth: Decimal | None = None
|
|
|
|
|
|
class DecisionRecord(BaseModel):
|
|
"""Row of the ``decisions`` table.
|
|
|
|
``id`` is :class:`int` and may be ``None`` before the row has been
|
|
inserted; the repository sets it after the auto-increment fires.
|
|
"""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
id: int | None = None
|
|
decision_type: Literal["entry_check", "exit_check", "kelly_recalib"]
|
|
proposal_id: UUID | None = None
|
|
timestamp: datetime
|
|
inputs_json: str
|
|
outputs_json: str
|
|
action_taken: str | None = None
|
|
notes: str | None = None
|
|
|
|
|
|
class DvolSnapshot(BaseModel):
|
|
"""Row of the ``dvol_history`` table."""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
timestamp: datetime
|
|
dvol: Decimal
|
|
eth_spot: Decimal
|
|
|
|
|
|
class MarketSnapshotRecord(BaseModel):
|
|
"""Row of the ``market_snapshots`` table.
|
|
|
|
Single point in time, single asset. Every numeric field is
|
|
optional because the ``market_snapshot`` collector is best-effort:
|
|
a single MCP failure NULLs the affected metric without dropping
|
|
the row.
|
|
"""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
timestamp: datetime
|
|
asset: str # "ETH", "BTC"
|
|
spot: Decimal | None = None
|
|
dvol: Decimal | None = None
|
|
realized_vol_30d: Decimal | None = None
|
|
iv_minus_rv: Decimal | None = None
|
|
funding_perp_annualized: Decimal | None = None
|
|
funding_cross_annualized: Decimal | None = None
|
|
dealer_net_gamma: Decimal | None = None
|
|
gamma_flip_level: Decimal | None = None
|
|
oi_delta_pct_4h: Decimal | None = None
|
|
liquidation_long_risk: str | None = None
|
|
liquidation_short_risk: str | None = None
|
|
macro_days_to_event: int | None = None
|
|
fetch_ok: bool
|
|
fetch_errors_json: str | None = None
|
|
|
|
|
|
class ManualAction(BaseModel):
|
|
"""Row of the ``manual_actions`` table."""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
id: int | None = None
|
|
kind: Literal[
|
|
"approve_proposal",
|
|
"reject_proposal",
|
|
"force_close",
|
|
"arm_kill",
|
|
"disarm_kill",
|
|
"run_cycle",
|
|
]
|
|
proposal_id: UUID | None = None
|
|
payload_json: str | None = None
|
|
created_at: datetime
|
|
consumed_at: datetime | None = None
|
|
consumed_by: str | None = None
|
|
result: str | None = None
|
|
|
|
|
|
class SystemStateRecord(BaseModel):
|
|
"""Singleton row of the ``system_state`` table."""
|
|
|
|
model_config = ConfigDict(extra="forbid")
|
|
|
|
id: int = Field(default=1)
|
|
kill_switch: int = 0
|
|
kill_reason: str | None = None
|
|
kill_at: datetime | None = None
|
|
last_health_check: datetime
|
|
last_kelly_calib: datetime | None = None
|
|
config_version: str
|
|
started_at: datetime
|
|
last_audit_hash: str | None = None
|