Files
ForexBot2/scripts/compare_oanda_tiingo.py
T
Harwinder Sekhon 37ff5b383d signal_log: persist agent panel verdict + per-persona takes to SQLite
Add panel_verdict, panel_confidence, panel_rationale, panel_takes_json
columns to the signals table (additive, idempotent ALTER TABLE migration
for existing DBs). _report_row now reads the panel fields attach_panel
writes onto the SignalReport, so LLM panel verdicts stop evaporating on
process exit. Also documents the Oanda re-pin (round_trip_cost stays
$0.30 for cross-book comparability and the live TP gate; $0.65 is the
measured headline) and adds the feed-divergence / parity / shadow-scan
study tooling referenced by CLAUDE.md.
2026-08-14 20:41:54 -04:00

117 lines
4.1 KiB
Python

#!/usr/bin/env python3
"""
Scratchpad — Oanda vs Tiingo candle parity check
=================================================
Fetches the last N closed XAU/USD candles from Oanda and Tiingo for
M5/M15/H1 via the normal provider stack (make_data_provider →
fetch_ohlcv: forming-candle drop + validate/clean included, i.e. exactly
what the signal pipeline consumes) and reports how well they line up:
* last-bar timestamp per provider (feed-lag check)
* timestamp mismatches (bars present in one feed only)
* per-bar OHLC diff table for the common tail
* max/mean |diff| per column, with the Close diff also in % of price
Both feeds are mid-price (Oanda mid of MBA candles; Tiingo resampled
mid), so small diffs are expected — this measures how small.
Usage:
venv/bin/python scripts/compare_oanda_tiingo.py [--n 10]
"""
import argparse
import sys
from pathlib import Path
import pandas as pd
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from config import PlatformConfig, Timeframe, XAUUSD
from providers import make_data_provider
TIMEFRAMES = [Timeframe.M5, Timeframe.M15, Timeframe.H1]
OHLC = ["Open", "High", "Low", "Close"]
def fetch_tail(provider, tf, n):
"""Last n closed bars from one provider, or None on failure."""
try:
df = provider.fetch_ohlcv(symbol=XAUUSD, timeframe=tf)
except Exception as e: # scratchpad: report, don't crash
print(f" !! {provider.name} {tf.value} fetch failed: {e}")
return None
return df.tail(n)
def compare_tf(tf, oanda_df, tiingo_df, n):
print(f"\n{'=' * 78}\n{tf.value} — last {n} closed bars\n{'=' * 78}")
if oanda_df is None or tiingo_df is None:
print(" skipped (a provider failed)")
return
print(f" Oanda last bar: {oanda_df.index[-1]} "
f"Tiingo last bar: {tiingo_df.index[-1]}")
common = oanda_df.index.intersection(tiingo_df.index)
only_o = oanda_df.index.difference(tiingo_df.index)
only_t = tiingo_df.index.difference(oanda_df.index)
if len(only_o):
print(f" bars only in Oanda : {[str(t) for t in only_o]}")
if len(only_t):
print(f" bars only in Tiingo: {[str(t) for t in only_t]}")
if not len(common):
print(" NO common timestamps — feeds are on different bar grids!")
return
o = oanda_df.loc[common, OHLC]
t = tiingo_df.loc[common, OHLC]
diff = o - t
print(f"\n {len(common)}/{n} bars share timestamps "
f"(window {common[0]} → {common[-1]})\n")
# Per-bar Close table — the number that matters most.
hdr = f" {'bar close (UTC)':<20}{'Oanda C':>12}{'Tiingo C':>12}{'ΔC ($)':>10}"
print(hdr + f"{'ΔO':>8}{'ΔH':>8}{'ΔL':>8}")
for ts in common:
row = (f" {str(ts):<20}{o.loc[ts, 'Close']:>12.2f}"
f"{t.loc[ts, 'Close']:>12.2f}{diff.loc[ts, 'Close']:>10.3f}")
row += (f"{diff.loc[ts, 'Open']:>8.3f}{diff.loc[ts, 'High']:>8.3f}"
f"{diff.loc[ts, 'Low']:>8.3f}")
print(row)
print("\n summary over common bars:")
for col in OHLC:
d = diff[col].abs()
print(f" {col:<6} max |Δ| = {d.max():8.4f} mean |Δ| = {d.mean():8.4f}")
px = o["Close"].iloc[-1]
dc = diff["Close"].abs()
print(f" Close Δ as % of price: max {dc.max() / px * 100:.4f}% "
f"mean {dc.mean() / px * 100:.4f}%")
exact = (diff[OHLC].abs() < 1e-9).all(axis=1).sum()
print(f" bars with identical OHLC: {exact}/{len(common)}")
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=10,
help="bars per timeframe to compare (default 10)")
args = ap.parse_args()
cfg = PlatformConfig()
oanda = make_data_provider(cfg, "oanda")
tiingo = make_data_provider(cfg, "tiingo")
for tf in TIMEFRAMES:
o_df = fetch_tail(oanda, tf, args.n)
t_df = fetch_tail(tiingo, tf, args.n)
compare_tf(tf, o_df, t_df, args.n)
print("\nDone. Note: Oanda=mid of MBA candles, Tiingo=resampled mid — "
"sub-$1 diffs on gold are normal feed divergence, not a bug.")
if __name__ == "__main__":
sys.exit(main())