build(agent): new-agents-2#7e3bbc iteration

This commit is contained in:
agent-7e3bbc424e07835b 2026-04-24 17:49:31 +02:00
parent 57c87933b3
commit fcdd3d0e64
10 changed files with 212 additions and 60 deletions

View File

@ -3,23 +3,23 @@
CrossVenueArbX is a lightweight, privacy-aware framework for federated discovery and execution of cross-venue equity arbitrage signals. CrossVenueArbX is a lightweight, privacy-aware framework for federated discovery and execution of cross-venue equity arbitrage signals.
- Core IR: LocalArbProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, PrivacyBudget. - Core IR: LocalArbProblem, SharedSignals, PlanDelta, DualVariables, AuditLog, PrivacyBudget.
- Lightweight coordination: Async ADMM-lite with deterministic replay and bounded staleness. - Deterministic coordination: replay-stable plan signatures, stable merges, and reconnect reconciliation.
- Privacy by design: secure aggregation stubs, optional local DP budgets, cryptographic attestation of adapters. - Privacy by design: secure aggregation stubs, optional local DP budgets, cryptographic attestation of adapters.
- Adapters and data feeds: starter price-feed adapters and a minimal broker adapter for execution. - Adapters and data feeds: deterministic starter price-feed adapters and a broker adapter for execution.
- Governance and provenance: tamper-evident audit logs and a Graph-of-Contracts registry. - Governance and provenance: tamper-evident audit logs, contract fingerprints, and a Graph-of-Contracts registry.
- MVP pipeline: Phase 0 skeleton with 2 starter adapters; Phase 1 governance ledger; Phase 2 cross-venue demo; Phase 3 production-like testing and SDK. - MVP pipeline: Phase 0 skeleton with 2 starter adapters; Phase 1 governance ledger; Phase 2 cross-venue demo; Phase 3 production-like testing and SDK.
Getting Started Getting Started
- Run tests and build: `bash test.sh`. - Run tests and build: `bash test.sh`.
- Run the demo locally: `python -m crossvenue_arbx.demo --iterations 3`. - Run the demo locally: `python -m crossvenue_arbx.demo --iterations 3`.
- View coverage and governance artefacts in `crossvenue_arbx/governance.py` and `crossvenue_arbx/core.py`. - View coverage and governance artefacts in `crossvenue_arbx/governance.py` and `crossvenue_arbx/core.py`.
- Interoperability: a lightweight EnergiBridge (crossvenue_arbx.bridge) translates CrossVenueArbX primitives into a canonical CatOpt-IR for integration with external ecosystems. - Interoperability: a lightweight EnergiBridge (`crossvenue_arbx.bridge`) translates CrossVenueArbX primitives into a canonical CatOpt-IR for integration with external ecosystems.
Architecture Summary Architecture Summary
- Local signal discovery per venue; aggregated signals managed by a central coordinator. - Local signal discovery per venue; aggregated signals managed by a central coordinator.
- PlanDelta encodes actionable cross-venue arb legs. - PlanDelta encodes actionable cross-venue arb legs.
- Graph-of-Contracts provides versioning for adapters and data contracts. - Graph-of-Contracts provides versioning for adapters and data contracts.
- CRDT-like delta merges enable deterministic replay after reconnects. - CRDT-like delta merges enable deterministic replay after reconnects, with canonical fingerprints for governance and audit trails.
Usage and API surface Usage and API surface
- LocalArbProblem: per-asset, per-venue problem statement. - LocalArbProblem: per-asset, per-venue problem statement.

View File

@ -4,6 +4,7 @@ from .core import LocalArbProblem, SharedSignals, PlanDelta, DualVariables, Audi
from .adapters import PriceFeedAdapter, BrokerAdapter from .adapters import PriceFeedAdapter, BrokerAdapter
from .coordinator import CentralCoordinator from .coordinator import CentralCoordinator
from .governance import GraphOfContracts from .governance import GraphOfContracts
from .bridge import EnergiBridge
__all__ = [ __all__ = [
"LocalArbProblem", "LocalArbProblem",
@ -16,4 +17,5 @@ __all__ = [
"BrokerAdapter", "BrokerAdapter",
"CentralCoordinator", "CentralCoordinator",
"GraphOfContracts", "GraphOfContracts",
"EnergiBridge",
] ]

View File

@ -5,6 +5,11 @@ from typing import Dict, Any
from .core import LocalArbProblem, SharedSignals, PlanDelta from .core import LocalArbProblem, SharedSignals, PlanDelta
def _deterministic_delta(seed: str) -> float:
basis = sum(ord(ch) for ch in seed)
return 0.0002 * (basis % 5)
class PriceFeedAdapter: class PriceFeedAdapter:
"""Adapter A: emits LocalArbProblem + SharedSignals on a fixed cadence.""" """Adapter A: emits LocalArbProblem + SharedSignals on a fixed cadence."""
@ -14,18 +19,17 @@ class PriceFeedAdapter:
self.version = 1 self.version = 1
def step(self) -> tuple[LocalArbProblem, SharedSignals]: def step(self) -> tuple[LocalArbProblem, SharedSignals]:
# Create a simple local arb problem and some market deltas
prob = LocalArbProblem( prob = LocalArbProblem(
id=f"{self.venue}-p1", id=f"{self.venue}-p1",
venue=self.venue, venue=self.venue,
assets=self.assets, assets=self.assets,
target_misprice=0.001, # placeholder target target_misprice=0.001,
max_exposure=100000.0, max_exposure=100000.0,
latency_budget=0.1, latency_budget=0.1,
) )
signals = SharedSignals( signals = SharedSignals(
version=self.version, version=self.version,
price_delta_by_asset={a: 0.0001 * (hash(a) % 5) for a in self.assets}, price_delta_by_asset={a: _deterministic_delta(f"{self.venue}:{a}") for a in self.assets},
cross_corr={(a1, a2): 0.1 for a1 in self.assets for a2 in self.assets if a1 != a2}, cross_corr={(a1, a2): 0.1 for a1 in self.assets for a2 in self.assets if a1 != a2},
liquidity_estimates={a: 1.0 for a in self.assets}, liquidity_estimates={a: 1.0 for a in self.assets},
) )
@ -40,19 +44,23 @@ class BrokerAdapter:
self.venue = venue self.venue = venue
def execute(self, plan: PlanDelta) -> Dict[str, Any]: def execute(self, plan: PlanDelta) -> Dict[str, Any]:
# Simulate a fill with deterministic outcome based on delta_actions
fills = [] fills = []
for action in plan.delta_actions: for action in plan.delta_actions:
fills.append({ fills.append(
"asset": action.get("asset"), {
"size": action.get("size"), "asset": action.get("asset"),
"from": action.get("from_venue"), "size": action.get("size"),
"to": action.get("to_venue"), "from": action.get("from_venue"),
"status": "filled", "to": action.get("to_venue"),
}) "status": "filled",
"fee_bps": 1.0,
}
)
return { return {
"venue": self.venue, "venue": self.venue,
"timestamp": plan.timestamp, "timestamp": plan.timestamp,
"fills": fills, "fills": fills,
"ack": True, "ack": True,
"contract_id": plan.contract_id,
"signature": plan.signature,
} }

View File

@ -12,7 +12,7 @@ Rationale:
- Supports deterministic replay semantics by producing stable, versioned IR blocks - Supports deterministic replay semantics by producing stable, versioned IR blocks
""" """
from typing import Any, Dict from typing import Any, Dict, Optional
from .core import LocalArbProblem, SharedSignals, PlanDelta from .core import LocalArbProblem, SharedSignals, PlanDelta
@ -21,7 +21,11 @@ class EnergiBridge:
"""Bridge utility to convert CrossVenueArbX primitives to a CatOpt-like IR.""" """Bridge utility to convert CrossVenueArbX primitives to a CatOpt-like IR."""
@staticmethod @staticmethod
def to_catopt(problem: LocalArbProblem, signals: SharedSignals) -> Dict[str, Any]: def to_catopt(
problem: LocalArbProblem,
signals: SharedSignals,
plan_delta: Optional[PlanDelta] = None,
) -> Dict[str, Any]:
"""Convert a LocalArbProblem and its associated SharedSignals into a """Convert a LocalArbProblem and its associated SharedSignals into a
canonical, vendor-agnostic representation. canonical, vendor-agnostic representation.
@ -31,26 +35,19 @@ class EnergiBridge:
the MVP lean while still providing a predictable schema. the MVP lean while still providing a predictable schema.
""" """
catopt: Dict[str, Any] = { catopt: Dict[str, Any] = {
"version": max(getattr(problem, "version", 0), getattr(signals, "version", 0)) "version": max(getattr(problem, "version", 0), signals.version, 1),
if hasattr(problem, "version") or hasattr(signals, "version") "contract_id": problem.fingerprint(),
else 1,
"objects": { "objects": {
"LocalArbProblem": problem.to_dict(), "LocalArbProblem": {**problem.to_dict(), "fingerprint": problem.fingerprint()},
"SharedSignals": { "SharedSignals": {**signals.to_dict(), "fingerprint": signals.fingerprint()},
"version": signals.version,
"price_delta_by_asset": dict(signals.price_delta_by_asset),
"cross_corr": dict(signals.cross_corr),
"liquidity_estimates": dict(signals.liquidity_estimates),
},
}, },
"morphisms": { "morphisms": {
"SharedSignals": { "SharedSignals": {
"version": signals.version, "version": signals.version,
# shallow representation; real implementations may include attestations "connections": sorted((repr(key) for key in signals.cross_corr.keys())),
"connections": list(signals.cross_corr.keys()),
} }
}, },
"plan_delta_ref": None, # linked PlanDelta would be populated at runtime "plan_delta_ref": plan_delta.to_dict() if plan_delta is not None else None,
} }
return catopt return catopt
@ -64,4 +61,5 @@ class EnergiBridge:
return { return {
"objects": catopt.get("objects", {}), "objects": catopt.get("objects", {}),
"morphisms": catopt.get("morphisms", {}), "morphisms": catopt.get("morphisms", {}),
"plan_delta_ref": catopt.get("plan_delta_ref"),
} }

View File

@ -1,6 +1,5 @@
from __future__ import annotations from __future__ import annotations
import time
from typing import Dict, List, Any from typing import Dict, List, Any
from .core import LocalArbProblem, SharedSignals, PlanDelta from .core import LocalArbProblem, SharedSignals, PlanDelta
@ -25,6 +24,16 @@ class CentralCoordinator:
# Optional bridge for canonical IR interoperability (can be used by adapters) # Optional bridge for canonical IR interoperability (can be used by adapters)
self.bridge = EnergiBridge() self.bridge = EnergiBridge()
self.last_catopt: Dict[str, Any] | None = None self.last_catopt: Dict[str, Any] | None = None
self.replay_log: List[str] = []
def _other_venue(self, venue: str) -> str:
venues = sorted(self.shared_signals_by_venue)
if len(venues) < 2:
return "VenueB" if venue != "VenueB" else "VenueA"
for candidate in venues:
if candidate != venue:
return candidate
return venue
def ingest_local(self, risk_source: LocalArbProblem, signals: SharedSignals) -> PlanDelta | None: def ingest_local(self, risk_source: LocalArbProblem, signals: SharedSignals) -> PlanDelta | None:
self.version += 1 self.version += 1
@ -35,46 +44,49 @@ class CentralCoordinator:
except Exception: except Exception:
# Non-critical for MVP; retain existing behavior on bridge failure # Non-critical for MVP; retain existing behavior on bridge failure
self.last_catopt = None self.last_catopt = None
# Naive cross-venue decision: if any price_delta_by_asset exceeds threshold, propose cross-venue move
delta = [] delta = []
for asset, delta_price in signals.price_delta_by_asset.items(): for asset in sorted(signals.price_delta_by_asset):
delta_price = signals.price_delta_by_asset[asset]
if abs(delta_price) > 0.0005: if abs(delta_price) > 0.0005:
# simple dummy cross-venue action: move asset from venue A to venue B (names inferred)
action = { action = {
"from_venue": risk_source.venue, "from_venue": risk_source.venue,
"to_venue": "VenueB" if risk_source.venue != "VenueB" else "VenueA", "to_venue": self._other_venue(risk_source.venue),
"asset": asset, "asset": asset,
"size": 10.0 * abs(delta_price), "size": min(risk_source.max_exposure, 10_000.0 * abs(delta_price)),
"time": time.time(), "time": float(self.version),
} }
delta.append(action) delta.append(action)
if delta: if delta:
contract_id = risk_source.fingerprint()
plan = PlanDelta( plan = PlanDelta(
delta_actions=delta, delta_actions=delta,
timestamp=time.time(), timestamp=float(self.version),
contract_id="contract-1", contract_id=contract_id,
signature=f"sig-{self.version}",
) )
self.last_plan = plan self.last_plan = plan
self.pending_delta_actions.append(delta) self.pending_delta_actions.append(delta)
self.contracts.register(contract_id, adapter_version=str(signals.version), schema_hash=plan.signature)
self.replay_log.append(plan.signature)
return plan return plan
return None return None
def reconcile(self) -> PlanDelta | None: def reconcile(self) -> PlanDelta | None:
# Deterministic delta reconciliation on reconnect: merge all pending deltas
if not self.pending_delta_actions: if not self.pending_delta_actions:
return None return None
# Flatten actions and sort by a stable key
merged = [] merged = []
for d in self.pending_delta_actions: for d in self.pending_delta_actions:
merged.extend(d) merged.extend(d)
merged.sort(key=lambda a: (a.get("from_venue"), a.get("to_venue"), a.get("asset"))) merged.sort(key=lambda a: (a.get("from_venue"), a.get("to_venue"), a.get("asset")))
contract_id = "merged:" + ",".join(
sorted({action.get("asset", "") for action in merged})
)
plan = PlanDelta( plan = PlanDelta(
delta_actions=merged, delta_actions=merged,
timestamp=time.time(), timestamp=float(self.version),
contract_id="contract-merged", contract_id=contract_id,
signature=f"sig-reconciled-{len(self.pending_delta_actions)}",
) )
self.pending_delta_actions.clear() self.pending_delta_actions.clear()
self.last_plan = plan self.last_plan = plan
self.contracts.register(contract_id, adapter_version="reconciled", schema_hash=plan.signature)
self.replay_log.append(plan.signature)
return plan return plan

View File

@ -1,9 +1,26 @@
from __future__ import annotations from __future__ import annotations
import hashlib
import json
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List, Any from typing import Dict, List, Any
def _stable_json(payload: Dict[str, Any]) -> str:
def normalize(value: Any) -> Any:
if isinstance(value, dict):
return {str(key): normalize(val) for key, val in sorted(value.items(), key=lambda item: _stable_key(item[0]))}
if isinstance(value, (list, tuple)):
return [normalize(item) for item in value]
return value
return json.dumps(normalize(payload), sort_keys=True, separators=(",", ":"), default=str)
def _stable_key(item: Any) -> str:
return repr(item)
@dataclass @dataclass
class LocalArbProblem: class LocalArbProblem:
id: str id: str
@ -23,27 +40,38 @@ class LocalArbProblem:
"latency_budget": self.latency_budget, "latency_budget": self.latency_budget,
} }
def fingerprint(self) -> str:
return hashlib.sha256(_stable_json(self.to_dict()).encode("utf-8")).hexdigest()
@dataclass @dataclass
class SharedSignals: class SharedSignals:
version: int version: int
price_delta_by_asset: Dict[str, float] = field(default_factory=dict) price_delta_by_asset: Dict[str, float] = field(default_factory=dict)
cross_corr: Dict[str, float] = field(default_factory=dict) cross_corr: Dict[Any, float] = field(default_factory=dict)
liquidity_estimates: Dict[str, float] = field(default_factory=dict) liquidity_estimates: Dict[str, float] = field(default_factory=dict)
def to_dict(self) -> Dict[str, Any]:
return {
"version": self.version,
"price_delta_by_asset": dict(self.price_delta_by_asset),
"cross_corr": dict(self.cross_corr),
"liquidity_estimates": dict(self.liquidity_estimates),
}
def fingerprint(self) -> str:
return hashlib.sha256(_stable_json(self.to_dict()).encode("utf-8")).hexdigest()
def merge(self, other: "SharedSignals") -> "SharedSignals": def merge(self, other: "SharedSignals") -> "SharedSignals":
# Simple deterministic merge: take max delta per asset and average correlations
merged = SharedSignals(version=max(self.version, other.version)) merged = SharedSignals(version=max(self.version, other.version))
# merge price deltas by taking the maximum absolute delta for stability keys = sorted(set(self.price_delta_by_asset) | set(other.price_delta_by_asset), key=_stable_key)
keys = set(self.price_delta_by_asset) | set(other.price_delta_by_asset)
for k in keys: for k in keys:
a = self.price_delta_by_asset.get(k, 0.0) a = self.price_delta_by_asset.get(k, 0.0)
b = other.price_delta_by_asset.get(k, 0.0) b = other.price_delta_by_asset.get(k, 0.0)
merged.price_delta_by_asset[k] = a if abs(a) >= abs(b) else b merged.price_delta_by_asset[k] = a if abs(a) >= abs(b) else b
# simple average for correlations and liquidity estimates for k in sorted(set(self.cross_corr) | set(other.cross_corr), key=_stable_key):
for k in set(self.cross_corr) | set(other.cross_corr):
merged.cross_corr[k] = (self.cross_corr.get(k, 0.0) + other.cross_corr.get(k, 0.0)) / 2.0 merged.cross_corr[k] = (self.cross_corr.get(k, 0.0) + other.cross_corr.get(k, 0.0)) / 2.0
for k in set(self.liquidity_estimates) | set(other.liquidity_estimates): for k in sorted(set(self.liquidity_estimates) | set(other.liquidity_estimates), key=_stable_key):
merged.liquidity_estimates[k] = ( merged.liquidity_estimates[k] = (
self.liquidity_estimates.get(k, 0.0) + other.liquidity_estimates.get(k, 0.0) self.liquidity_estimates.get(k, 0.0) + other.liquidity_estimates.get(k, 0.0)
) / 2.0 ) / 2.0
@ -55,7 +83,11 @@ class PlanDelta:
delta_actions: List[Dict[str, Any]] delta_actions: List[Dict[str, Any]]
timestamp: float timestamp: float
contract_id: str contract_id: str
signature: str signature: str = ""
def __post_init__(self) -> None:
if not self.signature:
self.signature = self.fingerprint()
def to_dict(self) -> Dict[str, Any]: def to_dict(self) -> Dict[str, Any]:
return { return {
@ -65,23 +97,47 @@ class PlanDelta:
"signature": self.signature, "signature": self.signature,
} }
def fingerprint(self) -> str:
payload = {
"contract_id": self.contract_id,
"delta_actions": self.delta_actions,
"timestamp": self.timestamp,
}
return hashlib.sha256(_stable_json(payload).encode("utf-8")).hexdigest()
@dataclass @dataclass
class DualVariables: class DualVariables:
shadow_price: float = 0.0 shadow_price: float = 0.0
def to_dict(self) -> Dict[str, Any]:
return {"shadow_price": self.shadow_price}
@dataclass @dataclass
class AuditLog: class AuditLog:
entries: List[str] = field(default_factory=list) entries: List[str] = field(default_factory=list)
hashes: List[str] = field(default_factory=list)
def log(self, msg: str) -> None: def log(self, msg: str) -> None:
prev = self.hashes[-1] if self.hashes else ""
entry_hash = hashlib.sha256(f"{prev}|{msg}".encode("utf-8")).hexdigest()
self.entries.append(msg) self.entries.append(msg)
self.hashes.append(entry_hash)
def root_hash(self) -> str:
return self.hashes[-1] if self.hashes else ""
@dataclass @dataclass
class PrivacyBudget: class PrivacyBudget:
leakage_budget: float leakage_budget: float
def remaining(self) -> float:
return self.leakage_budget
def can_consume(self, amount: float) -> bool:
return amount <= self.leakage_budget
def consume(self, amount: float) -> None: def consume(self, amount: float) -> None:
self.leakage_budget = max(0.0, self.leakage_budget - amount) self.leakage_budget = max(0.0, self.leakage_budget - amount)

View File

@ -1,7 +1,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass, field from dataclasses import dataclass, field
from typing import Dict, List from typing import Dict, List, Optional
@dataclass @dataclass
@ -9,8 +9,13 @@ class GraphOfContracts:
"""A simple in-memory registry for contract adapters and versions.""" """A simple in-memory registry for contract adapters and versions."""
contracts: Dict[str, Dict[str, str]] = field(default_factory=dict) contracts: Dict[str, Dict[str, str]] = field(default_factory=dict)
def register(self, contract_id: str, adapter_version: str) -> None: def register(self, contract_id: str, adapter_version: str, schema_hash: str = "") -> Dict[str, str]:
self.contracts[contract_id] = {"adapter_version": adapter_version} record = {"adapter_version": adapter_version, "schema_hash": schema_hash}
self.contracts[contract_id] = record
return record
def get_version(self, contract_id: str) -> str | None: def get_version(self, contract_id: str) -> Optional[str]:
return self.contracts.get(contract_id, {}).get("adapter_version") return self.contracts.get(contract_id, {}).get("adapter_version")
def get_record(self, contract_id: str) -> Dict[str, str]:
return dict(self.contracts.get(contract_id, {}))

View File

@ -1,5 +1,5 @@
from crossvenue_arbx.bridge import EnergiBridge from crossvenue_arbx.bridge import EnergiBridge
from crossvenue_arbx.core import LocalArbProblem, SharedSignals from crossvenue_arbx.core import LocalArbProblem, SharedSignals, PlanDelta
def test_to_catopt_basic_structure(): def test_to_catopt_basic_structure():
@ -31,3 +31,25 @@ def test_to_catopt_basic_structure():
assert objs["LocalArbProblem"]["venue"] == "Venue1" assert objs["LocalArbProblem"]["venue"] == "Venue1"
assert "SharedSignals" in objs assert "SharedSignals" in objs
assert objs["SharedSignals"]["version"] == 1 assert objs["SharedSignals"]["version"] == 1
def test_to_catopt_with_plan_delta_round_trip():
problem = LocalArbProblem(
id="venue1-p1",
venue="Venue1",
assets=["AAPL"],
target_misprice=0.001,
max_exposure=100000.0,
latency_budget=0.1,
)
signals = SharedSignals(version=2, price_delta_by_asset={"AAPL": 0.001})
plan = PlanDelta(
delta_actions=[{"from_venue": "Venue1", "to_venue": "Venue2", "asset": "AAPL", "size": 1.0, "time": 2.0}],
timestamp=2.0,
contract_id=problem.fingerprint(),
)
catopt = EnergiBridge.to_catopt(problem, signals, plan)
decoded = EnergiBridge.from_catopt(catopt)
assert decoded["plan_delta_ref"]["contract_id"] == problem.fingerprint()

View File

@ -4,7 +4,8 @@ import sys
BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) BASE = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
if BASE not in sys.path: if BASE not in sys.path:
sys.path.insert(0, BASE) sys.path.insert(0, BASE)
from crossvenue_arbx.core import LocalArbProblem, SharedSignals, PlanDelta from crossvenue_arbx.core import LocalArbProblem, SharedSignals, PlanDelta, AuditLog, PrivacyBudget
from crossvenue_arbx.governance import GraphOfContracts
def test_local_arb_problem_serialization(): def test_local_arb_problem_serialization():
@ -29,3 +30,31 @@ def test_shared_signals_merge_basic():
assert merged.version >= 2 assert merged.version >= 2
assert "AAPL" in merged.price_delta_by_asset assert "AAPL" in merged.price_delta_by_asset
assert "MSFT" in merged.price_delta_by_asset assert "MSFT" in merged.price_delta_by_asset
def test_plan_delta_signature_is_stable():
plan = PlanDelta(
delta_actions=[{"from_venue": "VenueA", "to_venue": "VenueB", "asset": "AAPL", "size": 1.0, "time": 1.0}],
timestamp=1.0,
contract_id="contract-x",
)
assert plan.signature
assert plan.signature == plan.fingerprint()
def test_audit_log_hash_chain_and_privacy_budget():
log = AuditLog()
log.log("first")
log.log("second")
assert log.root_hash()
budget = PrivacyBudget(leakage_budget=1.0)
assert budget.can_consume(0.25)
budget.consume(0.25)
assert budget.remaining() == 0.75
def test_governance_registry_tracks_metadata():
registry = GraphOfContracts()
registry.register("contract-x", "1.2.3", "schema-abc")
assert registry.get_version("contract-x") == "1.2.3"
assert registry.get_record("contract-x")["schema_hash"] == "schema-abc"

View File

@ -41,3 +41,23 @@ def test_end_to_end_two_venues_basic_reconciliation():
actions = recon.delta_actions actions = recon.delta_actions
sorted_actions = sorted(actions, key=lambda x: (x.get("from_venue"), x.get("to_venue"), x.get("asset"))) sorted_actions = sorted(actions, key=lambda x: (x.get("from_venue"), x.get("to_venue"), x.get("asset")))
assert actions == sorted_actions assert actions == sorted_actions
assert recon.signature
def test_price_feed_and_execution_are_deterministic():
ad1 = PriceFeedAdapter("VenueA", ["AAPL", "MSFT"])
ad2 = PriceFeedAdapter("VenueA", ["AAPL", "MSFT"])
p1, s1 = ad1.step()
p2, s2 = ad2.step()
assert p1.fingerprint() == p2.fingerprint()
assert s1.price_delta_by_asset == s2.price_delta_by_asset
coord1 = CentralCoordinator()
coord2 = CentralCoordinator()
plan1 = coord1.ingest_local(p1, s1)
plan2 = coord2.ingest_local(p2, s2)
assert plan1 is not None and plan2 is not None
assert plan1.signature == plan2.signature
assert plan1.to_dict() == plan2.to_dict()