#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Bybit 주식/ETF 퍼프 신규 상장 감지 → 텔레그램
import json, os, re, urllib.request, urllib.parse, sys, datetime

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

# 뜨면 바로 알려야 할 것들 (ETF 대체재 우선)
PRIORITY = ("SOXX","SMH","XSD","PSI","FTXL","XLK","QQQ","IGV","VGT","IYW",
            "SKYY","AIQ","BOTZ","ARKK","EWY","EWT","EWJ")

def send(msg):
    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)

# ── 신규 상장분 정체 파악용 (티커만으론 못 알아봄: APPSTOCK=AppLovin, MUU=MU 2x 등)
ALIAS = {"AMDSTOCK": "AMD", "CATSTOCK": "CAT", "APPSTOCK": "APP",
         "SKHYNIX": "000660.KS", "SAMSUNG": "005930.KS", "HYUNDAI": "005380.KS"}
LEVPAT = re.compile(r"(bull|bear|2x|3x|daily|tradr|ultra|direxion|proshares|granite|leverag)", re.I)
TECH_SEC = {"Technology", "Communication Services"}
KW = [("Health|Pharma|Bio|Medic|Drug", "Healthcare"),
      ("Bank|Financ|Capital|Insur|Broker", "Financial Services"),
      ("Energy|Oil|Gas|Petro", "Energy"), ("Gold|Silver|Mining|Materials", "Basic Materials"),
      ("Real Estate|REIT", "Real Estate"), ("Utilit", "Utilities"),
      ("Consumer|Retail|Staples", "Consumer Defensive"),
      ("Industrial|Aerospace|Defense", "Industrials")]
SECT_ETF = {"Healthcare": "XLV", "Financial Services": "XLF", "Energy": "XLE",
            "Industrials": "XLI", "Consumer Defensive": "XLP", "Consumer Cyclical": "XLY",
            "Basic Materials": "XLB", "Real Estate": "XLRE", "Utilities": "XLU"}

def _perf(t):
    try:
        u = ("https://query1.finance.yahoo.com/v8/finance/chart/"
             + urllib.parse.quote(t) + "?interval=1d&range=1y")
        r = urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"})
        d = json.load(urllib.request.urlopen(r, timeout=20))["chart"]["result"][0]
        q = d["indicators"]["quote"][0]["close"]
        ts = [datetime.datetime.fromtimestamp(a, datetime.timezone.utc).date() for a in d["timestamp"]]
        c = [x for x in q if x]
        last = c[-1]
        ytd = [v for dt, v in zip(ts, q) if v and dt >= datetime.date(datetime.date.today().year, 1, 1)]
        f = lambda k: (last / c[-k] - 1) * 100 if len(c) > k else None
        nm = d["meta"].get("longName") or d["meta"].get("shortName") or t
        return nm, last, ((last / ytd[0] - 1) * 100 if ytd else None), f(63), f(21)
    except Exception:
        return None, None, None, None, None

def _sector(t, nm):
    try:
        import yfinance as yf
        sec = yf.Ticker(t).info.get("sector")
        if sec: return sec
    except Exception:
        pass
    if nm:
        for pat, sec in KW:
            if re.search(pat, nm, re.I): return sec
    return None

def profile(sym):
    base = sym.replace("USDT", "")
    tk = ALIAS.get(base, base)
    nm, px, ytd, m3, m1 = _perf(tk)
    if not nm: return None
    return dict(sym=sym, tk=tk, name=nm, px=px, ytd=ytd, m3=m3, m1=m1,
                lev=bool(LEVPAT.search(nm)), sec=_sector(tk, nm))

def nontech_alert(new_syms):
    rows = [profile(s) for s in new_syms]
    rows = [r for r in rows if r]
    hit = [r for r in rows
           if not r["lev"] and r["sec"] and r["sec"] not in TECH_SEC
           and ((r["m3"] or -99) > 0 or (r["m1"] or -99) > 0)]
    if not hit: return None
    fmt = lambda v: f"{v:+.1f}%" if v is not None else "n/a"
    out = ["[신규상장 비테크 주목 " + str(datetime.date.today()) + "]"]
    for r in hit:
        out.append("")
        out.append(f"{r['sym'].replace('USDT','')} — {r['name']}")
        out.append(f"{r['sec']} / 현재 {r['px']:,.2f}")
        out.append(f"YTD {fmt(r['ytd'])} · 3M {fmt(r['m3'])} · 1M {fmt(r['m1'])}")
        etf = SECT_ETF.get(r["sec"])
        if etf:
            _, _, ey, e3, e1 = _perf(etf)
            out.append(f"섹터({etf}) YTD {fmt(ey)} · 3M {fmt(e3)} · 1M {fmt(e1)}")
    skip = [r["sym"].replace("USDT", "") for r in rows if r not in hit]
    if skip:
        out.append("")
        out.append("제외(테크/레버리지/추세미달): " + ", ".join(skip))
    return "\n".join(out)

u = "https://api.bybit.com/v5/market/instruments-info?category=linear&limit=1000"
req = urllib.request.Request(u, headers={"User-Agent": "Mozilla/5.0"})
d = json.load(urllib.request.urlopen(req, timeout=20))
cur = sorted({x["symbol"] for x in d["result"]["list"] if x.get("symbolType") == "stock"})

try:
    old = json.load(open(SNAP, encoding="utf-8"))
    prev = sorted({x["symbol"] if isinstance(x, dict) else x
                   for x in (old.get("list") if isinstance(old, dict) else old)})
except Exception:
    prev = []

new = [s for s in cur if s not in prev]
gone = [s for s in prev if s not in cur]

if not DRY:
    json.dump({"date": str(datetime.date.today()), "count": len(cur), "list": cur},
              open(SNAP, "w", encoding="utf-8"), ensure_ascii=False, indent=1)

if not prev:
    print(f"기준 스냅샷 생성: {len(cur)}개"); sys.exit()

lines = []
if new:
    hot = [s for s in new if s.replace("USDT","").replace("STOCK","") in PRIORITY]
    if hot: lines.append("★ 관심 ETF 상장: " + ", ".join(hot))
    lines.append("신규 " + str(len(new)) + "건: " + ", ".join(new))
if gone:
    lines.append("상장폐지 " + str(len(gone)) + "건: " + ", ".join(gone))

if lines:
    msg = f"[Bybit 종목 변동 {datetime.date.today()}] 총 {len(cur)}개\n" + "\n".join(lines)
    print(msg)
    if not DRY: send(msg)
    if new:
        try:
            m2 = nontech_alert(new)
        except Exception as e:
            m2 = None; print("[!] 프로파일 실패:", e)
        if m2:
            print("\n" + m2)
            if not DRY: send(m2)
else:
    print(f"변동 없음 (총 {len(cur)}개)")
