VIOLET V2a: V-TYPES domain layer + hypothesis properties + divergence reject-at-source
domain.py: refined scalar aliases (BarsHeld kills the bars_held=-106 UInt16 poison class by construction), DivergenceRow (DDL-shaped, frozen, extra=forbid), ExecDriverSettings (env boundary for the V2 driver; ttl override exists because the shared router clamps TTLs >= 0.5s), ExecGateReport schema, beartype 'typed' decorator with DOLPHIN_VIOLET_BEARTYPE=0 kill-switch. divergence.py: rows now parse through DivergenceRow before the sink — malformed rows die at the source with a rate-limited WARNING + counter, never at the head of the CH spool. Properties (hypothesis, derandomized): ExecutionRouter state machine (fill/retry mutual exclusion via pop-semantics, R1 exit escalation same trade_id, bounded retry chains, <=1 working ENTER), LatencyHistogram percentile laws (member-of-samples, monotone, extremes), DivergenceRow parse laws. 34 new tests; violet suite 64 green; router 77 green; zero shared-file edits. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -25,7 +25,10 @@ import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Callable, Dict, List, Optional
|
||||
|
||||
from pydantic import ValidationError
|
||||
|
||||
from .clock import PlaneClock, mono_ns
|
||||
from .domain import DivergenceRow
|
||||
|
||||
LOGGER = logging.getLogger("violet.divergence")
|
||||
|
||||
@@ -84,6 +87,7 @@ class FeedDivergenceMonitor:
|
||||
self._stream_task: Optional[asyncio.Task] = None
|
||||
self._rest_task: Optional[asyncio.Task] = None
|
||||
self.rows_emitted = 0
|
||||
self.rows_rejected = 0
|
||||
|
||||
# ── venue side ───────────────────────────────────────────────────────────
|
||||
|
||||
@@ -204,17 +208,29 @@ class FeedDivergenceMonitor:
|
||||
if (now - vm.mono) > self.venue_clock.staleness_budget_ns:
|
||||
continue
|
||||
divergence_bps = (vm.mid - scan_price) / scan_price * 1e4
|
||||
self.sink(self.table, {
|
||||
"ts": int(time.time() * 1000), # DateTime64(3)
|
||||
"session_id": self.session_id,
|
||||
"asset": key,
|
||||
"scan_price": scan_price,
|
||||
"venue_mid": vm.mid,
|
||||
"divergence_bps": divergence_bps,
|
||||
"scan_seq": int(scan_seq),
|
||||
"venue_seq": int(vm.seq),
|
||||
"mono_ns": int(now),
|
||||
})
|
||||
# V-TYPES: parse, don't validate — a malformed row dies here,
|
||||
# never at the head of the CH spool (the bars_held lesson).
|
||||
try:
|
||||
row = DivergenceRow(
|
||||
ts=int(time.time() * 1000), # DateTime64(3)
|
||||
session_id=self.session_id,
|
||||
asset=key,
|
||||
scan_price=scan_price,
|
||||
venue_mid=vm.mid,
|
||||
divergence_bps=divergence_bps,
|
||||
scan_seq=int(scan_seq),
|
||||
venue_seq=int(vm.seq),
|
||||
mono_ns=int(now),
|
||||
)
|
||||
except ValidationError as exc:
|
||||
self.rows_rejected += 1
|
||||
if self.rows_rejected == 1 or self.rows_rejected % 1000 == 0:
|
||||
self.logger.warning(
|
||||
"divergence row REJECTED at source (#%d) asset=%s: %s",
|
||||
self.rows_rejected, key, exc,
|
||||
)
|
||||
continue
|
||||
self.sink(self.table, row.model_dump())
|
||||
emitted += 1
|
||||
self.rows_emitted += emitted
|
||||
return emitted
|
||||
|
||||
Reference in New Issue
Block a user