|
| 1 | +from __future__ import annotations |
| 2 | + |
| 3 | +from datetime import date, datetime |
| 4 | +from pathlib import Path |
| 5 | +from typing import Any, Callable, Iterable |
| 6 | + |
| 7 | +from us_equity_strategies.signals import ( |
| 8 | + IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER, |
| 9 | + MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT, |
| 10 | + MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF, |
| 11 | + MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX, |
| 12 | + extract_consumer_market_signal_inputs_from_reference, |
| 13 | +) |
| 14 | + |
| 15 | + |
| 16 | +IBIT_SMART_DCA_PROFILE = "ibit_smart_dca" |
| 17 | +DEFAULT_MARKET_SIGNAL_CACHE_DIR = "/tmp/quant-platform-market-signals" |
| 18 | + |
| 19 | + |
| 20 | +def resolve_external_market_signal_inputs( |
| 21 | + *, |
| 22 | + strategy_profile: str, |
| 23 | + available_inputs: Iterable[str], |
| 24 | + runtime_settings: Any, |
| 25 | + as_of: Any = None, |
| 26 | + logger: Callable[[str], None] = print, |
| 27 | + client_factory: Any = None, |
| 28 | +) -> dict[str, Any]: |
| 29 | + if str(strategy_profile or "").strip().lower() != IBIT_SMART_DCA_PROFILE: |
| 30 | + return {} |
| 31 | + if "derived_indicators" not in {str(item) for item in available_inputs or ()}: |
| 32 | + return {} |
| 33 | + |
| 34 | + reference_type, reference = _market_signal_reference(runtime_settings) |
| 35 | + if reference is None: |
| 36 | + 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": {}} |
| 39 | + |
| 40 | + market_inputs, metadata = extract_consumer_market_signal_inputs_from_reference( |
| 41 | + reference, |
| 42 | + reference_type=reference_type, |
| 43 | + consumer=IBIT_SMART_DCA_MARKET_SIGNAL_CONSUMER, |
| 44 | + cache_dir=_market_signal_cache_dir(runtime_settings), |
| 45 | + as_of=_market_signal_as_of(as_of), |
| 46 | + client_factory=client_factory, |
| 47 | + ) |
| 48 | + logger( |
| 49 | + "market_signal_inputs_loaded | " |
| 50 | + f"profile={strategy_profile} reference_type={metadata.get('reference_type')} " |
| 51 | + f"source_uri={metadata.get('source_uri') or reference} " |
| 52 | + f"materialized_count={metadata.get('materialized_count')}" |
| 53 | + ) |
| 54 | + return dict(market_inputs) |
| 55 | + |
| 56 | + |
| 57 | +def _market_signal_reference(runtime_settings: Any) -> tuple[str, str | None]: |
| 58 | + consumption_audit_uri = _optional_string( |
| 59 | + getattr(runtime_settings, "market_signal_consumption_audit_uri", None) |
| 60 | + ) |
| 61 | + if consumption_audit_uri: |
| 62 | + return MARKET_SIGNAL_REFERENCE_CONSUMPTION_AUDIT, consumption_audit_uri |
| 63 | + |
| 64 | + handoff_manifest_uri = _optional_string( |
| 65 | + getattr(runtime_settings, "market_signal_handoff_manifest_uri", None) |
| 66 | + ) |
| 67 | + if handoff_manifest_uri: |
| 68 | + return MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF, handoff_manifest_uri |
| 69 | + |
| 70 | + handoff_index_uri = _optional_string( |
| 71 | + getattr(runtime_settings, "market_signal_handoff_index_uri", None) |
| 72 | + ) |
| 73 | + if handoff_index_uri: |
| 74 | + return MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX, handoff_index_uri |
| 75 | + |
| 76 | + return MARKET_SIGNAL_REFERENCE_PLATFORM_HANDOFF_INDEX, None |
| 77 | + |
| 78 | + |
| 79 | +def _market_signal_cache_dir(runtime_settings: Any) -> Path: |
| 80 | + configured = _optional_string(getattr(runtime_settings, "market_signal_cache_dir", None)) |
| 81 | + return Path(configured or DEFAULT_MARKET_SIGNAL_CACHE_DIR) |
| 82 | + |
| 83 | + |
| 84 | +def _market_signal_as_of(value: Any) -> str | None: |
| 85 | + if value is None: |
| 86 | + return None |
| 87 | + if isinstance(value, datetime): |
| 88 | + return value.date().isoformat() |
| 89 | + if isinstance(value, date): |
| 90 | + return value.isoformat() |
| 91 | + text = str(value).strip() |
| 92 | + return text[:10] if text else None |
| 93 | + |
| 94 | + |
| 95 | +def _optional_string(value: Any) -> str | None: |
| 96 | + text = str(value or "").strip() |
| 97 | + return text or None |
0 commit comments