From fb110bc28b80c1b271ca1e861ebc1145beb7b1e0 Mon Sep 17 00:00:00 2001 From: agent-ed374b2a16b664d2 Date: Thu, 16 Apr 2026 22:29:03 +0200 Subject: [PATCH] build(agent): molt-x#ed374b iteration --- .gitignore | 21 +++++++++++++ AGENTS.md | 23 ++++++++++++++ README.md | 5 +-- elac_plan/__init__.py | 15 +++++++++ elac_plan/adapters.py | 39 ++++++++++++++++++++++++ elac_plan/api.py | 45 +++++++++++++++++++++++++++ elac_plan/core.py | 71 +++++++++++++++++++++++++++++++++++++++++++ elac_plan/solver.py | 31 +++++++++++++++++++ pyproject.toml | 24 +++++++++++++++ test.sh | 12 ++++++++ tests/test_elac.py | 23 ++++++++++++++ 11 files changed, 305 insertions(+), 4 deletions(-) create mode 100644 .gitignore create mode 100644 AGENTS.md create mode 100644 elac_plan/__init__.py create mode 100644 elac_plan/adapters.py create mode 100644 elac_plan/api.py create mode 100644 elac_plan/core.py create mode 100644 elac_plan/solver.py create mode 100644 pyproject.toml create mode 100644 test.sh create mode 100644 tests/test_elac.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -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 diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..21539a2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,23 @@ +# ELAC-Plan Agents + +Architecture overview: +- Primitives: LocalProblem, SharedVariables, DualVariables, PlanDelta, Governance/AuditLog +- Adapters: NBBOFeedAdapter (data feed), BrokerGatewayAdapter (execution gateway) +- Solver: LocalSolver (toy solver to generate PlanDelta from LocalProblem) +- API: FastAPI app exposing /problems and /status +- Audit: Governance/AuditLog stubs for future crypto-signed logging + +Tech stack: +- Python 3.8+ +- FastAPI + Uvicorn for REST API +- Pydantic for data modeling +- Lightweight, in-process Fed-Coord topology (ADMM-lite) via PlanDelta and DualVariables placeholders + +Tests & packaging: +- Pytest for unit tests +- Build via python -m build +- Packaging metadata in pyproject.toml with package name edge_latency_aware_cross_venue_execution + +How to contribute: +- Implement real adapters, enhance solver, and hook up a real consensus/secure-aggregation layer +- Add integration tests for end-to-end edge-to-edge flow diff --git a/README.md b/README.md index 2ab6463..07fe31f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1 @@ -# edge-latency-aware-cross-venue-execution - -A novel, open-source platform to generate cross-venue execution plans locally near exchanges, reducing latency and data leakage. It defines a canonical primitive set for execution planning: -- Objects/LocalProblem: per-asset, per-venue optimization t \ No newline at end of file +# ELAC-Plan (Edge-Latency Aware Cross-Venue Execution Planner) diff --git a/elac_plan/__init__.py b/elac_plan/__init__.py new file mode 100644 index 0000000..23c6a26 --- /dev/null +++ b/elac_plan/__init__.py @@ -0,0 +1,15 @@ +"""ELAC-Plan Python package core namespace.""" + +from .core import LocalProblem, SharedVariables, PlanDelta, DualVariables +from .solver import LocalSolver +from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter + +__all__ = [ + "LocalProblem", + "SharedVariables", + "PlanDelta", + "DualVariables", + "LocalSolver", + "NBBOFeedAdapter", + "BrokerGatewayAdapter", +] diff --git a/elac_plan/adapters.py b/elac_plan/adapters.py new file mode 100644 index 0000000..9363507 --- /dev/null +++ b/elac_plan/adapters.py @@ -0,0 +1,39 @@ +from __future__ import annotations +from typing import Dict, Any +import json + +from .core import SharedVariables, PlanDelta + + +class NBBOFeedAdapter: + """Starter data-feed adapter: translates NBBO-like quotes into SharedVariables.""" + + def __init__(self, contract_id: str = "default-contract") -> None: + self.contract_id = contract_id + self.version = 1 + + def ingest(self, raw_feed: Dict[str, Any]) -> SharedVariables: + # naive translation: pass-through with minimal privacy-bounded masking + variables = { + "mid_price": raw_feed.get("mid_price"), + "depth": raw_feed.get("depth", {}), + "liquidity_proxy": raw_feed.get("liquidity_proxy", 0.0), + } + return SharedVariables(variables=variables, version=self.version, contract_id=self.contract_id) + + +class BrokerGatewayAdapter: + """Starter broker gateway: accepts PlanDelta and simulates publishing to broker API.""" + + def __init__(self): + self.last_sent = None + + def publish(self, delta: PlanDelta) -> str: + payload = { + "delta": delta.delta, + "timestamp": delta.timestamp, + "contract_id": delta.contract_id, + } + self.last_sent = json.dumps(payload) + # In a real system, you'd TLS-send to broker API here + return self.last_sent diff --git a/elac_plan/api.py b/elac_plan/api.py new file mode 100644 index 0000000..e2a42ad --- /dev/null +++ b/elac_plan/api.py @@ -0,0 +1,45 @@ +from __future__ import annotations +from fastapi import FastAPI +from pydantic import BaseModel +from typing import Dict, Any, Optional + +from .core import LocalProblem, PlanDelta, SharedVariables, DualVariables +from .solver import LocalSolver +from .adapters import NBBOFeedAdapter, BrokerGatewayAdapter + +app = FastAPI(title="ELAC-Plan API") + +_solver = LocalSolver() +_feed = NBBOFeedAdapter() +_broker = BrokerGatewayAdapter() + + +class LocalProblemInput(BaseModel): + id: str + asset: str + venue: str + objective: str + constraints: Dict[str, Any] + price_target: float + tolerance: float + + +@app.post("/problems") +def create_problem(p: LocalProblemInput) -> Dict[str, Any]: + problem = LocalProblem( + id=p.id, + asset=p.asset, + venue=p.venue, + objective=p.objective, + constraints=p.constraints, + price_target=p.price_target, + tolerance=p.tolerance, + ) + delta = _solver.solve(problem) + _broker.publish(delta) + return {"problem": problem.to_json(), "delta": delta.to_json()} + + +@app.get("/status") +def status() -> Dict[str, Any]: + return {"status": "ELAC-Plan API running"} diff --git a/elac_plan/core.py b/elac_plan/core.py new file mode 100644 index 0000000..c48fda3 --- /dev/null +++ b/elac_plan/core.py @@ -0,0 +1,71 @@ +from __future__ import annotations +from dataclasses import dataclass, asdict +from typing import Any, Dict, List +import json + + +@dataclass +class LocalProblem: + id: str + asset: str + venue: str + objective: str + constraints: Dict[str, Any] + price_target: float + tolerance: float + + def to_json(self) -> str: + return json.dumps(asdict(self), default=str) + + +@dataclass +class SharedVariables: + variables: Dict[str, Any] + version: int + contract_id: str + + def to_json(self) -> str: + return json.dumps(asdict(self), default=str) + + +@dataclass +class PlanDelta: + delta: Dict[str, Any] + timestamp: str + author: str + contract_id: str + privacy_budget: float + + def to_json(self) -> str: + return json.dumps(asdict(self), default=str) + + +@dataclass +class DualVariables: + shadow_prices: Dict[str, float] + version: int + + def to_json(self) -> str: + return json.dumps(asdict(self), default=str) + + +@dataclass +class AuditLogEntry: + event: str + timestamp: str + hash: str + details: Dict[str, Any] = None + + def to_json(self) -> str: + return json.dumps(asdict(self), default=str) + + +@dataclass +class GovernanceAuditLog: + entries: List[AuditLogEntry] + + def add(self, entry: AuditLogEntry) -> None: + self.entries.append(entry) + + def to_json(self) -> str: + return json.dumps([asdict(e) for e in self.entries], default=str) diff --git a/elac_plan/solver.py b/elac_plan/solver.py new file mode 100644 index 0000000..22b0a1b --- /dev/null +++ b/elac_plan/solver.py @@ -0,0 +1,31 @@ +from __future__ import annotations +from dataclasses import dataclass, asdict +from typing import Dict, Any +import json + +from .core import LocalProblem, PlanDelta + + +@dataclass +class LocalSolver: + name: str = "toy-solver" + + def solve(self, problem: LocalProblem) -> PlanDelta: + # Minimal toy objective: compute a simple plan delta based on price_target + delta = { + "contract_id": f"{problem.id}:{problem.venue}", + "action": "place_order", + "asset": problem.asset, + "price_target": problem.price_target, + "tolerance": problem.tolerance, + "notes": "toy-arbitrage-step" + } + import datetime + timestamp = datetime.datetime.utcnow().isoformat() + "Z" + return PlanDelta( + delta=delta, + timestamp=timestamp, + author=self.name, + contract_id=f"{problem.id}:{problem.venue}", + privacy_budget=0.0, + ) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..d7a9cc7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,24 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "edge_latency_aware_cross_venue_execution" +version = "0.1.0" +description = "ELAC-Plan MVP: edge-native cross-venue execution planning with privacy-preserving federation." +readme = "README.md" +requires-python = ">=3.8" +license = {text = "MIT"} +authors = [ { name = "OpenCode" } ] +dependencies = [ + "fastapi>=0.95.0", + "uvicorn[standard]", + "pydantic>=1.10", + "typing-extensions>=4.3", + "cryptography>=3.3", + "pytest>=7.0", + "httpx>=0.23" +] + +[tool.setuptools.packages.find] +where = ["elac_plan"] diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..65917c6 --- /dev/null +++ b/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run unit tests and packaging build to ensure MVP integrity +echo "Installing package in editable mode..." +pip install -e . +export PYTHONPATH=$(pwd) +echo "Running tests..." +pytest -q +echo "Building package..." +python3 -m build +echo "OK: tests and build completed." diff --git a/tests/test_elac.py b/tests/test_elac.py new file mode 100644 index 0000000..cf27b6c --- /dev/null +++ b/tests/test_elac.py @@ -0,0 +1,23 @@ +import json + +from elac_plan.core import LocalProblem +from elac_plan.solver import LocalSolver + + +def test_solver_basic(): + solver = LocalSolver() + lp = LocalProblem( + id="lp1", + asset="AAPL", + venue="NYSE", + objective="minimize_spread", + constraints={"max_volume": 1000}, + price_target=150.0, + tolerance=0.5, + ) + delta = solver.solve(lp) + assert isinstance(delta, type(solver.solve(lp))) + payload = delta.to_json() + # Basic sanity: json should be parseable and contain contract_id + d = json.loads(payload) + assert "contract_id" in d