application/agent: ResearchAgent (LangGraph create_react_agent) + build_tools (list_strategies, evaluate_candidate, evaluate_book wrapping the services) | adapters/llm.build_chat_model (config- swappable: local Ollama now, cluster/API later) | AgentSettings | CLI agent. The LLM proposes; the deterministic gauntlet inside the tools disposes. Verified: tools work + the model emits tool-calls (architecture correct). Local qwen2.5:3b is too weak for the reliable multi-step loop (model bottleneck, swappable via config). Also fixed flaky test seeds (hash()->deterministic). 14/14 tests. Bonus: tools surfaced GC+NQ+ES trend book = Sharpe 1.06 OOS 1.47 DSR 0.949. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
45 lines
2.2 KiB
Python
45 lines
2.2 KiB
Python
"""Portfolio-level validation: a diversified book of uncorrelated positive-drift sleeves has a higher,
|
|
more significant Sharpe than any single sleeve — and passes as ONE pre-specified hypothesis."""
|
|
from __future__ import annotations
|
|
|
|
import numpy as np
|
|
|
|
from fxhnt.application import PortfolioEvaluationService
|
|
from fxhnt.config import Settings
|
|
from fxhnt.domain.backtest import run_backtest
|
|
from fxhnt.domain.models import AssetClass, Market, PriceSeries, StrategySpec
|
|
from fxhnt.domain.portfolio import Book, StrategyAllocation, combine_returns
|
|
|
|
|
|
class FakeData:
|
|
name = "fake"
|
|
|
|
def fetch(self, market: Market, start=None, end=None) -> PriceSeries:
|
|
seed = sum(ord(c) for c in market.symbol) # deterministic (hash() is per-process randomized -> flaky)
|
|
rng = np.random.default_rng(seed) # uncorrelated per market
|
|
close = 100.0 * np.cumprod(1.0 + rng.normal(0.0005, 0.01, 1500)) # genuine drift -> diversified book clears the bar
|
|
return PriceSeries(market=market, dates=tuple(str(i) for i in range(1500)), close=close)
|
|
|
|
|
|
def test_combine_returns_aligns_on_common_dates() -> None:
|
|
s1 = (0.5, ("a", "b", "c"), np.array([0.1, 0.2, 0.3]))
|
|
s2 = (0.5, ("b", "c", "d"), np.array([1.0, 2.0, 3.0])) # common = b,c
|
|
dates, ret = combine_returns([s1, s2])
|
|
assert dates == ("b", "c")
|
|
assert np.allclose(ret, [0.5 * 0.2 + 0.5 * 1.0, 0.5 * 0.3 + 0.5 * 2.0])
|
|
|
|
|
|
def test_diversified_book_passes_and_beats_single_sleeve() -> None:
|
|
markets = [Market(symbol=s, asset_class=AssetClass.FUTURE) for s in ("AA", "BB", "CC", "DD")]
|
|
allocations = [StrategyAllocation(spec=StrategySpec(kind="buy_hold"), market=m, weight=0.25) for m in markets]
|
|
book = Book(name="div", allocations=allocations, max_gross_leverage=1.0)
|
|
|
|
svc = PortfolioEvaluationService(FakeData(), Settings())
|
|
ev = svc.evaluate_book(book, n_book_trials=1)
|
|
|
|
# the diversified book is significant as one pre-specified hypothesis
|
|
assert ev.verdict.passed
|
|
# and its Sharpe exceeds a single uncorrelated sleeve's (diversification lifts it)
|
|
single = run_backtest(FakeData().fetch(markets[0]), StrategySpec(kind="buy_hold")).stats.sharpe
|
|
assert ev.stats.sharpe > single
|