88 lines
3.7 KiB
Python
88 lines
3.7 KiB
Python
"""B3a-deploy: the `ingest-historical` CLI entrypoint wires the historical-EOD ingest against the
|
|
DEDICATED backtest warehouse path (never the live cockpit warehouse) and honors --limit. No network:
|
|
the Tiingo clients are monkeypatched with fakes."""
|
|
from __future__ import annotations
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
from fxhnt.config import Settings
|
|
|
|
|
|
def test_settings_has_backtest_warehouse_path_default() -> None:
|
|
s = Settings()
|
|
assert s.backtest_warehouse_path == "/backtest-data/warehouse.duckdb"
|
|
|
|
|
|
def test_settings_backtest_path_env_override(monkeypatch) -> None:
|
|
monkeypatch.setenv("FXHNT_BACKTEST_WAREHOUSE_PATH", "/tmp/bt.duckdb")
|
|
s = Settings()
|
|
assert s.backtest_warehouse_path == "/tmp/bt.duckdb"
|
|
|
|
|
|
class _FakeUniverse:
|
|
"""Survivorship-free set: three members; .survivorship_free() returns (sym, exch, start, end)."""
|
|
|
|
def survivorship_free(self):
|
|
return [
|
|
("AAA", "NYSE", "1990-01-02", "2026-06-01"),
|
|
("BBB", "NASDAQ", "1995-03-01", "2010-12-31"),
|
|
("CCC", "AMEX", "2000-06-15", "2026-06-01"),
|
|
]
|
|
|
|
|
|
class _FakeBars:
|
|
def bars(self, ticker, start_date=None):
|
|
return [{"date": "2020-01-02", "adjClose": 1.0, "close": 1.0, "volume": 100.0}]
|
|
|
|
|
|
def test_ingest_historical_builds_store_at_backtest_path_and_honors_limit(tmp_path, monkeypatch) -> None:
|
|
"""--limit N restricts to the first N tickers AND the ingest writes to the configured backtest path."""
|
|
import fxhnt.cli as cli
|
|
from fxhnt.adapters.warehouse.duckdb_feature_store import DuckDbFeatureStore
|
|
|
|
bt_path = tmp_path / "backtest" / "warehouse.duckdb"
|
|
monkeypatch.setenv("FXHNT_BACKTEST_WAREHOUSE_PATH", str(bt_path))
|
|
cli.get_settings.cache_clear() # the settings are lru_cached — pick up the env override
|
|
|
|
# Patch the concrete adapters the command imports (function-local imports -> patch at source module).
|
|
monkeypatch.setattr("fxhnt.adapters.data.tiingo_daily.TiingoDailyClient", lambda *a, **k: _FakeBars())
|
|
monkeypatch.setattr("fxhnt.adapters.data.tiingo_universe.TiingoUniverseSource", lambda *a, **k: _FakeUniverse())
|
|
|
|
result = CliRunner().invoke(cli.app, ["ingest-historical", "--limit", "2"])
|
|
cli.get_settings.cache_clear()
|
|
|
|
assert result.exit_code == 0, result.output
|
|
assert bt_path.exists(), "ingest must create the warehouse at the configured backtest path"
|
|
assert "2 ingested" in result.output # only the first 2 of 3 tickers (the --limit)
|
|
|
|
# The two limited tickers are in the dedicated warehouse; the third (CCC) is not.
|
|
store = DuckDbFeatureStore(str(bt_path))
|
|
try:
|
|
symbols = {row[0] for row in store.catalog()}
|
|
finally:
|
|
store.close()
|
|
assert symbols == {"AAA", "BBB"}
|
|
|
|
|
|
def test_ingest_historical_full_set_when_no_limit(tmp_path, monkeypatch) -> None:
|
|
"""No --limit (0) ingests the FULL survivorship-free set into the backtest warehouse."""
|
|
import fxhnt.cli as cli
|
|
from fxhnt.adapters.warehouse.duckdb_feature_store import DuckDbFeatureStore
|
|
|
|
bt_path = tmp_path / "warehouse.duckdb"
|
|
monkeypatch.setenv("FXHNT_BACKTEST_WAREHOUSE_PATH", str(bt_path))
|
|
cli.get_settings.cache_clear()
|
|
monkeypatch.setattr("fxhnt.adapters.data.tiingo_daily.TiingoDailyClient", lambda *a, **k: _FakeBars())
|
|
monkeypatch.setattr("fxhnt.adapters.data.tiingo_universe.TiingoUniverseSource", lambda *a, **k: _FakeUniverse())
|
|
|
|
result = CliRunner().invoke(cli.app, ["ingest-historical"])
|
|
cli.get_settings.cache_clear()
|
|
|
|
assert result.exit_code == 0, result.output
|
|
store = DuckDbFeatureStore(str(bt_path))
|
|
try:
|
|
symbols = {row[0] for row in store.catalog()}
|
|
finally:
|
|
store.close()
|
|
assert symbols == {"AAA", "BBB", "CCC"}
|