diff --git a/src/fxhnt/application/combined_book_source.py b/src/fxhnt/application/combined_book_source.py new file mode 100644 index 0000000..4f5f49c --- /dev/null +++ b/src/fxhnt/application/combined_book_source.py @@ -0,0 +1,34 @@ +"""Data source for the combined book — the A→B seam. The book reads its crypto panel + futures series +through this port; RawFileSource is the existing raw-file path, WarehouseSource (next task) reads the SSOT.""" +from __future__ import annotations + +from typing import Protocol + +import numpy as np + +from fxhnt.adapters.data.dbn_local import LocalDbnLoader +from fxhnt.application.crypto_momentum_research import load_crypto_panel +from fxhnt.domain.models import PriceSeries + + +class CombinedBookSource(Protocol): + def crypto_panel(self) -> tuple[list[int], list[str], np.ndarray, np.ndarray]: + """(days, syms, close[d×s], ret[d×s]) — same shape as load_crypto_panel.""" + ... + + def futures_series(self, root: str) -> PriceSeries: + """Continuous front-month PriceSeries for a futures root.""" + ... + + +class RawFileSource: + def __init__(self, data_dir: str, crypto_subdir: str = "crypto_pit") -> None: + self._dir = data_dir + self._sub = crypto_subdir + self._loader = LocalDbnLoader() + + def crypto_panel(self) -> tuple[list[int], list[str], np.ndarray, np.ndarray]: + return load_crypto_panel(f"{self._dir}/{self._sub}") + + def futures_series(self, root: str) -> PriceSeries: + return self._loader.load(f"{self._dir}/{root}.dbn", root) diff --git a/tests/unit/test_combined_book_source.py b/tests/unit/test_combined_book_source.py new file mode 100644 index 0000000..d58e0e2 --- /dev/null +++ b/tests/unit/test_combined_book_source.py @@ -0,0 +1,14 @@ +"""CombinedBookSource: RawFileSource reproduces load_crypto_panel; WarehouseSource (next task) matches it.""" +from __future__ import annotations + +import numpy as np + + +def test_raw_file_source_crypto_panel_matches_loader(tmp_path) -> None: + from fxhnt.application.combined_book_source import RawFileSource + cp = tmp_path / "crypto_pit"; cp.mkdir() + np.savez(cp / "BTCUSDT.npz", day=np.array([19000, 19001]), close=np.array([100.0, 110.0])) + np.savez(cp / "ETHUSDT.npz", day=np.array([19001]), close=np.array([50.0])) + days, syms, close, ret = RawFileSource(str(tmp_path)).crypto_panel() + assert days == [19000, 19001] and syms == ["BTCUSDT", "ETHUSDT"] + assert close[1, 0] == 110.0 and abs(ret[1, 0] - 0.10) < 1e-12