refactor: 精简仓库为 chanlun 核心与 web 分析,移除威科夫与遗留模块
删除根目录旧 Chan 模块、策略、配置、文档及 wyckoff 相关代码;更新缠论 pipeline 与笔中枢计算;补充 research 研究与 web 测试。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""分型级信号:绕开笔/线段/中枢的确认链,直接用分型 + 背驰做预设转折点。
|
||||
|
||||
动机:笔确认滞后 9~10 根、线段 101~121 根、买卖点 16~21 根,全部超过 alpha 半衰期(4~6 根)。
|
||||
而分型只需右侧 KLC 完成即可确认,滞后通常 1~3 根,是唯一来得及的结构。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
sys.path.insert(0, str(Path(__file__).resolve().parents[2]))
|
||||
|
||||
from chanlun import TF_DF
|
||||
from chanlun.core.ChanEnum import Chan_FX_TYPE
|
||||
|
||||
|
||||
@dataclass
|
||||
class FxSignal:
|
||||
"""一个分型转折信号。confirm_idx 是实时可交易时刻。"""
|
||||
|
||||
direction: int # +1 底分型(潜在买) / -1 顶分型(潜在卖)
|
||||
fx_idx: int # 分型极值所在K线
|
||||
confirm_idx: int # 右侧KLC完成、分型可被确认的K线
|
||||
lag: int # confirm_idx - fx_idx
|
||||
price: float # 分型极值价
|
||||
confirm_price: float # 确认时刻收盘价
|
||||
seg_macd_area: float # 本段(前一反向分型 -> 本分型)的 MACD 面积
|
||||
prev_seg_macd_area: float # 前一个同向段的 MACD 面积
|
||||
prev_extreme: float # 前一个同向分型的极值价
|
||||
is_divergence: bool # 是否背驰:价格创新极值但力度衰减
|
||||
ratio: float # 面积比 本段/前段,越小背驰越强
|
||||
|
||||
|
||||
def extract_fx_signals(chan: TF_DF, df: pd.DataFrame) -> list[FxSignal]:
|
||||
"""从已建好的缠论结构里抽取分型信号,并就地算好背驰。"""
|
||||
idx_of = {t: i for i, t in enumerate(df["date"].dt.strftime("%Y-%m-%d %H:%M:%S"))}
|
||||
n = len(df)
|
||||
closes = df["close"].to_numpy(dtype=float)
|
||||
macdhist = (
|
||||
df["macdhist"].to_numpy(dtype=float)
|
||||
if "macdhist" in df.columns
|
||||
else np.zeros(n)
|
||||
)
|
||||
if "macdhist" not in df.columns and hasattr(chan, "dataframe"):
|
||||
if "macdhist" in chan.dataframe.columns:
|
||||
macdhist = chan.dataframe["macdhist"].to_numpy(dtype=float)
|
||||
|
||||
# 按时间收集已成型的分型
|
||||
raw = []
|
||||
for klc in chan.klc_list:
|
||||
if klc.fx not in (Chan_FX_TYPE.TOP, Chan_FX_TYPE.BOTTOM):
|
||||
continue
|
||||
if klc.next is None or klc.next.end_klu is None:
|
||||
continue
|
||||
e_key = str(klc.end_time)
|
||||
c_key = str(klc.next.end_klu.time)
|
||||
if e_key not in idx_of or c_key not in idx_of:
|
||||
continue
|
||||
fx_idx = idx_of[e_key]
|
||||
confirm_idx = idx_of[c_key]
|
||||
if confirm_idx <= fx_idx:
|
||||
continue
|
||||
d = 1 if klc.fx == Chan_FX_TYPE.BOTTOM else -1
|
||||
raw.append({
|
||||
"d": d,
|
||||
"fx_idx": fx_idx,
|
||||
"confirm_idx": confirm_idx,
|
||||
"price": float(klc.low if d == 1 else klc.high),
|
||||
})
|
||||
|
||||
raw.sort(key=lambda r: r["fx_idx"])
|
||||
|
||||
def area(lo: int, hi: int, sign: int) -> float:
|
||||
"""区间内顺方向的 MACD 柱面积。sign=-1 取负柱(下跌段),+1 取正柱。"""
|
||||
if hi <= lo:
|
||||
return 0.0
|
||||
seg = macdhist[lo : hi + 1]
|
||||
vals = seg[seg < 0] if sign < 0 else seg[seg > 0]
|
||||
return float(np.abs(vals).sum())
|
||||
|
||||
signals: list[FxSignal] = []
|
||||
for i, r in enumerate(raw):
|
||||
d = r["d"]
|
||||
# 本段起点 = 上一个反向分型;前一同向段 = 再往前一组
|
||||
prev_opp = None
|
||||
prev_same = None
|
||||
prev_opp2 = None
|
||||
for j in range(i - 1, -1, -1):
|
||||
if prev_opp is None and raw[j]["d"] == -d:
|
||||
prev_opp = raw[j]
|
||||
continue
|
||||
if prev_opp is not None and prev_same is None and raw[j]["d"] == d:
|
||||
prev_same = raw[j]
|
||||
continue
|
||||
if prev_same is not None and prev_opp2 is None and raw[j]["d"] == -d:
|
||||
prev_opp2 = raw[j]
|
||||
break
|
||||
if prev_opp is None:
|
||||
continue
|
||||
|
||||
sign = -1 if d == 1 else 1 # 底分型前是下跌段,取负柱
|
||||
cur_area = area(prev_opp["fx_idx"], r["fx_idx"], sign)
|
||||
prev_area = (
|
||||
area(prev_opp2["fx_idx"], prev_same["fx_idx"], sign)
|
||||
if (prev_same is not None and prev_opp2 is not None)
|
||||
else 0.0
|
||||
)
|
||||
|
||||
# 背驰:价格创新极值(底更低 / 顶更高)但力度反而衰减
|
||||
new_extreme = False
|
||||
prev_extreme = np.nan
|
||||
if prev_same is not None:
|
||||
prev_extreme = prev_same["price"]
|
||||
new_extreme = (
|
||||
r["price"] <= prev_extreme if d == 1 else r["price"] >= prev_extreme
|
||||
)
|
||||
ratio = cur_area / prev_area if prev_area > 0 else np.nan
|
||||
is_div = bool(new_extreme and prev_area > 0 and cur_area < prev_area)
|
||||
|
||||
signals.append(
|
||||
FxSignal(
|
||||
direction=d,
|
||||
fx_idx=r["fx_idx"],
|
||||
confirm_idx=r["confirm_idx"],
|
||||
lag=r["confirm_idx"] - r["fx_idx"],
|
||||
price=r["price"],
|
||||
confirm_price=float(closes[r["confirm_idx"]]),
|
||||
seg_macd_area=cur_area,
|
||||
prev_seg_macd_area=prev_area,
|
||||
prev_extreme=float(prev_extreme) if prev_extreme == prev_extreme else np.nan,
|
||||
is_divergence=is_div,
|
||||
ratio=float(ratio) if ratio == ratio else np.nan,
|
||||
)
|
||||
)
|
||||
return signals
|
||||
|
||||
|
||||
def signals_to_frame(signals: list[FxSignal]) -> pd.DataFrame:
|
||||
return pd.DataFrame([s.__dict__ for s in signals])
|
||||
|
||||
|
||||
def add_forward_returns(
|
||||
sig: pd.DataFrame, df: pd.DataFrame, horizons=(3, 5, 10, 20, 40)
|
||||
) -> pd.DataFrame:
|
||||
"""以 confirm_idx 收盘价入场的方向调整收益。"""
|
||||
closes = df["close"].to_numpy(dtype=float)
|
||||
n = len(df)
|
||||
out = sig.copy()
|
||||
for h in horizons:
|
||||
vals = []
|
||||
for i, d in zip(out["confirm_idx"], out["direction"]):
|
||||
j = int(i) + h
|
||||
vals.append(d * (closes[j] - closes[int(i)]) / closes[int(i)] if j < n else np.nan)
|
||||
out[f"ret_{h}"] = vals
|
||||
return out
|
||||
Reference in New Issue
Block a user