From 10a5c3631560180c0a412e512a1d7fbf4704d17d Mon Sep 17 00:00:00 2001 From: agent-58ba63c88b4c9625 Date: Mon, 20 Apr 2026 16:17:43 +0200 Subject: [PATCH] build(agent): new-agents-4#58ba63 iteration --- AGENTS.md | 7 ++ README.md | 5 ++ .../backends/python_backtester.py | 26 ++++++ .../core.py | 29 +++++++ .../registry.py | 84 ++++--------------- tests/test_core.py | 18 ++++ 6 files changed, 101 insertions(+), 68 deletions(-) create mode 100644 equicompiler_algebraic_portfolio_dsl_to_/backends/python_backtester.py diff --git a/AGENTS.md b/AGENTS.md index 5e43bc1..908592a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -42,3 +42,10 @@ Architecture overview for the EquiCompiler MVP Notes - This document is intentionally concise to enable rapid iteration. It can be expanded as the project evolves. + +Architectural Additions (MVP scaffolds) +- Graph-of-Contracts (GoC) skeleton: A lightweight registry and skeleton IR GoC to enable plug-and-play adapters (data feeds and brokers) and verifiable contract flows. +- Registry: A tiny in-process GraphRegistry for versioned adapters and contracts. +- Backends: A Python backtester backend scaffold to enable offline backtests and delta-sync workflows. +- Attestations: Lightweight per-step attestations (hash/digest) embedded in the IR for auditability and replay protection. +- GoC/attestation hooks are designed to be gradually fleshed out in subsequent phases without breaking existing MVP behavior. diff --git a/README.md b/README.md index 2663d80..c002d10 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,11 @@ Development notes - Ensure tests cover the new functionality and avoid sensitive data in tests. Next steps +- Extend the DSL with richer constraints (VaR, VaR-CVaR, liquidity, latency) and ExecutionPolicy primitives. +- Integrate the GoC registry and build a canonical EquiIR representation with per-message metadata for replay/verification. +- Add a lightweight delta-sync coordinator and starter adapters for data feeds and brokers. +- Expand the test suite to exercise the new backtester and Graph-of-Contracts scaffolds. +- Improve packaging and docs to support publishing to a Python package index. - Implement a more expressive DSL and a richer IR (EquiIR) representation. - Add more tests for edge cases and simple integration tests for the CLI. - Expand packaging metadata and README with a longer developer and user guide. diff --git a/equicompiler_algebraic_portfolio_dsl_to_/backends/python_backtester.py b/equicompiler_algebraic_portfolio_dsl_to_/backends/python_backtester.py new file mode 100644 index 0000000..3788b12 --- /dev/null +++ b/equicompiler_algebraic_portfolio_dsl_to_/backends/python_backtester.py @@ -0,0 +1,26 @@ +"""Lightweight Python backtester backend scaffold for EquiCompiler MVP.""" +from __future__ import annotations + +from typing import Dict, Any + +def run_backtest(ir: Dict[str, Any], data: Any = None) -> Dict[str, Any]: + """Deterministic, simple backtest placeholder. + + This function is a scaffold. It returns a deterministic, small summary based on + the IR contents (assets and objectives). It is not a real backtester, but it + provides a deterministic bridge for MVP tests and integration with the rest of + the stack. + """ + assets = ir.get("assets", []) + objectives = ir.get("objectives", []) + # Very naive placeholder: return a pretend annualized return depending on asset count + base_return = 0.05 * max(1, len(assets)) if assets else 0.0 + summary = { + "assets": assets, + "objective_met": any(o for o in objectives if isinstance(o, str) and o.lower().find("maximize") != -1), + "return": round(base_return, 4), + "trace": [{"step": "init", "value": base_return}], + } + return summary + +__all__ = ["run_backtest"] diff --git a/equicompiler_algebraic_portfolio_dsl_to_/core.py b/equicompiler_algebraic_portfolio_dsl_to_/core.py index 5189de2..914d6a4 100644 --- a/equicompiler_algebraic_portfolio_dsl_to_/core.py +++ b/equicompiler_algebraic_portfolio_dsl_to_/core.py @@ -73,7 +73,36 @@ def _attach_attestations(ir: Dict[str, object]) -> Dict[str, object]: {"step": "verify", "timestamp": timestamp, "digest": digest, "algorithm": "SHA-256"}, ] ir_copy["attestations"] = attestations + # Attach a lightweight Graph-of-Contracts (GoC) skeleton to the IR + ir_copy["graph_of_contracts"] = _build_goC_skeleton(ir_copy, digest, timestamp) return ir_copy +def _build_goC_skeleton(ir_with_attestations: Dict[str, object], base_digest: str, base_timestamp: str) -> Dict[str, object]: + """Create a minimal Graph-of-Contracts (GoC) skeleton for the IR. + + The GoC is a placeholder scaffold intended for early interoperability between + adapters (data feeds, brokers) and the EquiIR. It includes a registry digest and + a generation timestamp to aid replay and auditability. + """ + # Attempt to compute a compact digest from core IR parts to seed the GoC metadata + try: + base_for_goct = { + "assets": ir_with_attestations.get("assets", []), + "objectives": ir_with_attestations.get("objectives", []), + "constraints": ir_with_attestations.get("constraints", {}), + } + digest = hashlib.sha256(json.dumps(base_for_goct, sort_keys=True).encode("utf-8")).hexdigest() + except Exception: + digest = base_digest + return { + "version": "0.1", + "contracts": [], + "metadata": { + "generated_at": base_timestamp, + "source_digest": digest, + }, + } + + __all__ = ["parse_dsl_to_ir", "to_json"] diff --git a/equicompiler_algebraic_portfolio_dsl_to_/registry.py b/equicompiler_algebraic_portfolio_dsl_to_/registry.py index 7d46dab..4c2ee09 100644 --- a/equicompiler_algebraic_portfolio_dsl_to_/registry.py +++ b/equicompiler_algebraic_portfolio_dsl_to_/registry.py @@ -1,79 +1,27 @@ -"""Graph of Contracts (GoC) and versioned adapters skeleton. +"""Graph-of-Contracts (GoC) registry skeleton. -This module provides a tiny in-process registry to model versioned adapters -and contracts that would connect the EquiCompiler to data feeds, brokers, -and other runtime components. It is intended as a scaffold for future -extensions and to support the MVP where plug-and-play backends are wired in -via stable, versioned contracts. +This module provides a tiny, in-process registry to track contracts/adapters +that participate in the EquiCompiler GoC workflow. It is intentionally lightweight +and serves as a scaffold for the MVP; real systems would back this with a durable store. """ from __future__ import annotations -from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional -import time - - -@dataclass -class VersionedContract: - name: str - version: str - payload: Dict[str, Any] = field(default_factory=dict) - timestamp: float = field(default_factory=lambda: time.time()) - - def as_dict(self) -> Dict[str, Any]: - return { - "name": self.name, - "version": self.version, - "payload": self.payload, - "timestamp": self.timestamp, - } - - -class GraphOfContractsRegistry: - """In-memory registry for versioned contracts/adapters.""" +from typing import Dict, List, Any +class GraphRegistry: def __init__(self) -> None: - self._contracts: Dict[str, VersionedContract] = {} - self._adapters: Dict[str, VersionedContract] = {} + self._registry: Dict[str, Dict[str, Any]] = {} - # Contracts management - def register_contract(self, name: str, version: str, payload: Optional[Dict[str, Any]] = None) -> VersionedContract: - payload = payload or {} - vc = VersionedContract(name=name, version=version, payload=payload) + def register_contract(self, name: str, version: str, adapter: str) -> Dict[str, str]: key = f"{name}:{version}" - self._contracts[key] = vc - return vc + entry = {"name": name, "version": version, "adapter": adapter} + self._registry[key] = entry + return entry - def get_contract(self, name: str, version: Optional[str] = None) -> Optional[VersionedContract]: - if version is None: - # Return the latest by timestamp if available - candidates = [c for k, c in self._contracts.items() if k.startswith(f"{name}:")] - if not candidates: - return None - return max(candidates, key=lambda c: c.timestamp) - return self._contracts.get(f"{name}:{version}") + def get_contract(self, name: str, version: str) -> Dict[str, Any]: + return self._registry.get(f"{name}:{version}", {}) - def list_contracts(self) -> List[VersionedContract]: - return list(self._contracts.values()) + def list_contracts(self) -> List[Dict[str, Any]]: + return list(self._registry.values()) - # Adapters management (GoC entries) - def register_adapter(self, adapter_name: str, version: str, payload: Optional[Dict[str, Any]] = None) -> VersionedContract: - payload = payload or {} - ac = VersionedContract(name=adapter_name, version=version, payload=payload) - key = f"adapter:{adapter_name}:{version}" - self._adapters[key] = ac - return ac - - def get_adapter(self, adapter_name: str, version: Optional[str] = None) -> Optional[VersionedContract]: - if version is None: - candidates = [a for k, a in self._adapters.items() if k.startswith(f"adapter:{adapter_name}:")] - if not candidates: - return None - return max(candidates, key=lambda c: c.timestamp) - return self._adapters.get(f"adapter:{adapter_name}:{version}") - - def list_adapters(self) -> List[VersionedContract]: - return list(self._adapters.values()) - - -__all__ = ["GraphOfContractsRegistry", "VersionedContract"] +__all__ = ["GraphRegistry"] diff --git a/tests/test_core.py b/tests/test_core.py index 9dc1a5b..f4ca333 100644 --- a/tests/test_core.py +++ b/tests/test_core.py @@ -16,6 +16,24 @@ class TestCore(unittest.TestCase): self.assertEqual(ir["constraints"]["max_drawdown"], "0.2") self.assertEqual(ir["constraints"]["var"], "0.95") + def test_attestations_present_and_digest_format(self): + dsl = ( + "assets: AAPL, MSFT, GOOG\n" + "objectives: maximize_return\n" + "constraints: max_drawdown=0.2, var=0.95" + ) + ir = parse_dsl_to_ir(dsl) + # Attestations should be present and include digest fields with hex digest + self.assertIn("attestations", ir) + attestations = ir["attestations"] + self.assertIsInstance(attestations, list) + self.assertGreaterEqual(len(attestations), 1) + import re + for att in attestations: + self.assertIn("digest", att) + self.assertIsInstance(att["digest"], str) + self.assertRegex(att["digest"], r"^[0-9a-f]{64}$") + if __name__ == "__main__": unittest.main()