From 7d3b758498317fbc513aa88be7b00c94fcdfa88d Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:17:56 +0800 Subject: [PATCH 1/4] feat(risk): stamp consecutive_losses before strategy evaluate Reuse QPK stamp helper so entrypoint circuit breakers see trailing loss streak from live equity history. Co-Authored-By: Claude Co-authored-by: Cursor --- pyproject.toml | 2 +- qsl.toml | 2 +- strategy_runtime.py | 14 ++++++- tests/test_strategy_runtime.py | 70 ++++++++++++++++++++++++++++++++++ uv.lock | 4 +- 5 files changed, 86 insertions(+), 6 deletions(-) create mode 100644 tests/test_strategy_runtime.py diff --git a/pyproject.toml b/pyproject.toml index e0c7867..9666947 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,7 @@ authors = [{ name = "QuantStrategyLab" }] dependencies = [ "flask>=3.0", "pandas>=2.0", - "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@37c81901160c5b31127a27dba1c63944933fb6bf", + "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@69a0256934d081b5ef309a885384b9eb9f62cf90", "cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@73844e92a8570a61e5a9dc6c245809d0b27b89bc", ] diff --git a/qsl.toml b/qsl.toml index d3882b0..153400f 100644 --- a/qsl.toml +++ b/qsl.toml @@ -6,7 +6,7 @@ allow_legacy = false enforce_bundle = true [qsl.requires] -quant_platform_kit = "37c81901160c5b31127a27dba1c63944933fb6bf" +quant_platform_kit = "69a0256934d081b5ef309a885384b9eb9f62cf90" cn_equity_strategies = "73844e92a8570a61e5a9dc6c245809d0b27b89bc" [qsl.compat] 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_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..40b3d85 100644 --- a/uv.lock +++ b/uv.lock @@ -454,14 +454,14 @@ requires-dist = [ { 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#37c81901160c5b31127a27dba1c63944933fb6bf" } [[package]] name = "six" From 67007f9bc9732e6ad82bd9790a8f27648a7064cc Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:24:46 +0800 Subject: [PATCH 2/4] fix(deps): correct QPK uv.lock revision fragment Co-Authored-By: Claude Co-authored-by: Cursor --- uv.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uv.lock b/uv.lock index 40b3d85..33a8714 100644 --- a/uv.lock +++ b/uv.lock @@ -461,7 +461,7 @@ provides-extras = ["test"] [[package]] name = "quant-platform-kit" version = "0.10.0" -source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=69a0256934d081b5ef309a885384b9eb9f62cf90#37c81901160c5b31127a27dba1c63944933fb6bf" } +source = { git = "https://github.com/QuantStrategyLab/QuantPlatformKit.git?rev=69a0256934d081b5ef309a885384b9eb9f62cf90#69a0256934d081b5ef309a885384b9eb9f62cf90" } [[package]] name = "six" From 3fbdd241ba48d22c93ac5aadcc7132c6f4d51855 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:29:44 +0800 Subject: [PATCH 3/4] chore(deps): bump CN strategies pin after QPK 69a0256 Co-Authored-By: Claude Co-authored-by: Cursor --- pyproject.toml | 2 +- qsl.toml | 2 +- uv.lock | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 9666947..62597b4 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -14,7 +14,7 @@ dependencies = [ "flask>=3.0", "pandas>=2.0", "quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@69a0256934d081b5ef309a885384b9eb9f62cf90", - "cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@73844e92a8570a61e5a9dc6c245809d0b27b89bc", + "cn-equity-strategies @ git+https://github.com/QuantStrategyLab/CnEquityStrategies.git@12c0cd4801060fcb2f9452ffd9a7f48df446ddd0", ] [project.optional-dependencies] diff --git a/qsl.toml b/qsl.toml index 153400f..02b0e2f 100644 --- a/qsl.toml +++ b/qsl.toml @@ -7,7 +7,7 @@ enforce_bundle = true [qsl.requires] quant_platform_kit = "69a0256934d081b5ef309a885384b9eb9f62cf90" -cn_equity_strategies = "73844e92a8570a61e5a9dc6c245809d0b27b89bc" +cn_equity_strategies = "12c0cd4801060fcb2f9452ffd9a7f48df446ddd0" [qsl.compat] bundle = "2026.07.2" diff --git a/uv.lock b/uv.lock index 33a8714..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,7 +450,7 @@ 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" }, From 364ae681afda5ec689e1ec492f12808252ab7238 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Fri, 10 Jul 2026 07:31:04 +0800 Subject: [PATCH 4/4] test(qsl): align metadata pin expectations with QPK/CN bump Co-Authored-By: Claude Co-authored-by: Cursor --- tests/test_qsl_metadata.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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"