#!/usr/bin/env python3
"""
params_advisor.py — 参数建议引擎
读取 parallel_analysis_latest.json，输出 params_suggestion.json
阈值全部写死，可直接执行。
"""
import json, time, pathlib, sys
from datetime import datetime

try:
    from src.market_context import is_defensive_regime, is_trend_regime, normalize_regime_label
except ModuleNotFoundError:
    from market_context import is_defensive_regime, is_trend_regime, normalize_regime_label

# ROOT now resolved via frozen_path (PyInstaller-safe)
from frozen_path import APP_ROOT as ROOT
ANALYSIS_FILE = ROOT / "logs" / "parallel_analysis_latest.json"
OUTPUT_FILE   = ROOT / "logs" / "params_suggestion.json"
CONFIG_OUTPUT_FILE = ROOT / "config" / "params_suggestion.json"
STATS_FILE    = ROOT / "logs" / "strategy_stats.ndjson"
TRADE_FILE    = ROOT / "logs" / "trading_ledger.ndjson"
RISK_FILE     = ROOT / "logs" / "risk_events.ndjson"

# ============ 从配置文件读取阈值 ============
CONFIG_FILE = ROOT / "config" / "strategy_rules_v3.json"

def _load_config():
    """从 strategy_rules_v3.json 读取所有阈值"""
    if not CONFIG_FILE.exists():
        return {}
    try:
        return json.loads(CONFIG_FILE.read_text(encoding="utf-8"))
    except:
        return {}

CONFIG = _load_config()
RULES  = CONFIG.get("risk_governor", {})
ENTRY  = CONFIG.get("entry_direction_filter", {})
POS    = CONFIG.get("position_sizing", {})
EXIT   = CONFIG.get("exit_strategy", {})

# 读取阈值（带默认值保护）
CONF_BASE          = RULES.get("min_confidence", 0.68)
CONF_RANGING_ADX  = RULES.get("min_confidence_ranging", 0.75)
CONF_LOSS3        = RULES.get("min_confidence_loss3", 0.78)
CONF_LOSS4        = RULES.get("min_confidence_loss4", 0.82)
MAX_LOTS_BASE     = POS.get("max_lots", 0.20)
MAX_LOTS_SIM      = POS.get("sim_stage_max", 0.10)
MAX_LOTS_LOSSRATE40 = POS.get("loss_rate_max_lots", 0.05)
CONSEC_LOSS_HALF  = POS.get("consecutive_loss_halve", 3)
CONSEC_LOSS_KILL  = RULES.get("max_consecutive_losses", 4)
SPREAD_KILL       = RULES.get("spread_kill", 70)
SPREAD_WIDE       = RULES.get("spread_wide_sl_expand", 50)
ATR_SL_WIDE       = RULES.get("sl_atr_multiplier_wide_spread", 1.8)
ATR_SL_BASE       = RULES.get("sl_atr_multiplier_normal", 1.5)
ATR_SL_WIDE_SPREAD = ATR_SL_WIDE   # alias
ATR_TP_BASE       = EXIT.get("tp_dynamic", {}).get("trend", {}).get("tp1_atr", 2.0)
ATR_TP_LONDON     = round(ATR_TP_BASE * 1.2, 2)   # 伦敦时段 TP 扩大 20%
ATR_TP_NYOPEN     = round(ATR_TP_BASE * 2.0, 2)   # 纽约开盘 TP 扩大 100%
BB_UPPER_BLOCK    = ENTRY.get("bb_upper_block", True)
BB_LOWER_BLOCK    = ENTRY.get("bb_lower_block", True)

# 趋势/震荡 RSI 阈值（分开定义）
TREND_RSI_LONG_BLOCK   = ENTRY.get("rsi_long_block_trending", 70)
TREND_RSI_SHORT_BLOCK  = ENTRY.get("rsi_short_block_trending", 30)
RANGE_RSI_LONG_BLOCK   = ENTRY.get("rsi_long_block_ranging", 70)
RANGE_RSI_SHORT_BLOCK  = ENTRY.get("rsi_short_block_ranging", 30)
TREND_ADX_THRESHOLD    = ENTRY.get("trend_adx_threshold", 25)

# 动态方向过滤（由策略进化控制）
TREND_LONG_FILTER  = ENTRY.get("trend_mode_long_filter", "ALLOW")
TREND_SHORT_FILTER = ENTRY.get("trend_mode_short_filter", "ALLOW")
RANGE_LONG_FILTER = ENTRY.get("range_mode_long_filter", "ALLOW")
RANGE_SHORT_FILTER = ENTRY.get("range_mode_short_filter", "ALLOW")
DISABLE_TREND_MODE = ENTRY.get("disable_trend_mode", False)
DISABLE_RANGE_MODE = ENTRY.get("disable_range_mode", False)

# ============ 辅助函数 ============

def _load_json(path):
    if not path.exists():
        return {}
    try:
        return json.loads(path.read_text(encoding="utf-8"))
    except:
        return {}

def _now():
    return datetime.now().strftime("%Y-%m-%dT%H:%M:%S+08:00")

def _read_ndjson(path):
    if not path.exists():
        return []
    try:
        return [json.loads(l) for l in path.read_text(encoding="utf-8").strip().split("\n") if l.strip()]
    except:
        return []

def _get(data, path, default=None):
    """安全读取嵌套字典，如 data.results.technical.indicators.rsi14"""
    keys = path.split(".")
    v = data
    for k in keys:
        if isinstance(v, dict):
            v = v.get(k)
        else:
            return default
        if v is None:
            return default
    return v

def _atr_from_stats():
    """从策略统计文件读取最近 ATR"""
    entries = _read_ndjson(STATS_FILE)
    if not entries:
        return None
    try:
        stats = entries[-1]
        return stats.get("atr", {})
    except:
        return None

# ============ 规则函数 ============

def rule_p01_confidence_threshold(tech, perf, risk):
    """
    P01: 置信度门槛
    触发: ADX < 25（震荡市场）→ 提高门槛
          连亏 ≥ 3 → 提高门槛
    """
    suggestions = []
    adx = _get(tech, "indicators.adx", 25)
    consec = _get(perf, "data.consecutive_losses", 0) or _get(risk, "data.consecutive_losses", 0)

    if consec >= 4:
        val, reason = CONF_LOSS4, f"连亏{consec}次，风险极大"
    elif consec >= 3:
        val, reason = CONF_LOSS3, f"连亏{consec}次，提高门槛"
    elif adx < 25:
        val, reason = CONF_RANGING_ADX, f"ADX={adx}，震荡市场假信号多"
    else:
        val, reason = CONF_BASE, f"ADX={adx}，趋势市场维持基础门槛"

    current = CONF_BASE
    if val != current:
        suggestions.append({
            "id": "P01",
            "param": "min_confidence_threshold",
            "category": "confidence",
            "current_value": current,
            "suggested_value": val,
            "reason": reason,
            "trigger_evidence": {"adx": adx, "consecutive_losses": consec},
            "priority": "HIGH" if consec >= 3 or adx < 25 else "LOW",
            "auto_apply": False,
            "m2_review_required": consec < 3 and adx < 25,
        })
    return suggestions

def rule_p02_max_lots(perf, risk, analysis):
    """
    P02: 最大仓位
    触发: 模拟盘阶段（无真实交易）→ 降半
          胜率 < 40% → 降至 0.05
          连亏 ≥ 3 → 减半
    """
    suggestions = []
    total_trades = _get(perf, "data.total_trades", 0)
    win_rate = _get(perf, "data.win_rate", 100)
    consec = _get(perf, "data.consecutive_losses", 0) or _get(risk, "data.consecutive_losses", 0)
    position_open = _get(risk, "data.position_open", False)

    # 基础值
    if total_trades == 0:
        val, reason = MAX_LOTS_SIM, f"模拟盘阶段（0真实交易）"
    elif win_rate < 40:
        val, reason = MAX_LOTS_LOSSRATE40, f"胜率{win_rate}%低于40%，大幅降仓"
    elif consec >= 3:
        val, reason = round(MAX_LOTS_BASE * 0.5, 2), f"连亏{consec}次，减半仓"
    else:
        return []  # 无需调整

    current = MAX_LOTS_BASE
    if val < current:
        suggestions.append({
            "id": "P02",
            "param": "max_position_size",
            "category": "risk",
            "current_value": current,
            "suggested_value": val,
            "reason": reason,
            "trigger_evidence": {
                "total_trades": total_trades,
                "win_rate": win_rate,
                "consecutive_losses": consec,
            },
            "priority": "HIGH",
            "auto_apply": False,
            "m2_review_required": False,
        })
    return suggestions

def rule_p03_rsi_direction_filter(tech, perf):
    """
    P03: RSI 方向过滤 — 趋势/震荡逻辑完全不同
    阈值从 strategy_rules_v3.json 读取
    """
    suggestions = []
    rsi = _get(tech, "indicators.rsi14", 50)
    adx = _get(tech, "indicators.adx", 25)
    regime = normalize_regime_label(_get(tech, "indicators.regime", "WEAK_TREND"))
    is_trending = is_trend_regime(regime) or (adx >= TREND_ADX_THRESHOLD)
    market_label = "WEAK_TREND" if is_trending else "RANGING"

    if is_trending:
        if rsi > TREND_RSI_LONG_BLOCK and TREND_SHORT_FILTER == "ALLOW":
            suggestions.append({
                "id": "P03_SHORT_TREND",
                "param": "trend_mode_short_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"趋势市场 RSI={rsi}>{TREND_RSI_LONG_BLOCK} 强势，阻止做空（不要逆势）",
                "trigger_evidence": {"rsi14": rsi, "adx": adx, "regime": regime, "market": market_label, "threshold": TREND_RSI_LONG_BLOCK},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
        elif rsi < TREND_RSI_SHORT_BLOCK:
            suggestions.append({
                "id": "P03_LONG_TREND",
                "param": "trend_mode_long_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"趋势市场 RSI={rsi}<{TREND_RSI_SHORT_BLOCK} 弱势，阻止做多（不要逆势）",
                "trigger_evidence": {"rsi14": rsi, "adx": adx, "regime": regime, "market": market_label, "threshold": TREND_RSI_SHORT_BLOCK},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
    else:
        if rsi > RANGE_RSI_LONG_BLOCK:
            suggestions.append({
                "id": "P03_LONG_RANGE",
                "param": "trend_mode_long_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"震荡市场 RSI={rsi}>{RANGE_RSI_LONG_BLOCK} 价格偏高，阻止做多（均值回归）",
                "trigger_evidence": {"rsi14": rsi, "adx": adx, "regime": regime, "market": market_label, "threshold": RANGE_RSI_LONG_BLOCK},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
        elif rsi < RANGE_RSI_SHORT_BLOCK:
            suggestions.append({
                "id": "P03_SHORT_RANGE",
                "param": "trend_mode_short_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"震荡市场 RSI={rsi}<{RANGE_RSI_SHORT_BLOCK} 价格偏低，阻止做空（均值回归）",
                "trigger_evidence": {"rsi14": rsi, "adx": adx, "regime": regime, "market": market_label, "threshold": RANGE_RSI_SHORT_BLOCK},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
    return suggestions

def rule_p04_bbands_filter(tech):
    """
    P04: 布林带过滤 — 趋势/震荡逻辑完全不同
    阈值从 strategy_rules_v3.json 读取
    """
    suggestions = []
    close = _get(tech, "indicators.close", 0)
    bb_upper = _get(tech, "indicators.bb_upper", 0)
    bb_lower = _get(tech, "indicators.bb_lower", 0)
    adx = _get(tech, "indicators.adx", 25)
    regime = normalize_regime_label(_get(tech, "indicators.regime", "WEAK_TREND"))
    is_trending = is_trend_regime(regime) or (adx >= TREND_ADX_THRESHOLD)
    market_label = "WEAK_TREND" if is_trending else "RANGING"

    if not all([close, bb_upper, bb_lower]):
        return suggestions

    if is_trending:
        if close >= bb_upper and BB_UPPER_BLOCK:
            suggestions.append({
                "id": "P04_SHORT_TREND",
                "param": "trend_mode_short_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"趋势市场 价格{close}≥布林上轨{bb_upper:.3f}，趋势延续，阻止做空",
                "trigger_evidence": {"close": close, "bb_upper": bb_upper, "bb_lower": bb_lower, "adx": adx, "regime": regime, "market": market_label},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
        elif close <= bb_lower and BB_LOWER_BLOCK:
            suggestions.append({
                "id": "P04_LONG_TREND",
                "param": "trend_mode_long_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"趋势市场 价格{close}≤布林下轨{bb_lower:.3f}，趋势延续，阻止做多",
                "trigger_evidence": {"close": close, "bb_upper": bb_upper, "bb_lower": bb_lower, "adx": adx, "regime": regime, "market": market_label},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
    else:
        if close >= bb_upper and BB_UPPER_BLOCK:
            suggestions.append({
                "id": "P04_LONG_RANGE",
                "param": "trend_mode_long_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"震荡市场 价格{close}≥布林上轨{bb_upper:.3f}，价格极值，阻止做多（均值回归）",
                "trigger_evidence": {"close": close, "bb_upper": bb_upper, "bb_lower": bb_lower, "adx": adx, "regime": regime, "market": market_label},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
        elif close <= bb_lower and BB_LOWER_BLOCK:
            suggestions.append({
                "id": "P04_SHORT_RANGE",
                "param": "trend_mode_short_filter",
                "category": "direction",
                "current_value": "ALLOW",
                "suggested_value": "BLOCK",
                "reason": f"震荡市场 价格{close}≤布林下轨{bb_lower:.3f}，价格极值，阻止做空（均值回归）",
                "trigger_evidence": {"close": close, "bb_upper": bb_upper, "bb_lower": bb_lower, "adx": adx, "regime": regime, "market": market_label},
                "priority": "HIGH",
                "auto_apply": False,
                "m2_review_required": False,
            })
    return suggestions

def rule_p05_spread_sl_adjustment(risk):
    """
    P05: 点差过大调整止损
    触发: 点差 > 50 → SL 扩大 20%
          点差 > 70 → 全局阻止开仓
    """
    suggestions = []
    spread_raw = _get(risk, "data.spread", 0)
    try:
        spread = float(spread_raw)
    except (TypeError, ValueError):
        spread = 0

    if spread > SPREAD_KILL:
        suggestions.append({
            "id": "P05_KILL",
            "param": "kill_switch",
            "category": "risk",
            "current_value": False,
            "suggested_value": True,
            "reason": f"点差{spread} > {SPREAD_KILL}，极端市场，全局阻止开仓",
            "trigger_evidence": {"spread": spread, "threshold": SPREAD_KILL},
            "priority": "CRITICAL",
            "auto_apply": False,
            "m2_review_required": False,
        })
    elif spread > 50:
        suggestions.append({
            "id": "P05_SL",
            "param": "sl_atr_multiplier",
            "category": "exit",
            "current_value": ATR_SL_BASE,
            "suggested_value": ATR_SL_WIDE_SPREAD,
            "reason": f"点差{spread} > 50，扩大止损防扫损",
            "trigger_evidence": {"spread": spread, "threshold": 50},
            "priority": "HIGH",
            "auto_apply": False,
            "m2_review_required": False,
        })

    return suggestions

def rule_p06_consecutive_loss(risk):
    """
    P06: 连亏处理
    触发: 连亏 ≥ 3 → 半仓
          连亏 ≥ 4 → Kill Switch
    """
    suggestions = []
    consec = _get(risk, "data.consecutive_losses", 0)

    if consec >= CONSEC_LOSS_KILL:
        suggestions.append({
            "id": "P06_KILL",
            "param": "kill_switch",
            "category": "risk",
            "current_value": False,
            "suggested_value": True,
            "reason": f"连亏{consec}次 ≥ {CONSEC_LOSS_KILL}，触发Kill Switch",
            "trigger_evidence": {"consecutive_losses": consec, "threshold": CONSEC_LOSS_KILL},
            "priority": "CRITICAL",
            "auto_apply": False,
            "m2_review_required": False,
        })
    elif consec >= CONSEC_LOSS_HALF:
        suggestions.append({
            "id": "P06_HALF",
            "param": "max_position_size",
            "category": "risk",
            "current_value": MAX_LOTS_BASE,
            "suggested_value": round(MAX_LOTS_BASE * 0.5, 2),
            "reason": f"连亏{consec}次 ≥ {CONSEC_LOSS_HALF}，减半仓",
            "trigger_evidence": {"consecutive_losses": consec, "threshold": CONSEC_LOSS_HALF},
            "priority": "HIGH",
            "auto_apply": False,
            "m2_review_required": False,
        })

    return suggestions

def rule_p07_session_tp_adjustment(sess):
    """
    P07: 时段调整 TP 目标
    触发: 伦敦时段 → TP × 1.2
          纽约开盘 → TP × 2.0
    """
    suggestions = []
    session = _get(sess, "data.current_session", "ASIA")

    if session == "LONDON":
        suggestions.append({
            "id": "P07_TP",
            "param": "tp_atr_multiplier",
            "category": "exit",
            "current_value": ATR_TP_BASE,
            "suggested_value": ATR_TP_LONDON,
            "reason": f"伦敦时段，趋势动能强，扩大止盈",
            "trigger_evidence": {"session": session},
            "priority": "MEDIUM",
            "auto_apply": False,
            "m2_review_required": True,
        })
    elif session == "NEW_YORK":
        suggestions.append({
            "id": "P07_TP_NY",
            "param": "tp_atr_multiplier",
            "category": "exit",
            "current_value": ATR_TP_BASE,
            "suggested_value": ATR_TP_NYOPEN,
            "reason": f"纽约时段，波动最大，扩大止盈目标",
            "trigger_evidence": {"session": session},
            "priority": "MEDIUM",
            "auto_apply": False,
            "m2_review_required": True,
        })

    return suggestions

def rule_p08_regime_confidence(tech):
    """
    P08: 格局不确定降低置信度
    触发: defensive regime → 额外降置信度 0.05
    """
    suggestions = []
    regime = normalize_regime_label(_get(tech, "indicators.regime", "WEAK_TREND"))
    conf = _get(tech, "indicators.confidence", CONF_BASE)

    if is_defensive_regime(regime):
        suggestions.append({
            "id": "P08",
            "param": "min_confidence_threshold",
            "category": "confidence",
            "current_value": CONF_BASE,
            "suggested_value": round(CONF_BASE + 0.05, 2),
            "reason": f"格局{regime}偏防御，ADX={_get(tech,'indicators.adx',0):.1f}，提高门槛防假信号",
            "trigger_evidence": {"regime": regime, "adx": _get(tech, "indicators.adx", 0)},
            "priority": "MEDIUM",
            "auto_apply": False,
            "m2_review_required": False,
        })

    return suggestions

# ============ 主逻辑 ============

def generate_suggestions():
    """读取分析结果，生成参数建议"""
    analysis = _load_json(ANALYSIS_FILE)
    if not analysis or "results" not in analysis:
        return None

    results = analysis.get("results", {})
    tech   = results.get("technical", {})
    sess   = results.get("sessions", {})
    perf   = results.get("performance", {})
    risk   = results.get("position_risk", {})
    forecast = results.get("forecast", {})

    all_suggestions = []

    # 按优先级顺序执行规则
    all_suggestions += rule_p05_spread_sl_adjustment(risk)   # 先检查点差（最紧急）
    all_suggestions += rule_p06_consecutive_loss(risk)      # 连亏处理
    all_suggestions += rule_p03_rsi_direction_filter(tech, perf)  # RSI 过滤
    all_suggestions += rule_p04_bbands_filter(tech)          # 布林带过滤
    all_suggestions += rule_p01_confidence_threshold(tech, perf, risk)  # 置信度门槛
    all_suggestions += rule_p02_max_lots(perf, risk, analysis)  # 仓位
    all_suggestions += rule_p07_session_tp_adjustment(sess)   # 时段 TP
    all_suggestions += rule_p08_regime_confidence(tech)       # 格局置信度

    # 去重（同类取最高优先级）
    seen = {}
    for s in sorted(all_suggestions, key=lambda x: {"CRITICAL":0,"HIGH":1,"MEDIUM":2,"LOW":3}[x["priority"]]):
        param = s["param"]
        if param not in seen or seen[param]["priority"] > s["priority"]:
            seen[param] = s
    final_suggestions = list(seen.values())

    # 分类统计
    high = [s for s in final_suggestions if s["priority"] == "HIGH"]
    medium = [s for s in final_suggestions if s["priority"] == "MEDIUM"]
    low = [s for s in final_suggestions if s["priority"] == "LOW"]
    critical = [s for s in final_suggestions if s["priority"] == "CRITICAL"]
    auto = [s for s in final_suggestions if s["auto_apply"]]
    m2_review = [s for s in final_suggestions if s.get("m2_review_required")]

    report = {
        "timestamp": _now(),
        "source": "params_advisor.py",
        "based_on": str(ANALYSIS_FILE),
        "suggestions": final_suggestions,
        "summary": {
            "total": len(final_suggestions),
            "critical": len(critical),
            "high": len(high),
            "medium": len(medium),
            "low": len(low),
            "auto_apply": len(auto),
            "m2_review_required": len(m2_review),
        },
        "rules_applied": [
            "P01_confidence_threshold",
            "P02_max_lots",
            "P03_rsi_direction_filter",
            "P04_bbands_filter",
            "P05_spread_sl_adjustment",
            "P06_consecutive_loss",
            "P07_session_tp_adjustment",
            "P08_regime_confidence",
        ],
    }

    payload_text = json.dumps(report, indent=2, ensure_ascii=False)
    OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
    OUTPUT_FILE.write_text(payload_text, encoding="utf-8")
    CONFIG_OUTPUT_FILE.parent.mkdir(parents=True, exist_ok=True)
    CONFIG_OUTPUT_FILE.write_text(payload_text, encoding="utf-8")
    return report

def main():
    print(f"[params_advisor] 生成参数建议...")
    report = generate_suggestions()
    if report is None:
        print("[params_advisor] 错误: 无法读取 analysis 文件")
        sys.exit(1)

    s = report["summary"]
    print(f"[params_advisor] 完成: 共{ s['total']}条建议 "
          f"(CRITICAL={s['critical']} HIGH={s['high']} MEDIUM={s['medium']} LOW={s['low']})")
    print(f"[params_advisor] 自动应用: {s['auto_apply']}条 | 需M2审批: {s['m2_review_required']}条")
    print(f"[params_advisor] 输出: {OUTPUT_FILE}")
    print(f"[params_advisor] 同步配置: {CONFIG_OUTPUT_FILE}")

    for sug in report["suggestions"]:
        auto_tag = "[OK]自动" if sug["auto_apply"] else ("[WARN]️M2审批" if sug.get("m2_review_required") else "[LIST]报告")
        print(f"  [{auto_tag}] {sug['id']} {sug['param']}: "
              f"{sug['current_value']} → {sug['suggested_value']} | {sug['reason'][:50]}")

if __name__ == "__main__":
    main()
