275 lines
9.0 KiB
Python
275 lines
9.0 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
from dataclasses import dataclass, field
|
|
import hashlib
|
|
from typing import List, Dict, Any, Optional
|
|
|
|
|
|
def _to_json(obj: Any) -> Any:
|
|
if hasattr(obj, "to_json"):
|
|
return obj.to_json()
|
|
if isinstance(obj, list):
|
|
return [_to_json(v) for v in obj]
|
|
if isinstance(obj, dict):
|
|
return {k: _to_json(v) for k, v in obj.items()}
|
|
return obj
|
|
|
|
|
|
def _from_json(cls, data: Any):
|
|
if hasattr(cls, "from_json"):
|
|
return cls.from_json(data)
|
|
return data
|
|
|
|
|
|
class LocalArbProblem:
|
|
def __init__(
|
|
self,
|
|
venue_id: str = "DEFAULT_VENUE",
|
|
asset_pair=None,
|
|
target_mispricing: float = 0.0,
|
|
liquidity_budget: float = 0.0,
|
|
latency_budget_ms: int | None = None,
|
|
latency_budget: float | None = None,
|
|
):
|
|
self.venue_id = venue_id
|
|
self.asset_pair = list(asset_pair) if asset_pair is not None else []
|
|
self.target_mispricing = float(target_mispricing)
|
|
self.liquidity_budget = float(liquidity_budget)
|
|
self.latency_budget_ms = latency_budget_ms
|
|
self.latency_budget = latency_budget
|
|
if self.latency_budget_ms is None and self.latency_budget is not None:
|
|
self.latency_budget_ms = int(self.latency_budget * 1000)
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {
|
|
"venue_id": self.venue_id,
|
|
"asset_pair": self.asset_pair,
|
|
"target_mispricing": self.target_mispricing,
|
|
"liquidity_budget": self.liquidity_budget,
|
|
"latency_budget_ms": self.latency_budget_ms,
|
|
}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "LocalArbProblem":
|
|
return cls(
|
|
venue_id=data.get("venue_id", "DEFAULT_VENUE"),
|
|
asset_pair=data.get("asset_pair", []),
|
|
target_mispricing=data.get("target_mispricing", 0.0),
|
|
liquidity_budget=data.get("liquidity_budget", 0.0),
|
|
latency_budget_ms=data.get("latency_budget_ms"),
|
|
)
|
|
|
|
def __eq__(self, other: object) -> bool:
|
|
if not isinstance(other, LocalArbProblem):
|
|
return False
|
|
return (
|
|
self.venue_id == other.venue_id
|
|
and self.asset_pair == other.asset_pair
|
|
and self.target_mispricing == other.target_mispricing
|
|
and self.liquidity_budget == other.liquidity_budget
|
|
and self.latency_budget_ms == other.latency_budget_ms
|
|
)
|
|
|
|
|
|
class SharedSignals:
|
|
def __init__(self, deltas: list[float], cross_venue_corr: float, liquidity_availability: float, latency_proxy: float):
|
|
self.deltas = deltas
|
|
self.cross_venue_corr = cross_venue_corr
|
|
self.liquidity_availability = liquidity_availability
|
|
self.latency_proxy = latency_proxy
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {
|
|
"deltas": self.deltas,
|
|
"cross_venue_corr": self.cross_venue_corr,
|
|
"liquidity_availability": self.liquidity_availability,
|
|
"latency_proxy": self.latency_proxy,
|
|
}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "SharedSignals":
|
|
return cls(
|
|
deltas=data.get("deltas", []),
|
|
cross_venue_corr=data.get("cross_venue_corr", 0.0),
|
|
liquidity_availability=data.get("liquidity_availability", 0.0),
|
|
latency_proxy=data.get("latency_proxy", 0.0),
|
|
)
|
|
|
|
|
|
@dataclass
|
|
class PlanDelta:
|
|
delta: List[Dict[str, Any]]
|
|
timestamp: float
|
|
author: str
|
|
signature: Optional[str] = None
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {
|
|
"delta": self.delta,
|
|
"timestamp": self.timestamp,
|
|
"author": self.author,
|
|
"signature": self.signature,
|
|
}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "PlanDelta":
|
|
return cls(
|
|
delta=data["delta"],
|
|
timestamp=data["timestamp"],
|
|
author=data["author"],
|
|
signature=data.get("signature"),
|
|
)
|
|
|
|
|
|
class DualVariables:
|
|
def __init__(self, shadow_prices: Dict[str, float] | None = None, venue_shadows: Dict[str, float] | None = None):
|
|
if shadow_prices is not None:
|
|
self.venue_shadows = dict(shadow_prices)
|
|
elif venue_shadows is not None:
|
|
self.venue_shadows = dict(venue_shadows)
|
|
else:
|
|
self.venue_shadows = {}
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {"venue_shadows": self.venue_shadows}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "DualVariables":
|
|
return cls(venue_shadows=data.get("venue_shadows", {}))
|
|
|
|
# Compatibility: provide shadow_prices alias used by existing IR mapping
|
|
@property
|
|
def shadow_prices(self) -> Dict[str, float]:
|
|
return self.venue_shadows
|
|
|
|
|
|
@dataclass
|
|
class PrivacyBudget:
|
|
def __init__(self, total_budget: float | None = None, spent: float = 0.0, budgets: Dict[str, float] | None = None):
|
|
if budgets is not None:
|
|
self.total_budget = budgets.get("total", budgets.get("signals", 0.0))
|
|
self.spent = budgets.get("spent", 0.0)
|
|
else:
|
|
self.total_budget = total_budget if total_budget is not None else 0.0
|
|
self.spent = spent
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {"total_budget": self.total_budget, "spent": self.spent}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "PrivacyBudget":
|
|
return cls(total_budget=data.get("total_budget", 0.0), spent=data.get("spent", 0.0))
|
|
|
|
@property
|
|
def budgets(self) -> Dict[str, float]:
|
|
# Compatibility: expose a single "signals" budget for the IR consumer
|
|
return {"signals": self.total_budget}
|
|
|
|
|
|
@dataclass
|
|
class AuditLogEntry:
|
|
def __init__(self, ts: float, event: str, signer: Optional[str] = None, details: Dict[str, Any] = None, signature: Optional[str] = None):
|
|
self.ts = ts
|
|
self.event = event
|
|
# Support both 'signer' and legacy 'signature' naming
|
|
self.signer = signer if signer is not None else signature
|
|
self.details = details if details is not None else {}
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {"event": self.event, "ts": self.ts, "signer": self.signer, "details": self.details}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "AuditLogEntry":
|
|
return cls(ts=data["ts"], event=data["event"], signer=data.get("signer"), details=data.get("details", {}))
|
|
|
|
@property
|
|
def signature(self) -> str | None:
|
|
return self.signer
|
|
|
|
|
|
@dataclass
|
|
class AuditLog:
|
|
entries: List[AuditLogEntry] = field(default_factory=list)
|
|
|
|
def append(self, entry: AuditLogEntry) -> None:
|
|
self.entries.append(entry)
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
serialized = []
|
|
for e in self.entries:
|
|
if hasattr(e, "to_json"):
|
|
serialized.append(e.to_json())
|
|
else:
|
|
serialized.append(e)
|
|
return {"entries": serialized}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "AuditLog":
|
|
entries = [AuditLogEntry.from_json(e) for e in data.get("entries", [])]
|
|
return cls(entries=entries)
|
|
|
|
|
|
@dataclass
|
|
class TimeRounds:
|
|
rounds: List[int] = field(default_factory=list)
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {"rounds": self.rounds}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "TimeRounds":
|
|
return cls(rounds=data.get("rounds", []))
|
|
|
|
|
|
@dataclass
|
|
class GoCRegistry:
|
|
adapters: Dict[str, str] = field(default_factory=dict) # adapter_name -> contract_version
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {"adapters": self.adapters}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "GoCRegistry":
|
|
return cls(adapters=data.get("adapters", {}))
|
|
|
|
|
|
@dataclass
|
|
class DeltaTrace:
|
|
"""Deterministic replay trace for a PlanDelta per venue.
|
|
|
|
This is a lightweight hash- chained log entry capturing the delta payload
|
|
and its provenance. It enables auditable backtesting and partition reconciliation
|
|
without exposing raw delta details beyond the payload envelope.
|
|
"""
|
|
delta_hash: str
|
|
parent_hash: str | None
|
|
timestamp: float
|
|
signer: str
|
|
payload: Dict[str, Any]
|
|
|
|
def to_json(self) -> Dict[str, Any]:
|
|
return {
|
|
"delta_hash": self.delta_hash,
|
|
"parent_hash": self.parent_hash,
|
|
"timestamp": self.timestamp,
|
|
"signer": self.signer,
|
|
"payload": self.payload,
|
|
}
|
|
|
|
@classmethod
|
|
def from_json(cls, data: Dict[str, Any]) -> "DeltaTrace":
|
|
return cls(
|
|
delta_hash=data["delta_hash"],
|
|
parent_hash=data.get("parent_hash"),
|
|
timestamp=data["timestamp"],
|
|
signer=data["signer"],
|
|
payload=data["payload"],
|
|
)
|
|
|
|
@staticmethod
|
|
def compute_hash(payload: Dict[str, Any]) -> str:
|
|
# Stable hash of the JSON payload
|
|
payload_bytes = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode("utf-8")
|
|
return hashlib.sha256(payload_bytes).hexdigest()
|