"""修正版：真值必须带 start_date/end_date（pro.adj_factor 默认全历史、6000 行上限，
老票会把最近段截掉）。本脚本做两件事：

  A) 6 只"退化股"：本地窗口内 distinct vs tushare 真值 distinct（同窗口）
  B) 80 只抽样：B-only 判据的命中/误报（真值带日期边界）

用法: ./zt_venv/bin/python code/_chk_factor_truth_fix.py
"""
import os, glob, time, random, pickle
import numpy as np
import pandas as pd

os.environ.setdefault('HTTP_PROXY', 'http://127.0.0.1:7897')
os.environ.setdefault('HTTPS_PROXY', 'http://127.0.0.1:7897')
import tushare as ts
ts.set_token('edf6739fe1a4de0d747600cc753a8b4bf335cf27ef0f5aea2d2aa64c')
pro = ts.pro_api()
ROOT = "backtest_zt_full"


def pull(code, s, e, retries=3):
    for _ in range(retries):
        try:
            t = pro.adj_factor(ts_code=code, start_date=s, end_date=e)
            if t is not None and len(t):
                return t.sort_values('trade_date')
        except Exception:
            pass
        time.sleep(2)
    return None


print("=== A) 退化股：同窗口 distinct 对比 ===")
for c in ['600705.SH', '000627.SZ', '600837.SH', '000961.SZ', '000671.SZ', '600068.SH']:
    fs = glob.glob(f"{ROOT}/daily_*/{c}.csv")
    if not fs:
        continue
    d = pd.read_csv(fs[0])
    s, e = str(int(d['trade_date'].min())), str(int(d['trade_date'].max()))
    t = pull(c, s, e)
    if t is None:
        print(f"{c}: tushare 无返回"); continue
    t = t[t['trade_date'].astype(int).between(int(s), int(e))]
    steps_true = int((t['adj_factor'].diff().abs() > 1e-9).sum())
    steps_loc = int((d['adj_factor'].diff().abs() > 1e-9).sum())
    print(f"{c} 窗口 {s}~{e}: 行 本地{len(d)}/真值{len(t)} | distinct 本地{d['adj_factor'].nunique()}"
          f"/真值{t['adj_factor'].nunique()} | 步数 本地{steps_loc}/真值{steps_true}")
    time.sleep(0.3)

print("\n=== B) 80 只抽样：带日期边界的真值重算命中/误报 ===")
files = []
for p in ["hs300", "zz500", "zz1000", "zz2000"]:
    files += glob.glob(f"{ROOT}/daily_{p}/*.csv")
random.seed(7)
sample = random.sample(files, 80)
cache = {}
if os.path.exists('/tmp/_true_fac_bounded.pkl'):
    cache = pickle.load(open('/tmp/_true_fac_bounded.pkl', 'rb'))
for f in sample:
    c = os.path.basename(f)[:-4]
    if c in cache:
        continue
    d = pd.read_csv(f, usecols=['trade_date'])
    s, e = str(int(d['trade_date'].min())), str(int(d['trade_date'].max()))
    t = pull(c, s, e)
    if t is None:
        continue
    t = t[['trade_date', 'adj_factor']].copy()
    t['trade_date'] = t['trade_date'].astype(int)
    t = t[t['trade_date'].between(int(s), int(e))]
    cache[c] = t
    time.sleep(0.3)
pickle.dump(cache, open('/tmp/_true_fac_bounded.pkl', 'wb'))

tp = fn = fp = tn = 0
fp_stocks = {}
miss_jumps = []
for f in sample:
    c = os.path.basename(f)[:-4]
    if c not in cache:
        continue
    t = cache[c].sort_values('trade_date')
    t['chg'] = t['adj_factor'].diff().abs() > 1e-9
    ex = {int(r.trade_date): float(r.adj_factor) / float(r.prev) - 1
          for r in t.assign(prev=t['adj_factor'].shift(1))[t['chg']].itertuples()}
    d = pd.read_csv(f)
    d['raw_prev'] = d['raw_close'].shift(1)
    td = d['trade_date'].astype(int).values
    prev_fac = d['adj_factor'].shift(1).values
    for i in range(1, len(d)):
        r = d.iloc[i]
        rp = float(r['raw_prev']); rc = float(r['raw_close']); pct = float(r['pct_chg'])
        if not rp > 0:
            continue
        got = abs((rc / rp - 1) * 100 - pct) >= 0.05
        isex = td[i] in ex
        if isex and got:
            tp += 1
        elif isex and not got:
            fn += 1; miss_jumps.append(abs(ex[td[i]]))
        elif not isex and got:
            fp += 1; fp_stocks[c] = fp_stocks.get(c, 0) + 1
        else:
            tn += 1
print(f"真除权 {tp+fn} -> 命中 {tp} ({tp/max(tp+fn,1):.1%}) | 漏 {fn}, 漏判跳幅 中位"
      f" {np.median(miss_jumps) if miss_jumps else 0:.4%} max {max(miss_jumps) if miss_jumps else 0:.3%}")
print(f"非除权 {fp+tn} -> B 命中(误报) {fp} ({fp/max(fp+tn,1):.3%}) | 集中在 {len(fp_stocks)} 只")
for c, n in sorted(fp_stocks.items(), key=lambda kv: -kv[1])[:10]:
    print(f"    {c}: {n}")
