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: 2 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"

[project]
name = "us-equity-strategies"
version = "0.7.32"
version = "0.7.33"
description = "Shared US equity strategy catalog and implementations"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"pandas>=2.0",
"pytz>=2024.1",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@268171a74a1b522f03940af399f71c37e9a32c70",
"quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@3368b261b6ab2210ecbfa79d0f93c5d0cd85c37d",
]

[tool.setuptools]
Expand Down
5 changes: 5 additions & 0 deletions src/us_equity_strategies/catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
"blend_gate_mid_soxl_weight": 0.65,
"blend_gate_active_soxx_weight": 0.20,
"blend_gate_defensive_soxx_weight": 0.15,
"blend_gate_rsi_cap_enabled": True,
"blend_gate_rsi_threshold": 70.0,
"blend_gate_dynamic_rsi_threshold_enabled": True,
"blend_gate_bollinger_cap_enabled": True,
"blend_gate_overlay_stack_triggers": True,
},
RUSSELL_1000_MULTI_FACTOR_DEFENSIVE_PROFILE: {
"benchmark_symbol": "SPY",
Expand Down
4 changes: 4 additions & 0 deletions src/us_equity_strategies/entrypoints/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -442,6 +442,8 @@ def evaluate_soxl_soxx_trend_income(ctx: StrategyContext) -> StrategyDecision:
"trend_ma20": plan.get("trend_ma20"),
"trend_ma20_slope": plan.get("trend_ma20_slope"),
"trend_rsi14": plan.get("trend_rsi14"),
"trend_rsi14_dynamic_threshold": plan.get("trend_rsi14_dynamic_threshold"),
"trend_rsi14_effective_threshold": plan.get("trend_rsi14_effective_threshold"),
"trend_bb_upper": plan.get("trend_bb_upper"),
**account_size_diagnostics,
"execution_annotations": {
Expand Down Expand Up @@ -476,6 +478,8 @@ def evaluate_soxl_soxx_trend_income(ctx: StrategyContext) -> StrategyDecision:
"trend_ma20": plan.get("trend_ma20"),
"trend_ma20_slope": plan.get("trend_ma20_slope"),
"trend_rsi14": plan.get("trend_rsi14"),
"trend_rsi14_dynamic_threshold": plan.get("trend_rsi14_dynamic_threshold"),
"trend_rsi14_effective_threshold": plan.get("trend_rsi14_effective_threshold"),
"trend_bb_upper": plan.get("trend_bb_upper"),
},
}
Expand Down
1 change: 1 addition & 0 deletions src/us_equity_strategies/manifests/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ def _manifest(
"blend_gate_defensive_soxx_weight": 0.15,
"blend_gate_rsi_cap_enabled": True,
"blend_gate_rsi_threshold": 70.0,
"blend_gate_dynamic_rsi_threshold_enabled": True,
"blend_gate_bollinger_cap_enabled": True,
"blend_gate_overlay_stack_triggers": True,
},
Expand Down
17 changes: 15 additions & 2 deletions src/us_equity_strategies/strategies/soxl_soxx_trend_income.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ def build_rebalance_plan(
blend_gate_defensive_soxx_weight=0.15,
blend_gate_rsi_cap_enabled=False,
blend_gate_rsi_threshold=70.0,
blend_gate_dynamic_rsi_threshold_enabled=False,
blend_gate_bollinger_cap_enabled=False,
blend_gate_overlay_stack_triggers=False,
):
Expand Down Expand Up @@ -181,6 +182,9 @@ def build_rebalance_plan(
trend_ma20 = _as_float_or_none(_indicator_value(indicators, trend_symbol, "ma20"))
trend_ma20_slope = _as_float_or_none(_indicator_value(indicators, trend_symbol, "ma20_slope"))
trend_rsi14 = _as_float_or_none(_indicator_value(indicators, trend_symbol, "rsi14"))
trend_rsi14_dynamic_threshold = _as_float_or_none(
_indicator_value(indicators, trend_symbol, "rsi14_dynamic_threshold")
)
trend_bb_mid = _as_float_or_none(_indicator_value(indicators, trend_symbol, "bb_mid"))
trend_bb_upper = _as_float_or_none(_indicator_value(indicators, trend_symbol, "bb_upper"))
trend_bb_lower = _as_float_or_none(_indicator_value(indicators, trend_symbol, "bb_lower"))
Expand All @@ -197,11 +201,15 @@ def build_rebalance_plan(
target_active_soxx_ratio = _as_clamped_ratio(blend_gate_active_soxx_weight, default=0.20)
target_defensive_soxx_ratio = _as_clamped_ratio(blend_gate_defensive_soxx_weight, default=0.15)
use_rsi_cap = _as_bool(blend_gate_rsi_cap_enabled, default=False)
use_dynamic_rsi_threshold = _as_bool(blend_gate_dynamic_rsi_threshold_enabled, default=False)
use_bollinger_cap = _as_bool(blend_gate_bollinger_cap_enabled, default=False)
stack_overlay_triggers = _as_bool(blend_gate_overlay_stack_triggers, default=False)
rsi_threshold = _as_float_or_none(blend_gate_rsi_threshold)
if rsi_threshold is None:
rsi_threshold = 70.0
effective_rsi_threshold = rsi_threshold
if use_dynamic_rsi_threshold and trend_rsi14_dynamic_threshold is not None:
effective_rsi_threshold = max(rsi_threshold, trend_rsi14_dynamic_threshold)

blend_tier = "defensive"
if trend_price > trend_entry_line:
Expand All @@ -212,10 +220,10 @@ def build_rebalance_plan(
overlay_trigger_reasons: list[str] = []
overlay_trigger_codes: list[str] = []
if base_blend_tier in {"full", "mid"}:
if use_rsi_cap and trend_rsi14 is not None and trend_rsi14 > rsi_threshold:
if use_rsi_cap and trend_rsi14 is not None and trend_rsi14 > effective_rsi_threshold:
overlay_trigger_codes.append("blend_gate_reason_rsi_cap")
overlay_trigger_reasons.append(
_translate_with_fallback(translator, "blend_gate_reason_rsi_cap", f"RSI>{rsi_threshold:.0f}")
_translate_with_fallback(translator, "blend_gate_reason_rsi_cap", f"RSI>{effective_rsi_threshold:.0f}")
)
if use_bollinger_cap and trend_bb_upper is not None and trend_price > trend_bb_upper:
overlay_trigger_codes.append("blend_gate_reason_bollinger_cap")
Expand Down Expand Up @@ -331,6 +339,8 @@ def build_rebalance_plan(
"ma20": trend_ma20,
"ma20_slope": trend_ma20_slope,
"rsi14": trend_rsi14,
"rsi14_dynamic_threshold": trend_rsi14_dynamic_threshold,
"rsi14_effective_threshold": effective_rsi_threshold,
"bb_mid": trend_bb_mid,
"bb_upper": trend_bb_upper,
"bb_lower": trend_bb_lower,
Expand Down Expand Up @@ -401,11 +411,14 @@ def build_rebalance_plan(
"trend_ma20": trend_ma20,
"trend_ma20_slope": trend_ma20_slope,
"trend_rsi14": trend_rsi14,
"trend_rsi14_dynamic_threshold": trend_rsi14_dynamic_threshold,
"trend_rsi14_effective_threshold": effective_rsi_threshold,
"trend_bb_mid": trend_bb_mid,
"trend_bb_upper": trend_bb_upper,
"trend_bb_lower": trend_bb_lower,
"blend_gate_rsi_cap_enabled": use_rsi_cap,
"blend_gate_rsi_threshold": rsi_threshold,
"blend_gate_dynamic_rsi_threshold_enabled": use_dynamic_rsi_threshold,
"blend_gate_bollinger_cap_enabled": use_bollinger_cap,
"blend_gate_overlay_stack_triggers": stack_overlay_triggers,
}
35 changes: 35 additions & 0 deletions tests/test_strategy_plans.py
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,41 @@ def test_soxl_soxx_trend_income_overlay_cap_can_downgrade_live_tier(self):
"market_status_blend_gate_overlay_capped",
)

dynamic_hold_plan = build_soxl_soxx_plan(
{
"soxl": {"price": 50.0, "ma_trend": 45.0},
"soxx": {
"price": 109.0,
"ma_trend": 100.0,
"rsi14": 75.0,
"rsi14_dynamic_threshold": 78.0,
"bb_upper": 120.0,
},
},
account_state,
**{**common_kwargs, "blend_gate_dynamic_rsi_threshold_enabled": True},
)
self.assertEqual(dynamic_hold_plan["blend_tier"], "full")
self.assertEqual(dynamic_hold_plan["overlay_trigger_count"], 0)
self.assertEqual(dynamic_hold_plan["trend_rsi14_effective_threshold"], 78.0)

dynamic_mid_plan = build_soxl_soxx_plan(
{
"soxl": {"price": 50.0, "ma_trend": 45.0},
"soxx": {
"price": 109.0,
"ma_trend": 100.0,
"rsi14": 79.0,
"rsi14_dynamic_threshold": 78.0,
"bb_upper": 120.0,
},
},
account_state,
**{**common_kwargs, "blend_gate_dynamic_rsi_threshold_enabled": True},
)
self.assertEqual(dynamic_mid_plan["blend_tier"], "mid")
self.assertEqual(dynamic_mid_plan["overlay_trigger_codes"], ("blend_gate_reason_rsi_cap",))

defensive_plan = build_soxl_soxx_plan(
{
"soxl": {"price": 50.0, "ma_trend": 45.0},
Expand Down