build(agent): new-agents-4#58ba63 iteration
This commit is contained in:
parent
02de4d9f97
commit
10a5c36315
|
|
@ -42,3 +42,10 @@ Architecture overview for the EquiCompiler MVP
|
||||||
|
|
||||||
Notes
|
Notes
|
||||||
- This document is intentionally concise to enable rapid iteration. It can be expanded as the project evolves.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,11 @@ Development notes
|
||||||
- Ensure tests cover the new functionality and avoid sensitive data in tests.
|
- Ensure tests cover the new functionality and avoid sensitive data in tests.
|
||||||
|
|
||||||
Next steps
|
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.
|
- Implement a more expressive DSL and a richer IR (EquiIR) representation.
|
||||||
- Add more tests for edge cases and simple integration tests for the CLI.
|
- 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.
|
- Expand packaging metadata and README with a longer developer and user guide.
|
||||||
|
|
|
||||||
|
|
@ -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"]
|
||||||
|
|
@ -73,7 +73,36 @@ def _attach_attestations(ir: Dict[str, object]) -> Dict[str, object]:
|
||||||
{"step": "verify", "timestamp": timestamp, "digest": digest, "algorithm": "SHA-256"},
|
{"step": "verify", "timestamp": timestamp, "digest": digest, "algorithm": "SHA-256"},
|
||||||
]
|
]
|
||||||
ir_copy["attestations"] = attestations
|
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
|
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"]
|
__all__ = ["parse_dsl_to_ir", "to_json"]
|
||||||
|
|
|
||||||
|
|
@ -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
|
This module provides a tiny, in-process registry to track contracts/adapters
|
||||||
and contracts that would connect the EquiCompiler to data feeds, brokers,
|
that participate in the EquiCompiler GoC workflow. It is intentionally lightweight
|
||||||
and other runtime components. It is intended as a scaffold for future
|
and serves as a scaffold for the MVP; real systems would back this with a durable store.
|
||||||
extensions and to support the MVP where plug-and-play backends are wired in
|
|
||||||
via stable, versioned contracts.
|
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass, field
|
from typing import Dict, List, Any
|
||||||
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."""
|
|
||||||
|
|
||||||
|
class GraphRegistry:
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self._contracts: Dict[str, VersionedContract] = {}
|
self._registry: Dict[str, Dict[str, Any]] = {}
|
||||||
self._adapters: Dict[str, VersionedContract] = {}
|
|
||||||
|
|
||||||
# Contracts management
|
def register_contract(self, name: str, version: str, adapter: str) -> Dict[str, str]:
|
||||||
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)
|
|
||||||
key = f"{name}:{version}"
|
key = f"{name}:{version}"
|
||||||
self._contracts[key] = vc
|
entry = {"name": name, "version": version, "adapter": adapter}
|
||||||
return vc
|
self._registry[key] = entry
|
||||||
|
return entry
|
||||||
|
|
||||||
def get_contract(self, name: str, version: Optional[str] = None) -> Optional[VersionedContract]:
|
def get_contract(self, name: str, version: str) -> Dict[str, Any]:
|
||||||
if version is None:
|
return self._registry.get(f"{name}:{version}", {})
|
||||||
# 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 list_contracts(self) -> List[VersionedContract]:
|
def list_contracts(self) -> List[Dict[str, Any]]:
|
||||||
return list(self._contracts.values())
|
return list(self._registry.values())
|
||||||
|
|
||||||
# Adapters management (GoC entries)
|
__all__ = ["GraphRegistry"]
|
||||||
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"]
|
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,24 @@ class TestCore(unittest.TestCase):
|
||||||
self.assertEqual(ir["constraints"]["max_drawdown"], "0.2")
|
self.assertEqual(ir["constraints"]["max_drawdown"], "0.2")
|
||||||
self.assertEqual(ir["constraints"]["var"], "0.95")
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue