#!/usr/bin/env python3
"""
XAUUSD 交易策略 - 趋势 + 震荡双模式
regime 检测 → 模式选择 → 信号生成 → SL/TP 计算
"""
import json, math, os, time, pathlib, urllib.request
import pandas as pd

# ============ 配置 ============
import sys
import pathlib

# 确保可以导入项目根目录下的 config_manager
ROOT_DIR = pathlib.Path(__file__).parent.parent.absolute()
if str(ROOT_DIR) not in sys.path:
    sys.path.insert(0, str(ROOT_DIR))
import config_manager

# 使用 config_manager 自动探测 MT5 路径，绝不使用硬编码
MT5_DIR = config_manager.get_mt5_dir()
if not MT5_DIR or not MT5_DIR.exists():
    MT5_DIR = ROOT_DIR / "test_mt5_files"
# EA Bridge 路径
_BRIDGE_TO_EA   = MT5_DIR / "gold_autotrade/bridge/to_ea"
_BRIDGE_FROM_EA = MT5_DIR / "gold_autotrade/bridge/from_ea"

# ============ 历史K线数据获取（仅 MT5 EA Bridge）============
import pathlib as _pathlib, time as _time, json as _json

# 进程内内存缓存（EA请求结果，5分钟有效）
_memory_cache = {"bars": [], "fetched_at": 0.0}


def _write_request_atomic(target_path: pathlib.Path, payload: dict):
    tmp_path = target_path.with_suffix(target_path.suffix + ".tmp")
    tmp_path.write_text(_json.dumps(payload))
    os.replace(tmp_path, target_path)


def _read_bars_from_ea(count=200, timeout=15, timeframe="M5"):
    """
    通过 MT5 EA Bridge 获取历史K线。
    优化策略：
    1. 先检查 from_ea 是否有现成的 bars 文件（EA 已写入的响应）
    2. 没有则发请求，最多等 timeout 秒
    3. 超时则返回空
    """
    sig_id = f"bars_{timeframe}_{int(_time.time() * 1000)}"
    req_file = _BRIDGE_TO_EA / f"{sig_id}.request.json"
    resp_file = _BRIDGE_FROM_EA / f"{sig_id}.bars.json"

    # 步骤1：快速清理旧的 bars 文件，防止读取到很久以前超时的残留响应
    try:
        _BRIDGE_FROM_EA.mkdir(parents=True, exist_ok=True)
        now_time = _time.time()
        for f in _BRIDGE_FROM_EA.glob(f"bars_{timeframe}_*.bars.json"):
            # 如果文件是最近 15 秒内生成的，勉强可以接受（可能是刚刚超时的请求返回的）
            if now_time - f.stat().st_mtime < 15:
                try:
                    raw = f.read_text()
                    import re as _re
                    raw_fixed = _re.sub(r'"time":(\d{4}\.\d{2}\.\d{2} \d{2}:\d{2}:\d{2})', r'"time":"\1"', raw)
                    data = _json.loads(raw_fixed)
                    bars = data.get("bars", [])
                    if bars and bars[-1].get("time"):
                        f.unlink(missing_ok=True)
                        return bars[-count:] if len(bars) >= count else bars
                except Exception:
                    f.unlink(missing_ok=True)
            else:
                # 过期残留文件，直接清理
                f.unlink(missing_ok=True)
    except Exception:
        pass

    # 步骤2：发请求等响应
    try:
        _BRIDGE_TO_EA.mkdir(parents=True, exist_ok=True)
        req = {
            "signal_id": sig_id,
            "action": "GET_BARS",
            "symbol": "XAUUSD",
            "timeframe": timeframe,
            "count": count
        }
        _write_request_atomic(req_file, req)
    except Exception:
        return []

    # 轮询等待响应（最多 timeout 秒）
    for _ in range(timeout * 10):
        try:
            if resp_file.exists():
                raw = resp_file.read_text()
                import re as _re
                raw_fixed = _re.sub(r'"time":(\d{4}\.\d{2}\.\d{2} \d{2}:\d{2}:\d{2})', r'"time":"\1"', raw)
                data = _json.loads(raw_fixed)
                bars = data.get("bars", [])
                resp_file.unlink(missing_ok=True)
                req_file.unlink(missing_ok=True)
                if bars:
                    return bars
                elif data.get("status") == "ERROR":
                    # 如果 EA 明确返回了 ERROR（例如 K 线数据不足），直接退出轮询，不再傻等
                    return []
        except Exception:
            pass
        _time.sleep(0.1)

    # 超时：清理
    try: req_file.unlink(missing_ok=True)
    except Exception: pass
    try: resp_file.unlink(missing_ok=True)
    except Exception: pass
    return []


def _read_bars(count=200, timeframe="M5"):
    """
    主入口：通过 MT5 EA Bridge 获取历史K线。
    优先返回内存缓存（5分钟内有效），无缓存则从 EA 实时读取。
    EA 是 XAUUSD 历史数据的唯一来源，不使用任何其他数据API。
    """
    global _memory_cache
    now = _time.time()
    cache_key = f"bars_{timeframe}"
    # 内存缓存有效期内直接返回
    if _memory_cache.get(cache_key) and (now - _memory_cache.get(f"{cache_key}_fetched_at", 0)) < 300:
        bars = _memory_cache[cache_key]
        return bars[-count:] if len(bars) >= count else bars

    # 无缓存或已过期，从 EA 读取
    ea_bars = _read_bars_from_ea(count, timeframe=timeframe)
    if ea_bars and len(ea_bars) >= 50:
        _memory_cache[cache_key] = ea_bars
        _memory_cache[f"{cache_key}_fetched_at"] = now
        return ea_bars[-count:] if len(ea_bars) >= count else ea_bars
    # 缓存过期但读取失败：尝试用旧缓存（哪怕过期）
    if _memory_cache.get(cache_key):
        return _memory_cache[cache_key][-count:]
    return []

# ============ 技术指标计算 ============
def _ema(data, period):
    """指数移动平均"""
    if len(data) < period:
        return [None] * len(data)
    k = 2.0 / (period + 1)
    ema = [None] * (period - 1)
    ema.append(sum(data[:period]) / period)
    for v in data[period:]:
        ema.append(v * k + ema[-1] * (1 - k))
    return ema

def _sma(data, period):
    """简单移动平均"""
    result = []
    for i in range(len(data)):
        if i < period - 1:
            result.append(None)
        else:
            result.append(sum(data[i - period + 1:i + 1]) / period)
    return result

def _rsi(closes, period=14):
    """RSI 指标"""
    if len(closes) < period + 1:
        return [None] * len(closes)
    gains, losses = [], []
    for i in range(1, len(closes)):
        delta = closes[i] - closes[i - 1]
        gains.append(max(delta, 0))
        losses.append(max(-delta, 0))
    result = [None] * (period + 1)
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / period
    if avg_loss == 0:
        result.append(100)
    else:
        rs = avg_gain / avg_loss
        result.append(100 - 100 / (1 + rs))
    for i in range(period, len(gains)):
        avg_gain = (avg_gain * (period - 1) + gains[i]) / period
        avg_loss = (avg_loss * (period - 1) + losses[i]) / period
        if avg_loss == 0:
            result.append(100)
        else:
            rs = avg_gain / avg_loss
            result.append(100 - 100 / (1 + rs))
    return result

def _macd(closes, fast=12, slow=26, signal=9):
    """MACD: (ema12 - ema26, signal=ema9 of MACD)"""
    ema_fast = _ema(closes, fast)
    ema_slow = _ema(closes, slow)
    macd_line = [None] * len(closes)
    for i in range(len(closes)):
        if ema_fast[i] is not None and ema_slow[i] is not None:
            macd_line[i] = ema_fast[i] - ema_slow[i]
    signal_line = _ema([v if v is not None else 0 for v in macd_line], signal)
    histogram = [None] * len(closes)
    for i in range(len(closes)):
        if macd_line[i] is not None and signal_line[i] is not None:
            histogram[i] = macd_line[i] - signal_line[i]
    return macd_line, signal_line, histogram

def _atr(bars, period=14):
    """Average True Range"""
    trs = []
    for i, b in enumerate(bars):
        if i == 0:
            trs.append(b["high"] - b["low"])
        else:
            hl = b["high"] - b["low"]
            hc = abs(b["high"] - bars[i-1]["close"])
            lc = abs(b["low"]  - bars[i-1]["close"])
            trs.append(max(hl, hc, lc))
    return _ema(trs, period)

def _bollinger_bands(closes, period=20, std_dev=2):
    """Bollinger Bands: mid, upper, lower"""
    mid = _sma(closes, period)
    std = []
    for i in range(len(closes)):
        if i < period - 1:
            std.append(None)
        else:
            vals = closes[i - period + 1:i + 1]
            m = sum(vals) / period
            variance = sum((v - m) ** 2 for v in vals) / period
            std.append(math.sqrt(variance))
    upper, lower = [], []
    for i in range(len(closes)):
        if mid[i] is None or std[i] is None:
            upper.append(None)
            lower.append(None)
        else:
            upper.append(mid[i] + std_dev * std[i])
            lower.append(mid[i] - std_dev * std[i])
    return mid, upper, lower

def _adx(bars, period=14):
    """
    计算 ADX（趋势强度指标）
    返回: (adx_value, trend_strength_label)
    """
    if len(bars) < period + 15:
        return 25.0, "UNCERTAIN"

    # 计算 +DI, -DI
    plus_dm, minus_dm, true_range = [], [], []
    for i in range(1, len(bars)):
        b = bars[i]
        pb = bars[i-1]
        hl = b["high"] - pb["high"]
        lh = pb["low"] - b["low"]
        plus_dm.append(max(hl, 0) if hl > lh else 0)
        minus_dm.append(max(lh, 0) if lh > hl else 0)
        tr = max(b["high"] - b["low"], abs(b["high"] - pb["close"]), abs(b["low"] - pb["close"]))
        true_range.append(tr)

    if len(true_range) < period:
        return 25.0, "UNCERTAIN"

    # 平滑（一次性计算）
    atr_vals = _ema(true_range, period)
    ema_plus_dm = _ema(plus_dm, period)
    ema_minus_dm = _ema(minus_dm, period)

    plus_di = []
    minus_di = []
    for i in range(len(plus_dm)):
        atr_v = atr_vals[i+1] if (i+1) < len(atr_vals) and atr_vals[i+1] is not None else None
        if atr_v and atr_v != 0 and (i+1) < len(ema_plus_dm) and ema_plus_dm[i+1] is not None:
            plus_di.append(100 * ema_plus_dm[i+1] / atr_v)
            minus_di.append(100 * ema_minus_dm[i+1] / atr_v if (i+1) < len(ema_minus_dm) and ema_minus_dm[i+1] is not None else 0)
        else:
            plus_di.append(0)
            minus_di.append(0)

    dx = []
    for i in range(len(plus_di)):
        di_sum = plus_di[i] + minus_di[i]
        if di_sum == 0:
            dx.append(0)
        else:
            dx.append(100 * abs(plus_di[i] - minus_di[i]) / di_sum)

    adx_vals = _ema(dx, period)
    adx = adx_vals[-1] if adx_vals and adx_vals[-1] is not None else 25.0

    if adx >= 25:
        label = "TRENDING"
    elif adx >= 18:
        label = "RANGING"
    else:
        label = "UNCERTAIN"

    return adx, label

# ============ 市场快照获取 ============
def get_live_bid_ask():
    """获取实时买卖价"""
    try:
        url = "https://forex-data-feed.swissquote.com/public-quotes/bboquotes/instrument/XAU/USD"
        req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req, timeout=5) as r:
            data = json.loads(r.read())
        profiles = data[0].get("spreadProfilePrices", [])
        if profiles:
            return float(profiles[0]["bid"]), float(profiles[0]["ask"])
        return float(data[0]["bids"][0]["price"]), float(data[0]["asks"][0]["price"])
    except Exception:
        return None, None

# ============ 多时段分析集成 ============
def _get_session_analysis():
    """获取多时段分析结果"""
    try:
        import sys
        sys.path.insert(0, str(pathlib.Path(__file__).parent))
        from session_analyzer import analyze_all_sessions, get_strategy_recommendation
        analysis = analyze_all_sessions()
        rec = get_strategy_recommendation(analysis)
        return analysis, rec
    except Exception as e:
        return None, None

# 注意：该文件主要提供 K 线数据获取、多时间周期状态计算以及基础指标函数。
# 旧版 analyze_market 已废弃，现由 signal_generators 中的模块统一生成信号。
