Tiingo WebSocket streaming: re-enable the level-breach monitor
The monitor has been dead since the Oanda feed switch (Oanda's provider does not stream). New config.json scalar data_providers.streaming selects a tick-stream provider independent of the candle feed; telegram_bot resolves it via _get_streaming_dp() (separate instance, candle cache stays on the active provider) and falls back to the active provider when unset — behavior is byte-identical to before when the key is absent. TiingoDataProvider grows the two sanctioned streaming overrides: supports_streaming=True and iter_ticks() on wss://api.tiingo.com/fx (mid price only — the no-spread rule extends to the stream). Two deliberate divergences from the TD reference implementation, both because Tiingo authenticates inside the socket: server "E" messages raise TiingoDataError (a swallowed auth error would end the generator cleanly and reconnect-hammer with zero backoff), and on_connect fires on the first received message rather than after the subscribe send. Field order of quote frames was live-verified: ask is data[7], not data[6] as the docs table suggests (data[6] is askSize). make_data_provider(config, name=None) now takes an explicit provider name; keyless streaming providers disable the monitor with one warning instead of a traceback-per-minute loop. Tests 266 → 273 (WS parser, ticker round-trip, streaming config scalar, explicit-name dispatch, streaming-dp resolution, monitor auto-disable). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Nsd8HcrV67GbCZXxZEn1Ma
This commit is contained in:
+34
-3
@@ -12,7 +12,9 @@ Auto-scan: Starts automatically on boot if TELEGRAM_CHAT_ID is set.
|
||||
M15 fires at :00/:15/:30/:45 + 10s buffer.
|
||||
H1 fires at :00 + 10s buffer.
|
||||
H4 fires at :00 every 4 hours + 10s buffer.
|
||||
WebSocket: Streams live ticks from TwelveData for XAU/USD.
|
||||
WebSocket: Streams live ticks from the streaming provider — config.json
|
||||
`data_providers.streaming` when set (e.g. tiingo), else the
|
||||
active provider if it streams; auto-disabled otherwise.
|
||||
When price crosses a key structure level (swing S/R, BB,
|
||||
EMA200) cached from the last boundary scan, triggers an
|
||||
immediate M15 scan and sends a Level Break Alert.
|
||||
@@ -203,6 +205,27 @@ def _get_shared_dp() -> BaseDataProvider:
|
||||
return _shared_dp
|
||||
|
||||
|
||||
_streaming_dp: Optional[BaseDataProvider] = None
|
||||
|
||||
|
||||
def _get_streaming_dp() -> BaseDataProvider:
|
||||
"""Provider for the tick stream: config.json data_providers.streaming
|
||||
when set (a SEPARATE instance — the candle cache stays on the active
|
||||
provider), else the shared active provider. Reads app config at call
|
||||
time, not the import-time _APP snapshot, so tests can swap it via
|
||||
set_app_config()."""
|
||||
global _streaming_dp
|
||||
if _streaming_dp is None:
|
||||
app = get_app_config()
|
||||
if app.streaming_provider is None or \
|
||||
app.streaming_provider == app.active_provider:
|
||||
_streaming_dp = _get_shared_dp()
|
||||
else:
|
||||
_streaming_dp = make_data_provider(
|
||||
PlatformConfig(), app.streaming_provider)
|
||||
return _streaming_dp
|
||||
|
||||
|
||||
# ── Signal formatting ─────────────────────────────────────────────────
|
||||
|
||||
_SIGNAL_EMOJI = {
|
||||
@@ -755,12 +778,19 @@ async def _ws_monitor(bot) -> None:
|
||||
"""Level-breach tick monitor. The provider owns the connection and
|
||||
protocol (`iter_ticks`); this loop owns reconnect/backoff and breach
|
||||
dispatch. Providers without streaming quotes simply disable it."""
|
||||
dp = _get_shared_dp()
|
||||
dp = _get_streaming_dp()
|
||||
if not dp.supports_streaming:
|
||||
logger.info(
|
||||
"Data provider %r has no streaming quotes — "
|
||||
"level-breach monitor disabled", dp.name)
|
||||
return
|
||||
if getattr(dp, "_api_key", "unused") is None:
|
||||
# Guard, not a traceback loop: without a key every connect
|
||||
# attempt would raise and re-log a full traceback every ≤60s.
|
||||
logger.warning(
|
||||
"Streaming provider %r has no API key — "
|
||||
"level-breach monitor disabled", dp.name)
|
||||
return
|
||||
|
||||
backoff = 1
|
||||
while _feed_active():
|
||||
@@ -951,7 +981,8 @@ def _status_text() -> str:
|
||||
lines.append(f"📢 Channel feed: *on* (`{CHANNEL_ID}`)")
|
||||
|
||||
if _ws_task and not _ws_task.done():
|
||||
lines.append("📡 WebSocket: *connected*")
|
||||
provider = _streaming_dp.name if _streaming_dp else "?"
|
||||
lines.append(f"📡 WebSocket: *connected* ({provider})")
|
||||
else:
|
||||
lines.append("❌ WebSocket: *disconnected*")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user