#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import json, os, re, sys, time, datetime as dt
import urllib.request, urllib.parse
from concurrent.futures import ThreadPoolExecutor

BASE = os.path.expanduser("~/Claude/Projects/에이전트구축")
SNAP = os.path.join(BASE, "bybit_stocks.json")
HIST = os.path.join(BASE, "bybit_turnover_hist.json")
TURN = os.path.join(BASE, "bybit_turnover.json")
NQLEV = os.path.join(BASE, "nq_levels.py")
DRY = "--dry" in sys.argv

MIN_TURNOVER = 1_000_000
SURGE_RATIO = 2.0
UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15"

ALIAS = {"AMDSTOCK":"AMD","CATSTOCK":"CAT","APPSTOCK":"APP","GMESTOCK":"GME",
         "PENGSTOCK":"PENN","WENSTOCK":"WEN","SKHYNIX":"000660.KS",
         "SAMSUNG":"005930.KS","HYUNDAI":"005380.KS","TENCENT":"0700.HK",
         "POPMART":"9992.HK","CXMT":"688825.SS","GIGADEVICE":"603986.SS",
         "MUU":"MU","MVLL":"MRVL"}
SKIP = {"MINIMAX","ZHIPU","PURR","BOT","BNC","BSP","BBX","FWDI","INTW","SHAZ",
        "QNTX","SNXX","STXX","KSTR","USAR","SSPC","SOXS","SQQQ","TZA","TBT",
        "UVXY","BITO"}

def send(msg):
    if DRY:
        print(msg); return
    s = open(NQLEV, encoding="utf-8").read()
    tok = re.search(r'BOT_TOKEN\s*=\s*["\']([^"\']+)', s).group(1)
    cid = re.search(r'CHAT_ID\s*=\s*["\']?([^"\'\s]+)', s).group(1)
    d = urllib.parse.urlencode({"chat_id": cid, "text": msg}).encode()
    urllib.request.urlopen(f"https://api.telegram.org/bot{tok}/sendMessage", d, timeout=15)

def get_turnover():
    u = "https://api.bybit.com/v5/market/tickers?category=linear"
    d = json.load(urllib.request.urlopen(u, timeout=30))["result"]["list"]
    syms = set(json.load(open(SNAP, encoding="utf-8"))["list"])
    return {x["symbol"]: float(x["turnover24h"]) for x in d if x["symbol"] in syms}

def yahoo(tk, rng="6mo"):
    for host in ("query2", "query1"):
        try:
            u = f"https://{host}.finance.yahoo.com/v8/finance/chart/{urllib.parse.quote(tk)}?range={rng}&interval=1d"
            r = urllib.request.Request(u, headers={"User-Agent": UA})
            res = json.load(urllib.request.urlopen(r, timeout=25))["chart"]["result"][0]
            q = res["indicators"]["quote"][0]; ts = res["timestamp"]
            s = {dt.datetime.utcfromtimestamp(ts[i]).date(): q["close"][i]
                 for i in range(len(ts)) if q["close"][i]}
            return s if len(s) > 65 else None
        except Exception:
            time.sleep(0.5)
    return None

def main():
    today = str(dt.date.today())
    cur = get_turnover()
    json.dump(cur, open(TURN, "w"), indent=1)
    hist = {}
    if os.path.exists(HIST):
        try: hist = json.load(open(HIST, encoding="utf-8"))
        except Exception: hist = {}
    hist[today] = cur
    for d in sorted(hist)[:-30]: hist.pop(d, None)
    json.dump(hist, open(HIST, "w"), indent=1)
    days = sorted(hist)
    prev_days = [d for d in days if d != today][-5:]

    surge = []
    for sym, tv in cur.items():
        if tv < MIN_TURNOVER: continue
        past = [hist[d].get(sym) for d in prev_days if hist[d].get(sym)]
        if len(past) < 3: continue
        avg = sum(past)/len(past)
        if avg > 0 and tv/avg >= SURGE_RATIO: surge.append((sym, tv, tv/avg))
    surge.sort(key=lambda x: -x[2])

    seen_before = set()
    for d in days[:-1]: seen_before |= set(hist[d].keys())
    fresh = [(s, cur[s]) for s in cur
             if s not in seen_before and cur[s] >= MIN_TURNOVER] if seen_before else []
    fresh.sort(key=lambda x: -x[1])

    pairs = []
    for sym, t in cur.items():
        if t < MIN_TURNOVER: continue
        base = sym[:-4] if sym.endswith("USDT") else sym
        if base in SKIP: continue
        pairs.append((sym, ALIAS.get(base, base)))

    bench = yahoo("SPY")
    if not bench:
        send("[자금흐름] SPY 조회 실패"); return
    with ThreadPoolExecutor(max_workers=8) as ex:
        got = list(ex.map(lambda p: (p[0], p[1], yahoo(p[1])), pairs))

    rows = []
    for sym, tk, s in got:
        if not s: continue
        c = sorted(set(s) & set(bench))
        if len(c) < 65: continue
        def rs(w):
            a, b = c[-1-w], c[-1]
            return ((s[b]/s[a]-1)-(bench[b]/bench[a]-1))*100
        rows.append((tk, cur[sym], rs(5), rs(20), rs(60)))
    rows.sort(key=lambda r: -r[3])
    inflect = [r for r in rows if r[3] > 0 > r[4]]
    strong  = [r for r in rows if r[3] > 0 and r[4] > 0]

    L = [f"[바이빗 자금흐름] {today}",
         f"거래대금 {MIN_TURNOVER:,.0f} 이상 {len(pairs)}개 / 전체 {len(cur)}개", ""]
    if fresh:
        L.append("■ 신규 상장 + 거래대금 통과")
        for s, t in fresh[:8]: L.append(f"  {s} {t:,.0f}")
        L.append("")
    if surge:
        L.append(f"■ 거래대금 급증 (5일평균 {SURGE_RATIO}배+)")
        for s, t, r in surge[:10]: L.append(f"  {s} {t:,.0f} ({r:.1f}배)")
        L.append("")
    elif len(prev_days) < 3:
        L.append("■ 거래대금 급증: 히스토리 부족"); L.append("")
    if inflect:
        L.append("■ 변곡 후보 (20일+ / 60일-)")
        for tk, t, r5, r20, r60 in inflect[:10]:
            L.append(f"  {tk} 20d{r20:+.1f} 60d{r60:+.1f} 5d{r5:+.1f} | {t:,.0f}")
        L.append("")
    if strong:
        L.append("■ 강세 지속 (20·60일 모두 +)")
        for tk, t, r5, r20, r60 in strong[:8]:
            L.append(f"  {tk} 20d{r20:+.1f} 60d{r60:+.1f} | {t:,.0f}")
        L.append("")
    L.append("■ 20일 RS 하위 5")
    for tk, t, r5, r20, r60 in rows[-5:]:
        L.append(f"  {tk} 20d{r20:+.1f} 60d{r60:+.1f} | {t:,.0f}")
    send("\n".join(L))

main()
