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
29 changes: 29 additions & 0 deletions src/hk_equity_strategies/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,12 @@
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 hk_equity_strategies.strategies import hk_equity_combo as hk_combo_strategy

HK_EQUITY_DOMAIN = global_etf_strategy.HK_EQUITY_DOMAIN
HK_GLOBAL_ETF_TACTICAL_ROTATION_PROFILE = global_etf_strategy.PROFILE_NAME
HK_LOW_VOL_DIVIDEND_QUALITY_SNAPSHOT_PROFILE = low_vol_dividend_strategy.PROFILE_NAME
HK_EQUITY_COMBO_PROFILE = hk_combo_strategy.PROFILE_NAME

HK_DIRECT_MARKET_HISTORY_PROFILES = frozenset(
{
Expand All @@ -34,11 +37,13 @@
STRATEGY_PLATFORM_COMPATIBILITY: dict[str, frozenset[str]] = {
HK_GLOBAL_ETF_TACTICAL_ROTATION_PROFILE: frozenset({"ibkr", "longbridge"}),
HK_LOW_VOL_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: frozenset({"ibkr", "longbridge"}),
HK_EQUITY_COMBO_PROFILE: frozenset({"manual"}),
}

STRATEGY_REQUIRED_INPUTS: dict[str, frozenset[str]] = {
HK_GLOBAL_ETF_TACTICAL_ROTATION_PROFILE: frozenset({"market_history"}),
HK_LOW_VOL_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: frozenset({"feature_snapshot"}),
HK_EQUITY_COMBO_PROFILE: frozenset({"market_history", "dividend_snapshot"}),
}

STRATEGY_DEFAULT_CONFIG: dict[str, dict[str, object]] = {
Expand Down Expand Up @@ -78,16 +83,24 @@
"execution_cash_reserve_ratio": low_vol_dividend_strategy.DEFAULT_EXECUTION_CASH_RESERVE_RATIO,
"rebalance_frequency": "monthly",
},
HK_EQUITY_COMBO_PROFILE: {
"etf_weight": 0.60,
"dividend_weight": 0.40,
"execution_cash_reserve_ratio": 0.02,
"rebalance_frequency": "monthly",
},
}

STRATEGY_ENTRYPOINT_ATTRIBUTES: dict[str, str] = {
HK_GLOBAL_ETF_TACTICAL_ROTATION_PROFILE: "hk_global_etf_tactical_rotation_entrypoint",
HK_LOW_VOL_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: "hk_low_vol_dividend_quality_snapshot_entrypoint",
HK_EQUITY_COMBO_PROFILE: "hk_equity_combo_entrypoint",
}

STRATEGY_TARGET_MODES: dict[str, str] = {
HK_GLOBAL_ETF_TACTICAL_ROTATION_PROFILE: "weight",
HK_LOW_VOL_DIVIDEND_QUALITY_SNAPSHOT_PROFILE: "weight",
HK_EQUITY_COMBO_PROFILE: "weight",
}


Expand Down Expand Up @@ -130,6 +143,11 @@ def _build_strategy_definition(
component_name="signal_logic",
module_path="hk_equity_strategies.strategies.hk_low_vol_dividend_quality_snapshot",
),
HK_EQUITY_COMBO_PROFILE: _build_strategy_definition(
HK_EQUITY_COMBO_PROFILE,
component_name="signal_logic",
module_path="hk_equity_strategies.strategies.hk_equity_combo",
),
}

STRATEGY_METADATA: dict[str, StrategyMetadata] = {
Expand Down Expand Up @@ -163,6 +181,17 @@ def _build_strategy_definition(
role="hk_snapshot_low_vol_dividend_quality",
status="runtime_enabled",
),
HK_EQUITY_COMBO_PROFILE: StrategyMetadata(
canonical_profile=HK_EQUITY_COMBO_PROFILE,
display_name="HK Equity Combo",
description="Combined HK equity strategy: Global ETF tactical rotation (60%) + low-vol dividend quality (40%) blended portfolio.",
aliases=(),
cadence="monthly review",
asset_scope="hk_equity_combo",
benchmark="2800",
role="hk_equity_combo",
status="runtime_enabled",
),
}

PROFILE_ALIASES: dict[str, str] = {
Expand Down
48 changes: 48 additions & 0 deletions src/hk_equity_strategies/combo_entrypoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""HK equity combo entrypoints — moved from QuantHkComboStrategies.
"""
from __future__ import annotations

from typing import Any

from quant_platform_kit.strategy_contracts import (
CallableStrategyEntrypoint, PositionTarget, StrategyContext, StrategyDecision,
)

from hk_equity_strategies.combo_manifests import hk_equity_combo_manifest
from hk_equity_strategies.strategies import hk_equity_combo


def _require_market_data(ctx: StrategyContext, key: str) -> Any:
if key not in ctx.market_data:
raise ValueError(f"StrategyContext.market_data[{key!r}] is required")
return ctx.market_data[key]


def evaluate_hk_equity_combo(ctx: StrategyContext) -> StrategyDecision:
config = {**hk_equity_combo_manifest.default_config, **ctx.runtime_config or {}}
config.pop("execution_cash_reserve_ratio", None)
config.pop("rebalance_frequency", None)
combined, metadata = hk_equity_combo.build_target_weights(
market_history=_require_market_data(ctx, "market_history"),
dividend_snapshot=_require_market_data(ctx, "dividend_snapshot"),
config=config,
)
diagnostics = {
**metadata, "signal_description": f"etf={config.get('etf_weight', 0.60):.0%} div={config.get('dividend_weight', 0.40):.0%}",
"status_description": f"etf={config.get('etf_weight', 0.60):.0%} div={config.get('dividend_weight', 0.40):.0%}",
"signal_source": hk_equity_combo.SIGNAL_SOURCE, "actionable": True,
}
return StrategyDecision(
positions=tuple(
PositionTarget(symbol=str(s), target_weight=float(w), role="target")
for s, w in sorted(combined.items()) if abs(float(w)) > 1e-12
),
risk_flags=(), diagnostics=diagnostics,
)


hk_equity_combo_entrypoint = CallableStrategyEntrypoint(
manifest=hk_equity_combo_manifest, _evaluate=evaluate_hk_equity_combo,
)

__all__ = ["evaluate_hk_equity_combo", "hk_equity_combo_entrypoint"]
33 changes: 33 additions & 0 deletions src/hk_equity_strategies/combo_manifests.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""HK equity combo manifests — moved from QuantHkComboStrategies.
"""
from __future__ import annotations

from quant_platform_kit.strategy_contracts import StrategyManifest

from hk_equity_strategies.strategies import hk_equity_combo as combo_strategy

HK_EQUITY_COMBO_PROFILE = combo_strategy.PROFILE_NAME


def _manifest(*, profile, domain, display_name, description, aliases=(), required_inputs=frozenset(), default_config=None):
return StrategyManifest(
profile=profile, domain=domain, display_name=display_name,
description=description, aliases=aliases,
required_inputs=required_inputs, default_config=default_config or {},
)


hk_equity_combo_manifest = _manifest(
profile=HK_EQUITY_COMBO_PROFILE,
domain="hk_equity",
display_name="HK Equity Combo",
description="Combined HK equity strategy: Global ETF tactical rotation (60%) + low-vol dividend quality (40%) blended portfolio.",
aliases=(),
required_inputs=frozenset({"market_history", "dividend_snapshot"}),
default_config={
"etf_weight": 0.60, "dividend_weight": 0.40,
"execution_cash_reserve_ratio": 0.02, "rebalance_frequency": "monthly",
},
)

__all__ = ["HK_EQUITY_COMBO_PROFILE", "hk_equity_combo_manifest"]
20 changes: 20 additions & 0 deletions src/hk_equity_strategies/entrypoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,9 +76,29 @@ def evaluate_hk_low_vol_dividend_quality_snapshot(ctx: StrategyContext) -> Strat
)


# ---------------------------------------------------------------------------
# HK Equity Combo entrypoint — delegates to combo_entrypoints
# ---------------------------------------------------------------------------


def evaluate_hk_equity_combo(ctx: StrategyContext) -> StrategyDecision:
from hk_equity_strategies.combo_entrypoints import evaluate_hk_equity_combo as _eval
return _eval(ctx)


from hk_equity_strategies.combo_manifests import hk_equity_combo_manifest # noqa: E402 — intentional late import

hk_equity_combo_entrypoint = CallableStrategyEntrypoint(
manifest=hk_equity_combo_manifest,
_evaluate=evaluate_hk_equity_combo,
)


__all__ = [
"evaluate_hk_global_etf_tactical_rotation",
"evaluate_hk_low_vol_dividend_quality_snapshot",
"evaluate_hk_equity_combo",
"hk_global_etf_tactical_rotation_entrypoint",
"hk_low_vol_dividend_quality_snapshot_entrypoint",
"hk_equity_combo_entrypoint",
]
Loading
Loading