Files
fxhnt/tests/integration/test_research_slice.py
jgrusewski 58bbb4520c feat: fxhnt foundation — hexagonal architecture + proven gauntlet + vertical slice
Enterprise clean-rebuild (no foxhunt code). Hexagonal/ports-and-adapters: pure domain (gauntlet
math, strategies, backtest, models) | ports (DataProvider, repositories) | adapters (Yahoo data,
SQLAlchemy operational [Postgres/SQLite], DuckDB analytical) | application (ResearchService, DI) |
CLI composition root. Gauntlet-first: Deflated Sharpe (Bailey-LdP) built + falsification-tested
(kills best-of-N-on-noise, keeps real premium). Full vertical slice runs end-to-end on real data:
data -> strategy(trend) -> backtest(net of costs) -> IS/OOS gauntlet -> persistence. 4/4 tests green.
Postgres+DuckDB split, pydantic contracts, typed, DRY via one-contract-per-port. ADR + README.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-09 12:21:47 +02:00

56 lines
2.3 KiB
Python

"""End-to-end vertical slice with a FAKE data provider (no network) + temp SQLite + temp DuckDB.
Proves data → backtest → gauntlet → persistence wires together and the domain stays infra-agnostic."""
from __future__ import annotations
import numpy as np
from fxhnt.adapters.persistence import DuckDbAnalyticalStore, SqlOperationalRepository
from fxhnt.application import ResearchService
from fxhnt.config import GauntletSettings, Settings
from fxhnt.domain.models import AssetClass, Market, PriceSeries, StrategySpec
class FakeDataProvider:
name = "fake"
def __init__(self, prices: PriceSeries) -> None:
self._prices = prices
def fetch(self, market: Market, start: str | None = None, end: str | None = None) -> PriceSeries:
return self._prices
def _trending_prices(market: Market, n: int = 2000, seed: int = 1) -> PriceSeries:
rng = np.random.default_rng(seed)
rets = rng.normal(0.0004, 0.01, n) # genuine upward drift -> trend has something real to ride
close = 100.0 * np.cumprod(1.0 + rets)
dates = tuple(f"20{10 + i // 365:02d}-{1 + (i // 30) % 12:02d}-{1 + i % 28:02d}" for i in range(n))
return PriceSeries(market=market, dates=dates, close=close)
def test_vertical_slice(tmp_path) -> None:
market = Market(symbol="TEST", asset_class=AssetClass.ETF)
settings = Settings(
operational_dsn=f"sqlite:///{tmp_path / 'op.db'}",
analytical_path=str(tmp_path / "an.duckdb"),
gauntlet=GauntletSettings(),
)
svc = ResearchService(
data=FakeDataProvider(_trending_prices(market)),
operational=SqlOperationalRepository(settings.operational_dsn),
analytical=DuckDbAnalyticalStore(settings.analytical_path),
settings=settings,
)
run = svc.evaluate_candidate(market, StrategySpec(kind="trend", params={"window": 100.0}), n_trials=1)
# the pipeline produced a coherent run
assert run.stats.n_obs > 1000
assert run.verdict.n_trials == 1
# it persisted to the operational store and round-trips
loaded = svc._op.get_run(run.run_id) # noqa: SLF001
assert loaded is not None and loaded.run_id == run.run_id
assert loaded.verdict.passed == run.verdict.passed
# the analytical store cached the prices
assert svc._an.load_prices(market) is not None