From 139a9483b49b6e74cff3fc6a926f62b78047ee2a Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sun, 5 Jul 2026 21:52:53 +0800 Subject: [PATCH 1/2] register chinext growth momentum quality snapshot Co-Authored-By: Codex --- README.md | 7 ++- README.zh-CN.md | 7 ++- docs/platform_integration.md | 3 +- src/cn_equity_strategies/__init__.py | 2 + src/cn_equity_strategies/catalog.py | 62 ++++++++++++++++++- .../entrypoints/__init__.py | 41 ++++++++++++ .../manifests/__init__.py | 41 ++++++++++++ tests/test_catalog.py | 32 +++++++--- 8 files changed, 180 insertions(+), 15 deletions(-) diff --git a/README.md b/README.md index 47d4293..ffec484 100644 --- a/README.md +++ b/README.md @@ -36,12 +36,17 @@ These profiles use platform-provided `market_history` and do not require a separ | --- | --- | --- | --- | --- | | `cn_industry_etf_rotation_aggressive` | CN Industry ETF Rotation Aggressive | `market_history` | `510300` | Optional QMT live candidate; not the default runtime profile. | +### Research-only snapshot profiles + +| Profile | Name | Input | Benchmark | Current role | +| --- | --- | --- | --- | --- | +| `cn_chinext_growth_momentum_quality_snapshot` | CN ChiNext Growth Momentum Quality Snapshot | `feature_snapshot` + manifest | `399006` | Research-only ChiNext growth-momentum-quality selector. | + ### Planned external snapshot scaffolds | Profile | Name | Notes | | --- | --- | --- | | `cn_small_cap_quality_snapshot` | CN Small-Cap Quality Snapshot | External snapshot scaffold; not registered in the runtime catalog yet. | -| `cn_chinext_growth_momentum_quality_snapshot` | CN ChiNext Growth Momentum Quality Snapshot | External snapshot scaffold for a ChiNext growth-momentum-quality sleeve; not registered in the runtime catalog yet. | | `cn_chinext_tactical_rotation` | CN ChiNext Tactical Rotation | Research-only ChiNext tactical rotation line. | ## Performance and evidence boundary diff --git a/README.zh-CN.md b/README.zh-CN.md index 02fc139..f016f1b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -28,12 +28,17 @@ | --- | --- | --- | --- | --- | | `cn_industry_etf_rotation_aggressive` | CN Industry ETF Rotation Aggressive | `market_history` | `510300` | **Optional QMT live candidate**,不是默认 runtime。 | +### 研究侧 snapshot 策略 + +| Profile | 名称 | 输入 | 基准 | 当前角色 | +| --- | --- | --- | --- | --- | +| `cn_chinext_growth_momentum_quality_snapshot` | CN ChiNext Growth Momentum Quality Snapshot | `feature_snapshot` + manifest | `399006` | **研究轨**:创业板成长动量质量选择。 | + ### 计划中的 snapshot-backed 策略 | Profile | 名称 | 输入 | 基准 | 当前角色 | | --- | --- | --- | --- | --- | | `cn_small_cap_quality_snapshot` | CN Small-Cap Quality Snapshot | `feature_snapshot` + manifest | `510300` | Research scaffold,尚未 promotion。 | -| `cn_chinext_growth_momentum_quality_snapshot` | CN ChiNext Growth Momentum Quality Snapshot | `feature_snapshot` + manifest | `399006` | Research scaffold,创业板成长动量质量增强腿。 | | `cn_equity_combo` | CN Equity Combo | `market_history` + `feature_snapshot` | `510300` | Research-only 组合轨;底层 legs 需重新过关。 | | `cn_chinext_tactical_rotation` | CN ChiNext Tactical Rotation | `market_history` | `510300` | Research-only 创业板 tactical 轮动线。 | diff --git a/docs/platform_integration.md b/docs/platform_integration.md index bad514e..bc03c7c 100644 --- a/docs/platform_integration.md +++ b/docs/platform_integration.md @@ -18,6 +18,7 @@ | --- | --- | --- | --- | | `cn_index_etf_tactical_rotation` | `market_history` | `qmt` | `weight` | | `cn_dividend_quality_snapshot` | `feature_snapshot` + manifest | `qmt` | `weight` | +| `cn_chinext_growth_momentum_quality_snapshot` | `feature_snapshot` + manifest | `qmt` | `weight` | | `cn_chinext_tactical_rotation` | `market_history` | `qmt` | `weight` | | `cn_equity_combo` | `market_history` + `feature_snapshot` | `qmt` | `weight` | @@ -44,7 +45,7 @@ The strategy also requires defensive ETF history for benchmark risk-off switchin ## Planned snapshot scaffolds -`cn_small_cap_quality_snapshot` and `cn_chinext_growth_momentum_quality_snapshot` are tracked as external snapshot scaffolds only. They are not registered in the runtime catalog until `CnEquitySnapshotPipelines` publishes a contract and promotion evidence. +`cn_small_cap_quality_snapshot` remains an external snapshot scaffold. `cn_chinext_growth_momentum_quality_snapshot` is now registered as a research-only snapshot-backed profile after `CnEquitySnapshotPipelines` published its contract, but it still needs promotion evidence before any runtime enablement. ## Current lane policy diff --git a/src/cn_equity_strategies/__init__.py b/src/cn_equity_strategies/__init__.py index ddff3ed..d85698e 100644 --- a/src/cn_equity_strategies/__init__.py +++ b/src/cn_equity_strategies/__init__.py @@ -2,6 +2,7 @@ __all__ = [ "CN_EQUITY_DOMAIN", + "CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE", "CN_EXTERNAL_SNAPSHOT_SCAFFOLD_PROFILES", "CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE", "CN_CHINEXT_TACTICAL_ROTATION_PROFILE", @@ -40,6 +41,7 @@ def __getattr__(name: str): if name in { "CN_EQUITY_DOMAIN", + "CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE", "CN_EXTERNAL_SNAPSHOT_SCAFFOLD_PROFILES", "CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE", "CN_CHINEXT_TACTICAL_ROTATION_PROFILE", diff --git a/src/cn_equity_strategies/catalog.py b/src/cn_equity_strategies/catalog.py index e386994..8063514 100644 --- a/src/cn_equity_strategies/catalog.py +++ b/src/cn_equity_strategies/catalog.py @@ -16,6 +16,9 @@ ) from cn_equity_strategies.strategies import cn_dividend_quality_snapshot as dividend_quality_strategy +from cn_equity_strategies.strategies import ( + cn_chinext_growth_momentum_quality_snapshot as chinext_growth_momentum_quality_strategy, +) from cn_equity_strategies.strategies import cn_chinext_tactical_rotation as chinext_tactical_strategy from cn_equity_strategies.strategies import cn_index_etf_tactical_rotation as index_etf_strategy from cn_equity_strategies.strategies import cn_industry_etf_rotation as industry_etf_strategy @@ -28,6 +31,9 @@ CN_INDUSTRY_ETF_ROTATION_PROFILE = industry_etf_strategy.PROFILE_NAME CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE = industry_etf_aggressive_strategy.PROFILE_NAME CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE = dividend_quality_strategy.PROFILE_NAME +CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE = ( + chinext_growth_momentum_quality_strategy.PROFILE_NAME +) CN_CHINEXT_TACTICAL_ROTATION_PROFILE = chinext_tactical_strategy.PROFILE_NAME CN_EQUITY_COMBO_PROFILE = cn_combo_strategy.PROFILE_NAME @@ -38,14 +44,18 @@ STRATEGY_STATUS_SCAFFOLD = "scaffold" CN_DIRECT_MARKET_HISTORY_PROFILES = frozenset({CN_INDUSTRY_ETF_ROTATION_PROFILE}) -CN_SNAPSHOT_BACKED_PROFILES = frozenset({CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE}) +CN_SNAPSHOT_BACKED_PROFILES = frozenset( + { + CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE, + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, + } +) # Scaffold profiles are planned but not yet implemented — calling # get_strategy_definition() on them will raise ValueError. Use # get_external_snapshot_scaffold_profiles() for forward-compat iteration only. CN_EXTERNAL_SNAPSHOT_SCAFFOLD_PROFILES: frozenset[str] = frozenset( { "cn_small_cap_quality_snapshot", - "cn_chinext_growth_momentum_quality_snapshot", } ) CN_LIVE_CANDIDATE_PROFILES = frozenset({CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE}) @@ -53,6 +63,7 @@ { CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE, CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE, + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, CN_EQUITY_COMBO_PROFILE, CN_CHINEXT_TACTICAL_ROTATION_PROFILE, } @@ -68,6 +79,7 @@ CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE: frozenset({"qmt"}), CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE: frozenset({"qmt"}), CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: frozenset({"qmt"}), + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: frozenset({"qmt"}), CN_CHINEXT_TACTICAL_ROTATION_PROFILE: frozenset({"qmt"}), CN_EQUITY_COMBO_PROFILE: frozenset({"qmt"}), } @@ -77,6 +89,7 @@ CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE: frozenset({"market_history"}), CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE: frozenset({"market_history"}), CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: frozenset({"feature_snapshot"}), + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: frozenset({"feature_snapshot"}), CN_CHINEXT_TACTICAL_ROTATION_PROFILE: frozenset({"market_history"}), CN_EQUITY_COMBO_PROFILE: frozenset({"market_history", "feature_snapshot"}), } @@ -180,6 +193,27 @@ "execution_cash_reserve_ratio": dividend_quality_strategy.DEFAULT_EXECUTION_CASH_RESERVE_RATIO, "rebalance_frequency": "monthly", }, + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: { + "safe_haven": chinext_growth_momentum_quality_strategy.SAFE_HAVEN, + "holdings_count": chinext_growth_momentum_quality_strategy.DEFAULT_HOLDINGS_COUNT, + "single_name_cap": chinext_growth_momentum_quality_strategy.DEFAULT_SINGLE_NAME_CAP, + "sector_cap": chinext_growth_momentum_quality_strategy.DEFAULT_SECTOR_CAP, + "min_adv20_cny": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_ADV20_CNY, + "min_market_cap_cny": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_MARKET_CAP_CNY, + "min_revenue_yoy": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_REVENUE_YOY, + "min_profit_yoy": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_PROFIT_YOY, + "min_momentum": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_MOMENTUM, + "min_list_days": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_LIST_DAYS, + "hold_buffer": chinext_growth_momentum_quality_strategy.DEFAULT_HOLD_BUFFER, + "hold_bonus": chinext_growth_momentum_quality_strategy.DEFAULT_HOLD_BONUS, + "risk_on_exposure": chinext_growth_momentum_quality_strategy.DEFAULT_RISK_ON_EXPOSURE, + "soft_defense_exposure": chinext_growth_momentum_quality_strategy.DEFAULT_SOFT_DEFENSE_EXPOSURE, + "hard_defense_exposure": chinext_growth_momentum_quality_strategy.DEFAULT_HARD_DEFENSE_EXPOSURE, + "soft_breadth_threshold": chinext_growth_momentum_quality_strategy.DEFAULT_SOFT_BREADTH_THRESHOLD, + "hard_breadth_threshold": chinext_growth_momentum_quality_strategy.DEFAULT_HARD_BREADTH_THRESHOLD, + "execution_cash_reserve_ratio": chinext_growth_momentum_quality_strategy.DEFAULT_EXECUTION_CASH_RESERVE_RATIO, + "rebalance_frequency": "monthly", + }, CN_EQUITY_COMBO_PROFILE: { "etf_weight": 0.30, "stock_weight": 0.50, @@ -195,6 +229,9 @@ CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE: "cn_index_etf_tactical_rotation_entrypoint", CN_CHINEXT_TACTICAL_ROTATION_PROFILE: "cn_chinext_tactical_rotation_entrypoint", CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: "cn_dividend_quality_snapshot_entrypoint", + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: ( + "cn_chinext_growth_momentum_quality_snapshot_entrypoint" + ), CN_EQUITY_COMBO_PROFILE: "cn_equity_combo_entrypoint", } @@ -204,6 +241,7 @@ CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE: "weight", CN_CHINEXT_TACTICAL_ROTATION_PROFILE: "weight", CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: "weight", + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: "weight", CN_EQUITY_COMBO_PROFILE: "weight", } @@ -260,6 +298,11 @@ def _build_strategy_definition( component_name="signal_logic", module_path="cn_equity_strategies.strategies.cn_dividend_quality_snapshot", ), + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: _build_strategy_definition( + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, + component_name="signal_logic", + module_path="cn_equity_strategies.strategies.cn_chinext_growth_momentum_quality_snapshot", + ), CN_EQUITY_COMBO_PROFILE: _build_strategy_definition( CN_EQUITY_COMBO_PROFILE, component_name="signal_logic", @@ -344,6 +387,21 @@ def _build_strategy_definition( role="cn_snapshot_dividend_quality", status="research_backtest_only", ), + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE: StrategyMetadata( + canonical_profile=CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, + display_name="CN ChiNext Growth Momentum Quality Snapshot", + localized_display_names={"zh": "创业板成长动量质量"}, + description=( + "Research-only snapshot-backed ChiNext selector emphasizing growth, momentum, " + "quality, and liquidity with defensive exposure control." + ), + aliases=(), + cadence="monthly review", + asset_scope="cn_single_name_snapshot_factor", + benchmark="399006", + role="cn_snapshot_chinext_growth_momentum_quality", + status="research_backtest_only", + ), CN_EQUITY_COMBO_PROFILE: StrategyMetadata( canonical_profile=CN_EQUITY_COMBO_PROFILE, display_name="CN Equity Combo", diff --git a/src/cn_equity_strategies/entrypoints/__init__.py b/src/cn_equity_strategies/entrypoints/__init__.py index 9a243fd..a0ec5dd 100644 --- a/src/cn_equity_strategies/entrypoints/__init__.py +++ b/src/cn_equity_strategies/entrypoints/__init__.py @@ -3,12 +3,16 @@ from quant_platform_kit.strategy_contracts import CallableStrategyEntrypoint, StrategyContext, StrategyDecision from cn_equity_strategies.manifests import ( + cn_chinext_growth_momentum_quality_snapshot_manifest, cn_chinext_tactical_rotation_manifest, cn_dividend_quality_snapshot_manifest, cn_index_etf_tactical_rotation_manifest, cn_industry_etf_rotation_aggressive_manifest, cn_industry_etf_rotation_manifest, ) +from cn_equity_strategies.strategies import ( + cn_chinext_growth_momentum_quality_snapshot as chinext_growth_momentum_quality_strategy, +) from cn_equity_strategies.strategies import cn_chinext_tactical_rotation as chinext_tactical_strategy from cn_equity_strategies.strategies import cn_dividend_quality_snapshot as dividend_quality_strategy from cn_equity_strategies.strategies import cn_index_etf_tactical_rotation as index_etf_strategy @@ -154,6 +158,41 @@ def evaluate_cn_chinext_tactical_rotation(ctx: StrategyContext) -> StrategyDecis ) +def evaluate_cn_chinext_growth_momentum_quality_snapshot(ctx: StrategyContext) -> StrategyDecision: + config = merge_runtime_config(cn_chinext_growth_momentum_quality_snapshot_manifest.default_config, ctx) + config.pop("execution_cash_reserve_ratio", None) + config.pop("rebalance_frequency", None) + config.pop("run_as_of", None) + weights, signal_desc, has_cash_residual, status_desc, metadata = ( + chinext_growth_momentum_quality_strategy.compute_signals( + require_market_data(ctx, "feature_snapshot"), + get_current_holdings(ctx), + **config, + ) + ) + diagnostics = { + **metadata, + "signal_description": signal_desc, + "status_description": status_desc, + "signal_source": chinext_growth_momentum_quality_strategy.SIGNAL_SOURCE, + "actionable": True, + } + risk_flags: tuple[str, ...] = () + if has_cash_residual: + risk_flags += ("cash_residual",) + return StrategyDecision( + positions=weights_to_positions(weights), + risk_flags=risk_flags, + diagnostics=diagnostics, + ) + + +cn_chinext_growth_momentum_quality_snapshot_entrypoint = CallableStrategyEntrypoint( + manifest=cn_chinext_growth_momentum_quality_snapshot_manifest, + _evaluate=evaluate_cn_chinext_growth_momentum_quality_snapshot, +) + + def evaluate_cn_dividend_quality_snapshot(ctx: StrategyContext) -> StrategyDecision: config = merge_runtime_config(cn_dividend_quality_snapshot_manifest.default_config, ctx) config.pop("execution_cash_reserve_ratio", None) @@ -206,12 +245,14 @@ def evaluate_cn_equity_combo(ctx: StrategyContext) -> StrategyDecision: __all__ = [ + "evaluate_cn_chinext_growth_momentum_quality_snapshot", "evaluate_cn_dividend_quality_snapshot", "evaluate_cn_chinext_tactical_rotation", "evaluate_cn_index_etf_tactical_rotation", "evaluate_cn_industry_etf_rotation", "evaluate_cn_industry_etf_rotation_aggressive", "evaluate_cn_equity_combo", + "cn_chinext_growth_momentum_quality_snapshot_entrypoint", "cn_chinext_tactical_rotation_entrypoint", "cn_dividend_quality_snapshot_entrypoint", "cn_index_etf_tactical_rotation_entrypoint", diff --git a/src/cn_equity_strategies/manifests/__init__.py b/src/cn_equity_strategies/manifests/__init__.py index b00a665..7b18855 100644 --- a/src/cn_equity_strategies/manifests/__init__.py +++ b/src/cn_equity_strategies/manifests/__init__.py @@ -3,6 +3,9 @@ from quant_platform_kit.strategy_contracts import StrategyManifest from cn_equity_strategies.strategies import cn_dividend_quality_snapshot as dividend_quality_strategy +from cn_equity_strategies.strategies import ( + cn_chinext_growth_momentum_quality_snapshot as chinext_growth_momentum_quality_strategy, +) from cn_equity_strategies.strategies import cn_chinext_tactical_rotation as chinext_tactical_strategy from cn_equity_strategies.strategies import cn_index_etf_tactical_rotation as index_etf_strategy from cn_equity_strategies.strategies import cn_industry_etf_rotation as industry_etf_strategy @@ -10,6 +13,9 @@ CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE = index_etf_strategy.PROFILE_NAME CN_CHINEXT_TACTICAL_ROTATION_PROFILE = chinext_tactical_strategy.PROFILE_NAME +CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE = ( + chinext_growth_momentum_quality_strategy.PROFILE_NAME +) CN_INDUSTRY_ETF_ROTATION_PROFILE = industry_etf_strategy.PROFILE_NAME CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE = industry_etf_aggressive_strategy.PROFILE_NAME CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE = dividend_quality_strategy.PROFILE_NAME @@ -158,6 +164,39 @@ def _manifest( }, ) +cn_chinext_growth_momentum_quality_snapshot_manifest = _manifest( + profile=CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, + domain=chinext_growth_momentum_quality_strategy.CN_EQUITY_DOMAIN, + display_name="CN ChiNext Growth Momentum Quality Snapshot", + description=( + "Research-only snapshot-backed ChiNext selector emphasizing growth, momentum, " + "quality, and liquidity with defensive exposure control." + ), + aliases=(), + required_inputs=frozenset({"feature_snapshot"}), + default_config={ + "safe_haven": chinext_growth_momentum_quality_strategy.SAFE_HAVEN, + "holdings_count": chinext_growth_momentum_quality_strategy.DEFAULT_HOLDINGS_COUNT, + "single_name_cap": chinext_growth_momentum_quality_strategy.DEFAULT_SINGLE_NAME_CAP, + "sector_cap": chinext_growth_momentum_quality_strategy.DEFAULT_SECTOR_CAP, + "min_adv20_cny": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_ADV20_CNY, + "min_market_cap_cny": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_MARKET_CAP_CNY, + "min_revenue_yoy": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_REVENUE_YOY, + "min_profit_yoy": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_PROFIT_YOY, + "min_momentum": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_MOMENTUM, + "min_list_days": chinext_growth_momentum_quality_strategy.DEFAULT_MIN_LIST_DAYS, + "hold_buffer": chinext_growth_momentum_quality_strategy.DEFAULT_HOLD_BUFFER, + "hold_bonus": chinext_growth_momentum_quality_strategy.DEFAULT_HOLD_BONUS, + "risk_on_exposure": chinext_growth_momentum_quality_strategy.DEFAULT_RISK_ON_EXPOSURE, + "soft_defense_exposure": chinext_growth_momentum_quality_strategy.DEFAULT_SOFT_DEFENSE_EXPOSURE, + "hard_defense_exposure": chinext_growth_momentum_quality_strategy.DEFAULT_HARD_DEFENSE_EXPOSURE, + "soft_breadth_threshold": chinext_growth_momentum_quality_strategy.DEFAULT_SOFT_BREADTH_THRESHOLD, + "hard_breadth_threshold": chinext_growth_momentum_quality_strategy.DEFAULT_HARD_BREADTH_THRESHOLD, + "execution_cash_reserve_ratio": chinext_growth_momentum_quality_strategy.DEFAULT_EXECUTION_CASH_RESERVE_RATIO, + "rebalance_frequency": "monthly", + }, +) + cn_dividend_quality_snapshot_manifest = _manifest( profile=CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE, domain=dividend_quality_strategy.CN_EQUITY_DOMAIN, @@ -217,11 +256,13 @@ def get_strategy_manifest(profile: str) -> StrategyManifest: __all__ = [ "CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE", "CN_CHINEXT_TACTICAL_ROTATION_PROFILE", + "CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE", "CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE", "CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE", "CN_INDUSTRY_ETF_ROTATION_PROFILE", "MANIFESTS", "get_strategy_manifest", + "cn_chinext_growth_momentum_quality_snapshot_manifest", "cn_chinext_tactical_rotation_manifest", "cn_dividend_quality_snapshot_manifest", "cn_index_etf_tactical_rotation_manifest", diff --git a/tests/test_catalog.py b/tests/test_catalog.py index 6926e71..009da28 100644 --- a/tests/test_catalog.py +++ b/tests/test_catalog.py @@ -5,6 +5,7 @@ from quant_platform_kit.common.strategies import get_strategy_component_map from cn_equity_strategies.catalog import ( + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE, CN_CHINEXT_TACTICAL_ROTATION_PROFILE, CN_EQUITY_COMBO_PROFILE, @@ -33,6 +34,7 @@ def test_catalog_declares_cn_strategy_status_layers(): catalog = get_strategy_definitions() assert set(catalog) == { CN_EQUITY_COMBO_PROFILE, + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, CN_CHINEXT_TACTICAL_ROTATION_PROFILE, CN_INDUSTRY_ETF_ROTATION_PROFILE, CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE, @@ -70,6 +72,15 @@ def test_catalog_declares_cn_strategy_status_layers(): assert get_compatible_platforms(CN_CHINEXT_TACTICAL_ROTATION_PROFILE) == frozenset({"qmt"}) assert get_strategy_metadata(CN_CHINEXT_TACTICAL_ROTATION_PROFILE).status == "research_backtest_only" + chinext_growth_definition = catalog[CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE] + assert chinext_growth_definition.domain == CN_EQUITY_DOMAIN + assert chinext_growth_definition.required_inputs == frozenset({"feature_snapshot"}) + assert get_compatible_platforms(CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE) == frozenset({"qmt"}) + assert ( + get_strategy_metadata(CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE).status + == "research_backtest_only" + ) + dividend_definition = catalog[CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE] assert dividend_definition.domain == CN_EQUITY_DOMAIN assert dividend_definition.required_inputs == frozenset({"feature_snapshot"}) @@ -83,13 +94,15 @@ def test_catalog_declares_cn_strategy_status_layers(): def test_profile_groups_keep_runtime_and_scaffolds_separate(): assert get_direct_market_history_profiles() == frozenset({CN_INDUSTRY_ETF_ROTATION_PROFILE}) - assert get_snapshot_backed_profiles() == frozenset({CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE}) - assert get_external_snapshot_scaffold_profiles() == frozenset( + assert get_snapshot_backed_profiles() == frozenset( { - "cn_small_cap_quality_snapshot", - "cn_chinext_growth_momentum_quality_snapshot", + CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE, + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, } ) + assert get_external_snapshot_scaffold_profiles() == frozenset( + {"cn_small_cap_quality_snapshot"} + ) assert get_scaffold_profiles() == get_external_snapshot_scaffold_profiles() assert get_shadow_profiles() == frozenset() assert get_live_candidate_profiles() == frozenset({CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE}) @@ -97,6 +110,7 @@ def test_profile_groups_keep_runtime_and_scaffolds_separate(): { CN_INDEX_ETF_TACTICAL_ROTATION_PROFILE, CN_DIVIDEND_QUALITY_SNAPSHOT_PROFILE, + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE, CN_CHINEXT_TACTICAL_ROTATION_PROFILE, CN_EQUITY_COMBO_PROFILE, } @@ -125,8 +139,9 @@ def test_research_scaffold_profile_is_not_in_runtime_catalog(): with pytest.raises(ValueError): get_strategy_definition("cn_small_cap_quality_snapshot") - with pytest.raises(ValueError): - get_strategy_definition("cn_chinext_growth_momentum_quality_snapshot") + assert get_strategy_definition(CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE).profile == ( + CN_CHINEXT_GROWTH_MOMENTUM_QUALITY_SNAPSHOT_PROFILE + ) def test_package_root_exports_use_local_source_tree() -> None: @@ -140,9 +155,6 @@ def test_package_root_exports_use_local_source_tree() -> None: {CN_INDUSTRY_ETF_ROTATION_AGGRESSIVE_PROFILE} ) assert cn_equity_strategies.get_scaffold_profiles() == frozenset( - { - "cn_small_cap_quality_snapshot", - "cn_chinext_growth_momentum_quality_snapshot", - } + {"cn_small_cap_quality_snapshot"} ) assert cn_equity_strategies.get_external_snapshot_scaffold_profiles() == cn_equity_strategies.get_scaffold_profiles() From 3428471833d80be8c1f4a76a3e44e76176d6df22 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sun, 5 Jul 2026 22:02:54 +0800 Subject: [PATCH 2/2] refresh gate status Co-Authored-By: Codex