- _push_bsp() 提取重复的key构造+推送逻辑 - bsp.klc None防护 - getattr替代hasattr+属性访问 - 修正首轮日志(不再写"不推送") Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
146 lines
5.0 KiB
Python
146 lines
5.0 KiB
Python
"""
|
|
实时中枢特征跟踪器
|
|
Real-time Pivot Feature Tracker
|
|
|
|
定位: 观察者 — 不修改管线,只观察 bi_zs_list 中当前中枢的特征变化。
|
|
每次管线重算后调用 update(),检测 bi_count 是否增长,若增长则重新计算
|
|
shift / contraction / duration。
|
|
"""
|
|
|
|
from collections import deque
|
|
from typing import Optional
|
|
from ChanPivotClassifier import ChanPivotClassifier
|
|
|
|
|
|
class ChanPivotMonitor:
|
|
"""
|
|
实时追踪当前中枢的结构特征。
|
|
|
|
update() 每次管线重算后调用,对比 bi_count 判断是否有新笔加入中枢。
|
|
若 bi_count 增长则重新计算 3 个结构特征并返回最新值。
|
|
"""
|
|
|
|
def __init__(self, window_size: int = 10):
|
|
self._window_size = window_size
|
|
self._duration_history: deque[int] = deque(maxlen=window_size)
|
|
self._current_zs_id: Optional[tuple] = None
|
|
self._current_bi_count: int = 0
|
|
self._current_is_sure: bool = False
|
|
self._current_state: Optional[dict] = None
|
|
self._duration_added_for_zs: set = set() # 已加入窗口的中枢 ID(上限 200)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Public API
|
|
# ------------------------------------------------------------------
|
|
|
|
def update(self, bi_zs_list: list) -> Optional[dict]:
|
|
"""
|
|
主入口:检测当前中枢特征变化。
|
|
|
|
参数:
|
|
bi_zs_list: 当前管线产出的笔中枢列表
|
|
|
|
返回:
|
|
特征 dict(有变化时),无变化返回 None
|
|
"""
|
|
if not bi_zs_list:
|
|
self._current_zs_id = None
|
|
self._current_bi_count = 0
|
|
self._current_is_sure = False
|
|
self._current_state = None
|
|
return None
|
|
|
|
zs = self._find_current_zs(bi_zs_list)
|
|
if zs is None:
|
|
return None
|
|
|
|
zs_id = self._make_zs_id(zs)
|
|
bi_count = len(zs.bi_list)
|
|
is_sure = zs.is_sure
|
|
|
|
# 无变化 → 跳过
|
|
if (zs_id == self._current_zs_id
|
|
and bi_count == self._current_bi_count
|
|
and is_sure == self._current_is_sure):
|
|
return None
|
|
|
|
# 中枢切换 → 将旧中枢 duration 加入窗口
|
|
if zs_id != self._current_zs_id:
|
|
self._maybe_add_to_history()
|
|
|
|
self._current_zs_id = zs_id
|
|
self._current_bi_count = bi_count
|
|
self._current_is_sure = is_sure
|
|
|
|
features = ChanPivotClassifier.compute_features(
|
|
zs, list(self._duration_history)
|
|
)
|
|
|
|
self._current_state = {
|
|
"zs_id": zs_id,
|
|
"zs_index": zs.index,
|
|
"zs_dir": str(zs.dir),
|
|
"bi_count": bi_count,
|
|
"is_sure": zs.is_sure,
|
|
"zg": round(zs.zg, 6),
|
|
"zd": round(zs.zd, 6),
|
|
"gg": round(zs.gg, 6),
|
|
"dd": round(zs.dd, 6),
|
|
**features,
|
|
"start_time": str(t) if (t := getattr(zs, "start_time", None)) else None,
|
|
}
|
|
|
|
# 中枢刚变为已确认时,将其 duration 加入滚动窗口
|
|
if is_sure and zs_id not in self._duration_added_for_zs:
|
|
self._add_duration(features["duration_raw"])
|
|
self._duration_added_for_zs.add(zs_id)
|
|
|
|
return self._current_state
|
|
|
|
def get_current(self) -> Optional[dict]:
|
|
"""返回当前中枢的最新特征"""
|
|
return self._current_state
|
|
|
|
def get_duration_history(self) -> list[int]:
|
|
"""返回用于归一化的 duration 滚动窗口"""
|
|
return list(self._duration_history)
|
|
|
|
# ------------------------------------------------------------------
|
|
# Internal
|
|
# ------------------------------------------------------------------
|
|
|
|
@staticmethod
|
|
def _make_zs_id(zs) -> tuple:
|
|
"""生成中枢的稳定标识(基于首笔首K线时间戳,不随 DataFrame 窗口偏移而变化)"""
|
|
bi0 = zs.bi_list[0]
|
|
return (bi0.start_klc.start_time,)
|
|
|
|
@staticmethod
|
|
def _find_current_zs(bi_zs_list: list):
|
|
"""
|
|
找到当前活跃中枢:
|
|
优先取最后一个 is_sure=False(形成中)的中枢,
|
|
没有则取最后一个 is_sure=True 的中枢。
|
|
"""
|
|
forming = None
|
|
last_sure = None
|
|
for zs in bi_zs_list:
|
|
if len(zs.bi_list) < 3:
|
|
continue
|
|
if not zs.is_sure:
|
|
forming = zs
|
|
else:
|
|
last_sure = zs
|
|
return forming if forming is not None else last_sure
|
|
|
|
def _add_duration(self, duration_raw: int):
|
|
"""将已确认中枢的 duration 加入滚动窗口"""
|
|
self._duration_history.append(duration_raw)
|
|
|
|
def _maybe_add_to_history(self):
|
|
"""旧中枢切换前,若已确认且未记录过,则将其 duration 加入窗口"""
|
|
if (self._current_state and self._current_state["is_sure"]
|
|
and self._current_zs_id not in self._duration_added_for_zs):
|
|
self._add_duration(self._current_state["duration_raw"])
|
|
self._duration_added_for_zs.add(self._current_zs_id)
|