#!/usr/bin/env python3
"""
GARCH 波动率仓位调整模块 v2
数据源：Yahoo Finance GC=F（黄金期货，5分钟周期）
模型：GARCH(1,1) + t 分布

波动率区间 → 仓位调整乘数（平方根缩放）：
  vol_ratio > 2.0x  →  HIGH      →  multiplier = 0.50
  vol_ratio 1.5~2.0  →  ELEVATED →  multiplier = 0.75
  vol_ratio 0.5~1.5  →  NORMAL   →  multiplier = 1.00
  vol_ratio < 0.5x  →  LOW       →  multiplier = 1.25

用法：
  from garch_volatility import get_vol_multiplier
  result = get_vol_multiplier()
  print(result["multiplier"], result["regime"])
"""

import sys, os, json, time, logging, warnings, pathlib
from datetime import datetime, timedelta
import numpy as np
import pandas as pd
from arch import arch_model

# 复用 strategy.py 的 EA bridge 获取函数（避免路径重复）
sys.path.insert(0, str(pathlib.Path(__file__).parent))
from strategy import _read_bars_from_ea

warnings.filterwarnings("ignore")

# Cached reference to avoid repeated import in hot path
_detect_active_symbol = None

# ============ 路径 ============
# ROOT now resolved via frozen_path (PyInstaller-safe)
from frozen_path import APP_ROOT as ROOT
# LOG_DIR now resolved via frozen_path
from frozen_path import LOGS_DIR as LOG_DIR
CACHE_FILE = LOG_DIR / "garch_volatility_cache.json"
SHARED_BARS_FILE = LOG_DIR / "shared_gc_bars.json"   # strategy 和 GARCH 共用的 K线缓存

# ============ 日志 ============
_logger = logging.getLogger("garch_volatility")
_h = logging.StreamHandler()
_h.setFormatter(logging.Formatter("%(asctime)s [GARCH] %(levelname)s %(message)s"))
_logger.addHandler(_h)
_logger.setLevel(logging.INFO)


# ============ 数据获取（MT5 EA Bridge）============

def _fetch_gc_f_5m(period="3d") -> pd.DataFrame:
    """
    通过 MT5 EA Bridge 获取 XAUUSD M5 历史K线
    返回 DataFrame，含 close 列（Unix 时间戳）
    """
    try:
        # 通过 EA Bridge 获取 bars
        bars = _read_bars_from_ea(count=200, timeout=30)
        if not bars or len(bars) < 50:
            _logger.warning(f"EA Bridge returned {len(bars) if bars else 0} bars, need 50+")
            return None

        # 转换为 DataFrame
        df = pd.DataFrame(bars)
        df["time"] = pd.to_datetime(df["time"], unit="s", utc=True)
        df = df.set_index("time")
        df = df.sort_index()
        df = df[["close"]].dropna()
        global _detect_active_symbol
        try:
            if _detect_active_symbol is None:
                from bridge.gold_trader import _detect_active_symbol as _das
                _detect_active_symbol = _das
            sym = _detect_active_symbol()
        except Exception:
            sym = "XAUUSD"
        _logger.info(f"Fetched {len(df)} {sym} M5 bars from EA, last close: {df.close.iloc[-1]:.2f}")
        return df
    except Exception as e:
        _logger.error(f"EA Bridge fetch failed: {e}")
        return None


def _compute_log_returns(prices: pd.Series) -> np.ndarray:
    """计算对数收益率（百分比），用于 GARCH 建模"""
    # 过滤掉零和负值
    valid = (prices > 0).values
    p = prices[valid].values
    if len(p) < 50:
        return np.array([])
    log_ret = np.diff(np.log(p))          # log return
    # 转为百分比（GARCH 数值稳定性）
    return log_ret * 100


# ============ GARCH 拟合 ============

def _fit_garch(ret: np.ndarray) -> tuple:
    """
    拟合 GARCH(1,1) + t 分布
    ret: 百分比收益率数组（已 demean）
    返回: (current_vol_pct, long_run_vol_pct, omega, alpha, beta, nu)
          current_vol = 年化后单位（百分比/年）
          long_run_vol = 年化长期波动率（百分比/年）
          nu = t 分布自由度（GARCH fit 失败时为 None）
    """
    if len(ret) < 120:
        return None, None, None, None, None, None

    try:
        model = arch_model(
            ret,
            vol="Garch", p=1, q=1,
            dist="t",
            rescale=False
        )
        res = model.fit(disp="off", options={"maxiter": 300})

        omega  = float(res.params.get("omega", 0))
        alpha  = float(res.params.get("alpha[1]", 0))
        beta   = float(res.params.get("beta[1]", 0))
        nu     = float(res.params.get("nu", 10))   # t 分布自由度

        # 当前条件波动率（最后一个值，即 t+1 预测）
        cond_vol_pct = float(res.conditional_volatility[-1])   # 单位：%*sqrt(5min_interval)

        # 年化因子（5分钟 → 年化）
        annual_factor = np.sqrt(252 * 288)

        # 当前条件波动率（年化）
        current_vol_annual = cond_vol_pct * annual_factor

        # 长期波动率：
        # 当 alpha+beta >= 1 时，GARCH 模型不稳定，用全样本已实现波动率替代
        if alpha + beta >= 1.0 - 1e-6:
            long_run_vol_pct = np.std(ret)  # 全样本标准差（已缩放）
            _logger.warning(f"alpha+beta={alpha+beta:.6f} >= 1 (边界情况)，使用全样本已实现波动率")
        else:
            long_run_var_pct2 = omega / (1 - alpha - beta)
            long_run_vol_pct  = np.sqrt(long_run_var_pct2)
        long_run_vol_annual = long_run_vol_pct * annual_factor

        _logger.info(
            f"GARCH(1,1) fit: omega={omega:.4f}, alpha={alpha:.4f}, beta={beta:.4f}, "
            f"nu={nu:.1f}, cond_vol={cond_vol_pct:.4f}%/5min, "
            f"annual_vol={current_vol_annual:.2f}%, long_run={long_run_vol_annual:.2f}%"
        )
        return current_vol_annual, long_run_vol_annual, omega, alpha, beta, nu

    except Exception as e:
        _logger.error(f"GARCH fit failed: {e}")
        return None, None, None, None, None, None


# ============ 核心函数 ============

def get_vol_multiplier(refresh=False) -> dict:
    """
    获取当前波动率区间和仓位调整乘数

    返回字典：
      status        : "ok" | "stale" | "error"
      regime       : "HIGH" | "ELEVATED" | "NORMAL" | "LOW"
      multiplier   : float，Governor 用此值乘以基础 max_position_size
      current_vol : 当前年化波动率（%）
      long_run_vol: 长期年化波动率（%）
      vol_ratio   : current_vol / long_run_vol
      annualized_vol_pct: 同 current_vol（别名）
      n_bars      : 使用的 K 线数量
      nu          : t 分布自由度（尾部肥瘦指标）
      updated_at  : ISO 时间戳
      age_seconds : 缓存年龄（秒）
    """
    # 检查缓存（5分钟有效期）
    if not refresh and CACHE_FILE.exists():
        try:
            mtime = CACHE_FILE.stat().st_mtime
            age   = time.time() - mtime
            if age < 300:
                cached = json.loads(CACHE_FILE.read_text(encoding="utf-8"))
                cached["age_seconds"]  = int(age)
                cached["from_cache"]   = True
                return cached
        except Exception:
            pass

    # 获取数据
    df = _fetch_gc_f_5m("3d")
    if df is None or len(df) < 120:
        # 尝试回退到缓存
        if CACHE_FILE.exists():
            try:
                cached = json.loads(CACHE_FILE.read_text(encoding="utf-8"))
                cached["status"]      = "stale"
                cached["age_seconds"] = int(time.time() - CACHE_FILE.stat().st_mtime)
                return cached
            except Exception:
                pass
        return _error_result("insufficient_data")

    prices = df.close
    ret    = _compute_log_returns(prices)
    if ret.size < 120:
        return _error_result("insufficient_returns")

    # 去均值（GARCH 要求）
    ret = ret - np.mean(ret)

    # 拟合
    current_vol, long_run_vol, omega, alpha, beta, nu = _fit_garch(ret)

    if current_vol is None:
        return _error_result("garch_fit_failed")

    # 波动率比率
    vol_ratio = current_vol / long_run_vol if long_run_vol > 1e-8 else 1.0

    # 区间判定
    if vol_ratio > 2.0:
        regime     = "HIGH"
        multiplier = 0.50
    elif vol_ratio > 1.5:
        regime     = "ELEVATED"
        multiplier = 0.75
    elif vol_ratio < 0.5:
        regime     = "LOW"
        multiplier = 1.25
    else:
        regime     = "NORMAL"
        multiplier = 1.00

    # t 分布自由度（nu）—— 从拟合结果中获取（尾部肥瘦指标）
    nu = float(nu)

    result = {
        "status":              "ok",
        "regime":             regime,
        "multiplier":         multiplier,
        "vol_ratio":          round(vol_ratio, 3),
        "current_vol":        round(current_vol, 3),       # 年化波动率（%）
        "long_run_vol":       round(long_run_vol, 3),     # 长期年化波动率（%）
        "annualized_vol_pct": round(current_vol, 3),       # 别名
        "n_bars":            len(ret),
        "omega":             round(omega, 6) if omega else 0,
        "alpha":             round(alpha, 4) if alpha else 0,
        "beta":              round(beta, 4) if beta else 0,
        "nu":                round(nu, 1),
        "updated_at":        datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00"),
        "age_seconds":        0,
        "from_cache":        False,
    }

    # 写缓存
    try:
        with open(CACHE_FILE, "w", encoding="utf-8") as f:
            json.dump(result, f, ensure_ascii=False, indent=2)
    except Exception as e:
        _logger.warning(f"Cache write failed: {e}")

    return result


def _error_result(reason: str) -> dict:
    return {
        "status":       "error",
        "regime":       "UNKNOWN",
        "multiplier":   1.0,
        "vol_ratio":    1.0,
        "current_vol":  0.0,
        "long_run_vol": 0.0,
        "annualized_vol_pct": 0.0,
        "n_bars":       0,
        "error":        reason,
        "updated_at":   datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00"),
        "age_seconds":  9999,
        "from_cache":   False,
    }


# ============ 命令行入口 ============

if __name__ == "__main__":
    import argparse
    parser = argparse.ArgumentParser(description="GARCH 波动率仓位调整")
    parser.add_argument("--refresh", action="store_true", help="强制刷新缓存")
    args = parser.parse_args()

    result = get_vol_multiplier(refresh=args.refresh)
    print(json.dumps(result, ensure_ascii=False, indent=2))
