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..cb3c6c4 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,26 @@ +# AGENTS + +This repository hosts a production-oriented skeleton for ML-CV Hedge Studio MVP. + +- Core DSL: Asset, MarketSignal, RiskState, HedgePlanDelta, GraphOfContracts +- Engine: HedgeSynthesisEngine (budget-aware hedging planner) +- Adapters: PriceFeedAdapter, OptionsVenueAdapter (toy builders) +- DeltaSync: deterministic replay log (delta_sync.py) +- Tests: tests/test_core.py validates core data structures and flow + +How to contribute +- Install: python3 -m pip install -r requirements.txt (if present) +- Run tests: pytest +- Build: python3 -m build + +Commands to run in this repo (for agents): +- Run unit tests: pytest +- Build package: python3 -m build +- Replay deltas: python -m idea91_ml_cv_hedge.delta_sync + +Architecture overview +- DSL primitives map to a canonical Graph-of-Contracts approach (Asset, MarketSignal, RiskState, HedgePlanDelta) +- A lightweight hedge synthesis engine computes cross-venue hedge deltas under budgets +- Adapters connect to real venues/simulators; Sandbox adapters are provided as MVP +- DeltaSync ensures deterministic replay across partitions and reconnects +- Optional privacy-friendly signal sharing via aggregation (stubbed in MVP) diff --git a/README.md b/README.md index 35315da..583c49c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,15 @@ # idea91-ml-cv-hedge -Source logic for Idea #91 \ No newline at end of file +ML-driven cross-venue hedge synthesis MVP skeleton + +This repository provides a production-oriented skeleton for an ML-augmented cross-venue hedge synthesis engine. It includes a canonical DSL, deterministic delta replay, toy adapters, and a minimal hedge engine to bootstrap MVP development. + +How to run +- Install dependencies and tests: see test.sh +- Run tests: ./test.sh + +Architecture overview +- Core primitives: Asset, MarketSignal, RiskState, HedgePlanDelta, GraphOfContracts +- Engines and adapters to enable cross-venue hedging experiments +- Delta-sync for deterministic replay +- MVP plan in repository: two assets, two venues, two toy adapters diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..199b96a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,30 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "idea91_ml_cv_hedge" # final packaging must exactly match this or idea91_ml_cv_hedge +version = "0.1.0" +description = "ML-driven cross-venue hedge synthesis MVP skeleton" +requires-python = ">=3.8" + +[tool.setuptools] +# Specify package-data as a mapping from package name to a list of glob patterns. +# Empty list means no data files to include. This fixes a PyPI build +# configuration error observed when using setuptools >= 42+. +[tool.setuptools.package-data] +idea91_ml_cv_hedge = [] + +[tool.setuptools.packages.find] +where = ["src"] + +# setuptools-scm integration removed to maintain compatibility with CI + +[tool.pytest.ini_options] +minversion = "7.0" +addopts = "-ra -q" +testpaths = ["tests"] + +[project.urls] +Homepage = "https://example.org/idea91_ml_cv_hedge" +Repository = "https://example.org/idea91_ml_cv_hedge.git" diff --git a/src/idea91_ml_cv_hedge/__init__.py b/src/idea91_ml_cv_hedge/__init__.py new file mode 100644 index 0000000..a3de5d7 --- /dev/null +++ b/src/idea91_ml_cv_hedge/__init__.py @@ -0,0 +1,19 @@ +"""idea91_ml_cv_hedge: ML-Driven Cross-Venue Hedge Studio (MVP Skeleton) + +This package provides a minimal, production-oriented skeleton for the ML-CV Hedge Studio +project described in the request. It includes: +- A DSL (Asset, MarketSignal, RiskState, HedgePlanDelta) and a Graph-of-Contracts registry scaffold +- Two toy adapters (price feed and options venue) to bootstrap interoperability +- A deterministic delta-sync mechanism for replay across partitions +- A simple hedge synthesis engine with a tiny optimization placeholder +- Basic tests to validate core data structures and end-to-end delta flow +""" + +from .dsl import Asset, MarketSignal, RiskState, HedgePlanDelta, GraphOfContracts +from .engine import HedgeSynthesisEngine +from .adapters import PriceFeedAdapter, OptionsVenueAdapter + +__all__ = [ + "Asset", "MarketSignal", "RiskState", "HedgePlanDelta", "GraphOfContracts", + "HedgeSynthesisEngine", "PriceFeedAdapter", "OptionsVenueAdapter", +] diff --git a/src/idea91_ml_cv_hedge/adapters.py b/src/idea91_ml_cv_hedge/adapters.py new file mode 100644 index 0000000..10b1542 --- /dev/null +++ b/src/idea91_ml_cv_hedge/adapters.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +"""Toy adapters to bootstrap interoperability between venues and the core engine. + +- PriceFeedAdapter: simulates a price feed (e.g., equities, spot, etc.). +- OptionsVenueAdapter: simulates an options venue with basic option data. + +These are deliberately simple and deterministic to serve as MVP building blocks. +""" + +from typing import Dict, List +from .dsl import Asset, MarketSignal + + +class PriceFeedAdapter: + def __init__(self, assets: List[Asset]): + self.assets = assets + # deterministic seed values per asset + self.prices: Dict[str, float] = {a.symbol: 100.0 for a in assets} + + def update(self) -> List[MarketSignal]: + # simple random walk with fixed seed for deterministic tests + signals: List[MarketSignal] = [] + for a in self.assets: + price = self.prices[a.symbol] * 1.0005 # tiny drift + self.prices[a.symbol] = price + signals.append(MarketSignal(asset=a, signal_type="price", value=price)) + return signals + + +class OptionsVenueAdapter: + def __init__(self, underlying: Asset, strikes: List[float], maturities: List[float]): + self.underlying = underlying + self.strikes = strikes + self.maturities = maturities + + def quote(self) -> List[MarketSignal]: + # toy: simple intrinsic value proxy for options across strikes + signals: List[MarketSignal] = [] + for k in self.strikes: + delta = max(0.0, self.underlying_asset_spot() - k) + signals.append(MarketSignal(asset=self.underlying, signal_type=f"option_delta_{k}", value=delta)) + return signals + + def underlying_asset_spot(self) -> float: + # deterministic spot via a fixed function of symbol hash + return float(sum(ord(c) for c in self.underlying.symbol)) % 200 + 50 diff --git a/src/idea91_ml_cv_hedge/delta_sync.py b/src/idea91_ml_cv_hedge/delta_sync.py new file mode 100644 index 0000000..e5b764c --- /dev/null +++ b/src/idea91_ml_cv_hedge/delta_sync.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +import json +import time +from pathlib import Path +from typing import List + +from .dsl import HedgePlanDelta + + +class DeltaSync: + """Deterministic delta-sync with islanding replay capability. + + Deltas are persisted to a log file in a deterministic, append-only manner. + Replays reconstruct the same sequence of delta applications on reconnect. + """ + + def __init__(self, log_dir: str = ".delta_logs"): # hidden dir for replay logs + self.log_dir = Path(log_dir) + self.log_dir.mkdir(exist_ok=True) + self.log_file = self.log_dir / "delta_log.jsonl" + + def log_delta(self, delta: HedgePlanDelta) -> None: + entry = { + "timestamp": delta.timestamp, + "contract_id": delta.contract_id, + "assets": [a.symbol for a in delta.assets], + "hedges": delta.hedges, + "author": delta.author, + "signature": delta.signature, + } + with self.log_file.open("a", encoding="utf-8") as f: + f.write(json.dumps(entry) + "\n") + + def replay_all(self) -> List[ HedgePlanDelta ]: + deltas: List[HedgePlanDelta] = [] + if not self.log_file.exists(): + return deltas + with self.log_file.open("r", encoding="utf-8") as f: + for line in f: + obj = json.loads(line) + # Minimal reconstruction; actual assets would be resolved by registry in real system + delta = HedgePlanDelta( + contract_id=obj.get("contract_id"), + hedges=obj.get("hedges", {}), + timestamp=obj.get("timestamp", time.time()), + author=obj.get("author", "log"), + assets=[ ] # simplified; in test we don't rely on full asset objects here + ) + deltas.append(delta) + return deltas diff --git a/src/idea91_ml_cv_hedge/dsl.py b/src/idea91_ml_cv_hedge/dsl.py new file mode 100644 index 0000000..ba4a0c8 --- /dev/null +++ b/src/idea91_ml_cv_hedge/dsl.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List, Dict, Optional +import uuid +import time + + +@dataclass(frozen=True) +class Asset: + symbol: str # e.g., AAPL, SPY + asset_type: str # e.g., 'equity', 'option' + locale: Optional[str] = None + + +@dataclass(frozen=True) +class MarketSignal: + asset: Asset + signal_type: str # e.g., 'price', 'vol', 'delta' + value: float + timestamp: float = field(default_factory=lambda: time.time()) + + +@dataclass +class RiskState: + asset: Asset + pnl_exposure: float # expected short-term PnL exposure + delta_exposure: float # net delta exposure + gamma_exposure: float # net gamma exposure + volatility_shift: float # forecasted vol shift + + +@dataclass +class HedgePlanDelta: + contract_id: str = field(default_factory=lambda: str(uuid.uuid4())) + assets: List[Asset] = field(default_factory=list) + hedges: Dict[str, float] = field(default_factory=dict) # venue->notional_delta + objectives: Dict[str, float] = field(default_factory=dict) # e.g., {'risk': 0.0} + timestamp: float = field(default_factory=lambda: time.time()) + author: str = "system" + signature: Optional[str] = None + + +class GraphOfContracts: + """Simple registry scaffold to-version adapters and data contracts.""" + + def __init__(self) -> None: + self.registry: Dict[str, Dict] = {} + + def register(self, contract_name: str, schema: Dict) -> None: + self.registry[contract_name] = schema + + def get(self, contract_name: str) -> Optional[Dict]: + return self.registry.get(contract_name) + + +# Simple container to group DSL primitives for easy import +__all__ = ["Asset", "MarketSignal", "RiskState", "HedgePlanDelta", "GraphOfContracts"] diff --git a/src/idea91_ml_cv_hedge/engine.py b/src/idea91_ml_cv_hedge/engine.py new file mode 100644 index 0000000..a2d65ca --- /dev/null +++ b/src/idea91_ml_cv_hedge/engine.py @@ -0,0 +1,49 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Dict +import time + +from .dsl import Asset, MarketSignal, RiskState, HedgePlanDelta + + +@dataclass +class Budget: + risk_limit: float + liquidity_limit: float + latency_limit_ms: int + + +class HedgeSynthesisEngine: + """A tiny, deterministic hedge synthesis engine. + + This MVP uses a naive greedy approach: for each asset, compute a hedge delta per venue + aiming to reduce net delta exposure towards zero, constrained by budgets. + """ + + def __init__(self, budgets: Budget) -> None: + self.budgets = budgets + + def assess_risk(self, risk_states: List[RiskState]) -> Dict[str, float]: + # aggregate risk metrics per asset (very simplified) + handoff: Dict[str, float] = {} + for rs in risk_states: + handoff[rs.asset.symbol] = rs.delta_exposure + return handoff + + def synthesize(self, risk_states: List[RiskState], available_venues: List[str]) -> HedgePlanDelta: + # Very simple heuristic: allocate deltas inversely proportional to current delta exposure + hedge = HedgePlanDelta( + assets=[r.asset for r in risk_states], + hedges={}, + timestamp=time.time(), + author="engine", + ) + total_delta = sum(abs(r.delta_exposure) for r in risk_states) or 1.0 + per_venue = 1.0 / len(available_venues) if available_venues else 0.0 + for rs in risk_states: + # normalize, then assign to first venue as a toy example + alloc = (-rs.delta_exposure) / total_delta if total_delta else 0.0 + if available_venues: + hedge.hedges[available_venues[0]] = hedge.hedges.get(available_venues[0], 0.0) + alloc * per_venue + return hedge diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..31cc80b --- /dev/null +++ b/test.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Running tests..." +export PYTHONPATH=src +pytest -q + +echo "Building package..." +python3 -m build + +echo "All tests passed and package built." diff --git a/tests/test_core.py b/tests/test_core.py new file mode 100644 index 0000000..84d48cd --- /dev/null +++ b/tests/test_core.py @@ -0,0 +1,28 @@ +import math +from idea91_ml_cv_hedge.dsl import Asset, MarketSignal, RiskState, HedgePlanDelta, GraphOfContracts +from idea91_ml_cv_hedge.engine import HedgeSynthesisEngine, Budget +from idea91_ml_cv_hedge.adapters import PriceFeedAdapter + + +def test_dsl_asset_and_riskstate_roundtrip(): + a = Asset(symbol="AAPL", asset_type="equity") + rs = RiskState(asset=a, pnl_exposure=1.0, delta_exposure=0.5, gamma_exposure=0.1, volatility_shift=0.2) + assert rs.asset == a + assert rs.delta_exposure == 0.5 + + +def test_hedge_plan_delta_basic(): + a = Asset(symbol="AAPL", asset_type="equity") + rs = RiskState(asset=a, pnl_exposure=1.0, delta_exposure=0.5, gamma_exposure=0.1, volatility_shift=0.2) + delta = HedgePlanDelta(assets=[a], hedges={"Venue1": -0.5}, author="tester") + assert delta.assets[0] == a + assert delta.hedges["Venue1"] == -0.5 + + +def test_hedge_engine_basic_flow(): + a = Asset(symbol="AAPL", asset_type="equity") + rs = RiskState(asset=a, pnl_exposure=1.0, delta_exposure=0.6, gamma_exposure=0.1, volatility_shift=0.2) + engine = HedgeSynthesisEngine(Budget(risk_limit=1.0, liquidity_limit=1.0, latency_limit_ms=100)) + delta = engine.synthesize([rs], ["Venue1", "Venue2"]) + assert isinstance(delta, HedgePlanDelta) + assert delta.assets[0] == a