#!/usr/bin/env python3
"""
Multi-Session Market Analyzer
分析亚洲、伦敦、纽约三大交易时段的市场状态
输出：每个时段的偏向、波动性、流动性质量
用于增强策略决策（方向过滤 + 置信度调整）
"""
import json, math, time, pathlib, urllib.request
from datetime import datetime, timezone, timedelta

try:
    from src.market_context import normalize_regime_label
except ModuleNotFoundError:
    from market_context import normalize_regime_label
from src.learning.meta_decision import get_threshold as _mt

# ============ 时区配置 ============
# 黄金主要交易时段（UTC）
SESSIONS = {
    "ASIA": {
        "name": "亚洲时段",
        "start_hour": 22,
        "end_hour": 8,
        "timezone": "UTC+8",
        "typical_range_pct": 0.40,  # 占日均波幅约40%
        "trend_bias": 0,   # -1=bearish, 0=neutral, 1=bullish (历史统计)
    },
    "LONDON": {
        "name": "伦敦时段",
        "start_hour": 8,   # 08:00 UTC
        "end_hour": 17,    # 17:00 UTC
        "timezone": "UTC+8",
        "typical_range_pct": 0.35,
        "trend_bias": 0,
    },
    "NEW_YORK": {
        "name": "纽约时段",
        "start_hour": 13,  # 13:00 UTC
        "end_hour": 22,    # 22:00 UTC
        "timezone": "UTC+8",
        "typical_range_pct": 0.50,  # 重叠时段波动最大
        "trend_bias": 0,
    },
    "ASIA_LONDON_OVERLAP": {
        "name": "亚欧重叠",
        "start_hour": 8,
        "end_hour": 13,
        "timezone": "UTC+8",
        "typical_range_pct": 0.55,
        "trend_bias": 0,
    },
    "LONDON_NY_OVERLAP": {
        "name": "欧美重叠",
        "start_hour": 13,
        "end_hour": 17,
        "timezone": "UTC+8",
        "typical_range_pct": 0.70,  # 最重要时段，趋势最强
        "trend_bias": 0,
    },
    "FRIDAY_PRE_CLOSE": {
        "name": "周五休市前清仓",
        "name_en": "FRIDAY_PRE_CLOSE",
        "start_hour": 0,
        "end_hour": 0,
        "timezone": "UTC+8",
        "typical_range_pct": 0.0,
        "trend_bias": 0,
    },
    "WEEKEND_CLOSED": {
        "name": "周末休市",
        "name_en": "WEEKEND_CLOSED",
        "start_hour": 0,
        "end_hour": 0,
        "timezone": "UTC+8",
        "typical_range_pct": 0.0,
        "trend_bias": 0,
    },
    "CRYPTO_24_7": {
        "name": "加密全天候",
        "start_hour": 0,
        "end_hour": 23,
        "timezone": "UTC",
        "typical_range_pct": 1.0,
        "trend_bias": 0,
    },
}

def get_live_bid_ask():
    """获取实时报价（委托给 data_layer）"""
    from src.data_layer import get_live_bid_ask as _dl_get
    return _dl_get()

def get_ea_bars(count=200):
    """通过 MT5 EA Bridge 获取 XAUUSD 历史K线（M5）"""
    try:
        import sys, pathlib, json, time
        sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
        from bridge.strategy import _read_bars_from_ea
        bars = _read_bars_from_ea(count=count)
        if bars is None:
            bars = []
        return bars
    except Exception as e:
        import traceback, logging
        try:
            logging.warning(f"[session_analyzer] get_ea_bars() failed: {e} — {traceback.format_exc()}")
        except Exception:
            pass
        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 _atr(bars, period=14):
    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))
    ema_trs = _ema(trs, period)
    return [v if v is not None else 0 for v in ema_trs]

def _rsi(closes, period=14):
    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))
    if len(gains) < period:
        return [None] * len(closes)
    result = [None] * (period + 1)
    avg_gain = sum(gains[:period]) / period
    avg_loss = sum(losses[:period]) / period
    # Flat price: both gains and losses are zero → RSI should be 50 (neutral), not ~99
    if avg_gain == 0 and avg_loss == 0:
        rs = 1.0  # RSI = 100 - 100/(1+1) = 50
    else:
        rs = avg_gain / avg_loss if avg_loss != 0 else 100
    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_gain == 0 and avg_loss == 0:
            rs = 1.0  # RSI = 50 (neutral)
        else:
            rs = avg_gain / avg_loss if avg_loss != 0 else 100
        result.append(100 - 100 / (1 + rs))
    return result


def _adx(bars, period=14):
    if len(bars) < period + 1:
        return 25.0, "TRANSITION"

    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, "TRANSITION"

    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]
        dx.append(0 if di_sum == 0 else 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 = "WEAK_TREND"
    elif adx >= 18:
        label = "RANGING"
    else:
        label = "TRANSITION"
    return adx, label


def _classify_asia_profile(session_key, direction, ema_bias, atr_pct, score, adx_value):
    if session_key not in ("ASIA", "ASIA_LONDON_OVERLAP"):
        return {
            "mode": "NORMAL",
            "block_new_positions": False,
            "confidence_penalty": 0.0,
            "risk_multiplier": 1.0,
            "entry_style": "MARKET",
        }

    overlap = session_key == "ASIA_LONDON_OVERLAP"
    low_atr_cutoff = _mt("asia_low_atr_cutoff") * (1.0 if not overlap else _mt("overlap_atr_mult"))
    trend_atr_cutoff = _mt("asia_trend_atr_cutoff") * (1.0 if not overlap else _mt("overlap_trend_atr_mult"))
    weak_points = 0.0
    if direction == "NEUTRAL":
        weak_points += _mt("weight_neutral_dir")
    if ema_bias == "NEUTRAL":
        weak_points += _mt("weight_neutral_ema")
    if adx_value < (_mt("asia_adx_cutoff") * (1.0 if not overlap else _mt("overlap_adx_mult"))):
        weak_points += _mt("weight_low_adx")
    if atr_pct < low_atr_cutoff:
        weak_points += _mt("weight_low_atr")
    if abs(score) < _mt("asia_weak_score_cutoff"):
        weak_points += _mt("weight_low_score")
    noise = weak_points >= _mt("noise_weight_threshold")
    trend = (
        direction != "NEUTRAL"
        and ema_bias == direction
        and adx_value >= (_mt("asia_trend_adx_cutoff") * (1.0 if not overlap else _mt("overlap_trend_adx_mult")))
        and atr_pct >= trend_atr_cutoff
        and abs(score) >= _mt("asia_trend_min_score")
    )

    if trend:
        return {
            "mode": "ASIA_TREND",
            "block_new_positions": False,
            "confidence_penalty": -0.01,
            "risk_multiplier": 0.8 if not overlap else 0.9,
            "entry_style": "LIMIT",
        }
    if noise:
        return {
            "mode": "ASIA_NOISE",
            "block_new_positions": True,
            "confidence_penalty": -0.08 if not overlap else -0.05,
            "risk_multiplier": 0.35 if not overlap else 0.45,
            "entry_style": "LIMIT",
        }
    return {
        "mode": "ASIA_DEFENSIVE",
        "block_new_positions": False,
        "confidence_penalty": -0.05 if not overlap else -0.03,
        "risk_multiplier": 0.55 if not overlap else 0.7,
        "entry_style": "LIMIT",
    }

def _dst_offset_utc(now_utc: datetime = None) -> int:
    """返回北半球夏令时导致的 UTC 偏移量。

    北半球夏令时: 3月第2个周日 → 11月第1个周日。
    - 夏令时期间(BST/EDT等): 返回 1 (时段UTC小时需-1)
    - 冬令时期间: 返回 0 (时段使用标准UTC小时)

    伦敦(BST, UTC+1): 标准 08:00 UTC → 夏令时 07:00 UTC
    纽约(EDT, UTC-4): 标准 13:00 UTC → 夏令时 12:00 UTC
    """
    if now_utc is None:
        now_utc = datetime.now(timezone.utc)
    year = now_utc.year
    # 3月第2个周日
    mar = datetime(year, 3, 1, tzinfo=timezone.utc)
    while mar.weekday() != 6:
        mar += timedelta(days=1)
    mar_dst_start = mar + timedelta(days=7)
    # 11月第1个周日
    nov = datetime(year, 11, 1, tzinfo=timezone.utc)
    while nov.weekday() != 6:
        nov += timedelta(days=1)
    nov_dst_end = nov
    if mar_dst_start <= now_utc < nov_dst_end:
        return 1
    return 0

def _hour_in_session(hour, session_key):
    cfg = SESSIONS[session_key]
    start_h = cfg["start_hour"]
    end_h = cfg["end_hour"]
    if start_h > end_h:
        return hour >= start_h or hour < end_h
    return start_h <= hour < end_h

def _is_crypto_symbol(symbol: str = "") -> bool:
    """判断是否为加密品种（24×7 交易，无传统时段概念）"""
    sym = (symbol or "").upper().strip()
    if not sym:
        return False
    # Detect crypto by symbol prefix or common crypto pairs
    crypto_prefixes = ("BTC", "ETH", "XRP", "SOL", "DOGE", "ADA", "DOT", "LTC", "BCH")
    for prefix in crypto_prefixes:
        if sym.startswith(prefix):
            return True
    return False


def _is_metal_symbol(symbol: str = "") -> bool:
    """判断是否为贵金属品种"""
    sym = (symbol or "").upper().strip()
    if not sym:
        return True  # default: assume metal (backward compatible)
    metal_ids = ("XAU", "XAG", "XPT", "XPD", "GOLD", "SILVER")
    for mid in metal_ids:
        if mid in sym:
            return True
    return False


def get_current_session(symbol: str = ""):
    """判断当前处于哪个交易时段（含夏令时/冬令时自动修正 + 品种感知）

    加密品种（BTC/ETH等）：24×7 不休市，返回 "CRYPTO_24_7"
    贵金属/外汇：传统三时段 + 周末休市
    """
    now_utc = datetime.now(timezone.utc)
    h = now_utc.hour

    # ── Crypto: 24×7, no sessions, no weekend ──
    if _is_crypto_symbol(symbol):
        return "CRYPTO_24_7"

    dst = _dst_offset_utc(now_utc)  # 1=夏令时(时段UTC小时-1), 0=冬令时

    # ── Weekend check for metals/forex (Fri 22:00 UTC → Sun 22:00 UTC) ──
    weekday = now_utc.weekday()  # Monday=0, Sunday=6
    if weekday == 5:                                                      # Saturday all day
        return "WEEKEND_CLOSED"
    if weekday == 4 and h >= 22:                                           # Friday after 22:00
        return "WEEKEND_CLOSED"
    if weekday == 6 and h < 22:                                            # Sunday before 22:00
        return "WEEKEND_CLOSED"

    # 夏令时调整：伦敦/纽约开盘UTC小时各减1（BST 07:00 UTC, EDT 12:00 UTC）
    ny_open = 13 - dst       # 13 UTC standard, 12 UTC DST
    london_open = 8 - dst    # 8 UTC standard, 7 UTC DST
    ny_close = 22 - dst      # 22 UTC standard, 21 UTC DST
    london_close = 17 - dst  # 17 UTC standard, 16 UTC DST

    if ny_open <= h < london_close:
        return "LONDON_NY_OVERLAP"
    elif london_open <= h < ny_open:
        return "ASIA_LONDON_OVERLAP"
    elif london_close <= h < ny_close:
        return "NEW_YORK"
    elif _hour_in_session(h, "ASIA"):
        return "ASIA"
    return "LONDON"


def _load_weekend_guard_config() -> dict:
    """Load weekend guard config from app_config.json with defaults."""
    try:
        import config_manager
        cfg = config_manager.load_app_config()
        guard = cfg.get("weekend_guard", {})
        if isinstance(guard, dict):
            return {
                "enabled": bool(guard.get("enabled", True)),
                "friday_close_utc": int(guard.get("friday_close_utc", 22)),
                "pre_close_minutes": int(guard.get("pre_close_minutes", 60)),
                "tick_silence_seconds": int(guard.get("tick_silence_seconds", 900)),
            }
    except Exception:
        pass
    return {
        "enabled": True,
        "friday_close_utc": 22,
        "pre_close_minutes": 60,
        "tick_silence_seconds": 900,
    }


def detect_weekend_guard_status(bars=None, price_age_seconds=None):
    """Detect Friday pre-close / weekend status using bar timestamps (NOT computer clock).

    Uses the latest bar's ``time`` field as the authoritative time source,
    which reflects MT5 server time calibrated to UTC by _normalize_bar_timestamps().

    Returns one of: "NORMAL", "FRIDAY_PRE_CLOSE", "WEEKEND_CLOSED"

    Parameters:
        bars: List of bar dicts with ``time`` field (Unix timestamp, UTC).
              If None or empty, falls back to computer clock for weekday check.
        price_age_seconds: Age of the most recent price/tick in seconds.
                           Used for tick-silence auto-detection.
    """
    cfg = _load_weekend_guard_config()
    if not cfg["enabled"]:
        return "NORMAL"

    close_hour = cfg["friday_close_utc"]
    pre_close_hour = close_hour - max(1, cfg["pre_close_minutes"] // 60)
    tick_silence_sec = cfg["tick_silence_seconds"]

    # 1. Determine current UTC time from bar timestamp (primary) or computer clock (fallback)
    now_utc = datetime.now(timezone.utc)
    if bars and len(bars) > 0:
        last_bar_ts = bars[-1].get("time", 0)
        if isinstance(last_bar_ts, (int, float)) and last_bar_ts > 1_000_000_000:
            try:
                now_utc = datetime.fromtimestamp(float(last_bar_ts), tz=timezone.utc)
            except (OSError, ValueError, OverflowError):
                pass  # fall back to computer clock

    weekday = now_utc.weekday()  # Monday=0, Sunday=6
    hour = now_utc.hour

    # 2. Saturday → definitely closed
    if weekday == 5:
        return "WEEKEND_CLOSED"

    # 3. Friday: check pre-close window
    if weekday == 4:
        # Already past close hour → closed
        if hour >= close_hour:
            return "WEEKEND_CLOSED"

        # Pre-close window → trigger position cleanup
        if hour >= pre_close_hour:
            return "FRIDAY_PRE_CLOSE"

        # Tick silence detection: if price stale > threshold on Friday afternoon
        # (19:00+ UTC), market may have closed earlier than configured
        if hour >= 19 and price_age_seconds is not None:
            age = float(price_age_seconds)
            if age > tick_silence_sec:
                return "FRIDAY_PRE_CLOSE"

    # 4. Sunday before open
    if weekday == 6:
        if hour < close_hour:
            return "WEEKEND_CLOSED"

    return "NORMAL"

def filter_session_bars(bars, session_key):
    """
    过滤出属于特定时段的K线（UTC小时范围）
    """
    filtered = []
    for b in bars:
        dt = datetime.fromtimestamp(b["time"], tz=timezone.utc)
        h = dt.hour
        if _hour_in_session(h, session_key):
            filtered.append(b)
    return filtered

def _compute_regime_aware_score(
    *,
    ema_score: int,
    rsi_zone: str,
    adx_now: float,
    atr_pct: float,
    regime: str = "UNKNOWN",
    regime_confidence: float = 0.0,
) -> tuple[float, str, float]:
    """Regime-aware directional scoring — the single source of truth for per-session direction.

    Key design principles:
      - TREND regimes: follow the trend (EMA weight dominant, RSI ignores extremes, ADX amplifies)
      - RANGING regimes: fade extremes (RSI contrarian, EMA weight reduced, ADX ignored)
      - TRANSITION / UNKNOWN / EXTREME: stay NEUTRAL, don't guess
      - ATR is never given a directional bias — high vol just means "big move", not "up"

    Returns (score, direction, confidence_adj).
    """
    regime_u = normalize_regime_label(regime)

    # ── Default weights (balanced) ──
    ema_weight = 3.0
    rsi_follow_trend = False   # False = contrarian (fade extremes), True = momentum (follow)
    adx_boost = True
    atr_bonus = 0

    # ── Regime-specific adjustments ──
    if regime_u in ("STRONG_TREND",):
        ema_weight = 5.0          # EMA 排列是主要信号
        rsi_follow_trend = True   # 趋势中不逆市做 RSI 反向
        adx_boost = True
        atr_bonus = 0             # ATR 不给方向性偏向

    elif regime_u in ("WEAK_TREND", "STEALTH_TREND"):
        ema_weight = 4.0
        rsi_follow_trend = True   # 趋势中跟随 RSI 方向
        adx_boost = True
        atr_bonus = 0

    elif regime_u in ("RANGING",):
        ema_weight = 1.5          # 震荡中 EMA 权重降低
        rsi_follow_trend = False  # 震荡中做均值回归：超卖→看涨，超买→看跌
        adx_boost = False         # ADX 在震荡中无意义
        atr_bonus = 0             # ATR 不给方向偏向

    elif regime_u in ("FAKE_TREND", "VOLATILITY_SPIKE"):
        ema_weight = 1.0          # 假突破/波动率尖峰中 EMA 不可靠
        rsi_follow_trend = False
        adx_boost = False
        atr_bonus = 0
        # 低置信度场景：直接返回 NEUTRAL
        return 0.0, "NEUTRAL", -0.05

    elif regime_u in ("EXTREME", "TRANSITION", "UNKNOWN"):
        # 极端行情或过渡期：不猜方向
        return 0.0, "NEUTRAL", -0.05

    # ── Compute score ──
    score = ema_score * ema_weight

    # RSI component
    if rsi_follow_trend:
        # TREND FOLLOWING: RSI extremes confirm trend strength.
        # Overbought in uptrend = strong → BULLISH. Overbought in downtrend = counter-trend rally → BEARISH.
        # Oversold in downtrend = strong → BEARISH. Oversold in uptrend = counter-trend dip → BULLISH.
        # The rule: RSI extreme always aligns with the EMA trend direction, never against it.
        if rsi_zone in ("OVERBOUGHT", "OVERSOLD"):
            score += 1.5 if ema_score > 0 else (-1.5 if ema_score < 0 else 0)
    else:
        # MEAN REVERSION: RSI extremes signal imminent reversal.
        # Oversold → expect bounce (BULLISH). Overbought → expect pullback (BEARISH).
        if rsi_zone == "OVERSOLD":
            score += 1.0
        elif rsi_zone == "OVERBOUGHT":
            score -= 1.0

    # ADX trend amplification
    if adx_boost and ema_score != 0:
        if adx_now > 35:
            score += ema_score * 2.0       # 强趋势：EMA 权重翻倍（与 ADX>25 区分）
        elif adx_now > 25:
            score += 1.0 if ema_score > 0 else -1.0

    # ATR component — direction-neutral, only affects confidence
    score += atr_bonus

    # ── Direction from score ──
    if score >= 2.5:
        direction = "BULLISH"
        confidence_adj = 0.04
    elif score <= -2.5:
        direction = "BEARISH"
        confidence_adj = 0.04
    elif score >= 1.0:
        direction = "SLIGHTLY_BULLISH"
        confidence_adj = 0.01
    elif score <= -1.0:
        direction = "SLIGHTLY_BEARISH"
        confidence_adj = 0.01
    else:
        direction = "NEUTRAL"
        confidence_adj = -0.03

    # Apply regime confidence modifier: lower regime confidence → shrink confidence_adj
    if regime_confidence > 0 and regime_confidence < 0.5:
        confidence_adj *= 0.5

    return score, direction, confidence_adj


def analyze_session(all_bars, session_key):
    """
    分析特定时段的K线，返回该时段的状态。
    """
    cfg = SESSIONS[session_key]

    # 过滤该时段的K线
    bars = filter_session_bars(all_bars, session_key)

    if len(bars) < 10:
        # 数据不足：用全量数据中最近 bar_count 根 K 线估算，上限 200
        bar_count = min(len(all_bars), 200)
        bars = all_bars[-bar_count:] if bar_count > 1 else all_bars[:]

    if len(bars) < 10:
        return None

    closes = [b["close"] for b in bars]
    highs  = [b["high"]  for b in bars]
    lows   = [b["low"]   for b in bars]

    # 计算 EMA 方向（趋势）
    ema9  = _ema(closes, 9)
    ema21 = _ema(closes, 21)
    ema50 = _ema(closes, 50)

    n = len(closes) - 1
    def v(arr): return arr[n] if n < len(arr) and arr[n] is not None else 0

    # ATR（波动性）
    atr_vals = _atr(bars, 14)
    atr_now = v(atr_vals)

    # RSI
    rsi_vals = _rsi(closes, 14)
    rsi_now = v(rsi_vals)
    adx_now, adx_label = _adx(bars, 14)

    # 计算日内范围（平均）
    daily_ranges = []
    for i in range(len(bars)):
        if i > 0:
            daily_ranges.append(bars[i]["high"] - bars[i]["low"])

    avg_range = sum(daily_ranges) / max(len(daily_ranges), 1) if daily_ranges else 0

    # 当前价格在 EMA50 的相对位置
    ema50v = v(ema50)
    ema9v  = v(ema9)
    ema21v = v(ema21)
    current_price = closes[-1] if closes else 0

    # 1. EMA 排列
    # 当 ema50 无足够数据时（bars < 50，v() 返回 0），不参与排列判断，
    # 只靠 ema9 vs ema21。防止 gold@$4300 价格下 ema9>ema21>0 永远为真的虚假 BULLISH。
    _ema50_valid = ema50[n] is not None if n < len(ema50) else False
    if _ema50_valid:
        if ema9v > ema21v > ema50v:
            ema_bias = "BULLISH"
            ema_score = 1
        elif ema9v < ema21v < ema50v:
            ema_bias = "BEARISH"
            ema_score = -1
        else:
            ema_bias = "NEUTRAL"
            ema_score = 0
    else:
        # 回退：仅用 ema9 vs ema21
        if ema9v > ema21v:
            ema_bias = "BULLISH"
            ema_score = 1
        elif ema9v < ema21v:
            ema_bias = "BEARISH"
            ema_score = -1
        else:
            ema_bias = "NEUTRAL"
            ema_score = 0

    # 2. RSI 极端区域
    # 当 bars < 14 时 RSI 为 None → v() 返回 0 → 0 < 35 为真 → 虚假 OVERSOLD
    _rsi_valid = rsi_vals[n] is not None if n < len(rsi_vals) else False
    if _rsi_valid:
        if rsi_now > 65:
            rsi_zone = "OVERBOUGHT"
        elif rsi_now < 35:
            rsi_zone = "OVERSOLD"
        else:
            rsi_zone = "NEUTRAL"
    else:
        rsi_zone = "NEUTRAL"
        rsi_now = 50.0

    # 3. 波动性评估（ATR vs 历史）
    atr_pct = (atr_now / current_price) * 100 if current_price else 0

    # ── Regime 感知方向评分（替代原固定公式）──
    # 获取该时段 bars 的 Regime 分类
    session_regime = "UNKNOWN"
    session_regime_conf = 0.0
    try:
        from src.regime_router import classify_regime
        _regime_result = classify_regime(bars)
        session_regime = _regime_result.get("regime", "UNKNOWN")
        session_regime_conf = float(_regime_result.get("confidence", 0.0) or 0.0)
    except Exception:
        pass

    score, direction, confidence_adj = _compute_regime_aware_score(
        ema_score=ema_score,
        rsi_zone=rsi_zone,
        adx_now=adx_now,
        atr_pct=atr_pct,
        regime=session_regime,
        regime_confidence=session_regime_conf,
    )

    # ── 量能背离修正（只修正 score，不覆盖 direction）──
    # direction 由 _compute_regime_aware_score 的阈值体系决定，
    # 量能修正仅对 score 做增量，direction 保持不变。
    try:
        from src.market_analyzer import (
            compute_volume_divergence,
            classify_trend_strength,
            compute_volume_price_match,
        )
        # EMA slope: 2-period change as percentage
        if n > 1 and ema9[n-2] is not None and ema9[n-2] > 0:
            ema_slope = (ema9v - float(ema9[n-2])) / max(0.01, float(ema9[n-2])) * 100
        else:
            ema_slope = 0.0

        vol_div = compute_volume_divergence(bars, closes)
        trend_class = classify_trend_strength(adx_now, ema_slope, atr_pct, direction)
        vol_price = compute_volume_price_match(bars, closes)

        if vol_div.get("divergence_type") == "BULLISH_DIVERGENCE":
            score += 1.5
        elif vol_div.get("divergence_type") == "BEARISH_DIVERGENCE":
            score -= 1.5
        trend_ds = trend_class.get("directional_score", 0)
        score += trend_ds * 2.0

        # Volume-enhanced direction override: only UPGRADE (NEUTRAL→SLIGHTLY_*),
        # never downgrade. The regime-aware direction is the base truth.
        if score >= 2.5 and direction in ("NEUTRAL", "SLIGHTLY_BULLISH"):
            direction = "BULLISH"
        elif score <= -2.5 and direction in ("NEUTRAL", "SLIGHTLY_BEARISH"):
            direction = "BEARISH"
        elif score >= 1.0 and direction == "NEUTRAL":
            direction = "SLIGHTLY_BULLISH"
        elif score <= -1.0 and direction == "NEUTRAL":
            direction = "SLIGHTLY_BEARISH"
    except Exception:
        vol_div = {"divergence_type": "NONE", "divergence_score": 0.0}
        trend_class = {"level": "UNKNOWN", "strength_score": 0.0, "directional_score": 0.0}
        vol_price = {"match_score": 0.0, "match_quality": "ERROR"}

    session_profile = _classify_asia_profile(session_key, direction, ema_bias, atr_pct, score, adx_now)
    confidence_adj += session_profile["confidence_penalty"]

    return {
        "session": session_key,
        "name": cfg["name"],
        "direction": direction,
        "ema_bias": ema_bias,
        "rsi_zone": rsi_zone,
        "rsi": round(rsi_now, 1),
        "ema9": round(ema9v, 3),
        "ema21": round(ema21v, 3),
        "ema50": round(ema50v, 3),
        "atr": round(atr_now, 3),
        "atr_pct": round(atr_pct, 3),
        "adx": round(adx_now, 2),
        "trend_strength": adx_label,
        "avg_range": round(avg_range, 3),
        "confidence_adj": confidence_adj,
        "score": score,
        "current_price": round(current_price, 3),
        "bar_count": len(bars),
        "session_profile": session_profile,
        # Regime-aware scoring context
        "regime": session_regime,
        "regime_confidence": session_regime_conf,
        # 新增三维分析
        "volume_divergence": vol_div,
        "trend_classification": trend_class,
        "volume_price_match": vol_price,
    }

def get_session_bias(session_key):
    """返回时段的固定偏向（历史统计）"""
    biases = {
        "ASIA":              {"bias": "NEUTRAL",  "note": "低波动，观望为主"},
        "LONDON":            {"bias": "BULLISH",   "note": "伦敦开盘推动方向"},
        "NEW_YORK":          {"bias": "BULLISH",   "note": "美国数据前后波动大"},
        "ASIA_LONDON_OVERLAP": {"bias": "NEUTRAL", "note": "亚欧交接，震荡"},
        "LONDON_NY_OVERLAP": {"bias": "BULLISH",  "note": "最重要时段，趋势最强"},
    }
    return biases.get(session_key, {"bias": "NEUTRAL", "note": ""})

# 主时段（无重叠）
_PRIMARY_SESSIONS = {"ASIA", "LONDON", "NEW_YORK"}

def analyze_all_sessions(symbol: str = ""):
    """
    主分析函数：加载数据 → 分析各时段 → 综合输出

    symbol: 交易品种（用于品种感知的时段判断。加密品种跳过传统三时段分析）
    """
    bid, ask = get_live_bid_ask()

    # 获取历史数据（5天5分钟数据）
    bars = get_ea_bars(count=200)

    # ── Crypto: 24×7, skip session breakdown entirely ──
    if _is_crypto_symbol(symbol):
        return {
            "sessions": {},
            "current_session": "CRYPTO_24_7",
            "dominant_session": "CRYPTO_24_7",
            "overall_technical_score": 0.0,
            "overall_direction": "NEUTRAL",
            "market_profile": "24x7 crypto market — no session-based analysis available",
        }

    if not bid and not bars:
        return {"error": "无法获取实时报价且无历史K线", "sessions": {}}

    # Fallback: when bid/ask unavailable or zero, estimate from last bar close
    if not bid and bars:
        last_close = float(bars[-1].get("close", 0) or 0)
        if last_close > 0:
            bid = last_close
            ask = last_close * 1.0001  # rough spread estimate
    if not bid:
        bid = 0.0
        ask = 0.0

    # 宏观相关性数据（DXY/美元指数 vs 黄金）
    macro_correlation = None
    try:
        from bridge.correlation_engine import compute_correlation_signal
        macro_correlation = compute_correlation_signal()
    except Exception:
        pass

    current_session_key = get_current_session(symbol=symbol)

    results = {}
    all_scores = []
    all_directions = []

    for session_key in SESSIONS:
        analysis = analyze_session(bars, session_key)
        if analysis is None:
            # Session has insufficient data — use neutral placeholder
            analysis = {
                "session": session_key,
                "name": SESSIONS[session_key]["name"],
                "direction": "NEUTRAL",
                "score": 0.0,
                "bar_count": 0,
                "is_active": (session_key == current_session_key),
                "data_quality": "insufficient",
            }
        else:
            # 叠加历史偏向
            hist = get_session_bias(session_key)
            analysis["hist_bias"] = hist["bias"]
            analysis["hist_note"] = hist["note"]
            analysis["is_active"] = (session_key == current_session_key)

        results[session_key] = analysis
        # 只加入主时段（非重叠）到综合方向投票
        if session_key in _PRIMARY_SESSIONS:
            all_scores.append(analysis.get("score", 0.0))
            all_directions.append(analysis.get("direction", "NEUTRAL"))

    # 综合判断（仅使用三个主时段：ASIA / LONDON / NEW_YORK）
    # 使用平均分数而非纯投票——单一强趋势时段即可影响方向
    avg_score = sum(all_scores) / max(len(all_scores), 1) if all_scores else 0
    bullish_count = all_directions.count("BULLISH")
    bearish_count = all_directions.count("BEARISH")

    # Score-based primary classification (narrower NEUTRAL band: ±1.5 instead of ±3 vote-based)
    if avg_score >= 2.0:
        overall_direction = "BULLISH"
    elif avg_score <= -2.0:
        overall_direction = "BEARISH"
    elif avg_score >= 0.8:
        overall_direction = "SLIGHTLY_BULLISH"
    elif avg_score <= -0.8:
        overall_direction = "SLIGHTLY_BEARISH"
    # Vote-based secondary check: if one session is strongly directional, allow it to override
    elif bearish_count >= 1 and bullish_count == 0 and avg_score <= -0.3:
        overall_direction = "SLIGHTLY_BEARISH"
    elif bullish_count >= 1 and bearish_count == 0 and avg_score >= 0.3:
        overall_direction = "SLIGHTLY_BULLISH"
    else:
        overall_direction = "NEUTRAL"

    # 当前时段加权（更重要）
    current_analysis = results.get(current_session_key, {})
    current_direction = current_analysis.get("direction", "NEUTRAL")
    current_conf_adj  = current_analysis.get("confidence_adj", 0)
    current_session_profile = current_analysis.get("session_profile", {
        "mode": "NORMAL",
        "block_new_positions": False,
        "confidence_penalty": 0.0,
        "risk_multiplier": 1.0,
        "entry_style": "MARKET",
    })

    # ── 四维 Regime 分类（新增：替代原单一 ADX 判断）──
    regime_label = "UNKNOWN"
    regime_detail = {}
    try:
        from src.regime_router import classify_regime
        regime_detail = classify_regime(bars, session=current_session_key)
        regime_label = regime_detail.get("regime", "UNKNOWN")
    except Exception:
        pass

    # ── Regime-aware direction override ──
    # When the regime classifier has high confidence in a trending regime,
    # its direction takes precedence over the per-session aggregate score.
    regime_aware_direction = overall_direction
    regime_override_reason = ""
    regime_confidence = float(regime_detail.get("confidence", 0.0) or 0.0)
    regime_flags = regime_detail.get("flags", []) if isinstance(regime_detail.get("flags", []), list) else []
    regime_direction_from_flags = ""
    for flag in regime_flags:
        if flag.startswith("direction="):
            regime_direction_from_flags = flag.split("=", 1)[1].strip().upper()

    if regime_confidence >= 0.70 and regime_label in ("STRONG_TREND", "WEAK_TREND", "STEALTH_TREND"):
        if regime_direction_from_flags in ("BULLISH", "BEARISH"):
            mapped = "BULLISH" if regime_direction_from_flags == "BULLISH" else "BEARISH"
            if mapped != overall_direction and overall_direction not in ("BULLISH", "BEARISH"):
                regime_aware_direction = mapped
                regime_override_reason = (
                    f"regime={regime_label}(conf={regime_confidence:.0%}) overrides "
                    f"session_aggregate={overall_direction}"
                )
    elif regime_confidence >= 0.50 and regime_label in ("TRANSITION", "EXTREME", "FAKE_TREND"):
        regime_aware_direction = "NEUTRAL"
        regime_override_reason = f"regime={regime_label} forces NEUTRAL"

    return {
        "timestamp": time.strftime("%Y-%m-%dT%H:%M:%SZ"),
        "bid": bid, "ask": ask,
        "spread": round((ask - bid) * 100, 1),
        "current_session": current_session_key,
        "current_session_name": SESSIONS[current_session_key]["name"],
        "overall_direction": overall_direction,
        "overall_score": round(avg_score, 2),
        "regime_aware_direction": regime_aware_direction,
        "regime_override_reason": regime_override_reason,
        "session_count": {
            "bullish": bullish_count,
            "neutral": all_directions.count("NEUTRAL"),
            "bearish": bearish_count,
        },
        "confidence_adj_for_signal": round(current_conf_adj, 3),
        "current_session_profile": current_session_profile,
        "current_analysis": current_analysis,
        "all_sessions": results,
        "bars_used": len(bars),
        "macro_correlation": macro_correlation,
        # 新增：四维 Regime 分类
        "regime": regime_label,
        "regime_detail": regime_detail,
    }

def get_strategy_recommendation(analysis_result):
    """
    根据多时段分析，生成策略建议
    """
    if "error" in analysis_result:
        return {"action": "HOLD", "reason": analysis_result["error"]}

    current = analysis_result.get("current_analysis", {})
    overall = analysis_result["overall_direction"]
    current_dir = current.get("direction", "NEUTRAL")
    rsi = current.get("rsi", 50)
    ema_bias = current.get("ema_bias", "NEUTRAL")
    score = analysis_result.get("overall_score", 0)
    session_profile = current.get("session_profile", analysis_result.get("current_session_profile", {
        "mode": "NORMAL",
        "block_new_positions": False,
        "confidence_penalty": 0.0,
        "risk_multiplier": 1.0,
        "entry_style": "MARKET",
    }))
    session_mode = session_profile.get("mode", "NORMAL")
    risk_multiplier = session_profile.get("risk_multiplier", 1.0)
    entry_style = session_profile.get("entry_style", "MARKET")

    # 方向过滤
    # 如果当前时段和总体方向一致 → 增强置信度
    # 如果矛盾 → 降低置信度或反向

    if overall == "BULLISH" and current_dir == "BULLISH":
        recommendation = "PREFER_LONG"
        reason = "多时段共振看涨"
        conf_bonus = 0.05
    elif overall == "BEARISH" and current_dir == "BEARISH":
        recommendation = "PREFER_SHORT"
        reason = "多时段共振看跌"
        conf_bonus = 0.05
    elif overall == "NEUTRAL" or current_dir == "NEUTRAL":
        recommendation = "CAUTIOUS"
        reason = "方向不明，缩小仓位"
        conf_bonus = -0.05
    elif overall == "SLIGHTLY_BULLISH":
        recommendation = "LONG_WITH_CAUTION"
        reason = "轻微看涨，控制仓位"
        conf_bonus = 0
    elif overall == "SLIGHTLY_BEARISH":
        recommendation = "SHORT_WITH_CAUTION"
        reason = "轻微看跌，控制仓位"
        conf_bonus = 0
    else:
        recommendation = "HOLD"
        reason = "方向冲突，观望"
        conf_bonus = -0.10

    # RSI 极端值二次过滤
    if rsi > 70 and recommendation in ("PREFER_LONG", "LONG_WITH_CAUTION"):
        recommendation = "AVOID_LONG"
        reason = f"RSI超买({rsi})，不追多"
        conf_bonus = -0.10
    elif rsi < 30 and recommendation in ("PREFER_SHORT", "SHORT_WITH_CAUTION"):
        recommendation = "AVOID_SHORT"
        reason = f"RSI超卖({rsi})，不追空"
        conf_bonus = -0.10

    if session_profile.get("block_new_positions"):
        recommendation = "AVOID"
        reason = f"{session_mode}：{session_mode}模式，禁止新开仓"
        conf_bonus = min(conf_bonus, -0.12)
    elif session_mode == "ASIA_DEFENSIVE":
        if recommendation == "PREFER_LONG":
            recommendation = "LONG_WITH_CAUTION"
        elif recommendation == "PREFER_SHORT":
            recommendation = "SHORT_WITH_CAUTION"
        elif recommendation not in ("HOLD", "AVOID_LONG", "AVOID_SHORT"):
            recommendation = "CAUTIOUS"
        reason = f"{session_mode}：波动不足但未失真，只允许轻仓防守"
        conf_bonus = min(conf_bonus, -0.04)
    elif session_mode == "ASIA_TREND" and current_dir in ("BULLISH", "BEARISH"):
        if recommendation in ("CAUTIOUS", "HOLD"):
            recommendation = "LONG_WITH_CAUTION" if current_dir == "BULLISH" else "SHORT_WITH_CAUTION"
            reason = f"{session_mode}：亚盘顺向结构成立，可轻仓限价跟随"
        conf_bonus = max(conf_bonus, 0.01)

    return {
        "recommendation": recommendation,
        "reason": reason,
        "conf_bonus": conf_bonus,
        "overall_direction": overall,
        "current_direction": current_dir,
        "score": score,
        "rsi": rsi,
        "ema_bias": ema_bias,
        "session_mode": session_mode,
        "session_profile": session_profile,
        "risk_multiplier": risk_multiplier,
        "entry_style": entry_style,
    }


def export_market_structure() -> dict:
    """为 pipeline / update_interpreter_contract 提供当前市场结构数据。"""
    try:
        result = analyze_all_sessions()
    except Exception:
        return {}
    if not isinstance(result, dict) or "error" in result:
        return {}

    current_analysis = result.get("current_analysis", {})
    if not isinstance(current_analysis, dict):
        return {}

    current_session_key = result.get("current_session", "UNKNOWN")
    adx = float(current_analysis.get("adx", 0.0) or 0.0)
    direction = str(current_analysis.get("direction", "NEUTRAL") or "NEUTRAL").upper()
    overall = str(result.get("overall_direction", "NEUTRAL") or "NEUTRAL").upper()
    rsi = float(current_analysis.get("rsi", 50.0) or 50.0)
    session_profile = current_analysis.get("session_profile", {})
    if not isinstance(session_profile, dict):
        session_profile = {}

    # Prefer 4D classifier regime when available; fall back to ADX-only
    _4d_regime = str(result.get("regime", "") or "").strip().upper()
    _4d_in_range = {"STRONG_TREND", "WEAK_TREND", "STEALTH_TREND", "RANGING",
                    "TRANSITION", "FAKE_TREND", "VOLATILITY_SPIKE", "EXTREME"}
    if _4d_regime in _4d_in_range:
        market_structure = _4d_regime
    elif adx >= 25:
        market_structure = "WEAK_TREND"
    elif adx >= 18:
        market_structure = "RANGING"
    else:
        market_structure = "TRANSITION"

    if overall == direction and direction in ("BULLISH", "BEARISH"):
        dominant_direction = direction
    elif overall in ("SLIGHTLY_BULLISH",) and direction in ("BULLISH", "NEUTRAL"):
        dominant_direction = "BULLISH"
    elif overall in ("SLIGHTLY_BEARISH",) and direction in ("BEARISH", "NEUTRAL"):
        dominant_direction = "BEARISH"
    else:
        dominant_direction = "MIXED"

    session_mode = str(session_profile.get("mode", "NORMAL") or "NORMAL").upper()
    block_new = bool(session_profile.get("block_new_positions", False))
    if block_new:
        execution_advice = "AVOID"
    elif session_mode in ("ASIA_NOISE",):
        execution_advice = "CAUTION"
    elif rsi > 75 or rsi < 25:
        execution_advice = "CAUTION"
    elif session_mode in ("ASIA_DEFENSIVE",):
        execution_advice = "REDUCE_ONLY"
    else:
        execution_advice = "ALLOW_FOLLOW"

    risk_mult = float(session_profile.get("risk_multiplier", 1.0) or 1.0)
    if dominant_direction == "BULLISH":
        buy_mult = round(1.0 * risk_mult, 4)
        sell_mult = round(0.6 * risk_mult, 4)
    elif dominant_direction == "BEARISH":
        buy_mult = round(0.6 * risk_mult, 4)
        sell_mult = round(1.0 * risk_mult, 4)
    else:
        buy_mult = round(0.8 * risk_mult, 4)
        sell_mult = round(0.8 * risk_mult, 4)

    # ── Entry timing hint: guides the pipeline on HOW to enter ──
    entry_timing_hint = "WAIT_CLEAR_SIGNAL"  # default: cautious
    if execution_advice in ("AVOID",):
        entry_timing_hint = "AVOID"
    elif session_mode in ("ASIA_NOISE", "ASIA_DEFENSIVE"):
        entry_timing_hint = "LIMIT_ONLY"     # low vol: use limit orders
    elif market_structure == "WEAK_TREND" and adx >= 25:
        if (dominant_direction == "BULLISH" and rsi < 60) or (dominant_direction == "BEARISH" and rsi > 40):
            entry_timing_hint = "MARKET_OK"   # trend healthy, not extended
        else:
            entry_timing_hint = "WAIT_PULLBACK"  # trend exists but RSI extended
    elif market_structure == "RANGING":
        if rsi < 35 or rsi > 65:
            entry_timing_hint = "MARKET_OK"   # mean reversion entry zone
        else:
            entry_timing_hint = "WAIT_CLEAR_SIGNAL"  # mid-range, wait
    elif session_mode in ("LONDON_NY_OVERLAP",) and adx >= 20:
        entry_timing_hint = "MARKET_OK"       # high-liquidity window

    return {
        "market_structure": market_structure,
        "entry_timing_hint": entry_timing_hint,
        "regime_subtype": f"{current_session_key}_{session_mode}",
        "dominant_direction": dominant_direction,
        "execution_advice": execution_advice,
        "buy_multiplier": buy_mult,
        "sell_multiplier": sell_mult,
        "structural_side": dominant_direction if dominant_direction in ("BUY", "SELL") else
                           ("BUY" if dominant_direction == "BULLISH" else
                            "SELL" if dominant_direction == "BEARISH" else ""),
        "continuation_bias": dominant_direction if dominant_direction in ("BULLISH", "BEARISH") else "NEUTRAL",
        "counter_trend_risk": "HIGH" if session_mode in ("ASIA_NOISE",) else "LOW",
        "current_session": current_session_key,
        "session_mode": session_mode,
        "adx": round(adx, 2),
        "rsi": round(rsi, 1),
        "reason": f"session:{current_session_key} structure:{market_structure} direction:{dominant_direction} advice:{execution_advice}",
        "timestamp": time.time(),
    }


# ============ 测试入口 ============
if __name__ == "__main__":
    print("=== 多时段市场分析 ===")
    result = analyze_all_sessions()

    rec = get_strategy_recommendation(result)

    print(f"\n当前时段: {result.get('current_session_name','?')} ({result.get('current_session','?')})")
    print(f"总体方向: {result.get('overall_direction','?')} (score={result.get('overall_score','?')})")
    print(f"各时段偏向: 多头={result.get('session_count',{}).get('bullish',0)} 空头={result.get('session_count',{}).get('bearish',0)} 中性={result.get('session_count',{}).get('neutral',0)}")

    print(f"\n策略建议: {rec['recommendation']}")
    print(f"原因: {rec['reason']}")
    print(f"置信度调整: {rec['conf_bonus']:+.2f}")
    print(f"RSI: {rec['rsi']} | EMA偏向: {rec['ema_bias']}")

    print("\n--- 各时段详情 ---")
    for key, s in result.get("all_sessions", {}).items():
        marker = " ◀" if s.get("is_active") else ""
        print(f"  [{key}] {s['name']}{marker}: {s['direction']} EMA={s['ema_bias']} RSI={s['rsi']} score={s['score']}")
