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.
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
#!/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())
|
||||
Reference in New Issue
Block a user