"""Step 6:多周期结构普查。 核查三个论断: 1. 大周期分型/笔够用,线段滞后太大 —— 实测各级结构的确认滞后。 2. BTC 趋势时很难有 15 分钟以上的中枢 —— 实测各周期中枢密度。 3. 必须区分趋势与盘整 —— 按缠论定义(单中枢=盘整,多个同向中枢=趋势)统计占比。 """ from __future__ import annotations import sys from pathlib import Path import numpy as np import pandas as pd sys.path.insert(0, str(Path(__file__).resolve().parent)) from lib.data import fetch_ohlcv sys.path.insert(0, str(Path(__file__).resolve().parents[1])) from chanlun import TF_DF from chanlun.core.ChanEnum import Chan_BI_DIR, Chan_ZS_DIR pd.set_option("display.width", 240) SYMBOL = "BTC/USDT:USDT" TFS = ["15m", "1h", "4h", "1d"] BARS_PER_DAY = {"15m": 96, "1h": 24, "4h": 6, "1d": 1} def lag_stats(items, idx_of, attr_end="end_time") -> tuple[float, float, int]: """结构确认滞后:sure_time 与结构终点之间隔了几根K线。""" lags = [] for it in items: if not getattr(it, "is_sure", False) or getattr(it, "sure_time", None) is None: continue e, s = str(getattr(it, attr_end, "")), str(it.sure_time) if e in idx_of and s in idx_of: lags.append(idx_of[s] - idx_of[e]) if not lags: return np.nan, np.nan, 0 a = np.array(lags) return float(np.median(a)), float(a.mean()), len(a) def classify_regime(bi_zs_list) -> list[str]: """缠论口径:相邻同向且不重叠的中枢构成趋势,否则为盘整。""" if len(bi_zs_list) < 2: return ["盘整"] * len(bi_zs_list) out = ["盘整"] for i in range(1, len(bi_zs_list)): prev, cur = bi_zs_list[i - 1], bi_zs_list[i] if cur.zd > prev.zg: out.append("上涨趋势") elif cur.zg < prev.zd: out.append("下跌趋势") else: out.append("盘整") return out def main() -> None: rows_struct, rows_zs, rows_regime = [], [], [] for tf in TFS: df = fetch_ohlcv(SYMBOL, tf, 10**9) idx_of = {t: i for i, t in enumerate(df["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))} days = len(df) / BARS_PER_DAY[tf] chan = TF_DF(df, 1, tf) fx_count = sum(1 for k in chan.klc_list if getattr(k, "fx_confirmed", False)) bi_lag_med, bi_lag_mean, bi_n = lag_stats(chan.bi_list, idx_of) seg_lag_med, seg_lag_mean, seg_n = lag_stats(chan.seg_list, idx_of) zs_pure = chan.cal_bi_zs_list_pure(chan.bi_list) zs_seg = chan.cal_bi_zs(chan.seg_list) rows_struct.append({ "周期": tf, "K线数": len(df), "跨度(天)": round(days), "KLC": len(chan.klc_list), "分型": fx_count, "笔": len(chan.bi_list), "线段": len(chan.seg_list), "笔滞后(中位)": bi_lag_med, "线段滞后(中位)": seg_lag_med, "线段滞后(均值)": round(seg_lag_mean, 1) if seg_lag_mean == seg_lag_mean else np.nan, }) rows_zs.append({ "周期": tf, "pure中枢": len(zs_pure), "seg中枢": len(zs_seg), "pure每百天": round(len(zs_pure) / days * 100, 1), "seg每百天": round(len(zs_seg) / days * 100, 1), "笔/中枢": round(len(chan.bi_list) / max(len(zs_pure), 1), 1), }) regimes = classify_regime(zs_pure) cnt = pd.Series(regimes).value_counts() total = max(len(regimes), 1) rows_regime.append({ "周期": tf, "中枢数": len(zs_pure), "盘整": cnt.get("盘整", 0), "上涨趋势": cnt.get("上涨趋势", 0), "下跌趋势": cnt.get("下跌趋势", 0), "趋势占比": f"{(cnt.get('上涨趋势', 0) + cnt.get('下跌趋势', 0)) / total * 100:.0f}%", }) print("########## 1. 各周期结构数量与确认滞后(单位:K线)##########") print(pd.DataFrame(rows_struct).to_string(index=False)) print("\n 说明:笔滞后 = 笔终点到笔被确认之间的K线数;线段同理。") print("\n########## 2. 中枢密度 ##########") print(pd.DataFrame(rows_zs).to_string(index=False)) print("\n########## 3. 趋势 / 盘整分布(pure 中枢,缠论口径)##########") print(pd.DataFrame(rows_regime).to_string(index=False)) # 中枢的时间跨度:BTC 趋势中「难有大级别中枢」的直接证据 print("\n########## 4. 中枢持续时间分布(pure 中枢,单位:K线)##########") rows = [] for tf in TFS: df = fetch_ohlcv(SYMBOL, tf, 10**9) idx_of = {t: i for i, t in enumerate(df["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))} chan = TF_DF(df, 1, tf) zs_list = chan.cal_bi_zs_list_pure(chan.bi_list) spans = [] for zs in zs_list: bis = getattr(zs, "bi_list", []) if not bis: continue s, e = str(bis[0].start_time), str(bis[-1].end_time) if s in idx_of and e in idx_of: spans.append(idx_of[e] - idx_of[s]) if spans: a = np.array(spans) rows.append({ "周期": tf, "中枢数": len(a), "跨度中位": int(np.median(a)), "跨度均值": round(a.mean(), 1), "最短": int(a.min()), "最长": int(a.max()), "覆盖K线占比": f"{a.sum() / len(df) * 100:.0f}%", }) print(pd.DataFrame(rows).to_string(index=False)) if __name__ == "__main__": main()