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: 2 additions & 0 deletions src/us_equity_strategies/signals/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT,
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF,
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX,
SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER,
SUPPORTED_MARKET_SIGNAL_REFERENCE_TYPES,
extract_consumer_market_signal_inputs_from_reference,
normalize_market_signal_fallback_mode,
Expand Down Expand Up @@ -152,6 +153,7 @@
"MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT",
"MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF",
"MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX",
"SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER",
"SUPPORTED_MARKET_SIGNAL_REFERENCE_TYPES",
"extract_consumer_market_signal_inputs_from_reference",
"normalize_market_signal_fallback_mode",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@

IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER = "us_equity:ibit_smart_dca"
NASDAQ_SP500_SMART_DCA_MARKET_SIGNAL_CONSUMER = "us_equity:nasdaq_sp500_smart_dca"
SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER = (
"us_equity:soxl_soxx_trend_income"
)
MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT = "consumption_audit"
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF = "platform_handoff"
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX = "platform_handoff_index"
Expand Down
18 changes: 18 additions & 0 deletions src/us_equity_strategies/signals/signal_bundle_contract.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,24 @@
"rsi14",
),
},
"us_equity:soxl_soxx_trend_income": {
"SOXL": (

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 Normalize SOXL symbols before fallback validation

When a SOXL/SOXX signal bundle uses the documented lowercase soxl/soxx symbols, the normal consumer validation accepts it because symbols are normalized and _canonical_market_data preserves the original keys. If fallback_mode=last_valid later loads that cached payload, _validate_market_signal_inputs_for_consumer does an exact derived_indicators.get(symbol) using this new uppercase SOXL contract key, so the fallback rejects a cache entry that the primary extraction path accepted; normalize the fallback lookup or align the contract casing with the canonical input shape.

Useful? React with 👍 / 👎.

"price",
"ma_trend",
),
"SOXX": (
"price",
"ma_trend",
"ma20",
"ma20_slope",
"rsi14",
"rsi14_dynamic_threshold",
"bb_upper",
"realized_volatility_10",
"realized_volatility_10_dynamic_threshold",
"realized_volatility_10_dynamic_sample_count",
),
},
"research:nasdaq_sp500_external_context_precomputed": {
"US-EQUITY-CONTEXT": (
"breadth_above_sma200_pct",
Expand Down
12 changes: 12 additions & 0 deletions tests/test_runtime_market_signal_inputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,18 @@
from us_equity_strategies.signals import runtime_market_signal_inputs as runtime_inputs


def test_runtime_market_signal_consumer_constants_are_exported():
assert runtime_inputs.IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER == (
"us_equity:ibit_smart_dca"
)
assert runtime_inputs.NASDAQ_SP500_SMART_DCA_MARKET_SIGNAL_CONSUMER == (
"us_equity:nasdaq_sp500_smart_dca"
)
assert runtime_inputs.SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER == (
"us_equity:soxl_soxx_trend_income"
)


def test_extract_consumer_market_signal_inputs_from_reference_uses_handoff_index(monkeypatch, tmp_path):
calls: dict[str, object] = {}

Expand Down
94 changes: 83 additions & 11 deletions tests/test_signal_bundle_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,31 @@ def _write_platform_handoff_inputs(tmp_path: Path) -> Path:
],
"research_consumers": [],
},
{
"family": "us_equity.semiconductor_rotation_daily",
"canonical_input": "derived_indicators",
"transform": "us_equity.semiconductor_rotation.v1",
"symbols": ["SOXL", "SOXX"],
"derived_indicator_fields": [
"price",
"ma_trend",
"ma20",
"ma20_slope",
"rsi14",
"rsi14_dynamic_threshold",
"bb_upper",
"realized_volatility_10",
"realized_volatility_10_dynamic_threshold",
"realized_volatility_10_dynamic_sample_count",
],
"compatible_profiles": [
"us_equity:soxl_soxx_trend_income",
],
"runtime_consumers": [
"us_equity:soxl_soxx_trend_income",
],
"research_consumers": [],
},
],
},
indent=2,
Expand All @@ -123,8 +148,8 @@ def _write_platform_handoff_inputs(tmp_path: Path) -> Path:
"catalog_sha256": _sha256_path(source_catalog_path),
"catalog_size_bytes": source_catalog_path.stat().st_size,
"catalog_schema_version": "market_signal_source_families.v1",
"family_count": 2,
"known_family_count": 2,
"family_count": 3,
"known_family_count": 3,
"missing_known_families": [],
"all_known_families_present": True,
"all_consumer_contracts_satisfied": True,
Expand Down Expand Up @@ -179,6 +204,28 @@ def _write_platform_handoff_inputs(tmp_path: Path) -> Path:
],
},
},
{
"consumer": "us_equity:soxl_soxx_trend_income",
"canonical_input": "derived_indicators",
"required_indicator_fields_by_symbol": {
"SOXL": [
"price",
"ma_trend",
],
"SOXX": [
"price",
"ma_trend",
"ma20",
"ma20_slope",
"rsi14",
"rsi14_dynamic_threshold",
"bb_upper",
"realized_volatility_10",
"realized_volatility_10_dynamic_threshold",
"realized_volatility_10_dynamic_sample_count",
],
},
},
{
"consumer": "research:ibit_btc_ahr999_precomputed",
"canonical_input": "derived_indicators",
Expand Down Expand Up @@ -265,8 +312,8 @@ def _write_platform_handoff_inputs(tmp_path: Path) -> Path:
"registry_size_bytes": registry_path.stat().st_size,
"registry_schema_version": "market_signal_consumer_contracts.v1",
"canonical_input": "derived_indicators",
"consumer_count": 9,
"known_consumer_count": 9,
"consumer_count": 10,
"known_consumer_count": 10,
"missing_known_consumers": [],
"all_known_consumers_present": True,
},
Expand Down Expand Up @@ -304,10 +351,11 @@ def _write_platform_handoff_inputs(tmp_path: Path) -> Path:
"consumer_contract_registry_manifest_sha256": _sha256_path(
registry_manifest_path
),
"source_family_count": 2,
"source_family_count": 3,
"source_families": [
"crypto.btc_cycle_daily",
"us_equity.technical_daily",
"us_equity.semiconductor_rotation_daily",
],
"all_known_source_families_present": True,
"all_consumer_contracts_satisfied": True,
Expand Down Expand Up @@ -654,9 +702,10 @@ def test_signal_bundle_cli_validates_platform_handoff_manifest(
assert summary["source_families"] == [
"crypto.btc_cycle_daily",
"us_equity.technical_daily",
"us_equity.semiconductor_rotation_daily",
]
assert summary["matched_source_families"] == ["crypto.btc_cycle_daily"]
assert summary["consumer_contract_count"] == 9
assert summary["consumer_contract_count"] == 10
assert summary["all_known_consumers_present"] is True
assert summary["all_runtime_consumers_covered"] is True
assert summary["handoff_linked_manifest_sha256s_verified"] is True
Expand Down Expand Up @@ -699,6 +748,7 @@ def test_signal_bundle_cli_validates_platform_handoff_index(
assert summary["source_families"] == [
"crypto.btc_cycle_daily",
"us_equity.technical_daily",
"us_equity.semiconductor_rotation_daily",
]
assert summary["matched_source_families"] == ["crypto.btc_cycle_daily"]
assert summary["handoff_linked_manifest_sha256s_verified"] is True
Expand Down Expand Up @@ -802,7 +852,7 @@ def test_signal_bundle_cli_validates_research_handoff_manifest(
assert summary["research_artifact_type"] == "btc_cycle_research_csv"
assert summary["research_transform"] == "crypto.btc.ahr999.v1"
assert summary["matched_source_families"] == ["crypto.btc_cycle_daily"]
assert summary["consumer_contract_count"] == 9
assert summary["consumer_contract_count"] == 10
assert summary["research_export_output_csv_verified"] is True
assert summary["handoff_linked_manifest_sha256s_verified"] is True

Expand All @@ -814,7 +864,7 @@ def test_signal_bundle_cli_prints_local_consumer_contract_registry(capsys) -> No
payload = json.loads(capsys.readouterr().out)
assert payload["schema_version"] == "market_signal_consumer_contracts.v1"
assert payload["canonical_input"] == "derived_indicators"
assert len(payload["contracts"]) == 9
assert len(payload["contracts"]) == 10
consumers = [contract["consumer"] for contract in payload["contracts"]]
assert "research:nasdaq_sp500_price_proxy" in consumers
price_proxy_contract = next(
Expand Down Expand Up @@ -942,6 +992,28 @@ def test_signal_bundle_cli_validates_consumer_contract_registry_manifest(
],
},
},
{
"consumer": "us_equity:soxl_soxx_trend_income",
"canonical_input": "derived_indicators",
"required_indicator_fields_by_symbol": {
"SOXL": [
"price",
"ma_trend",
],
"SOXX": [
"price",
"ma_trend",
"ma20",
"ma20_slope",
"rsi14",
"rsi14_dynamic_threshold",
"bb_upper",
"realized_volatility_10",
"realized_volatility_10_dynamic_threshold",
"realized_volatility_10_dynamic_sample_count",
],
},
},
{
"consumer": "research:ibit_btc_ahr999_precomputed",
"canonical_input": "derived_indicators",
Expand Down Expand Up @@ -1038,8 +1110,8 @@ def test_signal_bundle_cli_validates_consumer_contract_registry_manifest(
"registry_size_bytes": registry_path.stat().st_size,
"registry_schema_version": "market_signal_consumer_contracts.v1",
"canonical_input": "derived_indicators",
"consumer_count": 9,
"known_consumer_count": 9,
"consumer_count": 10,
"known_consumer_count": 10,
"missing_known_consumers": [],
"all_known_consumers_present": True,
},
Expand Down Expand Up @@ -1069,7 +1141,7 @@ def test_signal_bundle_cli_validates_consumer_contract_registry_manifest(
assert summary["registry_sha256"] == hashlib.sha256(
registry_path.read_bytes()
).hexdigest()
assert summary["consumer_count"] == 9
assert summary["consumer_count"] == 10
assert summary["all_known_consumers_present"] is True
assert summary["local_contract_registry_verified"] is True
assert summary["canonical_registry_payload_sha256"] == summary[
Expand Down
Loading