From dd40f9574205ff3e15c60ac24f48f0857d22989d Mon Sep 17 00:00:00 2001 From: YuWuKunCheng Date: Sat, 6 Jun 2026 11:04:11 +0800 Subject: [PATCH] =?UTF-8?q?=E8=A7=82=E5=AF=9F=E8=80=85=20=E7=BA=BF?= =?UTF-8?q?=E6=AE=B5=E5=88=86=E6=9E=90/=E6=89=A9=E5=B1=95=E5=88=86?= =?UTF-8?q?=E6=9E=90=20=E6=94=AF=E6=8C=81=E6=97=A0=E9=99=90=E9=80=92?= =?UTF-8?q?=E5=BD=92=EF=BC=8C=E4=BF=9D=E7=95=99=E4=B9=8B=E5=89=8D=E5=BA=8F?= =?UTF-8?q?=E5=88=97=E4=B8=BA@property=20=E5=BD=A2=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chanlun-py/Cargo.toml | 2 +- chanlun-py/chanlun.pyi | 2 - chanlun-py/chanlun/__init__.py | 3 +- chanlun-py/chanlun/__init__.pyi | 2 - chanlun-py/chanlun/chan.py | 313 +++++++++------- chanlun-py/pyproject.toml | 2 +- chanlun-py/src/business_py.rs | 128 ++++++- chanlun-py/src/equality_py.rs | 440 +++++++++++----------- chanlun/src/algorithm/segment.rs | 1 + chanlun/src/business/observer.rs | 527 ++++++++++++++------------ chanlun/src/main.rs | 14 +- main.py | 611 ++----------------------------- strategies.py | 4 +- 13 files changed, 838 insertions(+), 1211 deletions(-) diff --git a/chanlun-py/Cargo.toml b/chanlun-py/Cargo.toml index eb97c49..9d9e328 100644 --- a/chanlun-py/Cargo.toml +++ b/chanlun-py/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chanlun-py" -version = "26.6.17" +version = "26.6.34" edition = "2024" description = "缠论技术分析库 — Rust 高性能 Python 绑定" authors = ["YuYuKunKun"] diff --git a/chanlun-py/chanlun.pyi b/chanlun-py/chanlun.pyi index c581f47..2fc9fe2 100644 --- a/chanlun-py/chanlun.pyi +++ b/chanlun-py/chanlun.pyi @@ -6,8 +6,6 @@ from datetime import datetime # ========== Module-level functions ========== -def get_rs_log_level() -> str: ... -def set_rs_log_level(level: str) -> None: ... def get_log_level() -> str: ... def set_log_level(level: str) -> None: ... def get_分型模式() -> bool: ... diff --git a/chanlun-py/chanlun/__init__.py b/chanlun-py/chanlun/__init__.py index d79315b..7e6ec1f 100644 --- a/chanlun-py/chanlun/__init__.py +++ b/chanlun-py/chanlun/__init__.py @@ -14,7 +14,6 @@ __all__ = [ "指标容器", "指标计算器", "均线工具", - "测试_读取数据", "相对强弱指数", "相对方向", "立体分析器", @@ -49,7 +48,7 @@ __all__ = [ from ._chanlun import * from ._chanlun import set_log_level as _rs_set_log_level, get_log_level as _rs_get_log_level from . import chan -from .chan import 测试_读取数据, 转化为时间戳, 转化为时间戳_数字, set_log_level, get_log_level +from .chan import set_log_level, get_log_level def set_rs_log_level(level: str): diff --git a/chanlun-py/chanlun/__init__.pyi b/chanlun-py/chanlun/__init__.pyi index 1a5a5be..3ea5b94 100644 --- a/chanlun-py/chanlun/__init__.pyi +++ b/chanlun-py/chanlun/__init__.pyi @@ -11,8 +11,6 @@ def get_log_level() -> str: ... def set_log_level(level: str) -> None: ... def get_分型模式() -> bool: ... def set_分型模式(value: bool) -> None: ... -def 转化为时间戳(ts: Any) -> int: ... -def 转化为时间戳_数字(ts: Any) -> int: ... def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... def 缠论K线相等(A: 缠论K线, B: 缠论K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... def 分型相等(A: 分型, B: 分型, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... diff --git a/chanlun-py/chanlun/chan.py b/chanlun-py/chanlun/chan.py index 9bb3f88..6cf1be2 100644 --- a/chanlun-py/chanlun/chan.py +++ b/chanlun-py/chanlun/chan.py @@ -2440,7 +2440,7 @@ class K线: ) @classmethod - def 创建普K(cls, 标识: str, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: int, 周期: int) -> K线: + def 创建普K(cls, 标识: str, 时间戳: datetime | int, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: int, 周期: int) -> K线: """快捷构造普通K线 :param 标识: K线标识符 @@ -2458,7 +2458,7 @@ class K线: 标识=标识, 序号=序号, 周期=周期, - 时间戳=时间戳, + 时间戳=时间戳 if isinstance(时间戳, datetime) else 转化为时间戳(时间戳), # 注意此处只为兼容Rust绑定 开盘价=开盘价, 最高价=最高价, 最低价=最低价, @@ -3520,7 +3520,6 @@ class 虚线: return 结果 @classmethod - @lru_cache(maxsize=128) def 买卖意义(cls, 实线: 虚线, 观察员: 观察者) -> Tuple[bool, str]: """ 静止是相对的,而运动是绝对的 @@ -4775,7 +4774,7 @@ class 线段: @classmethod def _缺口突破(cls, 线段序列: List[虚线], 配置: 缠论配置, 层级: int) -> bool: """内部方法:处理缺口突破修正, 正常修正 - + 除此之外的修正皆为短路修正,此修正为正常处理 :param 线段序列: 线段列表 :param 配置: 缠论配置 :param 层级: 递归深度 @@ -4870,6 +4869,7 @@ class 线段: for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]: 线段._添加虚线(当前线段, 临时虚线) 线段._刷新(当前线段, 配置) + 当前线段.短路修正 = True if 当前线段.特征序列[2] is not None: 段 = 虚线.创建线段([左, 中, 右]) @@ -4991,6 +4991,12 @@ class 线段: return None # raise RuntimeError("线段分析 层级过深") + # if len(笔序列) < 3:return None + try: + 笔序列[2] + except IndexError: + return None + 线段递归分析 = 线段.分析 # -------------------- 1. 初始化第一个线段 -------------------- @@ -5200,7 +5206,7 @@ class 线段: if not 配置.扩展线段_当下分析: 左, 中, 右 = 当前线段.基础序列[:3] if not 相对方向.分析(左.高, 左.低, 右.高, 右.低).是否缺口(): - 当前线段.基础序列[:] = 当前线段.笔序列[:3] + 当前线段.基础序列[:] = 当前线段.基础序列[:3] 线段._武终(当前线段, sys._getframe().f_lineno) else: 线段._弹出扩展线段(线段序列, 当前线段, sys._getframe().f_lineno) @@ -5231,7 +5237,6 @@ class 线段: return 线段递归扩展分析(虚线序列, 线段序列, 配置) @classmethod - @lru_cache(maxsize=128) def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者) -> bool: """判断线段内部是否发生背驰(基于内部中枢和MACD) @@ -5535,11 +5540,10 @@ class 中枢: 线段.扩展分析(self.基础序列, 扩展线段, 配置) 中枢.分析(扩展线段, 扩展中枢, False, f"{self.标识}_扩展中枢_") - def _校验合法性(self, 序列: Sequence[虚线], 中枢序列: List[中枢]) -> bool: + def _校验合法性(self, 序列: Sequence[虚线]) -> bool: """校验当前中枢在给定序列中是否仍然合法 :param 序列: 基础虚线序列 - :param 中枢序列: 中枢列表 :return: 合法为True,不合法会原地裁剪基础序列 """ 有效序列 = self.基础序列[:] @@ -5754,7 +5758,7 @@ class 中枢: 当前中枢 = 中枢序列[-1] - if not 当前中枢._校验合法性(虚线序列, 中枢序列): + if not 当前中枢._校验合法性(虚线序列): 中枢._从中枢序列尾部弹出(中枢序列, 当前中枢) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) @@ -5829,8 +5833,41 @@ class 观察者: self.配置: 缠论配置 = 配置 self.__终止时间戳: Optional[datetime] = 转化为时间戳(self.配置.手动终止) if self.配置.手动终止 else None + self.线段分析层次 = 3 + self.扩展线段分析层次 = 3 + self.混合扩展线段分析层次 = 3 self.重置基础序列() + def 重置基础序列(self): + """清空所有分析序列,重置为初始状态""" + self.基础缠K序列: List[缠论K线] = [] + + self.普通K线序列: List[K线] = [] + self.缠论K线序列: List[缠论K线] = [] + + self.分型序列: List[分型] = [] + + self.笔序列: List[虚线] = [] + self.笔_中枢序列: List[中枢] = [] + + self.线段序列组: List[List[虚线],] = [] # 线段, 线段<线段>,线段<线段<线段>>... + self.中枢序列组: List[List[中枢],] = [] + for i in range(self.线段分析层次): + self.线段序列组.append(list()) + self.中枢序列组.append(list()) + + self.扩展线段序列组: List[List[虚线],] = [] # 扩展线段, 扩展线段<扩展线段>, 扩展线段<扩展线段<扩展线段>>... + self.扩展中枢序列组: List[List[中枢],] = [] + for i in range(self.扩展线段分析层次): + self.扩展线段序列组.append(list()) + self.扩展中枢序列组.append(list()) + + self.混合扩展线段序列组: List[List[虚线],] = [] # 扩展线段<线段>, 扩展线段<线段<线段>>, 扩展线段<线段<线段<线段>>>... + self.混合扩展中枢序列组: List[List[中枢],] = [] + for i in range(self.混合扩展线段分析层次): + self.混合扩展线段序列组.append(list()) + self.混合扩展中枢序列组.append(list()) + @property def 观察员(self): """观察员(自引用)""" @@ -5857,32 +5894,45 @@ class 观察者: """ return self.缠论K线序列[-1] if self.缠论K线序列 else None - def 重置基础序列(self): - """清空所有分析序列,重置为初始状态""" - self.基础缠K序列: List[缠论K线] = [] + @property + def 线段序列(self) -> List[虚线]: + return self.线段序列组[0] - self.普通K线序列: List[K线] = [] - self.缠论K线序列: List[缠论K线] = [] + @property + def 中枢序列(self) -> List[中枢]: + return self.中枢序列组[0] - self.分型序列: List[分型] = [] + @property + def 扩展线段序列(self) -> List[虚线]: + return self.扩展线段序列组[0] - self.笔序列: List[虚线] = [] - self.笔_中枢序列: List[中枢] = [] + @property + def 扩展中枢序列(self) -> List[中枢]: + return self.扩展中枢序列组[0] - self.线段序列: List[虚线] = [] - self.中枢序列: List[中枢] = [] + @property + def 扩展线段序列_线段(self) -> List[虚线]: + return self.混合扩展线段序列组[0] - self.扩展线段序列: List[虚线] = [] - self.扩展中枢序列: List[中枢] = [] + @property + def 扩展中枢序列_线段(self) -> List[中枢]: + return self.混合扩展中枢序列组[0] - self.扩展线段序列_线段: List[虚线] = [] - self.扩展中枢序列_线段: List[中枢] = [] + @property + def 线段_线段序列(self) -> List[虚线]: + return self.线段序列组[1] - self.线段_线段序列: List[虚线] = [] - self.线段_中枢序列: List[中枢] = [] + @property + def 线段_中枢序列(self) -> List[中枢]: + return self.中枢序列组[1] - self.扩展线段序列_扩展线段: List[虚线] = [] - self.扩展中枢序列_扩展线段: List[中枢] = [] + @property + def 扩展线段序列_扩展线段(self) -> List[虚线]: + return self.扩展线段序列组[1] + + @property + def 扩展中枢序列_扩展线段(self) -> List[中枢]: + return self.扩展中枢序列组[1] def 投喂原始数据(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float): """便捷入口,直接从 OHLCV 创建 K线 并投喂 @@ -5912,28 +5962,33 @@ class 观察者: if 当前分型 is None: return - self.配置.分析笔 and 笔.分析(当前分型, self.分型序列, self.笔序列, self.缠论K线序列, self.普通K线序列, 0, self.配置) + 笔.分析(当前分型, self.分型序列, self.笔序列, self.缠论K线序列, self.普通K线序列, 0, self.配置) if not self.分型序列: return - self.配置.分析笔中枢 and 中枢.分析(self.笔序列, self.笔_中枢序列) + 中枢.分析(self.笔序列, self.笔_中枢序列, True, "", 0) if not self.笔序列: return - self.配置.分析线段 and 线段.分析(self.笔序列, self.线段序列, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.线段序列, self.中枢序列) + for i in range(self.线段分析层次): + if i == 0: + 线段.分析(self.笔序列, self.线段序列组[i], self.配置) + 中枢.分析(self.线段序列组[i], self.中枢序列组[i], True, "", 0) + continue + 线段.分析(self.线段序列组[i - 1], self.线段序列组[i], self.配置) + 中枢.分析(self.线段序列组[i], self.中枢序列组[i], True, "", 0) - self.配置.分析扩展线段 and 线段.扩展分析(self.笔序列, self.扩展线段序列, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.扩展线段序列, self.扩展中枢序列) + for i in range(self.扩展线段分析层次): + if i == 0: + 线段.扩展分析(self.笔序列, self.扩展线段序列组[i], self.配置) + 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) + continue + 线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置) + 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) - self.配置.分析扩展线段 and 线段.扩展分析(self.线段序列, self.扩展线段序列_线段, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.扩展线段序列_线段, self.扩展中枢序列_线段) - - self.配置.分析线段 and 线段.分析(self.线段序列, self.线段_线段序列, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列) - - self.配置.分析扩展线段 and 线段.扩展分析(self.扩展线段序列, self.扩展线段序列_扩展线段, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.扩展线段序列_扩展线段, self.扩展中枢序列_扩展线段) + for i in range(self.混合扩展线段分析层次): + 线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置) + 中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0) def 测试_保存数据(self, root: str = None) -> str: """拆分各序列数据,单独存文件,文件名为对应变量名 @@ -5941,30 +5996,15 @@ class 观察者: :param root: 保存根目录(可选) :return: 保存目录 """ - # 提取各类文本数据 - 笔序列_文本数据 = [筆.获取数据文本() for 筆 in self.笔序列] - 线段序列_文本数据 = [实线.获取数据文本() for 实线 in self.线段序列] - 扩展线段序列_数据文本 = [实线.获取数据文本() for 实线 in self.扩展线段序列] - 扩展线段序列_线段_数据文本 = [实线.获取数据文本() for 实线 in self.扩展线段序列_线段] - 线段_线段序列_数据文本 = [实线.获取数据文本() for 实线 in self.线段_线段序列] - 扩展线段序列_扩展线段_数据文本 = [实线.获取数据文本() for 实线 in self.扩展线段序列_扩展线段] - 笔_中枢序列_数据文本 = [当前中枢.获取数据文本() for 当前中枢 in self.笔_中枢序列] - 中枢序列_数据文本 = [当前中枢.获取数据文本() for 当前中枢 in self.中枢序列] - 扩展中枢序列_数据文本 = [当前中枢.获取数据文本() for 当前中枢 in self.扩展中枢序列] - 扩展中枢序列_线段_数据文本 = [当前中枢.获取数据文本() for 当前中枢 in self.扩展中枢序列_线段] - 线段_中枢序列_数据文本 = [当前中枢.获取数据文本() for 当前中枢 in self.线段_中枢序列] - 扩展中枢序列_扩展线段_数据文本 = [当前中枢.获取数据文本() for 当前中枢 in self.扩展中枢序列_扩展线段] - - # ===================== 优化点:优先使用传入的 root 目录 ===================== if root is not None: # 使用用户指定的根目录 根目录 = Path(root) else: - # 默认:当前脚本所在目录 - 根目录 = Path(__file__).parent + # 默认:系统临时目录 + 根目录 = tempfile.gettempdir() - # 生成子目录名称(不变) + # 生成子目录名称 起始时间 = int(self.普通K线序列[0].时间戳.timestamp()) 结束时间 = int(self.普通K线序列[-1].时间戳.timestamp()) 目录标识 = f"Py_{self.标识}_{起始时间}_{结束时间}" @@ -5973,28 +6013,29 @@ class 观察者: 保存路径 = 根目录 / 目录标识 保存路径.mkdir(exist_ok=True, parents=True) # parents=True 支持多级目录自动创建 - # 映射:变量名 -> 数据列表 - 数据映射 = [ - ("笔序列_文本数据", 笔序列_文本数据), - ("线段序列_文本数据", 线段序列_文本数据), - ("扩展线段序列_数据文本", 扩展线段序列_数据文本), - ("扩展线段序列_线段_数据文本", 扩展线段序列_线段_数据文本), - ("线段_线段序列_数据文本", 线段_线段序列_数据文本), - ("扩展线段序列_扩展线段_数据文本", 扩展线段序列_扩展线段_数据文本), - ("笔_中枢序列_数据文本", 笔_中枢序列_数据文本), - ("中枢序列_数据文本", 中枢序列_数据文本), - ("扩展中枢序列_数据文本", 扩展中枢序列_数据文本), - ("扩展中枢序列_线段_数据文本", 扩展中枢序列_线段_数据文本), - ("线段_中枢序列_数据文本", 线段_中枢序列_数据文本), - ("扩展中枢序列_扩展线段_数据文本", 扩展中枢序列_扩展线段_数据文本), - ] - - # 逐个写入独立文件 - for 文件名, 数据列表 in 数据映射: + def 保存序列(序列): + if len(序列) == 0: + return + 数据列表 = [对象.获取数据文本() for 对象 in 序列] + 文件名 = 序列[0].标识 文件全路径 = 保存路径 / f"{文件名}.txt" + # 逐个写入独立文件 with open(文件全路径, "w", encoding="utf-8") as f: f.write("\n".join(数据列表)) f.write("\n") # 向 C99 对齐 + + 保存序列(self.笔序列) + 保存序列(self.笔_中枢序列) + for i in range(self.线段分析层次): + 保存序列(self.线段序列组[i]) + 保存序列(self.中枢序列组[i]) + for i in range(self.扩展线段分析层次): + 保存序列(self.扩展线段序列组[i]) + 保存序列(self.扩展中枢序列组[i]) + for i in range(self.线段分析层次): + 保存序列(self.混合扩展线段序列组[i]) + 保存序列(self.混合扩展中枢序列组[i]) + logger.warning(f"全部数据拆分保存完成,目录:{保存路径.resolve()}") return str(保存路径.resolve()) @@ -6003,44 +6044,55 @@ class 观察者: pass def 静态重新分析(self): - """静态重新分析(占位方法)""" + """静态重新分析""" self.分型序列: List[分型] = [] self.笔序列: List[虚线] = [] self.笔_中枢序列: List[中枢] = [] - self.线段序列: List[虚线] = [] - self.中枢序列: List[中枢] = [] + self.线段序列组: List[List[虚线],] = [] # 线段, 线段<线段>,线段<线段<线段>>... + self.中枢序列组: List[List[中枢],] = [] + for i in range(self.线段分析层次): + self.线段序列组.append(list()) + self.中枢序列组.append(list()) - self.扩展线段序列: List[虚线] = [] - self.扩展中枢序列: List[中枢] = [] + self.扩展线段序列组: List[List[虚线],] = [] # 扩展线段, 扩展线段<扩展线段>, 扩展线段<扩展线段<扩展线段>>... + self.扩展中枢序列组: List[List[中枢],] = [] + for i in range(self.扩展线段分析层次): + self.扩展线段序列组.append(list()) + self.扩展中枢序列组.append(list()) - self.扩展线段序列_线段: List[虚线] = [] - self.扩展中枢序列_线段: List[中枢] = [] - - self.线段_线段序列: List[虚线] = [] - self.线段_中枢序列: List[中枢] = [] - - self.扩展线段序列_扩展线段: List[虚线] = [] - self.扩展中枢序列_扩展线段: List[中枢] = [] + self.混合扩展线段序列组: List[List[虚线],] = [] # 扩展线段<线段>, 扩展线段<线段<线段>>, 扩展线段<线段<线段<线段>>>... + self.混合扩展中枢序列组: List[List[中枢],] = [] + for i in range(self.混合扩展线段分析层次): + self.混合扩展线段序列组.append(list()) + self.混合扩展中枢序列组.append(list()) for i in range(1, len(self.缠论K线序列) - 1): 当前分型 = 分型(self.缠论K线序列[i - 1], self.缠论K线序列[i], self.缠论K线序列[i + 1]) 笔.分析(当前分型, self.分型序列, self.笔序列, self.缠论K线序列, self.普通K线序列, 0, self.配置) - self.配置.分析笔中枢 and 中枢.分析(self.笔序列, self.笔_中枢序列) + 中枢.分析(self.笔序列, self.笔_中枢序列, True, "", 0) - self.配置.分析线段 and 线段.分析(self.笔序列, self.线段序列, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.线段序列, self.中枢序列) + for i in range(self.线段分析层次): + if i == 0: + 线段.分析(self.笔序列, self.线段序列组[i], self.配置) + 中枢.分析(self.线段序列组[i], self.中枢序列组[i], True, "", 0) + continue + 线段.分析(self.线段序列组[i - 1], self.线段序列组[i], self.配置) + 中枢.分析(self.线段序列组[i], self.中枢序列组[i], True, "", 0) - self.配置.分析扩展线段 and 线段.扩展分析(self.笔序列, self.扩展线段序列, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.扩展线段序列, self.扩展中枢序列) + for i in range(self.扩展线段分析层次): + if i == 0: + 线段.扩展分析(self.笔序列, self.扩展线段序列组[i], self.配置) + 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) + continue + 线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置) + 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) - self.配置.分析扩展线段 and 线段.扩展分析(self.线段序列, self.扩展线段序列_线段, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.扩展线段序列_线段, self.扩展中枢序列_线段) - - self.配置.分析线段 and 线段.分析(self.线段序列, self.线段_线段序列, self.配置) - self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列) + for i in range(self.混合扩展线段分析层次): + 线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置) + 中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0) def 加载本地数据(self, 文件路径: str): """重置基础序列后加载数据文件 @@ -6417,7 +6469,6 @@ def K线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: return True, "K线:全部字段结构、数值校验完全一致" -@lru_cache(4096) def 缠论K线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: """缠论K线:基础字段+标的K线递归校验""" 比对字段 = ["序号", "时间戳", "高", "低", "方向", "分型", "周期", "标识", "分型特征值", "原始起始序号", "原始结束序号", "标的K线", "买卖点信息"] @@ -6470,7 +6521,6 @@ def 缠论K线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: return True, "缠论K线:全部字段、嵌套原始K线校验一致" -@lru_cache(4096) def 分型相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: """分型:左/中/右缠论K线递归 + 自有字段""" 比对字段 = ["左", "中", "右", "_结构", "_时间戳", "_分型特征值"] @@ -6542,18 +6592,17 @@ def 缺口相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: return True, "缺口:上下沿价格校验完全一致" -@lru_cache(4096) def 线段特征相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: """线段特征:基础序列虚线列表逐项校验""" 比对字段 = ["序号", "标识", "线段方向", "基础序列"] - 标签 = f"线段特征校验[A标识={A.标识}, B标识={B.标识}]" + 标签 = f"线段特征校验[A标识={A.标识},B标识={B.标识}]" for 字段 in 比对字段: a有 = hasattr(A, 字段) b有 = hasattr(B, 字段) if a有 and not b有: - return False, f"{标签}:[{字段}]A存在、B缺失属性" + return False, f"{标签}: [{字段}]A存在、B缺失属性" if not a有 and b有: - return False, f"{标签}:[{字段}]B存在、A缺失属性" + return False, f"{标签}: [{字段}]B存在、A缺失属性" if not (a有 and b有): continue @@ -6561,7 +6610,7 @@ def 线段特征相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: valB = getattr(B, 字段) if 字段 == "基础序列": if len(valA) != len(valB): - return False, f"{标签}:[基础序列]列表长度不一致,A长度={len(valA)},B长度={len(valB)}" + return False, f"{标签}: [基础序列]列表长度不一致,A长度={len(valA)},B长度={len(valB)}" for idx, (itemA, itemB) in enumerate(zip(valA, valB)): eq, msg = 虚线相等(itemA, itemB, 浮点容差) if not eq: @@ -6569,25 +6618,24 @@ def 线段特征相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: elif 字段 == "线段方向": if str(valA) != str(valB): - return False, f"{标签}:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + return False, f"{标签}: [{字段}]数值不等,A={repr(valA)},B={repr(valB)}" else: if valA != valB: - return False, f"{标签}:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + return False, f"{标签}: [{字段}]数值不等,A={repr(valA)},B={repr(valB)}" return True, f"{标签}:字段与内部虚线序列全部一致" -@lru_cache(4096) def 中枢相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: """中枢:基础序列虚线列表+第三买卖线单虚线""" 比对字段 = ["序号", "标识", "级别", "基础序列", "第三买卖线", "本级_第三买卖线"] - 标签 = f"中枢校验[A标识={A.标识}, B标识={B.标识}]" + 标签 = f"中枢校验[A标识={A.标识},B标识={B.标识}]" for 字段 in 比对字段: a有 = hasattr(A, 字段) b有 = hasattr(B, 字段) if a有 and not b有: - return False, f"{标签}:[{字段}]A存在、B缺失属性" + return False, f"{标签}: [{字段}]A存在、B缺失属性" if not a有 and b有: - return False, f"{标签}:[{字段}]B存在、A缺失属性" + return False, f"{标签}: [{字段}]B存在、A缺失属性" if not (a有 and b有): continue @@ -6596,7 +6644,7 @@ def 中枢相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: if 字段 == "基础序列": if len(valA) != len(valB): - return False, f"{标签}:[基础序列]长度不一致 A={len(valA)},B={len(valB)}" + return False, f"{标签}: [基础序列]长度不一致 A={len(valA)},B={len(valB)}" for idx, (itemA, itemB) in enumerate(zip(valA, valB)): eq, msg = 虚线相等(itemA, itemB, 浮点容差) if not eq: @@ -6605,29 +6653,28 @@ def 中枢相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: if valA is None and valB is None: continue if valA is None or valB is None: - return False, f"{标签}:[{字段}]空值不一致 A={valA is None},B={valB is None}" + return False, f"{标签}: [{字段}]空值不一致 A={valA is None},B={valB is None}" eq, msg = 虚线相等(valA, valB, 浮点容差) if not eq: - return False, f"{标签}:[{字段}]子虚线异常 >> {msg}" + return False, f"{标签}: [{字段}]子虚线异常 >> {msg}" else: if valA != valB: - return False, f"{标签}:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + return False, f"{标签}: [{字段}]数值不等,A={repr(valA)},B={repr(valB)}" return True, f"{标签}:基础序列+第三买卖线全部校验一致" -@lru_cache(4096) def 虚线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: """虚线(笔/线段):全量字段、分型/缺口/K线/列表嵌套精细化报错""" 比对字段 = ["标识", "序号", "级别", "文", "武", "有效性", "基础序列", "特征序列", "实_中枢序列", "虚_中枢序列", "合_中枢序列", "确认K线", "模式", "_特征序列_显示", "前一缺口", "前一结束位置", "短路修正"] - 标签 = f"虚线校验[A标识={A.标识}, B标识={B.标识}]" + 标签 = f"虚线校验[A标识={A.标识},B标识={B.标识}]" for 字段 in 比对字段: a有 = hasattr(A, 字段) b有 = hasattr(B, 字段) if a有 and not b有: - return False, f"{标签}:[{字段}]A存在属性、B缺失属性" + return False, f"{标签}: [{字段}]A存在属性、B缺失属性" if not a有 and b有: - return False, f"{标签}:[{字段}]B存在属性、A缺失属性" + return False, f"{标签}: [{字段}]B存在属性、A缺失属性" if not (a有 and b有): continue @@ -6639,46 +6686,46 @@ def 虚线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: if valA is None and valB is None: continue if valA is None or valB is None: - return False, f"{标签}:[{字段}]分型空值不一致 A={valA is None},B={valB is None}" + return False, f"{标签}: [{字段}]分型空值不一致 A={valA is None},B={valB is None}" eq, msg = 分型相等(valA, valB, 浮点容差) if not eq: - return False, f"{标签}:[{字段}]子分型异常 >> {msg}" + return False, f"{标签}: [{字段}]子分型异常 >> {msg}" # 前一缺口 elif 字段 == "前一缺口": if valA is None and valB is None: continue if valA is None or valB is None: - return False, f"{标签}:[前一缺口]空值不一致 A={valA is None},B={valB is None}" + return False, f"{标签}: [前一缺口]空值不一致 A={valA is None},B={valB is None}" eq, msg = 缺口相等(valA, valB, 浮点容差) if not eq: - return False, f"{标签}:[前一缺口]子缺口异常 >> {msg}" + return False, f"{标签}: [前一缺口]子缺口异常 >> {msg}" # 前一缺口 elif 字段 == "前一结束位置": if valA is None and valB is None: continue if valA is None or valB is None: - return False, f"{标签}:[前一结束位置]空值不一致 A={valA is None},B={valB is None}" + return False, f"{标签}: [前一结束位置]空值不一致 A={valA is None},B={valB is None}" eq, msg = 虚线相等(valA, valB, 浮点容差) if not eq: - return False, f"{标签}:[前一结束位置]异常 >> {msg}" + return False, f"{标签}: [前一结束位置]异常 >> {msg}" # 确认K线 elif 字段 == "确认K线": if valA is None and valB is None: continue if valA is None or valB is None: - return False, f"{标签}:[确认K线]空值不一致 A={valA is None},B={valB is None}" + return False, f"{标签}: [确认K线]空值不一致 A={valA is None},B={valB is None}" eq, msg = 缠论K线相等(valA, valB, 浮点容差) if not eq: - return False, f"{标签}:[确认K线]子缠论K线异常 >> {msg}" + return False, f"{标签}: [确认K线]子缠论K线异常 >> {msg}" # 各类列表 elif 字段 in ("基础序列", "实_中枢序列", "虚_中枢序列", "合_中枢序列", "特征序列"): if len(valA) != len(valB): - return False, f"{标签}:[{字段}]列表长度不一致 A={len(valA)},B={len(valB)}" + return False, f"{标签}: [{字段}]列表长度不一致 A={len(valA)},B={len(valB)}" for idx, (itemA, itemB) in enumerate(zip(valA, valB)): if itemA is None and itemB is None: continue if itemA is None or itemB is None: - return False, f"{标签}:[{字段}][{idx}]单项空值不一致 A={itemA is None},B={itemB is None}" + return False, f"{标签}: [{字段}][{idx}]单项空值不一致 A={itemA is None},B={itemB is None}" if 字段 == "基础序列": eq, msg = 虚线相等(itemA, itemB, 浮点容差) elif "中枢" in 字段: @@ -6686,11 +6733,11 @@ def 虚线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: else: eq, msg = 线段特征相等(itemA, itemB, 浮点容差) if not eq: - return False, f"{标签}:[{字段}][{idx}]子项异常 >> {msg}" + return False, f"{标签}: [{字段}][{idx}]子项异常 >> {msg}" # 普通字段 else: if valA != valB: - return False, f"{标签}:[{字段}]数值不等 A={repr(valA)},B={repr(valB)}" + return False, f"{标签}: [{字段}]数值不等 A={repr(valA)},B={repr(valB)}" return True, f"{标签}:全字段、所有嵌套子结构校验全部一致" diff --git a/chanlun-py/pyproject.toml b/chanlun-py/pyproject.toml index 275a7f8..354cfe4 100644 --- a/chanlun-py/pyproject.toml +++ b/chanlun-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "chanlun" -version = "2606.21" +version = "2606.34" description = "缠论技术分析库 — Rust 高性能实现" readme = { file = "README.md", content-type = "text/markdown" } license = { file = "LICENSE", content-type = "text/plain" } diff --git a/chanlun-py/src/business_py.rs b/chanlun-py/src/business_py.rs index f50e11d..0224ddd 100644 --- a/chanlun-py/src/business_py.rs +++ b/chanlun-py/src/business_py.rs @@ -675,6 +675,13 @@ impl 买卖点Py { /// 线段_线段序列 / 线段_中枢序列 /// 扩展线段序列_扩展线段 / 扩展中枢序列_扩展线段 /// +/// 分析层次(可读写): +/// 线段分析层次: int (默认 3) / 扩展线段分析层次: int (默认 3) / 混合扩展线段分析层次: int (默认 3) +/// +/// 序列组(只读,返回 list[list[...]]): +/// 线段序列组 / 中枢序列组 / 扩展线段序列组 / 扩展中枢序列组 +/// 混合扩展线段序列组 / 混合扩展中枢序列组 +/// /// 核心方法: /// 增加原始K线(普K) — 喂入一根普通K线,触发全层级增量计算 /// 重置基础序列() — 清空所有计算状态和序列 @@ -1040,7 +1047,7 @@ impl 观察者Py { #[getter] fn 线段序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in &self.obs().线段序列 { + for d in self.obs().线段序列() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1049,7 +1056,7 @@ impl 观察者Py { #[getter] fn 中枢序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in &self.obs().中枢序列 { + for h in self.obs().中枢序列() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -1058,7 +1065,7 @@ impl 观察者Py { #[getter] fn 扩展线段序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in &self.obs().扩展线段序列 { + for d in self.obs().扩展线段序列() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1067,7 +1074,7 @@ impl 观察者Py { #[getter] fn 扩展中枢序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in &self.obs().扩展中枢序列 { + for h in self.obs().扩展中枢序列() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -1076,7 +1083,7 @@ impl 观察者Py { #[getter] fn 扩展线段序列_线段(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in &self.obs().扩展线段序列_线段 { + for d in self.obs().扩展线段序列_线段() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1085,7 +1092,7 @@ impl 观察者Py { #[getter] fn 扩展中枢序列_线段(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in &self.obs().扩展中枢序列_线段 { + for h in self.obs().扩展中枢序列_线段() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -1094,7 +1101,7 @@ impl 观察者Py { #[getter] fn 线段_线段序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in &self.obs().线段_线段序列 { + for d in self.obs().线段_线段序列() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1103,7 +1110,7 @@ impl 观察者Py { #[getter] fn 线段_中枢序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in &self.obs().线段_中枢序列 { + for h in self.obs().线段_中枢序列() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -1112,7 +1119,7 @@ impl 观察者Py { #[getter] fn 扩展线段序列_扩展线段(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in &self.obs().扩展线段序列_扩展线段 { + for d in self.obs().扩展线段序列_扩展线段() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1121,11 +1128,112 @@ impl 观察者Py { #[getter] fn 扩展中枢序列_扩展线段(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in &self.obs().扩展中枢序列_扩展线段 { + for h in self.obs().扩展中枢序列_扩展线段() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) } + + // ---- 分析层次 getter/setter ---- + + #[getter] + fn 线段分析层次(&self) -> usize { + self.obs().线段分析层次 + } + + #[setter] + #[pyo3(name = "线段分析层次")] + fn 设置_线段分析层次(&mut self, value: usize) { + self.obs_mut().线段分析层次 = value; + } + + #[getter] + fn 扩展线段分析层次(&self) -> usize { + self.obs().扩展线段分析层次 + } + + #[setter] + #[pyo3(name = "扩展线段分析层次")] + fn 设置_扩展线段分析层次(&mut self, value: usize) { + self.obs_mut().扩展线段分析层次 = value; + } + + #[getter] + fn 混合扩展线段分析层次(&self) -> usize { + self.obs().混合扩展线段分析层次 + } + + #[setter] + #[pyo3(name = "混合扩展线段分析层次")] + fn 设置_混合扩展线段分析层次(&mut self, value: usize) { + self.obs_mut().混合扩展线段分析层次 = value; + } + + // ---- 序列组 getters (返回 list[list[...]]) ---- + + #[getter] + fn 线段序列组(&self, py: Python<'_>) -> PyResult> { + self._虚线序列组_to_py(&self.obs().线段序列组, py) + } + + #[getter] + fn 中枢序列组(&self, py: Python<'_>) -> PyResult> { + self._中枢序列组_to_py(&self.obs().中枢序列组, py) + } + + #[getter] + fn 扩展线段序列组(&self, py: Python<'_>) -> PyResult> { + self._虚线序列组_to_py(&self.obs().扩展线段序列组, py) + } + + #[getter] + fn 扩展中枢序列组(&self, py: Python<'_>) -> PyResult> { + self._中枢序列组_to_py(&self.obs().扩展中枢序列组, py) + } + + #[getter] + fn 混合扩展线段序列组(&self, py: Python<'_>) -> PyResult> { + self._虚线序列组_to_py(&self.obs().混合扩展线段序列组, py) + } + + #[getter] + fn 混合扩展中枢序列组(&self, py: Python<'_>) -> PyResult> { + self._中枢序列组_to_py(&self.obs().混合扩展中枢序列组, py) + } +} + +impl 观察者Py { + fn _虚线序列组_to_py( + &self, + 组: &Vec>>, + py: Python<'_>, + ) -> PyResult> { + let outer = pyo3::types::PyList::empty(py); + for inner_vec in 组 { + let inner_list = pyo3::types::PyList::empty(py); + for d in inner_vec { + inner_list.append(dashed_to_py(py, Arc::clone(d)))?; + } + outer.append(inner_list)?; + } + Ok(outer.into()) + } + + fn _中枢序列组_to_py( + &self, + 组: &Vec>>, + py: Python<'_>, + ) -> PyResult> { + let outer = pyo3::types::PyList::empty(py); + for inner_vec in 组 { + let inner_list = pyo3::types::PyList::empty(py); + for h in inner_vec { + inner_list.append(hub_to_py(py, Arc::clone(h)))?; + } + outer.append(inner_list)?; + } + Ok(outer.into()) + } } // ========== K线合成器 ========== diff --git a/chanlun-py/src/equality_py.rs b/chanlun-py/src/equality_py.rs index 3eb057e..4b1e2e2 100644 --- a/chanlun-py/src/equality_py.rs +++ b/chanlun-py/src/equality_py.rs @@ -538,89 +538,75 @@ fn 中枢相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_HUB, - 4096, - ( - A.as_ptr() as usize, - B.as_ptr() as usize, - 浮点容差.to_bits() as i64 - ), - { - if let (Ok(a), Ok(b)) = ( - A.cast::(), - B.cast::(), - ) { - return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let a标识 = 尝试获取标识(A); + let b标识 = 尝试获取标识(B); + let 标签 = format!("中枢校验[A标识={a标识},B标识={b标识}]"); + for &字段 in &[ + "序号", + "标识", + "级别", + "基础序列", + "第三买卖线", + "本级_第三买卖线", + ] { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + + if 字段 == "基础序列" { + let len_a: usize = valA.len()?; + let len_b: usize = valB.len()?; + if len_a != len_b { + return Ok(( + false, + format!("{标签}: [基础序列] 长度不一致 A={len_a},B={len_b}"), + )); } - let a标识 = 尝试获取标识(A); - let b标识 = 尝试获取标识(B); - let 标签 = format!("中枢校验[A标识={a标识},B标识={b标识}]"); - for &字段 in &[ - "序号", - "标识", - "级别", - "基础序列", - "第三买卖线", - "本级_第三买卖线", - ] { - let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); - if a有 && !b有 { - return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性"))); + for idx in 0..len_a { + let itemA = valA.get_item(idx)?; + let itemB = valB.get_item(idx)?; + let (eq, msg) = 虚线相等(&itemA, &itemB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: 基础序列[{idx}]虚线异常 >> {msg}"))); } - if !a有 && b有 { - return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性"))); - } - if !a有 && !b有 { + } + } else if 字段 == "第三买卖线" || 字段 == "本级_第三买卖线" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { continue; } - let valA = A.getattr(字段)?; - let valB = B.getattr(字段)?; - - if 字段 == "基础序列" { - let len_a: usize = valA.len()?; - let len_b: usize = valB.len()?; - if len_a != len_b { - return Ok(( - false, - format!("{标签}: [基础序列] 长度不一致 A={len_a},B={len_b}"), - )); - } - for idx in 0..len_a { - let itemA = valA.get_item(idx)?; - let itemB = valB.get_item(idx)?; - let (eq, msg) = 虚线相等(&itemA, &itemB, 浮点容差)?; - if !eq { - return Ok(( - false, - format!("{标签}: 基础序列[{idx}]虚线异常 >> {msg}"), - )); - } - } - } else if 字段 == "第三买卖线" || 字段 == "本级_第三买卖线" { - if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { - if !r.0 { - return Ok((false, r.1)); - } else { - continue; - } - } - let (eq, msg) = 虚线相等(&valA, &valB, 浮点容差)?; - if !eq { - return Ok((false, format!("{标签}: [{字段}]子虚线异常 >> {msg}"))); - } - } else { - let eq: bool = valA.eq(&valB)?; - if !eq { - let ra = valA.repr()?.extract::().unwrap_or_default(); - let rb = valB.repr()?.extract::().unwrap_or_default(); - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); - } - } } - Ok((true, format!("{标签}: 基础序列+第三买卖线全部校验一致"))) + let (eq, msg) = 虚线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [{字段}]子虚线异常 >> {msg}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } } - ) + } + Ok((true, format!("{标签}: 基础序列+第三买卖线全部校验一致"))) } // ========== 虚线相等 ========== @@ -632,168 +618,154 @@ fn 虚线相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_DASH, - 4096, - ( - A.as_ptr() as usize, - B.as_ptr() as usize, - 浮点容差.to_bits() as i64 - ), - { - if let (Ok(a), Ok(b)) = ( - A.cast::(), - B.cast::(), - ) { - return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); - } - let a标识 = 尝试获取标识(A); - let b标识 = 尝试获取标识(B); - let 标签 = format!("虚线校验[A标识={a标识},B标识={b标识}]"); - let 比对字段 = [ - "标识", - "序号", - "级别", - "文", - "武", - "有效性", - "基础序列", - "特征序列", - "实_中枢序列", - "虚_中枢序列", - "合_中枢序列", - "确认K线", - "模式", - "_特征序列_显示", - "前一缺口", - "前一结束位置", - "短路修正", - ]; - for &字段 in &比对字段 { - let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); - if a有 && !b有 { - return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); - } - if !a有 && b有 { - return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); - } - if !a有 && !b有 { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let a标识 = 尝试获取标识(A); + let b标识 = 尝试获取标识(B); + let 标签 = format!("虚线校验[A标识={a标识},B标识={b标识}]"); + let 比对字段 = [ + "标识", + "序号", + "级别", + "文", + "武", + "有效性", + "基础序列", + "特征序列", + "实_中枢序列", + "虚_中枢序列", + "合_中枢序列", + "确认K线", + "模式", + "_特征序列_显示", + "前一缺口", + "前一结束位置", + "短路修正", + ]; + for &字段 in &比对字段 { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + + // 文/武:分型 + if 字段 == "文" || 字段 == "武" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { continue; } - let valA = A.getattr(字段)?; - let valB = B.getattr(字段)?; - - // 文/武:分型 - if 字段 == "文" || 字段 == "武" { - if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { - if !r.0 { - return Ok((false, r.1)); - } else { - continue; - } - } - let (eq, msg) = 分型相等(&valA, &valB, 浮点容差)?; - if !eq { - return Ok((false, format!("{标签}: [{字段}]子分型异常 >> {msg}"))); - } - } - // 前一缺口 - else if 字段 == "前一缺口" { - if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { - if !r.0 { - return Ok((false, r.1)); - } else { - continue; - } - } - let (eq, msg) = 缺口相等(&valA, &valB, 浮点容差)?; - if !eq { - return Ok((false, format!("{标签}: [前一缺口]子缺口异常 >> {msg}"))); - } - } - // 前一结束位置 - else if 字段 == "前一结束位置" { - if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { - if !r.0 { - return Ok((false, r.1)); - } else { - continue; - } - } - let (eq, msg) = 虚线相等(&valA, &valB, 浮点容差)?; - if !eq { - return Ok((false, format!("{标签}: [前一结束位置]异常 >> {msg}"))); - } - } - // 确认K线 - else if 字段 == "确认K线" { - if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { - if !r.0 { - return Ok((false, r.1)); - } else { - continue; - } - } - let (eq, msg) = 缠论K线相等(&valA, &valB, 浮点容差)?; - if !eq { - return Ok((false, format!("{标签}: [确认K线]子缠论K线异常 >> {msg}"))); - } - } - // 各类列表 - else if 字段 == "基础序列" - || 字段 == "实_中枢序列" - || 字段 == "虚_中枢序列" - || 字段 == "合_中枢序列" - || 字段 == "特征序列" - { - let len_a: usize = valA.len()?; - let len_b: usize = valB.len()?; - if len_a != len_b { - return Ok(( - false, - format!("{标签}: [{字段}]列表长度不一致 A={len_a},B={len_b}"), - )); - } - for idx in 0..len_a { - let itemA = valA.get_item(idx)?; - let itemB = valB.get_item(idx)?; - if let Some(r) = - 检查空值一致(&itemA, &itemB, &format!("{字段}[{idx}]"), &标签) - { - if !r.0 { - return Ok((false, r.1)); - } else { - continue; - } - } - let (eq, msg) = if 字段 == "基础序列" { - 虚线相等(&itemA, &itemB, 浮点容差)? - } else if 字段.contains("中枢") { - 中枢相等(&itemA, &itemB, 浮点容差)? - } else { - 线段特征相等(&itemA, &itemB, 浮点容差)? - }; - if !eq { - return Ok(( - false, - format!("{标签}: [{字段}][{idx}]子项异常 >> {msg}"), - )); - } - } - } - // 普通字段 - else { - let eq: bool = valA.eq(&valB)?; - if !eq { - let ra = valA.repr()?.extract::().unwrap_or_default(); - let rb = valB.repr()?.extract::().unwrap_or_default(); - return Ok((false, format!("{标签}: [{字段}]数值不等 A={ra},B={rb}"))); - } + } + let (eq, msg) = 分型相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [{字段}]子分型异常 >> {msg}"))); + } + } + // 前一缺口 + else if 字段 == "前一缺口" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; } } - Ok((true, format!("{标签}: 全字段所有嵌套子结构校验一致"))) + let (eq, msg) = 缺口相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [前一缺口]子缺口异常 >> {msg}"))); + } } - ) + // 前一结束位置 + else if 字段 == "前一结束位置" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 虚线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [前一结束位置]异常 >> {msg}"))); + } + } + // 确认K线 + else if 字段 == "确认K线" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 缠论K线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [确认K线]子缠论K线异常 >> {msg}"))); + } + } + // 各类列表 + else if 字段 == "基础序列" + || 字段 == "实_中枢序列" + || 字段 == "虚_中枢序列" + || 字段 == "合_中枢序列" + || 字段 == "特征序列" + { + let len_a: usize = valA.len()?; + let len_b: usize = valB.len()?; + if len_a != len_b { + return Ok(( + false, + format!("{标签}: [{字段}]列表长度不一致 A={len_a},B={len_b}"), + )); + } + for idx in 0..len_a { + let itemA = valA.get_item(idx)?; + let itemB = valB.get_item(idx)?; + if let Some(r) = + 检查空值一致(&itemA, &itemB, &format!("{字段}[{idx}]"), &标签) + { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = if 字段 == "基础序列" { + 虚线相等(&itemA, &itemB, 浮点容差)? + } else if 字段.contains("中枢") { + 中枢相等(&itemA, &itemB, 浮点容差)? + } else { + 线段特征相等(&itemA, &itemB, 浮点容差)? + }; + if !eq { + return Ok((false, format!("{标签}: [{字段}][{idx}]子项异常 >> {msg}"))); + } + } + } + // 普通字段 + else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}]数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 全字段所有嵌套子结构校验一致"))) } pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/chanlun/src/algorithm/segment.rs b/chanlun/src/algorithm/segment.rs index f78d9c5..e9b2745 100644 --- a/chanlun/src/algorithm/segment.rs +++ b/chanlun/src/algorithm/segment.rs @@ -1050,6 +1050,7 @@ impl 线段 { Self::_刷新(&线段序列[idx], 配置); let 当前线段 = Arc::clone(&线段序列[idx]); + 当前线段.短路修正.store(true, Ordering::Relaxed); if 当前线段.特征序列.read().unwrap()[2].is_some() { let 段 = 虚线::创建线段(&[ Arc::clone(&基础序列[基础序列.len() - 3]), diff --git a/chanlun/src/business/observer.rs b/chanlun/src/business/observer.rs index 3c54f7a..93e53ab 100644 --- a/chanlun/src/business/observer.rs +++ b/chanlun/src/business/observer.rs @@ -52,25 +52,22 @@ pub struct 观察者 { pub 笔序列: Vec>, pub 笔_中枢序列: Vec>, - // 线段 - pub 线段序列: Vec>, - pub 中枢序列: Vec>, + // 分析层次配置 + pub 线段分析层次: usize, + pub 扩展线段分析层次: usize, + pub 混合扩展线段分析层次: usize, - // 扩展线段(笔级) - pub 扩展线段序列: Vec>, - pub 扩展中枢序列: Vec>, + // 线段组: [0]=线段, [1]=线段<线段>, [2]=线段<线段<线段>> + pub 线段序列组: Vec>>, + pub 中枢序列组: Vec>>, - // 扩展线段(线段级) - pub 扩展线段序列_线段: Vec>, - pub 扩展中枢序列_线段: Vec>, + // 扩展线段组: [0]=扩展线段, [1]=扩展线段<扩展线段>, [2]=扩展线段<扩展线段<扩展线段>> + pub 扩展线段序列组: Vec>>, + pub 扩展中枢序列组: Vec>>, - // 线段之线段 - pub 线段_线段序列: Vec>, - pub 线段_中枢序列: Vec>, - - // 扩展线段之扩展线段 - pub 扩展线段序列_扩展线段: Vec>, - pub 扩展中枢序列_扩展线段: Vec>, + // 混合扩展线段组: [0]=扩展线段<线段>, [1]=扩展线段<线段<线段>>, [2]=扩展线段<线段<线段<线段>>> + pub 混合扩展线段序列组: Vec>>, + pub 混合扩展中枢序列组: Vec>>, // 终止时间戳 终止时间戳: Option, @@ -85,6 +82,30 @@ impl 观察者 { None }; + let 线段分析层次 = 3usize; + let 扩展线段分析层次 = 3usize; + let 混合扩展线段分析层次 = 3usize; + + let mut 线段序列组: Vec>> = Vec::with_capacity(线段分析层次); + let mut 中枢序列组: Vec>> = Vec::with_capacity(线段分析层次); + let mut 扩展线段序列组: Vec>> = Vec::with_capacity(扩展线段分析层次); + let mut 扩展中枢序列组: Vec>> = Vec::with_capacity(扩展线段分析层次); + let mut 混合扩展线段序列组: Vec>> = Vec::with_capacity(混合扩展线段分析层次); + let mut 混合扩展中枢序列组: Vec>> = Vec::with_capacity(混合扩展线段分析层次); + + for _ in 0..线段分析层次 { + 线段序列组.push(Vec::new()); + 中枢序列组.push(Vec::new()); + } + for _ in 0..扩展线段分析层次 { + 扩展线段序列组.push(Vec::new()); + 扩展中枢序列组.push(Vec::new()); + } + for _ in 0..混合扩展线段分析层次 { + 混合扩展线段序列组.push(Vec::new()); + 混合扩展中枢序列组.push(Vec::new()); + } + let mut instance = Self { 符号: 符号.clone(), 周期, @@ -95,16 +116,15 @@ impl 观察者 { 分型序列: Vec::new(), 笔序列: Vec::new(), 笔_中枢序列: Vec::new(), - 线段序列: Vec::new(), - 中枢序列: Vec::new(), - 扩展线段序列: Vec::new(), - 扩展中枢序列: Vec::new(), - 扩展线段序列_线段: Vec::new(), - 扩展中枢序列_线段: Vec::new(), - 线段_线段序列: Vec::new(), - 线段_中枢序列: Vec::new(), - 扩展线段序列_扩展线段: Vec::new(), - 扩展中枢序列_扩展线段: Vec::new(), + 线段分析层次, + 扩展线段分析层次, + 混合扩展线段分析层次, + 线段序列组, + 中枢序列组, + 扩展线段序列组, + 扩展中枢序列组, + 混合扩展线段序列组, + 混合扩展中枢序列组, 终止时间戳, }; instance.配置.标识 = 符号; @@ -126,6 +146,58 @@ impl 观察者 { self.缠论K线序列.last() } + // ---- 向后兼容的属性访问器 ---- + + /// 线段序列 (线段序列组[0]) + pub fn 线段序列(&self) -> &Vec> { + &self.线段序列组[0] + } + + /// 中枢序列 (中枢序列组[0]) + pub fn 中枢序列(&self) -> &Vec> { + &self.中枢序列组[0] + } + + /// 线段_线段序列 (线段序列组[1]) + pub fn 线段_线段序列(&self) -> &Vec> { + &self.线段序列组[1] + } + + /// 线段_中枢序列 (中枢序列组[1]) + pub fn 线段_中枢序列(&self) -> &Vec> { + &self.中枢序列组[1] + } + + /// 扩展线段序列 (扩展线段序列组[0]) + pub fn 扩展线段序列(&self) -> &Vec> { + &self.扩展线段序列组[0] + } + + /// 扩展中枢序列 (扩展中枢序列组[0]) + pub fn 扩展中枢序列(&self) -> &Vec> { + &self.扩展中枢序列组[0] + } + + /// 扩展线段序列_扩展线段 (扩展线段序列组[1]) + pub fn 扩展线段序列_扩展线段(&self) -> &Vec> { + &self.扩展线段序列组[1] + } + + /// 扩展中枢序列_扩展线段 (扩展中枢序列组[1]) + pub fn 扩展中枢序列_扩展线段(&self) -> &Vec> { + &self.扩展中枢序列组[1] + } + + /// 扩展线段序列_线段 (混合扩展线段序列组[0]) + pub fn 扩展线段序列_线段(&self) -> &Vec> { + &self.混合扩展线段序列组[0] + } + + /// 扩展中枢序列_线段 (混合扩展中枢序列组[0]) + pub fn 扩展中枢序列_线段(&self) -> &Vec> { + &self.混合扩展中枢序列组[0] + } + /// 重置基础序列 pub fn 重置基础序列(&mut self) { self.普通K线序列.clear(); @@ -134,16 +206,27 @@ impl 观察者 { self.分型序列.clear(); self.笔序列.clear(); self.笔_中枢序列.clear(); - self.线段序列.clear(); - self.中枢序列.clear(); - self.扩展线段序列.clear(); - self.扩展中枢序列.clear(); - self.扩展线段序列_线段.clear(); - self.扩展中枢序列_线段.clear(); - self.线段_线段序列.clear(); - self.线段_中枢序列.clear(); - self.扩展线段序列_扩展线段.clear(); - self.扩展中枢序列_扩展线段.clear(); + + self.线段序列组.clear(); + self.中枢序列组.clear(); + for _ in 0..self.线段分析层次 { + self.线段序列组.push(Vec::new()); + self.中枢序列组.push(Vec::new()); + } + + self.扩展线段序列组.clear(); + self.扩展中枢序列组.clear(); + for _ in 0..self.扩展线段分析层次 { + self.扩展线段序列组.push(Vec::new()); + self.扩展中枢序列组.push(Vec::new()); + } + + self.混合扩展线段序列组.clear(); + self.混合扩展中枢序列组.clear(); + for _ in 0..self.混合扩展线段分析层次 { + self.混合扩展线段序列组.push(Vec::new()); + self.混合扩展中枢序列组.push(Vec::new()); + } } /// 增加原始K线 — 单根K线投喂入口 @@ -166,7 +249,7 @@ impl 观察者 { /// 核心数据处理管道 fn __处理数据(&mut self, 普K: K线) { - // Step 1: 缠论K线分析 (普K is consumed by 分析 as &mut) + // Step 1: 缠论K线分析 let (_, 当前分型) = 缠论K线::分析( 普K, &mut self.缠论K线序列, @@ -178,92 +261,73 @@ impl 观察者 { None => return, }; - // Step 2: 笔分析 - if self.配置.分析笔 { - 笔::分析( - 当前分型, - &mut self.分型序列, - &mut self.笔序列, - &self.缠论K线序列, - &self.普通K线序列, - 0, - &self.配置, - ); - } + // Step 2: 笔分析(无条件) + 笔::分析( + 当前分型, + &mut self.分型序列, + &mut self.笔序列, + &self.缠论K线序列, + &self.普通K线序列, + 0, + &self.配置, + ); if self.分型序列.is_empty() { return; } - // Step 3: 笔中枢分析 - if self.配置.分析笔中枢 { - 中枢::分析(&self.笔序列, &mut self.笔_中枢序列, true, "", 0); - } + // Step 3: 笔中枢分析(无条件) + 中枢::分析(&self.笔序列, &mut self.笔_中枢序列, true, "", 0); if self.笔序列.is_empty() { return; } - // Step 4: 线段分析 - if self.配置.分析线段 { - 线段::分析( - &self.笔序列, - &mut self.线段序列, - &self.配置, - 0, - &[相对方向::向上, 相对方向::向下], - ); - } - if self.配置.分析线段中枢 { - 中枢::分析(&self.线段序列, &mut self.中枢序列, true, "", 0); + // Step 4: 线段分析 — 3 级递归 + for i in 0..self.线段分析层次 { + if i == 0 { + 线段::分析( + &self.笔序列, + &mut self.线段序列组[i], + &self.配置, + 0, + &[相对方向::向上, 相对方向::向下], + ); + } else { + let 源序列 = self.线段序列组[i - 1].clone(); + 线段::分析( + &源序列, + &mut self.线段序列组[i], + &self.配置, + 0, + &[相对方向::向上, 相对方向::向下], + ); + } + 中枢::分析(&self.线段序列组[i], &mut self.中枢序列组[i], true, "", 0); } - // Step 5: 扩展线段(笔级) - if self.配置.分析扩展线段 { - 线段::扩展分析(&self.笔序列, &mut self.扩展线段序列, &self.配置); - } - if self.配置.分析线段中枢 { - 中枢::分析(&self.扩展线段序列, &mut self.扩展中枢序列, true, "", 0); - } - - // Step 6: 扩展线段(线段级) - if self.配置.分析扩展线段 { - 线段::扩展分析(&self.线段序列, &mut self.扩展线段序列_线段, &self.配置); - } - if self.配置.分析线段中枢 { + // Step 5: 扩展线段分析 — 3 级递归 + for i in 0..self.扩展线段分析层次 { + if i == 0 { + 线段::扩展分析(&self.笔序列, &mut self.扩展线段序列组[i], &self.配置); + } else { + let 源序列 = self.扩展线段序列组[i - 1].clone(); + 线段::扩展分析(&源序列, &mut self.扩展线段序列组[i], &self.配置); + } 中枢::分析( - &self.扩展线段序列_线段, - &mut self.扩展中枢序列_线段, + &self.扩展线段序列组[i], + &mut self.扩展中枢序列组[i], true, "", 0, ); } - // Step 7: 线段之线段 - if self.配置.分析线段 { - 线段::分析( - &self.线段序列, - &mut self.线段_线段序列, - &self.配置, - 0, - &[相对方向::向上, 相对方向::向下], - ); - } - if self.配置.分析线段中枢 { - 中枢::分析(&self.线段_线段序列, &mut self.线段_中枢序列, true, "", 0); - } - - // Step 8: 扩展线段之扩展线段 - if self.配置.分析扩展线段 { - 线段::扩展分析( - &self.扩展线段序列, - &mut self.扩展线段序列_扩展线段, - &self.配置, - ); - } - if self.配置.分析线段中枢 { + // Step 6: 混合扩展线段分析 — 3 级递归 (源 = 线段序列组[i]) + for i in 0..self.混合扩展线段分析层次 { + let 源序列 = self.线段序列组[i].clone(); + 线段::扩展分析(&源序列, &mut self.混合扩展线段序列组[i], &self.配置); 中枢::分析( - &self.扩展线段序列_扩展线段, - &mut self.扩展中枢序列_扩展线段, + &self.混合扩展线段序列组[i], + &mut self.混合扩展中枢序列组[i], true, "", 0, @@ -279,16 +343,27 @@ impl 观察者 { self.分型序列.clear(); self.笔序列.clear(); self.笔_中枢序列.clear(); - self.线段序列.clear(); - self.中枢序列.clear(); - self.扩展线段序列.clear(); - self.扩展中枢序列.clear(); - self.扩展线段序列_线段.clear(); - self.扩展中枢序列_线段.clear(); - self.线段_线段序列.clear(); - self.线段_中枢序列.clear(); - self.扩展线段序列_扩展线段.clear(); - self.扩展中枢序列_扩展线段.clear(); + + self.线段序列组.clear(); + self.中枢序列组.clear(); + for _ in 0..self.线段分析层次 { + self.线段序列组.push(Vec::new()); + self.中枢序列组.push(Vec::new()); + } + + self.扩展线段序列组.clear(); + self.扩展中枢序列组.clear(); + for _ in 0..self.扩展线段分析层次 { + self.扩展线段序列组.push(Vec::new()); + self.扩展中枢序列组.push(Vec::new()); + } + + self.混合扩展线段序列组.clear(); + self.混合扩展中枢序列组.clear(); + for _ in 0..self.混合扩展线段分析层次 { + self.混合扩展线段序列组.push(Vec::new()); + self.混合扩展中枢序列组.push(Vec::new()); + } for i in 1..self.缠论K线序列.len() - 1 { let 当前分型 = 分型::new( @@ -307,102 +382,61 @@ impl 观察者 { ); } - if self.配置.分析笔中枢 { - 中枢::分析(&self.笔序列, &mut self.笔_中枢序列, true, "", 0); + 中枢::分析(&self.笔序列, &mut self.笔_中枢序列, true, "", 0); + + for i in 0..self.线段分析层次 { + if i == 0 { + 线段::分析( + &self.笔序列, + &mut self.线段序列组[i], + &self.配置, + 0, + &[相对方向::向上, 相对方向::向下], + ); + } else { + let 源序列 = self.线段序列组[i - 1].clone(); + 线段::分析( + &源序列, + &mut self.线段序列组[i], + &self.配置, + 0, + &[相对方向::向上, 相对方向::向下], + ); + } + 中枢::分析(&self.线段序列组[i], &mut self.中枢序列组[i], true, "", 0); } - if self.配置.分析线段 { - 线段::分析( - &self.笔序列, - &mut self.线段序列, - &self.配置, - 0, - &[相对方向::向上, 相对方向::向下], - ); - } - if self.配置.分析线段中枢 { - 中枢::分析(&self.线段序列, &mut self.中枢序列, true, "", 0); - } - - if self.配置.分析扩展线段 { - 线段::扩展分析(&self.笔序列, &mut self.扩展线段序列, &self.配置); - } - if self.配置.分析线段中枢 { - 中枢::分析(&self.扩展线段序列, &mut self.扩展中枢序列, true, "", 0); - } - - if self.配置.分析扩展线段 { - 线段::扩展分析(&self.线段序列, &mut self.扩展线段序列_线段, &self.配置); - } - if self.配置.分析线段中枢 { + for i in 0..self.扩展线段分析层次 { + if i == 0 { + 线段::扩展分析(&self.笔序列, &mut self.扩展线段序列组[i], &self.配置); + } else { + let 源序列 = self.扩展线段序列组[i - 1].clone(); + 线段::扩展分析(&源序列, &mut self.扩展线段序列组[i], &self.配置); + } 中枢::分析( - &self.扩展线段序列_线段, - &mut self.扩展中枢序列_线段, + &self.扩展线段序列组[i], + &mut self.扩展中枢序列组[i], true, "", 0, ); } - if self.配置.分析线段 { - 线段::分析( - &self.线段序列, - &mut self.线段_线段序列, - &self.配置, + for i in 0..self.混合扩展线段分析层次 { + let 源序列 = self.线段序列组[i].clone(); + 线段::扩展分析(&源序列, &mut self.混合扩展线段序列组[i], &self.配置); + 中枢::分析( + &self.混合扩展线段序列组[i], + &mut self.混合扩展中枢序列组[i], + true, + "", 0, - &[相对方向::向上, 相对方向::向下], ); } - if self.配置.分析线段中枢 { - 中枢::分析(&self.线段_线段序列, &mut self.线段_中枢序列, true, "", 0); - } } /// 测试_保存数据 — 输出各序列数据文本到文件 pub fn 测试_保存数据(&self, root: Option<&str>) -> String { - let 笔序列_文本数据: Vec = self.笔序列.iter().map(|b| b.获取数据文本()).collect(); - let 线段序列_文本数据: Vec = - self.线段序列.iter().map(|s| s.获取数据文本()).collect(); - let 扩展线段序列_数据文本: Vec = - self.扩展线段序列.iter().map(|s| s.获取数据文本()).collect(); - let 扩展线段序列_线段_数据文本: Vec = self - .扩展线段序列_线段 - .iter() - .map(|s| s.获取数据文本()) - .collect(); - let 线段_线段序列_数据文本: Vec = self - .线段_线段序列 - .iter() - .map(|s| s.获取数据文本()) - .collect(); - let 扩展线段序列_扩展线段_数据文本: Vec = self - .扩展线段序列_扩展线段 - .iter() - .map(|s| s.获取数据文本()) - .collect(); - - let 笔_中枢序列_数据文本: Vec = - self.笔_中枢序列.iter().map(|h| h.获取数据文本()).collect(); - let 中枢序列_数据文本: Vec = - self.中枢序列.iter().map(|h| h.获取数据文本()).collect(); - let 扩展中枢序列_数据文本: Vec = - self.扩展中枢序列.iter().map(|h| h.获取数据文本()).collect(); - let 扩展中枢序列_线段_数据文本: Vec = self - .扩展中枢序列_线段 - .iter() - .map(|h| h.获取数据文本()) - .collect(); - let 线段_中枢序列_数据文本: Vec = self - .线段_中枢序列 - .iter() - .map(|h| h.获取数据文本()) - .collect(); - let 扩展中枢序列_扩展线段_数据文本: Vec = self - .扩展中枢序列_扩展线段 - .iter() - .map(|h| h.获取数据文本()) - .collect(); - // 确定根目录 let 根目录 = match root { Some(r) => std::path::PathBuf::from(r), @@ -420,7 +454,50 @@ impl 观察者 { return String::new(); } - // 缠K data for debugging + // 辅助:保存序列到文件 (Python: 保存序列(序列)) + let 保存序列 = + |序列: &Vec>, 保存路径: &std::path::Path| -> Result<(), std::io::Error> { + if 序列.is_empty() { + return Ok(()); + } + let 数据列表: Vec = 序列.iter().map(|d| d.获取数据文本()).collect(); + let 文件名 = format!("{}.txt", 序列[0].标识.read().unwrap()); + std::fs::write(保存路径.join(&文件名), 数据列表.join("\n") + "\n")?; + Ok(()) + }; + + let 保存中枢序列 = + |序列: &Vec>, 保存路径: &std::path::Path| -> Result<(), std::io::Error> { + if 序列.is_empty() { + return Ok(()); + } + let 数据列表: Vec = 序列.iter().map(|h| h.获取数据文本()).collect(); + let 文件名 = format!("{}.txt", 序列[0].标识.read().unwrap()); + std::fs::write(保存路径.join(&文件名), 数据列表.join("\n") + "\n")?; + Ok(()) + }; + + // 保存笔/笔中枢 + let _ = 保存序列(&self.笔序列, &保存路径); + let _ = 保存中枢序列(&self.笔_中枢序列, &保存路径); + + // 保存线段组 + for i in 0..self.线段分析层次 { + let _ = 保存序列(&self.线段序列组[i], &保存路径); + let _ = 保存中枢序列(&self.中枢序列组[i], &保存路径); + } + // 保存扩展线段组 + for i in 0..self.扩展线段分析层次 { + let _ = 保存序列(&self.扩展线段序列组[i], &保存路径); + let _ = 保存中枢序列(&self.扩展中枢序列组[i], &保存路径); + } + // 保存混合扩展线段组 + for i in 0..self.混合扩展线段分析层次 { + let _ = 保存序列(&self.混合扩展线段序列组[i], &保存路径); + let _ = 保存中枢序列(&self.混合扩展中枢序列组[i], &保存路径); + } + + // 缠K/分型 debug data let 缠K序列_数据文本: Vec = self .缠论K线序列 .iter() @@ -438,6 +515,11 @@ impl 观察者 { ) }) .collect(); + let _ = std::fs::write( + 保存路径.join("缠K序列_数据文本.txt"), + 缠K序列_数据文本.join("\n") + "\n", + ); + let 分型序列_数据文本: Vec = self .分型序列 .iter() @@ -454,37 +536,10 @@ impl 观察者 { ) }) .collect(); - - let 数据映射: Vec<(&str, &[String])> = vec![ - ("笔序列_文本数据", &笔序列_文本数据), - ("线段序列_文本数据", &线段序列_文本数据), - ("扩展线段序列_数据文本", &扩展线段序列_数据文本), - ("扩展线段序列_线段_数据文本", &扩展线段序列_线段_数据文本), - ("线段_线段序列_数据文本", &线段_线段序列_数据文本), - ( - "扩展线段序列_扩展线段_数据文本", - &扩展线段序列_扩展线段_数据文本, - ), - ("笔_中枢序列_数据文本", &笔_中枢序列_数据文本), - ("中枢序列_数据文本", &中枢序列_数据文本), - ("扩展中枢序列_数据文本", &扩展中枢序列_数据文本), - ("扩展中枢序列_线段_数据文本", &扩展中枢序列_线段_数据文本), - ("线段_中枢序列_数据文本", &线段_中枢序列_数据文本), - ( - "扩展中枢序列_扩展线段_数据文本", - &扩展中枢序列_扩展线段_数据文本, - ), - ("缠K序列_数据文本", &缠K序列_数据文本), - ("分型序列_数据文本", &分型序列_数据文本), - ]; - - for (文件名, 数据列表) in &数据映射 { - let 文件路径 = 保存路径.join(format!("{}.txt", 文件名)); - let 内容 = 数据列表.join("\n") + "\n"; - if let Err(e) = std::fs::write(&文件路径, &内容) { - error!("写入文件失败: {} -> {}", 文件路径.display(), e); - } - } + let _ = std::fs::write( + 保存路径.join("分型序列_数据文本.txt"), + 分型序列_数据文本.join("\n") + "\n", + ); info!("全部数据拆分保存完成,目录:{}", 保存路径.display()); 保存路径.display().to_string() @@ -672,7 +727,7 @@ mod tests { let obs_ref = obs.read().unwrap(); // 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中 - for (i, seg) in obs_ref.线段序列.iter().enumerate() { + for (i, seg) in obs_ref.线段序列().iter().enumerate() { for (j, bi_in_seg) in seg.基础序列.read().unwrap().iter().enumerate() { let bi_ptr = Arc::as_ptr(bi_in_seg); let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr); @@ -706,10 +761,10 @@ mod tests { } } - for (i, hub) in obs_ref.中枢序列.iter().enumerate() { + for (i, hub) in obs_ref.中枢序列().iter().enumerate() { for (j, seg_in_hub) in hub.基础序列.read().unwrap().iter().enumerate() { let seg_ptr = Arc::as_ptr(seg_in_hub); - let found = obs_ref.线段序列.iter().any(|s| Arc::as_ptr(s) == seg_ptr); + let found = obs_ref.线段序列().iter().any(|s| Arc::as_ptr(s) == seg_ptr); if !found { info!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j); } @@ -738,8 +793,8 @@ mod tests { let obs = obs_ref.read().unwrap(); ( obs.笔序列.len(), - obs.线段序列.len(), - obs.中枢序列.len(), + obs.线段序列().len(), + obs.中枢序列().len(), obs.笔_中枢序列.len(), ) }; @@ -778,12 +833,12 @@ mod tests { } let 第一次笔数 = obs_ref.read().unwrap().笔序列.len(); - let 第一次段数 = obs_ref.read().unwrap().线段序列.len(); + let 第一次段数 = obs_ref.read().unwrap().线段序列().len(); // 重置 obs_ref.write().unwrap().重置基础序列(); assert_eq!(obs_ref.read().unwrap().笔序列.len(), 0); - assert_eq!(obs_ref.read().unwrap().线段序列.len(), 0); + assert_eq!(obs_ref.read().unwrap().线段序列().len(), 0); // 重新投喂 for i in 0..data.len() / size { @@ -794,7 +849,7 @@ mod tests { } let 第二次笔数 = obs_ref.read().unwrap().笔序列.len(); - let 第二次段数 = obs_ref.read().unwrap().线段序列.len(); + let 第二次段数 = obs_ref.read().unwrap().线段序列().len(); assert_eq!(第一次笔数, 第二次笔数, "重置后重新投喂笔数不一致"); assert_eq!(第一次段数, 第二次段数, "重置后重新投喂线段数不一致"); @@ -831,7 +886,7 @@ mod tests { let _短路 = bi.短路修正.load(Ordering::Relaxed); let _前一缺口 = *bi.前一缺口.read().unwrap(); } - for seg in &obs_ref.线段序列 { + for seg in obs_ref.线段序列() { let _ = seg.标识.read().unwrap().clone(); let _ = seg.基础序列.read().unwrap().len(); } diff --git a/chanlun/src/main.rs b/chanlun/src/main.rs index 9d5d477..d3910b0 100644 --- a/chanlun/src/main.rs +++ b/chanlun/src/main.rs @@ -96,13 +96,13 @@ fn 测试_读取数据(文件路径: &str) { println!("分型数量: {}", 观察员.分型序列.len()); println!("笔数量: {}", 观察员.笔序列.len()); println!("笔中枢数量: {}", 观察员.笔_中枢序列.len()); - println!("线段数量: {}", 观察员.线段序列.len()); - println!("中枢数量: {}", 观察员.中枢序列.len()); - println!("扩展线段数量: {}", 观察员.扩展线段序列.len()); - println!("线段_线段序列数量: {}", 观察员.线段_线段序列.len()); + println!("线段数量: {}", 观察员.线段序列().len()); + println!("中枢数量: {}", 观察员.中枢序列().len()); + println!("扩展线段数量: {}", 观察员.扩展线段序列().len()); + println!("线段_线段序列数量: {}", 观察员.线段_线段序列().len()); println!( "扩展线段_扩展线段数量: {}", - 观察员.扩展线段序列_扩展线段.len() + 观察员.扩展线段序列_扩展线段().len() ); println!("\n===== 保存分析数据 =====\n"); @@ -167,8 +167,8 @@ fn 测试_周期合成(文件路径: &str) { 观察员.缠论K线序列.len(), 观察员.分型序列.len(), 观察员.笔序列.len(), - 观察员.线段序列.len(), - 观察员.中枢序列.len(), + 观察员.线段序列().len(), + 观察员.中枢序列().len(), ); } } diff --git a/main.py b/main.py index 7373c4c..d916601 100644 --- a/main.py +++ b/main.py @@ -46,10 +46,7 @@ from jinja2 import Environment, FileSystemLoader from pydantic import BaseModel import backtrader as bt -try: - from chanlun import * -except ImportError: - from chan import * +from chanlun import * from strategies import * @@ -321,471 +318,6 @@ class 时间周期: return 数值 * 60 -class BSP点: - """缠论买卖点(独立于旧买卖点类,对标 CBS_Point)。支持同一笔上叠加多种类型。""" - - __slots__ = ("笔", "K线单元", "是否买点", "类型列表", "关联T1", "特征字典") - - def __init__(self, 笔: "虚线", 是否买点: bool, 类型: "买卖点类型", 关联T1: "Optional[BSP点]" = None, 特征字典: dict = None): - self.笔 = 笔 - self.K线单元 = 笔.武.中 - self.是否买点 = 是否买点 - self.类型列表: List["买卖点类型"] = [类型] - self.关联T1: "Optional[BSP点]" = 关联T1 - self.特征字典 = 特征字典 or {} - - def 添加类型(self, 类型: "买卖点类型", 关联T1: "Optional[BSP点]" = None): - if 类型 not in self.类型列表: - self.类型列表.append(类型) - if self.关联T1 is None: - self.关联T1 = 关联T1 - elif 关联T1 is not None: - assert self.关联T1.K线单元.序号 == 关联T1.K线单元.序号 - - @property - def 类型字符串(self) -> str: - return ",".join([t.value for t in self.类型列表]) - - @property - def 备注(self) -> str: - return self.类型字符串 - - -class 买卖点识别器: - """缠论六类买卖点识别引擎。对标 ./对标/chan.py BuySellPoint 逻辑。""" - - _最后确认位置: Dict[str, int] = {} # 对标 last_sure_pos - _BSP1字典: Dict[str, Dict[int, "BSP点"]] = {} # 对标 bsp1_dict: key → 笔序号 → BSP点 - - # ================================================================ - # 主入口 - # ================================================================ - - @staticmethod - def 计算(obs: "观察者") -> None: - if not obs.线段序列 or not obs.普通K线序列: - return - - key = obs.标识 - 最后确认 = 买卖点识别器._最后确认位置.get(key, -1) - - # 新确认的线段(仅用于 T1 增量识别,避免重复创建 T1) - 新确认线段 = [seg for i, seg in enumerate(obs.线段序列) if i > 最后确认 and seg.特征序列[2] is not None] - - if not 新确认线段: - return - - 买卖点识别器._最后确认位置[key] = obs.线段序列.index(新确认线段[-1]) - - 配置 = obs.配置 - - if 配置.买卖点_计算线段BSP1: - 买卖点识别器._计算线段BSP1(obs, 新确认线段) - - # T2 / T3 需要全部已确认线段(含历史),以便在后续笔到达后重新检查 - 全部已确认 = [seg for seg in obs.线段序列 if seg.特征序列[2] is not None] - 全量T1列表 = list(买卖点识别器._BSP1字典.get(key, {}).values()) if 配置.买卖点_依赖T1 else [] - - if 配置.买卖点_处理BSP2: - 买卖点识别器._处理BSP2(obs, 全量T1列表, 全部已确认) - - if 配置.买卖点_计算线段BSP3: - 买卖点识别器._计算线段BSP3(obs, 全量T1列表, 全部已确认) - - # ================================================================ - # MACD 指标计算 - # ================================================================ - - @staticmethod - def _计算MACD指标(笔: "虚线", K线序列: "List[K线]", 方式: str) -> float: - 始K = 笔.文.中.标的K线 - 终K = 笔.武.中.标的K线 - try: - 始索引 = K线序列.index(始K) - 终索引 = K线序列.index(终K) - except ValueError: - return float("inf") - - if 方式 == "峰": - 峰值 = 0.0 - for i in range(始索引, 终索引 + 1): - k = K线序列[i] - if k.macd and k.macd.MACD柱 is not None: - v = abs(k.macd.MACD柱) - if v > 峰值: - 峰值 = v - return 峰值 - else: - macd_dict = K线.获取MACD(K线序列, 始K, 终K) - return abs(macd_dict.get("总", 0.0)) - - # ================================================================ - # 中枢查找工具 - # ================================================================ - - @staticmethod - def _取线段内部中枢序列(段: "虚线", 来源: str) -> "List[中枢]": - if 来源 == "实": - return 段.实_中枢序列 - elif 来源 == "虚": - return 段.虚_中枢序列 - else: - return 段.合_中枢序列 - - @staticmethod - def _找多笔中枢(中枢序列: "List[中枢]") -> "Optional[中枢]": - for zs in reversed(中枢序列): - if len(zs) >= 3: - return zs - return None - - @staticmethod - def _全局索引(笔: "虚线", 笔序列: "List[虚线]") -> int: - """返回笔在全局笔序列中的索引。""" - try: - return 笔序列.index(笔) - except ValueError: - return -1 - - @staticmethod - def _笔振幅(笔: "虚线") -> float: - """笔的振幅 = |武分型特征值 - 文分型特征值|。对标 bi.amp()。""" - return abs(笔.武.分型特征值 - 笔.文.分型特征值) - - @staticmethod - def _有重叠(低1: float, 高1: float, 低2: float, 高2: float) -> bool: - """两区间是否有交集。对标 has_overlap。""" - return not (高1 < 低2 or 高2 < 低1) - - @staticmethod - def _段末同向笔(段: "虚线") -> "虚线": - """对标 seg.end_bi:当前段内最后一个与段方向相同的笔。 - - 段.基础序列包含两段笔(当前段 + 后一段开头),不能直接用 [-1]/[-2]。 - 必须用 分割序列 获取「前」——仅当前段的笔。 - """ - 前, _, _, _ = 线段.分割序列(段) - for 筆 in reversed(前): - if 筆.方向 == 段.方向: - return 筆 - return 前[-1] # 理论上不会到这里,前[-1] 总是同向 - - # ================================================================ - # BSP 创建 / 去重 — 对标 add_bs - # ================================================================ - - @staticmethod - def _创建BSP(obs: "观察者", 类型: "买卖点类型", 笔: "虚线", 关联T1: "Optional[BSP点]" = None, 特征字典: dict = None): - """对标 add_bs:创建或追加 BSP 类型(支持一笔多类型叠加)。""" - is_buy = 笔.方向.是否向下() - if 笔.序号 in obs.BSP字典: - exist_bsp = obs.BSP字典[笔.序号] - assert exist_bsp.是否买点 == is_buy, f"买卖方向冲突: {exist_bsp.类型字符串} vs {类型.value}" - exist_bsp.添加类型(类型, 关联T1) - return exist_bsp - - bsp = BSP点(笔=笔, 是否买点=is_buy, 类型=类型, 关联T1=关联T1, 特征字典=特征字典) - obs.BSP字典[笔.序号] = bsp - return bsp - - # ================================================================ - # T1 / T1P — 对标 cal_seg_bs1point → treat_bsp1 / treat_pz_bsp1 - # ================================================================ - - @staticmethod - def _计算线段BSP1(obs: "观察者", 已确认线段: "List[虚线]") -> "List[BSP点]": - """一类买卖点(T1中枢突破背离)和一类盘整买卖点(T1P)。对标 cal_seg_bs1point。""" - 结果: "List[BSP点]" = [] - 配置 = obs.配置 - 来源 = 配置.买卖点_中枢来源 - 笔序列 = obs.笔序列 - - for 段 in 已确认线段: - if not 段.基础序列 or len(段.基础序列) < 3: - continue - - is_buy = 段.方向.是否向下() - if not is_buy and not 段.方向.是否向上(): - continue - - 段末笔 = 买卖点识别器._段末同向笔(段) - - 中枢序列 = 买卖点识别器._取线段内部中枢序列(段, 来源) - - # 确定触发条件 — 对标 cal_single_bs1point - goto_T1 = False - 相关中枢 = None - if 中枢序列: - 相关中枢 = 中枢序列[-1] # zs_lst[-1] - if len(相关中枢) >= 3: # not is_one_bi_zs - 进入笔全局索引 = 买卖点识别器._全局索引(相关中枢[0], 笔序列) - if 进入笔全局索引 > 0: - 进入笔 = 笔序列[进入笔全局索引 - 1] - 段末笔全局索引 = 买卖点识别器._全局索引(段末笔, 笔序列) - 中枢末笔 = 相关中枢[-1] - 中枢末笔全局索引 = 买卖点识别器._全局索引(中枢末笔, 笔序列) - bi_out = 笔序列[中枢末笔全局索引 + 1] if 中枢末笔全局索引 >= 0 and 中枢末笔全局索引 + 1 < len(笔序列) else None - 中枢到达段末 = (bi_out is not None and bi_out.序号 >= 段末笔.序号) or 中枢末笔.序号 >= 段末笔.序号 - if 中枢到达段末 and 段末笔.序号 - 进入笔.序号 > 2: - goto_T1 = True - - if goto_T1 and 相关中枢 is not None: - # ---- T1 路径(对标 treat_bsp1)---- - 进入笔全局索引 = 买卖点识别器._全局索引(相关中枢[0], 笔序列) - 进入笔 = 笔序列[进入笔全局索引 - 1] - - # 突破检查 — 对标 end_bi_break - if is_buy: - if not (段末笔.低 < 相关中枢.低): - continue - else: - if not (段末笔.高 > 相关中枢.高): - continue - - # 可选峰值条件 — 对标 out_bi_is_peak - if 配置.买卖点_峰值条件: - if is_buy: - if not all(笔.低 >= 段末笔.低 for 笔 in 相关中枢 if 笔.序号 <= 段末笔.序号): - continue - else: - if not all(笔.高 <= 段末笔.高 for 笔 in 相关中枢 if 笔.序号 <= 段末笔.序号): - continue - - # MACD背离 — 对标 is_divergence - 进入指标 = 买卖点识别器._计算MACD指标(进入笔, obs.普通K线序列, 配置.买卖点_计算方式) - 离开指标 = 买卖点识别器._计算MACD指标(段末笔, obs.普通K线序列, 配置.买卖点_计算方式) - if not (离开指标 <= 配置.买卖点_背离率 * 进入指标): - continue - - 类型 = 买卖点类型.T1买 if is_buy else 买卖点类型.T1卖 - bsp = 买卖点识别器._创建BSP(obs, 类型=类型, 笔=段末笔, 特征字典={"divergence_rate": 离开指标 / (进入指标 + 1e-7)}) - 结果.append(bsp) - 买卖点识别器._BSP1字典.setdefault(obs.标识, {})[段末笔.序号] = bsp - - else: - # ---- T1P 盘整背离路径(对标 treat_pz_bsp1)---- - 离开笔 = 段末笔 - 段末笔全局索引 = 买卖点识别器._全局索引(段末笔, 笔序列) - if 段末笔全局索引 < 2: - continue - 进入笔 = 笔序列[段末笔全局索引 - 2] - - if 离开笔.方向 != 段.方向: - continue - - # 创新低/高检查 — 对标 treat_pz_bsp1 - if is_buy: - if 离开笔.低 > 进入笔.低: - continue - else: - if 离开笔.高 < 进入笔.高: - continue - - # MACD背离 — 对标 in_metric vs out_metric - 进入指标 = 买卖点识别器._计算MACD指标(进入笔, obs.普通K线序列, 配置.买卖点_计算方式) - 离开指标 = 买卖点识别器._计算MACD指标(离开笔, obs.普通K线序列, 配置.买卖点_计算方式) - if not (离开指标 <= 配置.买卖点_背离率 * 进入指标): - continue - - 类型 = 买卖点类型.T1P买 if is_buy else 买卖点类型.T1P卖 - bsp = 买卖点识别器._创建BSP(obs, 类型=类型, 笔=离开笔, 特征字典={"divergence_rate": 离开指标 / (进入指标 + 1e-7)}) - 结果.append(bsp) - 买卖点识别器._BSP1字典.setdefault(obs.标识, {})[段末笔.序号] = bsp - - return 结果 - - # ================================================================ - # T2 / T2S — 对标 treat_bsp2 / treat_bsp2s - # ================================================================ - - @staticmethod - def _处理BSP2(obs: "观察者", T1列表: "List[BSP点]", 已确认线段: "List[虚线]") -> "List[BSP点]": - """二类买卖点(T2回调确认)和二类特殊买卖点(T2S多级次二类)。""" - 结果: "List[BSP点]" = [] - 配置 = obs.配置 - 阈值 = 配置.买卖点_T2_回调阈值 - 最大层级 = 配置.买卖点_T2S_最大层级 - 笔序列 = obs.笔序列 - - for bsp1 in T1列表: - is_buy = bsp1.是否买点 - bsp1_笔 = bsp1.笔 - - bsp1_笔全局索引 = 买卖点识别器._全局索引(bsp1_笔, 笔序列) - if bsp1_笔全局索引 < 0: - continue - - # 对标:break_bi = bi_list[bsp1_bi.idx + 1]; bsp2_bi = bi_list[bsp1_bi.idx + 2] - if bsp1_笔全局索引 + 2 >= len(笔序列): - continue - 突破笔 = 笔序列[bsp1_笔全局索引 + 1] - 回调笔 = 笔序列[bsp1_笔全局索引 + 2] - - # 对标:bsp2_bi.amp() / break_bi.amp() <= max_bs2_rate - 突破振幅 = 买卖点识别器._笔振幅(突破笔) - if 突破振幅 == 0: - continue - 回调率 = 买卖点识别器._笔振幅(回调笔) / 突破振幅 - - if 回调率 <= 阈值: - # ---- T2 成立 ---- - 类型 = 买卖点类型.T2买 if is_buy else 买卖点类型.T2卖 - bsp = 买卖点识别器._创建BSP(obs, 类型=类型, 笔=回调笔, 关联T1=bsp1) - 结果.append(bsp) - continue - - # ---- T2S 多级次二类(对标 treat_bsp2s)---- - if 最大层级 is None or 最大层级 <= 0: - continue - - # 对标:初始重叠检查 - if not 买卖点识别器._有重叠(回调笔.低, 回调笔.高, 突破笔.低, 突破笔.高): - continue - - 重叠区低 = max(回调笔.低, 突破笔.低) if is_buy else 回调笔.低 - 重叠区高 = min(回调笔.高, 突破笔.高) if not is_buy else 回调笔.高 - - bias = 2 # 对标:从 bsp2_bi.idx + 2 开始,步进 2 - while 回调笔.序号 + bias < len(笔序列): - bsp2s_笔 = 笔序列[买卖点识别器._全局索引(回调笔, 笔序列) + bias] - - if 最大层级 is not None and bias // 2 > 最大层级: - break - - # 对标:首次重叠建立 _low/_high,之后检查 - if bias == 2: - if not 买卖点识别器._有重叠(回调笔.低, 回调笔.高, bsp2s_笔.低, bsp2s_笔.高): - break - 重叠区低 = max(回调笔.低, bsp2s_笔.低) - 重叠区高 = min(回调笔.高, bsp2s_笔.高) - else: - if not 买卖点识别器._有重叠(重叠区低, 重叠区高, bsp2s_笔.低, bsp2s_笔.高): - break - - # 对标:bsp2s_break_bsp1 = 不能突破突破笔极值 - if is_buy and bsp2s_笔.低 < 突破笔.低: - break - if not is_buy and bsp2s_笔.高 > 突破笔.高: - break - - # 对标:回调率检查 - bsp2s_回调率 = abs(bsp2s_笔.武.分型特征值 - 突破笔.武.分型特征值) / 突破振幅 - if bsp2s_回调率 > 阈值: - break - - # T2S成立 - 重叠区低 = max(重叠区低, bsp2s_笔.低) - 重叠区高 = min(重叠区高, bsp2s_笔.高) - - 类型 = 买卖点类型.T2S买 if is_buy else 买卖点类型.T2S卖 - bsp = 买卖点识别器._创建BSP(obs, 类型=类型, 笔=bsp2s_笔, 关联T1=bsp1) - 结果.append(bsp) - - bias += 2 # 对标:每次+2(同方向笔) - - return 结果 - - # ================================================================ - # T3A / T3B — 对标 cal_seg_bs3point → treat_bsp3_after / treat_bsp3_before - # ================================================================ - - @staticmethod - def _计算线段BSP3(obs: "观察者", T1列表: "List[BSP点]", 已确认线段: "List[虚线]") -> None: - """三类买卖点:T3A(后方回踩)和T3B(前方反抽)。""" - 配置 = obs.配置 - 来源 = 配置.买卖点_中枢来源 - 笔序列 = obs.笔序列 - - for 段 in 已确认线段: - seg_idx = obs.线段序列.index(段) if 段 in obs.线段序列 else -1 - is_buy = 段.方向.是否向下() - - # ================================================================ - # T3A:后段中枢回踩 — 对标 treat_bsp3_after - # ================================================================ - if seg_idx >= 0 and seg_idx + 1 < len(obs.线段序列): - # 对标:if BSP_CONF.bsp3_follow_1 and bsp1_bi.idx not in bsp_store_flat_dict → skip - bsp1_笔 = 买卖点识别器._段末同向笔(段) - if not 配置.买卖点_依赖T1 or bsp1_笔.序号 in 买卖点识别器._BSP1字典.get(obs.标识, {}): - 后段 = obs.线段序列[seg_idx + 1] - 后段内部中枢 = 买卖点识别器._取线段内部中枢序列(后段, 来源) - - # 对标:first_zs = next_seg.get_first_multi_bi_zs() - for 中枢_candidate in 后段内部中枢: - if len(中枢_candidate) < 3: - continue - 中枢末笔全局索引 = 买卖点识别器._全局索引(中枢_candidate[-1], 笔序列) - bi_out = 笔序列[中枢末笔全局索引 + 1] if 中枢末笔全局索引 >= 0 and 中枢末笔全局索引 + 1 < len(笔序列) else None - if bi_out is None or 买卖点识别器._全局索引(bi_out, 笔序列) + 1 >= len(笔序列): - continue - - bsp3_笔 = 笔序列[买卖点识别器._全局索引(bi_out, 笔序列) + 1] - - # 对标:bsp3_bi.dir == next_seg.dir → break - if bsp3_笔.方向 == 后段.方向: - break - - # 对标:bsp3_back2zs(bsp3_bi, zs) - if is_buy: - if bsp3_笔.低 < 中枢_candidate.高: - continue - else: - if bsp3_笔.高 > 中枢_candidate.低: - continue - - # 对标:bs3_peak 检查 - if 配置.买卖点_峰值条件: - if is_buy: - if not (bsp3_笔.高 >= 中枢_candidate.高高): - continue - else: - if not (bsp3_笔.低 <= 中枢_candidate.低低): - continue - - 类型 = 买卖点类型.T3A买 if is_buy else 买卖点类型.T3A卖 - bsp1 = 买卖点识别器._BSP1字典.get(obs.标识, {}).get(bsp1_笔.序号) - 买卖点识别器._创建BSP(obs, 类型=类型, 笔=bsp3_笔, 关联T1=bsp1) - break # 对标:找到一个就跳出 - - # ================================================================ - # T3B:前序买卖点位置向前搜索 — 对标 treat_bsp3_before - # ================================================================ - 段末笔 = 买卖点识别器._段末同向笔(段) - 段内部中枢 = 买卖点识别器._取线段内部中枢序列(段, 来源) - cmp_中枢 = 买卖点识别器._找多笔中枢(段内部中枢) - if cmp_中枢 is None: - continue - - # 对标:if BSP_CONF.bsp3_follow_1 and bsp1_bi.idx not in bsp_store_flat_dict → skip - if 配置.买卖点_依赖T1 and 段末笔.序号 not in 买卖点识别器._BSP1字典.get(obs.标识, {}): - continue - - bsp1_全局索引 = 买卖点识别器._全局索引(段末笔, 笔序列) - if bsp1_全局索引 < 0: - continue - - # 对标:bsp3_peak check on cmp_zs - # 对标:从 bsp1_bi.idx+2 开始,步进 2 - for offset in range(2, len(笔序列) - bsp1_全局索引, 2): - bsp3_笔全局索引 = bsp1_全局索引 + offset - if bsp3_笔全局索引 >= len(笔序列): - break - bsp3_笔 = 笔序列[bsp3_笔全局索引] - - # 对标:bsp3_back2zs(bsp3_bi, cmp_zs) - if is_buy: - if bsp3_笔.低 < cmp_中枢.高: - continue - else: - if bsp3_笔.高 > cmp_中枢.低: - continue - - 类型 = 买卖点类型.T3B买 if is_buy else 买卖点类型.T3B卖 - bsp1 = 买卖点识别器._BSP1字典.get(obs.标识, {}).get(段末笔.序号) - 买卖点识别器._创建BSP(obs, 类型=类型, 笔=bsp3_笔, 关联T1=bsp1) - break # 对标:找到一个就跳出 - - class 指令: 增: Final[str] = "APPEND" 改: Final[str] = "MODIFY" @@ -823,19 +355,18 @@ class 观察者(观察者): self.数据队列: queue.Queue = 数据队列 super().__init__(符号, 周期, 配置) self.__终止时间戳: Optional[datetime] = 转化为时间戳(self.配置.手动终止) if self.配置.手动终止 else None - - def 识别买卖点(self): - pass + self.买卖点字典 = dict() @final def 增加原始K线(self, 普K: K线): if self.__终止时间戳 and 普K.时间戳 > self.__终止时间戳: return - super().增加原始K线(普K) if self.配置.推送K线: self.报信(普K, 指令.添加("RawBar"), sys._getframe().f_lineno, 周期=普K.周期) + super().增加原始K线(普K) + try: self.数据队列 and self.数据队列.put((普K.时间戳, 普K.开盘价, 普K.高, 普K.低, 普K.收盘价, 普K.成交量, 0)) if self.数据通道 is not None and self.配置.图表展示: @@ -843,8 +374,6 @@ class 观察者(观察者): try: self.图表刷新() self.识别买卖点() - # 买卖点识别器.计算(self) - # self.标注买卖点() except: print("~~~~~~~~~~~~~~", self.当前K线) traceback.print_exc() @@ -867,26 +396,12 @@ class 观察者(观察者): def 重置基础序列(self): self.买卖点字典 = dict() - self.BSP字典: Dict[int, "BSP点"] = dict() - self._买卖点最后确认线段索引: int = -1 - 买卖点识别器._最后确认位置.pop(self.标识, None) - 买卖点识别器._BSP1字典.pop(self.标识, None) - self._已标注BSP序号: set = set() super().重置基础序列() def 读取任意数据(self, 魔法, **魔法参数): 魔法(**魔法参数) return self - def 加载本地数据(self, 文件路径: str): - self.重置基础序列() - with open(文件路径, "rb") as f: - buffer = f.read() - size = struct.calcsize(">6d") - for i in range(len(buffer) // size): - k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], self.周期) - self.增加原始K线(k线) - def 静态重新分析(self): self.买卖点字典 = dict() super().静态重新分析() @@ -932,50 +447,25 @@ class 观察者(观察者): if 当前买卖点.买卖点K线.时间戳 not in 活跃时间戳序列: 买卖点序列.add(当前买卖点) 当前买卖点.买卖点K线.买卖点信息.add(当前买卖点.备注) + self.报信(当前买卖点, 指令.添加(当前买卖点.备注), sys._getframe().f_lineno) def 图表刷新(self): - # ===================== 笔(你原来的写法) ===================== - for 筆 in self.笔序列[-3:]: - self.报信(筆, 指令.添加(筆.标识), 0) + def 报信(序列): + for 对象 in 序列[-3:]: + self.报信(对象, 指令.添加(对象.标识), 0) - # ===================== 笔中枢 ===================== - for 中枢对象 in self.笔_中枢序列[-3:]: - self.报信(中枢对象, 指令.添加(中枢对象.标识), 0) + 报信(self.笔序列) + 报信(self.笔_中枢序列) - # ===================== 线段、中枢 ===================== - for 线段对象 in self.线段序列[-3:]: - self.报信(线段对象, 指令.添加(线段对象.标识), 0) - - for 中枢对象 in self.中枢序列[-3:]: - self.报信(中枢对象, 指令.添加(中枢对象.标识), 0) - - # ===================== 扩展线段、扩展中枢 ===================== - for 线段对象 in self.扩展线段序列[-3:]: - self.报信(线段对象, 指令.添加(线段对象.标识), 0) - - for 中枢对象 in self.扩展中枢序列[-3:]: - self.报信(中枢对象, 指令.添加(中枢对象.标识), 0) - - # ===================== 扩展线段(线段级)、扩展中枢(线段级) ===================== - for 线段对象 in self.扩展线段序列_线段[-3:]: - self.报信(线段对象, 指令.添加(线段对象.标识), 0) - - for 中枢对象 in self.扩展中枢序列_线段[-3:]: - self.报信(中枢对象, 指令.添加(中枢对象.标识), 0) - - # ===================== 线段线段、线段中枢 ===================== - for 线段对象 in self.线段_线段序列[-3:]: - self.报信(线段对象, 指令.添加(线段对象.标识), 0) - - for 中枢对象 in self.线段_中枢序列[-3:]: - self.报信(中枢对象, 指令.添加(中枢对象.标识), 0) - - # ===================== 扩展线段(扩展级)、扩展中枢(扩展级) ===================== - for 线段对象 in self.扩展线段序列_扩展线段[-3:]: - self.报信(线段对象, 指令.添加(线段对象.标识), 0) - - for 中枢对象 in self.扩展中枢序列_扩展线段[-3:]: - self.报信(中枢对象, 指令.添加(中枢对象.标识), 0) + for i in range(self.线段分析层次): + 报信(self.线段序列组[i]) + 报信(self.中枢序列组[i]) + for i in range(self.扩展线段分析层次): + 报信(self.扩展线段序列组[i]) + 报信(self.扩展中枢序列组[i]) + for i in range(self.混合扩展线段分析层次): + 报信(self.混合扩展线段序列组[i]) + 报信(self.混合扩展中枢序列组[i]) # self.将图表数据固化到本地() @@ -1022,23 +512,6 @@ class 观察者(观察者): "showLabel": False if 对象.偏移 <= 1 else True, } - if type(对象) is BSP点: - message["type"] = "shape" - message["cmd"] = 命令.指令.upper() - message["id"] = f"BSP_{id(对象)}" - message["name"] = "arrow_down" if not 对象.是否买点 else "arrow_up" - k线 = 对象.K线单元 - message["points"] = [{"time": int(k线.时间戳), "price": k线.分型特征值}] - arrowColor = "#FF2800" if not 对象.是否买点 else "#00FF22" - text = f"{对象.类型字符串}, {对象.特征字典.get('divergence_rate', '')}" - message["overrides"] = { - "color": "#FFA500", - "arrowColor": "#FFA500", - "text": text, - "title": 对象.类型字符串, - "showLabel": True, - } - if type(对象) is 虚线 and 对象.标识 == "笔" and not self.配置.推送笔: return @@ -1084,8 +557,8 @@ class 观察者(观察者): message["name"] = "trend_line" if type(对象) is not 中枢 else "rectangle" if 命令.指令 != 指令.删: message["points"] = [ - {"time": int(缠论K线.时间戳对齐(self.缠论K线序列, 对象.文.中)), "price": 对象.文.分型特征值 if type(对象) is not 中枢 else 对象.高}, - {"time": int(缠论K线.时间戳对齐(self.缠论K线序列, 对象.武.中)), "price": 对象.武.分型特征值 if type(对象) is not 中枢 else 对象.低}, + {"time": int(缠论K线.时间戳对齐(self.基础缠K序列, 对象.文.中)), "price": 对象.文.分型特征值 if type(对象) is not 中枢 else 对象.高}, + {"time": int(缠论K线.时间戳对齐(self.基础缠K序列, 对象.武.中)), "price": 对象.武.分型特征值 if type(对象) is not 中枢 else 对象.低}, ] linewidths = {"笔": 1, "线段": 2, "走势": 3, "线段特征": 2} message["overrides"] = { @@ -1107,6 +580,9 @@ class 观察者(观察者): if type(对象) is not 线段特征: message["overrides"]["text"] = f"{对象.标识} {对象.序号} 周期:{self.周期} {getattr(对象, '四象', '')} {getattr(对象, '特征序列状态', '')} {getattr(对象, '级别', '')} {getattr(对象, '备注', '')}" + if type(对象) is 中枢: + message["overrides"]["text"] = f"{对象.标识} {对象.序号} 周期:{self.周期} 基础序列数量: {len(对象.基础序列)}" + if 对象.标识 in ("线段", "线段<线段>"): message["overrides"]["text"] = f"{对象.标识} {对象.序号} 周期:{self.周期} {线段.四象(对象)} {线段.特征序列状态(对象)} {getattr(对象, '级别', '')} {getattr(对象, '备注', '')}" @@ -1133,17 +609,6 @@ class 观察者(观察者): asyncio.ensure_future(self.数据通道.send_text(json.dumps(message))) return - def 标注买卖点(self): - """将 BSP字典 中的买卖点推送到图表。与旧的 添加买卖点/报信 独立。""" - if not self.配置.图表展示 or self.数据通道 is None: - return - 已标注 = getattr(self, "_已标注BSP序号", set()) - for 笔序号, bsp in self.BSP字典.items(): - if 笔序号 in 已标注: - continue - 已标注.add(笔序号) - self.报信(bsp, 指令.添加(bsp.备注), sys._getframe().f_lineno) - def 将图表数据固化到本地(self, static_shapes=None): template_path = "./templates/static.html" # 初始化 Jinja2 环境,模板目录为当前目录 @@ -1170,7 +635,6 @@ class 观察者(观察者): 全部 = [] for o in self.买卖点字典.values(): 全部.extend(o) - 全部.extend(self.BSP字典.values()) for 对象 in 全部: if type(对象) in (笔, 线段, 中枢, 线段特征): @@ -1234,23 +698,6 @@ class 观察者(观察者): static_shapes.append(message) continue - if type(对象) is BSP点: - message = dict() - message["type"] = "shape" - message["id"] = f"BSP_{id(对象)}" - message["shapeType"] = "arrow_down" if not 对象.是否买点 else "arrow_up" - k线 = 对象.K线单元 - message["points"] = [{"time": int(k线.时间戳), "price": k线.分型特征值}] - arrowColor = "#FF2800" if not 对象.是否买点 else "#00FF22" - text = f"{对象.类型字符串}" - message["overrides"] = { - "color": "#FFA500", - "arrowColor": arrowColor, - "text": text, - "title": 对象.类型字符串, - "showLabel": True, - } - static_shapes.append(message) else: print(type(对象), 对象) for item in static_shapes: @@ -1284,7 +731,7 @@ class 观察者(观察者): buffer = f.read() size = struct.calcsize(">6d") for i in range(len(buffer) // size): - k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期)) + k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号) 实例.增加原始K线(k线) return 实例 @@ -1858,7 +1305,7 @@ def 测试_随机生成(symbol: str = "btcusd", limit: int = 5000, freq: Support def 魔法(): 随机生成实例 = 观察者(symbol + "_gen", 周期=int(freq), 数据通道=ws, 配置=配置) dt = datetime(2008, 8, 8) - 原始K线 = K线.创建普K("随机", dt, 8888.55, 10000.00, 9000.22, 9527.33, 888, 0, int(freq)) + 原始K线 = K线.创建普K("随机", int(dt.timestamp()), 8888.55, 10000.00, 9000.22, 9527.33, 888, 0, int(freq)) 随机生成实例.增加原始K线(原始K线) for 方向 in 从序列中机选( int(limit), @@ -1910,7 +1357,7 @@ class Bitstamp: for bar in data["data"]["ohlc"]: K = K线.创建普K( 观察员.符号, - 转化为时间戳(int(bar["timestamp"])), + int(bar["timestamp"]), float(bar["open"]), float(bar["high"]), float(bar["low"]), @@ -2779,6 +2226,7 @@ if __name__ == "__main__": 本地随机 = random.Random(os.urandom(64)) 配置 = 随机配置(本地随机) print(f"[线程{线程编号:02d}] 开始 ...") + print(f"[线程{线程编号:02d}] ", 配置.to_dict()) 测试函数 = 测试_随机生成(symbol="btcusd", limit=10000, freq=时间周期.分(5), ws=None, 配置=配置) 结果 = 测试函数() # 实际执行 print(f"[线程{线程编号:02d}] 完成 | 笔序列长度: {len(结果.笔序列)}") @@ -2786,6 +2234,7 @@ if __name__ == "__main__": print(f"[线程{线程编号:02d}] 异常: {e}") traceback.print_exc() + start = datetime.now() # 创建并启动 50 个线程 线程列表 = [] for i in range(1, 51): @@ -2797,4 +2246,4 @@ if __name__ == "__main__": for 线程 in 线程列表: 线程.join() - print("\n全部 50 个随机回测线程已完成。") + print("\n全部 50 个随机回测线程已完成。", datetime.now() - start) diff --git a/strategies.py b/strategies.py index d4e0f3b..203d68d 100644 --- a/strategies.py +++ b/strategies.py @@ -1001,7 +1001,7 @@ class 回测(高级策略基类): def 检查买信号(self): if self.p.观察员.笔序列: k线 = self.p.观察员.缠论K线序列[-1] - self.日志(f"检查买信号 当前笔 {self.p.观察员.笔序列[-1]}") + # self.日志(f"检查买信号 当前笔 {self.p.观察员.笔序列[-1]}") if k线.买卖点信息: print(f"回测-首 {self.p.观察员.__class__.__name__}", k线.买卖点信息) 首 = True if k线.买卖点信息 and "买" in next(iter(k线.买卖点信息)) else False @@ -1024,7 +1024,7 @@ class 回测(高级策略基类): def 检查卖信号(self): if self.p.观察员.笔序列: - self.日志(f"检查卖信号 当前笔 {self.p.观察员.笔序列[-1]}") + # self.日志(f"检查卖信号 当前笔 {self.p.观察员.笔序列[-1]}") k线 = self.p.观察员.缠论K线序列[-1] if k线.买卖点信息: print(f"回测-首 {self.p.观察员.__class__.__name__}", k线.买卖点信息)