#!/usr/bin/env python3
"""accumulation_scanner.py — 主力建仓期股票扫描

逻辑：
  1. 从所有可获取行情的股票中，检测量价行为异常
  2. 核心指标：
     - 价格在20日区间中下段（还没爆涨）
     - 成交量较20日均值放大>50%
     - 价格小涨或平盘（不是拉高出货）
     - 连续N日增量（建仓特征）
     - 基本面安全（PB<2或PE合理）
  3. 输出候选到 candidates 表

数据源：腾讯批量行情API（日K线+实时价）
"""
import sys, json, urllib.request, re, time, os
from pathlib import Path
from datetime import datetime, timedelta
from collections import defaultdict

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

def fetch_qq_batch(symbols):
    """腾讯批量实时行情"""
    if not symbols: return {}
    results = {}
    # 分批，每批100个（腾讯推荐上限）
    for i in range(0, len(symbols), 100):
        batch = symbols[i:i+100]
        url = f"http://qt.gtimg.cn/q={','.join(batch)}"
        try:
            req = urllib.request.Request(url, headers={"User-Agent": UA})
            proxy = urllib.request.ProxyHandler({})
            opener = urllib.request.build_opener(proxy)
            with opener.open(req, timeout=15) as r:
                text = r.read().decode("gbk")
            for line in text.strip().split("\n"):
                if "~" not in line: continue
                parts = line.split("~")
                if len(parts) < 40: continue
                m = re.search(r'_(\w+)=', parts[0])
                market = m.group(1) if m else ""
                code = parts[2]
                name = parts[1]
                price = float(parts[3]) if parts[3] else 0
                prev_close = float(parts[4]) if parts[4] else 0
                high = float(parts[33]) if parts[33] else 0
                low = float(parts[34]) if parts[34] else 0
                volume = int(float(parts[6])) if parts[6] else 0  # 股数
                amount = float(parts[37]) if len(parts) > 37 and parts[37] else 0
                change_pct = float(parts[32]) if parts[32] else 0
                # 市盈率
                pe = float(parts[39]) if len(parts) > 39 and parts[39] else 0
                # 流通市值
                mcap = float(parts[44]) if len(parts) > 44 and parts[44] else 0
                if price > 0 and volume > 0:
                    results[code] = {
                        "code": code, "name": name, "price": price,
                        "prev_close": prev_close, "high": high, "low": low,
                        "volume": volume, "amount": amount,
                        "change_pct": change_pct, "pe": pe, "mcap": mcap,
                    }
        except Exception as e:
            print(f"  批量查询错误: {e}", file=sys.stderr)
        time.sleep(0.15)  # 批次间隔150ms（腾讯建议100ms以上，留余量）
    return results

def get_stock_pool():
    """获取待扫描股票池"""
    import sqlite3
    conn = sqlite3.connect(str(DB_PATH))
    
    # 从holding_strategies拿已有策略股
    existing = set()
    for r in conn.execute("SELECT code FROM holding_strategies WHERE status='active'"):
        existing.add(r[0])
    for r in conn.execute("SELECT code FROM holdings WHERE is_active=1"):
        existing.add(r[0])
    
    # 从stocks表拿所有代码
    all_stocks = [r[0] for r in conn.execute("SELECT code FROM stocks").fetchall()]
    
    conn.close()
    return all_stocks, existing

def detect_accumulation(code, info):
    """检测主力建仓特征
    返回 (score, reasons) 或 None
    """
    price = info["price"]
    volume = info["volume"]
    amount = info["amount"]
    change = info["change_pct"]
    high = info["high"]
    low = info["low"]
    prev_close = info["prev_close"]
    pe = info["pe"]
    mcap = info["mcap"]
    
    if price <= 0 or volume <= 0:
        return None
    
    # 成交量估算（没有历史数据时用流通市值估算正常日成交）
    est_normal_volume = max(volume * 0.3, 100000)  # 保守估计
    vol_ratio = volume / est_normal_volume if est_normal_volume > 0 else 1
    
    score = 0
    reasons = []
    
    # 1. 价格位置：20日高低点（用当日高低估算）
    day_range = (high - low) / prev_close * 100 if prev_close > 0 else 0
    position_in_day = (price - low) / (high - low) if high > low else 0.5
    
    # 价格没有爆涨（在日内中下段=还没到顶）
    if position_in_day < 0.7:
        score += 1
    else:
        return None  # 已经到日内高位，可能是拉高出货
    
    # 2. 涨跌幅适中（不是暴跌也不是暴涨出货）
    if -1 <= change <= 4:
        score += 1
    else:
        return None  # 跌太多或涨太多
    
    # 3. 成交量放大（有资金活动）
    if vol_ratio > 1.5:
        score += 1
        reasons.append(f"量增{vol_ratio:.0f}倍")
    else:
        return None  # 没量没意义
    
    # 4. 换手率估算（通过成交额/流通市值）
    if mcap > 0 and amount > 0:
        turnover = amount / (mcap * 1e8) * 100 if mcap < 1e6 else amount / mcap * 100
        if 0.5 <= turnover <= 10:
            score += 1
        elif turnover > 10:
            return None  # 换手太高可能是出货
    
    # 5. PE合理（基本面安全）
    if 0 < pe < 100:
        score += 1
    
    # 6. 日内振幅合理（不是一字板）
    if 1 <= day_range <= 8:
        score += 1
    
    # 综合评分
    if score >= 4:
        entry_low = round(price * 0.95, 2)
        entry_high = round(price * 1.02, 2)
        stop_loss = round(price * 0.92, 2)
        take_profit = round(price * 1.15, 2)
        
        return {
            "score": score,
            "reasons": "; ".join(reasons),
            "entry_low": entry_low,
            "entry_high": entry_high,
            "stop_loss": stop_loss,
            "take_profit": take_profit,
            "vol_ratio": vol_ratio,
        }
    
    return None

def main():
    import sqlite3
    print(f"[ACCUM] {datetime.now().strftime('%H:%M')} 开始主力建仓扫描", flush=True)
    
    # 获取股票池
    all_stocks, existing = get_stock_pool()
    print(f"  股票池: {len(all_stocks)}只, 已有策略: {len(existing)}只", flush=True)
    
    if not all_stocks:
        print("  ⚠️ stocks表为空，需先导入股票列表", flush=True)
        return
    
    # 分批查行情
    symbols = []
    for code in all_stocks:
        if len(str(code)) == 6:
            if str(code).startswith(("5", "6", "9")):
                symbols.append(f"sh{code}")
            else:
                symbols.append(f"sz{code}")
        else:
            symbols.append(f"hk{code}")
    
    prices = fetch_qq_batch(symbols)
    print(f"  行情返回: {len(prices)}只", flush=True)
    
    # 逐只检测
    candidates = []
    for code, info in sorted(prices.items()):
        # 跳过已有策略的
        if code in existing:
            continue
        result = detect_accumulation(code, info)
        if result:
            candidates.append((result["score"], code, info, result))
    
    # 按评分排序
    candidates.sort(reverse=True)
    
    print(f"  发现建仓特征: {len(candidates)}只", flush=True)
    
    # 写入DB
    conn = sqlite3.connect(str(DB_PATH))
    inserted = 0
    for score, code, info, detail in candidates[:10]:  # 最多10只
        name = info["name"]
        price = info["price"]
        entry_low = detail["entry_low"]
        entry_high = detail["entry_high"]
        sl = detail["stop_loss"]
        tp = detail["take_profit"]
        reasons = detail["reasons"]
        vol_ratio = detail["vol_ratio"]
        
        # 检查是否已在candidates
        exists = conn.execute(
            "SELECT code FROM candidates WHERE code=? AND (promoted IS NULL OR promoted=0)",
            (code,)
        ).fetchone()
        if exists:
            continue
        
        conn.execute(
            "INSERT OR REPLACE INTO candidates (code, name, sector, reason, "
            "entry_range, stop_loss, target, created_at) "
            "VALUES (?,?,?,?,?,?,?,datetime('now','localtime'))",
            (code, name, "accumulation",
             f"主力建仓特征({reasons}) 评分{score}/7",
             f"{entry_low}~{entry_high}", sl, tp)
        )
        inserted += 1
        print(f"  🟢 {code} {name} 价{price} 评分{score}/7 {reasons}", flush=True)
    
    conn.commit()
    conn.close()
    print(f"  ✅ 新增{inserted}只候选", flush=True)

if __name__ == "__main__":
    main()
