#!/usr/bin/env python3
"""batch_reassess.py — 批量补全九维分析（逐只处理，间隔防限流）

用法: python3 batch_reassess.py [--all] [--code XXXXXX]

流程：收集最新数据 → 调LLM(gateway)写九维分析+策略 → 保存到DB
"""
import sys, json, subprocess, sqlite3, re, time
from datetime import datetime

DB = "/home/hmo/MoFin/data/mofin.db"
GATEWAY = "http://127.0.0.1:8643/v1/chat/completions"
COOLDOWN_HOURS = 1

def has_llm_analysis(code):
    """检查是否为LLM生成的九维分析（>500字）"""
    conn = sqlite3.connect(DB)
    r = conn.execute("SELECT LENGTH(full_analysis) FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
    conn.close()
    return r and r[0] and r[0] > 500

def in_cooldown(code):
    """冷却期检查"""
    conn = sqlite3.connect(DB)
    r = conn.execute("SELECT reassessed_at FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
    conn.close()
    if not r or not r[0]:
        return False
    try:
        last = datetime.fromisoformat(r[0])
        diff = (datetime.now() - last).total_seconds() / 3600
        return diff < COOLDOWN_HOURS
    except:
        return False

def collect_data(code):
    """收集最新数据"""
    data = {"code": code}
    
    # 从DB读策略
    conn = sqlite3.connect(DB)
    r = conn.execute("SELECT name, entry_low, entry_high, stop_loss, take_profit, timing_signal, action, rr_ratio, tech_snapshot, sector_context, stock_category FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
    if r:
        data["name"] = r[0]
        data["entry_low"] = r[1] or 0
        data["entry_high"] = r[2] or 0
        data["stop_loss"] = r[3] or 0
        data["take_profit"] = r[4] or 0
        data["timing_signal"] = r[5] or ""
        data["action"] = r[6] or ""
        data["rr_ratio"] = r[7] or 0
        data["tech_snapshot"] = r[8] or ""
        data["sector_context"] = r[9] or ""
        data["stock_category"] = r[10] or ""
    conn.close()
    
    # 从腾讯API拉最新价和基本面
    prefix = "sh" if str(code).startswith(("6","9")) else "sz"
    try:
        r = subprocess.run(["curl", "-s", f"http://qt.gtimg.cn/q={prefix}{code}"], capture_output=True, timeout=10)
        parts = r.stdout.decode("gbk", errors="ignore").split("~")
        data["price"] = float(parts[3]) if len(parts) > 3 and parts[3] else 0
        data["pe"] = parts[39] if len(parts) > 39 and parts[39] else ""
        data["mcap"] = parts[44] if len(parts) > 44 and parts[44] else ""
        data["change_pct"] = parts[32] if len(parts) > 32 and parts[32] else "0"
    except:
        data["price"] = 0
    
    # 大盘
    try:
        conn = sqlite3.connect(DB)
        mr = conn.execute("SELECT structure FROM macro_context_log ORDER BY id DESC LIMIT 1").fetchone()
        if mr and mr[0]:
            s = json.loads(mr[0])
            data["macro"] = s.get("description", "大盘震荡")
        conn.close()
    except:
        data["macro"] = "大盘震荡"
    
    return data

def build_prompt(data):
    """构建LLM prompt，要求输出完整策略"""
    cash = 321271  # 可用现金（从DB读取）
    total = 952879  # 总资产
    
    # 拉取资金流数据
    _flow_note = "暂无资金流数据"
    try:
        import sqlite3 as _sq, json as _j
        _db = _sq.connect("/home/hmo/MoFin/data/mofin.db")
        _fr = _db.execute("SELECT cache_json FROM capital_flow_cache ORDER BY id DESC LIMIT 1").fetchone()
        if _fr and _fr[0]:
            _fc = _j.loads(_fr[0])
            _stocks = _fc.get("stocks", {})
            _s = _stocks.get(data['code'], {})
            if _s and _s.get("analysis"):
                _a = _s["analysis"]
                _net = _a.get("net_flow", 0)
                _main = _a.get("main_force", 0)
                _retail = _a.get("retail_flow", 0)
                _trend = _a.get("trend", "中性")
                _flow_note = f"净流入{_net:.0f}万 主力{_main:.0f}万 散户{_retail:.0f}万 趋势{_trend}"
        _db.close()
    except:
        pass
    
    # 拉取近期消息面
    _news_note = "暂无近期消息"
    try:
        import sqlite3 as _sq
        _db = _sq.connect("/home/hmo/MoFin/data/mofin.db")
        _nr = _db.execute(
            "SELECT summary, overall_sentiment, created_at FROM signal_news "
            "WHERE (code=? OR sector LIKE ?) AND overall_sentiment IN ('利好','利空') "
            "ORDER BY id DESC LIMIT 3",
            (data['code'], f'%{data.get("name","")[:4]}%')
        ).fetchall()
        if _nr:
            _news_note = " | ".join([f"{r[2][:10]} {r[1]} {r[0][:40]}" for r in _nr])
        _db.close()
    except:
        pass
    
    return f"""你是一个资深A股分析师。请对{data['code']} {data.get('name','')}做一个完整的九维矩阵分析，并输出策略参数。

⚠️ 重要：以下9个维度不是独立分析的，你必须交叉对比后给出综合结论。
例如：如果消息面利好但资金流在流出，说明利好可能是出货；如果基本面强但技术面破位，说明估值可能还没到底。

当前数据（以下数据均来自实时API，每条标注时间窗口，禁止使用模型内部训练数据）：
大盘：{data.get('macro','震荡')}（当日实时）
最新价：{data.get('price',0)}  涨跌：{data.get('change_pct','0')}%（当日实时）
PE={data.get('pe','?')}（最新财报）  市值={data.get('mcap','?')}亿
行业：{data.get('sector_context','?')}（当日实时）
技术面：{data.get('tech_snapshot','')[:300]}（MA=5/10/20/60日 支撑阻力=近20日 量价=当日+近5日趋势）
资金流：{_flow_note}（近5日累计）
消息面：{_news_note}（最近3条，自动标注抓取时间）
当前信号：{data.get('timing_signal','?')}  分类：{data.get('stock_category','?')}
原策略：{(data.get('action','') or '')[:200]}

我的总资产={total}元，可用现金={cash}元。

请严格按以下格式输出：

【交叉分析】用2-3句话说明哪些维度出现矛盾/共振，最关键的信号是什么
① 大盘×基本面 [一句话，说明矛盾关系]
② 大盘×消息面 [一句话]
③ 大盘×技术面 [一句话]
④ 大盘×资金面 [一句话]
⑤ 行业×基本面 [一句话]
⑥ 行业×消息面 [一句话]
⑦ 行业×技术面 [一句话]
⑧ 行业×资金面 [一句话]
⑨ 个股×基本面 [一句话]
⑩ 个股×消息面 [一句话]
⑪ 个股×技术面 [一句话]
⑫ 个股×资金面 [一句话]

【综合结论】（买入/关注/观望/卖出）
【操作建议】具体操作建议
【买入区间】最低价~最高价
【建议止损】数字
【建议止盈】数字

【建议仓位】只有综合结论为"买入"时才输出此项。仓位计算公式：
基础仓位按RR确定：RR<1.5→不推荐，RR1.5~3→8%，RR3~5→12%，RR5+→15%
大盘偏弱×0.8，大盘偏强×1.15
蓝筹/白马×1.2，成长×0.85，题材/短线×0.6
最终仓位范围：5%~20%
同时考虑：现金{cash}元足够买多少手。
输出格式："X%（理由：一句话说明为什么这个仓位）"""
def parse_response(text):
    """从LLM回复中提取策略参数"""
    result = {"signal": "", "entry_low": 0, "entry_high": 0, "stop_loss": 0, "take_profit": 0, "position": ""}
    
    # 信号
    sl = [l for l in text.split("\n") if "综合结论" in l]
    if sl:
        for kw in ["买入","关注","观望","卖出"]:
            if kw in sl[0]:
                result["signal"] = kw
                break
    
    # 买入区间
    zl = [l for l in text.split("\n") if "买入区间" in l]
    if zl:
        nums = re.findall(r'[\d.]+', zl[0])
        if len(nums) >= 2:
            result["entry_low"] = float(nums[0])
            result["entry_high"] = float(nums[1])
    
    # 止损
    for l in text.split("\n"):
        if "建议止损" in l:
            nums = re.findall(r'[\d.]+', l)
            if nums: result["stop_loss"] = float(nums[0])
    
    # 止盈
    for l in text.split("\n"):
        if "建议止盈" in l:
            nums = re.findall(r'[\d.]+', l)
            if nums: result["take_profit"] = float(nums[0])
    
    # 仓位：只有买入信号才需要，提取百分比数字
    result["position"] = ""
    if result["signal"] == "买入":
        for l in text.split("\n"):
            if "建议仓位" in l:
                nums = re.findall(r'[\d.]+', l)
                for n in nums:
                    f = float(n)
                    if 1 <= f <= 30:  # 合理的仓位范围
                        result["position"] = f"{f:.0f}%"
                        break
                break
    
    return result

def save_result(code, full_text, parsed):
    """保存LLM结果到DB"""
    conn = sqlite3.connect(DB)
    now = datetime.now().isoformat()
    
    updates = ["full_analysis=?", "reassessed_at=?"]
    params = [full_text, now]
    
    if parsed["signal"]:
        updates.append("timing_signal=?")
        params.append(parsed["signal"])
    if parsed["entry_low"] > 0:
        updates.append("entry_low=?")
        params.append(parsed["entry_low"])
    if parsed["entry_high"] > 0:
        updates.append("entry_high=?")
        params.append(parsed["entry_high"])
    if parsed["stop_loss"] > 0:
        updates.append("stop_loss=?")
        params.append(parsed["stop_loss"])
    if parsed["take_profit"] > 0:
        updates.append("take_profit=?")
        params.append(parsed["take_profit"])
    if parsed["position"]:
        updates.append("position_advice=?")
        params.append(parsed["position"])
    
    params.append(code)
    sql = f"UPDATE holding_strategies SET {', '.join(updates)} WHERE code=? AND status='active'"
    conn.execute(sql, params)
    conn.commit()
    
    # 买入信号→推XMPP通知（在conn close前执行）
    if parsed.get("signal") == "买入":
        try:
            _nr = conn.execute("SELECT name, price FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
            _name = _nr[0] if _nr else code
            _p = _nr[1] if _nr else 0
            _el = parsed.get("entry_low", 0)
            _eh = parsed.get("entry_high", 0)
            _sl = parsed.get("stop_loss", 0)
            _tp = parsed.get("take_profit", 0)
            _pos = parsed.get("position", "")
            _msg = f"📈 {_name}({code}) 价{_p}→12维分析生成买入信号！区间{_el}~{_eh} 损{_sl} 盈{_tp} 仓位{_pos}"
            import urllib.request, json as _jj
            _req = urllib.request.Request("http://127.0.0.1:5805/",
                data=_jj.dumps({"body": _msg, "to": "hmo@yoin.fun", "type": "chat"}).encode(),
                headers={"Content-Type": "application/json"})
            urllib.request.urlopen(_req, timeout=5)
            print(f"  📨 XMPP推送成功: {_msg[:60]}")
        except Exception as _e:
            print(f"  ⚠️ XMPP推送失败: {_e}")
    
    conn.close()

def process_stock(code):
    """处理单只股票"""
    print(f"\n{'='*50}")
    print(f"处理: {code}")
    print(f"{'='*50}")
    
    if has_llm_analysis(code):
        print(f"  ⏭ 已有LLM九维分析，跳过")
        return False
    
    if in_cooldown(code):
        print(f"  ⏭ 冷却期内，跳过")
        return False
    
    print(f"  收集数据...", flush=True)
    data = collect_data(code)
    if not data.get("price"):
        print(f"  ⚠️ 无价格数据，跳过")
        return False
    
    print(f"  调LLM生成九维分析...", flush=True)
    prompt = build_prompt(data)
    
    try:
        r = subprocess.run(["curl", "-s", "--max-time", "300",
            "-H", "Content-Type: application/json",
            "-H", "Authorization: Bearer hermes123",
            "-d", json.dumps({"model":"deepseek-v4-flash","messages":[{"role":"user","content":prompt}],"max_tokens":2048}),
            GATEWAY], capture_output=True, timeout=310)
        
        if r.returncode != 0:
            print(f"  ❌ curl失败: {r.stderr.decode()[:100]}")
            return False
        
        resp = json.loads(r.stdout)
        if "choices" not in resp:
            print(f"  ❌ API异常: {str(resp)[:200]}")
            return False
        
        full_text = resp["choices"][0]["message"]["content"]
        print(f"  ✅ LLM返回({len(full_text)}字)", flush=True)
        
        parsed = parse_response(full_text)
        print(f"  信号={parsed['signal']} 区间={parsed['entry_low']}~{parsed['entry_high']} 损={parsed['stop_loss']} 盈={parsed['take_profit']} 仓位={parsed['position']}")
        
        save_result(code, full_text, parsed)
        print(f"  ✅ 已保存到DB")
        return True
        
    except subprocess.TimeoutExpired:
        print(f"  ❌ 超时")
        return False
    except Exception as e:
        print(f"  ❌ 错误: {e}")
        return False

def main():
    codes = []
    if "--code" in sys.argv:
        idx = sys.argv.index("--code")
        codes = [sys.argv[idx+1]]
    else:
        # 所有自选策略
        conn = sqlite3.connect(DB)
        rows = conn.execute("SELECT code FROM holding_strategies WHERE status='active' AND decision_type='自选策略' ORDER BY code").fetchall()
        conn.close()
        codes = [r[0] for r in rows]
    
    print(f"待处理: {len(codes)}只")
    
    ok = 0
    fail = 0
    skip = 0
    for i, code in enumerate(codes):
        if has_llm_analysis(code):
            print(f"  [{i+1}/{len(codes)}] ⏭ {code} 已有LLM分析")
            skip += 1
            continue
        
        print(f"  [{i+1}/{len(codes)}] ", end="", flush=True)
        if process_stock(code):
            ok += 1
        else:
            fail += 1
        
        # 间隔15秒（防gateway过载）
        if i < len(codes) - 1:
            print(f"  等待15秒...", flush=True)
            time.sleep(15)
    
    print(f"\n{'='*50}")
    print(f"完成: {ok}成功, {fail}失败, {skip}跳过")
    print(f"{'='*50}")

if __name__ == "__main__":
    main()
