Switch active data source to Oanda v20 (practice feed)

config.json.example: data_providers.active = "oanda" (credentials stay
in env/.env or the top-level oanda block; TD and Tiingo remain
registered fallbacks). Backtest parquet cache is now keyed per provider
— Oanda and TD bars diverge (2026-07-24 study), so a feed switch must
never serve another feed's cached bars; twelve_data keeps the legacy
un-prefixed filename so pinned-book caches stay valid. 254 tests green;
live smoke-tested against the practice API (66 contract checks: all
scanned TFs, boundary/freshness guarantees, real spread $0.71,
fetch_range pagination, typed error paths, CLI + backtest end-to-end).

Claude-Session: https://claude.ai/code/session_01Mc7XKYyPySVXiUpcN82QEK
This commit is contained in:
Harwinder Sekhon
2026-07-28 20:12:59 -04:00
parent 083a4e8d63
commit 637c39e201
4 changed files with 33 additions and 6 deletions
+11 -3
View File
@@ -935,9 +935,17 @@ class GPUTechnicalEngine(TechnicalEngine):
CACHE_DIR = Path(__file__).resolve().parent / "cache"
def _cache_path(symbol: str, tf: Timeframe, start: datetime, end: datetime) -> Path:
def _cache_path(
symbol: str, tf: Timeframe, start: datetime, end: datetime,
provider_name: str = "twelve_data",
) -> Path:
safe_sym = symbol.replace("/", "_").replace("=X", "")
return CACHE_DIR / f"{safe_sym}_{tf.value}_{start:%Y%m%d}_{end:%Y%m%d}.parquet"
# Bars differ between feeds (Oanda vs TD divergence study 2026-07-24),
# so the cache is keyed per provider. twelve_data keeps the legacy
# un-prefixed filename so pinned-book caches stay valid.
prefix = "" if provider_name == "twelve_data" else f"{provider_name}_"
return CACHE_DIR / (
f"{prefix}{safe_sym}_{tf.value}_{start:%Y%m%d}_{end:%Y%m%d}.parquet")
def fetch_history(
@@ -962,7 +970,7 @@ def fetch_history(
cleaned data; a refetch is needed to restore dropped spike bars.)
"""
CACHE_DIR.mkdir(parents=True, exist_ok=True)
path = _cache_path(symbol, timeframe, start, end)
path = _cache_path(symbol, timeframe, start, end, provider.name)
if use_cache and path.exists():
logger.info(f"Loading cached {symbol} {timeframe.value}: {path.name}")
df = pd.read_parquet(path)