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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pandas>=2.0",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@37c81901160c5b31127a27dba1c63944933fb6bf",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@0c69df08144872ccd1d8bf523738e80748d8d664",
]

[project.optional-dependencies]
Expand Down
36 changes: 32 additions & 4 deletions src/hk_equity_strategies/entrypoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,14 @@
from hk_equity_strategies.strategies import hk_global_etf_tactical_rotation as global_etf_strategy
from hk_equity_strategies.strategies import hk_low_vol_dividend_quality_snapshot as low_vol_dividend_strategy

from ._common import apply_risk_gate, get_current_holdings, merge_runtime_config, require_market_data, weights_to_positions
from ._common import (
apply_risk_gate,
get_current_holdings,
merge_runtime_config,
record_strategy_decision,
require_market_data,
weights_to_positions,
)


def evaluate_hk_global_etf_tactical_rotation(ctx: StrategyContext) -> StrategyDecision:
Expand All @@ -36,7 +43,14 @@ def evaluate_hk_global_etf_tactical_rotation(ctx: StrategyContext) -> StrategyDe
risk_flags=risk_flags,
diagnostics=diagnostics,
)
return apply_risk_gate(decision)
decision = apply_risk_gate(decision)
record_strategy_decision(
ctx,
decision,
profile_id=hk_global_etf_tactical_rotation_manifest.profile,
domain=hk_global_etf_tactical_rotation_manifest.domain,
)
return decision


hk_global_etf_tactical_rotation_entrypoint = CallableStrategyEntrypoint(
Expand Down Expand Up @@ -69,7 +83,14 @@ def evaluate_hk_low_vol_dividend_quality_snapshot(ctx: StrategyContext) -> Strat
risk_flags=risk_flags,
diagnostics=diagnostics,
)
return apply_risk_gate(decision)
decision = apply_risk_gate(decision)
record_strategy_decision(
ctx,
decision,
profile_id=hk_low_vol_dividend_quality_snapshot_manifest.profile,
domain=hk_low_vol_dividend_quality_snapshot_manifest.domain,
)
return decision


hk_low_vol_dividend_quality_snapshot_entrypoint = CallableStrategyEntrypoint(
Expand All @@ -85,7 +106,14 @@ def evaluate_hk_low_vol_dividend_quality_snapshot(ctx: StrategyContext) -> Strat

def evaluate_hk_equity_combo(ctx: StrategyContext) -> StrategyDecision:
from hk_equity_strategies.combo_entrypoints import evaluate_hk_equity_combo as _eval
return apply_risk_gate(_eval(ctx))
decision = apply_risk_gate(_eval(ctx))
record_strategy_decision(
ctx,
decision,
profile_id=hk_equity_combo_manifest.profile,
domain=hk_equity_combo_manifest.domain,
)
return decision


from hk_equity_strategies.combo_manifests import hk_equity_combo_manifest # noqa: E402 — intentional late import
Expand Down
25 changes: 25 additions & 0 deletions src/hk_equity_strategies/entrypoints/_common.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,38 @@
from typing import Any

from quant_platform_kit.strategy_contracts import PositionTarget, StrategyContext, StrategyDecision
from quant_platform_kit.strategy_lifecycle.performance_monitor import PerformanceMonitor

logger = logging.getLogger(__name__)

# ---------------------------------------------------------------------------
# 风控硬门 — 每个 entrypoint 返回 StrategyDecision 前必须调用
# ---------------------------------------------------------------------------

_performance_monitor: PerformanceMonitor | None = None


def record_strategy_decision(
ctx: StrategyContext,
decision: StrategyDecision,
*,
profile_id: str,
domain: str,
) -> None:
"""Record per-run decision for live monitoring (roadmap 5a)."""
global _performance_monitor
try:
if _performance_monitor is None:
_performance_monitor = PerformanceMonitor()
_performance_monitor.record(
profile_id,
decision,
execution_result={},
domain=domain,
)
except Exception as exc: # pragma: no cover
logger.warning("PerformanceMonitor.record failed: %s", exc)


def apply_risk_gate(
decision: StrategyDecision,
Expand Down
Loading