build(agent): melter#14fd4b iteration
This commit is contained in:
parent
5fc778f166
commit
c41d1a1db4
|
|
@ -0,0 +1,21 @@
|
|||
node_modules/
|
||||
.npmrc
|
||||
.env
|
||||
.env.*
|
||||
__tests__/
|
||||
coverage/
|
||||
.nyc_output/
|
||||
dist/
|
||||
build/
|
||||
.cache/
|
||||
*.log
|
||||
.DS_Store
|
||||
tmp/
|
||||
.tmp/
|
||||
__pycache__/
|
||||
*.pyc
|
||||
.venv/
|
||||
venv/
|
||||
*.egg-info/
|
||||
.pytest_cache/
|
||||
READY_TO_PUBLISH
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
**Architecture**
|
||||
|
||||
MercuriusMesh (Phase 0) — a deterministic, offline-first sandbox for cross-venue experiments.
|
||||
|
||||
Tech stack
|
||||
- Language: Python 3.9+
|
||||
- Runtime packages: pydantic (data contracts), pytest (tests)
|
||||
|
||||
Project layout
|
||||
- mercuriusmesh/: core package with contracts, toy adapters, and a minimal deterministic simulator
|
||||
- tests/: pytest test-suite
|
||||
- test.sh: runs tests and builds the package
|
||||
|
||||
Guiding principles
|
||||
- Keep core contracts minimal and canonical: LocalBook, OrderEvent, QuoteSnapshot, SharedSignals, PlanDelta
|
||||
- Deterministic replay: append-only event log; replay reinitialises adapters and replays events
|
||||
- Small, well-tested chunks: this agent implements Phase 0 core contracts + toy two-venue demo
|
||||
|
||||
Testing
|
||||
- Run `bash test.sh` to run the full verification: pytest and `python3 -m build`.
|
||||
|
||||
Contribution rules
|
||||
- Follow the existing contracts module when adding fields; prefer additive, backward-compatible changes
|
||||
- Add adapter conformance tests to `tests/` and update AGENTS.md when adding new adapters
|
||||
17
README.md
17
README.md
|
|
@ -1,3 +1,16 @@
|
|||
# idea83-mercuriusmesh-cross-venue
|
||||
# MercuriusMesh — Cross-Venue Market Microstructure Sandbox (Phase 0)
|
||||
|
||||
Source logic for Idea #83
|
||||
This repository contains a Phase 0 implementation of MercuriusMesh: a deterministic,
|
||||
offline-first sandbox for cross-venue market microstructure experiments.
|
||||
|
||||
What is included
|
||||
- Core pydantic contracts: LocalBook, OrderEvent, QuoteSnapshot, SharedSignals, PlanDelta
|
||||
- A tiny deterministic two-venue simulator and toy adapters
|
||||
- Tests validating model serialization and deterministic replay
|
||||
|
||||
Run tests and build
|
||||
1. Install dev deps: `pip install -r requirements.txt` or `pip install pydantic pytest build`
|
||||
2. Run `bash test.sh` which runs `pytest` and `python3 -m build`
|
||||
|
||||
This is a focused chunk (Phase 0). Future work: latency/partition modeling, ADMM-lite coordinator,
|
||||
delta-sync, governance logs, conformance harness and adapters marketplace.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
"""MercuriusMesh core package (Phase 0).
|
||||
|
||||
Contains canonical contracts and a tiny deterministic two-venue simulator used in tests.
|
||||
"""
|
||||
|
||||
from .contracts import LocalBook, OrderEvent, QuoteSnapshot, SharedSignals, PlanDelta
|
||||
from .simulator import DeterministicSimulator
|
||||
|
||||
__all__ = [
|
||||
"LocalBook",
|
||||
"OrderEvent",
|
||||
"QuoteSnapshot",
|
||||
"SharedSignals",
|
||||
"PlanDelta",
|
||||
"DeterministicSimulator",
|
||||
]
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
from typing import Callable, List
|
||||
from .contracts import QuoteSnapshot, OrderEvent
|
||||
|
||||
|
||||
class ToyVenueAdapter:
|
||||
"""A tiny in-process adapter that exposes a simple order acceptance model.
|
||||
|
||||
It accepts OrderEvent objects and updates an internal top-of-book. Deterministic
|
||||
behaviour is achieved by not using random jitter and relying on caller ordering.
|
||||
"""
|
||||
|
||||
def __init__(self, venue_id: str, initial_bid: float = 100.0, initial_ask: float = 100.5):
|
||||
self.venue_id = venue_id
|
||||
self.best_bid = initial_bid
|
||||
self.best_ask = initial_ask
|
||||
self.listeners: List[Callable[[QuoteSnapshot], None]] = []
|
||||
|
||||
def on_snapshot(self, cb: Callable[[QuoteSnapshot], None]):
|
||||
self.listeners.append(cb)
|
||||
|
||||
def publish_snapshot(self):
|
||||
snap = QuoteSnapshot(
|
||||
venue_id=self.venue_id,
|
||||
asset="TEST/USD",
|
||||
bids=[[self.best_bid, 100.0]],
|
||||
asks=[[self.best_ask, 100.0]],
|
||||
)
|
||||
for cb in self.listeners:
|
||||
cb(snap)
|
||||
|
||||
def handle_order(self, order: OrderEvent) -> OrderEvent:
|
||||
# Very small deterministic matcher: marketable at top-of-book executes
|
||||
if order.side == "buy" and order.price is None:
|
||||
order.status = "filled"
|
||||
# naive mid update
|
||||
self.best_bid = max(self.best_bid, order.price or self.best_bid)
|
||||
elif order.side == "sell" and order.price is None:
|
||||
order.status = "filled"
|
||||
self.best_ask = min(self.best_ask, order.price or self.best_ask)
|
||||
else:
|
||||
order.status = "accepted"
|
||||
# publish new snapshot deterministically
|
||||
self.publish_snapshot()
|
||||
return order
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
from typing import List, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class LocalBook(BaseModel):
|
||||
"""A minimal per-venue order book snapshot.
|
||||
|
||||
This is intentionally tiny for Phase 0: top-of-book bids/asks with sizes and timestamp.
|
||||
"""
|
||||
venue_id: str
|
||||
asset: str
|
||||
best_bid: Optional[float]
|
||||
best_bid_size: Optional[float]
|
||||
best_ask: Optional[float]
|
||||
best_ask_size: Optional[float]
|
||||
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class OrderEvent(BaseModel):
|
||||
"""A canonical order lifecycle event used between adapters and simulator."""
|
||||
order_id: str
|
||||
venue_id: str
|
||||
asset: str
|
||||
side: str # 'buy' or 'sell'
|
||||
price: Optional[float]
|
||||
size: float
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
status: str = "submitted" # submitted, accepted, filled, cancelled
|
||||
|
||||
|
||||
class QuoteSnapshot(BaseModel):
|
||||
venue_id: str
|
||||
asset: str
|
||||
bids: List[List[float]] = [] # [[price, size], ...]
|
||||
asks: List[List[float]] = []
|
||||
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class SharedSignals(BaseModel):
|
||||
"""Lightweight aggregated signals used for cross-venue coordination."""
|
||||
asset: str
|
||||
mid_price: Optional[float]
|
||||
liquidity: Optional[float]
|
||||
timestamp: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
||||
|
||||
class PlanDelta(BaseModel):
|
||||
"""An incremental plan update: proposed hedge or quote update."""
|
||||
plan_id: str
|
||||
author: str
|
||||
asset: str
|
||||
venue_id: str
|
||||
side: str
|
||||
price: Optional[float]
|
||||
size: float
|
||||
created_at: datetime = Field(default_factory=datetime.utcnow)
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
from typing import Dict, List
|
||||
from .adapters import ToyVenueAdapter
|
||||
from .contracts import OrderEvent, LocalBook, SharedSignals
|
||||
|
||||
|
||||
class DeterministicSimulator:
|
||||
"""A tiny deterministic simulator that runs a sequence of events and supports replay.
|
||||
|
||||
The simulator stores a simple event log (append-only). Replaying the log deterministically
|
||||
rebuilds the same state. This is a minimal skeleton intended for Phase 0 testing and demos.
|
||||
"""
|
||||
|
||||
def __init__(self, seed: int = 0):
|
||||
self.seed = seed
|
||||
self.adapters: Dict[str, ToyVenueAdapter] = {}
|
||||
self.event_log: List[OrderEvent] = []
|
||||
self.state: Dict[str, LocalBook] = {}
|
||||
# store adapter initial params so replay can re-create identical adapters
|
||||
self._adapter_initials: Dict[str, Dict[str, float]] = {}
|
||||
|
||||
def add_adapter(self, adapter: ToyVenueAdapter):
|
||||
self.adapters[adapter.venue_id] = adapter
|
||||
# capture initial topology for deterministic replay
|
||||
self._adapter_initials[adapter.venue_id] = {"best_bid": adapter.best_bid, "best_ask": adapter.best_ask}
|
||||
|
||||
def snapshot_listener(snap):
|
||||
# update simple LocalBook
|
||||
self.state[adapter.venue_id] = LocalBook(
|
||||
venue_id=snap.venue_id,
|
||||
asset=snap.asset,
|
||||
best_bid=snap.bids[0][0] if snap.bids else None,
|
||||
best_bid_size=snap.bids[0][1] if snap.bids else None,
|
||||
best_ask=snap.asks[0][0] if snap.asks else None,
|
||||
best_ask_size=snap.asks[0][1] if snap.asks else None,
|
||||
)
|
||||
|
||||
adapter.on_snapshot(snapshot_listener)
|
||||
|
||||
def submit_order(self, order: OrderEvent) -> OrderEvent:
|
||||
# append to log and route to adapter deterministically by venue_id
|
||||
self.event_log.append(order)
|
||||
adapter = self.adapters.get(order.venue_id)
|
||||
if adapter is None:
|
||||
order.status = "rejected"
|
||||
return order
|
||||
return adapter.handle_order(order)
|
||||
|
||||
def replay(self) -> None:
|
||||
# clear state and re-apply events in order
|
||||
self.state = {}
|
||||
# re-create adapters to ensure deterministic initial conditions
|
||||
adapters_copy = {}
|
||||
for vid, initials in self._adapter_initials.items():
|
||||
adapters_copy[vid] = ToyVenueAdapter(vid, initial_bid=initials.get("best_bid", 100.0), initial_ask=initials.get("best_ask", 100.5))
|
||||
self.adapters = adapters_copy
|
||||
for vid in list(self.adapters.keys()):
|
||||
# re-register listeners and initial state
|
||||
self.add_adapter(self.adapters[vid])
|
||||
for e in list(self.event_log):
|
||||
self.submit_order(e)
|
||||
|
||||
def compute_shared_signals(self, asset: str = "TEST/USD") -> SharedSignals:
|
||||
# simple average mid from known LocalBook entries
|
||||
mids = []
|
||||
for lb in self.state.values():
|
||||
if lb.best_bid is not None and lb.best_ask is not None:
|
||||
mids.append((lb.best_bid + lb.best_ask) / 2)
|
||||
mid = sum(mids) / len(mids) if mids else None
|
||||
liquidity = sum((lb.best_bid_size or 0) + (lb.best_ask_size or 0) for lb in self.state.values())
|
||||
return SharedSignals(asset=asset, mid_price=mid, liquidity=liquidity)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[build-system]
|
||||
requires = ["setuptools>=61.0", "wheel"]
|
||||
build-backend = "setuptools.build_meta"
|
||||
|
||||
[project]
|
||||
name = "mercuriusmesh"
|
||||
version = "0.0.1"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.9"
|
||||
dependencies = ["pydantic>=1.10"]
|
||||
|
||||
[tool.setuptools]
|
||||
packages = ["mercuriusmesh"]
|
||||
include-package-data = true
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
[metadata]
|
||||
name = mercuriusmesh
|
||||
version = 0.0.1
|
||||
description = MercuriusMesh: Cross-venue market microstructure sandbox (toy phase0)
|
||||
long_description = file: README.md
|
||||
long_description_content_type = text/markdown
|
||||
author = OpenCode Agent
|
||||
license = MIT
|
||||
|
||||
[options]
|
||||
packages = find:
|
||||
install_requires =
|
||||
pydantic>=1.10
|
||||
pytest>=7.0
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
echo "Running tests..."
|
||||
# ensure package dir is importable for pytest
|
||||
export PYTHONPATH="$(pwd)"
|
||||
echo "Installing test dependencies (pydantic, pytest, build)..."
|
||||
python3 -m pip install --quiet --upgrade pip
|
||||
python3 -m pip install --quiet pydantic pytest build
|
||||
pytest -q
|
||||
|
||||
echo "Building source distribution and wheel..."
|
||||
python3 -m build
|
||||
|
||||
echo "All checks passed."
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
from mercuriusmesh.contracts import LocalBook, OrderEvent, QuoteSnapshot, SharedSignals, PlanDelta
|
||||
|
||||
|
||||
def test_contract_models():
|
||||
lb = LocalBook(venue_id="V1", asset="TEST/USD", best_bid=100.0, best_bid_size=10, best_ask=100.5, best_ask_size=12)
|
||||
assert lb.venue_id == "V1"
|
||||
oe = OrderEvent(order_id="o1", venue_id="V1", asset="TEST/USD", side="buy", price=None, size=1)
|
||||
assert oe.status == "submitted"
|
||||
qs = QuoteSnapshot(venue_id="V1", asset="TEST/USD", bids=[[100.0, 10]], asks=[[100.5, 12]])
|
||||
assert qs.bids[0][0] == 100.0
|
||||
ss = SharedSignals(asset="TEST/USD", mid_price=100.25, liquidity=22)
|
||||
assert ss.asset == "TEST/USD"
|
||||
pd = PlanDelta(plan_id="p1", author="a1", asset="TEST/USD", venue_id="V1", side="buy", price=100.1, size=2)
|
||||
assert pd.plan_id == "p1"
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
from mercuriusmesh.simulator import DeterministicSimulator
|
||||
from mercuriusmesh.adapters import ToyVenueAdapter
|
||||
from mercuriusmesh.contracts import OrderEvent
|
||||
|
||||
|
||||
def test_deterministic_replay():
|
||||
sim = DeterministicSimulator()
|
||||
a = ToyVenueAdapter("V1", initial_bid=100.0, initial_ask=100.5)
|
||||
b = ToyVenueAdapter("V2", initial_bid=99.8, initial_ask=100.3)
|
||||
sim.add_adapter(a)
|
||||
sim.add_adapter(b)
|
||||
|
||||
o1 = OrderEvent(order_id="o1", venue_id="V1", asset="TEST/USD", side="buy", price=None, size=1)
|
||||
o2 = OrderEvent(order_id="o2", venue_id="V2", asset="TEST/USD", side="sell", price=None, size=2)
|
||||
|
||||
sim.submit_order(o1)
|
||||
sim.submit_order(o2)
|
||||
|
||||
# snapshot state
|
||||
s1 = sim.compute_shared_signals()
|
||||
|
||||
# replay and compare
|
||||
sim.replay()
|
||||
s2 = sim.compute_shared_signals()
|
||||
|
||||
assert s1.mid_price == s2.mid_price
|
||||
assert s1.liquidity == s2.liquidity
|
||||
Loading…
Reference in New Issue