build(agent): molt-z#db0ec5 iteration
This commit is contained in:
parent
c0383daa11
commit
b5ea91f1be
|
|
@ -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
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
# APPS Agents Documentation
|
||||||
|
|
||||||
|
Architecture: Python MVP for Algebraic Portfolio Provenance Studio (APPS)
|
||||||
|
- Core DSL: algebraic_portfolio_provenance_studio_ve.dsl
|
||||||
|
- Deterministic Backtester: algebraic_portfolio_provenance_studio_ve.simulator
|
||||||
|
- Graph-of-Contracts registry: algebraic_portfolio_provenance_studio_ve.registry
|
||||||
|
- Adapters: algebraic_portfolio_provenance_studio_ve.adapters
|
||||||
|
- Tests: tests/test_basic.py
|
||||||
|
|
||||||
|
How to run locally:
|
||||||
|
- pytest -q
|
||||||
|
- python -m build
|
||||||
|
|
||||||
|
Packaging integration:
|
||||||
|
- pyproject.toml defines the package name algebraic_portfolio_provenance_studio_ve
|
||||||
|
|
||||||
|
Conventions:
|
||||||
|
- Minimal, well-scoped MVP. Each module is a stepping stone toward the full APPS architecture.
|
||||||
19
README.md
19
README.md
|
|
@ -1,3 +1,18 @@
|
||||||
# algebraic-portfolio-provenance-studio-ve
|
APPS: Algebraic Portfolio Provenance Studio
|
||||||
|
|
||||||
Gap addressed: existing investment tooling often relies on opaque, hard-to-audit solver code, with limited offline testing, restricted data-sharing, and weak cross-venue governance. There is a need for a lightweight, open, end-to-end toolchain that l
|
Overview
|
||||||
|
- Lightweight, end-to-end DSL for assets, objectives, risk budgets, and per-step plan deltas.
|
||||||
|
- Verifiable, audit-friendly backtesting with offline-first capabilities and a minimal Graph-of-Contracts registry scaffold.
|
||||||
|
- MVP: Python-based implementation suitable for local testing, with deterministic backtests and two toy adapters.
|
||||||
|
|
||||||
|
How to run
|
||||||
|
- Install tooling: python -m pip install -e .
|
||||||
|
- Run tests: pytest -q
|
||||||
|
- Build package: python -m build
|
||||||
|
|
||||||
|
Project layout (high level)
|
||||||
|
- algebraic_portfolio_provenance_studio_ve/: core library (dsl, simulator, registry, adapters)
|
||||||
|
- tests/: unit tests for MVP
|
||||||
|
- AGENTS.md: architecture and testing commands
|
||||||
|
- test.sh: test runner script (generated in this repo)
|
||||||
|
- READY_TO_PUBLISH: marker for publishing (created at finish)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
"""algebraic_portfolio_provenance_studio_ve
|
||||||
|
Minimal MVP scaffolding for APPS: Algebraic Portfolio Provenance Studio.
|
||||||
|
|
||||||
|
This package provides a tiny DSL representation, a deterministic backtester,
|
||||||
|
and simple adapters to bootstrap offline-first testing and cross-venue ideas.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from .dsl import LocalAsset, Objective, RiskBudget, PlanDelta, SharedSignals, AuditLog
|
||||||
|
from .simulator import DeterministicBacktest
|
||||||
|
from .registry import GoCRegistry
|
||||||
|
|
||||||
|
__all__ = [
|
||||||
|
"LocalAsset",
|
||||||
|
"Objective",
|
||||||
|
"RiskBudget",
|
||||||
|
"PlanDelta",
|
||||||
|
"SharedSignals",
|
||||||
|
"AuditLog",
|
||||||
|
"DeterministicBacktest",
|
||||||
|
"GoCRegistry",
|
||||||
|
]
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
"""Adapters package for APPS MVP."""
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
def price_series_equities(symbols: List[str], seed: int = 1) -> Dict[str, List[float]]:
|
||||||
|
# Simple deterministic series: start at 100 and apply a tiny walk
|
||||||
|
prices: Dict[str, List[float]] = {}
|
||||||
|
base = 100.0
|
||||||
|
for s in symbols:
|
||||||
|
series: List[float] = []
|
||||||
|
val = base
|
||||||
|
for i in range(steps := 10):
|
||||||
|
val = max(1.0, val * (1.0 + ((i * 13 + len(s)) % 5 - 2) * 0.01))
|
||||||
|
series.append(round(val, 2))
|
||||||
|
prices[s] = series
|
||||||
|
return prices
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
|
|
||||||
|
def price_series_fixed_income(bond_symbols: List[str]) -> Dict[str, List[float]]:
|
||||||
|
# Simple deterministic coupon-like par values with small drift
|
||||||
|
prices: Dict[str, List[float]] = {}
|
||||||
|
for s in bond_symbols:
|
||||||
|
series = [100.0, 99.5, 99.8, 100.2, 100.5, 100.2, 99.9, 100.1, 100.3, 100.0]
|
||||||
|
prices[s] = series
|
||||||
|
return prices
|
||||||
|
|
@ -0,0 +1,77 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
from typing import Dict, Optional, Any, List
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class LocalAsset:
|
||||||
|
symbol: str
|
||||||
|
asset_class: str # e.g., Equity, Bond
|
||||||
|
notional: float
|
||||||
|
constraints: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"symbol": self.symbol,
|
||||||
|
"asset_class": self.asset_class,
|
||||||
|
"notional": self.notional,
|
||||||
|
"constraints": self.constraints,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class Objective:
|
||||||
|
target_return: Optional[float] = None
|
||||||
|
target_vol: Optional[float] = None
|
||||||
|
target_sharpe: Optional[float] = None
|
||||||
|
liquidity_budget: Optional[float] = None
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"target_return": self.target_return,
|
||||||
|
"target_vol": self.target_vol,
|
||||||
|
"target_sharpe": self.target_sharpe,
|
||||||
|
"liquidity_budget": self.liquidity_budget,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class RiskBudget:
|
||||||
|
max_drawdown: Optional[float] = None
|
||||||
|
tail_risk: Optional[float] = None
|
||||||
|
exposure_caps: Dict[str, float] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {
|
||||||
|
"max_drawdown": self.max_drawdown,
|
||||||
|
"tail_risk": self.tail_risk,
|
||||||
|
"exposure_caps": self.exposure_caps,
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PlanDelta:
|
||||||
|
step: int
|
||||||
|
deltas: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {"step": self.step, "deltas": self.deltas}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SharedSignals:
|
||||||
|
signals: Dict[str, Any] = field(default_factory=dict)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {"signals": self.signals}
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class AuditLog:
|
||||||
|
events: List[Dict[str, Any]] = field(default_factory=list)
|
||||||
|
version: str = "0.0.1"
|
||||||
|
|
||||||
|
def log(self, event: Dict[str, Any]) -> None:
|
||||||
|
self.events.append(event)
|
||||||
|
|
||||||
|
def to_dict(self) -> Dict[str, Any]:
|
||||||
|
return {"events": self.events, "version": self.version}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from typing import Dict, Any
|
||||||
|
|
||||||
|
|
||||||
|
class GoCRegistry:
|
||||||
|
"""Graph-of-Contracts registry scaffold.
|
||||||
|
|
||||||
|
Keeps a tiny in-memory map of canonical contract versions and adapter stubs.
|
||||||
|
This is a minimal MVP placeholder to exercise the architecture.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self._registry: Dict[str, Dict[str, Any]] = {}
|
||||||
|
|
||||||
|
def register(self, contract_id: str, version: str, meta: Dict[str, Any]) -> None:
|
||||||
|
self._registry[contract_id] = {"version": version, "meta": meta}
|
||||||
|
|
||||||
|
def get(self, contract_id: str) -> Dict[str, Any]:
|
||||||
|
return self._registry.get(contract_id, {})
|
||||||
|
|
@ -0,0 +1,49 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Dict, List, Any
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class DeterministicBacktest:
|
||||||
|
assets: List[str]
|
||||||
|
initial_notional: float
|
||||||
|
steps: int
|
||||||
|
deltas: List[Dict[str, float]] # per-step rebalancing deltas in allocation fractions
|
||||||
|
|
||||||
|
def run(self) -> Dict[str, Any]:
|
||||||
|
# Simple deterministic backtest: start with equal allocation (or specified by deltas[0]), then apply deltas.
|
||||||
|
n = len(self.assets)
|
||||||
|
# Initialize equal weights if not provided
|
||||||
|
if self.deltas and len(self.deltas) >= 1:
|
||||||
|
weights = [self.deltas[0].get(a, 0.0) for a in self.assets]
|
||||||
|
else:
|
||||||
|
weights = [1.0 / n] * n
|
||||||
|
|
||||||
|
# Normalize
|
||||||
|
total = sum(weights) or 1.0
|
||||||
|
weights = [w / total for w in weights]
|
||||||
|
|
||||||
|
history = []
|
||||||
|
cash = 0.0
|
||||||
|
notional = self.initial_notional
|
||||||
|
for step in range(self.steps):
|
||||||
|
# Apply delta if provided for this step
|
||||||
|
if step < len(self.deltas):
|
||||||
|
d = self.deltas[step]
|
||||||
|
for i, a in enumerate(self.assets):
|
||||||
|
if a in d:
|
||||||
|
weights[i] = max(0.0, d[a])
|
||||||
|
# renormalize
|
||||||
|
total = sum(weights) or 1.0
|
||||||
|
weights = [w / total for w in weights]
|
||||||
|
|
||||||
|
# compute position values
|
||||||
|
values = {a: notional * w for a, w in zip(self.assets, weights)}
|
||||||
|
step_entry = {
|
||||||
|
"step": step,
|
||||||
|
"weights": dict(zip(self.assets, weights)),
|
||||||
|
"values": values,
|
||||||
|
}
|
||||||
|
history.append(step_entry)
|
||||||
|
|
||||||
|
return {"initial_notional": self.initial_notional, "steps": self.steps, "history": history}
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
[build-system]
|
||||||
|
requires = ["setuptools", "wheel"]
|
||||||
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
|
[project]
|
||||||
|
name = "algebraic_portfolio_provenance_studio_ve"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = "MVP: Algebraic Portfolio Provenance Studio (APPS) in Python"
|
||||||
|
authors = [{name = "OpenCode", email = "devnull@example.com"}]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.setuptools]
|
||||||
|
package-dir = { "" = "." }
|
||||||
|
|
||||||
|
[tool.setuptools.packages.find]
|
||||||
|
where = ["."]
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
# Ensure the repository root is on PYTHONPATH so tests can import the local package
|
||||||
|
export PYTHONPATH="$(pwd):${PYTHONPATH:-}"
|
||||||
|
|
||||||
|
echo "Running tests..."
|
||||||
|
pytest -q
|
||||||
|
|
||||||
|
echo "Building package (verify pyproject)..."
|
||||||
|
python3 -m build
|
||||||
|
|
||||||
|
echo "All tests passed and build completed."
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
from algebraic_portfolio_provenance_studio_ve.dsl import LocalAsset, Objective, RiskBudget, PlanDelta, SharedSignals, AuditLog
|
||||||
|
from algebraic_portfolio_provenance_studio_ve.simulator import DeterministicBacktest
|
||||||
|
|
||||||
|
|
||||||
|
def test_basic_dsl_construction_and_backtest():
|
||||||
|
# Build a tiny DSL example
|
||||||
|
a1 = LocalAsset(symbol="AAPL", asset_class="Equity", notional=50000.0)
|
||||||
|
a2 = LocalAsset(symbol="TBOND", asset_class="FixedIncome", notional=50000.0)
|
||||||
|
obj = Objective(target_return=0.08, target_vol=0.15)
|
||||||
|
rb = RiskBudget(max_drawdown=0.2, tail_risk=0.05, exposure_caps={"AAPL": 0.6, "TBOND": 0.5})
|
||||||
|
delta = PlanDelta(step=0, deltas={"AAPL": 0.5, "TBOND": 0.5})
|
||||||
|
|
||||||
|
# Run a tiny simulated backtest
|
||||||
|
backtest = DeterministicBacktest(
|
||||||
|
assets=[a1.symbol, a2.symbol],
|
||||||
|
initial_notional=a1.notional + a2.notional,
|
||||||
|
steps=3,
|
||||||
|
deltas=[{"AAPL": 0.6, "TBOND": 0.4}, {"AAPL": 0.4, "TBOND": 0.6}, {"AAPL": 0.5, "TBOND": 0.5}],
|
||||||
|
)
|
||||||
|
result = backtest.run()
|
||||||
|
assert isinstance(result, dict)
|
||||||
|
assert "history" in result
|
||||||
|
assert len(result["history"]) == 3
|
||||||
|
# sanity: history steps correspond to allocated weights that sum to 1.0
|
||||||
|
for st in result["history"]:
|
||||||
|
w = st["weights"]
|
||||||
|
assert abs(sum(w.values()) - 1.0) < 1e-6
|
||||||
Loading…
Reference in New Issue