Skip to content

Commit ef2d459

Browse files
authored
Use unified market signal profile registry (#198)
1 parent 4a6ed0b commit ef2d459

3 files changed

Lines changed: 83 additions & 13 deletions

File tree

market_signal_runtime.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,15 @@
55
from typing import Any, Callable, Iterable
66

77
from us_equity_strategies.signals import (
8-
IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER,
98
MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT,
109
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF,
1110
MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX,
12-
SOXL_SOXX_TREND_INCOME_MARKET_SIGNAL_CONSUMER,
11+
default_market_signal_inputs_when_unconfigured,
1312
extract_consumer_market_signal_inputs_from_reference,
13+
market_signal_consumer_for_strategy_profile,
1414
)
1515

1616

17-
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-
}
2317
DEFAULT_MARKET_SIGNAL_CACHE_DIR = "/tmp/quant-platform-market-signals"
2418

2519

@@ -33,7 +27,7 @@ def resolve_external_market_signal_inputs(
3327
client_factory: Any = None,
3428
) -> dict[str, Any]:
3529
normalized_profile = str(strategy_profile or "").strip().lower()
36-
consumer = MARKET_SIGNAL_CONSUMER_BY_PROFILE.get(normalized_profile)
30+
consumer = market_signal_consumer_for_strategy_profile(normalized_profile)
3731
if consumer is None:
3832
return {}
3933
if "derived_indicators" not in {str(item) for item in available_inputs or ()}:
@@ -46,9 +40,7 @@ def resolve_external_market_signal_inputs(
4640
f"{normalized_profile} external market signal is required "
4741
"but no signal reference is configured"
4842
)
49-
if normalized_profile == IBIT_SMART_DCA_PROFILE:
50-
return {"derived_indicators": {}}
51-
return {}
43+
return default_market_signal_inputs_when_unconfigured(normalized_profile)
5244

5345
market_inputs, metadata = extract_consumer_market_signal_inputs_from_reference(
5446
reference,

requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
flask
22
gunicorn
33
quant-platform-kit @ git+https://github.com/QuantStrategyLab/QuantPlatformKit.git@46ca4ea3de8f98a58e2dd86158e7f2070d085cd1
4-
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@6518b33136c3ff0d2fdf8fe12ecce2fc296eb2ca
4+
us-equity-strategies @ git+https://github.com/QuantStrategyLab/UsEquityStrategies.git@a8e8385c6d7daf2510888cdb40d1daffdc13fb72
55
hk-equity-strategies @ git+https://github.com/QuantStrategyLab/HkEquityStrategies.git@ec54c685b7dbea931016854db081b8eeaaaef7d2
66
pandas
77
requests

tests/test_market_signal_runtime.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ def test_soxl_without_reference_preserves_legacy_inputs():
5252
) == {}
5353

5454

55+
def test_nasdaq_without_reference_preserves_legacy_inputs():
56+
settings = SimpleNamespace(market_signal_required=False)
57+
58+
assert market_signal_runtime.resolve_external_market_signal_inputs(
59+
strategy_profile="nasdaq_sp500_smart_dca",
60+
available_inputs={"derived_indicators"},
61+
runtime_settings=settings,
62+
) == {}
63+
64+
5565
def test_soxl_required_reference_missing_raises():
5666
settings = SimpleNamespace(market_signal_required=True)
5767

@@ -129,6 +139,74 @@ def fake_extract(
129139
)
130140

131141

142+
def test_nasdaq_handoff_index_reference_is_extracted(monkeypatch, tmp_path):
143+
calls: dict[str, object] = {}
144+
145+
def fake_extract(
146+
reference,
147+
*,
148+
reference_type,
149+
consumer,
150+
cache_dir,
151+
as_of,
152+
client_factory=None,
153+
fallback_mode=None,
154+
fallback_max_stale_days=None,
155+
):
156+
calls["extract"] = (
157+
reference,
158+
reference_type,
159+
consumer,
160+
cache_dir,
161+
as_of,
162+
client_factory,
163+
fallback_mode,
164+
fallback_max_stale_days,
165+
)
166+
return {
167+
"derived_indicators": {
168+
"QQQ": {"close": 450.0, "sma200_gap": 0.08},
169+
"SPY": {"close": 520.0, "sma200_gap": 0.05},
170+
}
171+
}, {
172+
"reference_type": reference_type,
173+
"source_uri": reference,
174+
"materialized_count": 2,
175+
}
176+
177+
monkeypatch.setattr(
178+
market_signal_runtime,
179+
"extract_consumer_market_signal_inputs_from_reference",
180+
fake_extract,
181+
)
182+
settings = SimpleNamespace(
183+
market_signal_handoff_index_uri="gs://signals/platform_handoffs/index.json",
184+
market_signal_cache_dir=str(tmp_path),
185+
market_signal_required=True,
186+
market_signal_fallback_mode="last_valid",
187+
market_signal_max_stale_days=4,
188+
)
189+
190+
assert market_signal_runtime.resolve_external_market_signal_inputs(
191+
strategy_profile="nasdaq_sp500_smart_dca",
192+
available_inputs={"derived_indicators"},
193+
runtime_settings=settings,
194+
as_of=datetime(2026, 6, 19, tzinfo=timezone.utc),
195+
logger=lambda _message: None,
196+
client_factory=object,
197+
)["derived_indicators"]["QQQ"]["close"] == 450.0
198+
assert calls["extract"] == (
199+
"gs://signals/platform_handoffs/index.json",
200+
"platform_handoff_index",
201+
"us_equity:nasdaq_sp500_smart_dca",
202+
tmp_path,
203+
"2026-06-19",
204+
object,
205+
"last_valid",
206+
4,
207+
)
208+
209+
132210
def test_soxl_handoff_index_reference_is_extracted(monkeypatch, tmp_path):
133211
calls: dict[str, object] = {}
134212

0 commit comments

Comments
 (0)