"""买卖点 × MACD:几何候选在 core,背驰确认在此接合。""" from __future__ import annotations from dataclasses import dataclass from typing import Any, List, Optional, Sequence from ..core.ChanBI import ChanBI from ..core.ChanBSP import ChanBSP from ..core.ChanEnum import Chan_BI_DIR, Chan_BSP_DIR, Chan_BSP_TYPE from ..indicators.store import IndicatorStore def bi_macd_area(bi: ChanBI, store: Optional[IndicatorStore] = None) -> float: """笔内同向 macdhist 累积面积。优先用 IndicatorStore,否则回退 klu.macdhist。""" area = 0.0 for klc in bi.klc_list: for klu in klc.klu_list: if store is not None: hist = store.get(klu.idx, "macdhist", 0) or 0 else: hist = getattr(klu, "macdhist", 0) or 0 try: hist = float(hist) except (TypeError, ValueError): hist = 0.0 if bi.dir == Chan_BI_DIR.UP and hist > 0: area += hist elif bi.dir == Chan_BI_DIR.DOWN and hist < 0: area -= hist return area def check_bi_div( zs, leave_bi: ChanBI, store: Optional[IndicatorStore] = None, ) -> bool: """一类买卖点背驰:离开笔相对进入笔同向 MACD 柱面积收敛。""" enter_bi = zs.bi_list[0].pre if zs.bi_list else None if not enter_bi or enter_bi.dir != leave_bi.dir: return False leave_area = abs(bi_macd_area(leave_bi, store)) enter_area = abs(bi_macd_area(enter_bi, store)) return leave_area < enter_area def check_bi_pair_div( leave_bi: ChanBI, compare_bi: ChanBI, store: Optional[IndicatorStore] = None, ) -> bool: """两笔同向力度比较(离开笔面积 < 比较笔)。""" leave_area = abs(bi_macd_area(leave_bi, store)) compare_area = abs(bi_macd_area(compare_bi, store)) return leave_area < compare_area @dataclass class ConfirmedBSP: """几何买卖点 + MACD 确认结果。""" bsp: ChanBSP div_confirmed: bool hist_area: float = 0.0 compare_hist_area: float = 0.0 score: float = 0.0 macd_state: Any = None @property def type(self) -> Chan_BSP_TYPE: return self.bsp.type @property def dir(self) -> Chan_BSP_DIR: return self.bsp.dir @property def bi(self) -> ChanBI: return self.bsp.bi def confirm_bsp( geo_bsp_list: Sequence[ChanBSP], store: Optional[IndicatorStore] = None, require_div_for_types: Optional[Sequence[Chan_BSP_TYPE]] = None, ) -> List[ConfirmedBSP]: """ 将几何 BSP 升格为 ConfirmedBSP。 默认:B1/S1/T1 需要背驰确认;B3/S3 几何即可,div_confirmed=True。 """ if require_div_for_types is None: require_div_for_types = ( Chan_BSP_TYPE.B1, Chan_BSP_TYPE.S1, ) out: List[ConfirmedBSP] = [] for bsp in geo_bsp_list: area = bi_macd_area(bsp.bi, store) need_div = bsp.type in require_div_for_types if need_div and bsp.zs is not None: div_ok = check_bi_div(bsp.zs, bsp.bi, store) else: div_ok = True out.append( ConfirmedBSP( bsp=bsp, div_confirmed=div_ok, hist_area=area, score=1.0 if div_ok else 0.0, ) ) return out