diff --git a/README.md b/README.md index 3e2b11f..32afee5 100644 --- a/README.md +++ b/README.md @@ -1 +1,20 @@ -# DeltaForge MVP: Real-Time Cross-Asset Strategy Synthesis (Skeleton) +# DeltaForge Skeleton MVP + +A minimal, self-contained Python package that demonstrates the core primitives +and a deterministic flow for cross-venue hedging concepts. This skeleton is +designed to be extended with real adapters and a full ADMM-like coordination layer +while providing a concrete starting point for testing, packaging, and integration. + +What this adds +- Core DSL primitives: Asset, MarketSignal, StrategyDelta, PlanDelta (in deltaforge_skeleton.core) +- Two starter adapters (equity_feed and options_feed) that generate MarketSignal instances +- Simple Curator that synthesizes a PlanDelta from signals +- Lightweight ExecutionEngine to route plan steps across venues +- Toy Backtester to deterministically replay a plan +- Packaging metadata via pyproject.toml and a README for distribution + +Usage notes +- Import deltaforge_skeleton and instantiate the flow using the included components +- Run the test harness in test.sh to verify the wiring and deterministic behavior + +This is a skeleton intended for extension; it is not a production-ready trading engine. diff --git a/deltaforge_skeleton/__init__.py b/deltaforge_skeleton/__init__.py new file mode 100644 index 0000000..1fd4520 --- /dev/null +++ b/deltaforge_skeleton/__init__.py @@ -0,0 +1,25 @@ +"""DeltaForge Skeletal MVP + +A minimal, production-friendly Python package skeleton that implements a +canonical DSL surface and a toy but deterministic cross-venue workflow for +testing and integration with adapters. +""" + +from .core import Asset, MarketSignal, StrategyDelta, PlanDelta +from .adapters.equity_feed import EquityFeedAdapter +from .adapters.options_feed import OptionsFeedAdapter +from .curator import Curator +from .execution import ExecutionEngine +from .backtester import Backtester + +__all__ = [ + "Asset", + "MarketSignal", + "StrategyDelta", + "PlanDelta", + "EquityFeedAdapter", + "OptionsFeedAdapter", + "Curator", + "ExecutionEngine", + "Backtester", +] diff --git a/deltaforge_skeleton/adapters/__init__.py b/deltaforge_skeleton/adapters/__init__.py new file mode 100644 index 0000000..9060dbb --- /dev/null +++ b/deltaforge_skeleton/adapters/__init__.py @@ -0,0 +1,6 @@ +"""Adapters package""" + +from .equity_feed import EquityFeedAdapter +from .options_feed import OptionsFeedAdapter + +__all__ = ["EquityFeedAdapter", "OptionsFeedAdapter"] diff --git a/deltaforge_skeleton/adapters/equity_feed.py b/deltaforge_skeleton/adapters/equity_feed.py new file mode 100644 index 0000000..0ed035d --- /dev/null +++ b/deltaforge_skeleton/adapters/equity_feed.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from dataclasses import dataclass +from deltaforge_skeleton.core import MarketSignal, Asset + + +@dataclass +class EquityFeedAdapter: + name: str = "EquityFeed" + + def synthesize(self, symbol: str, price: float, timestamp: float) -> MarketSignal: + asset = Asset(symbol=symbol, asset_type="equity") + return MarketSignal(asset=asset, price=price, timestamp=timestamp, liquidity=1.0) diff --git a/deltaforge_skeleton/adapters/options_feed.py b/deltaforge_skeleton/adapters/options_feed.py new file mode 100644 index 0000000..24fc61d --- /dev/null +++ b/deltaforge_skeleton/adapters/options_feed.py @@ -0,0 +1,13 @@ +from __future__ import annotations + +from dataclasses import dataclass +from deltaforge_skeleton.core import MarketSignal, Asset + + +@dataclass +class OptionsFeedAdapter: + name: str = "OptionsFeed" + + def synthesize(self, symbol: str, price: float, timestamp: float) -> MarketSignal: + asset = Asset(symbol=symbol, asset_type="option") + return MarketSignal(asset=asset, price=price, timestamp=timestamp, liquidity=0.8) diff --git a/deltaforge_skeleton/backtester.py b/deltaforge_skeleton/backtester.py new file mode 100644 index 0000000..a11aab5 --- /dev/null +++ b/deltaforge_skeleton/backtester.py @@ -0,0 +1,11 @@ +from __future__ import annotations + +from deltaforge_skeleton.core import PlanDelta + + +class Backtester: + def replay(self, plan: PlanDelta) -> str: + # Deterministic replay: encode steps into a single string log + if not plan.steps: + return "empty plan" + return " | ".join(plan.steps) diff --git a/deltaforge_skeleton/core.py b/deltaforge_skeleton/core.py new file mode 100644 index 0000000..4dd79c3 --- /dev/null +++ b/deltaforge_skeleton/core.py @@ -0,0 +1,56 @@ +from __future__ import annotations + +from dataclasses import dataclass, field +from typing import List, Optional + + +@dataclass +class Asset: + symbol: str + asset_type: str = "equity" # or option, future, etc. + + +@dataclass +class MarketSignal: + asset: Asset + price: float + timestamp: float + liquidity: float = 1.0 + implied_vol: Optional[float] = None + + +@dataclass +class StrategyDelta: + asset: Asset + hedge_ratio: float + target_pnl: float + constraints: Optional[List[str]] = field(default_factory=list) + + +@dataclass +class PlanDelta: + steps: List[str] = field(default_factory=list) + timestamp: float = 0.0 + provenance: Optional[str] = None + + +# Lightweight placeholders for governance primitives +@dataclass +class DualVariables: + values: List[float] = field(default_factory=list) + + +@dataclass +class PrivacyBudget: + budget: float = 0.0 + + +@dataclass +class AuditLog: + entries: List[str] = field(default_factory=list) + + +@dataclass +class PolicyBlock: + name: str + rules: List[str] = field(default_factory=list) diff --git a/deltaforge_skeleton/curator.py b/deltaforge_skeleton/curator.py new file mode 100644 index 0000000..7c3c974 --- /dev/null +++ b/deltaforge_skeleton/curator.py @@ -0,0 +1,22 @@ +from __future__ import annotations + +from typing import List +from deltaforge_skeleton.core import MarketSignal, PlanDelta, StrategyDelta, Asset + + +class Curator: + def __init__(self): + self.audit = [] + + def synthesize(self, signals: List[MarketSignal]) -> PlanDelta: + # Naive delta-synthesis: for each asset, create a delta hedge with 1x price weight + steps = [] + for s in signals: + asset = s.asset + # simple heuristic: hedge ratio proportional to liquidity and inverse of price + hedge_ratio = max(0.0, min(1.0, 1.0 * (s.liquidity / max(1.0, s.price)))) + obj = StrategyDelta(asset=asset, hedge_ratio=hedge_ratio, target_pnl=0.0, constraints=[]) + steps.append(f"HEDGE {asset.symbol} with ratio {hedge_ratio:.3f}") + plan = PlanDelta(steps=steps, timestamp=0.0, provenance="deltaforge-skeleton-curation") + self.audit.append("synthesized plan from signals") + return plan diff --git a/deltaforge_skeleton/execution.py b/deltaforge_skeleton/execution.py new file mode 100644 index 0000000..e82ce44 --- /dev/null +++ b/deltaforge_skeleton/execution.py @@ -0,0 +1,19 @@ +from __future__ import annotations + +from typing import List +from deltaforge_skeleton.core import PlanDelta + + +class ExecutionEngine: + def __init__(self): + self.log: List[str] = [] + + def route(self, plan: PlanDelta) -> List[str]: + # Very small mock routing: just annotate steps with a venue tag + routed = [] + for i, step in enumerate(plan.steps or []): + venue = "VenueA" if i % 2 == 0 else "VenueB" + action = f"{step} -> {venue}" + routed.append(action) + self.log.append("routed plan across venues") + return routed diff --git a/pyproject.toml b/pyproject.toml index 78896d3..82f01cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,13 +1,19 @@ [build-system] -requires = ["setuptools>=42", "wheel"] +requires = ["setuptools>=61.0"] build-backend = "setuptools.build_meta" [project] name = "deltaforge-skeleton" -version = "0.1.0" -description = "MVP skeleton for DeltaForge real-time cross-asset strategy synthesis" -readme = "README.md" +version = "0.0.1" +description = "DeltaForge MVP skeleton: core DSL, adapters, curator, execution, and backtester" +authors = [{name = "OpenCode"}] requires-python = ">=3.8" -license = { text = "MIT" } -authors = [ { name = "OpenCode AI" } ] -dependencies = [] +readme = "README.md" + +[tool.setuptools.packages.find] +where = ["."] +# Include all deltaforge_skeleton packages that live at the project root or subpackages. +include = ["deltaforge_skeleton*"] + +# Optionally enable data files to be included in the package build +# Note: Avoid top-level [tool.setuptools] configuration that sets include_package_data here. diff --git a/test.sh b/test.sh index 306cd38..b328005 100644 --- a/test.sh +++ b/test.sh @@ -1,36 +1,38 @@ #!/usr/bin/env bash set -euo pipefail -echo "Running DeltaForge MVP tests..." -python3 -m build >/dev/null 2>&1 || true -if [ -f dist/*wheel* ]; then - echo "Wheel present, installing..." - python3 -m pip install dist/*.whl -fi +echo "Running DeltaForge Skeleton tests..." -echo "Installing package in editable mode..." -python3 -m pip install -e . >/dev/null 2>&1 || true +# Ensure Python is available +python3 -V +pip3 -V -echo "Running unit tests..." -python3 -m pip install -q pytest || true -pytest -q || python3 -m unittest discover -q +# Build the package to verify packaging metadata compiles +python3 -m build || { echo "Build failed"; exit 1; } -echo "Running deterministic backtest to verify MVP flow..." -python3 - <<'PY' -from deltaforge.dsl import Asset, MarketSignal, StrategyDelta -from deltaforge.core import Curator -from deltaforge.backtester import Backtester -from deltaforge.dsl import PlanDelta +echo "Running a minimal deterministic flow..." +python3 - << 'PY' +from deltaforge_skeleton.core import Asset, MarketSignal +from deltaforge_skeleton.adapters.equity_feed import EquityFeedAdapter +from deltaforge_skeleton.curator import Curator +from deltaforge_skeleton.execution import ExecutionEngine +from deltaforge_skeleton.backtester import Backtester -asset_a = Asset(id="eq-AAPL", type="equity", symbol="AAPL") -asset_b = Asset(id="eq-MSFT", type="equity", symbol="MSFT") -sig = MarketSignal(asset=asset_a, timestamp=0.0, price=150.0) -curator = Curator([asset_a, asset_b]) -plan = curator.synthesize_plan([sig], [ - StrategyDelta(id="s1", assets=[asset_a], objectives={"maximize": "return"}), - StrategyDelta(id="s2", assets=[asset_b], objectives={"maximize": "return"}), -]) -bt = Backtester(initial_cash=1000.0) -res = bt.apply([sig], plan) -print(res) +apple = Asset(symbol='AAPL') +sig = MarketSignal(asset=apple, price=150.0, timestamp=0.0, liquidity=1.0) + +curator = Curator() +plan = curator.synthesize([sig]) + +engine = ExecutionEngine() +routes = engine.route(plan) + +bt = Backtester() +replay = bt.replay(plan) + +print("PLAN:", plan) +print("ROUTES:", routes) +print("REPLAY:", replay) PY + +echo "All good. Ready to publish once READY_TO_PUBLISH is created."