Files
Chan/research/step1b_entry_diagnosis.py
T
jackyu66gitandCursor 7f393b93ed refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块
删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-27 01:05:12 +08:00

109 lines
4.2 KiB
Python

"""Step 1b:诊断「多空双向短期均亏」的成因。
假设:sure_time 那根确认K线本身就是一段快速行情的末端,
在它的收盘价入场等于追单,因此无论方向下一根都倾向回撤。
检验办法:看 entry bar 收盘价在其前后窗口里的分位位置。
"""
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.bsp_eval import forward_returns, run_pipeline, to_events
from lib.data import fetch_ohlcv
pd.set_option("display.width", 220)
def main() -> None:
symbol, tf = "BTC/USDT:USDT", "1h"
df = fetch_ohlcv(symbol, tf, 50000)
chan, bsp_list = run_pipeline(df, tf)
events = to_events(bsp_list, df)
fwd = forward_returns(events, df)
closes = df["close"].to_numpy(dtype=float)
n = len(df)
look = 12 # 回看窗口
rows = []
for ev in events:
i = ev.entry_idx
if i - look < 0 or i + look >= n:
continue
past = closes[i - look : i + 1]
# 入场价在过去 look 根里的分位:1.0=最高
pct_past = float((past <= closes[i]).mean())
# 顺信号方向的分位:做多时越靠近区间高点越像追高
dir_pct = pct_past if ev.direction == 1 else 1.0 - pct_past
# 确认K线自身的涨跌幅,及其相对近期波动的倍数
bar_ret = (closes[i] - closes[i - 1]) / closes[i - 1]
vol = np.std(np.diff(past) / past[:-1], ddof=1)
rows.append(
{
"bsp_type": ev.bsp_type,
"side": "LONG" if ev.direction == 1 else "SHORT",
"dir_pct": dir_pct,
"bar_ret_signed": ev.direction * bar_ret,
"bar_ret_z": ev.direction * bar_ret / vol if vol > 0 else np.nan,
# 从分型点到入场点,价格已经走掉多少(信号被吃掉的部分)
"move_since_fx": ev.direction
* (closes[i] - closes[i - ev.lag_bars])
/ closes[i - ev.lag_bars],
"lag_bars": ev.lag_bars,
}
)
diag = pd.DataFrame(rows)
print(f"[样本] n={len(diag)}\n")
print("[入场价在过去12根中的顺向分位] 0.5=中性, >0.5=追单(做多买在高位/做空卖在低位)")
print(f" 全体均值 = {diag['dir_pct'].mean():.3f} 中位数 = {diag['dir_pct'].median():.3f}")
print(diag.groupby("side")["dir_pct"].agg(["count", "mean", "median"]).to_string())
print()
print("[确认K线自身的顺向涨跌] 正=确认那根K线就在朝信号方向猛冲")
print(f" 均值 = {diag['bar_ret_signed'].mean() * 100:+.3f}% z均值 = {diag['bar_ret_z'].mean():+.2f}")
print(diag.groupby("side")["bar_ret_signed"].agg(["mean", "median"]).map(lambda v: f"{v*100:+.3f}%").to_string())
print()
print("[从分型点到确认点,价格已顺向走掉多少] 这是滞后吃掉的利润")
print(f" 均值 = {diag['move_since_fx'].mean() * 100:+.2f}% 中位数 = {diag['move_since_fx'].median() * 100:+.2f}%")
print(
diag.groupby("bsp_type")[["move_since_fx", "lag_bars"]]
.agg({"move_since_fx": "mean", "lag_bars": "mean"})
.assign(move_since_fx=lambda d: (d["move_since_fx"] * 100).map(lambda v: f"{v:+.2f}%"))
.to_string()
)
print()
# 对照:若能在分型点入场(理想但不可实现),收益如何
ideal = []
for ev in events:
i = ev.entry_idx - ev.lag_bars # 分型所在K线
if i < 0:
continue
p0 = closes[i]
for h in (1, 5, 10, 20, 40):
j = i + h
if j < n:
ideal.append({"h": h, "ret": ev.direction * (closes[j] - p0) / p0})
idf = pd.DataFrame(ideal)
print("[对照] 假想在分型点入场(含未来函数,仅作上界参考)")
print(
idf.groupby("h")["ret"]
.agg(n="count", mean="mean", winrate=lambda s: (s > 0).mean())
.assign(mean=lambda d: (d["mean"] * 100).map(lambda v: f"{v:+.2f}%"),
winrate=lambda d: (d["winrate"] * 100).map(lambda v: f"{v:.0f}%"))
.to_string()
)
if __name__ == "__main__":
main()