diff --git a/pyproject.toml b/pyproject.toml index e0c7867..62597b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,8 +13,8 @@ authors = [{ name = "QuantStrategyLab" }] dependencies = [ "flask>=3.0", "pandas>=2.0", - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@37c81901160c5b31127a27dba1c63944933fb6bf", - "cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@73844e92a8570a61e5a9dc6c245809d0b27b89bc", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@69a0256934d081b5ef309a885384b9eb9f62cf90", + "cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@12c0cd4801060fcb2f9452ffd9a7f48df446ddd0", ] [project.optional-dependencies] diff --git a/qsl.toml b/qsl.toml index d3882b0..02b0e2f 100644 --- a/qsl.toml +++ b/qsl.toml @@ -6,8 +6,8 @@ allow_legacy = false enforce_bundle = true [qsl.requires] -quant_platform_kit = "37c81901160c5b31127a27dba1c63944933fb6bf" -cn_equity_strategies = "73844e92a8570a61e5a9dc6c245809d0b27b89bc" +quant_platform_kit = "69a0256934d081b5ef309a885384b9eb9f62cf90" +cn_equity_strategies = "12c0cd4801060fcb2f9452ffd9a7f48df446ddd0" [qsl.compat] bundle = "2026.07.2" diff --git a/strategy_runtime.py b/strategy_runtime.py index b98de75..078058d 100644 --- a/strategy_runtime.py +++ b/strategy_runtime.py @@ -47,10 +47,20 @@ def evaluate(self, *, available_inputs: Mapping[str, Any]) -> StrategyEvaluation runtime_config = dict(self.runtime_config) apply_runtime_policy_to_runtime_config(runtime_config, self.runtime_adapter) + stamped_inputs = dict(available_inputs) + snapshot = stamped_inputs.get("portfolio_snapshot") + if snapshot is not None: + from quant_platform_kit.strategy_lifecycle.live_equity import stamp_consecutive_losses_on_snapshot + + stamped_inputs["portfolio_snapshot"] = stamp_consecutive_losses_on_snapshot( + snapshot, + strategy_profile=self.profile, + ) + if _FEATURE_SNAPSHOT_INPUT in frozenset(self.entrypoint.manifest.required_inputs): return self._evaluate_feature_snapshot_strategy( runtime_config=runtime_config, - available_inputs=available_inputs, + available_inputs=stamped_inputs, ) as_of = datetime.now(timezone.utc) @@ -58,7 +68,7 @@ def evaluate(self, *, available_inputs: Mapping[str, Any]) -> StrategyEvaluation entrypoint=self.entrypoint, runtime_adapter=self.runtime_adapter, as_of=as_of, - available_inputs=dict(available_inputs), + available_inputs=stamped_inputs, runtime_config=runtime_config, ) decision = self.entrypoint.evaluate(ctx) diff --git a/tests/test_qsl_metadata.py b/tests/test_qsl_metadata.py index fd453ca..432a545 100644 --- a/tests/test_qsl_metadata.py +++ b/tests/test_qsl_metadata.py @@ -15,5 +15,5 @@ def test_qsl_metadata_has_runtime_platform_fields() -> None: assert qsl["enforce_bundle"] is True assert qsl["compat"]["bundle"] == "2026.07.2" requires = qsl["requires"] - assert requires["quant_platform_kit"] == "37c81901160c5b31127a27dba1c63944933fb6bf" - assert requires["cn_equity_strategies"] == "73844e92a8570a61e5a9dc6c245809d0b27b89bc" + assert requires["quant_platform_kit"] == "69a0256934d081b5ef309a885384b9eb9f62cf90" + assert requires["cn_equity_strategies"] == "12c0cd4801060fcb2f9452ffd9a7f48df446ddd0" diff --git a/tests/test_strategy_runtime.py b/tests/test_strategy_runtime.py new file mode 100644 index 0000000..d73b87e --- /dev/null +++ b/tests/test_strategy_runtime.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from unittest.mock import patch + +from quant_platform_kit.common.models import PortfolioSnapshot +from quant_platform_kit.strategy_contracts import ( + StrategyDecision, + StrategyManifest, + StrategyRuntimeAdapter, +) +from runtime_config_support import PlatformRuntimeSettings +import strategy_runtime as strategy_runtime_module + + +def _runtime_settings() -> PlatformRuntimeSettings: + return PlatformRuntimeSettings( + strategy_profile="cn_industry_etf_rotation", + strategy_display_name="CN Industry ETF Rotation", + strategy_domain="cn_equity", + dry_run_only=True, + market_history_path=None, + feature_snapshot_path=None, + feature_snapshot_manifest_path=None, + ) + + +def test_evaluate_stamps_consecutive_losses_on_portfolio_snapshot(): + class _Entrypoint: + manifest = StrategyManifest( + profile="cn_industry_etf_rotation", + domain="cn_equity", + display_name="CN Industry ETF Rotation", + description="test", + required_inputs=frozenset({"portfolio_snapshot"}), + ) + + def evaluate(self, ctx): + self.ctx = ctx + return StrategyDecision() + + entrypoint = _Entrypoint() + runtime = strategy_runtime_module.LoadedStrategyRuntime( + entrypoint=entrypoint, + runtime_adapter=StrategyRuntimeAdapter(portfolio_input_name="portfolio_snapshot"), + runtime_settings=_runtime_settings(), + ) + snapshot = PortfolioSnapshot( + as_of=datetime.now(timezone.utc), + total_equity=100_000.0, + positions=(), + metadata={}, + ) + stamped = PortfolioSnapshot( + as_of=snapshot.as_of, + total_equity=snapshot.total_equity, + positions=(), + metadata={"consecutive_losses": 3}, + ) + + with patch( + "quant_platform_kit.strategy_lifecycle.live_equity.stamp_consecutive_losses_on_snapshot", + return_value=stamped, + ) as stamp: + result = runtime.evaluate(available_inputs={"portfolio_snapshot": snapshot}) + + stamp.assert_called_once() + assert entrypoint.ctx.portfolio is stamped + assert entrypoint.ctx.portfolio.metadata["consecutive_losses"] == 3 + assert result.metadata["strategy_profile"] == "cn_industry_etf_rotation" diff --git a/uv.lock b/uv.lock index 9d0bf0e..042f7a1 100644 --- a/uv.lock +++ b/uv.lock @@ -37,7 +37,7 @@ wheels = [ [[package]] name = "cn-equity-strategies" version = "0.1.0" -source = { git = "https://github.com/QuantStrategyLab/CnEquityStrategies.git?rev=73844e92a8570a61e5a9dc6c245809d0b27b89bc#73844e92a8570a61e5a9dc6c245809d0b27b89bc" } +source = { git = "https://github.com/QuantStrategyLab/CnEquityStrategies.git?rev=12c0cd4801060fcb2f9452ffd9a7f48df446ddd0#12c0cd4801060fcb2f9452ffd9a7f48df446ddd0" } dependencies = [ { name = "pandas" }, { name = "quant-platform-kit" }, @@ -450,18 +450,18 @@ test = [ [package.metadata] requires-dist = [ - { name = "cn-equity-strategies", git = "https://github.com/QuantStrategyLab/CnEquityStrategies.git?rev=73844e92a8570a61e5a9dc6c245809d0b27b89bc" }, + { name = "cn-equity-strategies", git = "https://github.com/QuantStrategyLab/CnEquityStrategies.git?rev=12c0cd4801060fcb2f9452ffd9a7f48df446ddd0" }, { name = "flask", specifier = ">=3.0" }, { name = "pandas", specifier = ">=2.0" }, { name = "pytest", marker = "extra == 'test'", specifier = ">=8" }, - { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=37c81901160c5b31127a27dba1c63944933fb6bf" }, + { name = "quant-platform-kit", git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=69a0256934d081b5ef309a885384b9eb9f62cf90" }, ] provides-extras = ["test"] [[package]] name = "quant-platform-kit" version = "0.10.0" -source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=37c81901160c5b31127a27dba1c63944933fb6bf#37c81901160c5b31127a27dba1c63944933fb6bf" } +source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=69a0256934d081b5ef309a885384b9eb9f62cf90#69a0256934d081b5ef309a885384b9eb9f62cf90" } [[package]] name = "six"