Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -214,7 +214,7 @@
("russell_1000_multi_factor_defensive", "罗素1000多因子"),
("tech_communication_pullback_enhancement", "科技通信回调增强"),
("qqq_tech_enhancement", "科技通信回调增强"),
("mega_cap_leader_rotation_top50_balanced", "Mega Cap Top50 平衡龙头轮动"),
("mega_cap_leader_rotation_top50_balanced", "美股超大盘50强平衡龙头轮动"),
("outside_monthly_execution_window", "当前不在月度执行窗口"),
("no_execution_window_after_snapshot", "快照后没有可用执行窗口"),
("no-op", "不执行"),
Expand Down
13 changes: 13 additions & 0 deletions src/quant_platform_kit/common/strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ class StrategyMetadata:
canonical_profile: str
display_name: str
description: str
localized_display_names: Mapping[str, str] = field(default_factory=dict)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve positional aliases in StrategyMetadata

Callers that still use the pre-existing positional form StrategyMetadata(profile, name, desc, aliases) now have the aliases tuple bound to localized_display_names instead of aliases. In that scenario alias resolution silently loses the aliases, and the new row builders that call _localized_display_name_fields() raise AttributeError because the tuple has no .get(). Add the new optional field after the existing fields or make it keyword-only to avoid breaking the public constructor shape.

Useful? React with 👍 / 👎.

aliases: tuple[str, ...] = ()
cadence: str | None = None
asset_scope: str | None = None
Expand Down Expand Up @@ -275,11 +276,21 @@ def build_strategy_index_rows(strategy_catalog: StrategyCatalog) -> list[dict[st
"compatible_capabilities": definition.compatible_capabilities,
"target_mode": _resolve_target_mode(definition),
"bundled_config_relpath": definition.bundled_config_relpath,
**_localized_display_name_fields(metadata),
}
)
return rows


def _localized_display_name_fields(metadata: StrategyMetadata | None) -> dict[str, object]:
if metadata is None:
return {}
zh_name = str(metadata.localized_display_names.get("zh") or "").strip()
if not zh_name:
return {}
return {"display_name_zh": zh_name}


def get_catalog_target_mode(
strategy_catalog: StrategyCatalog,
profile: str,
Expand Down Expand Up @@ -441,6 +452,7 @@ def build_platform_profile_matrix(
"is_default": definition.profile == policy.default_profile,
"is_rollback": definition.profile == policy.rollback_profile,
"domain": definition.domain,
**_localized_display_name_fields(metadata),
}
)
return rows
Expand Down Expand Up @@ -471,6 +483,7 @@ def build_platform_profile_status_matrix(
"is_default": definition.profile == policy.default_profile,
"is_rollback": definition.profile == policy.rollback_profile,
"domain": definition.domain,
**_localized_display_name_fields(metadata),
}
)
return rows
Expand Down
5 changes: 5 additions & 0 deletions tests/test_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def test_catalog_helpers_resolve_alias_and_metadata(self) -> None:
canonical_profile="global_etf_rotation",
display_name="Global ETF Rotation Defense",
description="rotation",
localized_display_names={"zh": "全球 ETF 防守轮动"},
aliases=("global_macro_etf_rotation",),
cadence="quarterly",
benchmark="VOO",
Expand Down Expand Up @@ -175,6 +176,7 @@ def test_catalog_helpers_resolve_alias_and_metadata(self) -> None:
rows = build_strategy_index_rows(catalog)
by_profile = {row["canonical_profile"]: row for row in rows}
self.assertEqual(by_profile["global_etf_rotation"]["display_name"], "Global ETF Rotation Defense")
self.assertEqual(by_profile["global_etf_rotation"]["display_name_zh"], "全球 ETF 防守轮动")
self.assertEqual(by_profile["global_etf_rotation"]["target_mode"], "weight")

def test_platform_policy_helpers_build_matrix_and_resolve_enabled_profile(self) -> None:
Expand All @@ -185,6 +187,7 @@ def test_platform_policy_helpers_build_matrix_and_resolve_enabled_profile(self)
canonical_profile="global_etf_rotation",
display_name="Global ETF Rotation Defense",
description="rotation",
localized_display_names={"zh": "全球 ETF 防守轮动"},
aliases=("global_macro_etf_rotation",),
),
},
Expand All @@ -204,6 +207,7 @@ def test_platform_policy_helpers_build_matrix_and_resolve_enabled_profile(self)
)
matrix = build_platform_profile_matrix(catalog, policy=policy)
self.assertEqual(matrix[0]["display_name"], "Global ETF Rotation Defense")
self.assertEqual(matrix[0]["display_name_zh"], "全球 ETF 防守轮动")
definition = resolve_platform_strategy_definition(
"global_macro_etf_rotation",
platform_id="ibkr",
Expand All @@ -216,6 +220,7 @@ def test_platform_policy_helpers_build_matrix_and_resolve_enabled_profile(self)
policy=policy,
eligible_profiles=frozenset({"global_etf_rotation"}),
)
self.assertEqual(status_matrix[0]["display_name_zh"], "全球 ETF 防守轮动")
self.assertEqual(status_matrix[0]["eligible"], True)
self.assertEqual(status_matrix[0]["enabled"], True)

Expand Down