#!/usr/bin/env python3
"""candidate_filter.py — 候选股多级过滤管道

从 candidates 表读取未过滤的候选，逐级执行过滤：
  Stage 2: 多日K线确认（量价连续性）
  Stage 3: 技术位分析（MA位置）
  Stage 4: 资金性质（大单流向）
  Stage 5: 基本面（PE/PB/行业）

用法: python3 candidate_filter.py [--stage 2|3|4|5] [--code XXXXXX]
"""
import sys, json, urllib.request, sqlite3, re, time
from pathlib import Path
from datetime import datetime

DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
UA = "Mozilla/5.0"

def get_conn():
    return sqlite3.connect(str(DB_PATH))

def log_candidate(conn, code, stage, passed, detail):
    """记录过滤日志"""
    conn.execute(
        "UPDATE candidates SET log = COALESCE(log, '[]')"
    )
    # SQLite JSON操作
    existing = conn.execute("SELECT log FROM candidates WHERE code=?", (code,)).fetchone()
    if existing and existing[0]:
        try:
            logs = json.loads(existing[0])
        except:
            logs = []
    else:
        logs = []
    logs.append({"stage": stage, "passed": passed, "detail": detail, "time": datetime.now().strftime("%m-%d %H:%M")})
    conn.execute("UPDATE candidates SET log=? WHERE code=?", (json.dumps(logs, ensure_ascii=False), code))


# ── Stage 2: 多日K线确认 ──

def fetch_daily_klines(code):
    """拉取近10日日K线（Sina 240分钟线=日K）"""
    raw = str(code).strip()
    if raw.startswith(("6", "9")):
        prefix = "sh"
    elif raw.startswith(("0", "3")):
        prefix = "sz"
    else:
        return None
    
    import subprocess as _sp, json as _json
    url = f"http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol={prefix}{raw}&scale=240&ma=5&datalen=10"
    try:
        r = _sp.run(["curl", "-s", "--noproxy", "*", url], capture_output=True, timeout=10)
        data = _json.loads(r.stdout)
        if not data:
            return None
        result = []
        for k in data:
            result.append({
                "date": k.get("day", "")[:10],
                "open": float(k["open"]),
                "close": float(k["close"]),
                "high": float(k["high"]),
                "low": float(k["low"]),
                "volume": int(k["volume"]),
                "price": float(k["close"]),
                "change_pct": 0,
            })
        # 计算涨跌幅
        for i in range(1, len(result)):
            prev = result[i-1]["close"]
            if prev > 0:
                result[i]["change_pct"] = (result[i]["close"] / prev - 1) * 100
        return result
    except Exception as e:
        return None
        return None


def stage2_confirm(code, name, klines):
    """第二关：多日K线确认
    检查：多日量价配合、建仓特征
    """
    if not klines or len(klines) < 3:
        return False, 0, "K线不足3日"
    
    recent = klines[-5:]  # 最近5日
    score = 0
    checks = []
    
    # 1. 成交量连续递增
    vols = [k["volume"] for k in recent]
    vol_rising = sum(1 for i in range(len(vols)-1) if vols[i] < vols[i+1])
    if vol_rising >= 3:
        score += 2
        checks.append(f"量增{vol_rising}/4日")
    elif vol_rising >= 2:
        score += 1
        checks.append(f"量微增{vol_rising}/4日")
    
    # 2. 涨放量、跌缩量
    up_vol = sum(k["volume"] for k in recent if k["change_pct"] >= 0)
    down_vol = sum(k["volume"] for k in recent if k["change_pct"] < 0)
    if down_vol > 0 and up_vol / down_vol > 1.5:
        score += 2
        checks.append(f"涨量/跌量={up_vol/down_vol:.1f}")
    elif down_vol > 0 and up_vol / down_vol > 1:
        score += 1
    
    # 3. 价格趋势
    closes = [k["close"] for k in recent]
    up_days = sum(1 for i in range(1, len(closes)) if closes[i] > closes[i-1])
    if up_days >= 3:
        score += 2
        checks.append(f"涨{up_days}/4日")
    elif up_days >= 2:
        score += 1
    
    # 4. 无异常放量（单日>3倍均量=可能出货）
    avg_vol = sum(vols) / len(vols) if vols else 1
    max_ratio = max(v / avg_vol for v in vols) if avg_vol > 0 else 1
    if max_ratio < 2.5:
        score += 1
    else:
        checks.append(f"异常量{max_ratio:.0f}倍")
    
    passed = score >= 4
    detail = f"评分{score}/7 | {'; '.join(checks)}"
    return passed, score, detail


# ── Stage 3: 技术位分析 ──

def stage3_technical(code, name, klines):
    """第三关：技术位（当日数据估算）"""
    if not klines or len(klines) == 0:
        return False, 0, "无数据"
    
    today = klines[-1]
    price = today.get("price", 0)
    high = today.get("high", 0)
    low = today.get("low", 0)
    
    score = 0
    checks = []
    
    if price <= 0:
        return False, 0, "价格无效"
    
    # 日内位置（在高低点中下段还有空间）
    if high > low:
        pos = (price - low) / (high - low)
        if pos < 0.7:
            score += 1
            checks.append(f"日内位置{pos:.0%}")
    
    # 有明确支撑（今日低点作为参考支撑）
    if low > 0 and price > low:
        score += 1
        checks.append(f"支撑{low:.2f}")
    
    # 有上涨空间（今日高点作为参考阻力）
    if high > price:
        upside = (high / price - 1) * 100
        if upside > 2:
            score += 1
            checks.append(f"空间{upside:.0f}%")
    
    passed = score >= 2
    return passed, score, "; ".join(checks) if checks else "基础通过"


# ── Stage 4: 资金性质分析 ──

def stage4_capital_flow(code, name):
    """第四关：资金性质（从腾讯实时行情提取外盘/内盘比）"""
    raw = str(code).strip()
    if raw.startswith(("6", "9")):
        prefix = "sh"
    elif raw.startswith(("0", "3")):
        prefix = "sz"
    else:
        return False, 0, "非A股"
    
    import subprocess as _sp
    url = f"http://qt.gtimg.cn/q={prefix}{raw}"
    try:
        r = _sp.run(["curl", "-s", url], capture_output=True, timeout=10)
        text = r.stdout.decode("gbk", errors="ignore")
        parts = text.split("~")
        if len(parts) < 40:
            return False, 0, "数据不足"
        
        # 腾讯字段：[7]=外盘(主动买,股),[8]=内盘(主动卖,股)
        try:
            outer = int(float(parts[7])) if parts[7] else 0  # 外盘
            inner = int(float(parts[8])) if parts[8] else 0  # 内盘
        except:
            return False, 0, "解析失败"
        
        if outer <= 0 or inner <= 0:
            return False, 0, "无盘口数据"
        
        score = 0
        ratio = outer / inner if inner > 0 else 1
        checks = []
        
        if ratio > 1.3:
            score += 2
            checks.append(f"外/内={ratio:.2f}")
        elif ratio > 1.0:
            score += 1
            checks.append(f"买稍强{ratio:.2f}")
        else:
            checks.append(f"卖稍强{ratio:.2f}")
        
        # 绝对量也说明资金活跃度
        total = outer + inner
        if total > 50000000:  # >5000万股
            score += 1
            checks.append(f"活跃{total/10000:.0f}万")
        
        return score >= 1, score, "; ".join(checks)
    except:
        return False, 0, "接口失败"


# ── Stage 5: 基本面 ──

def stage5_fundamental(code, name, price):
    """第五关：基本面
    从已有数据判断，不调外部API
    """
    conn = get_conn()
    score = 0
    checks = []
    
    # PE（从stocks表或live_prices）
    r = conn.execute("SELECT 1 FROM holdings WHERE code=? AND is_active=1", (code,)).fetchone()
    is_holding = r is not None
    if is_holding:
        checks.append("已持仓")
    else:
        score += 1  # 新标的加分
    
    # 检查是否已被其他候选覆盖
    r2 = conn.execute("SELECT code FROM holding_strategies WHERE code=? AND status='active'", (code,)).fetchone()
    if r2:
        checks.append("已有策略")
    else:
        score += 1
    
    conn.close()
    return score >= 1, score, "; ".join(checks) if checks else "新标的"


# ── 主流程 ──

def main():
    stage_filter = None
    single_code = None
    for i, arg in enumerate(sys.argv[1:]):
        if arg == "--stage" and i+1 < len(sys.argv):
            stage_filter = int(sys.argv[i+2])
        if arg == "--code" and i+1 < len(sys.argv):
            single_code = sys.argv[i+2]
    
    conn = get_conn()
    
    # 读待过滤的候选
    query = "SELECT code, name, reason FROM candidates WHERE 1=1"
    params = []
    if single_code:
        query += " AND code=?"
        params.append(single_code)
    else:
        query += " AND (pass_final IS NULL OR pass_final=0)"
    
    rows = conn.execute(query, params).fetchall()
    print(f"[FILTER] 待处理候选: {len(rows)}只", flush=True)
    
    stages = [(2, stage2_confirm, "多日K线"), (3, stage3_technical, "技术位"),
              (4, stage4_capital_flow, "资金流"), (5, stage5_fundamental, "基本面")]
    
    for code, name, reason in rows:
        current_score = 0
        print(f"  {code} {name}", flush=True)
        
        # 获取K线（多关需要）
        klines = None
        
        for stage_num, stage_fn, stage_name in stages:
            if stage_filter and stage_num != stage_filter:
                continue
            
            # 检查是否已通过此关
            col = f"pass_s{stage_num}"
            existing = conn.execute(f"SELECT {col} FROM candidates WHERE code=?", (code,)).fetchone()
            if existing and existing[0]:
                continue
            
            if stage_num in (2, 3) and klines is None:
                klines = fetch_daily_klines(code)
            
            if stage_num == 2:
                passed, sscore, detail = stage_fn(code, name, klines)
                conn.execute("UPDATE candidates SET score_2nd=?, pass_s2=?, reason=? WHERE code=?",
                            (sscore, 1 if passed else 0, detail, code))
                log_candidate(conn, code, 2, passed, detail)
                print(f"    S2:{'✅' if passed else '❌'} {detail}", flush=True)
            
            elif stage_num == 3:
                passed, sscore, detail = stage_fn(code, name, klines)
                conn.execute("UPDATE candidates SET score_3rd=?, pass_s3=?, reason=? WHERE code=?",
                            (sscore, 1 if passed else 0, detail, code))
                log_candidate(conn, code, 3, passed, detail)
                print(f"    S3:{'✅' if passed else '❌'} {detail}", flush=True)
            
            elif stage_num == 4:
                passed, sscore, detail = stage_fn(code, name)
                conn.execute("UPDATE candidates SET score_4th=?, pass_s4=?, reason=? WHERE code=?",
                            (sscore, 1 if passed else 0, detail, code))
                log_candidate(conn, code, 4, passed, detail)
                print(f"    S4:{'✅' if passed else '❌'} {detail}", flush=True)
            
            elif stage_num == 5:
                price = 0  # 从live_prices获取
                r = conn.execute("SELECT price FROM live_prices WHERE code=?", (code,)).fetchone()
                if r: price = r[0]
                passed, sscore, detail = stage_fn(code, name, price)
                conn.execute("UPDATE candidates SET score_5th=?, pass_s5=?, reason=? WHERE code=?",
                            (sscore, 1 if passed else 0, detail, code))
                log_candidate(conn, code, 5, passed, detail)
                print(f"    S5:{'✅' if passed else '❌'} {detail}", flush=True)
        
        # 计算综合评分
        s2 = conn.execute("SELECT score_2nd FROM candidates WHERE code=?", (code,)).fetchone()[0] or 0
        s3 = conn.execute("SELECT score_3rd FROM candidates WHERE code=?", (code,)).fetchone()[0] or 0
        s4 = conn.execute("SELECT score_4th FROM candidates WHERE code=?", (code,)).fetchone()[0] or 0
        s5 = conn.execute("SELECT score_5th FROM candidates WHERE code=?", (code,)).fetchone()[0] or 0
        final = current_score + s2 + s3 + s4 + s5
        conn.execute("UPDATE candidates SET score_final=?, pass_final=1 WHERE code=?",
                    (final, code))
    
    conn.commit()
    conn.close()
    print(f"[FILTER] 完成", flush=True)

if __name__ == "__main__":
    main()
