#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# UVXY 급등 트리거 감시. 가격은 TradingView 스캐너로 BYBIT:UVXYUSDT.P 직접 조회.
# 바이비트 API는 태국에서 지오블록이라 안 씀. VPN 불필요.
import json, os, re, urllib.request, urllib.parse, datetime as dt

BASE      = os.path.expanduser("~/Claude/Projects/에이전트구축")
STATE_F   = os.path.join(BASE, "uvxy_watch_state.json")
LOG_F     = os.path.join(BASE, "uvxy_watch.log")
NQ_LEVELS = os.path.join(BASE, "nq_levels.py")

TICKER   = "BYBIT:UVXYUSDT.P"
CHAT_ID  = None
T_SINGLE = 2.0
T_DOUBLE = 1.0
TARGETS  = (2.0, 3.0)
TIME_STOP_MIN = 90
WINDOW_UTC = (13, 30, 16, 0)
IGNORE_WINDOW = os.environ.get("UVXY_ALL_DAY") == "1"
COOLDOWN_MIN = 60
MIN_GAP, MAX_GAP = 180, 900   # 직전 관측과의 간격 허용 범위(초)

def log(msg):
    ts = dt.datetime.now(dt.timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
    line = "[%s] %s" % (ts, msg)
    print(line)
    try:
        with open(LOG_F, "a", encoding="utf-8") as f:
            f.write(line + "\n")
    except Exception:
        pass

def get_secrets():
    src = open(NQ_LEVELS, encoding="utf-8").read()
    tok = re.search(r'BOT_TOKEN\s*=\s*["\']([^"\']+)["\']', src)
    cid = re.search(r'CHAT_ID\s*=\s*["\']?(-?\d+)["\']?', src)
    if not tok:
        raise RuntimeError("nq_levels.py에서 BOT_TOKEN 못 찾음")
    return tok.group(1), (CHAT_ID or (cid.group(1) if cid else None))

def telegram(text):
    token, chat = get_secrets()
    if not chat:
        raise RuntimeError("CHAT_ID 없음")
    url = "https://api.telegram.org/bot%s/sendMessage" % token
    data = urllib.parse.urlencode({"chat_id": chat, "text": text,
                                   "disable_web_page_preview": "true"}).encode()
    with urllib.request.urlopen(urllib.request.Request(url, data=data), timeout=20) as r:
        return json.loads(r.read().decode())

def price():
    b = {"symbols": {"tickers": [TICKER], "query": {"types": []}},
         "columns": ["close", "volume"]}
    req = urllib.request.Request("https://scanner.tradingview.com/crypto/scan",
                                 data=json.dumps(b).encode(),
                                 headers={"Content-Type": "application/json",
                                          "User-Agent": "Mozilla/5.0"})
    with urllib.request.urlopen(req, timeout=20) as r:
        d = json.loads(r.read().decode())
    if not d.get("data"):
        raise RuntimeError("스캐너 응답 비어있음")
    return float(d["data"][0]["d"][0])

def load_state():
    try:
        return json.load(open(STATE_F, encoding="utf-8"))
    except Exception:
        return {}

def save_state(s):
    json.dump(s, open(STATE_F, "w", encoding="utf-8"), ensure_ascii=False, indent=1)

def in_window(now):
    if IGNORE_WINDOW:
        return True
    h1, m1, h2, m2 = WINDOW_UTC
    cur = now.hour * 60 + now.minute
    return h1 * 60 + m1 <= cur <= h2 * 60 + m2

def main():
    now = dt.datetime.now(dt.timezone.utc)
    if not in_window(now):
        return
    st = load_state()
    try:
        p = price()
    except Exception as e:
        log("가격조회 실패: %s" % e)
        if st.get("last_err_day") != now.strftime("%Y-%m-%d"):
            st["last_err_day"] = now.strftime("%Y-%m-%d")
            save_state(st)
            try: telegram("UVXY 감시 오류\n%s" % e)
            except Exception: pass
        return

    ts = now.timestamp()
    hist = st.get("hist", [])
    hist.append([ts, p])
    hist = hist[-6:]
    st["hist"] = hist
    save_state(st)

    if len(hist) < 3:
        log("관측 누적 중 %d/3 | 현재가 %.3f" % (len(hist), p))
        return

    # 간격 검증: 잠자기 등으로 건너뛴 구간은 무효
    def ret(i):
        gap = hist[i][0] - hist[i-1][0]
        if not (MIN_GAP <= gap <= MAX_GAP):
            return None
        return (hist[i][1] / hist[i-1][1] - 1) * 100

    r_last, r_prev = ret(-1), ret(-2)
    if r_last is None:
        log("간격 이상, 이번 회차 판정 생략 | 현재가 %.3f" % p)
        return

    kind = None
    if r_last >= T_SINGLE:
        kind = "단발 %+.2f%%" % r_last
    elif r_prev is not None and r_last >= T_DOUBLE and r_prev >= T_DOUBLE:
        kind = "연속 %+.2f%% → %+.2f%%" % (r_prev, r_last)

    log("직전 %+.2f%% / 그전 %s / 현재가 %.3f%s"
        % (r_last, ("%+.2f%%" % r_prev) if r_prev is not None else "n/a", p,
           (" / TRIGGER " + kind) if kind else ""))

    if not kind:
        return
    if ts - st.get("last_fire_ts", 0) < COOLDOWN_MIN * 60:
        log("쿨다운 중, 알림 생략")
        return

    base = hist[-2][1]
    t1, t2 = base * 1.02, base * 1.03
    bkk = (now + dt.timedelta(minutes=TIME_STOP_MIN, hours=7)).strftime("%H:%M")
    msg = ("UVXY 급등 트리거\n%s\n\n현재가 %.3f\n기준가 %.3f\n\n"
           "목표 +2%%  %.3f   (도달 84%%)\n목표 +3%%  %.3f   (도달 56%%)\n\n"
           "도달 중앙 40분\n시간손절 %s (방콕)\n— 미체결 시 숏 재구축 검토\n\n"
           "헤드라인 확인할 것.\n없으면 +2%%에서 종료, 있으면 연장."
           % (kind, p, base, t1, t2, bkk))
    try:
        telegram(msg)
        st["last_fire_ts"] = ts
        save_state(st)
        log("알림 발송")
    except Exception as e:
        log("텔레그램 실패: %s" % e)

if __name__ == "__main__":
    main()
