#!/usr/bin/env python3
"""chk_price_health.py — 价格三档口径体检(只读, 不写盘)。

背景(2026-09-13): 后复权/hfq 口径已统一为 `raw_* × adj_factor` 运行时派生, 历史无需重拉。
本脚本给出支撑该结论的三项证据, 并暴露遗留脏列 `pre_close`。

用法: python code/chk_price_health.py [--factor] [--preclose] [--exdiv]   (默认三项全跑)
  --factor   adj_factor 覆盖: 行数 / NaN / 覆盖起始日分布
  --preclose pre_close 脏列: pct_chg 与 raw_close/pre_close 不自洽的行占比(按年 + 最集中交易日)
  --exdiv    真除权日(因子比偏离>1e-4): 当日因子比 vs 前日raw_close/pre_close 反推比 的吻合率

结论口径: hfq 精确(无需重拉) ✅ ; pre_close 只能显示、不可校验/兜底 ❌ 。
"""
import sys, glob, os
import numpy as np
import pandas as pd

BASE = os.environ.get('ZT_BASE', '/Users/xpresso/zt_app/backtest_zt_full')
FS = sorted(glob.glob(f'{BASE}/daily_*/*.csv'))
WANT = [a for a in ('--factor', '--preclose', '--exdiv') if a in sys.argv] or ['--factor', '--preclose', '--exdiv']
print(f'库 {BASE}: {len(FS)} 只日线')

if '--factor' in WANT:
    tot = nan = 0
    first = {}
    for f in FS:
        d = pd.read_csv(f, dtype={'trade_date': str})
        if 'adj_factor' not in d.columns:
            print(f'  ✗ 无 adj_factor 列: {f}'); continue
        tot += len(d); nan += int(d['adj_factor'].isna().sum())
        first[d['trade_date'].min()] = first.get(d['trade_date'].min(), 0) + 1
    print(f'[factor] 行 {tot} | adj_factor NaN {nan} | 起始日 top3 '
          f'{sorted(first.items(), key=lambda x: -x[1])[:3]}')

if '--preclose' in WANT:
    tot = dirty = okA = okB = neither = 0
    by_year, by_day = {}, {}
    for f in FS:
        d = pd.read_csv(f, dtype={'trade_date': str}).sort_values('trade_date').reset_index(drop=True)
        if len(d) < 3:
            continue
        prev = d['raw_close'].shift(1).values; pc = d['pct_chg'].values
        a = (d['raw_close'].values / d['pre_close'].values - 1) * 100
        b = (d['raw_close'].values / prev - 1) * 100
        good = np.logical_and(np.isfinite(prev), np.isfinite(pc))
        bad = np.logical_and(good, np.abs(a - pc) >= 0.05)
        tot += int(good.sum())
        for i in np.where(bad)[0]:
            dirty += 1
            y = d['trade_date'].values[i][:4]
            by_year[y] = by_year.get(y, 0) + 1
            by_day[d['trade_date'].values[i]] = by_day.get(d['trade_date'].values[i], 0) + 1
            if abs(b[i] - pc[i]) < 0.05: okB += 1
            elif abs(a[i] - pc[i]) < 0.05: okA += 1
            else: neither += 1
    print(f'[preclose] 可判行 {tot} | 脏(pre_close 与 pct_chg 不自洽) {dirty} ({dirty/max(tot,1)*100:.2f}%) '
          f'| 按年 {dict(sorted(by_year.items()))}')
    print(f'           脏行归属: pct_chg 跟 前日raw_close(=pre_close 才是脏的) {okB} / 跟 pre_close {okA} / 都不符 {neither}')
    print(f'           最集中交易日 {sorted(by_day.items(), key=lambda x: -x[1])[:5]}')

if '--exdiv' in WANT:
    n = ok = 0; worst = 0.0
    for f in FS:
        d = pd.read_csv(f, dtype={'trade_date': str}).sort_values('trade_date').reset_index(drop=True)
        if len(d) < 3:
            continue
        fp = d['adj_factor'].shift(1).values; prev = d['raw_close'].shift(1).values
        fr = d['adj_factor'].values / fp
        real = np.logical_and(np.isfinite(fr), np.abs(fr - 1) > 1e-4)
        i = np.where(np.logical_and(real, np.isfinite(prev)))[0]
        if not len(i):
            continue
        imp = prev[i] / d['pre_close'].values[i]
        err = np.abs(imp - fr[i]) / fr[i]
        n += len(i); ok += int((err < 1e-4).sum()); worst = max(worst, float(np.median(err)))
    print(f'[exdiv] 真除权日行 {n} | pre_close 反推因子吻合(<1e-4) {ok} ({ok/max(n,1)*100:.2f}%) '
          f'→ pre_close 不可作因子兜底源; 增量守卫见 update_daily.py 除权守卫')