"""plot_ideal_top10 兼容重建（Mac 迁移包缺原文件）
sim_live_daily.py / scan_daily_tb.py 只用到 chain_for_buy(d, code, bp, bd)：
给定个股日线+买入日，定位信号链 (t0i, tai, tbi)（原dailies索引）。
逻辑与 scan_daily_tb.py find_tb_today() 同源，但从买入日反推突破日(tb=bd-1)。
"""
import numpy as np
import pandas as pd

def chain_for_buy(d, code, bp, bd):
    """d: 个股日线df; bd: 买入日(=突破日次日)。返回 (t0i, tai, tbi) 或 (None,None,None)"""
    d = d.sort_values('trade_date').reset_index(drop=True)
    n = len(d)
    if n < 25:
        return None, None, None
    dates = d['trade_date'].values
    # 突破日 tb = 买入日的前一个交易日
    idx = np.where(dates == bd)[0]
    if len(idx) == 0:
        return None, None, None
    tbi = idx[0] - 1
    if tbi < 1:
        return None, None, None
    # 以下与 find_tb_today 同口径：从 t0=涨停日 向后找 ta(缩量) 与 tb(突破)
    LIMIT_UP = {'main': 9.9, 'gem': 19.9, 'star': 19.9, 'bj': 29.9}
    def board_type(c):
        if c.startswith(('60', '00')):
            return 'main'
        if c.startswith(('30', '68')):
            return 'gem'
        return 'bj'
    bt = LIMIT_UP[board_type(code)]
    pct = d['pct_chg'].astype(float).values
    close = d['close'].astype(float).values
    vol = d['vol'].astype(float).values
    vb, vs, bs = bp['vol_base'], bp['vol_shrink'], bp['break_strength']
    sw, bu, mn = int(bp['shrink_win']), int(bp['break_up']), int(bp['ma_n'])
    ztv = bp.get('t0_vol_max', bp.get('zt_vol_filter', 0))
    t0min = bp.get('t0_vol_min', 0.0)
    taddmax = bp.get('ta_dd_max', 0.0)
    talowfloor = bp.get('ta_low_floor', 0.0)
    tbmin = bp.get('tb_vol_min', 0.0)
    tbmax = bp.get('tb_vol_max', 0.0)
    for t0 in np.where(pct >= bt - 0.05)[0]:
        if t0 + 2 >= n or t0 > tbi:
            continue
        v0 = vol[t0]
        if v0 <= 0:
            continue
        ma10z = vol[max(0, t0 - 10):t0].mean() if t0 >= 10 else vol[:t0].mean()
        if t0min > 0:
            if ma10z <= 0 or v0 < ma10z * t0min:
                continue
        if ztv > 0:
            if ma10z > 0 and v0 >= ma10z * ztv:
                continue
        p0 = close[t0]
        p0_floor = p0 * (1 - taddmax) if taddmax > 0 else None
        ta = None
        ta_min_close = None
        for j in range(t0 + 1, min(t0 + sw + 1, n)):
            shrink_ok = False
            if vb in ('zt', 'or') and vol[j] < v0 * vs:
                shrink_ok = True
            if vb in ('maN', 'or'):
                m = vol[max(0, j - mn):j].mean() if j >= mn else vol[:j].mean()
                if m > 0 and vol[j] < m * vs:
                    shrink_ok = True
            if shrink_ok and close[j] < p0:
                if p0_floor is not None and close[j] < p0_floor:
                    continue
                if talowfloor > 0 and close[j] < p0 * talowfloor:
                    continue
                if ta_min_close is None or close[j] < ta_min_close:
                    ta_min_close = close[j]
                    ta = j
        if ta is None or ta >= tbi:
            continue
        k = tbi
        if close[k] > p0 * (1 + bs):
            ok = True
            if tbmin > 0 or tbmax > 0:
                cb = vol[t0 + 1:k].mean() if k > t0 + 1 else vol[k]
                if cb > 0:
                    if tbmin > 0 and vol[k] < cb * tbmin:
                        ok = False
                    if ok and tbmax > 0 and vol[k] > cb * tbmax:
                        ok = False
                else:
                    ok = False
            if ok:
                return t0, ta, k
    return None, None, None
