वैलिडेशन डेटा लीक के बिना चुने हुए होल्डआउट पूर्वानुमान निर्यात
सारांश
यह यूटिलिटी लाइव-डेमो नोटबुक के लिए पूर्वानुमान आर्टिफ़ैक्ट तैयार करती है। यह अलग-अलग कॉलम नामों और तारीख प्रारूपों को एक समान टाइमस्टैम्प, परिसंपत्ति और पूर्वानुमान संरचना में सामान्यीकृत करती है। यह डुप्लिकेट टाइमस्टैम्प-परिसंपत्ति अवलोकनों को समूहित करके उनके स्कोर का औसत निकालती है। लोडर पहले कमिट किए गए मॉडल आउटपुट खोजता है, और फ़ाइलें उपलब्ध न होने पर रजिस्ट्री देखता है।
रजिस्ट्री फ़ॉलबैक दर्ज संकेत, आवंटन और जोखिम-ओवरले बैकटेस्ट में सबसे मज़बूत वैलिडेशन शार्प से जुड़े मॉडल रन को प्राथमिकता देता है और उस रन के होल्डआउट पूर्वानुमान निर्यात करता है। इससे निर्यात किए गए पूर्वानुमान कॉन्फ़िगरेशन चुनने में इस्तेमाल हुए वैलिडेशन डेटा से अलग रहते हैं, जिससे चुने मॉडल का उसी चयन नमूने पर मूल्यांकन करने का जोखिम घटता है। यदि कोई पात्र बैकटेस्ट विजेता दर्ज न हो, तो फ़ॉलबैक अनुरोधित अवधि के औसत सूचना गुणांक से होल्डआउट पूर्वानुमान सेट चुनता है। यह फ़ॉलबैक एक व्यावहारिक पुनर्प्राप्ति मार्ग है, लेकिन चयन का अलग मानदंड इस्तेमाल करता है; कोड आर्टिफ़ैक्ट लोड करने का तर्क है और यह प्रमाण नहीं देता कि कोई चुना गया मॉडल आउट-ऑफ़-सैंपल अच्छा प्रदर्शन करता है।
मुख्य विचार
- आर्टिफ़ैक्ट आगे की नोटबुक में देने से पहले पूर्वानुमान कॉलम और टाइमस्टैम्प सामान्यीकृत करें।
- बाहरी मूल्यांकन के लिए होल्डआउट पूर्वानुमानों को प्राथमिकता दें, जब मॉडल चयन में वैलिडेशन डेटा का उपयोग हुआ हो।
- मुख्य रजिस्ट्री मार्ग निर्दिष्ट बैकटेस्ट चरणों में वैलिडेशन शार्प के आधार पर प्रशिक्षण रन चुनता है।
- पात्र बैकटेस्ट चरण दर्ज न होने पर सूचना-गुणांक रैंकिंग फ़ॉलबैक देती है।
- कमिट की गई फ़ाइलें और पात्र रजिस्ट्री पूर्वानुमान, दोनों उपलब्ध न हों तो लोडर कोई आर्टिफ़ैक्ट नहीं लौटाता।
टैग
पूरा पाठ
# demo_artifacts.py
```py
from __future__ import annotations
import polars as pl
from utils.paths import get_case_study_source_dir
def normalize_demo_predictions(df: pl.DataFrame, asset_column: str) -> pl.DataFrame:
"""Normalize prediction artifacts to ``timestamp``, ``asset_column``, ``prediction``."""
rename_map = {}
# Normalize entity column: accept "asset" or "symbol" as source
if asset_column not in df.columns:
if "asset" in df.columns:
rename_map["asset"] = asset_column
elif "symbol" in df.columns:
rename_map["symbol"] = asset_column
if "prediction" not in df.columns and "y_score" in df.columns:
rename_map["y_score"] = "prediction"
# Normalize legacy "date" column to canonical "timestamp"
if "date" in df.columns and "timestamp" not in df.columns:
rename_map["date"] = "timestamp"
if rename_map:
df = df.rename(rename_map)
required = {"timestamp", asset_column, "prediction"}
if required - set(df.columns):
raise ValueError(
f"Prediction artifact missing required columns: {required - set(df.columns)}"
)
ts = df["timestamp"]
if ts.dtype == pl.Utf8:
df = df.with_columns(pl.col("timestamp").str.to_date())
elif ts.dtype in (pl.Datetime, pl.Date):
df = df.with_columns(pl.col("timestamp").dt.date())
return (
df.select(["timestamp", asset_column, "prediction"])
.group_by(["timestamp", asset_column])
.agg(pl.col("prediction").mean().alias("prediction"))
.sort([asset_column, "timestamp"])
)
def load_demo_predictions(strategy_id: str, horizon: int, asset_column: str) -> pl.DataFrame | None:
"""Load prediction artifacts for live-demo notebooks.
Searches committed model directories first, then falls back to the
content-addressed registry (run_log/predictions/) which is the
primary output of the model training pipeline. The registry fallback
returns the sealed **holdout** split — the once-touched out-of-sample
set — never validation, so a deployment export never ships predictions
from the data used to select the model.
"""
import os
import sqlite3
from pathlib import Path
source_dir = get_case_study_source_dir(strategy_id)
models_dir = source_dir / "models"
candidates = [
models_dir / "gbm" / f"fwd_ret_{horizon}d" / "predictions.parquet",
models_dir / "linear" / f"fwd_ret_{horizon}d" / "predictions.parquet",
models_dir / "deep_learning" / f"fwd_ret_{horizon}d" / "predictions.parquet",
models_dir / "tabular_dl" / f"fwd_ret_{horizon}d" / "predictions.parquet",
]
# Also check seeded predictions in ML4T_OUTPUT_DIR (CI / test mode)
output_dir = os.environ.get("ML4T_OUTPUT_DIR", "")
if output_dir:
seeded_dir = Path(output_dir) / strategy_id / "models"
candidates.append(seeded_dir / f"predictions_reg_{horizon}d.parquet")
for path in candidates:
if not path.exists():
continue
df = pl.read_parquet(path)
has_time = "date" in df.columns or "timestamp" in df.columns
has_entity = "asset" in df.columns or "symbol" in df.columns
has_score = "y_score" in df.columns or "prediction" in df.columns
if not (has_time and has_entity and has_score):
continue
return normalize_demo_predictions(df, asset_column)
# Fall back to registry — export the sealed HOLDOUT prediction set, never
# validation. A deployment bridge that exported validation predictions would
# be backtesting on the data used to select the model (leakage). The holdout
# is the once-touched out-of-sample set, so it is the only honest thing to
# ship to an external backtester.
#
# Pick that holdout by the *selected winner*, not by raw IC: the selected configuration is
# the cross-stage validation-Sharpe rank-1 config, pooling the selection
# stages (signal/allocation/risk_overlay). cost_sensitivity is a
# perturbation, not a selection axis, and the holdout stage is the sealed
# set itself, so both are excluded. The winner's training run owns exactly
# one holdout prediction set (the "one holdout per case study" rule), which
# is what we export. This also derives the label/horizon from the winner
# rather than trusting the caller's `horizon`. The IC-sorted query is kept
# only as a fallback for case studies that have predictions but no backtest
# stages recorded.
registry_db = source_dir / "run_log" / "registry.db"
if registry_db.exists():
conn = sqlite3.connect(str(registry_db))
pred_hash = None
winner = conn.execute(
"""SELECT ps.training_hash
FROM backtest_runs br
JOIN backtest_metrics bm ON br.backtest_hash = bm.backtest_hash
JOIN prediction_sets ps ON br.prediction_hash = ps.prediction_hash
WHERE br.stage IN ('signal', 'allocation', 'risk_overlay')
ORDER BY bm.sharpe DESC LIMIT 1""",
).fetchone()
if winner:
row = conn.execute(
"""SELECT prediction_hash FROM prediction_sets
WHERE training_hash = ? AND split = 'holdout'
ORDER BY prediction_hash LIMIT 1""",
(winner[0],),
).fetchone()
if row:
pred_hash = row[0]
if pred_hash is None:
label = f"fwd_ret_{horizon}d"
row = conn.execute(
"""SELECT ps.prediction_hash
FROM prediction_sets ps
JOIN training_runs tr ON ps.training_hash = tr.training_hash
JOIN prediction_metrics pm ON ps.prediction_hash = pm.prediction_hash
WHERE tr.label = ? AND ps.split = 'holdout'
ORDER BY pm.ic_mean DESC LIMIT 1""",
(label,),
).fetchone()
if row:
pred_hash = row[0]
conn.close()
if pred_hash:
pred_path = source_dir / "run_log" / "predictions" / pred_hash / "predictions.parquet"
if pred_path.exists():
df = pl.read_parquet(pred_path)
return normalize_demo_predictions(df, asset_column)
return None
```स्रोत के लाइसेंस के तहत श्रेय सहित पूरा पाठ दिखाया गया है। लाइसेंस: MIT
यह सारांश मूल स्रोत के आधार पर Stratmill के शोध एजेंट ने लिखा है; यह स्रोत की प्रति नहीं है।