#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""口径溯源：三板样本为何对不上（46 vs 261）+ 分层随机对照"""
import glob, json, sqlite3, statistics, random
from collections import defaultdict
from datetime import date

DB = "/root/px_history.db"
FEE = 0.12
random.seed(20261107)

con = sqlite3.connect("file:%s?mode=ro" % DB, uri=True)
px, by_code = {}, defaultdict(list)
for d, ts, o, cl, pc, h, l in con.execute(
        "SELECT trade_date, ts_code, open, close, pre_close, high, low FROM daily"):
    if not o or not cl or not pc:
        continue
    px[(d, ts)] = (o, cl, pc, h, l)
    by_code[ts].append(d)
for ts in by_code:
    by_code[ts].sort()
pos = {ts: {d: i for i, d in enumerate(v)} for ts, v in by_code.items()}
days = sorted({d for d, _ in px})
nxt = {days[i]: days[i + 1] for i in range(len(days) - 1)}

def L(code):
    if code.startswith(("300", "301", "688", "689")):
        return 20.0
    if code.startswith(("8", "4", "920")):
        return 30.0
    return 10.0

def suf(c):
    if c.startswith(("60", "68", "90")):
        return "SH"
    if c.startswith(("00", "30", "20")):
        return "SZ"
    return "BJ"

def up_px(pc, l):
    return round(pc * (1 + l / 100.0) + 1e-9, 2)

def dn_px(pc, l):
    return round(pc * (1 - l / 100.0) + 1e-9, 2)

# 严格涨停：收盘价恰等于涨停价（排除次新/无涨跌幅限制造成的“越界”）
lu_strict = set()
lu_loose = set()
for (d, ts), (o, cl, pc0, h0, l0) in px.items():
    up = up_px(pc0, L(ts.split(".")[0]))
    if abs(cl - up) <= 0.005:
        lu_strict.add((d, ts))
    if cl >= up - 0.005:
        lu_loose.add((d, ts))
print("涨停日判定：严格 %d 个 / 宽松 %d 个（差 %d）"
      % (len(lu_strict), len(lu_loose), len(lu_loose) - len(lu_strict)), flush=True)

pool = []
for path in sorted(glob.glob("/opt/kpl-cli/data/auction_hist/20*.json")):
    try:
        d = json.load(open(path, encoding="utf-8"))
    except Exception:
        continue
    day = (d.get("day") or "").replace("-", "")
    for r in (d.get("rows") or []):
        c = str(r.get("code") or "")
        if len(c) != 6 or not c.isdigit() or r.get("auction_pct") is None:
            continue
        pool.append(dict(day=day, code=c, name=str(r.get("name") or ""),
                         ap=float(r["auction_pct"]), bid=float(r.get("limit_bid") or 0)))

def first_day(ts):
    return by_code[ts][0]

def build(lu, need_bid=5e6):
    out = []
    for r in pool:
        if r["bid"] < need_bid or "ST" in r["name"].upper():
            continue
        ts = r["code"] + "." + suf(r["code"])
        t = px.get((r["day"], ts))
        if not t:
            continue
        o, cl, pc0, h0, l0 = t
        l = L(r["code"])
        if abs(o - up_px(pc0, l)) < 0.005:          # 竞价即涨停买不进
            continue
        i = pos[ts].get(r["day"])
        if i is None or i < 2:
            continue
        d1_, d2_ = by_code[ts][i - 1], by_code[ts][i - 2]
        if not ((d1_, ts) in lu and (d2_, ts) in lu):
            continue
        n1 = nxt.get(r["day"])
        p1 = px.get((n1, ts)) if n1 else None
        if not p1:
            continue
        o1, c1, pc1, h1, l1 = p1
        f = pc1 / cl if cl and abs(pc1 / cl - 1) > 0.002 else 1.0
        stuck = bool(abs(h1 - l1) < 0.005 and abs(c1 - dn_px(pc1, l)) < 0.005)
        age = pos[ts][r["day"]]
        out.append(dict(day=r["day"], code=r["code"], l=l, ap=r["ap"], bid=r["bid"],
                        age=age, stuck=stuck,
                        ret=(c1 / f / o - 1) * 100 - FEE))
    return out

def stat(v):
    if not v:
        return None
    return dict(n=len(v), win=sum(1 for x in v if x > 0) / len(v) * 100,
                avg=sum(v) / len(v), med=statistics.median(v))

print("\n== A. 三板集合（不限竞价涨幅）笔数溯源")
for lab, lu in (("严格涨停", lu_strict), ("宽松涨停", lu_loose)):
    b = build(lu)
    print("  %s：三板可买 %d 笔" % (lab, len(b)))
    by = defaultdict(int)
    for x in b:
        by["%dcm" % int(x["l"])] += 1
    print("     分板块", dict(by))
    for lo, hi, tl in ((0, 1, "0~1%"), (1, 2, "1~2%")):
        sub = [x for x in b if lo < x["ap"] < hi]
        print("     %s 档 %d 笔" % (tl, len(sub)))
    sub = [x for x in b if 0 < x["ap"] < 1 and x["bid"] >= 5e7]
    print("     0~1%% 且委买≥5000万：%d 笔" % len(sub))
    # 剔除上市不足 60 个交易日的次新
    sub2 = [x for x in b if 0 < x["ap"] < 1 and x["bid"] >= 5e7 and x["age"] >= 60]
    print("     其中再剔次新(上市<60交易日)：%d 笔" % len(sub2))

print("\n== B. 最终格子重算（严格涨停 + 0<竞价<1 + 委买≥5000万 + T+1 收盘卖）")
b = [x for x in build(lu_strict) if 0 < x["ap"] < 1 and x["bid"] >= 5e7 and not x["stuck"]]
s = stat([x["ret"] for x in b])
print("  全部：%d 笔｜胜率 %.2f%%｜平均 %+.2f%%｜中位 %+.2f%%" % (s["n"], s["win"], s["avg"], s["med"]))
for l in (10, 20, 30):
    v = [x["ret"] for x in b if x["l"] == l]
    x = stat(v)
    if x:
        print("  %dcm：%2d 笔｜胜率 %5.2f%%｜平均 %+6.2f%%｜中位 %+6.2f%%" % (l, x["n"], x["win"], x["avg"], x["med"]))
for yr in ("2024", "2025", "2026"):
    x = stat([i["ret"] for i in b if i["day"][:4] == yr])
    if x:
        print("  %s：%2d 笔｜胜率 %5.2f%%｜平均 %+6.2f%%" % (yr, x["n"], x["win"], x["avg"]))
half = days[len(days) // 2]
for lab, sub in (("前半", [i for i in b if i["day"] < half]), ("后半", [i for i in b if i["day"] >= half])):
    x = stat([i["ret"] for i in sub])
    if x:
        print("  %s：%2d 笔｜胜率 %5.2f%%｜平均 %+6.2f%%｜中位 %+6.2f%%" % (lab, x["n"], x["win"], x["avg"], x["med"]))

print("\n== C. 跌停格子：分层随机对照（按交易日抽，控制同日聚集）")
dn = []
for r in pool:
    if r["bid"] < 5e6 or "ST" in r["name"].upper():
        continue
    ts = r["code"] + "." + suf(r["code"])
    t = px.get((r["day"], ts))
    if not t:
        continue
    o, cl, pc0, h0, l0 = t
    l = L(r["code"])
    if abs(o - dn_px(pc0, l)) > 0.005:
        continue
    n1 = nxt.get(r["day"])
    p1 = px.get((n1, ts)) if n1 else None
    if not p1:
        continue
    o1, c1, pc1, h1, l1 = p1
    f = pc1 / cl if cl and abs(pc1 / cl - 1) > 0.002 else 1.0
    stuck = bool(abs(h1 - l1) < 0.005 and abs(c1 - dn_px(pc1, l)) < 0.005)
    dn.append(dict(day=r["day"], bid=r["bid"], l=l, stuck=stuck, ret=(c1 / f / o - 1) * 100 - FEE))
ok = [x for x in dn if not x["stuck"]]
tgt = [x for x in ok if (1e7 <= x["bid"] < 1.5e7) or (2e7 <= x["bid"] < 3e7)]
s = stat([x["ret"] for x in tgt])
print("  目标格子 %d 笔｜胜率 %.2f%%｜平均 %+.2f%%" % (s["n"], s["win"], s["avg"]))
byday = defaultdict(list)
for x in ok:
    byday[x["day"]].append(x["ret"])
rl = list(byday.values())
means = []
for _ in range(3000):
    pick = []
    while len(pick) < s["n"]:
        pick.extend(random.choice(rl))
    means.append(sum(pick[:s["n"]]) / s["n"])
means.sort()
better = sum(1 for m in means if m >= s["avg"]) / len(means) * 100
print("  分层随机（按交易日抽）3000 次：中位 %+.2f%%、95 分位 %+.2f%%；本口径排前 %.1f%%"
      % (means[len(means) // 2], means[int(len(means) * 0.95)], better))
print("\nDONE", flush=True)
