From eaafccbe7ca3bdd4e8ebebe3fe225bf0459f979a Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 19 Aug 2026 11:49:12 +0800 Subject: [PATCH 1/3] fix(research): freeze TQQQ core-only exclusions Co-Authored-By: Codex --- .../entrypoints/__init__.py | 40 +++++++---- tests/test_entrypoint_risk_gate.py | 71 ++++++++++++++++--- 2 files changed, 88 insertions(+), 23 deletions(-) diff --git a/src/us_equity_strategies/entrypoints/__init__.py b/src/us_equity_strategies/entrypoints/__init__.py index 8fe8379..5348e40 100644 --- a/src/us_equity_strategies/entrypoints/__init__.py +++ b/src/us_equity_strategies/entrypoints/__init__.py @@ -628,6 +628,31 @@ def compute_tqqq_growth_income_decision(ctx: StrategyContext) -> StrategyDecisio ) +def _validate_tqqq_core_only_research_runtime_config(config: Mapping[str, object]) -> None: + """Reject any runtime configuration that re-enables excluded P2 components.""" + candidate_assets = ("TQQQ", "QQQM", "BOXX") + ai_extensions = config.get("ai_extensions") + if ( + config.get("benchmark_symbol") != "QQQ" + or tuple(config.get("managed_symbols") or ()) != candidate_assets + or config.get("signal_effective_after_trading_days") != 1 + or config.get("dual_drive_unlevered_symbol") != "QQQM" + or config.get("income_layer_enabled") is not False + or config.get("option_overlay_enabled") is not False + or config.get("option_growth_overlay_enabled") is not False + or config.get("option_income_overlay_enabled") is not False + or not isinstance(ai_extensions, Mapping) + or ai_extensions.get("enabled") is not False + or config.get("dual_drive_volatility_delever_retention_mode") != "none" + or config.get("dual_drive_volatility_delever_retention_ratio") != 0.0 + or config.get("dual_drive_volatility_delever_taco_veto_enabled") is not False + or config.get("dual_drive_macro_risk_governor_enabled") is not False + or config.get("dual_drive_crisis_defense_enabled") is not False + or config.get("market_regime_control_enabled") is not False + ): + raise ValueError("invalid TQQQ core-only research config") + + def evaluate_tqqq_growth_income_promotion_research( ctx: StrategyContext, *, @@ -643,21 +668,8 @@ def evaluate_tqqq_growth_income_promotion_research( scope = "MEMBER" try: config = merge_runtime_config(tqqq_growth_income_manifest.default_config, ctx) - ai_extensions = config.get("ai_extensions") candidate_assets = ("TQQQ", "QQQM", "BOXX") - if ( - config.get("benchmark_symbol") != "QQQ" - or tuple(config.get("managed_symbols") or ()) != candidate_assets - or config.get("signal_effective_after_trading_days") != 1 - or config.get("dual_drive_unlevered_symbol") != "QQQM" - or config.get("income_layer_enabled") is not False - or config.get("option_overlay_enabled") is not False - or config.get("option_growth_overlay_enabled") is not False - or config.get("option_income_overlay_enabled") is not False - or not isinstance(ai_extensions, Mapping) - or ai_extensions.get("enabled") is not False - ): - raise ValueError("invalid TQQQ core-parity config") + _validate_tqqq_core_only_research_runtime_config(config) raw_decision = _build_tqqq_growth_income_decision(ctx) portfolio = require_portfolio(ctx) diff --git a/tests/test_entrypoint_risk_gate.py b/tests/test_entrypoint_risk_gate.py index ad5642c..76c3efa 100644 --- a/tests/test_entrypoint_risk_gate.py +++ b/tests/test_entrypoint_risk_gate.py @@ -20,6 +20,28 @@ _TQQQ_NOW = datetime(2026, 8, 11, 12, 0, tzinfo=timezone.utc) +def _tqqq_core_runtime_config(**overrides: object) -> dict[str, object]: + config: dict[str, object] = { + "benchmark_symbol": "QQQ", + "managed_symbols": _TQQQ_CORE_ASSETS, + "signal_effective_after_trading_days": 1, + "dual_drive_unlevered_symbol": "QQQM", + "income_layer_enabled": False, + "option_overlay_enabled": False, + "option_growth_overlay_enabled": False, + "option_income_overlay_enabled": False, + "ai_extensions": {"enabled": False}, + "dual_drive_volatility_delever_retention_mode": "none", + "dual_drive_volatility_delever_retention_ratio": 0.0, + "dual_drive_volatility_delever_taco_veto_enabled": False, + "dual_drive_macro_risk_governor_enabled": False, + "dual_drive_crisis_defense_enabled": False, + "market_regime_control_enabled": False, + } + config.update(overrides) + return config + + def _tqqq_candidate() -> CandidateRiskIdentity: return CandidateRiskIdentity( strategy_profile="tqqq_core_parity_v1", @@ -80,15 +102,7 @@ def _tqqq_context(*, runtime_config: dict[str, object] | None = None) -> Strateg as_of=_TQQQ_NOW, portfolio=snapshot, market_data={"benchmark_history": history}, - runtime_config=runtime_config - or { - "managed_symbols": _TQQQ_CORE_ASSETS, - "signal_effective_after_trading_days": 1, - "income_layer_enabled": False, - "option_overlay_enabled": False, - "option_growth_overlay_enabled": False, - "option_income_overlay_enabled": False, - }, + runtime_config=runtime_config or _tqqq_core_runtime_config(), ) @@ -203,6 +217,45 @@ def test_tqqq_core_parity_invalid_overrides_fail_closed_after_one_assessment( engine.assess.assert_called_once() +@pytest.mark.parametrize( + ("key", "value"), + ( + ("dual_drive_volatility_delever_retention_mode", "environment"), + ("dual_drive_volatility_delever_retention_ratio", 0.25), + ("dual_drive_volatility_delever_taco_veto_enabled", True), + ("dual_drive_macro_risk_governor_enabled", True), + ("dual_drive_crisis_defense_enabled", True), + ("market_regime_control_enabled", True), + ), +) +def test_tqqq_core_parity_rejects_reenabled_p2_components_after_one_assessment( + key: str, + value: object, +) -> None: + candidate = _tqqq_candidate() + runtime_config = _tqqq_core_runtime_config(**{key: value}) + engine = Mock() + engine.assess.return_value = RiskAction(action="approve", reason="passed") + + with ( + patch("quant_platform_kit.risk.gate._utc_now", return_value=_TQQQ_NOW), + patch("quant_platform_kit.risk.gate.build_risk_engine", return_value=engine), + ): + result = entrypoints.evaluate_tqqq_growth_income_promotion_research( + _tqqq_context(runtime_config=runtime_config), + candidate_identity=candidate, + mandate_provenance=_tqqq_mandate(candidate), + stop_loss_distances={symbol: 0.05 for symbol in _TQQQ_CORE_ASSETS}, + drawdown_scalar=1.0, + inputs_fresh=True, + ) + + assert result.assessment.outcome == "REJECT" + assert result.decision.positions == () + assert "invalid_scope" in result.assessment.reason_codes + engine.assess.assert_called_once() + + def _soxl_candidate(**overrides: object) -> CandidateRiskIdentity: values: dict[str, object] = { "strategy_profile": "soxl_soxx_trend_income", From 5f0c30cdcaf3ee0f3f1c050acbe172580ea40c81 Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Wed, 19 Aug 2026 14:33:26 +0800 Subject: [PATCH 2/3] feat(research): add TQQQ P2 v2 adapter Co-Authored-By: Codex --- .../entrypoints/__init__.py | 20 +++ tests/test_tqqq_core_only_p2_v2_research.py | 170 ++++++++++++++++++ 2 files changed, 190 insertions(+) create mode 100644 tests/test_tqqq_core_only_p2_v2_research.py diff --git a/src/us_equity_strategies/entrypoints/__init__.py b/src/us_equity_strategies/entrypoints/__init__.py index 5348e40..8127301 100644 --- a/src/us_equity_strategies/entrypoints/__init__.py +++ b/src/us_equity_strategies/entrypoints/__init__.py @@ -653,6 +653,25 @@ def _validate_tqqq_core_only_research_runtime_config(config: Mapping[str, object raise ValueError("invalid TQQQ core-only research config") +def build_tqqq_core_only_p2_v2_research_decision( + ctx: StrategyContext, +) -> StrategyDecision: + """Build the future-bindable, core-only TQQQ P2 v2 research decision. + + The caller supplies an already-materialized ``StrategyContext``. This + adapter validates the frozen core-only runtime exclusions before delegating + to the existing value-decision builder. It does not assess a risk mandate, + size a position, record a decision, fetch market data, or create orders. + + It is intentionally separate from the P2 v1 promotion-research seam so a + later P2 v2 binding can pin this named public entrypoint without changing + the frozen v1 candidate or claiming that an existing P3 replay uses it. + """ + config = merge_runtime_config(tqqq_growth_income_manifest.default_config, ctx) + _validate_tqqq_core_only_research_runtime_config(config) + return _build_tqqq_growth_income_decision(ctx) + + def evaluate_tqqq_growth_income_promotion_research( ctx: StrategyContext, *, @@ -1943,6 +1962,7 @@ def evaluate_us_equity_combo_leveraged(ctx: StrategyContext) -> StrategyDecision "us_equity_combo_leveraged_entrypoint", "evaluate_global_etf_rotation", "compute_tqqq_growth_income_decision", + "build_tqqq_core_only_p2_v2_research_decision", "evaluate_tqqq_growth_income_promotion_research", "evaluate_tqqq_growth_income", "evaluate_soxl_soxx_trend_income", diff --git a/tests/test_tqqq_core_only_p2_v2_research.py b/tests/test_tqqq_core_only_p2_v2_research.py new file mode 100644 index 0000000..38345ee --- /dev/null +++ b/tests/test_tqqq_core_only_p2_v2_research.py @@ -0,0 +1,170 @@ +from __future__ import annotations + +from datetime import datetime, timezone +from unittest.mock import patch + +import pytest + +from quant_platform_kit.common.models import PortfolioSnapshot +from quant_platform_kit.strategy_contracts import StrategyContext + +import us_equity_strategies.entrypoints as entrypoints + + +_AS_OF = datetime(2026, 8, 11, 12, 0, tzinfo=timezone.utc) +_CORE_ASSETS = ("TQQQ", "QQQM", "BOXX") + + +def _core_only_runtime_config(**overrides: object) -> dict[str, object]: + config: dict[str, object] = { + "benchmark_symbol": "QQQ", + "managed_symbols": _CORE_ASSETS, + "signal_effective_after_trading_days": 1, + "dual_drive_unlevered_symbol": "QQQM", + "income_layer_enabled": False, + "option_overlay_enabled": False, + "option_growth_overlay_enabled": False, + "option_income_overlay_enabled": False, + "ai_extensions": {"enabled": False}, + "dual_drive_volatility_delever_retention_mode": "none", + "dual_drive_volatility_delever_retention_ratio": 0.0, + "dual_drive_volatility_delever_taco_veto_enabled": False, + "dual_drive_macro_risk_governor_enabled": False, + "dual_drive_crisis_defense_enabled": False, + "market_regime_control_enabled": False, + } + config.update(overrides) + return config + + +def _context( + history: list[dict[str, float]], + *, + runtime_config: dict[str, object] | None = None, +) -> StrategyContext: + snapshot = PortfolioSnapshot( + as_of=_AS_OF, + total_equity=100_000.0, + buying_power=100_000.0, + cash_balance=100_000.0, + positions=(), + metadata={"observed_effective_exposure": 0.0}, + ) + return StrategyContext( + as_of=_AS_OF, + portfolio=snapshot, + market_data={"benchmark_history": history}, + runtime_config=runtime_config or _core_only_runtime_config(), + ) + + +def _targets(ctx: StrategyContext) -> tuple[dict[str, float], dict[str, object]]: + decision = entrypoints.build_tqqq_core_only_p2_v2_research_decision(ctx) + return ( + {position.symbol: float(position.target_value or 0.0) for position in decision.positions}, + dict(decision.diagnostics), + ) + + +def _rising_history() -> list[dict[str, float]]: + return [ + {"close": 100.0 + index, "high": 101.0 + index, "low": 99.0 + index} + for index in range(260) + ] + + +def test_public_p2_v2_adapter_validates_then_delegates_to_existing_builder() -> None: + ctx = _context(_rising_history()) + with ( + patch.object( + entrypoints, + "_build_tqqq_growth_income_decision", + wraps=entrypoints._build_tqqq_growth_income_decision, + ) as builder, + patch.object(entrypoints, "assess_with_evidence") as assess, + patch.object(entrypoints, "risk_budgeted_target_weights") as size, + patch.object(entrypoints, "record_strategy_decision") as record, + ): + targets, diagnostics = _targets(ctx) + + builder.assert_called_once_with(ctx) + assert "build_tqqq_core_only_p2_v2_research_decision" in entrypoints.__all__ + assess.assert_not_called() + size.assert_not_called() + record.assert_not_called() + assert targets == { + "BOXX": 8_000.0, + "DGRO": 0.0, + "QQQI": 0.0, + "QQQM": 45_000.0, + "SCHD": 0.0, + "SGOV": 0.0, + "SPYI": 0.0, + "TQQQ": 45_000.0, + } + assert diagnostics["notification_context"]["signal"]["state"] == "entry" + + +def test_p2_v2_synthetic_trend_defense_parks_in_boxx() -> None: + history = [ + {"close": 360.0 - index, "high": 361.0 - index, "low": 359.0 - index} + for index in range(260) + ] + + targets, diagnostics = _targets(_context(history)) + + assert targets["TQQQ"] == 0.0 + assert targets["QQQM"] == 0.0 + assert targets["BOXX"] == 98_000.0 + assert diagnostics["notification_context"]["signal"]["state"] == "idle" + + +def test_p2_v2_synthetic_pullback_reentry_restores_tqqq_and_qqqm() -> None: + history = [ + {"close": 120.0, "high": 121.0, "low": 119.0} for _ in range(220) + ] + [ + { + "close": 100.0 + index * 0.45, + "high": 101.0 + index * 0.45, + "low": 99.0 + index * 0.45, + } + for index in range(21) + ] + + targets, diagnostics = _targets(_context(history)) + + assert targets["TQQQ"] == 45_000.0 + assert targets["QQQM"] == 45_000.0 + assert targets["BOXX"] == 8_000.0 + assert diagnostics["notification_context"]["signal"]["state"] == "entry" + + +def test_p2_v2_synthetic_volatility_redirects_tqqq_to_qqqm() -> None: + history = [ + {"close": 100.0, "high": 101.0, "low": 99.0} for _ in range(230) + ] + [ + {"close": close, "high": close + 1.0, "low": close - 1.0} + for close in (130.0, 80.0, 135.0, 82.0, 140.0, 85.0, 145.0, 88.0, 150.0, 90.0, 155.0) + ] + + targets, diagnostics = _targets(_context(history)) + + assert targets["TQQQ"] == 0.0 + assert targets["QQQM"] == 90_000.0 + assert targets["BOXX"] == 8_000.0 + assert diagnostics["dual_drive_volatility_delever_applied"] is True + assert diagnostics["dual_drive_volatility_delever_redirect_symbol"] == "QQQM" + + +def test_p2_v2_rejects_reenabled_retention_before_builder_runs() -> None: + ctx = _context( + _rising_history(), + runtime_config=_core_only_runtime_config( + dual_drive_volatility_delever_retention_mode="environment", + ), + ) + with patch.object(entrypoints, "_build_tqqq_growth_income_decision") as builder: + with pytest.raises(ValueError, match="invalid TQQQ core-only research config"): + entrypoints.build_tqqq_core_only_p2_v2_research_decision(ctx) + + builder.assert_not_called() From 1e3c1b8b2a8b0c4bfc17eca5930e5ecf6051d64a Mon Sep 17 00:00:00 2001 From: Pigbibi <20649888+Pigbibi@users.noreply.github.com> Date: Sun, 23 Aug 2026 19:04:55 +0800 Subject: [PATCH 3/3] docs: record smart dca p3 evidence boundary Co-Authored-By: Codex --- .../evidence/nasdaq_sp500_smart_dca/README.md | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 docs/evidence/nasdaq_sp500_smart_dca/README.md diff --git a/docs/evidence/nasdaq_sp500_smart_dca/README.md b/docs/evidence/nasdaq_sp500_smart_dca/README.md new file mode 100644 index 0000000..2135297 --- /dev/null +++ b/docs/evidence/nasdaq_sp500_smart_dca/README.md @@ -0,0 +1,30 @@ +# Nasdaq/S&P 500 Smart DCA — P3 evidence boundary + +This directory is reserved for the unified P3 evidence package for +`nasdaq_sp500_smart_dca`. + +The current research notes and tests are not promoted to a P3 evidence package +yet. The existing sweep is a price-only proxy and does not include a committed, +hash-pinned input manifest, point-in-time validation, or a complete cost/turnover +ledger. Those omissions must be resolved before the lifecycle matrix can mark +P3 as verified. + +## Current status + +- lifecycle stage: `P3` +- status: `DEFERRED` +- authority: research only; `no_order=true` +- source notes: `docs/research/nasdaq_sp500_smart_dca.md` +- follow-up matrix: `docs/research/nasdaq_sp500_price_proxy_matrix_2026-06-19.md` + +## Required artifacts before registration + +1. Hash-pinned QQQ/SPY proxy input manifest. +2. Frozen configuration snapshot and code revision. +3. Benchmark and cost-model records. +4. Reproducible trial ledger and locked holdout result. +5. Validated evidence package consumed by `gate_evidence_package.py`. + +No file in this directory asserts performance, shadow, paper, live, or capital +authority. It is intentionally a visible placeholder so the missing evidence +cannot be mistaken for an untracked implementation gap.