feat(orchestration): fold bybit precompute into a Dagster asset (K8sRunLauncher-ready)
Move _persist_bybit_book + _BYBIT_BOOK_SIDS to application/bybit_book_persist.py (single source of truth; breaks the cli<->assets coupling). Add a new nightly asset bybit_book_precompute (deps=[bybit_warehouse_refresh]) that wraps the same helper backtest-refs/bybit-persist-sleeve-ret run — writing bybit_sleeve_ret + the report-kind gate refs in one compute-once pass. Tagged dagster-k8s/config 6Gi req / 8Gi limit so K8sRunLauncher runs it in its own right-sized Job (never the 4Gi daemon). Wired into combined_book_job + defs; tests updated (13->14 assets + presence + wiring). CLI commands left intact (deleted at the cron layer later). 60 tests pass. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -781,6 +781,34 @@ def bybit_warehouse_refresh(context: AssetExecutionContext) -> dict: # type: ig
|
||||
store.close()
|
||||
|
||||
|
||||
@asset(
|
||||
deps=[bybit_warehouse_refresh],
|
||||
op_tags={"dagster-k8s/config": {"container_config": {"resources": {
|
||||
"requests": {"memory": "6Gi"}, "limits": {"memory": "8Gi"}}}}},
|
||||
)
|
||||
def bybit_book_precompute(context: AssetExecutionContext) -> dict: # type: ignore[type-arg]
|
||||
"""NIGHTLY heavy precompute of the Bybit 4-edge book's scheduled data — the SINGLE asset that folds in the
|
||||
retired `compare-measured-precompute` + `backtest-refs` CronJobs. Runs AFTER `bybit_warehouse_refresh` on
|
||||
the LIQUID `bybit_features` universe and, in ONE compute-once pass over the 4 deploy sleeves, writes BOTH
|
||||
the `bybit_sleeve_ret` cockpit-sim precompute AND the `_BYBIT_BOOK_SIDS` report-kind reconciliation-gate
|
||||
refs (bybit_4edge / bybit_4edge_levered / positioning / xsfunding / unlock). Thin wrapper over the shared
|
||||
`_persist_bybit_book` helper — the SAME logic `backtest-refs --all` and `bybit-persist-sleeve-ret` run —
|
||||
so there is exactly one source of the persist logic.
|
||||
|
||||
HEAVY compute — must NOT run in the 4Gi dagster daemon (inline in `bybit_4edge_nav` it OOM-killed the
|
||||
daemon, exit 137, and — OOM being uncatchable — skipped the forward-nav step). It is tagged for a
|
||||
high-memory per-run K8s Job (K8sRunLauncher isolates it in its own pod, `op_tags` requests 6Gi/limits
|
||||
8Gi), so the daemon never carries the compute. `_persist_bybit_book` is imported lazily inside the body
|
||||
(assets.py stays import-light, matching the other assets)."""
|
||||
from fxhnt.application.bybit_book_persist import _persist_bybit_book
|
||||
from fxhnt.config import get_settings
|
||||
|
||||
n, scope = _persist_bybit_book(get_settings())
|
||||
context.log.info(f"bybit_book_precompute: persisted {n} bybit_sleeve_ret row(s) + the report-kind "
|
||||
f"reconciliation-gate refs from {scope}")
|
||||
return {"bybit_sleeve_ret_rows": n, "scope": scope}
|
||||
|
||||
|
||||
@asset
|
||||
def deribit_funding_bars(context: AssetExecutionContext) -> dict: # type: ignore[type-arg]
|
||||
"""NIGHTLY incremental ingest of Deribit perp DAILY funding into `bybit_features` (feature
|
||||
|
||||
@@ -17,6 +17,7 @@ from dagster import DefaultScheduleStatus, Definitions, ScheduleDefinition, defi
|
||||
from fxhnt.adapters.orchestration.assets import (
|
||||
bybit_4edge_levered_nav,
|
||||
bybit_4edge_nav,
|
||||
bybit_book_precompute,
|
||||
bybit_paper_book,
|
||||
bybit_warehouse_refresh,
|
||||
cockpit_forward,
|
||||
@@ -35,7 +36,8 @@ combined_book_job = define_asset_job(
|
||||
selection=[
|
||||
futures_bars, sixtyforty_nav,
|
||||
xsfunding_nav, unlock_nav, multistrat_nav, multistrat_levered_nav,
|
||||
deribit_funding_bars, bybit_warehouse_refresh, bybit_4edge_nav, bybit_4edge_levered_nav,
|
||||
deribit_funding_bars, bybit_warehouse_refresh, bybit_book_precompute,
|
||||
bybit_4edge_nav, bybit_4edge_levered_nav,
|
||||
positioning_nav, bybit_paper_book, cockpit_forward,
|
||||
],
|
||||
)
|
||||
@@ -53,7 +55,8 @@ defs = Definitions(
|
||||
assets=[
|
||||
futures_bars, sixtyforty_nav,
|
||||
xsfunding_nav, unlock_nav, multistrat_nav, multistrat_levered_nav,
|
||||
deribit_funding_bars, bybit_warehouse_refresh, bybit_4edge_nav, bybit_4edge_levered_nav,
|
||||
deribit_funding_bars, bybit_warehouse_refresh, bybit_book_precompute,
|
||||
bybit_4edge_nav, bybit_4edge_levered_nav,
|
||||
positioning_nav, bybit_paper_book, cockpit_forward,
|
||||
],
|
||||
jobs=[combined_book_job],
|
||||
|
||||
121
src/fxhnt/application/bybit_book_persist.py
Normal file
121
src/fxhnt/application/bybit_book_persist.py
Normal file
@@ -0,0 +1,121 @@
|
||||
"""Single source of the Bybit 4-edge book's scheduled-data persist logic.
|
||||
|
||||
`_persist_bybit_book` precomputes the deploy sleeves' full daily return series ONCE (liquid
|
||||
`bybit_features` universe) and, from that SAME series, writes BOTH the `bybit_sleeve_ret` cockpit-sim
|
||||
precompute AND the `_BYBIT_BOOK_SIDS` report-kind reconciliation-gate refs. It lives here (a neutral
|
||||
application module) — NOT in the CLI composition root — so its three callers can share it without any of
|
||||
them dragging in the other's import graph:
|
||||
|
||||
* `fxhnt bybit-persist-sleeve-ret` / `fxhnt backtest-refs --all` (the CLI commands);
|
||||
* the nightly `bybit_book_precompute` Dagster asset (runs in an own-memory per-run K8s Job).
|
||||
|
||||
Keeping it out of `cli.py` matters: `cli.py` is the composition root (`from __future__ import annotations`
|
||||
+ heavy top-level typer/broker/databento imports) AND `_persist_bybit_book` imports back INTO
|
||||
`fxhnt.adapters.orchestration.assets` — so an asset importing it from `cli.py` would both pull the whole
|
||||
CLI chain into the import-light daemon graph and route through that awkward cli↔assets coupling. This
|
||||
module has neither problem.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
# The report-kind sids covering the bybit 4-edge book (+ its standalone single-sleeve tracks) — handled as a
|
||||
# compute-once batch by `_persist_bybit_book`, NOT by the generic per-sid `backtest_ref` loop in
|
||||
# `backtest_refs_cmd`. `xsfunding`/`unlock` joined 2026-07 (Phase 0b venue consolidation: their own Bybit
|
||||
# forward tracks, like `positioning`) — all three sleeves are already computed as part of
|
||||
# `_DEFAULT_BYBIT_SLEEVES` below, so this is a routing change only, no new compute.
|
||||
_BYBIT_BOOK_SIDS = ("bybit_4edge", "bybit_4edge_levered", "positioning", "xsfunding", "unlock")
|
||||
|
||||
|
||||
def _persist_bybit_book(settings, *, min_dollar_vol: float = 10_000_000.0,
|
||||
cost_bps: float | None = None) -> tuple[int, str]:
|
||||
"""Compute-once writer of the bybit 4-edge book's scheduled data. Precomputes the 4 sleeve return
|
||||
series ONCE (liquid `bybit_features` universe), then (a) persists them to `bybit_sleeve_ret` (the
|
||||
cockpit sim's precompute) and (b) upserts the `_BYBIT_BOOK_SIDS` report-kind reconciliation-gate refs
|
||||
(bybit_4edge / bybit_4edge_levered / positioning / xsfunding / unlock) from the SAME series. Single source of the
|
||||
bybit-book persist logic, shared by the on-demand `bybit-persist-sleeve-ret` command, the scheduled
|
||||
`backtest-refs --all`, and the nightly `bybit_book_precompute` asset (own-memory per-run K8s Job).
|
||||
|
||||
Why an application helper, not the nightly daemon asset body: this is the heavy 4-sleeve compute. Wired
|
||||
INLINE into the nightly `bybit_4edge_nav` asset it OOM-killed the 4Gi dagster daemon (exit 137) — and
|
||||
because OOM is uncatchable the try/except guard could not save the forward-nav step either, breaking the
|
||||
forward track. So this writer runs in its OWN memory: the `bybit_book_precompute` asset is tagged for a
|
||||
high-memory per-run K8s Job (K8sRunLauncher), and it is ALSO reachable via `backtest-refs --all` /
|
||||
`bybit-persist-sleeve-ret` for an out-of-band run.
|
||||
|
||||
MEMORY-BOUNDED: only the `--min-dollar-vol` liquid subset is READ (never the full ~794-coin table).
|
||||
IDEMPOTENT: (run_date, sleeve) primary-key overwrite — re-running on the same data rewrites the same rows.
|
||||
unlock needs the DefiLlama calendar (DB-first, JSON fallback); absent → the unlock sleeve is simply
|
||||
omitted, never fabricated.
|
||||
|
||||
cost_bps defaults to `settings.bybit_cost_bps` (None sentinel) — the SAME single-sourced cost the
|
||||
forward tracks charge, so this ref can never silently diverge from what the tracks actually pay."""
|
||||
import datetime as dt
|
||||
import logging
|
||||
|
||||
from fxhnt.adapters.orchestration.assets import _data_dir, persist_bybit_sleeve_returns
|
||||
from fxhnt.adapters.persistence.paper_repo import PaperRepo
|
||||
from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
|
||||
from fxhnt.application.bybit_liquidity import liquid_universe
|
||||
from fxhnt.application.unlock_calendar_loader import load_unlock_events, operational_unlock_repo
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
cost_bps = settings.bybit_cost_bps if cost_bps is None else cost_bps
|
||||
store = TimescaleFeatureStore(settings.operational_dsn, table="bybit_features")
|
||||
try:
|
||||
universe = liquid_universe(store, min_dollar_vol=min_dollar_vol) or None
|
||||
try:
|
||||
unlock_events = load_unlock_events(operational_unlock_repo(settings),
|
||||
f"{_data_dir()}/unlock_calendar.json")
|
||||
except Exception as exc: # noqa: BLE001 — absent calendar → unlock sleeve omitted, not a crash
|
||||
log.warning("_persist_bybit_book: could not load unlock calendar: %s", exc)
|
||||
unlock_events = []
|
||||
|
||||
# COMPUTE-ONCE: the 4 deploy sleeves' full backtest series are the SAME input the row-persist and BOTH
|
||||
# backtest references (naive + levered) need. Compute them ONE time here and reuse for all three, instead
|
||||
# of recomputing the heavy multi-symbol walk-forward 3x (the old cost). The positioning capturability
|
||||
# haircuts (capacity + tail-cap) are baked into the positioning series HERE so every downstream
|
||||
# reference (bybit_4edge, levered, standalone positioning) reconciles against the capturable book.
|
||||
from fxhnt.application.bybit_book_eval import _DEFAULT_BYBIT_SLEEVES, sleeve_returns_from_store
|
||||
from fxhnt.application.bybit_forward_track import POSITIONING_TAIL_CAP_K
|
||||
sleeve_rets = {
|
||||
s: sleeve_returns_from_store(store, s, universe=universe, cost_bps=cost_bps,
|
||||
unlock_events=unlock_events,
|
||||
capacity_capital=settings.paper_capital,
|
||||
tail_cap_k=POSITIONING_TAIL_CAP_K)
|
||||
for s in _DEFAULT_BYBIT_SLEEVES}
|
||||
|
||||
repo = PaperRepo(settings.operational_dsn)
|
||||
repo.migrate()
|
||||
n = persist_bybit_sleeve_returns(
|
||||
repo, store, at=dt.datetime.now(dt.UTC), universe=universe,
|
||||
cost_bps=cost_bps, unlock_events=unlock_events, sleeve_returns=sleeve_rets)
|
||||
|
||||
# Persist the `_BYBIT_BOOK_SIDS` report-kind reconciliation-gate refs (bybit_4edge naive-eqwt / levered
|
||||
# / standalone positioning / standalone xsfunding / standalone unlock) from the SAME precomputed sleeve
|
||||
# series — so the reconciliation gate has an `expected_window_return` to reconcile the forward against.
|
||||
# Without it the gate's divergence band is silently skipped and it degrades to a bare min-days +
|
||||
# clean-exec timer — a false-PASS risk on the real-capital book. `bybit_report_sid_kwargs` is the SINGLE
|
||||
# source of the per-sid identity kwargs; for bybit_4edge/bybit_4edge_levered/positioning it is ALSO
|
||||
# shared with `report_backtest_summary` (backtest_refs_report.py) so the two can never drift apart —
|
||||
# xsfunding/unlock are NOT in that module's `_REPORT_SIDS` (their refs are only ever computed here,
|
||||
# gated out of the generic per-sid loop below by `sid in _BYBIT_BOOK_SIDS`).
|
||||
# positioning's/xsfunding's/unlock's OWN forward tracks use BybitFourEdgeStrategy(sleeves=[<sleeve>]) —
|
||||
# the SAME single-sleeve construction (no vol overlay), so their reconciliation-gate ref is that book's
|
||||
# summary keyed as their own sid. crypto_tstrend / stablecoin have DIFFERENT forward constructions than
|
||||
# their raw sleeve series, so their refs are persisted from their own strategies in
|
||||
# `_persist_track_backtest_ref` (their nav assets), NOT here.
|
||||
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
|
||||
from fxhnt.application.bybit_forward_track import bybit_4edge_backtest_summary, bybit_report_sid_kwargs
|
||||
fwd_repo = ForwardNavRepo(settings.operational_dsn)
|
||||
fwd_repo.migrate()
|
||||
for sid in _BYBIT_BOOK_SIDS:
|
||||
bt = bybit_4edge_backtest_summary(
|
||||
store, universe=universe, cost_bps=cost_bps, unlock_events=unlock_events,
|
||||
sleeve_returns=sleeve_rets, **bybit_report_sid_kwargs(sid))
|
||||
fwd_repo.upsert_backtest_summary(bt, at=dt.datetime.now(dt.UTC).replace(tzinfo=None))
|
||||
log.info("_persist_bybit_book: %s backtest ref cagr=%.1f%% sharpe=%.2f -> backtest_summary",
|
||||
sid, bt.cagr * 100, bt.sharpe)
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
scope = f"{len(universe)} liquid symbols" if universe else "full universe (no liquid subset)"
|
||||
return n, scope
|
||||
104
src/fxhnt/cli.py
104
src/fxhnt/cli.py
@@ -18,6 +18,7 @@ from fxhnt.application import (
|
||||
PortfolioEvaluationService,
|
||||
ResearchService,
|
||||
)
|
||||
from fxhnt.application.bybit_book_persist import _BYBIT_BOOK_SIDS, _persist_bybit_book
|
||||
from fxhnt.config import Settings, get_settings
|
||||
from fxhnt.domain.models import AssetClass, Market, StrategySpec
|
||||
from fxhnt.domain.portfolio import Book, StrategyAllocation, book_from_runs
|
||||
@@ -2958,109 +2959,6 @@ def bybit_book_eval(
|
||||
"universe is GLOBAL; live Bybit-EU (MiCA) trades a SMALLER subset, so this is an upper bound.")
|
||||
|
||||
|
||||
# The report-kind sids covering the bybit 4-edge book (+ its standalone single-sleeve tracks) — handled as a
|
||||
# compute-once batch by `_persist_bybit_book`, NOT by the generic per-sid `backtest_ref` loop in
|
||||
# `backtest_refs_cmd`. `xsfunding`/`unlock` joined 2026-07 (Phase 0b venue consolidation: their own Bybit
|
||||
# forward tracks, like `positioning`) — all three sleeves are already computed as part of
|
||||
# `_DEFAULT_BYBIT_SLEEVES` below, so this is a routing change only, no new compute.
|
||||
_BYBIT_BOOK_SIDS = ("bybit_4edge", "bybit_4edge_levered", "positioning", "xsfunding", "unlock")
|
||||
|
||||
|
||||
def _persist_bybit_book(settings, *, min_dollar_vol: float = 10_000_000.0,
|
||||
cost_bps: float | None = None) -> tuple[int, str]:
|
||||
"""Compute-once writer of the bybit 4-edge book's scheduled data. Precomputes the 4 sleeve return
|
||||
series ONCE (liquid `bybit_features` universe), then (a) persists them to `bybit_sleeve_ret` (the
|
||||
cockpit sim's precompute) and (b) upserts the `_BYBIT_BOOK_SIDS` report-kind reconciliation-gate refs
|
||||
(bybit_4edge / bybit_4edge_levered / positioning / xsfunding / unlock) from the SAME series. Single source of the
|
||||
bybit-book persist logic, shared by the on-demand `bybit-persist-sleeve-ret` command and the scheduled
|
||||
`backtest-refs --all`. Returns (rows_written, scope_desc).
|
||||
|
||||
Why a CLI helper, not the nightly daemon asset: this is the heavy 4-sleeve compute. Wired into the
|
||||
nightly `bybit_4edge_nav` asset it OOM-killed the 4Gi dagster daemon (exit 137) — and because OOM is
|
||||
uncatchable the try/except guard could not save the forward-nav step either, breaking the forward track.
|
||||
So this writer runs in its OWN-MEMORY CronJob (`infra/k8s/jobs/fxhnt-backtest-refs.yaml`, scheduled
|
||||
weekly) or on-demand via `bybit-persist-sleeve-ret`.
|
||||
|
||||
MEMORY-BOUNDED: only the `--min-dollar-vol` liquid subset is READ (never the full ~794-coin table).
|
||||
IDEMPOTENT: (run_date, sleeve) primary-key overwrite — re-running on the same data rewrites the same rows.
|
||||
unlock needs the DefiLlama calendar (DB-first, JSON fallback); absent → the unlock sleeve is simply
|
||||
omitted, never fabricated.
|
||||
|
||||
cost_bps defaults to `settings.bybit_cost_bps` (None sentinel) — the SAME single-sourced cost the
|
||||
forward tracks charge, so this ref can never silently diverge from what the tracks actually pay."""
|
||||
import datetime as dt
|
||||
import logging
|
||||
|
||||
from fxhnt.adapters.orchestration.assets import _data_dir, persist_bybit_sleeve_returns
|
||||
from fxhnt.adapters.persistence.paper_repo import PaperRepo
|
||||
from fxhnt.adapters.warehouse.timescale_feature_store import TimescaleFeatureStore
|
||||
from fxhnt.application.bybit_liquidity import liquid_universe
|
||||
from fxhnt.application.unlock_calendar_loader import load_unlock_events, operational_unlock_repo
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
cost_bps = settings.bybit_cost_bps if cost_bps is None else cost_bps
|
||||
store = TimescaleFeatureStore(settings.operational_dsn, table="bybit_features")
|
||||
try:
|
||||
universe = liquid_universe(store, min_dollar_vol=min_dollar_vol) or None
|
||||
try:
|
||||
unlock_events = load_unlock_events(operational_unlock_repo(settings),
|
||||
f"{_data_dir()}/unlock_calendar.json")
|
||||
except Exception as exc: # noqa: BLE001 — absent calendar → unlock sleeve omitted, not a crash
|
||||
log.warning("_persist_bybit_book: could not load unlock calendar: %s", exc)
|
||||
unlock_events = []
|
||||
|
||||
# COMPUTE-ONCE: the 4 deploy sleeves' full backtest series are the SAME input the row-persist and BOTH
|
||||
# backtest references (naive + levered) need. Compute them ONE time here and reuse for all three, instead
|
||||
# of recomputing the heavy multi-symbol walk-forward 3x (the old cost). The positioning capturability
|
||||
# haircuts (capacity + tail-cap) are baked into the positioning series HERE so every downstream
|
||||
# reference (bybit_4edge, levered, standalone positioning) reconciles against the capturable book.
|
||||
from fxhnt.application.bybit_book_eval import _DEFAULT_BYBIT_SLEEVES, sleeve_returns_from_store
|
||||
from fxhnt.application.bybit_forward_track import POSITIONING_TAIL_CAP_K
|
||||
sleeve_rets = {
|
||||
s: sleeve_returns_from_store(store, s, universe=universe, cost_bps=cost_bps,
|
||||
unlock_events=unlock_events,
|
||||
capacity_capital=settings.paper_capital,
|
||||
tail_cap_k=POSITIONING_TAIL_CAP_K)
|
||||
for s in _DEFAULT_BYBIT_SLEEVES}
|
||||
|
||||
repo = PaperRepo(settings.operational_dsn)
|
||||
repo.migrate()
|
||||
n = persist_bybit_sleeve_returns(
|
||||
repo, store, at=dt.datetime.now(dt.UTC), universe=universe,
|
||||
cost_bps=cost_bps, unlock_events=unlock_events, sleeve_returns=sleeve_rets)
|
||||
|
||||
# Persist the `_BYBIT_BOOK_SIDS` report-kind reconciliation-gate refs (bybit_4edge naive-eqwt / levered
|
||||
# / standalone positioning / standalone xsfunding / standalone unlock) from the SAME precomputed sleeve
|
||||
# series — so the reconciliation gate has an `expected_window_return` to reconcile the forward against.
|
||||
# Without it the gate's divergence band is silently skipped and it degrades to a bare min-days +
|
||||
# clean-exec timer — a false-PASS risk on the real-capital book. `bybit_report_sid_kwargs` is the SINGLE
|
||||
# source of the per-sid identity kwargs; for bybit_4edge/bybit_4edge_levered/positioning it is ALSO
|
||||
# shared with `report_backtest_summary` (backtest_refs_report.py) so the two can never drift apart —
|
||||
# xsfunding/unlock are NOT in that module's `_REPORT_SIDS` (their refs are only ever computed here,
|
||||
# gated out of the generic per-sid loop below by `sid in _BYBIT_BOOK_SIDS`).
|
||||
# positioning's/xsfunding's/unlock's OWN forward tracks use BybitFourEdgeStrategy(sleeves=[<sleeve>]) —
|
||||
# the SAME single-sleeve construction (no vol overlay), so their reconciliation-gate ref is that book's
|
||||
# summary keyed as their own sid. crypto_tstrend / stablecoin have DIFFERENT forward constructions than
|
||||
# their raw sleeve series, so their refs are persisted from their own strategies in
|
||||
# `_persist_track_backtest_ref` (their nav assets), NOT here.
|
||||
from fxhnt.adapters.persistence.forward_nav import ForwardNavRepo
|
||||
from fxhnt.application.bybit_forward_track import bybit_4edge_backtest_summary, bybit_report_sid_kwargs
|
||||
fwd_repo = ForwardNavRepo(settings.operational_dsn)
|
||||
fwd_repo.migrate()
|
||||
for sid in _BYBIT_BOOK_SIDS:
|
||||
bt = bybit_4edge_backtest_summary(
|
||||
store, universe=universe, cost_bps=cost_bps, unlock_events=unlock_events,
|
||||
sleeve_returns=sleeve_rets, **bybit_report_sid_kwargs(sid))
|
||||
fwd_repo.upsert_backtest_summary(bt, at=dt.datetime.now(dt.UTC).replace(tzinfo=None))
|
||||
log.info("_persist_bybit_book: %s backtest ref cagr=%.1f%% sharpe=%.2f -> backtest_summary",
|
||||
sid, bt.cagr * 100, bt.sharpe)
|
||||
finally:
|
||||
store.close()
|
||||
|
||||
scope = f"{len(universe)} liquid symbols" if universe else "full universe (no liquid subset)"
|
||||
return n, scope
|
||||
|
||||
|
||||
@app.command("honest-reference-report")
|
||||
def honest_reference_report(
|
||||
min_dollar_vol: float = typer.Option(
|
||||
|
||||
@@ -44,6 +44,11 @@ def test_definitions_load_with_assets_and_schedule() -> None:
|
||||
assert "paper_book_snapshot" not in asset_keys, "paper_book_snapshot must be retired"
|
||||
# Bybit forward paper track (out-of-sample record): nightly warehouse refresh + naive eq-wt 4-edge NAV.
|
||||
assert "bybit_warehouse_refresh" in asset_keys, f"missing bybit_warehouse_refresh: {asset_keys}"
|
||||
# bybit_book_precompute (2026-07-19): the heavy 4-sleeve precompute folded in from the retired
|
||||
# compare-measured-precompute + backtest-refs CronJobs — writes bybit_sleeve_ret (cockpit-sim precompute)
|
||||
# + the report-kind reconciliation-gate refs in one compute-once pass. Runs in an own-memory per-run K8s
|
||||
# Job (tagged 6Gi/8Gi), depends on bybit_warehouse_refresh.
|
||||
assert "bybit_book_precompute" in asset_keys, f"missing bybit_book_precompute: {asset_keys}"
|
||||
assert "bybit_4edge_nav" in asset_keys, f"missing bybit_4edge_nav: {asset_keys}"
|
||||
# observe-only levered shadow of the 4-edge book — independently recomputes from the warehouse, own state file
|
||||
assert "bybit_4edge_levered_nav" in asset_keys, f"missing bybit_4edge_levered_nav: {asset_keys}"
|
||||
@@ -60,10 +65,10 @@ def test_definitions_load_with_assets_and_schedule() -> None:
|
||||
# the leg is now CLI-driven via `run_bybit_testnet` / `execute-bybit`, not a Dagster asset.
|
||||
assert "bybit_testnet_reconcile" not in asset_keys, "bybit_testnet_reconcile must be removed"
|
||||
assert "bybit_testnet_record" not in asset_keys, "bybit_testnet_record must be removed"
|
||||
# 13 assets: futures_bars, sixtyforty_nav,
|
||||
# 14 assets: futures_bars, sixtyforty_nav,
|
||||
# xsfunding_nav, unlock_nav, multistrat_nav, multistrat_levered_nav,
|
||||
# deribit_funding_bars, bybit_warehouse_refresh, bybit_4edge_nav, bybit_4edge_levered_nav, positioning_nav,
|
||||
# bybit_paper_book, cockpit_forward
|
||||
# deribit_funding_bars, bybit_warehouse_refresh, bybit_book_precompute, bybit_4edge_nav,
|
||||
# bybit_4edge_levered_nav, positioning_nav, bybit_paper_book, cockpit_forward
|
||||
# (xvenue_carry_nav retired 2026-07-07; multistrat_nav re-added 2026-07-08;
|
||||
# bybit_testnet_reconcile/bybit_testnet_record removed 2026-07-11, Task 7;
|
||||
# combined_forward_nav retired 2026-07-11 — vestigial crypto-momentum book;
|
||||
@@ -72,8 +77,10 @@ def test_definitions_load_with_assets_and_schedule() -> None:
|
||||
# crypto_bars/crypto_funding/crypto_spot Binance ingest retired 2026-07-13, Phase 0b Task 7b;
|
||||
# paper_book_snapshot retired 2026-07-14, Phase 0b Task 7d — the dead combined-crypto book;
|
||||
# vrp_nav ARCHIVED/de-wired 2026-07-15 — VRP falsified/shelved, asset fn kept but unregistered;
|
||||
# multistrat_levered_nav added 2026-07-18 — observe-only levered shadow of the ETF book)
|
||||
assert len(asset_keys) == 13, f"expected 13 assets, got {len(asset_keys)}: {asset_keys}"
|
||||
# multistrat_levered_nav added 2026-07-18 — observe-only levered shadow of the ETF book;
|
||||
# bybit_book_precompute added 2026-07-19 — heavy 4-sleeve precompute folded in from the retired
|
||||
# compare-measured-precompute + backtest-refs CronJobs)
|
||||
assert len(asset_keys) == 14, f"expected 14 assets, got {len(asset_keys)}: {asset_keys}"
|
||||
|
||||
# bybit_paper_book is READ-ONLY against the warehouse: its ONLY upstream is bybit_warehouse_refresh.
|
||||
from dagster import AssetKey as _AK
|
||||
@@ -106,6 +113,22 @@ def test_definitions_load_with_assets_and_schedule() -> None:
|
||||
assert "vrp_nav" not in upstream, f"vrp_nav must be de-wired from cockpit_forward: {upstream}"
|
||||
|
||||
|
||||
def test_bybit_book_precompute_wired_into_job_and_defs():
|
||||
"""The heavy 4-sleeve precompute (folded in from the retired compare-measured-precompute + backtest-refs
|
||||
CronJobs) must be BOTH a registered asset AND in the nightly `combined_book_forward_job` selection — else
|
||||
it would exist but never materialize on the schedule."""
|
||||
from fxhnt.adapters.orchestration import definitions as d
|
||||
|
||||
# in the Definitions asset set
|
||||
asset_keys = {k.to_user_string() for k in d.defs.resolve_all_asset_keys()}
|
||||
assert "bybit_book_precompute" in asset_keys, f"missing from defs.assets: {asset_keys}"
|
||||
|
||||
# in the combined_book_forward_job selection (so the daily schedule materializes it)
|
||||
job_keys = {k.to_user_string() for k in d.combined_book_job.selection.resolve(list(d.defs.assets))}
|
||||
assert "bybit_book_precompute" in job_keys, (
|
||||
f"bybit_book_precompute must be in combined_book_forward_job selection: {job_keys}")
|
||||
|
||||
|
||||
def test_bybit_testnet_exec_removed_from_definitions():
|
||||
from fxhnt.adapters.orchestration.definitions import defs
|
||||
job_names = {j.name for j in defs.jobs}
|
||||
|
||||
Reference in New Issue
Block a user