#!/usr/bin/env python3
"""stock_profile.py — 个股综合画像系统

将宏观、行业、基本面、技术面四维数据整合为一只股票的完整画像。
输出：分类（短炒/中短线/中长线/深套）、综合评分、操作建议基调。
"""

import json
import os
import urllib.request
from datetime import datetime
from typing import Optional

DATA_DIR = "/home/hmo/web-dashboard/data"
MTF_CACHE_PATH = os.path.join(DATA_DIR, "multi_tf_cache.json")
MACRO_PATH = os.path.join(DATA_DIR, "macro_context.json")
PORTFOLIO_PATH = os.path.join(DATA_DIR, "portfolio.json")

# 腾讯API字段索引（quote批量接口）
F = {
    "name": 1, "code": 2, "price": 3, "close_yest": 4, "open": 5,
    "volume": 6, "outer_vol": 7, "inner_vol": 8,
    "timestamp": 30, "change": 31, "change_pct": 32,
    "high": 33, "low": 34,
    "turnover": 37, "turnover_rate": 38, "pe": 39,
    "high_limit": 41, "low_limit": 42, "amplitude": 43,
    "market_cap_流通": 44, "market_cap_总": 45, "pb": 46,
    "ep": 47, "es": 48, "eps": 49,
    "avg_price": 51,
    "sector_tag": 60, "sector": 61,
    "high_52w": 67, "low_52w": 68,
}

# 港股字段偏移不同
F_HK = {
    "name": 1, "code": 2, "price": 3, "close_yest": 4,
    "change": 31, "change_pct": 32,
    "high": 33, "low": 34, "high_limit": 48, "low_limit": 49,
    "pe": 57, "pb": 58, "eps": 72, "market_cap_总": 45,
    "turnover": 37,
}


def get_quote(code: str) -> dict:
    """获取腾讯API实时行情+基本面"""
    raw = str(code).split("_")[0]
    if len(raw) == 5 and raw.isdigit():
        prefix = "hk"
        fields = F_HK
    elif raw.startswith("6") or raw.startswith("5"):
        prefix = "sh"
        fields = F
    else:
        prefix = "sz"
        fields = F

    url = f"http://qt.gtimg.cn/q={prefix}{raw}"
    try:
        req = urllib.request.Request(url, headers={
            "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36"
        })
        with urllib.request.urlopen(req, timeout=5) as resp:
            raw_text = resp.read().decode("gbk")
        fields_raw = raw_text.split('"')[1].split("~")
    except Exception as e:
        return {"code": code, "error": str(e)}

    def get(idx):
        try:
            v = fields_raw[idx].strip()
            return float(v) if v else None
        except (IndexError, ValueError):
            return None

    def get_str(idx):
        try:
            return fields_raw[idx].strip()
        except IndexError:
            return ""

    is_hk = prefix == "hk"

    result = {
        "code": raw,
        "name": get_str(fields["name"]),
        "price": get(fields["price"]),
        "change_pct": get(fields["change_pct"]),
        "high": get(fields["high"]),
        "low": get(fields["low"]),
        "pe": get(fields["pe"]),
        "pb": get(fields["pb"]),
        "eps": get(fields["eps"]),
    }

    if is_hk:
        result["market_cap"] = get(fields["market_cap_总"])
        result["high_52w"] = get(fields["high_limit"])
        result["low_52w"] = get(fields["low_limit"])
    else:
        result["market_cap"] = get(fields["market_cap_总"])
        result["market_cap_流通"] = get(fields["market_cap_流通"])
        result["high_52w"] = get(fields["high_52w"])
        result["low_52w"] = get(fields["low_52w"])
        result["turnover_rate"] = get(fields["turnover_rate"])
        result["amplitude"] = get(fields["amplitude"])
        result["sector"] = get_str(fields["sector"])
        result["outer_vol"] = get(fields["outer_vol"])
        result["inner_vol"] = get(fields["inner_vol"])

    return result


def load_mtf_cache() -> dict:
    try:
        with open(MTF_CACHE_PATH) as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}


def load_macro() -> dict:
    """加载宏观上下文"""
    try:
        with open(MACRO_PATH) as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {}


def get_fundamental_rating(f: dict) -> dict:
    """基本面评分"""
    pe = f.get("pe")
    pb = f.get("pb")
    eps = f.get("eps")
    mcap = f.get("market_cap")
    high_52w = f.get("high_52w")
    low_52w = f.get("low_52w")
    price = f.get("price")
    chg = f.get("change_pct")

    score = 50  # 基准分
    signals = []
    details = {}

    # PE评估
    if pe and pe > 0:
        details["pe"] = round(pe, 2)
        if pe < 15:
            score += 15
            signals.append("PE低估值")
        elif pe < 30:
            score += 8
            signals.append("PE合理")
        elif pe < 60:
            score += 0
            signals.append("PE偏高")
        else:
            score -= 10
            signals.append("PE>60高估")
    elif pe and pe < 0:
        details["pe"] = round(pe, 2)
        score -= 5
        signals.append("PE为负(亏损)")

    # PB评估
    if pb and pb > 0:
        details["pb"] = round(pb, 2)
        if pb < 1.5:
            score += 10
            signals.append("PB低")
        elif pb < 3:
            score += 5
            signals.append("PB合理")
        elif pb < 8:
            score += 0
        else:
            score -= 5
            signals.append("PB>8偏高")
    elif pb and pb < 0:
        score -= 5

    # EPS评估
    if eps:
        details["eps"] = round(eps, 2)
        if eps > 2:
            score += 10
            signals.append("EPS优秀>2")
        elif eps > 0.5:
            score += 5
            signals.append("EPS良好")
        elif eps > 0:
            score += 2
        else:
            score -= 5
            signals.append("EPS为负")

    # 52周位置
    if high_52w and low_52w and price and high_52w > low_52w:
        position = (price - low_52w) / (high_52w - low_52w)
        details["52w_position"] = round(position, 2)
        if position < 0.2:
            score += 10
            signals.append("近52周低位")
        elif position < 0.4:
            score += 5
            signals.append("52周偏低")
        elif position > 0.8:
            score -= 5
            signals.append("近52周高位")
        elif position > 0.95:
            score -= 10
            signals.append("52周顶部区域")

    # 市值评估（大盘股加分）
    if mcap and mcap > 0:
        details["market_cap_亿"] = round(mcap, 0)
        if mcap > 1000:
            score += 5
            signals.append("大盘股")
        elif mcap > 100:
            score += 0
        else:
            score -= 5
            signals.append("小盘股")

    return {
        "score": max(0, min(100, score)),
        "signals": signals,
        "details": details,
    }


def classify_stock(f: dict, mtf: dict, macro: dict) -> dict:
    """股票分类：短炒 / 中短线 / 中长线 / 深套持有"""
    price = f.get("price", 0)
    pe = f.get("pe") or 0
    chg = f.get("change_pct", 0)
    eps = f.get("eps") or 0

    # 多周期趋势
    mtf_adj = mtf.get("strategy_adjustment", {})
    trend_align = mtf_adj.get("trend_alignment", "未知")
    mtf_daily = mtf.get("daily", {})
    mtf_trend = mtf_daily.get("trend", {})
    mas = mtf_daily.get("mas", {})
    ma20 = mas.get("ma20") or 0
    ma60 = mas.get("ma60") or 0

    # 基本面得分
    fund_rating = get_fundamental_rating(f)
    fund_score = fund_rating["score"]

    # 短期涨幅判断（近20日）
    mtf_sr = mtf_daily.get("support_resistance", {})
    high_20d = mtf_sr.get("high_52w", price)  # 近20日最高
    low_20d = mtf_sr.get("low_52w", price)    # 近20日最低
    recent_volatility = ((high_20d - low_20d) / low_20d * 100) if low_20d > 0 else 0

    # ---- 分类逻辑 ----
    category = "中短线"
    reason = []
    position_suggestion = ""
    time_horizon = ""

    # 1. 深套检查
    cost = 0
    try:
        pf = json.load(open(PORTFOLIO_PATH))
        for h in pf.get("holdings", []):
            if h.get("code") == f.get("code"):
                cost = h.get("cost", 0) or 0
                break
    except Exception:
        pass

    if cost > 0 and price > 0:
        profit_pct = (price - cost) / cost * 100
    else:
        profit_pct = 0

    if profit_pct < -20:
        category = "深套持有"
        reason.append(f"浮亏{profit_pct:.0f}%")
        position_suggestion = "不补不割，等趋势反转"
        time_horizon = "长期"
        return {
            "category": category,
            "reasons": reason,
            "fundamental_score": fund_score,
            "position_suggestion": position_suggestion,
            "time_horizon": time_horizon,
            "volatility_20d": round(recent_volatility, 1),
        }

    # 2. 短线爆发判断
    # 特征：近20日振幅大(>30%)、涨幅大、PE可能极高或为负、换手率高
    recent_chg = chg or 0
    is_high_volatility = recent_volatility > 30
    is_momentum = is_high_volatility and (pe > 100 or pe < 0)
    is_turnover_high = f.get("turnover_rate", 0) or 0 > 10 if f.get("turnover_rate") else False

    if is_momentum or (is_high_volatility and is_turnover_high):
        category = "短炒"
        if is_momentum:
            reason.append("高波动+高PE题材驱动")
        if is_turnover_high:
            reason.append(f"换手率{f.get('turnover_rate',0):.1f}%活跃")
        position_suggestion = "小仓位快进快出，止损严格"
        time_horizon = "数日~2周"
    # 3. 中长线判断
    # 特征：基本面好(PE合理<30、EPS>1、大盘股)、多周期看多、行业向好
    elif (fund_score >= 65 and pe < 30 and eps > 0.5) or \
         (trend_align == "多周期看多" and fund_score >= 55 and ma20 > 0 and price > ma20):
        category = "中长线"
        if fund_score >= 65:
            reason.append(f"基本面良好({fund_score}分)")
        if trend_align == "多周期看多":
            reason.append("多周期共振看多")
        if pe and pe < 20:
            reason.append(f"PE{pe:.0f}低估")
        position_suggestion = "正常仓位配置，趋势不破不走"
        time_horizon = "数月~1年"
    # 4. 中短线（默认）
    else:
        category = "中短线"
        if fund_score >= 50:
            reason.append("基本面中等")
        else:
            reason.append(f"基本面偏弱({fund_score}分)")
        if trend_align in ("震荡/无明显方向", "多周期分化"):
            reason.append("方向不明")
        position_suggestion = "中等仓位，技术面操作为主"
        time_horizon = "2周~3月"

    return {
        "category": category,
        "reasons": reason,
        "fundamental_score": fund_score,
        "position_suggestion": position_suggestion,
        "time_horizon": time_horizon,
        "volatility_20d": round(recent_volatility, 1),
    }


def full_profile(code: str) -> dict:
    """完整个股画像"""
    # 获取实时行情+基本面
    quote = get_quote(code)
    if "error" in quote:
        return {"code": code, "error": quote["error"]}

    # 多周期技术面
    from multi_timeframe import full_multi_tf_analysis
    mtf = full_multi_tf_analysis(code)

    # 宏观环境
    macro = load_macro()

    # 基本面评分
    fund_rating = get_fundamental_rating(quote)

    # 股票分类
    classification = classify_stock(quote, mtf, macro)

    # 综合评分（四维加权）
    # 技术面得分: 从multi_timeframe的趋势判断中提取
    tech_score = 50
    mtf_daily = mtf.get("daily", {})
    mtf_trend = mtf_daily.get("trend", {})
    if mtf_trend.get("trend") == "up":
        tech_score = 70
    elif mtf_trend.get("trend") == "strong_up":
        tech_score = 85
    elif mtf_trend.get("trend") == "down":
        tech_score = 30
    elif mtf_trend.get("trend") == "strong_down":
        tech_score = 15
    elif mtf_trend.get("trend") == "sideways":
        tech_score = 50

    # 行业得分（从宏观读取该行业表现）
    sector_score = 50
    macro_structure = macro.get("structure", {})
    sector_map = {
        "芯片": ["688981", "00981", "300548"],
        "信息技术": ["00700", "300124"],
        "新能源电池": ["300750", "300035", "600110"],
        "机器人": ["300124"],
        "周期": ["601899", "600739"],
        "蓝筹": ["600036", "02318", "00700"],
    }
    # 找该股票所属行业
    for sector_name, sector_codes in sector_map.items():
        if code in sector_codes or any(code.startswith(c[:2]) for c in sector_codes):
            sector_data = macro_structure.get(sector_name)
            if sector_data:
                chg_val = sector_data if isinstance(sector_data, (int, float)) else \
                          float(str(sector_data).replace("%", "").replace("+", "")) if sector_data else 0
                if chg_val > 1:
                    sector_score = 70
                elif chg_val < -1:
                    sector_score = 30
                else:
                    sector_score = 50
            break

    # 宏观得分（整体市场情绪）
    macro_score = 50
    overall = macro_structure.get("overall", "neutral")
    if overall == "strong_bullish":
        macro_score = 80
    elif overall == "bullish":
        macro_score = 65
    elif overall == "bearish":
        macro_score = 35
    elif overall == "strong_bearish":
        macro_score = 20

    # 综合评分 = 基本面30% + 技术面30% + 行业20% + 宏观20%
    composite = round(
        fund_rating["score"] * 0.30 +
        tech_score * 0.30 +
        sector_score * 0.20 +
        macro_score * 0.20
    )

    return {
        "code": code,
        "name": quote.get("name", ""),
        "price": quote.get("price"),
        "change_pct": quote.get("change_pct"),
        "fundamentals": {
            "pe": quote.get("pe"),
            "pb": quote.get("pb"),
            "eps": quote.get("eps"),
            "market_cap": quote.get("market_cap"),
            "high_52w": quote.get("high_52w"),
            "low_52w": quote.get("low_52w"),
            "rating": fund_rating,
        },
        "classification": classification,
        "multi_timeframe": {
            "daily_ma_trend": mtf.get("daily", {}).get("trend", {}).get("ma_trend", ""),
            "weekly_trend": mtf.get("weekly", {}).get("trend", {}).get("description", ""),
            "monthly_trend": mtf.get("monthly", {}).get("trend", {}).get("description", ""),
            "trend_alignment": mtf.get("strategy_adjustment", {}).get("trend_alignment", ""),
            "ma20": mtf.get("daily", {}).get("mas", {}).get("ma20"),
            "ma60": mtf.get("daily", {}).get("mas", {}).get("ma60"),
        },
        "scoring": {
            "fundamental": fund_rating["score"],
            "technical": tech_score,
            "sector": sector_score,
            "macro": macro_score,
            "composite": composite,
        },
        "strategy_note": (
            f"{classification['category']} | "
            f"综合{composite}分(基本{fund_rating['score']}/技术{tech_score}/行业{sector_score}/宏观{macro_score}) | "
            f"{classification['position_suggestion']}"
        ),
    }


if __name__ == "__main__":
    import sys
    codes = sys.argv[1:] or ["300548", "600110", "600036"]
    for code in codes:
        p = full_profile(code)
        print(json.dumps(p, ensure_ascii=False, indent=2))
        print("-" * 60)
