f7d8f1113c
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). Claude-Session: https://claude.ai/code/session_01Nsd8HcrV67GbCZXxZEn1Ma
58 lines
1.9 KiB
Python
58 lines
1.9 KiB
Python
"""
|
|
ForexBot2 — Data-provider package
|
|
==================================
|
|
One class per provider, all subclassing `BaseDataProvider` (which owns
|
|
caching, validation, boundary guarantees, and range pagination — the
|
|
behavior the signal pipeline depends on).
|
|
|
|
Adding a provider = one module implementing `_fetch_raw` + one registry
|
|
entry + one config.json `data_providers` block. The live candle feed is
|
|
selected by config.json `data_providers.active`; the optional
|
|
`data_providers.streaming` scalar selects a different provider for the
|
|
tick stream (level-breach monitor). Registration alone changes nothing.
|
|
"""
|
|
|
|
from typing import Dict, Optional
|
|
|
|
from app_config import AppConfigError, get_app_config
|
|
from config import PlatformConfig
|
|
|
|
from providers.base import BaseDataProvider, DataIntegrityError, _is_forex_weekend
|
|
from providers.twelve_data import TwelveDataProvider
|
|
from providers.oanda import OandaDataError, OandaDataProvider
|
|
from providers.tiingo import TiingoDataError, TiingoDataProvider
|
|
|
|
#: config.json data_providers.active selects from here.
|
|
PROVIDER_REGISTRY: Dict[str, type] = {
|
|
"twelve_data": TwelveDataProvider,
|
|
"oanda": OandaDataProvider,
|
|
"tiingo": TiingoDataProvider,
|
|
}
|
|
|
|
|
|
def make_data_provider(config: PlatformConfig,
|
|
name: Optional[str] = None) -> BaseDataProvider:
|
|
"""Build a provider by name (default: config.json data_providers.active)."""
|
|
if name is None:
|
|
name = get_app_config().active_provider
|
|
cls = PROVIDER_REGISTRY.get(name)
|
|
if cls is None:
|
|
raise AppConfigError(
|
|
f'Unknown data provider "{name}"; '
|
|
f"registered: {sorted(PROVIDER_REGISTRY)}")
|
|
return cls(config)
|
|
|
|
|
|
__all__ = [
|
|
"BaseDataProvider",
|
|
"DataIntegrityError",
|
|
"OandaDataError",
|
|
"OandaDataProvider",
|
|
"TiingoDataError",
|
|
"TiingoDataProvider",
|
|
"TwelveDataProvider",
|
|
"PROVIDER_REGISTRY",
|
|
"make_data_provider",
|
|
"_is_forex_weekend",
|
|
]
|