#!/usr/bin/env python3
"""ai-portfolio-evening-watch v2 : market data fetcher.
Fetches 6669.TW, 00412A.TW, ^SOX, AMZN/MSFT/GOOGL via yfinance; FRED HY/IG OAS via CSV.
Prints a JSON blob. On any feed failure -> emits null (never fabricates).
"""
import json, sys, io, contextlib
from datetime import datetime, timezone, timedelta

import numpy as np
import pandas as pd
import yfinance as yf

TZ = timezone(timedelta(hours=8))
out = {"fetched_at_taipei": datetime.now(TZ).isoformat()}


def rsi14(close: pd.Series) -> float:
    d = close.diff()
    up = d.clip(lower=0)
    dn = -d.clip(upper=0)
    rs = up.rolling(14).mean() / dn.rolling(14).mean()
    rs = rs.replace([np.inf, -np.inf], np.nan).fillna(50)
    return float((100 - 100 / (1 + rs)).iloc[-1])


def ema(s, n):
    return s.ewm(span=n, adjust=False).mean()


# ---------- 6669 緯穎 ----------
try:
    t = yf.Ticker("6669.TW")
    h = t.history(period="6mo", auto_adjust=False)
    c = h["Close"].dropna()
    ma20 = float(c.rolling(20).mean().iloc[-1])
    ma60 = float(c.rolling(60).mean().iloc[-1])
    macd = ema(c, 12) - ema(c, 26)
    signal = ema(macd, 9)
    hist = macd - signal
    v = h["Volume"].dropna()
    v20 = float(v.rolling(20).mean().iloc[-1])
    # KDJ (9,3,3) on H/L/C
    low9 = h["Low"].rolling(9).min()
    high9 = h["High"].rolling(9).max()
    rsv = (c - low9) / (high9 - low9) * 100
    k = rsv.ewm(com=2, adjust=False).mean()
    d = k.ewm(com=2, adjust=False).mean()
    recent20 = c.tail(20)
    out["6669"] = {
        "ok": True,
        "last_bar_date": str(c.index[-1].date()),
        "close": float(c.iloc[-1]),
        "prev_close": float(c.iloc[-2]),
        "ma20": ma20,
        "ma60": ma60,
        "rsi14": rsi14(c),
        "macd": float(macd.iloc[-1]),
        "signal": float(signal.iloc[-1]),
        "hist": float(hist.iloc[-1]),
        "hist_prev": float(hist.iloc[-2]),
        "K": float(k.iloc[-1]),
        "D": float(d.iloc[-1]),
        "K_prev": float(k.iloc[-2]),
        "D_prev": float(d.iloc[-2]),
        "volume": float(v.iloc[-1]),
        "vol20avg": v20,
        "vol_ratio": float(v.iloc[-1]) / v20 if v20 else None,
        "low20": float(recent20.min()),
        "high20": float(recent20.max()),
        "high60": float(c.tail(60).max()),
        "bars": int(len(c)),
        "closes_tail": [float(x) for x in c.tail(6)],
    }
except Exception as e:
    out["6669"] = {"ok": False, "error": repr(e)[:300]}

# ---------- 00412A 主動安聯亞半導體 ----------
try:
    t2 = yf.Ticker("00412A.TW")
    h2 = t2.history(period="6mo", auto_adjust=False)
    if h2 is None or len(h2) == 0:
        raise ValueError("empty history")
    c2 = h2["Close"].dropna()
    out["00412A"] = {
        "ok": True,
        "last_bar_date": str(c2.index[-1].date()),
        "close": float(c2.iloc[-1]),
        "bars": int(len(c2)),
        "first_bar_date": str(c2.index[0].date()),
        "min_close": float(c2.min()),
        "max_close": float(c2.max()),
        "vol10avg": (float(h2["Volume"].dropna().rolling(10).mean().iloc[-1])
                     if "Volume" in h2 and h2["Volume"].dropna().shape[0] >= 10 else None),
        "closes_tail": [float(x) for x in c2.tail(6)],
    }
except Exception as e:
    out["00412A"] = {"ok": False, "error": repr(e)[:300]}

# ---------- ^SOX ----------
try:
    s = yf.Ticker("^SOX")
    hs = s.history(period="6mo", auto_adjust=False)
    cs = hs["Close"].dropna()
    out["SOX"] = {
        "ok": True,
        "last_bar_date": str(cs.index[-1].date()),
        "close": float(cs.iloc[-1]),
        "prev_close": float(cs.iloc[-2]),
        "high60": float(cs.tail(60).max()),
        "chg5d_pct": float((cs.iloc[-1] / cs.iloc[-6] - 1) * 100) if len(cs) >= 6 else None,
        "bars": int(len(cs)),
    }
except Exception as e:
    out["SOX"] = {"ok": False, "error": repr(e)[:300]}

# ---------- hyperscalers ----------
hyp = {}
for sym in ["AMZN", "MSFT", "GOOGL"]:
    try:
        hh = yf.Ticker(sym).history(period="1mo", auto_adjust=False)
        ch = hh["Close"].dropna()
        hyp[sym] = {
            "ok": True,
            "last_bar_date": str(ch.index[-1].date()),
            "close": float(ch.iloc[-1]),
            "prev_close": float(ch.iloc[-2]),
            "day_pct": float((ch.iloc[-1] / ch.iloc[-2] - 1) * 100),
            "chg3d_pct": float((ch.iloc[-1] / ch.iloc[-4] - 1) * 100) if len(ch) >= 4 else None,
        }
    except Exception as e:
        hyp[sym] = {"ok": False, "error": repr(e)[:200]}
out["hyperscalers"] = hyp

# ---------- FRED HY / IG OAS ----------
try:
    import urllib.request

    def fred(series):
        url = f"https://fred.stlouisfed.org/graph/fredgraph.csv?id={series}"
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=30) as r:
            txt = r.read().decode("utf-8")
        rows = [l.split(",") for l in txt.strip().splitlines()]
        hdr = rows[0]
        di, vi = hdr.index(series), 1
        vals = [(r[0], r[vi]) for r in rows[1:] if r[vi] not in (".", "")]
        return vals[-1], len(vals)

    (hd, hn) = fred("BAMLH0A0HYM2")
    (id_, in_) = fred("BAMLC0A0CM")
    hy = float(hd[1]); ig = float(id_[1])
    out["credit"] = {
        "ok": True,
        "hy_date": hd[0], "hy_oas": hy,
        "ig_date": id_[0], "ig_oas": ig,
        "spread_bps": round(hy - ig, 1),
        "hy_rows": hn, "ig_rows": in_,
    }
except Exception as e:
    out["credit"] = {"ok": False, "error": repr(e)[:300]}

print("###JSON###")
print(json.dumps(out, ensure_ascii=False, indent=1))