#!/usr/bin/env python3
"""
patch_v3_fib_bug.py
====================
Applies two surgical fixes to live_signal_monitor_v3.py:

PATCH 1 (Layer 2 fix): Change `signal_bar = df.iloc[-1]` to `df.iloc[-2]`
  so v3 uses the LAST COMPLETED 15M bar for Fib target calculation,
  not the in-progress current bar (which may have just 60 seconds of
  tick data and unreliable H/L values).

PATCH 2 (Layer 1 fix): Add explicit diagnostic logging when the Fib
  pull guard fires. Previously silent-returned with no log entry.
  Now logs the z-score, direction, pull in pips, and bar OHLC values.

Why both? Patch 1 likely fixes the root cause (today's z=+2.81 and
z=+3.70 signals silently didn't arm because the in-progress bar's
C-L was too small). Patch 2 ensures we have visibility next time
ANY guard fires — no more silent failures.

Usage:
  python patch_v3_fib_bug.py

The script:
  1. Locates live_signal_monitor_v3.py automatically
  2. Creates a .backup copy first (safe to re-run, won't double-backup)
  3. Applies both patches with strict text matching
  4. Verifies Python syntax of the result
  5. Reports success or rolls back on any error
"""
import sys
import shutil
from pathlib import Path

# ── Locate the v3 file ─────────────────────────────────────────────────────
candidates = [
    Path(r"C:\Users\paul_\OneDrive\fx_macro_intraday\src\execution\live_signal_monitor_v3.py"),
    Path(r"C:\Users\Administrator\OneDrive\fx_macro_intraday\src\execution\live_signal_monitor_v3.py"),
    Path(__file__).parent / "live_signal_monitor_v3.py",
]
target = None
for p in candidates:
    if p.exists():
        target = p
        break

if target is None:
    print("ERROR: could not find live_signal_monitor_v3.py at any of:")
    for p in candidates:
        print(f"  {p}")
    print("\nPlace this script next to live_signal_monitor_v3.py and re-run.")
    sys.exit(1)

print(f"Target file: {target}")

# ── Backup ─────────────────────────────────────────────────────────────────
backup = target.with_suffix(".py.backup")
if not backup.exists():
    shutil.copy2(target, backup)
    print(f"Backup created: {backup}")
else:
    print(f"Backup already exists (not overwriting): {backup}")

# ── Read current content ───────────────────────────────────────────────────
content = target.read_text(encoding="utf-8")
original_content = content

# ── PATCH 1: signal_bar = df.iloc[-1] -> df.iloc[-2] ───────────────────────
old1 = '''    if cfg.get("signal_timeframe") == "15m":
        # df is already 15M bars with z-score; latest row IS the signal bar
        signal_bar = df.iloc[-1]
        bh = float(signal_bar["high"])
        bl = float(signal_bar["low"])
        bc = float(signal_bar["close"])'''

new1 = '''    if cfg.get("signal_timeframe") == "15m":
        # df is 15M bars with z-score. Use df.iloc[-2] = LAST COMPLETED bar
        # for Fib target, NOT df.iloc[-1] = in-progress current bar.
        # Bug found 2026-05-27: z=+2.81 (11:31 UTC) and z=+3.70 (16:01 UTC)
        # signals silently didn't arm because the in-progress bar (1 minute
        # of tick data) had C-L < 1 pip, hitting the Fib pull guard. Using
        # the completed prior bar gives stable H/L/C from a full 15 minutes
        # of price action — same source the backtest uses.
        signal_bar = df.iloc[-2]
        bh = float(signal_bar["high"])
        bl = float(signal_bar["low"])
        bc = float(signal_bar["close"])'''

if old1 in content:
    content = content.replace(old1, new1)
    print("PATCH 1 applied: signal_bar = df.iloc[-2] (use completed bar)")
elif new1 in content:
    print("PATCH 1 skipped: already applied")
else:
    print("ERROR: PATCH 1 — original text not found. Aborting.")
    print("File may already be modified or differ from expected v3.")
    sys.exit(2)

# ── PATCH 2: Add diagnostic logging to Fib pull guards ─────────────────────
old2 = '''    if direction == 1:
        pull = bc - bl
        if pull <= 0.0001: return
        target = bc - cfg["fib"] * pull
    else:
        pull = bh - bc
        if pull <= 0.0001: return
        target = bc + cfg["fib"] * pull'''

new2 = '''    if direction == 1:
        pull = bc - bl
        if pull <= 0.0001:
            # Diagnostic logging — previously this guard silent-returned with
            # no log entry, making it impossible to tell why a threshold-
            # crossing signal didn\'t arm. Bug found 2026-05-27.
            log.info(f"[{pair}] SIGNAL z={current_z:+.3f} dir=LONG NOT ARMED: "
                     f"bar pull (C-L) = {pull*10000:.2f}p < 1.0p Fib guard | "
                     f"bar H={bh:.5f} L={bl:.5f} C={bc:.5f}")
            save_state(pair)
            return
        target = bc - cfg["fib"] * pull
    else:
        pull = bh - bc
        if pull <= 0.0001:
            log.info(f"[{pair}] SIGNAL z={current_z:+.3f} dir=SHORT NOT ARMED: "
                     f"bar pull (H-C) = {pull*10000:.2f}p < 1.0p Fib guard | "
                     f"bar H={bh:.5f} L={bl:.5f} C={bc:.5f}")
            save_state(pair)
            return
        target = bc + cfg["fib"] * pull'''

if old2 in content:
    content = content.replace(old2, new2)
    print("PATCH 2 applied: diagnostic logging on Fib pull guards")
elif new2 in content:
    print("PATCH 2 skipped: already applied")
else:
    print("ERROR: PATCH 2 — original text not found. Aborting.")
    # Roll back PATCH 1 if applied
    content = original_content
    print("Rolled back any earlier patches. File unchanged.")
    sys.exit(3)

# ── Verify Python syntax ───────────────────────────────────────────────────
import ast
try:
    ast.parse(content)
    print("Syntax check: OK")
except SyntaxError as e:
    print(f"ERROR: patched file has Python syntax error: {e}")
    print("Aborting — file not modified.")
    sys.exit(4)

# ── Write back ─────────────────────────────────────────────────────────────
target.write_text(content, encoding="utf-8")
print(f"\nSUCCESS: patched {target}")
print(f"Backup of original: {backup}")
print("\nNext steps:")
print("  1. Wait for OneDrive sync to VPS (~60s)")
print("  2. Verify on VPS: Get-Content <path> | Select-String 'NOT ARMED'")
print("     Should return a match.")
print("  3. Restart v3 monitor (Ctrl+C, up arrow, Enter)")
print("  4. Next threshold-cross signal will either arm cleanly OR log")
print("     the exact reason it didn\'t — no more silent misses.")
