Skip to content

Commit 025f9db

Browse files
authored
Enable SOXL market signal runtime (#117)
1 parent dfd2c3a commit 025f9db

3 files changed

Lines changed: 110 additions & 7 deletions

File tree

market_signal_runtime.py

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,17 @@
99
MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT,
1010
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF,
1111
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX,
12+
SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER,
1213
extract_consumer_market_signal_inputs_from_reference,
1314
)
1415

1516

1617
IBIT_SMART_DCA_PROFILE = "ibit_smart_dca"
18+
SOXL_SOXX_TREND_INCOME_PROFILE = "soxl_soxx_trend_income"
19+
MARKET_SIGNAL_CONSUMER_BY_PROFILE = {
20+
IBIT_SMART_DCA_PROFILE: IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER,
21+
SOXL_SOXX_TREND_INCOME_PROFILE: SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER,
22+
}
1723
DEFAULT_MARKET_SIGNAL_CACHE_DIR = "/tmp/quant-platform-market-signals"
1824

1925

@@ -26,21 +32,28 @@ def resolve_external_market_signal_inputs(
2632
logger: Callable[[str], None] = print,
2733
client_factory: Any = None,
2834
) -> dict[str, Any]:
29-
if str(strategy_profile or "").strip().lower() != IBIT_SMART_DCA_PROFILE:
35+
normalized_profile = str(strategy_profile or "").strip().lower()
36+
consumer = MARKET_SIGNAL_CONSUMER_BY_PROFILE.get(normalized_profile)
37+
if consumer is None:
3038
return {}
3139
if "derived_indicators" not in {str(item) for item in available_inputs or ()}:
3240
return {}
3341

3442
reference_type, reference = _market_signal_reference(runtime_settings)
3543
if reference is None:
3644
if bool(getattr(runtime_settings, "market_signal_required", False)):
37-
raise RuntimeError("IBIT external market signal is required but no signal reference is configured")
38-
return {"derived_indicators": {}}
45+
raise RuntimeError(
46+
f"{normalized_profile} external market signal is required "
47+
"but no signal reference is configured"
48+
)
49+
if normalized_profile == IBIT_SMART_DCA_PROFILE:
50+
return {"derived_indicators": {}}
51+
return {}
3952

4053
market_inputs, metadata = extract_consumer_market_signal_inputs_from_reference(
4154
reference,
4255
reference_type=reference_type,
43-
consumer=IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER,
56+
consumer=consumer,
4457
cache_dir=_market_signal_cache_dir(runtime_settings),
4558
as_of=_market_signal_as_of(as_of),
4659
client_factory=client_factory,

tests/test_market_signal_runtime.py

Lines changed: 93 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
import market_signal_runtime
99

1010

11-
def test_non_ibit_profile_does_not_load_market_signal():
11+
def test_unsupported_profile_does_not_load_market_signal():
1212
settings = SimpleNamespace(market_signal_required=True)
1313

1414
assert (
1515
market_signal_runtime.resolve_external_market_signal_inputs(
16-
strategy_profile="soxl_soxx_trend_income",
16+
strategy_profile="tqqq_growth_income",
1717
available_inputs={"derived_indicators"},
1818
runtime_settings=settings,
1919
)
@@ -42,6 +42,30 @@ def test_ibit_required_reference_missing_raises():
4242
)
4343

4444

45+
def test_soxl_without_reference_preserves_legacy_inputs():
46+
settings = SimpleNamespace(market_signal_required=False)
47+
48+
assert market_signal_runtime.resolve_external_market_signal_inputs(
49+
strategy_profile="soxl_soxx_trend_income",
50+
available_inputs={"derived_indicators"},
51+
runtime_settings=settings,
52+
) == {}
53+
54+
55+
def test_soxl_required_reference_missing_raises():
56+
settings = SimpleNamespace(market_signal_required=True)
57+
58+
with pytest.raises(
59+
RuntimeError,
60+
match="soxl_soxx_trend_income external market signal is required",
61+
):
62+
market_signal_runtime.resolve_external_market_signal_inputs(
63+
strategy_profile="soxl_soxx_trend_income",
64+
available_inputs={"derived_indicators"},
65+
runtime_settings=settings,
66+
)
67+
68+
4569
def test_ibit_handoff_index_reference_is_extracted(monkeypatch, tmp_path):
4670
calls: dict[str, object] = {}
4771

@@ -103,3 +127,70 @@ def fake_extract(
103127
"last_valid",
104128
5,
105129
)
130+
131+
132+
def test_soxl_handoff_index_reference_is_extracted(monkeypatch, tmp_path):
133+
calls: dict[str, object] = {}
134+
135+
def fake_extract(
136+
reference,
137+
*,
138+
reference_type,
139+
consumer,
140+
cache_dir,
141+
as_of,
142+
client_factory=None,
143+
fallback_mode=None,
144+
fallback_max_stale_days=None,
145+
):
146+
calls["extract"] = (
147+
reference,
148+
reference_type,
149+
consumer,
150+
cache_dir,
151+
as_of,
152+
client_factory,
153+
fallback_mode,
154+
fallback_max_stale_days,
155+
)
156+
return {
157+
"derived_indicators": {
158+
"SOXL": {"price": 25.0, "ma_trend": 24.0},
159+
"SOXX": {"price": 400.0, "ma_trend": 390.0},
160+
}
161+
}, {
162+
"reference_type": reference_type,
163+
"source_uri": reference,
164+
"materialized_count": 2,
165+
}
166+
167+
monkeypatch.setattr(
168+
market_signal_runtime,
169+
"extract_consumer_market_signal_inputs_from_reference",
170+
fake_extract,
171+
)
172+
settings = SimpleNamespace(
173+
market_signal_handoff_index_uri="gs://signals/platform_handoffs/index.json",
174+
market_signal_cache_dir=str(tmp_path),
175+
market_signal_required=True,
176+
market_signal_fallback_mode="none",
177+
)
178+
179+
assert market_signal_runtime.resolve_external_market_signal_inputs(
180+
strategy_profile="soxl_soxx_trend_income",
181+
available_inputs={"derived_indicators"},
182+
runtime_settings=settings,
183+
as_of=datetime(2026, 6, 19, tzinfo=timezone.utc),
184+
logger=lambda _message: None,
185+
client_factory=object,
186+
)["derived_indicators"]["SOXL"]["price"] == 25.0
187+
assert calls["extract"] == (
188+
"gs://signals/platform_handoffs/index.json",
189+
"platform_handoff_index",
190+
"us_equity:soxl_soxx_trend_income",
191+
tmp_path,
192+
"2026-06-19",
193+
object,
194+
"none",
195+
3,
196+
)

tests/test_strategy_registry.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,3 @@ def test_profile_status_matrix_reports_firstrade_without_bridge_metadata():
2222
assert all(row["platform"] == FIRSTRADE_PLATFORM for row in rows)
2323
assert all("strategy_adapter_source_platform" not in row for row in rows)
2424
assert "global_etf_rotation" in get_supported_profiles_for_platform(FIRSTRADE_PLATFORM)
25-

0 commit comments

Comments
 (0)