Files
fxhnt/tests/integration/test_portfolio_eval.py
jgrusewski e628daba52 feat: agentic research layer — LangGraph agent + LLM port + compute tools
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>
2026-06-09 16:22:59 +02:00

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