diff --git a/src/quant_engine/artifact.py b/src/quant_engine/artifact.py index d5491bf..badc942 100644 --- a/src/quant_engine/artifact.py +++ b/src/quant_engine/artifact.py @@ -51,6 +51,7 @@ class ResearchRunArtifact: schema_version: str _run: pd.DataFrame + _signals: pd.DataFrame _nav: pd.DataFrame _trades: pd.DataFrame _positions: pd.DataFrame @@ -67,6 +68,10 @@ class ResearchRunArtifact: def nav(self) -> pd.DataFrame: return _frame_copy(self._nav) + @property + def signals(self) -> pd.DataFrame: + return _frame_copy(self._signals) + @property def trades(self) -> pd.DataFrame: return _frame_copy(self._trades) @@ -95,6 +100,7 @@ class ResearchRunArtifact: """Return isolated table snapshots keyed by stable logical table name.""" return { "run": self.run, + "signals": self.signals, "nav": self.nav, "trades": self.trades, "positions": self.positions, @@ -140,6 +146,7 @@ class ResearchRunArtifact: def _internal_table_frames(self) -> Mapping[str, pd.DataFrame]: return { "run": self._run, + "signals": self._signals, "nav": self._nav, "trades": self._trades, "positions": self._positions, @@ -234,6 +241,34 @@ def _build_nav( return nav +def _build_signals(result: FactorBacktestResult, run_id: str) -> pd.DataFrame: + columns = [ + "run_id", + "signal_date", + "execution_date", + "asset_id", + "factor_score", + "target_weight", + ] + rows: list[dict[str, object]] = [] + for signal_date, scores in result.factor_scores.iterrows(): + execution_date = pd.Timestamp(result.schedule.signal_to_execution.at[signal_date]).date() + for asset, score in scores.items(): + rows.append( + { + "run_id": run_id, + "signal_date": pd.Timestamp(signal_date).date(), + "execution_date": execution_date, + "asset_id": asset, + "factor_score": float(score), + "target_weight": float( + result.schedule.decision_weights.at[signal_date, asset] + ), + } + ) + return pd.DataFrame(rows, columns=columns) + + def _build_trades(result: FactorBacktestResult, run_id: str) -> pd.DataFrame: trades = result.execution.trades_frame.copy(deep=True) trades.insert(0, "run_id", run_id) @@ -445,6 +480,7 @@ def build_research_run_artifact( return ResearchRunArtifact( schema_version=RESEARCH_ARTIFACT_SCHEMA_VERSION, _run=run, + _signals=_build_signals(result, normalized_run_id), _nav=_build_nav(result, normalized_run_id, benchmark_returns), _trades=_build_trades(result, normalized_run_id), _positions=_build_positions(result, normalized_run_id),