#!/usr/bin/env python3
"""§7 自查双测: ①切片不变性 ②qfq 偏差分布"""
import os, sys, glob, random
import numpy as np, pandas as pd
sys.path.insert(0, '/Users/xpresso/zt_app/code')
import grid_or as g

BASE = g.BASE
BAK = os.path.expanduser('~/zt_app/_hfq_ohlc_backup_20260912')

# ---- qfq 偏差分布(旧落盘 4dp vs 现派生) ----
cur = {os.path.basename(f)[:-4]: f for p in ['hs300','zz500','zz1000','zz2000']
       for f in glob.glob(f'{BASE}/daily_{p}/*.csv')}
buck = {'>1e-4':0,'1e-5~1e-4':0,'1e-6~1e-5':0,'<=1e-6':0}
nfiles_rel_big = 0; worst = []
rows = 0
for bf in sorted(glob.glob(f'{BAK}/*.csv')):
    code = os.path.basename(bf)[:-4]
    if code not in cur: continue
    d = pd.read_csv(bf, dtype={'trade_date': str})
    if 'qfq_close' not in d.columns: continue
    c = pd.read_csv(cur[code], dtype={'trade_date': str})
    m = d.merge(c, on='trade_date', suffixes=('_o','_c'))
    if not len(m): continue
    rows += len(m)
    fac = m['adj_factor'].astype(float); rc = m['raw_close_c'].astype(float)
    der = (rc*fac/fac.iloc[-1]).round(4)
    old = m['qfq_close'].astype(float)
    rel = ((old-der).abs()/old.abs().clip(lower=1e-9))
    buck['>1e-4'] += int((rel>1e-4).sum())
    buck['1e-5~1e-4'] += int(((rel>1e-5)&(rel<=1e-4)).sum())
    buck['1e-6~1e-5'] += int(((rel>1e-6)&(rel<=1e-5)).sum())
    buck['<=1e-6'] += int((rel<=1e-6).sum())
    if rel.max() > 1e-5: nfiles_rel_big += 1
    i = int(rel.values.argmax())
    worst.append((float(rel.values[i]), code, m.iloc[i]['trade_date']))
worst.sort(reverse=True)
print('qfq 相对偏差分档(逐行 %d): %s' % (rows, buck))
print('  相对偏差 >1e-5 的文件数: %d / %d' % (nfiles_rel_big, len(cur)))
print('  最差 5 例:', [(f'{r:.2e}', c, d) for r, c, d in worst[:5]])

# ---- 切片不变性 ----
print('\n切片不变性(load_pool 全段 vs 窗口切片重派生):')
random.seed(11)
files = sorted(glob.glob(f'{BASE}/daily_*/*.csv'))
sample = random.sample(files, 60)
worst_q = 0.0; worst_h = 0.0; nbad = 0; win = None
for f in sample:
    d = pd.read_csv(f, dtype={'trade_date': str})
    full = g._derive_split_prices(d.copy())
    n = len(full)
    if n < 120: continue
    a, b = n//3, n//3 + 120
    sl = full.iloc[a:b].copy()
    sl = sl.drop(columns=['qfq_open','qfq_high','qfq_low','qfq_close',
                          'hfq_open','hfq_high','hfq_low','hfq_close'], errors='ignore')
    sl2 = g._derive_split_prices(sl)                      # 切片重派生(靠盖章列)
    ref = full.iloc[a:b]
    dq = float(np.abs(sl2['qfq_close'].astype(float).values - ref['qfq_close'].astype(float).values).max())
    dh = float(np.abs(sl2['hfq_close'].astype(float).values - ref['hfq_close'].astype(float).values).max())
    worst_q = max(worst_q, dq); worst_h = max(worst_h, dh)
    if dq > 0 or dh > 0: nbad += 1
    if dq == worst_q: win = (os.path.basename(f), str(ref['trade_date'].iloc[0]), str(ref['trade_date'].iloc[-1]))
print('  抽样 %d 文件: qfq 最大偏差 %.6f, hfq 最大偏差 %.6f, 不一致文件 %d' % (len(sample), worst_q, worst_h, nbad))
print('  最差窗口:', win)
# 未盖章切片(负向): 直接在裸切片上派生 -> 应出现漂移
print('\n负向(未盖章裸切片 → 旧行为漂移):')
codes = [os.path.basename(x)[:-4] for x in sample[:1]]
bad = 0; mx = 0.0
for f in sample[:120]:
    raw = pd.read_csv(f, dtype={'trade_date': str})
    n = len(raw)
    if n < 120: continue
    a, b = n//3, n//3+120
    sl = raw.iloc[a:b].copy()
    sl2 = g._derive_split_prices(sl)
    full = g._derive_split_prices(raw.copy()).iloc[a:b]
    d = float(np.abs(sl2['qfq_close'].astype(float).values - full['qfq_close'].astype(float).values).max())
    if d > 0: bad += 1
    mx = max(mx, d)
print('  裸切片重派生与全段不一致文件数: %d, 最大偏差 %.4f' % (bad, mx))
