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
4 changes: 4 additions & 0 deletions notifications/telegram.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,8 @@
"strategy_name_mega_cap_leader_rotation_top50_balanced": "Mega Cap Top50 平衡龙头轮动",
"strategy_name_nasdaq_sp500_smart_dca": "纳斯达克 / 标普智能定投",
"strategy_name_hk_listed_global_etf_rotation": "港股上市全球 ETF 轮动",
"strategy_name_hk_high_dividend_low_vol_trend": "港股高股息低波趋势",
"strategy_name_hk_low_vol_dividend_quality": "港股低波股息质量",
"strategy_name_tqqq_growth_income": "TQQQ 增长收益",
"strategy_name_soxl_soxx_trend_income": "SOXL/SOXX 半导体趋势收益",
},
Expand Down Expand Up @@ -279,6 +281,8 @@
"strategy_name_mega_cap_leader_rotation_top50_balanced": "Mega Cap Leader Rotation Top50 Balanced",
"strategy_name_nasdaq_sp500_smart_dca": "Nasdaq/S&P 500 Smart DCA",
"strategy_name_hk_listed_global_etf_rotation": "HK-listed Global ETF Rotation",
"strategy_name_hk_high_dividend_low_vol_trend": "HK High Dividend Low-Volatility Trend",
"strategy_name_hk_low_vol_dividend_quality": "HK Low-Volatility Dividend Quality",
"strategy_name_tqqq_growth_income": "TQQQ Growth Income",
"strategy_name_soxl_soxx_trend_income": "SOXL/SOXX Semiconductor Trend Income",
},
Expand Down
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
flask
gunicorn
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.35
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.7.49
hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@71141ce9e8a343cec8e2140994071eea66422bc6
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@v0.7.36
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@v0.7.50
hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@hk-low-vol-dividend-quality-20260603

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 Pin the HK strategy dependency to an immutable ref

Because this now points hk-equity-strategies at a branch name, any later pip install -r requirements.txt or Cloud Run build can pick up whatever commit that branch has moved to rather than the runtime adapter/version validated by this change. The other strategy deps are pinned to tags and the previous HK dep was pinned to a SHA, so for rollout reproducibility this should use an immutable tag or commit SHA once the HK low-vol adapter is selected.

Useful? React with 👍 / 👎.

pandas
numpy
requests
Expand Down
14 changes: 11 additions & 3 deletions scripts/print_strategy_switch_env_plan.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,12 @@ def _should_add_local_src(candidate: Path) -> bool:
]


def _feature_snapshot_filenames(profile: str, snapshot_contract_version: str | None) -> tuple[str, str]:
suffix = "factor_snapshot" if ".factor_snapshot." in str(snapshot_contract_version or "") else "feature_snapshot"
snapshot_filename = f"{profile}_{suffix}_latest.csv"
return snapshot_filename, f"{snapshot_filename}.manifest.json"


def build_switch_plan(
profile: str,
*,
Expand Down Expand Up @@ -223,10 +229,12 @@ def build_switch_plan(

hints: dict[str, str] = {}
if requires_feature_snapshot:
hints["feature_snapshot_filename"] = f"{definition.profile}_feature_snapshot_latest.csv"
hints["feature_snapshot_manifest_filename"] = (
f"{definition.profile}_feature_snapshot_latest.csv.manifest.json"
snapshot_filename, manifest_filename = _feature_snapshot_filenames(
definition.profile,
runtime_requirements.get("snapshot_contract_version"),
)
hints["feature_snapshot_filename"] = snapshot_filename
hints["feature_snapshot_manifest_filename"] = manifest_filename
if artifact_paths.bundled_config_path is not None:
hints["bundled_strategy_config_path"] = str(artifact_paths.bundled_config_path)

Expand Down
4 changes: 4 additions & 0 deletions tests/test_notifications.py
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,10 @@ def test_strategy_display_name_translates_new_live_profiles():
assert en_name("nasdaq_sp500_smart_dca") == "Nasdaq/S&P 500 Smart DCA"
assert zh_name("hk_listed_global_etf_rotation") == "港股上市全球 ETF 轮动"
assert en_name("hk_listed_global_etf_rotation") == "HK-listed Global ETF Rotation"
assert zh_name("hk_high_dividend_low_vol_trend") == "港股高股息低波趋势"
assert en_name("hk_high_dividend_low_vol_trend") == "HK High Dividend Low-Volatility Trend"
assert zh_name("hk_low_vol_dividend_quality") == "港股低波股息质量"
assert en_name("hk_low_vol_dividend_quality") == "HK Low-Volatility Dividend Quality"


def test_supported_strategy_profiles_have_translated_names():
Expand Down
38 changes: 38 additions & 0 deletions tests/test_runtime_config_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@
"soxl_soxx_trend_income",
"tech_communication_pullback_enhancement",
"tqqq_growth_income",
"hk_high_dividend_low_vol_trend",
"hk_listed_global_etf_rotation",
"hk_low_vol_dividend_quality",
}
)
HK_DISABLED_PROFILES = frozenset(
Expand Down Expand Up @@ -1058,6 +1060,42 @@ def test_print_strategy_switch_env_plan_for_mega_cap_top50_balanced_profile():
assert plan["hints"]["feature_snapshot_filename"] == "mega_cap_leader_rotation_top50_balanced_feature_snapshot_latest.csv"


def test_print_strategy_switch_env_plan_for_hk_low_vol_dividend_quality_profile():
result = subprocess.run(
[
sys.executable,
str(SWITCH_PLAN_SCRIPT_PATH),
"--profile",
"hk_low_vol_dividend_quality",
"--dry-run-only",
"--deployment-selector",
"hk-verify",
"--account-scope",
"HK",
"--service-name",
"interactive-brokers-hk-verify-service",
"--json",
],
check=True,
capture_output=True,
text=True,
)

plan = json.loads(result.stdout)
assert plan["canonical_profile"] == "hk_low_vol_dividend_quality"
assert plan["enabled"] is True
assert plan["profile_group"] == "snapshot_backed"
assert plan["input_mode"] == "feature_snapshot"
assert plan["snapshot_contract_version"] == "hk_low_vol_dividend_quality.factor_snapshot.v1"
assert plan["set_env"]["IBKR_DRY_RUN_ONLY"] == "true"
assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_PATH"] == "<required>"
assert plan["set_env"]["IBKR_FEATURE_SNAPSHOT_MANIFEST_PATH"] == "<required>"
assert plan["hints"]["feature_snapshot_filename"] == "hk_low_vol_dividend_quality_factor_snapshot_latest.csv"
assert plan["hints"]["feature_snapshot_manifest_filename"] == (
"hk_low_vol_dividend_quality_factor_snapshot_latest.csv.manifest.json"
)


@pytest.mark.parametrize("profile", sorted(HK_DISABLED_PROFILES))
def test_print_strategy_switch_env_plan_rejects_hk_disabled_profiles(profile):
result = subprocess.run(
Expand Down