86 lines
3.6 KiB
Python
86 lines
3.6 KiB
Python
"""`fxhnt compare-measured-precompute` — the OWN-MEMORY writer of the Bybit single-book measured-cost
|
|
artifacts.
|
|
|
|
The per-coin measured-cost compute used to run LIVE in the cockpit web handler and OOM-killed the 4Gi
|
|
`fxhnt-dashboard` pod (exit 137). The compute now runs only here (own-memory Job), persisting
|
|
`compare_measured_cache`. This asserts the CLI computes + upserts the artifacts and is idempotent.
|
|
|
|
(Phase 0b venue consolidation, Task 7b: the Binance combined-book leg + the Binance-vs-Bybit COMPARE
|
|
artifact this CLI used to also persist are retired — Bybit is fxhnt's sole crypto venue; the CLI now
|
|
persists ONLY the `bybit_4edge` (+ `bybit_4edge_levered`) single-book curves.)
|
|
|
|
NO network: the operational DSN points at a temp sqlite db; the `bybit_features` warehouse + cockpit tables
|
|
are seeded on it directly.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import datetime as dt
|
|
|
|
from typer.testing import CliRunner
|
|
|
|
from fxhnt.cli import app
|
|
|
|
_DAY = 86_400
|
|
_AT = dt.datetime(2026, 6, 24, tzinfo=dt.UTC)
|
|
|
|
|
|
def _seed(dsn: str) -> None:
|
|
"""Seed a wide-spread Bybit `xsfunding` carry universe + sleeve returns on `bybit_features` — the
|
|
inputs the precompute reads."""
|
|
from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
|
|
|
|
store = TimescaleFeatureStore(dsn, table="bybit_features")
|
|
start = dt.date(2021, 1, 1)
|
|
days = 60
|
|
funding = {"AAAUSDT": 0.001, "BBBUSDT": 0.001, "XXXUSDT": -0.001, "YYYUSDT": -0.001}
|
|
for idx, (sym, fund) in enumerate(funding.items()):
|
|
rows = []
|
|
for d in range(days):
|
|
f = fund + 0.0005 * ((d + idx) % 3 - 1)
|
|
half = 0.01 # 200bp wide spread
|
|
ts = (start + dt.timedelta(days=d)).toordinal() * _DAY
|
|
rows.append((ts, {"funding": f, "close": 100.0, "spot_close": 100.0,
|
|
"high": 100.0 * (1 + half), "low": 100.0 * (1 - half),
|
|
"turnover": 50_000_000.0}))
|
|
store.write_features(sym, rows)
|
|
store.close()
|
|
|
|
|
|
def _patch(monkeypatch, tmp_path) -> str:
|
|
dsn = f"sqlite:///{tmp_path / 'op.db'}"
|
|
monkeypatch.setenv("FXHNT_OPERATIONAL_DSN", dsn)
|
|
from fxhnt import cli as climod
|
|
climod.get_settings.cache_clear()
|
|
_seed(dsn)
|
|
return dsn
|
|
|
|
|
|
def test_cli_persists_single_book_cache(monkeypatch, tmp_path) -> None:
|
|
dsn = _patch(monkeypatch, tmp_path)
|
|
res = CliRunner().invoke(app, ["compare-measured-precompute", "--capital", "100000"])
|
|
assert res.exit_code == 0, res.output
|
|
assert "persisted" in res.output.lower()
|
|
|
|
from fxhnt.adapters.persistence.paper_repo import PaperRepo
|
|
from fxhnt.application.compare_measured_cost import read_cached_single_measured
|
|
payload = read_cached_single_measured(PaperRepo(dsn), "bybit_4edge")
|
|
assert payload is not None
|
|
assert payload["book"] == "bybit_4edge"
|
|
assert payload["dates_iso"] and payload["equity"]
|
|
|
|
|
|
def test_cli_is_idempotent(monkeypatch, tmp_path) -> None:
|
|
dsn = _patch(monkeypatch, tmp_path)
|
|
runner = CliRunner()
|
|
r1 = runner.invoke(app, ["compare-measured-precompute", "--capital", "100000"])
|
|
assert r1.exit_code == 0, r1.output
|
|
from fxhnt.adapters.persistence.paper_repo import PaperRepo
|
|
from fxhnt.application.compare_measured_cost import read_cached_single_measured
|
|
first = read_cached_single_measured(PaperRepo(dsn), "bybit_4edge")
|
|
|
|
r2 = runner.invoke(app, ["compare-measured-precompute", "--capital", "100000"])
|
|
assert r2.exit_code == 0, r2.output
|
|
second = read_cached_single_measured(PaperRepo(dsn), "bybit_4edge")
|
|
assert first is not None and second is not None
|
|
assert first["equity"] == second["equity"]
|