diff --git a/AGENTS_SKELETON.md b/AGENTS_SKELETON.md new file mode 100644 index 0000000..35722a6 --- /dev/null +++ b/AGENTS_SKELETON.md @@ -0,0 +1,12 @@ +# DeltaForge Skeleton Addendum + +- This repository now includes a production-oriented MVP skeleton for DeltaForge: + - Core DSL: Asset, MarketSignal, StrategyDelta, PlanDelta, SharedSignals, DualVariables (deltaforge_skeleton/dsl.py) + - Lightweight Curator (ADMM-like) in deltaforge_skeleton/curator.py + - Two starter adapters: deltaforge_skeleton/adapters/equity_feed.py and deltaforge_skeleton/adapters/options_feed.py + - Minimal ExecutionEngine (deltaforge_skeleton/execution.py) and Backtester (deltaforge_skeleton/backtester.py) + - Packaging metadata (pyproject.toml) and a basic test harness (deltaforge_skeleton/tests/test_basic.py) +- How to run tests: ./test.sh +- Packaging test: python3 -m build +- Tests are unittest-based and run via python3 -m unittest discover -s deltaforge_skeleton/tests +- Ready-to-publish guard: READY_TO_PUBLISH exists at repo root when MVP is production-ready diff --git a/README.md b/README.md index 8ac4d7a..c95f51e 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,16 @@ # DeltaForge Skeleton -A production-friendly Python package skeleton for DeltaForge MVP, enabling a canonical DSL surface, -two-venue adapters, a lightweight ADMM-like coordinator, and deterministic replay-backed backtests. +A production-oriented MVP scaffold for a real-time cross-venue delta-hedge engine. -- Core DSL: Asset, MarketSignal, StrategyDelta, PlanDelta -- Adapters: EquityFeed, OptionsFeed (two starter adapters) -- Curator: simple cross-venue planning -- ExecutionEngine and Backtester: deterministic routing and replay -- GoC registry scaffolding for future interoperability +- Core DSL: Asset, MarketSignal, StrategyDelta, PlanDelta, SharedSignals, DualVariables +- Lightweight curator (ADMM-like) for cross-venue coherence +- Two starter adapters: equity_feed and options_feed +- Minimal ExecutionEngine to route actions across venues +- Toy Backtester with deterministic replay +- Packaging metadata and test harness to verify packaging and end-to-end flow -This repository is designed to be extended toward a production MVP with robust contracts and governance. +How to run tests +- python3 -m build +- pytest (if pytest is installed) or python3 -m unittest discover -s deltaforge_skeleton/tests + +This skeleton is designed to be extended into a full MVP within the DeltaForge ecosystem. diff --git a/deltaforge_skeleton/__init__.py b/deltaforge_skeleton/__init__.py index af1622d..d35db66 100644 --- a/deltaforge_skeleton/__init__.py +++ b/deltaforge_skeleton/__init__.py @@ -1,15 +1,12 @@ -"""DeltaForge Skeletal MVP +"""DeltaForge Skeleton package -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. +A minimal, production-oriented MVP scaffold for a cross-venue delta-hedge engine. """ -from .core import Asset, MarketSignal, StrategyDelta, PlanDelta -from .adapters.equity_feed import EquityFeedAdapter -from .adapters.options_feed import OptionsFeedAdapter +from .dsl import Asset, MarketSignal, StrategyDelta, PlanDelta, SharedSignals, DualVariables from .curator import Curator -from .goc_registry import GoCRegistry +from .adapters import equity_feed as equity_feed +from .adapters import options_feed as options_feed from .execution import ExecutionEngine from .backtester import Backtester @@ -18,10 +15,11 @@ __all__ = [ "MarketSignal", "StrategyDelta", "PlanDelta", - "EquityFeedAdapter", - "OptionsFeedAdapter", + "SharedSignals", + "DualVariables", "Curator", + "equity_feed", + "options_feed", "ExecutionEngine", "Backtester", - "GoCRegistry", ] diff --git a/deltaforge_skeleton/adapters/__init__.py b/deltaforge_skeleton/adapters/__init__.py index 9060dbb..7917345 100644 --- a/deltaforge_skeleton/adapters/__init__.py +++ b/deltaforge_skeleton/adapters/__init__.py @@ -1,6 +1,4 @@ -"""Adapters package""" +from . import equity_feed +from . import options_feed -from .equity_feed import EquityFeedAdapter -from .options_feed import OptionsFeedAdapter - -__all__ = ["EquityFeedAdapter", "OptionsFeedAdapter"] +__all__ = ["equity_feed", "options_feed"] diff --git a/deltaforge_skeleton/adapters/equity_feed.py b/deltaforge_skeleton/adapters/equity_feed.py index 0ed035d..1a83fae 100644 --- a/deltaforge_skeleton/adapters/equity_feed.py +++ b/deltaforge_skeleton/adapters/equity_feed.py @@ -1,13 +1,14 @@ from __future__ import annotations - -from dataclasses import dataclass -from deltaforge_skeleton.core import MarketSignal, Asset +from typing import List +from ..dsl import Asset, MarketSignal -@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) +def generate_equity_signals(symbols: List[str], price_dict: dict) -> List[MarketSignal]: + signals = [] + import time + ts = time.time() + for s in symbols: + asset = Asset(symbol=s, asset_type="equity") + price = price_dict.get(s, 100.0) + signals.append(MarketSignal(asset=asset, price=price, timestamp=ts)) + return signals diff --git a/deltaforge_skeleton/adapters/options_feed.py b/deltaforge_skeleton/adapters/options_feed.py index 24fc61d..bcdbc13 100644 --- a/deltaforge_skeleton/adapters/options_feed.py +++ b/deltaforge_skeleton/adapters/options_feed.py @@ -1,13 +1,11 @@ from __future__ import annotations - -from dataclasses import dataclass -from deltaforge_skeleton.core import MarketSignal, Asset +from typing import List +from ..dsl import Asset, MarketSignal -@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) +def generate_options_signals(symbol: str, underlying_price: float) -> MarketSignal: + asset = Asset(symbol=symbol, asset_type="option") + # Very naive option price proxy for this MVP: static premium around 5% of underlying + price = max(1.0, underlying_price * 0.05) + import time + return MarketSignal(asset=asset, price=price, timestamp=time.time()) diff --git a/deltaforge_skeleton/backtester.py b/deltaforge_skeleton/backtester.py index a11aab5..8c7ed5e 100644 --- a/deltaforge_skeleton/backtester.py +++ b/deltaforge_skeleton/backtester.py @@ -1,11 +1,17 @@ from __future__ import annotations - -from deltaforge_skeleton.core import PlanDelta +from typing import List +from .dsl import PlanDelta, StrategyDelta 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) + """Toy deterministic replay engine for end-to-end validation.""" + + def __init__(self, seed: int = 0): + self.seed = seed + + def replay(self, plan: PlanDelta) -> float: + # Very naive replay: sum absolute deltas to produce a pseudo-PnL + pnl = sum(abs(a.delta) for a in plan.actions) + # Incorporate timestamp entropy for determinism in tests + pnl += (plan.timestamp or 0.0) * 1e-6 + return float(pnl) diff --git a/deltaforge_skeleton/curator.py b/deltaforge_skeleton/curator.py index 8b6f446..47ae547 100644 --- a/deltaforge_skeleton/curator.py +++ b/deltaforge_skeleton/curator.py @@ -1,41 +1,28 @@ from __future__ import annotations - from typing import List -from deltaforge_skeleton.core import MarketSignal, PlanDelta, StrategyDelta, Asset - -# Optional cross-venue coordination (ADMM-lite) import. The coordination -# module is kept minimal to avoid imposing heavy dependencies on the MVP. -try: - from deltaforge_skeleton.coordination import ADMMCoordinator -except Exception: - ADMMCoordinator = None # type: ignore +from .dsl import PlanDelta, SharedSignals, DualVariables class Curator: + """A lightweight ADMM-like coordinator for cross-venue coherence. + + This is a minimal stub that validates a PlanDelta against a simple + constraint: total delta across venues must sum to zero (delta-neutral). + In a real system, this would implement distributed optimization across venues. + """ + def __init__(self): - self.audit = [] + pass - def synthesize(self, signals: List[MarketSignal]) -> PlanDelta: - # Naive delta-synthesis: for each asset, create a delta hedge with 1x price weight - steps: List[str] = [] - 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") - - # Optional cross-venue coordination: if a real ADMM coordinator is available, - # attempt to coordinate across a default two-venue setup. This keeps the MVP - # lightweight while enabling future multi-venue coherence checks. - if ADMMCoordinator is not None: - try: - coordinator = ADMMCoordinator() - plan = coordinator.coordinate(plan, venues=["VenueA", "VenueB"]) - except Exception: - # Silently ignore coordination issues to preserve existing behavior - pass - - return plan + def enforce(self, local_plan: PlanDelta, shared: SharedSignals, duals: DualVariables) -> PlanDelta: + # Simple validation: if sum of deltas != 0, attempt to rebalance by offsetting last action + total = sum(a.delta for a in local_plan.actions) + if total != 0 and local_plan.actions: + # Adjust the last action to ensure delta-neutrality as a placeholder behavior + last = local_plan.actions[-1] + offset = -total + last.delta += offset + # Attach a simple safety tag for auditability in this MVP + local_plan.safety_tags = local_plan.safety_tags or [] + local_plan.safety_tags.append("delta-neutralized-by-curator") + return local_plan diff --git a/deltaforge_skeleton/dsl.py b/deltaforge_skeleton/dsl.py new file mode 100644 index 0000000..67764e8 --- /dev/null +++ b/deltaforge_skeleton/dsl.py @@ -0,0 +1,50 @@ +from __future__ import annotations +from dataclasses import dataclass, field +from typing import List, Dict, Optional + + +@dataclass +class Asset: + symbol: str + asset_type: str # e.g., 'equity' or 'option' + + def __post_init__(self): + if not self.symbol: + raise ValueError("Asset.symbol must be non-empty") + if self.asset_type not in {"equity", "option", "future"}: + raise ValueError("asset_type must be 'equity', 'option', or 'future'") + + +@dataclass +class MarketSignal: + asset: Asset + price: float + timestamp: float + + +@dataclass +class StrategyDelta: + # A high-level delta that would be applied to a local problem + asset: Asset + delta: float + theta: Optional[float] = None # optional auxiliary metric + + +@dataclass +class SharedSignals: + prices: Dict[str, float] # symbol -> price + greeks: Dict[str, Dict[str, float]] # symbol -> greek-name -> value + liquidity: Dict[str, float] # symbol -> liquidity proxy + + +@dataclass +class DualVariables: + coupling_multipliers: Dict[str, float] # venue_id -> multiplier + + +@dataclass +class PlanDelta: + # A delta plan that can be applied to move a local problem toward feasibility + actions: List[StrategyDelta] = field(default_factory=list) + timestamp: float = 0.0 + safety_tags: List[str] = field(default_factory=list) diff --git a/deltaforge_skeleton/execution.py b/deltaforge_skeleton/execution.py index e82ce44..7a4b133 100644 --- a/deltaforge_skeleton/execution.py +++ b/deltaforge_skeleton/execution.py @@ -1,19 +1,20 @@ from __future__ import annotations - from typing import List -from deltaforge_skeleton.core import PlanDelta + +from .dsl import PlanDelta, StrategyDelta class ExecutionEngine: + """Minimal, latency-aware router that would dispatch actions to venues.""" + def __init__(self): - self.log: List[str] = [] + pass 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 + # In this MVP, we simply return venue labels for actions as a stub + venue_actions = [] + for a in plan.actions: + venue_actions.append(f"venue-A/{a.asset.symbol}:{a.delta}") + if not venue_actions: + venue_actions.append("no-actions") + return venue_actions diff --git a/deltaforge_skeleton/tests/test_basic.py b/deltaforge_skeleton/tests/test_basic.py new file mode 100644 index 0000000..5416101 --- /dev/null +++ b/deltaforge_skeleton/tests/test_basic.py @@ -0,0 +1,42 @@ +import unittest + +from deltaforge_skeleton.dsl import Asset, PlanDelta, StrategyDelta +from deltaforge_skeleton.curator import Curator +from deltaforge_skeleton.adapters.equity_feed import generate_equity_signals +from deltaforge_skeleton.adapters.options_feed import generate_options_signals +from deltaforge_skeleton.execution import ExecutionEngine +from deltaforge_skeleton.backtester import Backtester + + +class TestDeltaForgeSkeleton(unittest.TestCase): + def test_end_to_end_scenario(self): + # Simple two-asset delta hedge: AAPL and MSFT across two venues (conceptual) + aapl = Asset(symbol="AAPL", asset_type="equity") + msft = Asset(symbol="MSFT", asset_type="equity") + + # Create two simple plan deltas to simulate actions on each venue + plan = PlanDelta(actions=[StrategyDelta(asset=aapl, delta=1.0), StrategyDelta(asset=msft, delta=-1.0)], timestamp=123.0) + + curator = Curator() + # Shared signals and dual vars are placeholders for this MVP + class Dummy: + pass + shared = Dummy() + duals = Dummy() + curated = curator.enforce(plan, shared, duals) + + eng = ExecutionEngine() + routed = eng.route(curated) + + bt = Backtester() + pnl = bt.replay(curated) + + self.assertTrue(len(routed) >= 1) + self.assertIsInstance(pnl, float) + + +def main(): + unittest.main() + +if __name__ == "__main__": + main() diff --git a/pyproject.toml b/pyproject.toml index 695b88c..5572b59 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,16 +1,14 @@ [build-system] -requires = ["setuptools>=61", "wheel"] +requires = ["setuptools>=42", "wheel"] build-backend = "setuptools.build_meta" [project] name = "deltaforge-skeleton" version = "0.1.0" -description = "DeltaForge MVP skeleton: cross-venue synthesis engine with two assets and two venues." +description = "DeltaForge MVP skeleton: core DSL, curator, adapters, execution, and backtester for cross-venue hedges (2 assets, 2 venues)" readme = "README.md" requires-python = ">=3.8" -license = {text = "MIT"} -authors = [ { name = "OpenCode DeltaForge", email = "dev@example.com" } ] -dependencies = [] +license = "MIT" [tool.setuptools.packages.find] where = ["deltaforge_skeleton"] diff --git a/test.sh b/test.sh index b328005..a6fdd50 100644 --- a/test.sh +++ b/test.sh @@ -3,36 +3,10 @@ set -euo pipefail echo "Running DeltaForge Skeleton tests..." -# Ensure Python is available -python3 -V -pip3 -V +# 1) Build the package to verify packaging metadata and directory structure +python3 -m build -# Build the package to verify packaging metadata compiles -python3 -m build || { echo "Build failed"; exit 1; } +# 2) Run unit tests (standard library unittest) +python3 -m unittest discover -s deltaforge_skeleton/tests -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 - -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." +echo "All tests passed."