"""
Chart-3 granularity study: ticks vs 1-second candles, and what the box size does.

Answers two questions with the same data instead of an opinion:
  1. Does the 0.001% chart print more noise on raw ticks or on 1s closes?
  2. Does shrinking the box reduce the loss per cycle, or just multiply cycles?

  python noise.py                     # 6h window, 2026-11-07 12:00 UTC
  python noise.py --hours 12 --from 2026-11-06T00:00

Output: a table on stdout + chart3_compare.html (visual, same window).
"""
import argparse
import json
import os
import statistics
import urllib.request
from datetime import datetime, timezone
from decimal import Decimal as D

import backtest as bt
import zec_renko_bot as z

BOXES = [D("0.001"), D("0.0005"), D("0.00025"), D("0.0001")]
WHIPSAW_S = 60


def ticks_window(files, start_ms, end_ms):
    out = []
    days = {datetime.fromtimestamp(ms / 1000, timezone.utc).strftime("%Y-%m-%d")
            for ms in range(start_ms, end_ms, 3_600_000)}
    for path in files:
        if ".zip" in path and not any(d in path for d in days):
            continue                       # skip files outside the window
        for price, ts in bt.iter_ticks(path):
            if start_ms <= ts <= end_ms:
                out.append((price, ts))
    return out


def ones_window(start_ms, end_ms):
    """1-second closes from Binance USD-M futures."""
    rows, cur = [], start_ms
    while cur < end_ms:
        # USD-M futures has no 1-second interval; spot does, same underlying asset
        url = ("https://api.binance.com/api/v3/klines?symbol=ZECUSDT&interval=1s"
               f"&startTime={cur}&limit=1000")
        batch = json.load(urllib.request.urlopen(url, timeout=30))
        if not batch:
            break
        rows += [(D(k[4]), int(k[0])) for k in batch]
        nxt = int(batch[-1][0]) + 1000
        if nxt <= cur:
            break
        cur = nxt
        if nxt >= end_ms:
            break
    return [(p, t) for p, t in rows if t <= end_ms]


def renko_path(feed, pct):
    """Feed (price, ts) pairs through the bot's own Renko; return bricks with timestamps."""
    r = z.Renko(pct)
    bricks, prev_dir = [], 0
    for price, ts in feed:
        for _o, _c, d in r.feed(price):
            bricks.append((ts, d))
            prev_dir = d
    return bricks


def metrics(bricks):
    if not bricks:
        return dict(n=0, per_h=0, flips=0, whipsaw=0, life=0)
    flips = sum(1 for i in range(1, len(bricks)) if bricks[i][1] != bricks[i - 1][1])
    whip = sum(1 for i in range(1, len(bricks))
               if bricks[i][1] != bricks[i - 1][1]
               and (bricks[i][0] - bricks[i - 1][0]) <= WHIPSAW_S * 1000)
    span_h = (bricks[-1][0] - bricks[0][0]) / 3_600_000 or 1
    lives = [(bricks[i][0] - bricks[i - 1][0]) / 1000 for i in range(1, len(bricks))]
    return dict(n=len(bricks), per_h=len(bricks) / span_h, flips=flips, whipsaw=whip,
                life=statistics.median(lives) if lives else 0)


def svg(bricks, title, minutes=30, w=520, h=260):
    if not bricks:
        return ""
    cut = bricks[0][0] + minutes * 60_000
    shown = [b for b in bricks if b[0] <= cut][:160]
    if not shown:
        return ""
    bw = max(2.0, (w - 40) / max(len(shown), 1))
    parts = []
    for i, (ts, d) in enumerate(shown):
        x = 20 + i * bw
        parts.append(f'<rect x="{x:.1f}" y="20" width="{max(bw - 1, 1):.1f}" height="{h - 60}" '
                     f'fill="{"#2ec27e" if d == 1 else "#e05252"}" opacity="0.85"/>')
    return (f'<div class="panel"><h3>{title} — {len(shown)} bricks in {minutes} min</h3>'
            f'<svg viewBox="0 0 {w} {h}" width="100%">'
            f'<line x1="20" y1="{h - 40}" x2="{w - 20}" y2="{h - 40}" stroke="#888"/>'
            + "".join(parts) +
            f'<text x="20" y="{h - 18}" fill="#888" font-size="12">each bar = one 0.001% brick, left to right in time</text>'
            f'</svg></div>')


def main():
    ap = argparse.ArgumentParser()
    ap.add_argument("--from", dest="start", default="2026-11-07T12:00")
    ap.add_argument("--hours", type=float, default=6)
    ap.add_argument("--data", default="data")
    a = ap.parse_args()

    start = datetime.fromisoformat(a.start).replace(tzinfo=timezone.utc)
    s_ms = int(start.timestamp() * 1000)
    e_ms = s_ms + int(a.hours * 3_600_000)

    files = sorted(f for f in os.listdir(a.data) if f.endswith(".zip"))
    files = [os.path.join(a.data, f) for f in files]
    tick_feed = ticks_window(files, s_ms, e_ms)
    print(f"window {start} UTC +{a.hours:g}h | ticks {len(tick_feed):,}")
    one_feed = ones_window(s_ms, e_ms)
    print(f"1-second closes: {len(one_feed):,}")

    print(f"\n{'box':>9} {'feed':>6} {'bricks':>7} {'per hour':>9} {'dir flips':>10} "
          f"{'reversed <=60s':>15} {'median life s':>14}")
    rows = []
    for box in BOXES:
        for name, feed in (("tick", tick_feed), ("1s", one_feed)):
            m = metrics(renko_path(feed, box))
            rows.append((box, name, m))
            print(f"{float(box) * 100:>8.3f}% {name:>6} {m['n']:>7} {m['per_h']:>9.0f} "
                  f"{m['flips']:>10} {m['whipsaw']:>15} {m['life']:>14.1f}")

    base = D("0.001")
    for name, feed in (("tick", tick_feed), ("1s", one_feed)):
        m0 = next(r[2] for r in rows if r[0] == base and r[1] == name)
        m1 = next(r[2] for r in rows if r[0] == D("0.00025") and r[1] == name)
        if m0["n"] and m1["n"]:
            print(f"\n{name}: 0.001% -> 0.00025% multiplies bricks by "
                  f"{m1['n'] / m0['n']:.1f}x and whipsaw by "
                  f"{(m1['whipsaw'] / m1['n']) / (m0['whipsaw'] / m0['n']):.2f}x (as a share).")

    html = ("<!doctype html><meta charset='utf-8'><title>chart 3 · tick vs 1s</title>"
            "<style>body{background:#14161a;color:#e8e8e8;font:15px system-ui;padding:24px}"
            "h1{font-size:20px}.panel{margin:18px 0}h3{font-size:14px;color:#9fb0c0;margin:0 0 6px}"
            "table{border-collapse:collapse;margin-top:14px}td,th{padding:5px 12px;border-bottom:1px solid #2a2f36;text-align:right}"
            "th{color:#9fb0c0;font-weight:500}</style>"
            f"<h1>Chart 3 · raw ticks vs 1-second closes · {start:%Y-%m-%d %H:%M} UTC, {a.hours:g}h</h1>")
    html += svg(renko_path(tick_feed, base), "raw ticks, 0.001% box")
    html += svg(renko_path(one_feed, base), "1-second closes, 0.001% box")
    html += "<table><tr><th>box</th><th>feed</th><th>bricks</th><th>/hour</th><th>flips</th><th>rev ≤60s</th><th>life s</th></tr>"
    for box, name, m in rows:
        html += (f"<tr><td>{float(box) * 100:.3f}%</td><td>{name}</td><td>{m['n']}</td>"
                 f"<td>{m['per_h']:.0f}</td><td>{m['flips']}</td><td>{m['whipsaw']}</td>"
                 f"<td>{m['life']:.1f}</td></tr>")
    html += "</table>"
    with open("chart3_compare.html", "w", encoding="utf-8") as f:
        f.write(html)
    print("\nwrote chart3_compare.html")


if __name__ == "__main__":
    main()
