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..87ab35d --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,17 @@ +# DeltaForge MVP: Architectural Guide + +Overview +- Purpose: A minimal, auditable cross-venue hedging engine for two assets across two venues. +- Scope: Core DSL sketches, two starter adapters, a tiny central curator, and a toy backtester. + +Architecture sketch +- DSL: StrategyDelta, Asset, MarketSignal, PlanDelta (data classes with simple validation). +- Adapters: canonical signals from venue data translated to StrategyDelta objects. +- Coordinating layer: local risk solvers per venue + a central curator that enforces cross-venue constraints via aggregated signals (ADMM-lite style). +- Execution adapter: routes to venues with latency/fee metadata in the plan delta. +- Backtester: deterministic replay engine. +- Governance: tamper-evident logs; cryptographic tag placeholders. + +How to contribution +- Run tests via test.sh; ensure deterministic behavior. +- Extend with new adapters, new assets, or richer DSL primitives. diff --git a/README.md b/README.md index 8739fb3..8512d57 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,32 @@ # deltaforge-real-time-cross-asset-strateg -A novel, open-source engine that automatically synthesizes, validates, and executes hedging and arbitrage strategies across options, equities, and futures across multiple venues with ultra-low latency. It provides a concise DSL to declare strategy ob \ No newline at end of file +DeltaForge MVP: Minimal, open-source engine for real-time cross-asset hedging across options and equities across two venues. + +Overview +- A compact DSL to declare strategy objectives (risk budgets, PnL targets, liquidity constraints). +- Two starter adapters (equity feed and options market data). +- Lightweight, two-venue risk solvers with a central curator for cross-venue constraints. +- Minimal deterministic backtester and a toy execution adapter. +- MVP runs in a small two-asset, two-venue scenario demonstrating delta-hedges and cross-venue spreads. + +What’s included +- Python package deltaforge_mvp with core DSL scaffolds and demo flow. +- Adapters: deltaforge_mvp/adapters/equity_feed.py, deltaforge_mvp/adapters/options_feed.py +- Venue coordination: deltaforge_mvp/coordination.py (ADMM-lite style coordination skeleton) +- Backtester: deltaforge_mvp/backtester.py +- Demo: deltaforge_mvp/demo.py (orchestrates a simple delta hedge across two venues) +- Tests: tests/test_demo.py +- Test script: test.sh +- Metadata: AGENTS.md (architecture guide) and READY_TO_PUBLISH placeholder + +How to run (locally) +- Build and install: python -m build +- Run demo: python -m deltaforge_mvp.demo +- Run tests: bash test.sh + +Note: This is a minimal, initial MVP skeleton intended to bootstrap discussion and future extension. It focuses on the core concepts with simple deterministic flows. + +Usage notes: +- The MVP uses a tiny Python package deltaforge_mvp with a demo orchestrator and two starter adapters. +- To run the demo: python -m deltaforge_mvp.demo +- To execute tests: bash test.sh diff --git a/deltaforge_mvp/__init__.py b/deltaforge_mvp/__init__.py new file mode 100644 index 0000000..09bcbd0 --- /dev/null +++ b/deltaforge_mvp/__init__.py @@ -0,0 +1,5 @@ +"""DeltaForge MVP package init. +Expose lightweight APIs for a tiny cross-venue hedging engine scaffold. +""" + +from .core import StrategyDelta, Asset, MarketSignal, PlanDelta # re-export for convenience diff --git a/deltaforge_mvp/adapters/equity_feed.py b/deltaforge_mvp/adapters/equity_feed.py new file mode 100644 index 0000000..dc78f4f --- /dev/null +++ b/deltaforge_mvp/adapters/equity_feed.py @@ -0,0 +1,10 @@ +"""Starter equity feed adapter: emits simple price signals for an equity.""" +from __future__ import annotations +import time + +from deltaforge_mvp.core import Asset, MarketSignal + + +def generate_signal(symbol: str, price: float) -> MarketSignal: + asset = Asset(type="equity", symbol=symbol) + return MarketSignal(asset=asset, price=price, volatility=0.2, liquidity=1.0, timestamp=time.time()) diff --git a/deltaforge_mvp/adapters/options_feed.py b/deltaforge_mvp/adapters/options_feed.py new file mode 100644 index 0000000..b7d0c4e --- /dev/null +++ b/deltaforge_mvp/adapters/options_feed.py @@ -0,0 +1,14 @@ +"""Starter options feed adapter: emits option market signals.""" +from __future__ import annotations +import time + +from deltaforge_mvp.core import Asset, MarketSignal + + +def create_option_symbol(underlying: str, strike: float, expiry: str) -> Asset: + return Asset(type="option", underlying=underlying, strike=strike, expires=expiry) + + +def generate_signal(underlying: str, strike: float, expiry: str, price: float) -> MarketSignal: + asset = Asset(type="option", underlying=underlying, strike=strike, expires=expiry) + return MarketSignal(asset=asset, price=price, volatility=0.25, liquidity=0.8, timestamp=time.time()) diff --git a/deltaforge_mvp/backtester.py b/deltaforge_mvp/backtester.py new file mode 100644 index 0000000..e53f29f --- /dev/null +++ b/deltaforge_mvp/backtester.py @@ -0,0 +1,18 @@ +"""Deterministic replay backtester (toy) for MVP.""" +from __future__ import annotations + +from typing import List + +from deltaforge_mvp.core import PlanDelta + + +class Backtester: + def __init__(self, seed: int = 0): + self.seed = seed + + def replay(self, plan: PlanDelta) -> dict: + # Very small deterministic stub: compute a fake PnL based on number of deltas and a seed + pnl = 0.0 + for d in plan.deltas: + pnl += (d.delta * 0.5) # arbitrary scaling for demo + return {"pnl": pnl, "delta_count": len(plan.deltas), "seed": self.seed} diff --git a/deltaforge_mvp/coordination.py b/deltaforge_mvp/coordination.py new file mode 100644 index 0000000..e39d351 --- /dev/null +++ b/deltaforge_mvp/coordination.py @@ -0,0 +1,29 @@ +"""ADMM-lite style coordination skeleton for cross-venue coherence.""" +from __future__ import annotations + +from typing import List + +from deltaforge_mvp.core import PlanDelta, StrategyDelta + + +class LocalRiskSolver: + def __init__(self, venue_name: str): + self.venue_name = venue_name + + def propose(self, signals: List[StrategyDelta]) -> PlanDelta: + # Minimal heuristic: aggregate deltas and propose a single PlanDelta per venue + # In real system this would solve a convex program; here we pass through the deltas. + pd = PlanDelta(deltas=signals, venue=self.venue_name, author="local-solver") + return pd + + +class CentralCurator: + def __init__(self): + pass + + def enforce(self, plans: List[PlanDelta]) -> PlanDelta: + # Naive: merge all deltas into a single PlanDelta with combined deltas + merged = [] + for p in plans: + merged.extend(p.deltas) + return PlanDelta(deltas=merged, venue=None, author="curator") diff --git a/deltaforge_mvp/core.py b/deltaforge_mvp/core.py new file mode 100644 index 0000000..948eb7e --- /dev/null +++ b/deltaforge_mvp/core.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import List, Optional + + +@dataclass +class Asset: + """Canonical asset representation. + Example: {"type": "equity", "symbol": "AAPL"} or {"type": "option", "underlying": "AAPL", "strike": 150, "expires": "2026-12-17"} + """ + + type: str # 'equity', 'option', 'future' + symbol: Optional[str] = None + underlying: Optional[str] = None + strike: Optional[float] = None + expires: Optional[str] = None + + def canonical_id(self) -> str: + if self.type == "equity": + return f"EQ:{self.symbol}" + if self.type == "option": + return f"OP:{self.underlying}:{self.strike}:{self.expires}" + if self.type == "future": + return f"FU:{self.symbol or self.underlying}:{self.expires}" + return f"UNK:{self.symbol or 'UNDEF'}" + + +@dataclass +class MarketSignal: + """Lightweight market signal used by adapters to convey prices, liquidity, etc.""" + asset: Asset + price: float + volatility: float = 0.0 + liquidity: float = 1.0 + timestamp: float = 0.0 + + +@dataclass +class StrategyDelta: + """Local decision block; describes intent to adjust hedges for an asset. + This is a light DSL-like structure that adapters translate into venue orders. + """ + asset: Asset + delta: float # directional hedge to apply (positive means buy delta exposure, etc.) + vega: float = 0.0 + gamma: float = 0.0 + target_pnl: Optional[float] = None + max_order_size: float = 1.0 + timestamp: float = 0.0 + + +@dataclass +class PlanDelta: + """Incremental hedges/adjustments with metadata for auditability.""" + deltas: List[StrategyDelta] + confidence: float = 1.0 + venue: Optional[str] = None + author: str = "system" + timestamp: float = 0.0 + signature: Optional[str] = None # placeholder for cryptographic tag diff --git a/deltaforge_mvp/demo.py b/deltaforge_mvp/demo.py new file mode 100644 index 0000000..2c1a568 --- /dev/null +++ b/deltaforge_mvp/demo.py @@ -0,0 +1,44 @@ +"""Minimal demonstration orchestrating two venues with a delta hedge.""" +from __future__ import annotations +import time + +from deltaforge_mvp.core import Asset, MarketSignal, StrategyDelta, PlanDelta +from deltaforge_mvp.adapters.equity_feed import generate_signal as equity_signal +from deltaforge_mvp.adapters.options_feed import generate_signal as option_signal +from deltaforge_mvp.coordination import LocalRiskSolver, CentralCurator +from deltaforge_mvp.backtester import Backtester + + +def main(): + # Define two assets: one equity, one option (simplified) + eq = Asset(type="equity", symbol="AAPL") + opt = Asset(type="option", underlying="AAPL", strike=150, expires="2026-12-17") + + # Generate simple signals from two adapters (toy values) + s1 = MarketSignal(asset=eq, price=180.0, volatility=0.2, liquidity=1.0, timestamp=time.time()) + s2 = option_signal(underlying="AAPL", strike=150, expiry="2026-12-17", price=5.0) + + # Local hedging decisions (tiny delta values for demo) + d1 = StrategyDelta(asset=eq, delta=0.5, timestamp=time.time()) + d2 = StrategyDelta(asset=opt, delta=-0.3, timestamp=time.time()) + + # Venue solvers + solver_a = LocalRiskSolver("Venue-A") + solver_b = LocalRiskSolver("Venue-B") + plan_a = solver_a.propose([d1]) + plan_b = solver_b.propose([d2]) + + curator = CentralCurator() + merged_plan = curator.enforce([plan_a, plan_b]) + + backtester = Backtester(seed=42) + results = backtester.replay(merged_plan) + + print("DeltaForge MVP Demo Results:") + print(results) + + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..eaee05c --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["setuptools>=42", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "deltaforge-mvp" +version = "0.1.0" +description = "Minimal MVP scaffolding for DeltaForge cross-venue hedgingEngine" +readme = "README.md" +requires-python = ">=3.8" +license = {text = "MIT"} +authors = [ { name = "DeltaForge MVP" } ] +dependencies = ["numpy"] diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..e6dfd6f --- /dev/null +++ b/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Running Python build and tests for DeltaForge MVP..." +python -m build >/dev/null 2>&1 || { echo "[build] failed"; exit 1; } +pytest -q || { echo "[tests] failed"; exit 1; } +echo "All tests passed." diff --git a/tests/test_demo.py b/tests/test_demo.py new file mode 100644 index 0000000..582bdc9 --- /dev/null +++ b/tests/test_demo.py @@ -0,0 +1,28 @@ +import pytest +import sys +import os + +# Robust import in case pytest runs with a constrained PYTHONPATH +try: + from deltaforge_mvp.demo import main +except Exception: + # Fallback: load module directly from repo path + import importlib.util + import pathlib + repo_root = pathlib.Path(__file__).parents[1].resolve() + # Ensure repo root is on sys.path for dynamic import fallback + if str(repo_root) not in sys.path: + sys.path.insert(0, str(repo_root)) + demo_path = repo_root / 'deltaforge_mvp' / 'demo.py' + spec = importlib.util.spec_from_file_location("deltaforge_mvp.demo", str(demo_path)) + if spec is None or spec.loader is None: + raise ImportError("Could not load deltaforge_mvp.demo fallback module") + mod = importlib.util.module_from_spec(spec) + spec.loader.exec_module(mod) # type: ignore + main = mod.main + + +def test_demo_runs(): + # Ensure the demo runs without raising + ret = main() + assert ret == 0