Provider abstraction: providers/ package with BaseDataProvider template

One class per provider (TwelveData, Oanda, Tiingo), all subclassing
BaseDataProvider which owns cache, validation/cleaning, freshness +
boundary guarantees, fetch_range pagination, and streaming stubs —
subclasses implement only _fetch_raw. config.json data_providers.active
selects the live feed; data_provider.py / oanda_provider.py stay as
back-compat shims. Module 16 provider-abstraction tests (253 total green).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Mc7XKYyPySVXiUpcN82QEK
This commit is contained in:
Harwinder Sekhon
2026-07-28 20:09:00 -04:00
parent 95f8c47d4f
commit c8beb7e3ae
17 changed files with 1573 additions and 935 deletions
+5 -5
View File
@@ -15,7 +15,7 @@ import pandas as pd
from chart_patterns import detect_chart_pattern
from config import PlatformConfig, SignalStrength, Timeframe
from data_provider import ForexDataProvider, make_data_provider
from providers import BaseDataProvider, make_data_provider
from engine import TechnicalEngine, TechnicalAnalysis
from news_engine import NewsEngine, FundamentalContext
@@ -180,7 +180,7 @@ class SignalAggregator:
try:
df = self.data_provider.fetch_ohlcv(symbol, tf)
dfs[tf] = df
if not self.data_provider._is_data_fresh(df, symbol, tf):
if not self.data_provider.is_data_fresh(df, symbol, tf):
stale_tfs.append(tf.value)
except Exception as e:
logger.warning(f"Fetch failed for {symbol} {tf.value}: {e}")
@@ -214,7 +214,7 @@ class SignalAggregator:
bar_close = (
pd.Timestamp(report.timestamp)
+ pd.Timedelta(
seconds=ForexDataProvider.CANDLE_SECONDS[
seconds=BaseDataProvider.CANDLE_SECONDS[
self.config.primary_timeframe
]
)
@@ -637,7 +637,7 @@ class SignalAggregator:
close_h = (
pd.Timestamp(report.timestamp)
+ pd.Timedelta(
seconds=ForexDataProvider.CANDLE_SECONDS[
seconds=BaseDataProvider.CANDLE_SECONDS[
self.config.primary_timeframe
]
)
@@ -939,7 +939,7 @@ class SignalAggregator:
close_h = (
pd.Timestamp(report.timestamp)
+ pd.Timedelta(
seconds=ForexDataProvider.CANDLE_SECONDS[
seconds=BaseDataProvider.CANDLE_SECONDS[
self.config.primary_timeframe
]
)