33from __future__ import annotations
44
55from datetime import date
6+ import json
7+ import os
8+ from pathlib import Path
69from typing import Any , Mapping
710
811import pandas as pd
912
1013from quant_platform_kit .strategy_lifecycle .contracts import BacktestResult
1114
12- from .orchestrator_runner import CryptoLivePoolBacktestRunner
15+ from .orchestrator_runner import CryptoLivePoolBacktestRunner , PROFILE_NAME
1316
1417
1518class InsufficientEvidenceError (RuntimeError ):
1619 """Raised when lifecycle wiring does not provide a real market panel."""
1720
1821
22+ PREFLIGHT_CONTRACT_VERSION = "crypto.lifecycle_preflight.v1"
23+ PREFLIGHT_ENV = "CRYPTO_LIFECYCLE_PREFLIGHT_ROOT"
24+
25+
26+ def load_preflight_panel () -> pd .DataFrame :
27+ configured = os .environ .get (PREFLIGHT_ENV ) or os .environ .get ("PREFLIGHT_BUNDLE_ROOT" )
28+ if not configured :
29+ raise InsufficientEvidenceError (f"{ PREFLIGHT_ENV } is required for no-arg lifecycle registration" )
30+ root = Path (configured ).expanduser ().resolve ()
31+ if not (root / "research_panel.csv.gz" ).exists ():
32+ raise InsufficientEvidenceError (f"preflight bundle missing research_panel.csv.gz: { root } " )
33+ try :
34+ manifest = json .loads ((root / "manifest.json" ).read_text (encoding = "utf-8" ))
35+ panel = pd .read_csv (root / "research_panel.csv.gz" , compression = "gzip" )
36+ except (OSError , UnicodeError , json .JSONDecodeError , ValueError ) as exc :
37+ raise InsufficientEvidenceError (f"invalid lifecycle preflight bundle: { root } " ) from exc
38+ if manifest .get ("contract_version" ) != PREFLIGHT_CONTRACT_VERSION or manifest .get ("strategy_profile" ) != PROFILE_NAME :
39+ raise InsufficientEvidenceError ("lifecycle preflight manifest mismatch" )
40+ required = {"date" , "symbol" , "in_universe" , "open" , "final_score" }
41+ if not required .issubset (panel .columns ):
42+ raise InsufficientEvidenceError ("research_panel.csv.gz missing required columns" )
43+ panel ["date" ] = pd .to_datetime (panel ["date" ], errors = "coerce" )
44+ panel ["open" ] = pd .to_numeric (panel ["open" ], errors = "coerce" )
45+ panel ["final_score" ] = pd .to_numeric (panel ["final_score" ], errors = "coerce" )
46+ if panel .empty or panel ["date" ].isna ().any () or panel ["open" ].isna ().any () or panel ["final_score" ].isna ().any ():
47+ raise InsufficientEvidenceError ("research_panel.csv.gz contains invalid numeric/date content" )
48+ if (date .today () - panel ["date" ].dt .normalize ().max ().date ()).days > 3 :
49+ raise InsufficientEvidenceError ("research panel preflight artifact is stale" )
50+ return panel .set_index (["date" , "symbol" ]).sort_index ()
51+
52+
1953class CryptoBacktestRunner :
2054 """Expose the real crypto backtest engine through the lifecycle contract.
2155
@@ -25,8 +59,8 @@ class CryptoBacktestRunner:
2559 runner used by tests/research fixtures.
2660 """
2761
28- def __init__ (self , * , panel : pd .DataFrame ) -> None :
29- self ._runner = CryptoLivePoolBacktestRunner (panel = panel )
62+ def __init__ (self , * , panel : pd .DataFrame | None = None ) -> None :
63+ self ._runner = CryptoLivePoolBacktestRunner (panel = panel if panel is not None else load_preflight_panel () )
3064
3165 def run (
3266 self ,
@@ -40,6 +74,4 @@ def run(
4074
4175def build_backtest_runner (* , panel : pd .DataFrame | None = None ) -> CryptoBacktestRunner :
4276 """Build the real adapter; lifecycle wiring must inject a prepared panel."""
43- if panel is None :
44- raise InsufficientEvidenceError ("build_backtest_runner requires a real prepared market panel" )
4577 return CryptoBacktestRunner (panel = panel )
0 commit comments