From 1477cde86bf473d708b0587c6512297ba8f5a578 Mon Sep 17 00:00:00 2001 From: YuWuKunCheng Date: Thu, 11 Jun 2026 22:01:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E9=80=BB?= =?UTF-8?q?=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chan.py | 333 +++++-- chanlun-py/Cargo.toml | 13 +- chanlun-py/README.md | 71 ++ chanlun-py/chanlun.pyi | 56 +- chanlun-py/chanlun/__init__.pyi | 21 + chanlun-py/chanlun/chan.py | 333 +++++-- chanlun-py/pyproject.toml | 2 +- chanlun-py/src/algorithm_py.rs | 355 +++++-- chanlun-py/src/business_py.rs | 99 +- chanlun-py/src/cache.rs | 199 ++++ chanlun-py/src/config_py.rs | 45 +- chanlun-py/src/equality_py.rs | 664 ++++++------- chanlun-py/src/indicators_py.rs | 38 +- chanlun-py/src/kline_py.rs | 103 +- chanlun-py/src/lib.rs | 88 +- chanlun-py/src/structure_py.rs | 93 +- chanlun-py/src/types_py.rs | 22 +- chanlun-py/tests/test_all.py | 69 ++ chanlun/Cargo.toml | 6 +- chanlun/src/algorithm/bi.rs | 81 +- chanlun/src/algorithm/divergence.rs | 34 +- chanlun/src/algorithm/hub.rs | 303 +++--- chanlun/src/algorithm/segment.rs | 1282 +++++++++++-------------- chanlun/src/business/bsp.rs | 2 +- chanlun/src/business/multi_frame.rs | 29 +- chanlun/src/business/observer.rs | 305 +++--- chanlun/src/business/synthesizer.rs | 4 +- chanlun/src/config.rs | 2 +- chanlun/src/indicators/boll.rs | 11 +- chanlun/src/indicators/calculator.rs | 38 +- chanlun/src/indicators/container.rs | 4 +- chanlun/src/indicators/kdj.rs | 25 +- chanlun/src/indicators/rsi.rs | 30 +- chanlun/src/kline/bar.rs | 195 +++- chanlun/src/kline/chan_kline.rs | 80 +- chanlun/src/lib.rs | 1 + chanlun/src/log.rs | 68 ++ chanlun/src/main.rs | 5 +- chanlun/src/structure/dash_line.rs | 697 ++++++++------ chanlun/src/structure/fractal_obj.rs | 46 +- chanlun/src/structure/segment_feat.rs | 85 +- chanlun/src/types/direction.rs | 28 + chanlun/src/types/fractal.rs | 2 +- 43 files changed, 3613 insertions(+), 2354 deletions(-) create mode 100644 chanlun-py/src/cache.rs create mode 100644 chanlun/src/log.rs diff --git a/chan.py b/chan.py index 4855af9..bc7fd55 100644 --- a/chan.py +++ b/chan.py @@ -31,12 +31,14 @@ from __future__ import annotations import json import math import os +from collections import deque +import random import struct import sys import tempfile import datetime as datetime_module from dataclasses import dataclass, field -from datetime import datetime +from datetime import datetime, timedelta from enum import Enum from functools import lru_cache from pathlib import Path @@ -52,6 +54,7 @@ from typing import ( Sequence, Callable, Set, + Generator, ) from loguru import logger @@ -1425,6 +1428,23 @@ class 相对方向(Enum): return 相对方向.逆 raise RuntimeError("无法识别的方向") + @classmethod + def 从序列中机选( + cls, + 数量: int, + 可选方向: List["相对方向"], + 可重复: bool = True, # 是否允许重复选择 + ) -> Generator["相对方向", None, None]: + if not 可重复 and 数量 > len(可选方向): + raise ValueError("数量超过可选方向数") + + if 可重复: + while 数量 > 0: + yield random.choice(可选方向) + 数量 -= 1 + else: + yield from random.sample(可选方向, 数量) + class 分型结构(Enum): """描述三根K线构成的顶底分型形态。 @@ -1797,10 +1817,11 @@ class 相对强弱指数: 下跌幅度: float = 0.0, 平滑系数: float = 0.0, RSI_SMA: Optional[float] = None, - RSI历史队列: List[float] = None, + RSI历史队列: Optional[deque[float]] = None, + RSI和: float = 0.0, ): if RSI历史队列 is None: - RSI历史队列 = [] + RSI历史队列 = deque() # 原始数据 self.时间戳 = 时间戳 @@ -1829,6 +1850,7 @@ class 相对强弱指数: # RSI的SMA(信号线)相关字段 self.RSI_SMA = RSI_SMA self.RSI历史队列 = RSI历史队列 + self.RSI和 = RSI和 @classmethod def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: @@ -1916,19 +1938,18 @@ class 相对强弱指数: # ----- 计算RSI的SMA(简单移动平均) ----- RSI_SMA = None - 历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else [] + 历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else deque() + RSI和 = 前一个RSI.RSI和 if RSI_SMA周期 is not None and RSI_SMA周期 > 0 and RSI is not None: - # 将当前RSI加入队列 历史队列.append(RSI) - # 保持队列长度不超过周期 + RSI和 += RSI if len(历史队列) > RSI_SMA周期: - 历史队列.pop(0) - # 计算SMA(即使队列未满也计算当前平均值) + RSI和 -= 历史队列.popleft() if 历史队列: - RSI_SMA = sum(历史队列) / len(历史队列) + RSI_SMA = RSI和 / len(历史队列) else: - # 未启用SMA,清空队列 - 历史队列 = [] + 历史队列 = deque() + RSI和 = 0.0 return cls( 时间戳=当前时间, @@ -1945,6 +1966,7 @@ class 相对强弱指数: RSI_SMA周期=RSI_SMA周期, RSI_SMA=RSI_SMA, RSI历史队列=历史队列, + RSI和=RSI和, ) @classmethod @@ -1996,16 +2018,16 @@ class 随机指标: K: Optional[float] = None, D: Optional[float] = None, J: Optional[float] = None, - 历史最高价队列: list[float] = None, - 历史最低价队列: list[float] = None, + 历史最高价队列: Optional[deque[float]] = None, + 历史最低价队列: Optional[deque[float]] = None, 前一个RSV: Optional[float] = None, 前一个K: Optional[float] = None, 前一个D: Optional[float] = None, ): if 历史最高价队列 is None: - 历史最高价队列 = [] + 历史最高价队列 = deque() if 历史最低价队列 is None: - 历史最低价队列 = [] + 历史最低价队列 = deque() # 原始数据 self.时间戳 = 时间戳 @@ -2065,8 +2087,8 @@ class 随机指标: K=None, D=None, J=None, - 历史最高价队列=[初始最高价], - 历史最低价队列=[初始最低价], + 历史最高价队列=deque([初始最高价]), + 历史最低价队列=deque([初始最低价]), 前一个RSV=None, 前一个K=None, 前一个D=None, @@ -2113,13 +2135,13 @@ class 随机指标: 历史最高价 = 前一个KDJ.历史最高价队列.copy() 历史最高价.append(当前最高价) if len(历史最高价) > N: - 历史最高价.pop(0) + 历史最高价.popleft() # 更新历史最低价队列 历史最低价 = 前一个KDJ.历史最低价队列.copy() 历史最低价.append(当前最低价) if len(历史最低价) > N: - 历史最低价.pop(0) + 历史最低价.popleft() # 计算RSV(需要队列长度达到N才能计算) RSV = None @@ -2218,7 +2240,7 @@ class 布林带: self.上轨 = 上轨 self.中轨 = 中轨 self.下轨 = 下轨 - self._历史队列 = 历史队列 if 历史队列 is not None else [] + self._历史队列 = 历史队列 if 历史队列 is not None else deque() self._均值 = _均值 self._方差和 = _方差和 @@ -2233,7 +2255,7 @@ class 布林带: :return: 初始的布林带实例 """ 价格 = 指标.K线取值(k线, 计算方式) - return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格]) + return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=deque([价格])) @classmethod def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带: @@ -2251,7 +2273,7 @@ class 布林带: q = prev._历史队列.copy() q.append(当前价) if len(q) > 周期: - q.pop(0) + q.popleft() # 增量均值和方差 if len(q) < 周期: @@ -2969,6 +2991,58 @@ class K线: """ return 序列[序列.index(始) : 序列.index(终) + 1] + def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> "K线": + 时间偏移 = timedelta(seconds=self.周期) + 时间戳: datetime = self.时间戳 + 时间偏移 + 成交量: float = 998 + 高: float = 0 + 低: float = 0 + 高低差 = self.高 - self.低 + match 方向: + case 相对方向.向上: + 偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883)) + 低 = self.低 + 偏移 + 高 = self.高 + 偏移 + case 相对方向.向下: + 偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883)) + 低 = self.低 - 偏移 + 高 = self.高 - 偏移 + case 相对方向.向上缺口: + 偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883)) + 低 = self.低 + 偏移 + 高 = self.高 + 偏移 + case 相对方向.向下缺口: + 偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883)) + 低 = self.低 - 偏移 + 高 = self.高 - 偏移 + case 相对方向.衔接向上: + 偏移 = self.高 - self.低 + 高 = self.高 + 偏移 + 低 = self.高 + case 相对方向.衔接向下: + 偏移 = self.高 - self.低 + 高 = self.低 + 低 = self.低 - 偏移 + + try: + 小数点 = [len(str(n).split(".")[-1]) for n in (self.开盘价, self.高, self.低, self.收盘价)] + except: + 小数点 = [2, 1] + 新K线 = K线.创建普K( + 标识=self.标识, + 时间戳=时间戳, + 开盘价=round(random.uniform(高, 低), max(小数点)), + 最高价=round(高, max(小数点)), + 最低价=round(低, max(小数点)), + 收盘价=round(random.uniform(高, 低), max(小数点)), + 成交量=成交量 * random.random(), + 序号=self.序号 + 1, + 周期=self.周期, + ) + + # assert 相对方向.分析(self, 新K线) is 方向, (方向, 相对方向.分析(self, 新K线)) + return 新K线 + class 缠论K线: """经包含处理后的标准化K线,有方向和分型结构标记。 @@ -3723,7 +3797,7 @@ class 虚线: 段.实_中枢序列 = [] 段.虚_中枢序列 = [] 段.合_中枢序列 = [] - 段.基础序列 = 虚线序列 + 段.基础序列 = 虚线序列[:] return 段 @classmethod @@ -3892,6 +3966,111 @@ class 虚线: return True return False + @classmethod + def _计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): + """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) + + :param 普K序列: K线序列 + :param 方向: 运行方向 + :return: [柱子背驰, DIF背驰, DEA背驰] + """ + if 方向 is 相对方向.向上: + 柱子序列 = [] + 离差值序列 = [] + 信号线序列 = [] + for k线 in 普K序列: + m = k线.macd + if m.MACD柱 > 0: + 柱子序列.append(k线) + if m.DIF > 0: + 离差值序列.append(k线) + if m.DEA > 0: + 信号线序列.append(k线) + + if not 柱子序列: + return [False, False, False] + 最高柱子 = max(柱子序列, key=lambda k线: k线.macd.MACD柱) + 最高离差值 = max(离差值序列, key=lambda k线: k线.macd.DIF) if 离差值序列 else None + 最高信号线 = max(信号线序列, key=lambda k线: k线.macd.DEA) if 信号线序列 else None + + 结果 = [] + 柱子 = [最高柱子, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.MACD柱 > 柱子[1].macd.MACD柱 and 柱子[0].高 < 柱子[1].高: + 结果.append(True) + else: + 结果.append(False) + + if 最高离差值 is not None: + 柱子 = [最高离差值, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DIF > 柱子[1].macd.DIF and 柱子[0].高 < 柱子[1].高: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + if 最高信号线 is not None: + 柱子 = [最高信号线, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DEA > 柱子[1].macd.DEA and 柱子[0].高 < 柱子[1].高: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + return 结果 + else: + 柱子序列 = [] + 离差值序列 = [] + 信号线序列 = [] + for k线 in 普K序列: + m = k线.macd + if m.MACD柱 < 0: + 柱子序列.append(k线) + if m.DIF < 0: + 离差值序列.append(k线) + if m.DEA < 0: + 信号线序列.append(k线) + + if not 柱子序列: + return [False, False, False] + 最高柱子 = max(柱子序列, key=lambda k线: abs(k线.macd.MACD柱)) + 最高离差值 = max(离差值序列, key=lambda k线: abs(k线.macd.DIF)) if 离差值序列 else None + 最高信号线 = max(信号线序列, key=lambda k线: abs(k线.macd.DEA)) if 信号线序列 else None + + 结果 = [] + 柱子 = [最高柱子, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.MACD柱 < 柱子[1].macd.MACD柱 and 柱子[0].低 > 柱子[1].低: + 结果.append(True) + else: + 结果.append(False) + + if 最高离差值 is not None: + 柱子 = [最高离差值, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DIF < 柱子[1].macd.DIF and 柱子[0].低 > 柱子[1].低: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + if 最高信号线 is not None: + 柱子 = [最高信号线, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DEA < 柱子[1].macd.DEA and 柱子[0].低 > 柱子[1].低: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + return 结果 + @classmethod def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) @@ -4430,8 +4609,7 @@ class 笔: 临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck) 递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) if 分型序列 and 分型序列[-1] is 临时分型: - """""" - # logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型) + logger.warning(f"笔.分析 事后修复错过的笔:{临时分型}, 当前分型: {当前分型}") 递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) return 递归层次 @@ -4589,14 +4767,14 @@ class 线段特征: return self.标识 # f"{self.标识}:{self.序号}" def __str__(self): - if not len(self): + if not len(self.基础序列): return f"{self.标识}<{self.线段方向}, 空>" - return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>" + return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>" def __repr__(self): - if not len(self): + if not len(self.基础序列): return f"{self.标识}<{self.线段方向}, 空>" - return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>" + return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>" @property def 文(self) -> 分型: @@ -4807,6 +4985,11 @@ class 线段: __slots__ = [] + @staticmethod + def _索引(序列: list, 项) -> int: + """O(1) index lookup — 序列元素序号连续递增。""" + return 项.序号 - 序列[0].序号 + @classmethod def _添加虚线(cls, 段: 虚线, 筆: 虚线): """向线段中添加一笔 @@ -4937,7 +5120,7 @@ class 线段: break if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0): - 段.基础序列[:] = 基础序列[:] + 段.基础序列[:] = 基础序列 else: raise RuntimeError() else: @@ -4954,7 +5137,7 @@ class 线段: return 基础序列 = 段.基础序列 if 段.前一结束位置 and 段.前一结束位置 in 基础序列: - 基础序列 = 段.基础序列[段.基础序列.index(段.前一结束位置) - 1 :] + 基础序列 = 段.基础序列[cls._索引(段.基础序列, 段.前一结束位置) - 1 :] 特征序列 = 线段特征.静态分析(基础序列, 段.方向, 线段.四象(段), 配置.线段_特征序列忽视老阴老阳) if len(特征序列) >= 3: @@ -5072,7 +5255,7 @@ class 线段: 特征后一笔 = 最近特征.基础序列[-1] if 特征后一笔 is not None: - 序号 = 段.基础序列.index(特征后一笔) + 序号 = cls._索引(段.基础序列, 特征后一笔) if 序号 < len(段.基础序列) - 1: 下一笔 = 段.基础序列[序号 + 1] if (段.方向 is 相对方向.向上 and 段.高 <= 下一笔.高) or (段.方向 is 相对方向.向下 and 段.低 >= 下一笔.低): @@ -5091,15 +5274,16 @@ class 线段: :param 序列: 参考序列 """ 基础序列 = [] + 序列集 = set(序列) if not isinstance(序列, set) else 序列 for 元素 in 段.基础序列: - if 元素 not in 序列: + if 元素 not in 序列集: break if 基础序列: if not 基础序列[-1].之后是(元素): break 基础序列.append(元素) - 段.基础序列[:] = 基础序列[:] + 段.基础序列[:] = 基础序列 段.特征序列[2] = None @classmethod @@ -5168,16 +5352,17 @@ class 线段: return True @classmethod - def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: str): + def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int): """内部方法:向线段序列添加新线段 :param 线段序列: 线段列表 :param 待添加线段: 新线段 :param 配置: 缠论配置 :param 行号: 调用行号 + :param 层级: 递归层级 """ if 线段序列 and not 线段序列[-1].之后是(待添加线段): - raise ValueError(f"线段.向序列中添加 不连续[{行号}]", 线段序列[-1].武, 待添加线段.文) + raise ValueError(f"线段.向序列中添加 不连续[{行号}, {层级}]", 线段序列[-1].武, 待添加线段.文) 待添加线段.模式 = "文武" if not 线段序列: @@ -5188,10 +5373,10 @@ class 线段: if not 之前线段.特征序列[2] and not 之前线段.短路修正: assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列 - raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段.右 = None", 之前线段) + raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段.右 = None", 之前线段) if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正: - raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段[-1] not in 待添加虚线!", 之前线段) + raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段[-1] not in 待添加虚线!", 之前线段) 待添加线段.序号 = 之前线段.序号 + 1 待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None @@ -5204,13 +5389,14 @@ class 线段: # logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线) @classmethod - def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str): + def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int): """内部方法:从线段序列弹出最后一个线段 :param 线段序列: 线段列表 :param 待弹出线段: 待弹出的线段 :param 配置: 缠论配置 :param 行号: 调用行号 + :param 层级: 递归层级 :return: 弹出的线段或None """ if not 线段序列: @@ -5223,7 +5409,7 @@ class 线段: if 右 is not None: 结构 = 分型结构.分析(左, 中, 右, True, True) if 结构 in (分型结构.顶, 分型结构.底) and not 相对方向.分析(左.高, 左.低, 中.高, 中.低).是否缺口(): - logger.warning(f"警告<{行号}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}") + logger.warning(f"警告<{行号}, {层级}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}") 线段序列.pop() 待弹出线段.前一结束位置 = None @@ -5268,7 +5454,7 @@ class 线段: # 执行修正 序列 = 当前线段.基础序列[:] - 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级) assert 线段序列, "缺口突破: 线段序列为第二次空!" 当前线段 = 线段序列[-1] @@ -5277,7 +5463,7 @@ class 线段: assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!" 当前线段基础序列.extend(序列) - 当前线段.基础序列[:] = 当前线段基础序列[:] + 当前线段.基础序列[:] = 当前线段基础序列 线段._刷新(当前线段, 配置) return True @@ -5305,7 +5491,7 @@ class 线段: assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!" # 切割基础序列 - 基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :] + 基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :] # 长度条件 if not (len(基础序列) == 4 and len(线段序列) >= 2): @@ -5321,19 +5507,25 @@ class 线段: logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出 基础序列 = 当前线段.基础序列[:] - 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级) assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!" 当前线段 = 线段序列[-1] 当前线段.特征序列[2] = None - assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" - for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]: + # assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" + if 当前线段.基础序列[-1] not in 基础序列: + logger.error(f"非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!") + 序号 = 0 + else: + 序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1 + + for 临时虚线 in 基础序列[序号:]: 线段._添加虚线(当前线段, 临时虚线) 线段._刷新(当前线段, 配置) 当前线段.短路修正 = True if 当前线段.特征序列[2] is not None: 段 = 虚线.创建线段([左, 中, 右]) - 线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级) 段.特征序列[0] = 线段特征.新建([中], 段.方向) return True @@ -5379,7 +5571,7 @@ class 线段: # 执行修正 当前线段.短路修正 = True 新段 = 虚线.创建线段(基础序列) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) return True @classmethod @@ -5422,7 +5614,7 @@ class 线段: # 创建第一个新段(之后基础序列去掉最后3个) 新段 = 虚线.创建线段(之后基础序列[:-3]) 新段.短路修正 = True - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) # 根据当前线段的四象决定是否清空前一个缺口 if 线段.四象(当前线段) in ("老阴", "老阳"): @@ -5430,7 +5622,7 @@ class 线段: # 创建第二个新段(最后3个元素) 新段 = 虚线.创建线段(之后基础序列[-3:]) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) return True @@ -5466,7 +5658,7 @@ class 线段: if not 线段._基础判断(左, 中, 右, 关系序列): # FIXME 首个线段必须有明确方向 continue 段 = 虚线.创建线段([左, 中, 右]) - 线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级) 段.特征序列[0] = 线段特征.新建([中], 段.方向) break if not 线段序列: @@ -5475,7 +5667,7 @@ class 线段: # -------------------- 2. 清理无效的尾部引用 -------------------- while 线段序列 and 线段序列[-1].前一结束位置: if 线段序列[-1].前一结束位置 not in 笔序列: - 线段._弹出线段(线段序列, 线段序列[-1], 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 线段序列[-1], 配置, sys._getframe().f_lineno, 层级) else: break @@ -5487,7 +5679,7 @@ class 线段: 线段._序列重置(当前线段, 笔序列) if len(当前线段.基础序列) < 3: - 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级) if not 线段序列: return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) @@ -5497,7 +5689,7 @@ class 线段: if 当前线段.特征序列[2] is not None: 基础序列 = 线段.分割序列(当前线段)[1] 新段 = 虚线.创建线段(基础序列) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) if 线段.四象(当前线段) in ("老阴", "老阳"): 新段.前一缺口 = None @@ -5514,9 +5706,10 @@ class 线段: 当前线段 = 线段序列[-1] if not 当前线段.基础序列: raise RuntimeError - 起始索引 = 笔序列.index(当前线段.基础序列[-1]) + 1 + 起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1 - for 当前虚线 in 笔序列[起始索引:]: + for idx in range(起始索引, len(笔序列)): + 当前虚线 = 笔序列[idx] 当前线段 = 线段序列[-1] 四象 = 线段.四象(当前线段) @@ -5543,7 +5736,7 @@ class 线段: 基础序列 = 线段.分割序列(当前线段)[1] 新段 = 虚线.创建线段(基础序列) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) if 四象 in ("老阴", "老阳"): 新段.前一缺口 = None @@ -5575,15 +5768,16 @@ class 线段: :param 序列: 参考序列 """ 基础序列 = [] + 序列集 = set(序列) if not isinstance(序列, set) else 序列 for 元素 in 段.基础序列: - if 元素 not in 序列: + if 元素 not in 序列集: break if 基础序列: if not 基础序列[-1].之后是(元素): logger.warning(" 线段._验证序列 数据不连续") break 基础序列.append(元素) - 段.基础序列[:] = 基础序列[:] + 段.基础序列[:] = 基础序列 if len(段.基础序列) % 2 == 0: 段.基础序列 and 段.基础序列.pop() @@ -5676,7 +5870,7 @@ class 线段: if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号: return None - 序号 = 虚线序列.index(当前线段.基础序列[-1]) + 1 + 序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1 if 序号 >= len(虚线序列): return None @@ -5739,7 +5933,7 @@ class 线段: if 当前段.实_中枢序列: if 阳[-1] in 当前段.实_中枢序列[-1].基础序列: # 当前最后一笔在最后一中枢里 - 序号 = 当前段.基础序列.index(当前段.实_中枢序列[-1].基础序列[0]) + 序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0]) 进入段 = 当前段.基础序列[序号 - 1] 离开段 = 阳[-1] assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号) @@ -5793,7 +5987,7 @@ class 线段: 笔序列.append(停顿) 线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向.顺, 相对方向.逆, 相对方向.同]) if 线段序列 and 线段序列[-1].武 is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1: - 新段 = 虚线.创建线段(线段序列[-1].基础序列[:]) + 新段 = 虚线.创建线段(线段序列[-1].基础序列) 新段.序号 = self.序号 线段._刷新(新段, 观察员.配置) if 新段.方向 is self.方向: @@ -5984,7 +6178,7 @@ class 中枢: :return: 虚线列表 """ - 序列: List = self.基础序列[:] + 序列: List = self.基础序列.copy() if self.第三买卖线 is not None: 序列.append(self.第三买卖线) return 序列 @@ -6008,13 +6202,14 @@ class 中枢: """ 有效序列 = self.基础序列[:] 无效序列 = [] + 序列集 = set(序列) for 元素 in self.基础序列: - if 元素 not in 序列: + if 元素 not in 序列集: 无效序列.append(元素) if 无效序列: 无效 = 无效序列[0] - 序号 = self.基础序列.index(无效) + 序号 = 线段._索引(self.基础序列, 无效) 有效序列 = self.基础序列[:序号] if len(有效序列) < 3: @@ -6022,14 +6217,14 @@ class 中枢: self.本级_第三买卖线 = None return False - self.基础序列[:] = 有效序列 + self.基础序列 = 有效序列 有效序列 = [] for 元素 in self.基础序列: if 相对方向.分析(self.高, self.低, 元素.高, 元素.低).是否缺口(): break 有效序列.append(元素) - self.基础序列[:] = 有效序列 + self.基础序列 = 有效序列 if len(self.基础序列) < 3: return False @@ -6201,7 +6396,7 @@ class 中枢: 左, 中, 右 = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] if 中枢.基础检查(左, 中, 右): 新中枢 = 中枢.创建(左, 中, 右, 中.级别, 标识) - 序号 = 虚线序列.index(左) + 序号 = 线段._索引(虚线序列, 左) if 跳过首部 and (左.序号 == 0 or 序号 == 0): continue # 方便计算走势 if 序号 >= 2: @@ -6222,7 +6417,7 @@ class 中枢: 中枢._从中枢序列尾部弹出(中枢序列, 当前中枢) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) - 序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1 + 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1 基础序列 = [] for 当前虚线 in 虚线序列[序号:]: diff --git a/chanlun-py/Cargo.toml b/chanlun-py/Cargo.toml index b39ff43..87bc46a 100644 --- a/chanlun-py/Cargo.toml +++ b/chanlun-py/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chanlun-py" -version = "26.6.47" +version = "26.6.73" edition = "2024" description = "缠论技术分析库 — Rust 高性能 Python 绑定" authors = ["YuYuKunKun"] @@ -12,11 +12,12 @@ crate-type = ["cdylib"] name = "chanlun" [dependencies] -chanlun = { path = "../chanlun" } -lru = "0.18" +chanlun = "26.6.4" # { path = "../chanlun" } +parking_lot = "0.12" +tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] } +tracing-core = "0.1" +dashmap = "6" +tracing = "0.1" pyo3 = { version = "0.28", features = ["experimental-inspect"] } serde_json = "1" chrono = "0.4" -tracing = "0.1" -tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] } -tracing-core = "0.1" diff --git a/chanlun-py/README.md b/chanlun-py/README.md index cc71e28..3ad8065 100644 --- a/chanlun-py/README.md +++ b/chanlun-py/README.md @@ -72,6 +72,77 @@ pip install target/wheels/chanlun-*.whl - 类名 / 方法名 / 字段名与 `chan.py` 保持一致 - 支持 `.nb` 二进制文件格式(大端字节序) +## 性能配置 + +### 缓存模式 + +Python 对象缓存有两种模式,通过环境变量 `CHANLUN_CACHE_MODE` 或函数调用切换: + +```python +from chanlun._chanlun import set_cache_mode, get_cache_mode + +# 默认:thread_local,每线程独立缓存,零锁,多线程场景最佳 +print(get_cache_mode()) # "thread_local" + +# 全局:dashmap 分片哈希表,跨线程 Python `is` 身份一致 +set_cache_mode("global") # 必须在创建任何观察者之前调用 +``` + +```bash +# 环境变量方式 +CHANLUN_CACHE_MODE=global python main.py # 全局缓存 +python main.py # 默认:线程局部缓存 +``` + +| 模式 | 性能 | Python `is` 跨线程 | 适用场景 | +|------|------|---------------------|----------| +| `thread_local`(默认) | 零锁,最快 | 否 | 批量回测、多线程独立分析 | +| `global` | dashmap 分片锁 | 是 | 测试验证、跨线程对象共享 | + +### 日志模式 + +日志输出有三种模式,通过环境变量 `CHANLUN_LOG_MODE` 或函数调用切换: + +```python +from chanlun._chanlun import set_log_mode, set_log_level, get_log_mode + +# 默认:off,不输出,零开销 +print(get_log_mode()) # "off" + +# 简单模式:直接 eprintln/println +set_log_mode("simple") +set_log_level("debug") # 必需:设置日志级别启用输出 + +# Tracing 模式:带时间戳和文件位置格式化输出 +set_log_mode("tracing") +set_log_level("debug") +``` + +```bash +# 环境变量方式 +CHANLUN_LOG_MODE=simple python main.py # 简单输出 +CHANLUN_LOG_MODE=tracing python main.py # 格式化输出 +python main.py # 默认:静默 +``` + +| 模式 | 输出方式 | 性能 | 格式 | +|------|---------|------|------| +| `off`(默认) | 无 | 零开销 | — | +| `simple` | `eprintln!` / `println!` | 极轻 | 纯文本 | +| `tracing` | tracing-subscriber | 稍重 | `2026-06-12 01:57:59.942 WARN file.rs:line` | + +### 观察者直传(避免 Python list 转换) + +背驰分析新增 `_OBS` 后缀方法,直接接受观察者引用,跳过 `list[K线]` ↔ `Vec>` 转换: + +```python +# 旧方式:构建 Python 列表 +result = 背驰分析.MACD背驰(进入段, 离开段, obs.普通K线序列, "总") + +# 新方式:直接传观察者 +result = 背驰分析.MACD背驰_OBS(进入段, 离开段, obs, "总") +``` + ## 许可 本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parse(MIT)、termcolor(MIT)。 diff --git a/chanlun-py/chanlun.pyi b/chanlun-py/chanlun.pyi index b7d4adf..b70bc78 100644 --- a/chanlun-py/chanlun.pyi +++ b/chanlun-py/chanlun.pyi @@ -5,11 +5,18 @@ from typing import Any, ClassVar, Optional, List, Dict, Tuple, Union 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_log_mode() -> str: ... +def set_log_mode(mode: str) -> None: ... +def get_cache_mode() -> str: ... +def set_cache_mode(mode: str) -> None: ... def get_分型模式() -> bool: ... def set_分型模式(value: bool) -> 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]: ... @@ -75,6 +82,8 @@ class 相对方向: def 翻转(self) -> 相对方向: ... @classmethod def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... + @classmethod + def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... def __hash__(self) -> int: ... @@ -326,6 +335,7 @@ class K线: def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... + def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ... def __bytes__(self) -> bytes: ... def __eq__(self, other: Any) -> bool: ... def __hash__(self) -> int: ... @@ -438,9 +448,9 @@ class 虚线: @property def 模式(self) -> str: ... @property - def 特征序列_显示(self) -> bool: ... - @特征序列_显示.setter - def 特征序列_显示(self, value: bool) -> None: ... + def _特征序列_显示(self) -> bool: ... + @_特征序列_显示.setter + def _特征序列_显示(self, value: bool) -> None: ... @property def 特征序列(self) -> List[Optional[线段特征]]: ... @property @@ -526,6 +536,8 @@ class 虚线: class 线段特征: @property def 序号(self) -> int: ... + @序号.setter + def 序号(self, value: int) -> None: ... @property def 标识(self) -> str: ... @标识.setter @@ -570,6 +582,18 @@ class 背驰分析: def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... @classmethod def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... + @classmethod + def MACD背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 方式: str = "总") -> bool: ... + @classmethod + def 全量背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 任意背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 配置背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置) -> bool: ... + @classmethod + def 任选背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 背驰模式_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置, 模式: str) -> bool: ... class 笔: @classmethod @@ -793,6 +817,30 @@ class 观察者: def 扩展线段序列_扩展线段(self) -> List[虚线]: ... @property def 扩展中枢序列_扩展线段(self) -> List[中枢]: ... + @property + def 线段分析层次(self) -> int: ... + @线段分析层次.setter + def 线段分析层次(self, value: int) -> None: ... + @property + def 扩展线段分析层次(self) -> int: ... + @扩展线段分析层次.setter + def 扩展线段分析层次(self, value: int) -> None: ... + @property + def 混合扩展线段分析层次(self) -> int: ... + @混合扩展线段分析层次.setter + def 混合扩展线段分析层次(self, value: int) -> None: ... + @property + def 线段序列组(self) -> List[List[虚线]]: ... + @property + def 中枢序列组(self) -> List[List[中枢]]: ... + @property + def 扩展线段序列组(self) -> List[List[虚线]]: ... + @property + def 扩展中枢序列组(self) -> List[List[中枢]]: ... + @property + def 混合扩展线段序列组(self) -> List[List[虚线]]: ... + @property + def 混合扩展中枢序列组(self) -> List[List[中枢]]: ... def 重置基础序列(self) -> None: ... def 增加原始K线(self, 普K: K线) -> None: ... def 投喂原始数据(self, 时间戳: int, 开: float, 高: float, 低: float, 收: float, 量: float) -> None: ... diff --git a/chanlun-py/chanlun/__init__.pyi b/chanlun-py/chanlun/__init__.pyi index d3a8f39..b70bc78 100644 --- a/chanlun-py/chanlun/__init__.pyi +++ b/chanlun-py/chanlun/__init__.pyi @@ -9,10 +9,16 @@ 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_log_mode() -> str: ... +def set_log_mode(mode: str) -> None: ... +def get_cache_mode() -> str: ... +def set_cache_mode(mode: str) -> None: ... def get_分型模式() -> bool: ... def set_分型模式(value: bool) -> 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]: ... @@ -76,6 +82,8 @@ class 相对方向: def 翻转(self) -> 相对方向: ... @classmethod def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... + @classmethod + def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... def __hash__(self) -> int: ... @@ -327,6 +335,7 @@ class K线: def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... + def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ... def __bytes__(self) -> bytes: ... def __eq__(self, other: Any) -> bool: ... def __hash__(self) -> int: ... @@ -573,6 +582,18 @@ class 背驰分析: def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... @classmethod def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... + @classmethod + def MACD背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 方式: str = "总") -> bool: ... + @classmethod + def 全量背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 任意背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 配置背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置) -> bool: ... + @classmethod + def 任选背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 背驰模式_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置, 模式: str) -> bool: ... class 笔: @classmethod diff --git a/chanlun-py/chanlun/chan.py b/chanlun-py/chanlun/chan.py index 62de866..7294d72 100644 --- a/chanlun-py/chanlun/chan.py +++ b/chanlun-py/chanlun/chan.py @@ -31,12 +31,14 @@ from __future__ import annotations import json import math import os +from collections import deque +import random import struct import sys import tempfile import datetime as datetime_module from dataclasses import dataclass, field -from datetime import datetime +from datetime import datetime, timedelta from enum import Enum from functools import lru_cache from pathlib import Path @@ -52,6 +54,7 @@ from typing import ( Sequence, Callable, Set, + Generator, ) from loguru import logger @@ -1425,6 +1428,23 @@ class 相对方向(Enum): return 相对方向.逆 raise RuntimeError("无法识别的方向") + @classmethod + def 从序列中机选( + cls, + 数量: int, + 可选方向: List["相对方向"], + 可重复: bool = True, # 是否允许重复选择 + ) -> Generator["相对方向", None, None]: + if not 可重复 and 数量 > len(可选方向): + raise ValueError("数量超过可选方向数") + + if 可重复: + while 数量 > 0: + yield random.choice(可选方向) + 数量 -= 1 + else: + yield from random.sample(可选方向, 数量) + class 分型结构(Enum): """描述三根K线构成的顶底分型形态。 @@ -1797,10 +1817,11 @@ class 相对强弱指数: 下跌幅度: float = 0.0, 平滑系数: float = 0.0, RSI_SMA: Optional[float] = None, - RSI历史队列: List[float] = None, + RSI历史队列: Optional[deque[float]] = None, + RSI和: float = 0.0, ): if RSI历史队列 is None: - RSI历史队列 = [] + RSI历史队列 = deque() # 原始数据 self.时间戳 = 时间戳 @@ -1829,6 +1850,7 @@ class 相对强弱指数: # RSI的SMA(信号线)相关字段 self.RSI_SMA = RSI_SMA self.RSI历史队列 = RSI历史队列 + self.RSI和 = RSI和 @classmethod def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: @@ -1916,19 +1938,18 @@ class 相对强弱指数: # ----- 计算RSI的SMA(简单移动平均) ----- RSI_SMA = None - 历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else [] + 历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else deque() + RSI和 = 前一个RSI.RSI和 if RSI_SMA周期 is not None and RSI_SMA周期 > 0 and RSI is not None: - # 将当前RSI加入队列 历史队列.append(RSI) - # 保持队列长度不超过周期 + RSI和 += RSI if len(历史队列) > RSI_SMA周期: - 历史队列.pop(0) - # 计算SMA(即使队列未满也计算当前平均值) + RSI和 -= 历史队列.popleft() if 历史队列: - RSI_SMA = sum(历史队列) / len(历史队列) + RSI_SMA = RSI和 / len(历史队列) else: - # 未启用SMA,清空队列 - 历史队列 = [] + 历史队列 = deque() + RSI和 = 0.0 return cls( 时间戳=当前时间, @@ -1945,6 +1966,7 @@ class 相对强弱指数: RSI_SMA周期=RSI_SMA周期, RSI_SMA=RSI_SMA, RSI历史队列=历史队列, + RSI和=RSI和, ) @classmethod @@ -1996,16 +2018,16 @@ class 随机指标: K: Optional[float] = None, D: Optional[float] = None, J: Optional[float] = None, - 历史最高价队列: list[float] = None, - 历史最低价队列: list[float] = None, + 历史最高价队列: Optional[deque[float]] = None, + 历史最低价队列: Optional[deque[float]] = None, 前一个RSV: Optional[float] = None, 前一个K: Optional[float] = None, 前一个D: Optional[float] = None, ): if 历史最高价队列 is None: - 历史最高价队列 = [] + 历史最高价队列 = deque() if 历史最低价队列 is None: - 历史最低价队列 = [] + 历史最低价队列 = deque() # 原始数据 self.时间戳 = 时间戳 @@ -2065,8 +2087,8 @@ class 随机指标: K=None, D=None, J=None, - 历史最高价队列=[初始最高价], - 历史最低价队列=[初始最低价], + 历史最高价队列=deque([初始最高价]), + 历史最低价队列=deque([初始最低价]), 前一个RSV=None, 前一个K=None, 前一个D=None, @@ -2113,13 +2135,13 @@ class 随机指标: 历史最高价 = 前一个KDJ.历史最高价队列.copy() 历史最高价.append(当前最高价) if len(历史最高价) > N: - 历史最高价.pop(0) + 历史最高价.popleft() # 更新历史最低价队列 历史最低价 = 前一个KDJ.历史最低价队列.copy() 历史最低价.append(当前最低价) if len(历史最低价) > N: - 历史最低价.pop(0) + 历史最低价.popleft() # 计算RSV(需要队列长度达到N才能计算) RSV = None @@ -2218,7 +2240,7 @@ class 布林带: self.上轨 = 上轨 self.中轨 = 中轨 self.下轨 = 下轨 - self._历史队列 = 历史队列 if 历史队列 is not None else [] + self._历史队列 = 历史队列 if 历史队列 is not None else deque() self._均值 = _均值 self._方差和 = _方差和 @@ -2233,7 +2255,7 @@ class 布林带: :return: 初始的布林带实例 """ 价格 = 指标.K线取值(k线, 计算方式) - return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格]) + return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=deque([价格])) @classmethod def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带: @@ -2251,7 +2273,7 @@ class 布林带: q = prev._历史队列.copy() q.append(当前价) if len(q) > 周期: - q.pop(0) + q.popleft() # 增量均值和方差 if len(q) < 周期: @@ -2969,6 +2991,58 @@ class K线: """ return 序列[序列.index(始) : 序列.index(终) + 1] + def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> "K线": + 时间偏移 = timedelta(seconds=self.周期) + 时间戳: datetime = self.时间戳 + 时间偏移 + 成交量: float = 998 + 高: float = 0 + 低: float = 0 + 高低差 = self.高 - self.低 + match 方向: + case 相对方向.向上: + 偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883)) + 低 = self.低 + 偏移 + 高 = self.高 + 偏移 + case 相对方向.向下: + 偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883)) + 低 = self.低 - 偏移 + 高 = self.高 - 偏移 + case 相对方向.向上缺口: + 偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883)) + 低 = self.低 + 偏移 + 高 = self.高 + 偏移 + case 相对方向.向下缺口: + 偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883)) + 低 = self.低 - 偏移 + 高 = self.高 - 偏移 + case 相对方向.衔接向上: + 偏移 = self.高 - self.低 + 高 = self.高 + 偏移 + 低 = self.高 + case 相对方向.衔接向下: + 偏移 = self.高 - self.低 + 高 = self.低 + 低 = self.低 - 偏移 + + try: + 小数点 = [len(str(n).split(".")[-1]) for n in (self.开盘价, self.高, self.低, self.收盘价)] + except: + 小数点 = [2, 1] + 新K线 = K线.创建普K( + 标识=self.标识, + 时间戳=时间戳, + 开盘价=round(random.uniform(高, 低), max(小数点)), + 最高价=round(高, max(小数点)), + 最低价=round(低, max(小数点)), + 收盘价=round(random.uniform(高, 低), max(小数点)), + 成交量=成交量 * random.random(), + 序号=self.序号 + 1, + 周期=self.周期, + ) + + # assert 相对方向.分析(self, 新K线) is 方向, (方向, 相对方向.分析(self, 新K线)) + return 新K线 + class 缠论K线: """经包含处理后的标准化K线,有方向和分型结构标记。 @@ -3723,7 +3797,7 @@ class 虚线: 段.实_中枢序列 = [] 段.虚_中枢序列 = [] 段.合_中枢序列 = [] - 段.基础序列 = 虚线序列 + 段.基础序列 = 虚线序列[:] return 段 @classmethod @@ -3892,6 +3966,111 @@ class 虚线: return True return False + @classmethod + def _计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): + """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) + + :param 普K序列: K线序列 + :param 方向: 运行方向 + :return: [柱子背驰, DIF背驰, DEA背驰] + """ + if 方向 is 相对方向.向上: + 柱子序列 = [] + 离差值序列 = [] + 信号线序列 = [] + for k线 in 普K序列: + m = k线.macd + if m.MACD柱 > 0: + 柱子序列.append(k线) + if m.DIF > 0: + 离差值序列.append(k线) + if m.DEA > 0: + 信号线序列.append(k线) + + if not 柱子序列: + return [False, False, False] + 最高柱子 = max(柱子序列, key=lambda k线: k线.macd.MACD柱) + 最高离差值 = max(离差值序列, key=lambda k线: k线.macd.DIF) if 离差值序列 else None + 最高信号线 = max(信号线序列, key=lambda k线: k线.macd.DEA) if 信号线序列 else None + + 结果 = [] + 柱子 = [最高柱子, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.MACD柱 > 柱子[1].macd.MACD柱 and 柱子[0].高 < 柱子[1].高: + 结果.append(True) + else: + 结果.append(False) + + if 最高离差值 is not None: + 柱子 = [最高离差值, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DIF > 柱子[1].macd.DIF and 柱子[0].高 < 柱子[1].高: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + if 最高信号线 is not None: + 柱子 = [最高信号线, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DEA > 柱子[1].macd.DEA and 柱子[0].高 < 柱子[1].高: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + return 结果 + else: + 柱子序列 = [] + 离差值序列 = [] + 信号线序列 = [] + for k线 in 普K序列: + m = k线.macd + if m.MACD柱 < 0: + 柱子序列.append(k线) + if m.DIF < 0: + 离差值序列.append(k线) + if m.DEA < 0: + 信号线序列.append(k线) + + if not 柱子序列: + return [False, False, False] + 最高柱子 = max(柱子序列, key=lambda k线: abs(k线.macd.MACD柱)) + 最高离差值 = max(离差值序列, key=lambda k线: abs(k线.macd.DIF)) if 离差值序列 else None + 最高信号线 = max(信号线序列, key=lambda k线: abs(k线.macd.DEA)) if 信号线序列 else None + + 结果 = [] + 柱子 = [最高柱子, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.MACD柱 < 柱子[1].macd.MACD柱 and 柱子[0].低 > 柱子[1].低: + 结果.append(True) + else: + 结果.append(False) + + if 最高离差值 is not None: + 柱子 = [最高离差值, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DIF < 柱子[1].macd.DIF and 柱子[0].低 > 柱子[1].低: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + if 最高信号线 is not None: + 柱子 = [最高信号线, 普K序列[-1]] + 柱子.sort(key=lambda k线: k线.时间戳) + if 柱子[0].macd.DEA < 柱子[1].macd.DEA and 柱子[0].低 > 柱子[1].低: + 结果.append(True) + else: + 结果.append(False) + else: + 结果.append(False) + + return 结果 + @classmethod def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) @@ -4430,8 +4609,7 @@ class 笔: 临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck) 递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) if 分型序列 and 分型序列[-1] is 临时分型: - """""" - # logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型) + logger.warning(f"笔.分析 事后修复错过的笔:{临时分型}, 当前分型: {当前分型}") 递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) return 递归层次 @@ -4589,14 +4767,14 @@ class 线段特征: return self.标识 # f"{self.标识}:{self.序号}" def __str__(self): - if not len(self): + if not len(self.基础序列): return f"{self.标识}<{self.线段方向}, 空>" - return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>" + return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>" def __repr__(self): - if not len(self): + if not len(self.基础序列): return f"{self.标识}<{self.线段方向}, 空>" - return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>" + return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>" @property def 文(self) -> 分型: @@ -4807,6 +4985,11 @@ class 线段: __slots__ = [] + @staticmethod + def _索引(序列: list, 项) -> int: + """O(1) index lookup — 序列元素序号连续递增。""" + return 项.序号 - 序列[0].序号 + @classmethod def _添加虚线(cls, 段: 虚线, 筆: 虚线): """向线段中添加一笔 @@ -4937,7 +5120,7 @@ class 线段: break if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0): - 段.基础序列[:] = 基础序列[:] + 段.基础序列[:] = 基础序列 else: raise RuntimeError() else: @@ -4954,7 +5137,7 @@ class 线段: return 基础序列 = 段.基础序列 if 段.前一结束位置 and 段.前一结束位置 in 基础序列: - 基础序列 = 段.基础序列[段.基础序列.index(段.前一结束位置) - 1 :] + 基础序列 = 段.基础序列[cls._索引(段.基础序列, 段.前一结束位置) - 1 :] 特征序列 = 线段特征.静态分析(基础序列, 段.方向, 线段.四象(段), 配置.线段_特征序列忽视老阴老阳) if len(特征序列) >= 3: @@ -5072,7 +5255,7 @@ class 线段: 特征后一笔 = 最近特征.基础序列[-1] if 特征后一笔 is not None: - 序号 = 段.基础序列.index(特征后一笔) + 序号 = cls._索引(段.基础序列, 特征后一笔) if 序号 < len(段.基础序列) - 1: 下一笔 = 段.基础序列[序号 + 1] if (段.方向 is 相对方向.向上 and 段.高 <= 下一笔.高) or (段.方向 is 相对方向.向下 and 段.低 >= 下一笔.低): @@ -5091,15 +5274,16 @@ class 线段: :param 序列: 参考序列 """ 基础序列 = [] + 序列集 = set(序列) if not isinstance(序列, set) else 序列 for 元素 in 段.基础序列: - if 元素 not in 序列: + if 元素 not in 序列集: break if 基础序列: if not 基础序列[-1].之后是(元素): break 基础序列.append(元素) - 段.基础序列[:] = 基础序列[:] + 段.基础序列[:] = 基础序列 段.特征序列[2] = None @classmethod @@ -5168,16 +5352,17 @@ class 线段: return True @classmethod - def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: str): + def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int): """内部方法:向线段序列添加新线段 :param 线段序列: 线段列表 :param 待添加线段: 新线段 :param 配置: 缠论配置 :param 行号: 调用行号 + :param 层级: 递归层级 """ if 线段序列 and not 线段序列[-1].之后是(待添加线段): - raise ValueError(f"线段.向序列中添加 不连续[{行号}]", 线段序列[-1].武, 待添加线段.文) + raise ValueError(f"线段.向序列中添加 不连续[{行号}, {层级}]", 线段序列[-1].武, 待添加线段.文) 待添加线段.模式 = "文武" if not 线段序列: @@ -5188,10 +5373,10 @@ class 线段: if not 之前线段.特征序列[2] and not 之前线段.短路修正: assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列 - raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段.右 = None", 之前线段) + raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段.右 = None", 之前线段) if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正: - raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段[-1] not in 待添加虚线!", 之前线段) + raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段[-1] not in 待添加虚线!", 之前线段) 待添加线段.序号 = 之前线段.序号 + 1 待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None @@ -5204,13 +5389,14 @@ class 线段: # logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线) @classmethod - def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str): + def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int): """内部方法:从线段序列弹出最后一个线段 :param 线段序列: 线段列表 :param 待弹出线段: 待弹出的线段 :param 配置: 缠论配置 :param 行号: 调用行号 + :param 层级: 递归层级 :return: 弹出的线段或None """ if not 线段序列: @@ -5223,7 +5409,7 @@ class 线段: if 右 is not None: 结构 = 分型结构.分析(左, 中, 右, True, True) if 结构 in (分型结构.顶, 分型结构.底) and not 相对方向.分析(左.高, 左.低, 中.高, 中.低).是否缺口(): - logger.warning(f"警告<{行号}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}") + logger.warning(f"警告<{行号}, {层级}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}") 线段序列.pop() 待弹出线段.前一结束位置 = None @@ -5268,7 +5454,7 @@ class 线段: # 执行修正 序列 = 当前线段.基础序列[:] - 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级) assert 线段序列, "缺口突破: 线段序列为第二次空!" 当前线段 = 线段序列[-1] @@ -5277,7 +5463,7 @@ class 线段: assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!" 当前线段基础序列.extend(序列) - 当前线段.基础序列[:] = 当前线段基础序列[:] + 当前线段.基础序列[:] = 当前线段基础序列 线段._刷新(当前线段, 配置) return True @@ -5305,7 +5491,7 @@ class 线段: assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!" # 切割基础序列 - 基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :] + 基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :] # 长度条件 if not (len(基础序列) == 4 and len(线段序列) >= 2): @@ -5321,19 +5507,25 @@ class 线段: logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出 基础序列 = 当前线段.基础序列[:] - 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级) assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!" 当前线段 = 线段序列[-1] 当前线段.特征序列[2] = None - assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" - for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]: + # assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" + if 当前线段.基础序列[-1] not in 基础序列: + logger.error(f"非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!") + 序号 = 0 + else: + 序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1 + + for 临时虚线 in 基础序列[序号:]: 线段._添加虚线(当前线段, 临时虚线) 线段._刷新(当前线段, 配置) 当前线段.短路修正 = True if 当前线段.特征序列[2] is not None: 段 = 虚线.创建线段([左, 中, 右]) - 线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级) 段.特征序列[0] = 线段特征.新建([中], 段.方向) return True @@ -5379,7 +5571,7 @@ class 线段: # 执行修正 当前线段.短路修正 = True 新段 = 虚线.创建线段(基础序列) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) return True @classmethod @@ -5422,7 +5614,7 @@ class 线段: # 创建第一个新段(之后基础序列去掉最后3个) 新段 = 虚线.创建线段(之后基础序列[:-3]) 新段.短路修正 = True - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) # 根据当前线段的四象决定是否清空前一个缺口 if 线段.四象(当前线段) in ("老阴", "老阳"): @@ -5430,7 +5622,7 @@ class 线段: # 创建第二个新段(最后3个元素) 新段 = 虚线.创建线段(之后基础序列[-3:]) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) return True @@ -5466,7 +5658,7 @@ class 线段: if not 线段._基础判断(左, 中, 右, 关系序列): # FIXME 首个线段必须有明确方向 continue 段 = 虚线.创建线段([左, 中, 右]) - 线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级) 段.特征序列[0] = 线段特征.新建([中], 段.方向) break if not 线段序列: @@ -5475,7 +5667,7 @@ class 线段: # -------------------- 2. 清理无效的尾部引用 -------------------- while 线段序列 and 线段序列[-1].前一结束位置: if 线段序列[-1].前一结束位置 not in 笔序列: - 线段._弹出线段(线段序列, 线段序列[-1], 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 线段序列[-1], 配置, sys._getframe().f_lineno, 层级) else: break @@ -5487,7 +5679,7 @@ class 线段: 线段._序列重置(当前线段, 笔序列) if len(当前线段.基础序列) < 3: - 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级) if not 线段序列: return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) @@ -5497,7 +5689,7 @@ class 线段: if 当前线段.特征序列[2] is not None: 基础序列 = 线段.分割序列(当前线段)[1] 新段 = 虚线.创建线段(基础序列) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) if 线段.四象(当前线段) in ("老阴", "老阳"): 新段.前一缺口 = None @@ -5514,9 +5706,10 @@ class 线段: 当前线段 = 线段序列[-1] if not 当前线段.基础序列: raise RuntimeError - 起始索引 = 笔序列.index(当前线段.基础序列[-1]) + 1 + 起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1 - for 当前虚线 in 笔序列[起始索引:]: + for idx in range(起始索引, len(笔序列)): + 当前虚线 = 笔序列[idx] 当前线段 = 线段序列[-1] 四象 = 线段.四象(当前线段) @@ -5543,7 +5736,7 @@ class 线段: 基础序列 = 线段.分割序列(当前线段)[1] 新段 = 虚线.创建线段(基础序列) - 线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") + 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级) if 四象 in ("老阴", "老阳"): 新段.前一缺口 = None @@ -5575,15 +5768,16 @@ class 线段: :param 序列: 参考序列 """ 基础序列 = [] + 序列集 = set(序列) if not isinstance(序列, set) else 序列 for 元素 in 段.基础序列: - if 元素 not in 序列: + if 元素 not in 序列集: break if 基础序列: if not 基础序列[-1].之后是(元素): logger.warning(" 线段._验证序列 数据不连续") break 基础序列.append(元素) - 段.基础序列[:] = 基础序列[:] + 段.基础序列[:] = 基础序列 if len(段.基础序列) % 2 == 0: 段.基础序列 and 段.基础序列.pop() @@ -5676,7 +5870,7 @@ class 线段: if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号: return None - 序号 = 虚线序列.index(当前线段.基础序列[-1]) + 1 + 序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1 if 序号 >= len(虚线序列): return None @@ -5739,7 +5933,7 @@ class 线段: if 当前段.实_中枢序列: if 阳[-1] in 当前段.实_中枢序列[-1].基础序列: # 当前最后一笔在最后一中枢里 - 序号 = 当前段.基础序列.index(当前段.实_中枢序列[-1].基础序列[0]) + 序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0]) 进入段 = 当前段.基础序列[序号 - 1] 离开段 = 阳[-1] assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号) @@ -5793,7 +5987,7 @@ class 线段: 笔序列.append(停顿) 线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向.顺, 相对方向.逆, 相对方向.同]) if 线段序列 and 线段序列[-1].武 is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1: - 新段 = 虚线.创建线段(线段序列[-1].基础序列[:]) + 新段 = 虚线.创建线段(线段序列[-1].基础序列) 新段.序号 = self.序号 线段._刷新(新段, 观察员.配置) if 新段.方向 is self.方向: @@ -5984,7 +6178,7 @@ class 中枢: :return: 虚线列表 """ - 序列: List = self.基础序列[:] + 序列: List = self.基础序列.copy() if self.第三买卖线 is not None: 序列.append(self.第三买卖线) return 序列 @@ -6008,13 +6202,14 @@ class 中枢: """ 有效序列 = self.基础序列[:] 无效序列 = [] + 序列集 = set(序列) for 元素 in self.基础序列: - if 元素 not in 序列: + if 元素 not in 序列集: 无效序列.append(元素) if 无效序列: 无效 = 无效序列[0] - 序号 = self.基础序列.index(无效) + 序号 = 线段._索引(self.基础序列, 无效) 有效序列 = self.基础序列[:序号] if len(有效序列) < 3: @@ -6022,14 +6217,14 @@ class 中枢: self.本级_第三买卖线 = None return False - self.基础序列[:] = 有效序列 + self.基础序列 = 有效序列 有效序列 = [] for 元素 in self.基础序列: if 相对方向.分析(self.高, self.低, 元素.高, 元素.低).是否缺口(): break 有效序列.append(元素) - self.基础序列[:] = 有效序列 + self.基础序列 = 有效序列 if len(self.基础序列) < 3: return False @@ -6201,7 +6396,7 @@ class 中枢: 左, 中, 右 = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] if 中枢.基础检查(左, 中, 右): 新中枢 = 中枢.创建(左, 中, 右, 中.级别, 标识) - 序号 = 虚线序列.index(左) + 序号 = 线段._索引(虚线序列, 左) if 跳过首部 and (左.序号 == 0 or 序号 == 0): continue # 方便计算走势 if 序号 >= 2: @@ -6222,7 +6417,7 @@ class 中枢: 中枢._从中枢序列尾部弹出(中枢序列, 当前中枢) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) - 序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1 + 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1 基础序列 = [] for 当前虚线 in 虚线序列[序号:]: diff --git a/chanlun-py/pyproject.toml b/chanlun-py/pyproject.toml index e1cca15..4a3ac7b 100644 --- a/chanlun-py/pyproject.toml +++ b/chanlun-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "chanlun" -version = "2606.47" +version = "2606.73" description = "缠论技术分析库 — Rust 高性能实现" readme = { file = "README.md", content-type = "text/markdown" } license = { file = "LICENSE", content-type = "text/plain" } diff --git a/chanlun-py/src/algorithm_py.rs b/chanlun-py/src/algorithm_py.rs index a55e5fd..65c5e7d 100644 --- a/chanlun-py/src/algorithm_py.rs +++ b/chanlun-py/src/algorithm_py.rs @@ -26,33 +26,20 @@ use crate::kline_py::chan_kline_to_py; use crate::structure_py::{dashed_to_py, fractal_to_py}; use pyo3::prelude::*; use pyo3::types::{PyDict, PyList, PyType}; -use std::collections::HashMap; use std::sync::Arc; -use std::sync::RwLock; use std::sync::atomic::Ordering; -// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 -static HUB_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); +// 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换) pub(crate) fn hub_to_py( py: Python<'_>, inner: Arc ) -> Py<中枢Py> { let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = HUB_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { + if let Some(cached) = crate::cache::hub_get(py, key) { return cached; } - HUB_IDENTITY - .write() - .unwrap() - .retain(|_, v| v.get_refcnt(py) > 1); let obj = Py::new(py, 中枢Py { inner }).unwrap(); - HUB_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py)); + crate::cache::hub_insert(py, key, &obj); obj } @@ -90,16 +77,21 @@ impl 背驰分析Py { 方式: &str, py: Python<'_>, ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let 方式 = 方式.to_string(); let rc_list: Vec> = K线序列 .iter() .map(|k| k.bind(py).borrow().inner.clone()) .collect(); - chanlun::algorithm::divergence::背驰分析::MACD背驰( - &进入段.borrow().inner, - &离开段.borrow().inner, - &rc_list, - 方式, - ) + py.detach(move || { + chanlun::algorithm::divergence::背驰分析::MACD背驰( + &进入段_inner, + &离开段_inner, + &rc_list, + &方式, + ) + }) } #[classmethod] @@ -137,15 +129,19 @@ impl 背驰分析Py { 普K序列: Vec>, py: Python<'_>, ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); let rc_list: Vec> = 普K序列 .iter() .map(|k| k.bind(py).borrow().inner.clone()) .collect(); - chanlun::algorithm::divergence::背驰分析::全量背驰( - &进入段.borrow().inner, - &离开段.borrow().inner, - &rc_list, - ) + py.detach(move || { + chanlun::algorithm::divergence::背驰分析::全量背驰( + &进入段_inner, + &离开段_inner, + &rc_list, + ) + }) } #[classmethod] @@ -157,15 +153,19 @@ impl 背驰分析Py { 普K序列: Vec>, py: Python<'_>, ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); let rc_list: Vec> = 普K序列 .iter() .map(|k| k.bind(py).borrow().inner.clone()) .collect(); - chanlun::algorithm::divergence::背驰分析::任意背驰( - &进入段.borrow().inner, - &离开段.borrow().inner, - &rc_list, - ) + py.detach(move || { + chanlun::algorithm::divergence::背驰分析::任意背驰( + &进入段_inner, + &离开段_inner, + &rc_list, + ) + }) } #[classmethod] @@ -178,17 +178,21 @@ impl 背驰分析Py { 配置: &Bound<'_, 缠论配置Py>, py: Python<'_>, ) -> PyResult { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); let rc_list: Vec> = 普K序列 .iter() .map(|k| k.bind(py).borrow().inner.clone()) .collect(); let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::divergence::背驰分析::配置背驰( - &进入段.borrow().inner, - &离开段.borrow().inner, - &rc_list, - &config, - )) + Ok(py.detach(move || { + chanlun::algorithm::divergence::背驰分析::配置背驰( + &进入段_inner, + &离开段_inner, + &rc_list, + &config, + ) + })) } #[classmethod] @@ -200,15 +204,19 @@ impl 背驰分析Py { 普K序列: Vec>, py: Python<'_>, ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); let rc_list: Vec> = 普K序列 .iter() .map(|k| k.bind(py).borrow().inner.clone()) .collect(); - chanlun::algorithm::divergence::背驰分析::任选背驰( - &进入段.borrow().inner, - &离开段.borrow().inner, - &rc_list, - ) + py.detach(move || { + chanlun::algorithm::divergence::背驰分析::任选背驰( + &进入段_inner, + &离开段_inner, + &rc_list, + ) + }) } #[classmethod] @@ -222,18 +230,169 @@ impl 背驰分析Py { 模式: &str, py: Python<'_>, ) -> PyResult { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); let rc_list: Vec> = 普K序列 .iter() .map(|k| k.bind(py).borrow().inner.clone()) .collect(); let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::divergence::背驰分析::背驰模式( - &进入段.borrow().inner, - &离开段.borrow().inner, - &rc_list, - &config, - 模式, - )) + let 模式 = 模式.to_string(); + Ok(py.detach(move || { + chanlun::algorithm::divergence::背驰分析::背驰模式( + &进入段_inner, + &离开段_inner, + &rc_list, + &config, + &模式, + ) + })) + } + + // ---- 观察者直传(跳过 Python list→Vec 转换,直接借用观察者内部 &[Arc]) ---- + + #[classmethod] + #[pyo3(name = "MACD背驰_OBS", signature = (进入段, 离开段, 观察员, 方式 = "总"))] + fn MACD背驰_obs( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 观察员: &Bound<'_, 观察者Py>, + 方式: &str, + py: Python<'_>, + ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let 方式 = 方式.to_string(); + let obs_arc = 观察员.borrow().inner.clone().expect("观察者未初始化"); + py.detach(move || { + let guard = obs_arc.read(); + chanlun::algorithm::divergence::背驰分析::MACD背驰( + &进入段_inner, + &离开段_inner, + &guard.普通K线序列, + &方式, + ) + }) + } + + #[classmethod] + #[pyo3(name = "全量背驰_OBS", signature = (进入段, 离开段, 观察员))] + fn 全量背驰_obs( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 观察员: &Bound<'_, 观察者Py>, + py: Python<'_>, + ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let obs_arc = 观察员.borrow().inner.clone().expect("观察者未初始化"); + py.detach(move || { + let guard = obs_arc.read(); + chanlun::algorithm::divergence::背驰分析::全量背驰( + &进入段_inner, + &离开段_inner, + &guard.普通K线序列, + ) + }) + } + + #[classmethod] + #[pyo3(name = "任意背驰_OBS", signature = (进入段, 离开段, 观察员))] + fn 任意背驰_obs( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 观察员: &Bound<'_, 观察者Py>, + py: Python<'_>, + ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let obs_arc = 观察员.borrow().inner.clone().expect("观察者未初始化"); + py.detach(move || { + let guard = obs_arc.read(); + chanlun::algorithm::divergence::背驰分析::任意背驰( + &进入段_inner, + &离开段_inner, + &guard.普通K线序列, + ) + }) + } + + #[classmethod] + #[pyo3(name = "配置背驰_OBS", signature = (进入段, 离开段, 观察员, 配置))] + fn 配置背驰_obs( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 观察员: &Bound<'_, 观察者Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let config = 配置.borrow().to_rust_config(py)?; + let obs_arc = 观察员.borrow().inner.clone().expect("观察者未初始化"); + Ok(py.detach(move || { + let guard = obs_arc.read(); + chanlun::algorithm::divergence::背驰分析::配置背驰( + &进入段_inner, + &离开段_inner, + &guard.普通K线序列, + &config, + ) + })) + } + + #[classmethod] + #[pyo3(name = "任选背驰_OBS", signature = (进入段, 离开段, 观察员))] + fn 任选背驰_obs( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 观察员: &Bound<'_, 观察者Py>, + py: Python<'_>, + ) -> bool { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let obs_arc = 观察员.borrow().inner.clone().expect("观察者未初始化"); + py.detach(move || { + let guard = obs_arc.read(); + chanlun::algorithm::divergence::背驰分析::任选背驰( + &进入段_inner, + &离开段_inner, + &guard.普通K线序列, + ) + }) + } + + #[classmethod] + #[pyo3(name = "背驰模式_OBS", signature = (进入段, 离开段, 观察员, 配置, 模式))] + fn 背驰模式_obs( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 观察员: &Bound<'_, 观察者Py>, + 配置: &Bound<'_, 缠论配置Py>, + 模式: &str, + py: Python<'_>, + ) -> PyResult { + let 进入段_inner = Arc::clone(&进入段.borrow().inner); + let 离开段_inner = Arc::clone(&离开段.borrow().inner); + let config = 配置.borrow().to_rust_config(py)?; + let 模式 = 模式.to_string(); + let obs_arc = 观察员.borrow().inner.clone().expect("观察者未初始化"); + Ok(py.detach(move || { + let guard = obs_arc.read(); + chanlun::algorithm::divergence::背驰分析::背驰模式( + &进入段_inner, + &离开段_inner, + &guard.普通K线序列, + &config, + &模式, + ) + })) } } @@ -345,7 +504,7 @@ impl 笔Py { .map(|k| k.bind(py).borrow().inner.clone()) .collect(); let config = 配置.borrow().to_rust_config(py)?; - let depth = match 当前分型_rc { + let depth = py.detach(|| match 当前分型_rc { Some(fr) => chanlun::algorithm::bi::笔::分析( fr, &mut fr_seq, @@ -356,17 +515,21 @@ impl 笔Py { &config, ), None => 递归层次, - }; + }); - // 写回 Python 列表 + // 写回 Python 列表 (bulk extend) + let fr_items: Vec> = fr_seq + .iter() + .map(|f| fractal_to_py(py, Arc::clone(f)).into_any()) + .collect(); 分型序列.call_method0("clear")?; - for f in fr_seq { - 分型序列.call_method1("append", (fractal_to_py(py, f),))?; - } + 分型序列.call_method1("extend", (PyList::new(py, &fr_items)?,))?; + let bi_items: Vec> = bi_seq + .iter() + .map(|d| dashed_to_py(py, Arc::clone(d)).into_any()) + .collect(); 笔序列.call_method0("clear")?; - for d in bi_seq { - 笔序列.call_method1("append", (dashed_to_py(py, d),))?; - } + 笔序列.call_method1("extend", (PyList::new(py, &bi_items)?,))?; Ok(depth) } @@ -552,19 +715,23 @@ impl 线段Py { let rel_list: Vec = 关系序列 .map(|v| v.into_iter().map(|d| d.inner).collect()) .unwrap_or(default_rel); - chanlun::algorithm::segment::线段::分析( - &bi_list, - &mut seg_seq, - &config, - 层级, - &rel_list, - ); + py.detach(|| { + chanlun::algorithm::segment::线段::分析( + &bi_list, + &mut seg_seq, + &config, + 层级, + &rel_list, + ); + }); - // 写回 Python 列表 + // 写回 Python 列表 (bulk extend) + let items: Vec> = seg_seq + .iter() + .map(|d| dashed_to_py(py, Arc::clone(d)).into_any()) + .collect(); 线段序列.call_method0("clear")?; - for d in seg_seq { - 线段序列.call_method1("append", (dashed_to_py(py, d),))?; - } + 线段序列.call_method1("extend", (PyList::new(py, &items)?,))?; Ok(()) } @@ -590,13 +757,17 @@ impl 线段Py { } let config = 配置.borrow().to_rust_config(py)?; - chanlun::algorithm::segment::线段::扩展分析(&dash_list, &mut seg_seq, &config); + py.detach(|| { + chanlun::algorithm::segment::线段::扩展分析(&dash_list, &mut seg_seq, &config); + }); - // 写回 Python 列表 + // 写回 Python 列表 (bulk extend) + let items: Vec> = seg_seq + .iter() + .map(|d| dashed_to_py(py, Arc::clone(d)).into_any()) + .collect(); 线段序列.call_method0("clear")?; - for d in seg_seq { - 线段序列.call_method1("append", (dashed_to_py(py, d),))?; - } + 线段序列.call_method1("extend", (PyList::new(py, &items)?,))?; Ok(()) } @@ -714,7 +885,7 @@ impl 中枢Py { #[getter] fn 标识(&self) -> String { - self.inner.标识.read().unwrap().clone() + self.inner.标识.read().clone() } #[getter] @@ -725,7 +896,7 @@ impl 中枢Py { #[getter] fn 基础序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in self.inner.基础序列.read().unwrap().iter() { + for d in self.inner.基础序列.read().iter() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -736,7 +907,6 @@ impl 中枢Py { self.inner .第三买卖线 .read() - .unwrap() .as_ref() .map(|d| dashed_to_py(py, Arc::clone(d))) } @@ -746,7 +916,6 @@ impl 中枢Py { self.inner .本级_第三买卖线 .read() - .unwrap() .as_ref() .map(|d| dashed_to_py(py, Arc::clone(d))) } @@ -845,13 +1014,17 @@ impl 中枢Py { hub_seq.push(Arc::clone(&h.inner)); } let config = 配置.borrow().to_rust_config(配置.py())?; - self.inner.获取扩展中枢(&mut hub_seq, &config); + py.detach(|| { + self.inner.获取扩展中枢(&mut hub_seq, &config); + }); - // 写回 Python 列表 + // 写回 Python 列表 (bulk extend) + let items: Vec> = hub_seq + .iter() + .map(|h| hub_to_py(py, Arc::clone(h)).into_any()) + .collect(); 扩展中枢.call_method0("clear")?; - for h in hub_seq { - 扩展中枢.call_method1("append", (hub_to_py(py, h),))?; - } + 扩展中枢.call_method1("extend", (PyList::new(py, &items)?,))?; Ok(()) } @@ -958,13 +1131,17 @@ impl 中枢Py { hub_seq.push(Arc::clone(&h.inner)); } - chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级); + py.detach(|| { + chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级); + }); - // 写回 Python 列表 + // 写回 Python 列表 (bulk extend) + let items: Vec> = hub_seq + .iter() + .map(|h| hub_to_py(py, Arc::clone(h)).into_any()) + .collect(); 中枢序列.call_method0("clear")?; - for h in hub_seq { - 中枢序列.call_method1("append", (hub_to_py(py, h),))?; - } + 中枢序列.call_method1("extend", (PyList::new(py, &items)?,))?; Ok(()) } diff --git a/chanlun-py/src/business_py.rs b/chanlun-py/src/business_py.rs index 582d0c3..386d99d 100644 --- a/chanlun-py/src/business_py.rs +++ b/chanlun-py/src/business_py.rs @@ -22,15 +22,16 @@ * SOFTWARE. */ +use parking_lot::RwLock; use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; -use std::sync::RwLock; use crate::algorithm_py::hub_to_py; use crate::kline_py::bar_to_py; use crate::structure_py::{dashed_to_py, fractal_to_py, 分型Py}; use std::collections::HashMap; use std::sync::Arc; +use std::sync::atomic::{AtomicU64, Ordering}; use crate::config_py::缠论配置Py; use crate::kline_py::{K线Py, 缠论K线Py}; @@ -693,28 +694,27 @@ impl 买卖点Py { #[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)] pub struct 观察者Py { pub(crate) inner: Option>>, - 配置缓存: std::sync::Mutex>>, + 配置缓存: parking_lot::Mutex>>, + 最后配置版本: AtomicU64, } impl 观察者Py { pub(crate) fn obs( &self, - ) -> std::sync::RwLockReadGuard<'_, chanlun::business::observer::观察者> { + ) -> parking_lot::RwLockReadGuard<'_, chanlun::business::observer::观察者> { self.inner .as_ref() .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造") .read() - .unwrap_or_else(|e| e.into_inner()) } pub(crate) fn obs_mut( &self, - ) -> std::sync::RwLockWriteGuard<'_, chanlun::business::observer::观察者> { + ) -> parking_lot::RwLockWriteGuard<'_, chanlun::business::observer::观察者> { self.inner .as_ref() .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造") .write() - .unwrap_or_else(|e| e.into_inner()) } } @@ -770,7 +770,8 @@ impl 观察者Py { inner: Some(chanlun::business::observer::观察者::new( 符号, 周期, config, )), - 配置缓存: std::sync::Mutex::new(None), + 配置缓存: parking_lot::Mutex::new(None), + 最后配置版本: AtomicU64::new(0), }) } @@ -824,7 +825,7 @@ impl 观察者Py { #[getter] fn 配置(&self, py: Python<'_>) -> PyResult> { - let mut cache = self.配置缓存.lock().unwrap(); + let mut cache = self.配置缓存.lock(); if let Some(ref cached) = *cache { Ok(cached.clone_ref(py)) } else { @@ -838,11 +839,8 @@ impl 观察者Py { #[setter] fn set_配置(&self, value: &Bound<'_, 缠论配置Py>) -> PyResult<()> { let config = value.borrow().to_rust_config(value.py())?; + *self.配置缓存.lock() = Some(value.clone().unbind()); self.obs_mut().配置 = config; - self.配置缓存 - .lock() - .unwrap() - .replace(value.clone().unbind()); Ok(()) } @@ -861,25 +859,53 @@ impl 观察者Py { /// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现) #[pyo3(name = "_增加原始K线")] - fn 增加原始K线_impl(&mut self, 普K: &Bound<'_, K线Py>) -> PyResult<()> { - self.obs_mut().增加原始K线((*普K.borrow().inner).clone()); - Ok(()) + fn 增加原始K线_impl(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> { + let kline = (*普K.borrow().inner).clone(); + let obs_arc = slf + .borrow() + .inner + .clone() + .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造"); + let symbol = obs_arc.read().符号.clone(); + let result = slf.py().detach(move || { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + obs_arc.write().增加原始K线(kline); + })) + }); + match result { + Ok(()) => Ok(()), + Err(e) => { + let msg = e + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| e.downcast_ref::().cloned()) + .unwrap_or_else(|| "未知算法错误".into()); + Err(pyo3::exceptions::PyRuntimeError::new_err(format!( + "[{symbol}] 算法异常: {msg}" + ))) + } + } } /// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写) fn 增加原始K线(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> { - // 同步缓存的 Python 配置到 Rust 观察者(支持 obs.配置 直接修改) + // 版本对比,仅在配置变更时同步(支持 obs.配置.field = value 直接修改) { let me = slf.borrow(); - if let Some(ref cached) = *me.配置缓存.lock().unwrap() { + if let Some(ref cached) = *me.配置缓存.lock() { let py = slf.py(); - if let Ok(config) = cached.bind(py).borrow().to_rust_config(py) { - me.obs_mut().配置 = config; + let cfg_ref = cached.bind(py).borrow(); + let current_version = cfg_ref.版本.load(Ordering::Relaxed); + if current_version != me.最后配置版本.load(Ordering::Relaxed) { + if let Ok(config) = cfg_ref.to_rust_config(py) { + me.obs_mut().配置 = config; + } + me.最后配置版本.store(current_version, Ordering::Relaxed); } } } - slf.call_method1("_增加原始K线", (普K,))?; - Ok(()) + // 直接调用 Rust 实现,跳过 Python dispatch + Self::增加原始K线_impl(slf, 普K) } /// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线, @@ -931,9 +957,31 @@ impl 观察者Py { /// 静态重新分析(内部实现) #[pyo3(name = "_静态重新分析")] - fn 静态重新分析_impl(&mut self) -> PyResult<()> { - self.obs_mut().静态重新分析(); - Ok(()) + fn 静态重新分析_impl(slf: &Bound<'_, Self>) -> PyResult<()> { + let obs_arc = slf + .borrow() + .inner + .clone() + .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造"); + let symbol = obs_arc.read().符号.clone(); + let result = slf.py().detach(move || { + std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + obs_arc.write().静态重新分析(); + })) + }); + match result { + Ok(()) => Ok(()), + Err(e) => { + let msg = e + .downcast_ref::<&str>() + .map(|s| s.to_string()) + .or_else(|| e.downcast_ref::().cloned()) + .unwrap_or_else(|| "未知算法错误".into()); + Err(pyo3::exceptions::PyRuntimeError::new_err(format!( + "[{symbol}] 静态重新分析异常: {msg}" + ))) + } + } } /// 静态重新分析(公开分发器,支持子类重写) @@ -1413,7 +1461,8 @@ impl 立体分析器Py { for (周期, obs_rc) in &self.inner.单体分析器 { let obs_py = 观察者Py { inner: Some(obs_rc.clone()), - 配置缓存: std::sync::Mutex::new(None), + 配置缓存: parking_lot::Mutex::new(None), + 最后配置版本: AtomicU64::new(0), }; dict.set_item(周期, obs_py)?; } diff --git a/chanlun-py/src/cache.rs b/chanlun-py/src/cache.rs new file mode 100644 index 0000000..f1b0c46 --- /dev/null +++ b/chanlun-py/src/cache.rs @@ -0,0 +1,199 @@ +use dashmap::DashMap; +use pyo3::prelude::*; +use pyo3::types::PySet; +/// 缓存模式:线程局部(默认,零锁)或全局(dashmap,跨线程共享) +use std::cell::RefCell; +use std::collections::HashMap; +use std::sync::OnceLock; + +pub enum CacheMode { + ThreadLocal, + Global, +} + +static CACHE_MODE: OnceLock = OnceLock::new(); + +pub fn get_mode() -> &'static CacheMode { + CACHE_MODE.get_or_init(|| match std::env::var("CHANLUN_CACHE_MODE").as_deref() { + Ok("global") => CacheMode::Global, + _ => CacheMode::ThreadLocal, + }) +} + +pub fn peek_mode() -> Option<&'static CacheMode> { + CACHE_MODE.get() +} + +pub fn set_mode(mode: CacheMode) -> Result<(), String> { + CACHE_MODE + .set(mode) + .map_err(|_| "缓存模式已初始化,请在创建任何观察者之前调用 set_cache_mode".into()) +} + +// ========== BAR_IDENTITY ========== +thread_local! { + static BAR_LOCAL: RefCell>> = RefCell::new(HashMap::new()); +} +static BAR_GLOBAL: std::sync::LazyLock>> = + std::sync::LazyLock::new(DashMap::new); + +pub fn bar_get(py: Python<'_>, key: usize) -> Option> { + match get_mode() { + CacheMode::ThreadLocal => BAR_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))), + CacheMode::Global => BAR_GLOBAL.get(&key).map(|p| p.clone_ref(py)), + } +} +pub fn bar_insert(py: Python<'_>, key: usize, obj: &Py) { + match get_mode() { + CacheMode::ThreadLocal => BAR_LOCAL.with(|m| { + let mut m = m.borrow_mut(); + m.retain(|_, v| v.get_refcnt(py) > 1); + m.insert(key, obj.clone_ref(py)); + }), + CacheMode::Global => { + BAR_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1); + BAR_GLOBAL.insert(key, obj.clone_ref(py)); + } + } +} + +// ========== KLINE_IDENTITY ========== +thread_local! { + static KLINE_LOCAL: RefCell>> = RefCell::new(HashMap::new()); +} +static KLINE_GLOBAL: std::sync::LazyLock>> = + std::sync::LazyLock::new(DashMap::new); + +pub fn kline_get(py: Python<'_>, key: usize) -> Option> { + match get_mode() { + CacheMode::ThreadLocal => { + KLINE_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))) + } + CacheMode::Global => KLINE_GLOBAL.get(&key).map(|p| p.clone_ref(py)), + } +} +pub fn kline_insert(py: Python<'_>, key: usize, obj: &Py) { + match get_mode() { + CacheMode::ThreadLocal => KLINE_LOCAL.with(|m| { + let mut m = m.borrow_mut(); + m.retain(|_, v| v.get_refcnt(py) > 1); + m.insert(key, obj.clone_ref(py)); + }), + CacheMode::Global => { + KLINE_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1); + KLINE_GLOBAL.insert(key, obj.clone_ref(py)); + } + } +} + +// ========== FRACTAL_IDENTITY ========== +use crate::structure_py::分型Py; +thread_local! { + static FRACTAL_LOCAL: RefCell>> = RefCell::new(HashMap::new()); +} +static FRACTAL_GLOBAL: std::sync::LazyLock>> = + std::sync::LazyLock::new(DashMap::new); + +pub fn fractal_get(py: Python<'_>, key: usize) -> Option> { + match get_mode() { + CacheMode::ThreadLocal => { + FRACTAL_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))) + } + CacheMode::Global => FRACTAL_GLOBAL.get(&key).map(|p| p.clone_ref(py)), + } +} +pub fn fractal_insert(py: Python<'_>, key: usize, obj: &Py<分型Py>) { + match get_mode() { + CacheMode::ThreadLocal => FRACTAL_LOCAL.with(|m| { + let mut m = m.borrow_mut(); + m.retain(|_, v| v.get_refcnt(py) > 1); + m.insert(key, obj.clone_ref(py)); + }), + CacheMode::Global => { + FRACTAL_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1); + FRACTAL_GLOBAL.insert(key, obj.clone_ref(py)); + } + } +} + +// ========== DASHED_IDENTITY ========== +use crate::structure_py::虚线Py; +thread_local! { + static DASHED_LOCAL: RefCell>> = RefCell::new(HashMap::new()); +} +static DASHED_GLOBAL: std::sync::LazyLock>> = + std::sync::LazyLock::new(DashMap::new); + +pub fn dashed_get(py: Python<'_>, key: usize) -> Option> { + match get_mode() { + CacheMode::ThreadLocal => { + DASHED_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))) + } + CacheMode::Global => DASHED_GLOBAL.get(&key).map(|p| p.clone_ref(py)), + } +} +pub fn dashed_insert(py: Python<'_>, key: usize, obj: &Py<虚线Py>) { + match get_mode() { + CacheMode::ThreadLocal => DASHED_LOCAL.with(|m| { + let mut m = m.borrow_mut(); + m.retain(|_, v| v.get_refcnt(py) > 1); + m.insert(key, obj.clone_ref(py)); + }), + CacheMode::Global => { + DASHED_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1); + DASHED_GLOBAL.insert(key, obj.clone_ref(py)); + } + } +} + +// ========== HUB_IDENTITY ========== +use crate::algorithm_py::中枢Py; +thread_local! { + static HUB_LOCAL: RefCell>> = RefCell::new(HashMap::new()); +} +static HUB_GLOBAL: std::sync::LazyLock>> = + std::sync::LazyLock::new(DashMap::new); + +pub fn hub_get(py: Python<'_>, key: usize) -> Option> { + match get_mode() { + CacheMode::ThreadLocal => HUB_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))), + CacheMode::Global => HUB_GLOBAL.get(&key).map(|p| p.clone_ref(py)), + } +} +pub fn hub_insert(py: Python<'_>, key: usize, obj: &Py<中枢Py>) { + match get_mode() { + CacheMode::ThreadLocal => HUB_LOCAL.with(|m| { + let mut m = m.borrow_mut(); + m.retain(|_, v| v.get_refcnt(py) > 1); + m.insert(key, obj.clone_ref(py)); + }), + CacheMode::Global => { + HUB_GLOBAL.retain(|_, v| v.get_refcnt(py) > 1); + HUB_GLOBAL.insert(key, obj.clone_ref(py)); + } + } +} + +// ========== BSP_CACHE ========== +thread_local! { + static BSP_LOCAL: RefCell>> = RefCell::new(HashMap::new()); +} +static BSP_GLOBAL: std::sync::LazyLock>> = + std::sync::LazyLock::new(DashMap::new); + +pub fn bsp_get(py: Python<'_>, key: usize) -> Option> { + match get_mode() { + CacheMode::ThreadLocal => BSP_LOCAL.with(|m| m.borrow().get(&key).map(|p| p.clone_ref(py))), + CacheMode::Global => BSP_GLOBAL.get(&key).map(|p| p.clone_ref(py)), + } +} +pub fn bsp_insert(py: Python<'_>, key: usize, obj: Py) { + match get_mode() { + CacheMode::ThreadLocal => BSP_LOCAL.with(|m| { + m.borrow_mut().insert(key, obj); + }), + CacheMode::Global => { + BSP_GLOBAL.insert(key, obj); + } + } +} diff --git a/chanlun-py/src/config_py.rs b/chanlun-py/src/config_py.rs index 328557c..8e128ff 100644 --- a/chanlun-py/src/config_py.rs +++ b/chanlun-py/src/config_py.rs @@ -22,10 +22,11 @@ * SOFTWARE. */ +use chanlun::warn; use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; use std::collections::HashMap; -use tracing::warn; +use std::sync::atomic::{AtomicU64, Ordering}; /// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 /// @@ -95,6 +96,8 @@ use tracing::warn; #[pyclass(name = "缠论配置", module = "chanlun._chanlun")] pub struct 缠论配置Py { fields: HashMap>, + 缓存: parking_lot::Mutex>, + pub(crate) 版本: AtomicU64, } #[pymethods] @@ -120,7 +123,11 @@ impl 缠论配置Py { // 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换 let config = dict_to_rust_config(&fields)?; let fields = config_to_field_dict(&config)?; - Ok(Self { fields }) + Ok(Self { + fields, + 缓存: parking_lot::Mutex::new(Some(config)), + 版本: AtomicU64::new(1), + }) } fn __getattr__(&self, name: &str, py: Python<'_>) -> PyResult> { @@ -135,10 +142,13 @@ impl 缠论配置Py { fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> { if self.fields.contains_key(name) { self.fields.insert(name.to_string(), value.clone().unbind()); + *self.缓存.lock() = None; + self.版本.fetch_add(1, Ordering::Relaxed); // 通过 serde 往返验证类型 match dict_to_rust_config(&self.fields) { Ok(config) => { self.fields = config_to_field_dict(&config)?; + *self.缓存.lock() = Some(config); Ok(()) } Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!( @@ -217,7 +227,11 @@ impl 缠论配置Py { let config = dict_to_rust_config(&fields)?; let fields = config_to_field_dict(&config)?; - Ok(Self { fields }) + Ok(Self { + fields, + 缓存: parking_lot::Mutex::new(Some(config)), + 版本: AtomicU64::new(1), + }) } #[classmethod] @@ -231,7 +245,11 @@ impl 缠论配置Py { fn 不推送(_cls: &Bound<'_, PyType>) -> PyResult { let config = chanlun::config::缠论配置::default().不推送(); let fields = config_to_field_dict(&config)?; - Ok(Self { fields }) + Ok(Self { + fields, + 缓存: parking_lot::Mutex::new(Some(config)), + 版本: AtomicU64::new(1), + }) } #[classmethod] @@ -302,18 +320,31 @@ impl 缠论配置Py { let config = dict_to_rust_config(&fields)?; let fields = config_to_field_dict(&config)?; - Ok(Self { fields }) + Ok(Self { + fields, + 缓存: parking_lot::Mutex::new(Some(config)), + 版本: AtomicU64::new(1), + }) } pub(crate) fn to_rust_config( &self, _py: Python<'_>, ) -> PyResult { - dict_to_rust_config(&self.fields) + if let Some(ref cached) = *self.缓存.lock() { + return Ok(cached.clone()); + } + let config = dict_to_rust_config(&self.fields)?; + *self.缓存.lock() = Some(config.clone()); + Ok(config) } pub(crate) fn from_rust_config(config: &chanlun::config::缠论配置) -> PyResult { - config_to_field_dict(config).map(|fields| Self { fields }) + config_to_field_dict(config).map(|fields| Self { + fields, + 缓存: parking_lot::Mutex::new(Some(config.clone())), + 版本: AtomicU64::new(1), + }) } } diff --git a/chanlun-py/src/equality_py.rs b/chanlun-py/src/equality_py.rs index 9ff699c..737e66c 100644 --- a/chanlun-py/src/equality_py.rs +++ b/chanlun-py/src/equality_py.rs @@ -22,33 +22,10 @@ * SOFTWARE. */ -use std::num::NonZeroUsize; - use crate::business_py::立体分析器Py; use crate::business_py::观察者Py; -use std::sync::Mutex; - -use lru::LruCache; use pyo3::prelude::*; -/// 缓存辅助宏:在调用点创建静态 LruCache,先查后存 -macro_rules! with_cache { - ($cache:ident, $size:literal, $key_expr:expr, $compute:expr) => {{ - use std::sync::LazyLock; - static $cache: LazyLock>> = - LazyLock::new(|| Mutex::new(LruCache::new(NonZeroUsize::new($size).unwrap()))); - let key = $key_expr; - if let Some(cached) = $cache.lock().unwrap().get(&key) { - return Ok(cached.clone()); - } - let result: PyResult<(bool, String)> = $compute; - if let Ok(ref r) = result { - $cache.lock().unwrap().put(key, r.clone()); - } - result - }}; -} - /// 从 Python 值中提取时间戳(兼容 i64 和 datetime 两种类型) fn 提取时间戳(val: &Bound<'_, PyAny>) -> PyResult { if let Ok(ts) = val.extract::() { @@ -129,71 +106,60 @@ fn K线相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_KLINE, - 128, - ( - 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 标签 = "K线校验"; - let 比对字段 = [ - "标识", - "序号", - "周期", - "时间戳", - "高", - "低", - "开盘价", - "收盘价", - "成交量", - ]; - 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 Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { - let (ok, m) = r?; - if !ok { - return Ok((false, format!("{标签}: [{字段}]{}", m))); - } - } else if 字段 == "时间戳" { - let a = 提取时间戳(&valA).unwrap_or(0); - let b = 提取时间戳(&valB).unwrap_or(0); - if a != b { - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); - } - } 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!("{标签}: 全部字段一致"))) + // 快速路径 + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + // 回退路径 + let 标签 = "K线校验"; + let 比对字段 = [ + "标识", + "序号", + "周期", + "时间戳", + "高", + "低", + "开盘价", + "收盘价", + "成交量", + ]; + 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 Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{}", m))); + } + } else if 字段 == "时间戳" { + let a = 提取时间戳(&valA).unwrap_or(0); + let b = 提取时间戳(&valB).unwrap_or(0); + if a != b { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); + } + } 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!("{标签}: 全部字段一致"))) } // ========== 缠论K线相等 ========== @@ -205,102 +171,91 @@ fn 缠论K线相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_CHAN_K, - 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 标签 = "缠论K线校验"; + 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 let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{m}"))); } - let 标签 = "缠论K线校验"; - 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有 { + } else if 字段 == "标的K线" { + 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 Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { - let (ok, m) = r?; - if !ok { - return Ok((false, format!("{标签}: [{字段}]{m}"))); - } - } 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线子项异常 >> {msg}"))); - } - } else if 字段 == "时间戳" { - let a = 提取时间戳(&valA).unwrap_or(0); - let b = 提取时间戳(&valB).unwrap_or(0); - if a != b { - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); - } - } else if 字段 == "方向" || 字段 == "分型" { - let sa = valA.str()?.extract::().unwrap_or_default(); - let sb = valB.str()?.extract::().unwrap_or_default(); - if sa != sb { - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); - } - } else if 字段 == "买卖点信息" { - let py = A.py(); - let set_a = py.import("builtins")?.getattr("set")?.call1((&valA,))?; - let set_b = py.import("builtins")?.getattr("set")?.call1((&valB,))?; - let eq: bool = set_a.eq(set_b)?; - 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}"))); - } - } 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) = K线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: 标的K线子项异常 >> {msg}"))); + } + } else if 字段 == "时间戳" { + let a = 提取时间戳(&valA).unwrap_or(0); + let b = 提取时间戳(&valB).unwrap_or(0); + if a != b { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); + } + } else if 字段 == "方向" || 字段 == "分型" { + let sa = valA.str()?.extract::().unwrap_or_default(); + let sb = valB.str()?.extract::().unwrap_or_default(); + if sa != sb { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); + } + } else if 字段 == "买卖点信息" { + let py = A.py(); + let set_a = py.import("builtins")?.getattr("set")?.call1((&valA,))?; + let set_b = py.import("builtins")?.getattr("set")?.call1((&valB,))?; + let eq: bool = set_a.eq(set_b)?; + 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}"))); + } + } 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!("{标签}: 全部字段嵌套校验一致"))) } // ========== 分型相等 ========== @@ -312,88 +267,77 @@ fn 分型相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_FRACTAL, - 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 标签 = "分型校验"; + // Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter + for &字段 in &["左", "中", "右"] { + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; } - let 标签 = "分型校验"; - // Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter - for &字段 in &["左", "中", "右"] { - let valA = A.getattr(字段)?; - let valB = B.getattr(字段)?; - 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线子项异常 >> {msg}"))); - } - } - for &(字段, 字段别名) in &[ - ("_结构", "结构"), - ("_时间戳", "时间戳"), - ("_分型特征值", "分型特征值"), - ] { - // 先尝试 Python 侧的下划线名,再尝试 Rust 侧的无下划线名 - let valA = 获取属性任意(A, &[字段, 字段别名])?; - let valB = 获取属性任意(B, &[字段, 字段别名])?; - let (a有, b有) = (valA.is_some(), valB.is_some()); - if a有 && !b有 { - return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); - } - if !a有 && b有 { - return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); - } - if !a有 && !b有 { - continue; - } - let valA = valA.unwrap(); - let valB = valB.unwrap(); - - if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { - let (ok, m) = r?; - if !ok { - return Ok((false, format!("{标签}: [{字段}]{m}"))); - } - } else if 字段 == "_时间戳" { - let a = 提取时间戳(&valA).unwrap_or(0); - let b = 提取时间戳(&valB).unwrap_or(0); - if a != b { - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); - } - } else if 字段 == "_结构" { - let sa = valA.str()?.extract::().unwrap_or_default(); - let sb = valB.str()?.extract::().unwrap_or_default(); - if sa != sb { - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); - } - } 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!("{标签}: 自有字段+三根缠论K线全部校验一致"))) } - ) + let (eq, msg) = 缠论K线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [{字段}]缠论K线子项异常 >> {msg}"))); + } + } + for &(字段, 字段别名) in &[ + ("_结构", "结构"), + ("_时间戳", "时间戳"), + ("_分型特征值", "分型特征值"), + ] { + // 先尝试 Python 侧的下划线名,再尝试 Rust 侧的无下划线名 + let valA = 获取属性任意(A, &[字段, 字段别名])?; + let valB = 获取属性任意(B, &[字段, 字段别名])?; + let (a有, b有) = (valA.is_some(), valB.is_some()); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = valA.unwrap(); + let valB = valB.unwrap(); + + if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{m}"))); + } + } else if 字段 == "_时间戳" { + let a = 提取时间戳(&valA).unwrap_or(0); + let b = 提取时间戳(&valB).unwrap_or(0); + if a != b { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); + } + } else if 字段 == "_结构" { + let sa = valA.str()?.extract::().unwrap_or_default(); + let sb = valB.str()?.extract::().unwrap_or_default(); + if sa != sb { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); + } + } 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!("{标签}: 自有字段+三根缠论K线全部校验一致"))) } // ========== 缺口相等 ========== @@ -405,54 +349,42 @@ fn 缺口相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_GAP, - 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 标签 = "缺口校验"; - 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 Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { - let (ok, m) = r?; - if !ok { - return Ok((false, format!("{标签}: [{字段}]{m}"))); - } - } 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!("{标签}: 上下沿价格校验完全一致"))) + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let 标签 = "缺口校验"; + 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 Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{m}"))); + } + } 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!("{标签}: 上下沿价格校验完全一致"))) } - // ========== 线段特征相等 ========== #[pyfunction] @@ -462,74 +394,60 @@ fn 线段特征相等( B: &Bound<'_, PyAny>, 浮点容差: f64, ) -> PyResult<(bool, String)> { - with_cache!( - C_SEG_FEAT, - 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 标签 = "线段特征校验"; - 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}"), - )); - } - 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 字段 == "线段方向" { - let sa = valA.str()?.extract::().unwrap_or_default(); - let sb = valB.str()?.extract::().unwrap_or_default(); - if sa != sb { - return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); - } - } 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!("{标签}: 字段与内部虚线序列全部一致"))) + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let 标签 = "线段特征校验"; + 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}"), + )); + } + 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 字段 == "线段方向" { + let sa = valA.str()?.extract::().unwrap_or_default(); + let sb = valB.str()?.extract::().unwrap_or_default(); + if sa != sb { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); + } + } 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!("{标签}: 字段与内部虚线序列全部一致"))) } // ========== 中枢相等 ========== @@ -790,8 +708,8 @@ fn 观察者相等( .inner .clone() .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("观察者B 内部为空"))?; - let obs_a = arc_a.read().unwrap(); - let obs_b = arc_b.read().unwrap(); + let obs_a = arc_a.read(); + let obs_b = arc_b.read(); Ok(obs_a.相等(&obs_b, 容差)) } diff --git a/chanlun-py/src/indicators_py.rs b/chanlun-py/src/indicators_py.rs index 9a755e8..b97ae9e 100644 --- a/chanlun-py/src/indicators_py.rs +++ b/chanlun-py/src/indicators_py.rs @@ -290,7 +290,7 @@ impl 相对强弱指数Py { } #[getter] fn RSI历史队列(&self) -> Vec { - self.inner.RSI历史队列.clone() + self.inner.RSI历史队列.iter().copied().collect() } fn __str__(&self) -> String { @@ -470,11 +470,11 @@ impl 随机指标Py { } #[getter] fn 历史最高价队列(&self) -> Vec { - self.inner.历史最高价队列.clone() + self.inner.历史最高价队列.iter().copied().collect() } #[getter] fn 历史最低价队列(&self) -> Vec { - self.inner.历史最低价队列.clone() + self.inner.历史最低价队列.iter().copied().collect() } #[getter] fn 前一个RSV(&self) -> Option { @@ -796,16 +796,12 @@ impl 指标容器Py { } fn __getitem__(&self, 名称: &str, py: Python<'_>) -> PyResult> { - if self.包含(名称) { - match self.inner.获取(名称) { - Some(v) => 指标值_to_py(v, py), - None => Ok(py.None()), - } - } else { - Err(pyo3::exceptions::PyKeyError::new_err(format!( + match self.inner.获取(名称) { + Some(v) => 指标值_to_py(v, py), + None => Err(pyo3::exceptions::PyKeyError::new_err(format!( "指标 '{}' 不存在", 名称 - ))) + ))), } } @@ -822,16 +818,12 @@ impl 指标容器Py { } return Ok(dict.into()); } - if self.包含(名称) { - match self.inner.获取(名称) { - Some(v) => 指标值_to_py(v, py), - None => Ok(py.None()), - } - } else { - Err(pyo3::exceptions::PyAttributeError::new_err(format!( + match self.inner.获取(名称) { + Some(v) => 指标值_to_py(v, py), + None => Err(pyo3::exceptions::PyAttributeError::new_err(format!( "指标 '{}' 不存在于 指标容器 中", 名称 - ))) + ))), } } @@ -917,7 +909,12 @@ impl 均线工具Py { return Ok(sum / (n.max(1)) as f64); } - let prev_key = format!("SMA_{}", period); + let prev_key = { + let mut s = String::with_capacity(8); + use std::fmt::Write; + write!(&mut s, "SMA_{}", period).unwrap(); + s + }; // 尝试从前一根K线的均线缓存中读取 let prev_cached = 普K序列[n - 2] .bind(py) @@ -925,7 +922,6 @@ impl 均线工具Py { .inner .指标 .read() - .unwrap() .均线() .and_then(|m| m.get(&prev_key)) .copied(); diff --git a/chanlun-py/src/kline_py.rs b/chanlun-py/src/kline_py.rs index 24f757f..c813ecf 100644 --- a/chanlun-py/src/kline_py.rs +++ b/chanlun-py/src/kline_py.rs @@ -22,11 +22,11 @@ * SOFTWARE. */ +use parking_lot::RwLock; use pyo3::prelude::*; use pyo3::types::{PyBytes, PyDict, PyList, PyType}; use std::collections::HashMap; use std::sync::Arc; -use std::sync::RwLock; use std::sync::atomic::Ordering; use crate::config_py::缠论配置Py; @@ -149,7 +149,6 @@ impl K线Py { self.inner .指标 .read() - .unwrap() .macd_cloned() .map(|m| 平滑异同移动平均线Py { inner: m }) } @@ -159,7 +158,6 @@ impl K线Py { self.inner .指标 .read() - .unwrap() .rsi_cloned() .map(|r| 相对强弱指数Py { inner: r }) } @@ -169,7 +167,6 @@ impl K线Py { self.inner .指标 .read() - .unwrap() .kdj_cloned() .map(|k| 随机指标Py { inner: k }) } @@ -178,7 +175,7 @@ impl K线Py { #[getter] fn 指标(&self) -> 指标容器Py { 指标容器Py { - inner: self.inner.指标.read().unwrap().clone(), + inner: self.inner.指标.read().clone(), } } @@ -339,6 +336,39 @@ impl K线Py { .take(end_idx - start_idx + 1) .collect()) } + + /// 根据当前K线和方向生成下一根K线(用于随机回测) + #[pyo3(signature = (方向, 居中 = false))] + fn 根据当前K线生成新K线( + &self, 方向: &Bound<'_, PyAny>, 居中: bool + ) -> PyResult { + let dir: chanlun::types::相对方向 = if let Ok(d) = 方向.extract::>() + { + d.inner + } else if let Ok(i) = 方向.extract::() { + match i { + 0 => chanlun::types::相对方向::向上, + 1 => chanlun::types::相对方向::向下, + 2 => chanlun::types::相对方向::向上缺口, + 3 => chanlun::types::相对方向::向下缺口, + 4 => chanlun::types::相对方向::衔接向上, + 5 => chanlun::types::相对方向::衔接向下, + _ => { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "无效方向: {i}" + ))); + } + } + } else { + return Err(pyo3::exceptions::PyTypeError::new_err( + "方向 必须是 相对方向 或 int (0-5)", + )); + }; + let new_bar = self.inner.根据当前K线生成新K线(dir, 居中); + Ok(Self { + inner: Arc::new(new_bar), + }) + } } // ========== 缠论K线 ========== @@ -371,57 +401,29 @@ impl 缠论K线Py { } } -/// 对象标识缓存:Arc 地址 → 规范 Python 对象 -/// 确保同一底层 Arc 指针在 Python 侧始终映射到同一 PyObject -/// 使用全局 static 而非 thread_local!,保证跨线程对象标识和买卖点信息一致性 -static BAR_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); - -static KLINE_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); - -/// 买卖点信息缓存 — 按 Arc 指针全局共享,确保所有 wrapper 看到同一 PySet -static BSP_CACHE: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); - -/// 将 Rc 转为 Py,确保同一 Rc 地址总是返回同一 Python 对象 pub(crate) fn bar_to_py( py: Python<'_>, inner: std::sync::Arc, ) -> Py { let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = BAR_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { + if let Some(cached) = crate::cache::bar_get(py, key) { return cached; } let obj = Py::new(py, K线Py { inner }).unwrap(); - BAR_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py)); + crate::cache::bar_insert(py, key, &obj); obj } -/// 将 Rc<缠论K线> 转为 Py<缠论K线Py>,确保同一 Rc 地址总是返回同一 Python 对象 pub(crate) fn chan_kline_to_py( py: Python<'_>, inner: std::sync::Arc, ) -> Py<缠论K线Py> { let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = KLINE_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { + if let Some(cached) = crate::cache::kline_get(py, key) { return cached; } let obj = Py::new(py, 缠论K线Py::from_rc(inner)).unwrap(); - KLINE_IDENTITY - .write() - .unwrap() - .insert(key, obj.clone_ref(py)); + crate::cache::kline_insert(py, key, &obj); obj } @@ -462,7 +464,7 @@ impl 缠论K线Py { #[getter] fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> { - crate::types_py::获取相对方向单例(py, *self.inner.方向.read().unwrap()) + crate::types_py::获取相对方向单例(py, *self.inner.方向.read()) } #[getter] @@ -470,7 +472,6 @@ impl 缠论K线Py { self.inner .分型 .read() - .unwrap() .map(|f| crate::types_py::获取分型结构单例(py, f)) } @@ -501,7 +502,7 @@ impl 缠论K线Py { #[getter] fn 标的K线(&self, py: Python<'_>) -> Py { - bar_to_py(py, self.inner.标的K线.read().unwrap().clone()) + bar_to_py(py, self.inner.标的K线.read().clone()) } /// pandas 兼容 — 返回所有字段构成的字典 @@ -556,11 +557,7 @@ impl 缠论K线Py { // 复制买卖点信息到镜像 let src_key = Arc::as_ptr(&self.inner) as usize; let dst_key = Arc::as_ptr(&mirror.inner) as usize; - let cached_src = BSP_CACHE - .read() - .unwrap() - .get(&src_key) - .map(|p| p.clone_ref(py)); + let cached_src = crate::cache::bsp_get(py, src_key); if let Some(cached_src) = cached_src && let Ok(new_set) = pyo3::types::PySet::empty(py) { @@ -568,7 +565,7 @@ impl 缠论K线Py { let _ = new_set.add(item); } let py_set: Py = new_set.into(); - BSP_CACHE.write().unwrap().insert(dst_key, py_set); + crate::cache::bsp_insert(py, dst_key, py_set); } mirror } @@ -595,25 +592,19 @@ impl 缠论K线Py { fn 买卖点信息(&self, py: Python<'_>) -> PyResult> { let key = Arc::as_ptr(&self.inner) as usize; // 检查全局缓存 - let cached = BSP_CACHE.read().unwrap().get(&key).map(|p| p.clone_ref(py)); + let cached = crate::cache::bsp_get(py, key); if let Some(set) = cached { return Ok(set.into_any()); } // 创建新的 PySet,从 Rust HashSet 同步已有内容 let set = pyo3::types::PySet::empty(py)?; - let bsp_info = self.inner.买卖点信息.read().unwrap(); + let bsp_info = self.inner.买卖点信息.read(); for item in bsp_info.iter() { set.add(item.as_str())?; } drop(bsp_info); - BSP_CACHE.write().unwrap().insert(key, set.into()); - Ok(BSP_CACHE - .read() - .unwrap() - .get(&key) - .unwrap() - .clone_ref(py) - .into_any()) + crate::cache::bsp_insert(py, key, set.into()); + Ok(crate::cache::bsp_get(py, key).unwrap().into_any()) } #[classmethod] diff --git a/chanlun-py/src/lib.rs b/chanlun-py/src/lib.rs index f5d9235..4007392 100644 --- a/chanlun-py/src/lib.rs +++ b/chanlun-py/src/lib.rs @@ -102,6 +102,7 @@ fn init_tracing() { mod algorithm_py; mod business_py; +pub(crate) mod cache; mod config_py; mod equality_py; mod indicators_py; @@ -139,9 +140,7 @@ fn get_log_level() -> &'static str { 级别数字转名称(LOG_LEVEL.load(Ordering::Relaxed)) } -/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off") -/// -/// 设为 "off" 可完全关闭日志输出。 +/// 设置日志级别 — 自动启用日志,同步更新 tracing subscriber #[pyfunction] fn set_log_level(level: &str) -> PyResult<()> { let 数字 = 级别名称转数字(level).ok_or_else(|| { @@ -150,32 +149,91 @@ fn set_log_level(level: &str) -> PyResult<()> { level )) })?; - - let guard = 过滤器句柄锁 - .get() - .ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("日志系统尚未初始化"))?; - let handle = guard.lock().unwrap(); - let 名称 = 级别数字转名称(数字); - let filter = tracing_subscriber::EnvFilter::new(名称); - handle - .reload(filter) - .map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("切换日志级别失败"))?; - LOG_LEVEL.store(数字, Ordering::Relaxed); + chanlun::log::日志启用.store(数字 < 5, Ordering::Relaxed); + // 同步更新 tracing subscriber + if let Some(guard) = 过滤器句柄锁.get() { + let handle = guard.lock().unwrap(); + let 名称 = 级别数字转名称(数字); + let filter = tracing_subscriber::EnvFilter::new(名称); + let _ = handle.reload(filter); + } Ok(()) } +/// 获取日志输出模式 ("off", "simple", "tracing") +#[pyfunction] +fn get_log_mode() -> &'static str { + match chanlun::log::get_log_mode() { + 0 => "off", + 1 => "simple", + 2 => "tracing", + _ => "unknown", + } +} + +/// 设置日志输出模式(必须在任何日志输出之前调用) +/// - "off": 不输出 +/// - "simple": 直接 eprintln/println(默认) +/// - "tracing": 带时间戳和格式化的 tracing subscriber +#[pyfunction] +fn set_log_mode(mode: &str) -> PyResult<()> { + let m = match mode.to_lowercase().as_str() { + "off" | "0" => 0u8, + "simple" | "on" | "1" => 1u8, + "tracing" | "2" => 2u8, + _ => { + return Err(pyo3::exceptions::PyValueError::new_err( + "无效日志模式,有效值: 'off', 'simple', 'tracing'", + )); + } + }; + if m == 2 { + init_tracing(); + } + chanlun::log::set_log_mode(m); + Ok(()) +} + +/// 获取缓存模式 ("thread_local" 或 "global") +#[pyfunction] +fn get_cache_mode() -> &'static str { + match crate::cache::peek_mode().unwrap_or(&crate::cache::CacheMode::ThreadLocal) { + crate::cache::CacheMode::ThreadLocal => "thread_local", + crate::cache::CacheMode::Global => "global", + } +} + +/// 设置缓存模式(必须在创建任何观察者之前调用) +#[pyfunction] +fn set_cache_mode(mode: &str) -> PyResult<()> { + let m = match mode.to_lowercase().as_str() { + "thread_local" | "local" => crate::cache::CacheMode::ThreadLocal, + "global" => crate::cache::CacheMode::Global, + _ => { + return Err(pyo3::exceptions::PyValueError::new_err( + "无效缓存模式,有效值: 'thread_local', 'global'", + )); + } + }; + crate::cache::set_mode(m).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e)) +} + /// 缠论技术分析库 — Rust 高性能实现 #[pymodule] /// 缠论技术分析库 — Rust 高性能实现 fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { - init_tracing(); + chanlun::log::init_from_env(); m.add_function(wrap_pyfunction!(get_分型模式, m)?)?; m.add_function(wrap_pyfunction!(set_分型模式, m)?)?; m.add_function(wrap_pyfunction!(get_扩展线段模式, m)?)?; m.add_function(wrap_pyfunction!(set_扩展线段模式, m)?)?; m.add_function(wrap_pyfunction!(get_log_level, m)?)?; m.add_function(wrap_pyfunction!(set_log_level, m)?)?; + m.add_function(wrap_pyfunction!(get_log_mode, m)?)?; + m.add_function(wrap_pyfunction!(set_log_mode, m)?)?; + m.add_function(wrap_pyfunction!(get_cache_mode, m)?)?; + m.add_function(wrap_pyfunction!(set_cache_mode, m)?)?; // 阶段 1: 枚举和基础类型 types_py::register(m)?; // 阶段 2: 配置 diff --git a/chanlun-py/src/structure_py.rs b/chanlun-py/src/structure_py.rs index eec6cd7..87f17de 100644 --- a/chanlun-py/src/structure_py.rs +++ b/chanlun-py/src/structure_py.rs @@ -24,9 +24,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; -use std::collections::HashMap; use std::sync::Arc; -use std::sync::RwLock; use std::sync::atomic::Ordering; use crate::algorithm_py::hub_to_py; @@ -35,37 +33,18 @@ use crate::kline_py::{K线Py, bar_to_py, 缠论K线Py}; // ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ---- -// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 -static FRACTAL_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); -static DASHED_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); -static SEGFEAT_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); +// 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换) pub(crate) fn fractal_to_py( py: Python<'_>, inner: Arc, ) -> Py<分型Py> { let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = FRACTAL_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { + if let Some(cached) = crate::cache::fractal_get(py, key) { return cached; } - // 清理 refcnt==1 的过期条目(仅缓存持有,Python 侧已无引用) - FRACTAL_IDENTITY - .write() - .unwrap() - .retain(|_, v| v.get_refcnt(py) > 1); let obj = Py::new(py, 分型Py { inner }).unwrap(); - FRACTAL_IDENTITY - .write() - .unwrap() - .insert(key, obj.clone_ref(py)); + crate::cache::fractal_insert(py, key, &obj); obj } @@ -74,23 +53,11 @@ pub(crate) fn dashed_to_py( inner: Arc, ) -> Py<虚线Py> { let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = DASHED_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { + if let Some(cached) = crate::cache::dashed_get(py, key) { return cached; } - DASHED_IDENTITY - .write() - .unwrap() - .retain(|_, v| v.get_refcnt(py) > 1); let obj = Py::new(py, 虚线Py { inner }).unwrap(); - DASHED_IDENTITY - .write() - .unwrap() - .insert(key, obj.clone_ref(py)); + crate::cache::dashed_insert(py, key, &obj); obj } @@ -98,25 +65,7 @@ pub(crate) fn segfeat_to_py( py: Python<'_>, inner: Arc, ) -> Py<线段特征Py> { - let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = SEGFEAT_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { - return cached; - } - SEGFEAT_IDENTITY - .write() - .unwrap() - .retain(|_, v| v.get_refcnt(py) > 1); - let obj = Py::new(py, 线段特征Py { inner }).unwrap(); - SEGFEAT_IDENTITY - .write() - .unwrap() - .insert(key, obj.clone_ref(py)); - obj + Py::new(py, 线段特征Py { inner }).unwrap() } use crate::types_py::{分型结构Py, 相对方向Py, 缺口Py}; @@ -393,7 +342,7 @@ impl 虚线Py { #[getter] fn 标识(&self) -> String { - self.inner.标识.read().unwrap().clone() + self.inner.标识.read().clone() } #[getter] @@ -413,7 +362,7 @@ impl 虚线Py { #[getter] fn 武(&self, py: Python<'_>) -> Py<分型Py> { - fractal_to_py(py, Arc::clone(&*self.inner.武.read().unwrap())) + fractal_to_py(py, Arc::clone(&*self.inner.武.read())) } #[getter] @@ -423,7 +372,7 @@ impl 虚线Py { #[getter] fn 模式(&self) -> String { - self.inner.模式.read().unwrap().clone() + self.inner.模式.read().clone() } #[getter(_特征序列_显示)] @@ -439,7 +388,7 @@ impl 虚线Py { #[getter] fn 特征序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for item in self.inner.特征序列.read().unwrap().iter() { + for item in self.inner.特征序列.read().iter() { match item { Some(feat) => list.append(segfeat_to_py(py, Arc::clone(feat)))?, None => { @@ -460,18 +409,13 @@ impl 虚线Py { self.inner .确认K线 .read() - .unwrap() .as_ref() .map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k))) } #[getter] fn 前一缺口(&self) -> Option<缺口Py> { - self.inner - .前一缺口 - .read() - .unwrap() - .map(|q| 缺口Py { inner: q }) + self.inner.前一缺口.read().map(|q| 缺口Py { inner: q }) } #[getter] @@ -479,7 +423,6 @@ impl 虚线Py { self.inner .前一结束位置 .read() - .unwrap() .as_ref() .map(|d| dashed_to_py(py, Arc::clone(d))) } @@ -489,7 +432,7 @@ impl 虚线Py { #[getter] fn 基础序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in self.inner.基础序列.read().unwrap().iter() { + for d in self.inner.基础序列.read().iter() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -498,7 +441,7 @@ impl 虚线Py { #[getter] fn 实_中枢序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in self.inner.实_中枢序列.read().unwrap().iter() { + for h in self.inner.实_中枢序列.read().iter() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -507,7 +450,7 @@ impl 虚线Py { #[getter] fn 虚_中枢序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in self.inner.虚_中枢序列.read().unwrap().iter() { + for h in self.inner.虚_中枢序列.read().iter() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -516,7 +459,7 @@ impl 虚线Py { #[getter] fn 合_中枢序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for h in self.inner.合_中枢序列.read().unwrap().iter() { + for h in self.inner.合_中枢序列.read().iter() { list.append(hub_to_py(py, Arc::clone(h)))?; } Ok(list.into()) @@ -528,7 +471,7 @@ impl 虚线Py { /// 笔序列 fn 笔序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in self.inner.基础序列.read().unwrap().iter() { + for d in self.inner.基础序列.read().iter() { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1005,12 +948,12 @@ impl 线段特征Py { #[getter] fn 标识(&self) -> String { - self.inner.标识.read().unwrap().clone() + self.inner.标识.read().clone() } #[setter] fn set_标识(&self, value: String) { - *self.inner.标识.write().unwrap() = value; + *self.inner.标识.write() = value; } #[getter] diff --git a/chanlun-py/src/types_py.rs b/chanlun-py/src/types_py.rs index 48ca8b0..0f79eda 100644 --- a/chanlun-py/src/types_py.rs +++ b/chanlun-py/src/types_py.rs @@ -22,8 +22,8 @@ * SOFTWARE. */ +use parking_lot::Mutex; use std::collections::HashMap; -use std::sync::Mutex; use pyo3::basic::CompareOp; use pyo3::prelude::*; @@ -37,7 +37,7 @@ pub fn 获取分型结构单例( py: Python<'_>, inner: chanlun::types::分型结构, ) -> Py<分型结构Py> { - let mut guard = 分型结构_单例缓存.lock().unwrap(); + let mut guard = 分型结构_单例缓存.lock(); if let Some(ref map) = *guard { return map[&(inner as u8)].clone_ref(py); } @@ -67,7 +67,7 @@ pub fn 获取相对方向单例( py: Python<'_>, inner: chanlun::types::相对方向, ) -> Py<相对方向Py> { - let mut guard = 相对方向_单例缓存.lock().unwrap(); + let mut guard = 相对方向_单例缓存.lock(); if let Some(ref map) = *guard { return map[&(inner as u8)].clone_ref(py); } @@ -312,6 +312,22 @@ impl 相对方向Py { chanlun::types::相对方向::分析(前高, 前低, 后高, 后低), ) } + + /// 从可选方向序列中随机选取指定数量 + #[classmethod] + #[pyo3(signature = (数量, 可选方向, 可重复 = true))] + fn 从序列中机选( + _cls: &Bound<'_, PyType>, + 数量: usize, + 可选方向: Vec>, + 可重复: bool, + py: Python<'_>, + ) -> Vec> { + let dirs: Vec = + 可选方向.iter().map(|d| d.borrow(py).inner).collect(); + let result = chanlun::types::相对方向::从序列中机选(数量, &dirs, 可重复); + result.iter().map(|d| 获取相对方向单例(py, *d)).collect() + } } // ========== 分型结构 ========== diff --git a/chanlun-py/tests/test_all.py b/chanlun-py/tests/test_all.py index 39985ff..9b011f1 100644 --- a/chanlun-py/tests/test_all.py +++ b/chanlun-py/tests/test_all.py @@ -2776,5 +2776,74 @@ class Test缠论配置双端一致(unittest.TestCase): self.assertFalse(diff_py[k], f"不推送差异字段 {k} 应为 False") +class Test生成K线双端一致(unittest.TestCase): + """根据当前K线生成新K线 Rust vs chan.py 输出一致.""" + + def test_生成K线_居中各方向双端一致(self): + """居中模式下各方向生成K线双端OHLC一致(居中=确定性输出)""" + import chanlun + from chanlun import chan + + bar_rs = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300) + bar_py = chan.K线.创建普K("btcusd", chan.转化为时间戳(1000000000), 50000, 50200, 49800, 50100, 100, 0, 300) + + directions = { + "向上": 0, + "向下": 1, + "向上缺口": 2, + "向下缺口": 3, + "衔接向上": 4, + "衔接向下": 5, + } + py_dirs = { + "向上": chan.相对方向.向上, + "向下": chan.相对方向.向下, + "向上缺口": chan.相对方向.向上缺口, + "向下缺口": chan.相对方向.向下缺口, + "衔接向上": chan.相对方向.衔接向上, + "衔接向下": chan.相对方向.衔接向下, + } + + for name in directions: + new_rs = bar_rs.根据当前K线生成新K线(directions[name], 居中=True) + new_py = bar_py.根据当前K线生成新K线(py_dirs[name], 居中=True) + + # 居中模式下,高和低是确定性的(偏移=高低差*0.5) + # 开盘价/收盘价/成交量含随机,不比较 + tol = 1.0 + abs(new_py.高) * 1e-6 + self.assertAlmostEqual(new_rs.高, new_py.高, delta=tol, msg=f"{name}: 最高价不一致 (R={new_rs.高}, P={new_py.高})") + self.assertAlmostEqual(new_rs.低, new_py.低, delta=tol, msg=f"{name}: 最低价不一致 (R={new_rs.低}, P={new_py.低})") + + # 时间戳和序号 + self.assertEqual(new_rs.序号, new_py.序号) + self.assertEqual(int(new_rs.时间戳), int(chan.转化为时间戳_数字(new_py.时间戳))) + + def test_生成K线_居中外推验证(self): + """居中向上生成:新K线的高/低应整体高于原K线.""" + import chanlun + + bar = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300) + new = bar.根据当前K线生成新K线(0, 居中=True) + + self.assertGreater(new.高, bar.高, "向上:新高应高于原高") + self.assertGreater(new.低, bar.低, "向上:新低应高于原低") + + new_down = bar.根据当前K线生成新K线(1, 居中=True) + self.assertLess(new_down.高, bar.高, "向下:新高应低于原高") + self.assertLess(new_down.低, bar.低, "向下:新低应低于原低") + + def test_生成K线_衔接验证(self): + """衔接向上:新K线的低 = 原K线的高(无缝衔接).""" + import chanlun + + bar = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300) + new = bar.根据当前K线生成新K线(4, 居中=True) + + self.assertAlmostEqual(new.低, bar.高, delta=1e-6, msg="衔接向上:新低应=原高") + + new_down = bar.根据当前K线生成新K线(5, 居中=True) + self.assertAlmostEqual(new_down.高, bar.低, delta=1e-6, msg="衔接向下:新高应=原低") + + if __name__ == "__main__": unittest.main() diff --git a/chanlun/Cargo.toml b/chanlun/Cargo.toml index 788264e..4b793e7 100644 --- a/chanlun/Cargo.toml +++ b/chanlun/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chanlun" -version = "26.6.3" +version = "26.6.4" edition = "2024" license = "MIT" description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。" @@ -17,6 +17,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" byteorder = "1" chrono = { version = "0.4", features = ["serde"] } -cached = "1" +parking_lot = "0.12" tracing = "0.1" -tracing-subscriber = "0.3" +fastrand = "2" diff --git a/chanlun/src/algorithm/bi.rs b/chanlun/src/algorithm/bi.rs index 44cdc03..cf280cd 100644 --- a/chanlun/src/algorithm/bi.rs +++ b/chanlun/src/algorithm/bi.rs @@ -29,9 +29,9 @@ use crate::kline::chan_kline::缠论K线; use crate::structure::dash_line::虚线; use crate::structure::fractal_obj::分型; use crate::types::{分型结构, 相对方向}; +use crate::{error, warn}; use std::sync::Arc; use std::sync::atomic::Ordering; -use tracing::{error, warn}; /// 笔 — 从分型生成笔的算法集合(静态方法命名空间) pub struct 笔; @@ -54,8 +54,7 @@ impl 笔 { if let (Some(高点), Some(低点)) = (&实际高点, &实际低点) { let 原始数量 = 1 - + (低点.标的K线.read().unwrap().序号 - 高点.标的K线.read().unwrap().序号) - .unsigned_abs() as usize; + + (低点.标的K线.read().序号 - 高点.标的K线.read().序号).unsigned_abs() as usize; if 原始数量 >= 配置.笔内元素数量 as usize { return 配置.笔内元素数量 as usize; } @@ -76,8 +75,8 @@ impl 笔 { && let (Some(高_k), Some(低_k)) = (&实际高点, &实际低点) { let 原始数量 = 1 - + (低_k.标的K线.read().unwrap().序号 - 高_k.标的K线.read().unwrap().序号) - .unsigned_abs() as usize; + + (低_k.标的K线.read().序号 - 高_k.标的K线.read().序号).unsigned_abs() + as usize; // 向上笔 if 筆.方向().是否向上() && 低_k.低.get() < 筆.低() @@ -219,7 +218,7 @@ impl 笔 { /// 判断笔的相对关系是否合理 pub fn _相对关系(筆: &虚线, 配置: &缠论配置) -> bool { let 文分型 = &筆.文; - let 武分型 = 筆.武.read().unwrap(); + let 武分型 = 筆.武.read(); let 相对关系 = if 配置.笔内起始分型包含整笔 { let 文中_rc = Arc::clone(&文分型.中); @@ -261,8 +260,8 @@ impl 笔 { 武分型.中.低.get(), ); if 配置.笔内原始K线包含整笔 { - let 文标的 = 文分型.中.标的K线.read().unwrap(); - let 武标的 = 武分型.中.标的K线.read().unwrap(); + let 文标的 = 文分型.中.标的K线.read(); + let 武标的 = 武分型.中.标的K线.read(); if crate::types::相对方向::分析(文标的.高, 文标的.低, 武标的.高, 武标的.低) .是否包含() { @@ -280,10 +279,7 @@ impl 笔 { /// 以文会友 — 根据起点分型找笔 pub fn 以文会友(笔序列: &[Arc<虚线>], 文: &Arc<分型>) -> Option> { - 笔序列 - .iter() - .find(|b| Arc::as_ptr(&b.文) == Arc::as_ptr(文)) - .cloned() + 笔序列.iter().find(|b| Arc::ptr_eq(&b.文, 文)).cloned() } /// 以武会友 — 根据终点分型找笔 @@ -291,7 +287,7 @@ impl 笔 { 笔序列 .iter() .rev() - .find(|b| Arc::as_ptr(&*b.武.read().unwrap()) == Arc::as_ptr(武)) + .find(|b| Arc::ptr_eq(&*b.武.read(), 武)) .cloned() } @@ -305,8 +301,7 @@ impl 笔 { for b in 笔序列.iter().rev() { // Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号 if b.文.中.序号.load(Ordering::Relaxed) - 偏移 <= 缠K.序号.load(Ordering::Relaxed) - && 缠K.序号.load(Ordering::Relaxed) - <= b.武.read().unwrap().中.序号.load(Ordering::Relaxed) + && 缠K.序号.load(Ordering::Relaxed) <= b.武.read().中.序号.load(Ordering::Relaxed) && b.文.中.周期 == 缠K.周期 && b.文.中.标识 == 缠K.标识 { @@ -323,7 +318,7 @@ impl 笔 { let 旧分型 = 分型序列.pop(); if let (Some(旧笔), Some(旧分型)) = (笔序列.pop(), 旧分型) { assert!( - Arc::as_ptr(&旧笔.武.read().unwrap()) == Arc::as_ptr(&旧分型), + Arc::ptr_eq(&旧笔.武.read(), &旧分型), "最后一笔终点错误{}", 行号 ); @@ -388,7 +383,7 @@ impl 笔 { // Python line 2343-2348: 笔弱化模式 if 配置.笔弱化 && !笔序列.is_empty() { let 前一笔 = 笔序列.last().unwrap(); - let 前一笔缠K数 = 前一笔.武.read().unwrap().中.序号.load(Ordering::Relaxed) + let 前一笔缠K数 = 前一笔.武.read().中.序号.load(Ordering::Relaxed) - 前一笔.文.中.序号.load(Ordering::Relaxed) + 1; if 前一笔缠K数 == 3 { @@ -434,7 +429,7 @@ impl 笔 { // Python line 2359-2367: 文官调整 if let Some(ref 文官_k) = 文官 - && Arc::as_ptr(文官_k) != Arc::as_ptr(&之前分型.中) + && !Arc::ptr_eq(文官_k, &之前分型.中) && let Some(临时分型) = 分型::从缠K序列中获取分型(缠K序列, 文官_k) { @@ -476,7 +471,7 @@ impl 笔 { if Self::_相对关系(&当前笔, 配置) && let Some(ref 武将_k) = 武将 - && Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) + && Arc::ptr_eq(武将_k, &当前分型.中) { // 直接添加(对照 Python _添加新笔:直接 append) Self::_添加新笔(分型序列, 笔序列, 当前分型, 当前笔, line!()); @@ -490,7 +485,7 @@ impl 笔 { _ => Self::_次高(&基础序列, 配置.笔内相同终点取舍), }; if let Some(ref 武将_k) = 武将 - && Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) + && Arc::ptr_eq(武将_k, &当前分型.中) && Self::_相对关系(&当前笔, 配置) { Self::_添加新笔(分型序列, 笔序列, 当前分型, 当前笔, line!()); @@ -557,13 +552,12 @@ impl 笔 { if !分型序列.is_empty() && Arc::as_ptr(分型序列.last().unwrap()) == Arc::as_ptr(&临时分型_rc) - && let Some(武_idx) = 缠K序列 - .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(武将_k)) + && let Some(武_idx) = + 缠K序列.iter().position(|k| Arc::ptr_eq(k, 武将_k)) { for ck in &缠K序列[武_idx..] { - if (*ck.分型.read().unwrap() == Some(分型结构::底) - || *ck.分型.read().unwrap() == Some(分型结构::顶)) + if (*ck.分型.read() == Some(分型结构::底) + || *ck.分型.read() == Some(分型结构::顶)) && let Some(错过分型) = 分型::从缠K序列中获取分型(缠K序列, ck) { @@ -577,6 +571,15 @@ impl 笔 { 递归层次 + 1, 配置, ); + if !分型序列.is_empty() + && Arc::as_ptr(分型序列.last().unwrap()) + == Arc::as_ptr(&错过分型_rc) + { + warn!( + "笔.分析 事后修复错过的笔:{}, 当前分型: {}", + 错过分型_rc, 当前分型 + ); + } } } } @@ -649,14 +652,10 @@ impl 笔 { 新筆 .序号 .store(前一笔.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - if 新筆.武.read().unwrap().左.is_none() || 新筆.武.read().unwrap().右.is_none() - { + if 新筆.武.read().左.is_none() || 新筆.武.read().右.is_none() { 新筆.有效性.store(false, Ordering::Relaxed); } - if matches!( - 前一笔.武.read().unwrap().结构(), - 分型结构::上 | 分型结构::下 - ) { + if matches!(前一笔.武.read().结构(), 分型结构::上 | 分型结构::下) { error!("_添加新笔[{}] 出现无效分型 {}", 行号, 前一笔); } } @@ -676,7 +675,7 @@ impl 笔 { Self::_实际低点(&基础序列, 配置.笔内相同终点取舍), ) && Arc::ptr_eq(&筆.文.中, &实际高) - && Arc::ptr_eq(&筆.武.read().unwrap().中, &实际低) + && Arc::ptr_eq(&筆.武.read().中, &实际低) { return true; } @@ -686,7 +685,7 @@ impl 笔 { Self::_实际高点(&基础序列, 配置.笔内相同终点取舍), ) && Arc::ptr_eq(&筆.文.中, &实际低) - && Arc::ptr_eq(&筆.武.read().unwrap().中, &实际高) + && Arc::ptr_eq(&筆.武.read().中, &实际高) { return true; } @@ -696,9 +695,9 @@ impl 笔 { /// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合 pub fn 获取所有停顿位置(筆: &虚线, 观察员: &观察者) -> Vec<虚线> { - let mut 笔序列 = Vec::new(); - let 文 = Arc::clone(&筆.文); let 基础序列 = 筆.获取缠K序列(&观察员.缠论K线序列); + let mut 笔序列 = Vec::with_capacity(基础序列.len() / 2); + let 文 = Arc::clone(&筆.文); if 基础序列.len() < 5 { return 笔序列; @@ -707,10 +706,8 @@ impl 笔 { for i in 3..基础序列.len() - 1 { let k = &基础序列[i]; - let 匹配顶 = - *k.分型.read().unwrap() == Some(分型结构::顶) && 筆.方向() == 相对方向::向上; - let 匹配底 = - *k.分型.read().unwrap() == Some(分型结构::底) && 筆.方向() == 相对方向::向下; + let 匹配顶 = *k.分型.read() == Some(分型结构::顶) && 筆.方向() == 相对方向::向上; + let 匹配底 = *k.分型.read() == Some(分型结构::底) && 筆.方向() == 相对方向::向下; if 匹配顶 || 匹配底 { let 左 = Arc::clone(&基础序列[i - 1]); let 中 = Arc::clone(k); @@ -737,12 +734,12 @@ impl 笔 { for 筆 in &停顿位置 { let k线范围 = K线::截取rc( &观察员.普通K线序列, - &当前筆.文.中.标的K线.read().unwrap().clone(), - &当前筆.武.read().unwrap().中.标的K线.read().unwrap().clone(), + &当前筆.文.中.标的K线.read().clone(), + &当前筆.武.read().中.标的K线.read().clone(), ); let 背驰信号 = 虚线::计算K线序列MACD趋向背驰(&k线范围, 筆.方向()); if 背驰信号.iter().all(|&x| x) { - 结果.push(Arc::clone(&筆.武.read().unwrap().中)); + 结果.push(Arc::clone(&筆.武.read().中)); } } diff --git a/chanlun/src/algorithm/divergence.rs b/chanlun/src/algorithm/divergence.rs index cecc45f..3e4cdf7 100644 --- a/chanlun/src/algorithm/divergence.rs +++ b/chanlun/src/algorithm/divergence.rs @@ -39,13 +39,13 @@ impl 背驰分析 { ) -> bool { let 进入MACD = Self::_获取MACD面积( K线序列, - &进入段.文.中.标的K线.read().unwrap(), - &进入段.武.read().unwrap().中.标的K线.read().unwrap(), + &进入段.文.中.标的K线.read(), + &进入段.武.read().中.标的K线.read(), ); let 离开MACD = Self::_获取MACD面积( K线序列, - &离开段.文.中.标的K线.read().unwrap(), - &离开段.武.read().unwrap().中.标的K线.read().unwrap(), + &离开段.文.中.标的K线.read(), + &离开段.武.read().中.标的K线.read(), ); // 计算面积(绝对值求和) @@ -69,18 +69,18 @@ impl 背驰分析 { /// 斜率背驰 — 价格斜率背驰 pub fn 斜率背驰(进入段: &虚线, 离开段: &虚线) -> bool { - let dx = (进入段.武.read().unwrap().时间戳() - 进入段.文.时间戳()) as f64; + let dx = (进入段.武.read().时间戳() - 进入段.文.时间戳()) as f64; if dx == 0.0 { return false; } - let dy = 进入段.武.read().unwrap().分型特征值 - 进入段.文.分型特征值; + let dy = 进入段.武.read().分型特征值 - 进入段.文.分型特征值; let 进入斜率 = dy / dx; - let dx = (离开段.武.read().unwrap().时间戳() - 离开段.文.时间戳()) as f64; + let dx = (离开段.武.read().时间戳() - 离开段.文.时间戳()) as f64; if dx == 0.0 { return false; } - let dy = 离开段.武.read().unwrap().分型特征值 - 离开段.文.分型特征值; + let dy = 离开段.武.read().分型特征值 - 离开段.文.分型特征值; let 离开斜率 = dy / dx; if 进入段.方向() == 相对方向::向上 { @@ -92,12 +92,12 @@ impl 背驰分析 { /// 测度背驰 — 价格时间测度背驰 pub fn 测度背驰(进入段: &虚线, 离开段: &虚线) -> bool { - let dx = (进入段.武.read().unwrap().时间戳() - 进入段.文.时间戳()) as f64; - let dy = 进入段.武.read().unwrap().分型特征值 - 进入段.文.分型特征值; + let dx = (进入段.武.read().时间戳() - 进入段.文.时间戳()) as f64; + let dy = 进入段.武.read().分型特征值 - 进入段.文.分型特征值; let 进入测度 = (dx * dx + dy * dy).sqrt(); - let dx = (离开段.武.read().unwrap().时间戳() - 离开段.文.时间戳()) as f64; - let dy = 离开段.武.read().unwrap().分型特征值 - 离开段.文.分型特征值; + let dx = (离开段.武.read().时间戳() - 离开段.文.时间戳()) as f64; + let dy = 离开段.武.read().分型特征值 - 离开段.文.分型特征值; let 离开测度 = (dx * dx + dy * dy).sqrt(); if 进入段.方向() == 相对方向::向上 { @@ -187,12 +187,8 @@ impl 背驰分析 { // ---- 内部辅助 ---- fn _获取MACD面积(K线序列: &[Arc], 始: &Arc, 终: &Arc) -> MACD面积 { - let 始_idx = K线序列 - .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(始)); - let 终_idx = K线序列 - .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(终)); + let 始_idx = K线序列.iter().position(|k| Arc::ptr_eq(k, 始)); + let 终_idx = K线序列.iter().position(|k| Arc::ptr_eq(k, 终)); let mut 阳 = 0.0f64; let mut 阴 = 0.0f64; @@ -200,7 +196,7 @@ impl 背驰分析 { if let (Some(始), Some(终)) = (始_idx, 终_idx) { let (始, 终) = if 始 <= 终 { (始, 终) } else { (终, 始) }; for k in &K线序列[始..=终] { - if let Some(macd) = k.指标.read().unwrap().macd() { + if let Some(macd) = k.指标.read().macd() { let hist = macd.MACD柱; if hist >= 0.0 { 阳 += hist; diff --git a/chanlun/src/algorithm/hub.rs b/chanlun/src/algorithm/hub.rs index efda218..77b681a 100644 --- a/chanlun/src/algorithm/hub.rs +++ b/chanlun/src/algorithm/hub.rs @@ -25,8 +25,9 @@ use crate::structure::dash_line::虚线; use crate::structure::fractal_obj::分型; use crate::types::相对方向; +use parking_lot::RwLock; +use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; -use std::sync::{Arc, RwLock}; /// 中枢 — 三段虚线重叠区间构成的价格中枢 /// @@ -59,11 +60,11 @@ impl Clone for 中枢 { fn clone(&self) -> Self { Self { 序号: AtomicI64::new(self.序号.load(Ordering::Relaxed)), - 标识: RwLock::new(self.标识.read().unwrap().clone()), + 标识: RwLock::new(self.标识.read().clone()), 级别: AtomicI64::new(self.级别.load(Ordering::Relaxed)), - 基础序列: RwLock::new(self.基础序列.read().unwrap().clone()), - 第三买卖线: RwLock::new(self.第三买卖线.read().unwrap().clone()), - 本级_第三买卖线: RwLock::new(self.本级_第三买卖线.read().unwrap().clone()), + 基础序列: RwLock::new(self.基础序列.read().clone()), + 第三买卖线: RwLock::new(self.第三买卖线.read().clone()), + 本级_第三买卖线: RwLock::new(self.本级_第三买卖线.read().clone()), } } } @@ -83,9 +84,9 @@ impl 中枢 { /// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线 pub fn _添加虚线(&self, 实线: Arc<虚线>) { - self.基础序列.write().unwrap().push(实线); - *self.本级_第三买卖线.write().unwrap() = None; - *self.第三买卖线.write().unwrap() = None; + self.基础序列.write().push(实线); + *self.本级_第三买卖线.write() = None; + *self.第三买卖线.write() = None; } /// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号" @@ -94,24 +95,25 @@ impl 中枢 { "{}:{}:{}:{}", self.文().中.标识, self.文().中.周期, - self.标识.read().unwrap(), + self.标识.read(), self.序号.load(Ordering::Relaxed) ) } /// 返回基础序列的最后一根虚线(当前离开段) pub fn 离开段(&self) -> Arc<虚线> { - Arc::clone(&self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1]) + let guard = self.基础序列.read(); + Arc::clone(&guard[guard.len() - 1]) } /// 返回中枢方向(与基础序列第一段方向相反) pub fn 方向(&self) -> 相对方向 { - self.基础序列.read().unwrap()[0].方向().翻转() + self.基础序列.read()[0].方向().翻转() } /// 中枢上沿 = min(前三段的高) pub fn 高(&self) -> f64 { - self.基础序列.read().unwrap()[..3] + self.基础序列.read()[..3] .iter() .map(|x| x.高()) .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) @@ -120,7 +122,7 @@ impl 中枢 { /// 中枢下沿 = max(前三段的低) pub fn 低(&self) -> f64 { - self.基础序列.read().unwrap()[..3] + self.基础序列.read()[..3] .iter() .map(|x| x.低()) .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) @@ -131,7 +133,6 @@ impl 中枢 { pub fn 高高(&self) -> f64 { self.基础序列 .read() - .unwrap() .iter() .map(|x| x.高()) .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) @@ -142,7 +143,6 @@ impl 中枢 { pub fn 低低(&self) -> f64 { self.基础序列 .read() - .unwrap() .iter() .map(|x| x.低()) .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) @@ -151,46 +151,47 @@ impl 中枢 { /// 返回基础序列第一段的起点分型 pub fn 文(&self) -> Arc<分型> { - Arc::clone(&self.基础序列.read().unwrap()[0].文) + Arc::clone(&self.基础序列.read()[0].文) } /// 返回基础序列最后一段的终点分型 pub fn 武(&self) -> Arc<分型> { - Arc::clone( - &*self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1] - .武 - .read() - .unwrap(), - ) + let guard = self.基础序列.read(); + Arc::clone(&*guard[guard.len() - 1].武.read()) } /// 设置第三类买卖点对应的虚线 pub fn 设置第三买卖线(&self, 线: Option>) { - *self.第三买卖线.write().unwrap() = 线; + *self.第三买卖线.write() = 线; } /// 获取序列 — 基础序列 + 第三买卖线(若有) pub fn 获取序列(&self) -> Vec> { - let mut 序列: Vec> = self.基础序列.read().unwrap().clone(); - if let Some(ref 三买) = *self.第三买卖线.read().unwrap() { + let mut 序列: Vec> = self.基础序列.read().clone(); + if let Some(ref 三买) = *self.第三买卖线.read() { 序列.push(Arc::clone(三买)); } 序列 } + /// 获取基础序列最后一个元素 + pub fn 最后(&self) -> Option> { + self.基础序列.read().last().cloned() + } + /// 返回序列化数据文本,用于调试和存储 pub fn 获取数据文本(&self) -> String { - let 第三买卖线_str = match &*self.第三买卖线.read().unwrap() { + let 第三买卖线_str = match &*self.第三买卖线.read() { Some(x) => format!("{}", x), None => "None".to_string(), }; - let 本级_第三买卖线_str = match &*self.本级_第三买卖线.read().unwrap() { + let 本级_第三买卖线_str = match &*self.本级_第三买卖线.read() { Some(x) => format!("{}", x), None => "None".to_string(), }; format!( "{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}", - self.标识.read().unwrap(), + self.标识.read(), self.序号.load(Ordering::Relaxed), self.级别.load(Ordering::Relaxed), self.文().时间戳(), @@ -204,67 +205,63 @@ impl 中枢 { /// 校验中枢合法性 pub fn _校验合法性(&self, 序列: &[Arc<虚线>]) -> bool { - let mut 有效序列 = self.基础序列.read().unwrap().clone(); + let guard = self.基础序列.read(); + let mut 有效序列 = guard.clone(); let mut 无效序列: Vec> = Vec::new(); - for 元素 in self.基础序列.read().unwrap().iter() { - if !序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(元素)) { + let 首序号 = 序列[0].序号.load(Ordering::Relaxed); + for 元素 in guard.iter() { + let idx = (元素.序号.load(Ordering::Relaxed) - 首序号) as usize; + if idx >= 序列.len() || !Arc::ptr_eq(&序列[idx], 元素) { 无效序列.push(Arc::clone(元素)); } } - if !无效序列.is_empty() { let 无效 = &无效序列[0]; - if let Some(pos) = self - .基础序列 - .read() - .unwrap() - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(无效)) - { - 有效序列 = self.基础序列.read().unwrap()[..pos].to_vec(); - } + // Python: 序号 = 线段._索引(self.基础序列, 无效) + let pos = crate::algorithm::segment::线段::_索引(&guard, 无效); + 有效序列 = guard[..pos].to_vec(); } + drop(guard); if 有效序列.len() < 3 { self.设置第三买卖线(None); - *self.本级_第三买卖线.write().unwrap() = None; + *self.本级_第三买卖线.write() = None; return false; } - *self.基础序列.write().unwrap() = 有效序列; + *self.基础序列.write() = 有效序列; let 中枢高 = self.高(); let 中枢低 = self.低(); 有效序列 = Vec::new(); - for 元素 in self.基础序列.read().unwrap().iter() { + for 元素 in self.基础序列.read().iter() { if crate::types::相对方向::分析(中枢高, 中枢低, 元素.高(), 元素.低()).是否缺口() { break; } 有效序列.push(Arc::clone(元素)); } - *self.基础序列.write().unwrap() = 有效序列; + *self.基础序列.write() = 有效序列; - if self.基础序列.read().unwrap().len() < 3 { - return false; - } - - for i in 1..self.基础序列.read().unwrap().len() { - let 前 = &self.基础序列.read().unwrap()[i - 1]; - let 后 = &self.基础序列.read().unwrap()[i]; - if !前.之后是(后) { + let 是缺口 = { + let guard = self.基础序列.read(); + if guard.len() < 3 { return false; } - } - - if !crate::types::相对方向::分析( - self.基础序列.read().unwrap()[0].高(), - self.基础序列.read().unwrap()[0].低(), - self.基础序列.read().unwrap()[2].高(), - self.基础序列.read().unwrap()[2].低(), - ) - .是否缺口() - { + for i in 1..guard.len() { + if !guard[i - 1].之后是(&guard[i]) { + return false; + } + } + crate::types::相对方向::分析( + guard[0].高(), + guard[0].低(), + guard[2].高(), + guard[2].低(), + ) + .是否缺口() + }; + if !是缺口 { let 重叠高 = self.高(); let 重叠低 = self.低(); if 重叠低 > 重叠高 { @@ -272,10 +269,12 @@ impl 中枢 { } } - let 三买线_opt = self.第三买卖线.read().unwrap().clone(); + let 三买线_opt = self.第三买卖线.read().clone(); if let Some(ref 三买线) = 三买线_opt { - if 序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(三买线)) { - if !self.基础序列.read().unwrap().last().unwrap().之后是(三买线) { + let 首序号 = 序列[0].序号.load(Ordering::Relaxed); + let idx = (三买线.序号.load(Ordering::Relaxed) - 首序号) as usize; + if idx < 序列.len() && Arc::ptr_eq(&序列[idx], 三买线) { + if !self.基础序列.read().last().unwrap().之后是(三买线) { self.设置第三买卖线(None); } else if !crate::types::相对方向::分析( self.高(), @@ -298,16 +297,16 @@ impl 中枢 { /// 完整性 — 详见教你炒股票43:有关背驰的补习课 /// 不完整时下一个中枢大概率会与当前中枢发生扩展 pub fn 完整性(&self, 虚实: &str) -> bool { - if *self.基础序列.read().unwrap()[0].标识.read().unwrap() == "笔" { - return self.第三买卖线.read().unwrap().is_some(); + if *self.基础序列.read()[0].标识.read() == "笔" { + return self.第三买卖线.read().is_some(); } - let 基础序列_ref = self.基础序列.read().unwrap(); + let 基础序列_ref = self.基础序列.read(); let 最后段 = 基础序列_ref.last().unwrap(); let 内部中枢_vec = if 虚实 == "合" { - 最后段.合_中枢序列.read().unwrap() + 最后段.合_中枢序列.read() } else { - 最后段.实_中枢序列.read().unwrap() + 最后段.实_中枢序列.read() }; for 内部中枢 in 内部中枢_vec.iter() { if crate::types::相对方向::分析( @@ -330,24 +329,19 @@ impl 中枢 { 扩展中枢: &mut Vec>, 配置: &crate::config::缠论配置, ) { - if self.基础序列.read().unwrap().len() >= 9 { + if self.基础序列.read().len() >= 9 { let mut 扩展线段: Vec> = Vec::new(); - let 基础序列_ref = self.基础序列.read().unwrap(); + let 基础序列_ref = self.基础序列.read(); crate::algorithm::segment::线段::扩展分析(&基础序列_ref, &mut 扩展线段, 配置); - 中枢::分析( - &扩展线段, - 扩展中枢, - false, - &format!("{}_扩展中枢_", self.标识.read().unwrap()), - 0, - ); + let 标识 = format!("{}_扩展中枢_", self.标识.read()); + 中枢::分析(&扩展线段, 扩展中枢, false, &标识, 0); } } /// 当前状态 — 详见教你炒股票49:利润率最大的操作模式 /// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下 pub fn 当前状态(&self) -> &str { - let 基础序列_ref = self.基础序列.read().unwrap(); + let 基础序列_ref = self.基础序列.read(); let 最后 = Arc::clone(基础序列_ref.last().unwrap()); let 尾部 = 最后.获取_武(); let 关系 = crate::types::相对方向::分析( @@ -390,7 +384,7 @@ impl 中枢 { assert!(Self::基础检查(&左, &中, &右), "中枢.创建 基础检查失败"); Self::new( 0, - format!("{}中枢<{}>", 标识, 中.标识.read().unwrap()), + format!("{}中枢<{}>", 标识, 中.标识.read()), 级别, vec![左, 中, 右], ) @@ -422,18 +416,8 @@ impl 中枢 { 待添加中枢 .序号 .store(前一个.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - let 前_last_序号 = 前一个 - .获取序列() - .last() - .unwrap() - .序号 - .load(Ordering::Relaxed); - let new_last_序号 = 待添加中枢 - .获取序列() - .last() - .unwrap() - .序号 - .load(Ordering::Relaxed); + let 前_last_序号 = 前一个.最后().unwrap().序号.load(Ordering::Relaxed); + let new_last_序号 = 待添加中枢.最后().unwrap().序号.load(Ordering::Relaxed); if 前_last_序号 > new_last_序号 { panic!( "向中枢序列尾部添加 序号错误 前last={} > new_last={}", @@ -478,11 +462,8 @@ impl 中枢 { let 右 = &虚线序列[i + 1]; if Self::基础检查(左, 中, 右) { - // Python: 序号 = 虚线序列.index(左) - let 序号 = 虚线序列 - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(左)) - .expect("中枢.分析: 左元素不在虚线序列中"); + // Python: 序号 = 线段._索引(虚线序列, 左) + let 序号: usize = crate::algorithm::segment::线段::_索引(虚线序列, 左); if 跳过首部 && (左.序号.load(Ordering::Relaxed) == 0 || 序号 == 0) { continue; } @@ -525,22 +506,16 @@ impl 中枢 { return; } - // 找到当前中枢最后一个元素在虚线序列中的位置 + // Python: 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1 let 起始索引 = { let cur = &中枢序列[当前中枢_idx]; - let 最后元素 = &cur.基础序列.read().unwrap()[cur.基础序列.read().unwrap().len() - 1]; - match 虚线序列 - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(最后元素)) - { - Some(idx) => idx + 1, - None => return, - } + let guard = cur.基础序列.read(); + crate::algorithm::segment::线段::_索引(虚线序列, &guard[guard.len() - 1]) + 1 }; let mut 中枢高 = 中枢序列[当前中枢_idx].高(); let mut 中枢低 = 中枢序列[当前中枢_idx].低(); - let mut 候选序列: Vec> = Vec::new(); + let mut 候选序列 = Vec::with_capacity(3); for 当前虚线_ref in &虚线序列[起始索引..] { let 当前虚线 = Arc::clone(当前虚线_ref); @@ -553,12 +528,7 @@ impl 中枢 { // Python: if 当前中枢.基础序列[-1].之后是(当前虚线): let needs_三买 = { let cur = &中枢序列[当前中枢_idx]; - cur.基础序列 - .read() - .unwrap() - .last() - .unwrap() - .之后是(&当前虚线) + cur.基础序列.read().last().unwrap().之后是(&当前虚线) }; if needs_三买 { 中枢序列[当前中枢_idx].设置第三买卖线(Some(当前虚线.clone())); @@ -570,17 +540,11 @@ impl 中枢 { 中枢序列[当前中枢_idx] .基础序列 .read() - .unwrap() .last() .unwrap() .之后是(&当前虚线), "中枢延伸: 不连续 {}, {}", - 中枢序列[当前中枢_idx] - .基础序列 - .read() - .unwrap() - .last() - .unwrap(), + 中枢序列[当前中枢_idx].基础序列.read().last().unwrap(), 当前虚线 ); 中枢序列[当前中枢_idx]._添加虚线(当前虚线); @@ -594,7 +558,6 @@ impl 中枢 { let 起始方向 = 中枢序列[当前中枢_idx] .基础序列 .read() - .unwrap() .last() .unwrap() .方向() @@ -628,13 +591,13 @@ impl 中枢 { ), ); } - if *self.标识.read().unwrap() != *other.标识.read().unwrap() { + if *self.标识.read() != *other.标识.read() { return ( false, format!( "中枢: [标识] 不等 A={},B={}", - self.标识.read().unwrap(), - other.标识.read().unwrap() + self.标识.read(), + other.标识.read() ), ); } @@ -649,8 +612,8 @@ impl 中枢 { ); } // 基础序列 - let a_seq = self.基础序列.read().unwrap(); - let b_seq = other.基础序列.read().unwrap(); + let a_seq = self.基础序列.read(); + let b_seq = other.基础序列.read(); if a_seq.len() != b_seq.len() { return ( false, @@ -692,16 +655,16 @@ impl 中枢 { }; 检查单个( "第三买卖线", - &self.第三买卖线.read().unwrap(), - &other.第三买卖线.read().unwrap(), + &self.第三买卖线.read(), + &other.第三买卖线.read(), 浮点容差, ) .map_err(|e| (false, e)) .ok(); 检查单个( "本级_第三买卖线", - &self.本级_第三买卖线.read().unwrap(), - &other.本级_第三买卖线.read().unwrap(), + &self.本级_第三买卖线.read(), + &other.本级_第三买卖线.read(), 浮点容差, ) .map_err(|e| (false, e)) @@ -712,21 +675,26 @@ impl 中枢 { impl std::fmt::Display for 中枢 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let 序列_str = self - .基础序列 - .read() - .unwrap() - .iter() - .map(|d| format!("{}", d)) - .collect::>() - .join(", "); + let guard = self.基础序列.read(); + let len = guard.len(); + let 序列_str = if let Some((first, rest)) = guard.split_first() { + let mut s = format!("{}", first); + for d in rest { + use std::fmt::Write; + write!(&mut s, ", {}", d).unwrap(); + } + s + } else { + String::new() + }; + drop(guard); write!( f, "{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})", - self.标识.read().unwrap(), + self.标识.read(), crate::utils::format_f64_g(self.高()), crate::utils::format_f64_g(self.低()), - self.基础序列.read().unwrap().len(), + len, 序列_str, self.文(), self.武(), @@ -851,11 +819,11 @@ mod tests { ); assert_eq!(中枢.序号.load(Ordering::Relaxed), 1); - assert_eq!(*中枢.标识.read().unwrap(), "测试中枢"); + assert_eq!(*中枢.标识.read(), "测试中枢"); assert_eq!(中枢.级别.load(Ordering::Relaxed), 1); - assert_eq!(中枢.基础序列.read().unwrap().len(), 3); - assert!(中枢.第三买卖线.read().unwrap().is_none()); - assert!(中枢.本级_第三买卖线.read().unwrap().is_none()); + assert_eq!(中枢.基础序列.read().len(), 3); + assert!(中枢.第三买卖线.read().is_none()); + assert!(中枢.本级_第三买卖线.read().is_none()); } #[test] @@ -877,16 +845,16 @@ mod tests { // RefCell 第三买卖线读写 中枢.设置第三买卖线(Some(Arc::clone(&笔1))); - assert!(中枢.第三买卖线.read().unwrap().is_some()); + assert!(中枢.第三买卖线.read().is_some()); assert_eq!( - Arc::as_ptr(中枢.第三买卖线.read().unwrap().as_ref().unwrap()), + Arc::as_ptr(中枢.第三买卖线.read().as_ref().unwrap()), Arc::as_ptr(&笔1) ); // 本级_第三买卖线 - assert!(中枢.本级_第三买卖线.read().unwrap().is_none()); - *中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&笔3)); - assert!(中枢.本级_第三买卖线.read().unwrap().is_some()); + assert!(中枢.本级_第三买卖线.read().is_none()); + *中枢.本级_第三买卖线.write() = Some(Arc::clone(&笔3)); + assert!(中枢.本级_第三买卖线.read().is_some()); } // ============================================================ @@ -906,14 +874,11 @@ mod tests { 1, vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)], ); - assert_eq!(中枢.基础序列.read().unwrap().len(), 3); + assert_eq!(中枢.基础序列.read().len(), 3); 中枢._添加虚线(Arc::clone(&笔4)); - assert_eq!(中枢.基础序列.read().unwrap().len(), 4); - assert_eq!( - Arc::as_ptr(&中枢.基础序列.read().unwrap()[3]), - Arc::as_ptr(&笔4) - ); + assert_eq!(中枢.基础序列.read().len(), 4); + assert_eq!(Arc::as_ptr(&中枢.基础序列.read()[3]), Arc::as_ptr(&笔4)); } #[test] @@ -930,14 +895,14 @@ mod tests { vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)], ); 中枢.设置第三买卖线(Some(Arc::clone(&笔1))); - *中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&笔2)); - assert!(中枢.第三买卖线.read().unwrap().is_some()); - assert!(中枢.本级_第三买卖线.read().unwrap().is_some()); + *中枢.本级_第三买卖线.write() = Some(Arc::clone(&笔2)); + assert!(中枢.第三买卖线.read().is_some()); + assert!(中枢.本级_第三买卖线.read().is_some()); 中枢._添加虚线(Arc::clone(&笔4)); // 添加虚线后第三买卖线被清除 - assert!(中枢.第三买卖线.read().unwrap().is_none()); - assert!(中枢.本级_第三买卖线.read().unwrap().is_none()); + assert!(中枢.第三买卖线.read().is_none()); + assert!(中枢.本级_第三买卖线.read().is_none()); } // ============================================================ @@ -963,15 +928,15 @@ mod tests { // 基础序列中的 Rc 指针应一致 for i in 0..3 { assert_eq!( - Arc::as_ptr(&中枢.基础序列.read().unwrap()[i]), - Arc::as_ptr(&克隆.基础序列.read().unwrap()[i]) + Arc::as_ptr(&中枢.基础序列.read()[i]), + Arc::as_ptr(&克隆.基础序列.read()[i]) ); } // 第三买卖线 Rc 指针应一致 assert_eq!( - Arc::as_ptr(中枢.第三买卖线.read().unwrap().as_ref().unwrap()), - Arc::as_ptr(克隆.第三买卖线.read().unwrap().as_ref().unwrap()) + Arc::as_ptr(中枢.第三买卖线.read().as_ref().unwrap()), + Arc::as_ptr(克隆.第三买卖线.read().as_ref().unwrap()) ); } @@ -1030,12 +995,12 @@ mod tests { // 通过 rc1 添加虚线 中枢1._添加虚线(Arc::clone(&笔4)); - assert_eq!(中枢2.基础序列.read().unwrap().len(), 4); + assert_eq!(中枢2.基础序列.read().len(), 4); // 验证共享的 Arc<虚线> 指针一致 assert_eq!( - Arc::as_ptr(&中枢1.基础序列.read().unwrap()[3]), - Arc::as_ptr(&中枢2.基础序列.read().unwrap()[3]) + Arc::as_ptr(&中枢1.基础序列.read()[3]), + Arc::as_ptr(&中枢2.基础序列.read()[3]) ); } } diff --git a/chanlun/src/algorithm/segment.rs b/chanlun/src/algorithm/segment.rs index fc88ce1..ff28fe4 100644 --- a/chanlun/src/algorithm/segment.rs +++ b/chanlun/src/algorithm/segment.rs @@ -32,9 +32,9 @@ use crate::structure::dash_line::虚线; use crate::structure::fractal_obj::分型; use crate::structure::segment_feat::线段特征; use crate::types::{分型结构, 相对方向, 缺口}; +use crate::warn; use std::sync::Arc; use std::sync::atomic::Ordering; -use tracing::warn; /// 线段 — 从笔生成线段的算法集合(静态方法命名空间) pub struct 线段; @@ -52,6 +52,17 @@ impl 线段 { // 基础操作 // ================================================================ + /// 从序号计算索引位置(O(1) — 与 Python _索引 对齐) + /// + /// 序列元素序号连续递增,因此可用 `项.序号 - 序列[0].序号` 直接得到位置。 + /// 当笔序列中元素被 pop 再 push 替换时(_弹出旧笔 → _添加新笔), + /// 新旧元素序号相同但 Arc 不同,此时 Arc::ptr_eq 会漏判,而序号算术不受影响。 + pub fn _索引(序列: &[Arc<虚线>], 项: &虚线) -> usize { + let 项序号 = 项.序号.load(Ordering::Relaxed); + let 首序号 = 序列[0].序号.load(Ordering::Relaxed); + (项序号 - 首序号) as usize + } + /// 解引用 Arc<虚线> → &虚线。内部 RwLock/Atomic 字段支持通过不可变引用修改。 fn 取段(段_rc: &Arc<虚线>) -> &虚线 { 段_rc @@ -60,70 +71,42 @@ impl 线段 { /// 向线段的基础序列中添加一笔 pub fn _添加虚线(段_rc: &Arc<虚线>, 筆: Arc<虚线>) { let 段 = Self::取段(段_rc); - if !段.基础序列.read().unwrap().is_empty() { - if !分型::判断分型( - &段.基础序列 - .read() - .unwrap() - .last() - .unwrap() - .武 - .read() - .unwrap(), - &筆.文, - "中", - ) { - panic!( - "线段.添加虚线 不连续 {} {}", - 段.基础序列.read().unwrap().last().unwrap(), - 筆 - ); + let 基础序列 = 段.基础序列.read(); + if !基础序列.is_empty() { + let 最后 = 基础序列.last().unwrap(); + if !分型::判断分型(&最后.武.read(), &筆.文, "中") { + panic!("线段.添加虚线 不连续 {} {}", 最后, 筆); } - if *段 - .基础序列 - .read() - .unwrap() - .last() - .unwrap() - .标识 - .read() - .unwrap() - != *筆.标识.read().unwrap() - { + if *最后.标识.read() != *筆.标识.read() { panic!( "线段.添加虚线 标识不符 {} {}", - *段.基础序列 - .read() - .unwrap() - .last() - .unwrap() - .标识 - .read() - .unwrap(), - 筆.标识.read().unwrap() + *最后.标识.read(), + 筆.标识.read() ); } } - 段.基础序列.write().unwrap().push(筆); + drop(基础序列); + 段.基础序列.write().push(筆); } /// 更新线段的终点分型 pub fn _武斗(段_rc: &Arc<虚线>, 武: &Arc<分型>, 行号: u32) { let 段 = Self::取段(段_rc); - if Arc::as_ptr(&*段.武.read().unwrap()) == Arc::as_ptr(武) { + if Arc::ptr_eq(&*段.武.read(), 武) { return; } - if 段.武.read().unwrap().分型特征值 == 武.分型特征值 - && 段.武.read().unwrap().时间戳() != 武.时间戳() + let 段_武 = 段.武.read(); + if 段_武.分型特征值 == 武.分型特征值 && 段_武.时间戳() != 武.时间戳() { warn!( "{}.武斗[{}], 发现特征值相等但时间戳不同, {}, {}", - 段.标识.read().unwrap(), + 段.标识.read(), 行号, - 段.武.read().unwrap(), + 段_武, 武 ); } + drop(段_武); if 段.文.结构 == 武.结构 { panic!("文武结构相同 {} {} {}", 行号, 段.文, 武); } @@ -143,19 +126,19 @@ impl 线段 { if 武.分型特征值 < 段.文.分型特征值 { panic!( "向上线段, 结束点小于起点 {} {} {}", - 段.标识.read().unwrap(), + 段.标识.read(), 段.文, 武 ); } - if 段.武.read().unwrap().分型特征值 > 武.分型特征值 - && 段.模式.read().unwrap().as_str() == "文武" + let 段_武 = 段.武.read(); + if 段_武.分型特征值 > 武.分型特征值 && 段.模式.read().as_str() == "文武" { warn!( "{}.武斗[{}] 出现回退 从 {} ==>>> {}", - 段.标识.read().unwrap(), + 段.标识.read(), 行号, - 段.武.read().unwrap(), + 段_武, 武 ); } @@ -163,42 +146,32 @@ impl 线段 { if 武.分型特征值 > 段.文.分型特征值 { panic!( "向下线段, 结束点大于起点 {} {} {}", - 段.标识.read().unwrap(), + 段.标识.read(), 段.文, 武 ); } - if 段.武.read().unwrap().分型特征值 < 武.分型特征值 - && 段.模式.read().unwrap().as_str() == "文武" + let 段_武 = 段.武.read(); + if 段_武.分型特征值 < 武.分型特征值 && 段.模式.read().as_str() == "文武" { warn!( "{}.武斗[{}] 出现回退 从 {} ==>>> {}", - 段.标识.read().unwrap(), + 段.标识.read(), 行号, - 段.武.read().unwrap(), + 段_武, 武 ); } } - *段.武.write().unwrap() = Arc::clone(武); + *段.武.write() = Arc::clone(武); } /// 武终 — 线段终结时设置终点 pub fn _武终(段_rc: &Arc<虚线>, 行号: u32) { let 武 = { let 段 = Self::取段(段_rc); - if 段.模式.read().unwrap().as_str() != "文武" { - Some(Arc::clone( - &*段 - .基础序列 - .read() - .unwrap() - .last() - .unwrap() - .武 - .read() - .unwrap(), - )) + if 段.模式.read().as_str() != "文武" { + Some(Arc::clone(&*段.基础序列.read().last().unwrap().武.read())) } else { None } @@ -211,9 +184,12 @@ impl 线段 { /// 验证序列 — 截断无效尾部 pub fn _验证序列(段_rc: &Arc<虚线>, 序列: &[Arc<虚线>]) { let 段 = Self::取段(段_rc); - let mut 基础序列: Vec> = Vec::new(); - for 元素 in 段.基础序列.read().unwrap().iter() { - if !序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(元素)) { + let guard = 段.基础序列.read(); + let 首序号 = 序列[0].序号.load(Ordering::Relaxed); + let mut 基础序列: Vec> = Vec::with_capacity(guard.len()); + for 元素 in guard.iter() { + let idx = (元素.序号.load(Ordering::Relaxed) - 首序号) as usize; + if idx >= 序列.len() || !Arc::ptr_eq(&序列[idx], 元素) { break; } if !基础序列.is_empty() && !基础序列.last().unwrap().之后是(元素) { @@ -225,15 +201,19 @@ impl 线段 { if 基础序列.len().is_multiple_of(2) { 基础序列.pop(); } - *段.基础序列.write().unwrap() = 基础序列; + drop(guard); + *段.基础序列.write() = 基础序列; } /// 序列重置 — 截取到序列中的有效部分 pub fn 序列重置(段_rc: &Arc<虚线>, 序列: &[Arc<虚线>]) { let 段 = Self::取段(段_rc); - let mut 基础序列: Vec> = Vec::new(); - for 元素 in 段.基础序列.read().unwrap().iter() { - if !序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(元素)) { + let guard = 段.基础序列.read(); + let 首序号 = 序列[0].序号.load(Ordering::Relaxed); + let mut 基础序列: Vec> = Vec::with_capacity(guard.len()); + for 元素 in guard.iter() { + let idx = (元素.序号.load(Ordering::Relaxed) - 首序号) as usize; + if idx >= 序列.len() || !Arc::ptr_eq(&序列[idx], 元素) { break; } if !基础序列.is_empty() && !基础序列.last().unwrap().之后是(元素) { @@ -241,8 +221,9 @@ impl 线段 { } 基础序列.push(Arc::clone(元素)); } - *段.基础序列.write().unwrap() = 基础序列; - 段.特征序列.write().unwrap()[2] = None; + drop(guard); + *段.基础序列.write() = 基础序列; + 段.特征序列.write()[2] = None; } // ================================================================ @@ -292,7 +273,7 @@ impl 线段 { /// 小阳: 向上线段 /// 少阴: 向下线段 pub fn 四象(段: &虚线) -> String { - if 段.前一缺口.read().unwrap().is_some() { + if 段.前一缺口.read().is_some() { if 段.方向() == 相对方向::向上 { "老阳".into() } else { @@ -307,10 +288,10 @@ impl 线段 { /// 获取缺口 — 从特征序列第一二元素之间检测缺口 pub fn 获取缺口(段: &虚线) -> Option<缺口> { - if 段.模式.read().unwrap().as_str() != "文武" { + if 段.模式.read().as_str() != "文武" { return None; } - let 特序 = 段.特征序列.read().unwrap(); + let 特序 = 段.特征序列.read(); let 左 = 特序[0].as_ref()?; let 中 = 特序[1].as_ref()?; let 相对关系 = 相对方向::分析(左.高(), 左.低(), 中.高(), 中.低()); @@ -325,12 +306,8 @@ impl 线段 { /// 特征分型终结 — 检查特征序列是否形成正常分型终结 pub fn 特征分型终结(段: &虚线) -> bool { - let 特征序列 = 线段特征::静态分析( - &段.基础序列.read().unwrap(), - 段.方向(), - &Self::四象(段), - false, - ); + let 特征序列 = + 线段特征::静态分析(&段.基础序列.read(), 段.方向(), &Self::四象(段), false); if 特征序列.len() >= 3 { let idx = 特征序列.len(); if let Some(结构) = 分型结构::分析( @@ -352,7 +329,7 @@ impl 线段 { /// 特征序列状态 — 返回三个特征序列元素是否为 Some pub fn 特征序列状态(段: &虚线) -> (bool, bool, bool) { - let 特序 = 段.特征序列.read().unwrap(); + let 特序 = 段.特征序列.read(); (特序[0].is_some(), 特序[1].is_some(), 特序[2].is_some()) } @@ -361,7 +338,7 @@ impl 线段 { 段_rc: &Arc<虚线>, 序列: Vec>>, 行号: u32 ) { let 段 = Self::取段(段_rc); - if 段.模式.read().unwrap().as_str() != "文武" { + if 段.模式.read().as_str() != "文武" { return; } @@ -374,29 +351,25 @@ impl 线段 { let 左 = 序列[0].clone(); let 中 = 序列[1].clone(); let 右 = 序列[2].clone(); - *段.特征序列.write().unwrap() = vec![左, 中, 右]; + *段.特征序列.write() = vec![左, 中, 右]; - if let Some(ref 右特征) = 段.特征序列.read().unwrap()[2] { - let mut 基础序列: Vec> = Vec::new(); + if let Some(ref 右特征) = 段.特征序列.read()[2] { + let guard = 段.基础序列.read(); + let mut 基础序列: Vec> = Vec::with_capacity(guard.len()); let 右尾 = 右特征.基础序列.last().expect("特征序列元素不应为空"); - if !段 - .基础序列 - .read() - .unwrap() - .iter() - .any(|x| Arc::as_ptr(x) == Arc::as_ptr(右尾)) - { + if !guard.iter().any(|x| Arc::ptr_eq(x, 右尾)) { panic!("右特征最后一个元素不在基础序列中"); } - for 元素 in 段.基础序列.read().unwrap().iter() { + for 元素 in guard.iter() { 基础序列.push(Arc::clone(元素)); - if Arc::as_ptr(元素) == Arc::as_ptr(右尾) { + if Arc::ptr_eq(元素, 右尾) { break; } } + drop(guard); if 基础序列.len() >= 6 && 基础序列.len().is_multiple_of(2) { - *段.基础序列.write().unwrap() = 基础序列; + *段.基础序列.write() = 基础序列; } else { panic!("设置特征序列: 基础序列长度不足或非偶数"); } @@ -408,18 +381,23 @@ impl 线段 { // Compute new feature sequence, then delegate to 设置特征序列 for truncation let 序列: Vec>> = { let 段 = &**段_rc; - if 段.模式.read().unwrap().as_str() != "文武" { + if 段.模式.read().as_str() != "文武" { return; } - let mut 基础序列 = 段.基础序列.read().unwrap().clone(); - if let Some(ref 前结束) = *段.前一结束位置.read().unwrap() - && let Some(idx) = 基础序列 - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(前结束)) - && idx > 0 - { - 基础序列 = 基础序列[idx - 1..].to_vec(); - } + // 仅在需要时克隆(避免全量克隆后立即截断的双重分配) + let 基础序列 = { + let 完整 = 段.基础序列.read(); + if let Some(ref 前结束) = *段.前一结束位置.read() { + let idx = 线段::_索引(&完整, 前结束); + if idx > 0 && idx < 完整.len() { + 完整[idx - 1..].to_vec() + } else { + 完整.clone() + } + } else { + 完整.clone() + } + }; let 四象 = Self::四象(段); let 特征序列 = 线段特征::静态分析( @@ -459,13 +437,13 @@ impl 线段 { /// 查找贯穿伤 — 基础序列中穿透文分型特征值的笔 pub fn 查找贯穿伤(段: &虚线) -> Option> { - for 贯穿伤 in 段.基础序列.read().unwrap().iter().skip(3) { + for 贯穿伤 in 段.基础序列.read().iter().skip(3) { if 段.方向().是否向上() { - if 贯穿伤.武.read().unwrap().分型特征值 < 段.文.分型特征值 { + if 贯穿伤.武.read().分型特征值 < 段.文.分型特征值 { return Some(Arc::clone(贯穿伤)); } } else { - if 贯穿伤.武.read().unwrap().分型特征值 > 段.文.分型特征值 { + if 贯穿伤.武.read().分型特征值 > 段.文.分型特征值 { return Some(Arc::clone(贯穿伤)); } } @@ -479,36 +457,29 @@ impl 线段 { /// 分割序列 — 将线段的基础序列分为前、后、第三买卖线、贯穿伤 pub fn 分割序列(段: &虚线, 所属中枢: Option<&中枢>) -> 分割结果 { - if 段.模式.read().unwrap().as_str() != "文武" { - return ( - 段.基础序列.read().unwrap().clone(), - Vec::new(), - Vec::new(), - None, - ); + if 段.模式.read().as_str() != "文武" { + return (段.基础序列.read().clone(), Vec::new(), Vec::new(), None); } - let mut 前: Vec> = Vec::new(); - let mut 后: Vec> = Vec::new(); + let len = 段.基础序列.read().len(); + let mut 前 = Vec::with_capacity(len); + let mut 后 = Vec::with_capacity(len / 2); let mut 第三买卖线: Vec> = Vec::new(); let mut 贯穿伤: Option> = None; - for 筆 in 段.基础序列.read().unwrap().iter() { + for 筆 in 段.基础序列.read().iter() { if 前.is_empty() { 前.push(Arc::clone(筆)); continue; } - if Arc::as_ptr(&*前.last().unwrap().武.read().unwrap()) - != Arc::as_ptr(&*段.武.read().unwrap()) - && 后.is_empty() - { + if !Arc::ptr_eq(&*前.last().unwrap().武.read(), &*段.武.read()) && 后.is_empty() { 前.push(Arc::clone(筆)); } if !后.is_empty() { 后.push(Arc::clone(筆)); } - if Arc::as_ptr(&筆.文) == Arc::as_ptr(&*段.武.read().unwrap()) { + if Arc::ptr_eq(&筆.文, &*段.武.read()) { 后.push(Arc::clone(筆)); } } @@ -516,11 +487,11 @@ impl 线段 { let mut 状态 = None; if let Some(中枢) = 所属中枢 { - *中枢.本级_第三买卖线.write().unwrap() = None; + *中枢.本级_第三买卖线.write() = None; let 尾部 = if let Some(后笔) = 后.last() { - 后笔.武.read().unwrap().clone() + 后笔.武.read().clone() } else { - 段.武.read().unwrap().clone() + 段.武.read().clone() }; if 中枢.高() >= 尾部.分型特征值 && 尾部.分型特征值 >= 中枢.低() { @@ -535,7 +506,7 @@ impl 线段 { if 状态 == Some("中枢之上") { let 中枢高 = 所属中枢.as_ref().unwrap().高(); let 中枢低 = 所属中枢.as_ref().unwrap().低(); - for 筆 in 段.基础序列.read().unwrap().iter().rev() { + for 筆 in 段.基础序列.read().iter().rev() { if 筆.方向() == 相对方向::向下 { let 关系 = 相对方向::分析(中枢高, 中枢低, 筆.高(), 筆.低()); if 关系 == 相对方向::向上缺口 { @@ -548,7 +519,7 @@ impl 线段 { } if 状态 == Some("中枢之下") { - for 筆 in 段.基础序列.read().unwrap().iter().rev() { + for 筆 in 段.基础序列.read().iter().rev() { if 筆.方向() == 相对方向::向上 { let 关系 = 相对方向::分析( 所属中枢.as_ref().unwrap().高(), @@ -568,17 +539,17 @@ impl 线段 { if !第三买卖线.is_empty() { 第三买卖线.reverse(); if let Some(中枢) = 所属中枢 { - *中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&第三买卖线[0])); + *中枢.本级_第三买卖线.write() = Some(Arc::clone(&第三买卖线[0])); } } if !后.is_empty() { if 段.方向().是否向上() { - if 后[0].武.read().unwrap().分型特征值 < 段.文.分型特征值 { + if 后[0].武.read().分型特征值 < 段.文.分型特征值 { 贯穿伤 = Some(Arc::clone(&后[0])); } } else { - if 后[0].武.read().unwrap().分型特征值 > 段.文.分型特征值 { + if 后[0].武.read().分型特征值 > 段.文.分型特征值 { 贯穿伤 = Some(Arc::clone(&后[0])); } } @@ -590,10 +561,10 @@ impl 线段 { /// 刷新 — 完整刷新线段的特征序列和内部中枢 pub fn _刷新(段_rc: &Arc<虚线>, 配置: &缠论配置) { let 段 = Self::取段(段_rc); - if 段.模式.read().unwrap().as_str() != "文武" { + if 段.模式.read().as_str() != "文武" { return; } - if 段.基础序列.read().unwrap().is_empty() { + if 段.基础序列.read().is_empty() { warn!(" 线段.刷新 基础序列为空"); return; } @@ -603,7 +574,7 @@ impl 线段 { // After 刷新特征序列, work with the updated segment let (武斗_武文, 特征后一笔_opt) = { let 段2 = Self::取段(段_rc); - let 特序_ref = 段2.特征序列.read().unwrap(); + let 特序_ref = 段2.特征序列.read(); let 有效特征序列: Vec<&Arc<线段特征>> = 特序_ref.iter().filter_map(|x| x.as_ref()).collect(); @@ -612,19 +583,17 @@ impl 线段 { } else if !有效特征序列.is_empty() { let 最近特征 = 有效特征序列[有效特征序列.len() - 1]; - let 特征后一笔 = if 最近特征.基础序列.last().map(|x| { - 段2.基础序列 - .read() - .unwrap() - .iter() - .any(|b| Arc::as_ptr(b) == Arc::as_ptr(x)) - }) == Some(true) + let 特征后一笔 = if 最近特征 + .基础序列 + .last() + .map(|x| 段2.基础序列.read().iter().any(|b| Arc::ptr_eq(b, x))) + == Some(true) { Some(Arc::clone(最近特征.基础序列.last().unwrap())) } else { 笔::以武会友( - &段2.基础序列.read().unwrap(), - &最近特征.基础序列.last().unwrap().武.read().unwrap(), + &段2.基础序列.read(), + &最近特征.基础序列.last().unwrap().武.read(), ) }; @@ -646,19 +615,16 @@ impl 线段 { } else if let Some(特征后一笔) = 特征后一笔_opt { let 武斗候选 = { let 段2 = Self::取段(段_rc); - if let Some(序号) = 段2 - .基础序列 - .read() - .unwrap() - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(&特征后一笔)) - { - if 序号 < 段2.基础序列.read().unwrap().len() - 1 { - let 下一笔 = Arc::clone(&段2.基础序列.read().unwrap()[序号 + 1]); + let 基础序列_ref = 段2.基础序列.read(); + // Python: 序号 = cls._索引(段.基础序列, 特征后一笔) + let 序号 = 线段::_索引(&基础序列_ref, &特征后一笔); + if 序号 < 基础序列_ref.len() { + if 序号 < 段2.基础序列.read().len() - 1 { + let 下一笔 = Arc::clone(&段2.基础序列.read()[序号 + 1]); if (段2.方向() == 相对方向::向上 && 段2.高() <= 下一笔.高()) || (段2.方向() == 相对方向::向下 && 段2.低() >= 下一笔.低()) { - Some(下一笔.武.read().unwrap().clone()) + Some(下一笔.武.read().clone()) } else { None } @@ -680,23 +646,15 @@ impl 线段 { /// 获取内部中枢序列 — 内部实现 fn 获取内部中枢序列_内部(段: &虚线, _配置: &缠论配置) -> 中枢序列组 { - if 段.模式.read().unwrap().as_str() != "文武" { + if 段.模式.read().as_str() != "文武" { 中枢::分析( - &段.基础序列.read().unwrap(), - &mut 段.合_中枢序列.write().unwrap(), + &段.基础序列.read(), + &mut 段.合_中枢序列.write(), true, - &format!( - "{}_{}_合_", - 段.标识.read().unwrap(), - 段.序号.load(Ordering::Relaxed) - ), + &format!("{}_{}_合_", 段.标识.read(), 段.序号.load(Ordering::Relaxed)), 0, ); - return ( - Vec::new(), - Vec::new(), - 段.合_中枢序列.read().unwrap().clone(), - ); + return (Vec::new(), Vec::new(), 段.合_中枢序列.read().clone()); } // Use 分割序列 to get前/后 @@ -704,42 +662,30 @@ impl 线段 { 中枢::分析( &前, - &mut 段.实_中枢序列.write().unwrap(), + &mut 段.实_中枢序列.write(), true, - &format!( - "{}_{}_实_", - 段.标识.read().unwrap(), - 段.序号.load(Ordering::Relaxed) - ), + &format!("{}_{}_实_", 段.标识.read(), 段.序号.load(Ordering::Relaxed)), 0, ); 中枢::分析( &后, - &mut 段.虚_中枢序列.write().unwrap(), + &mut 段.虚_中枢序列.write(), true, - &format!( - "{}_{}_虚_", - 段.标识.read().unwrap(), - 段.序号.load(Ordering::Relaxed) - ), + &format!("{}_{}_虚_", 段.标识.read(), 段.序号.load(Ordering::Relaxed)), 0, ); 中枢::分析( - &段.基础序列.read().unwrap(), - &mut 段.合_中枢序列.write().unwrap(), + &段.基础序列.read(), + &mut 段.合_中枢序列.write(), true, - &format!( - "{}_{}_合_", - 段.标识.read().unwrap(), - 段.序号.load(Ordering::Relaxed) - ), + &format!("{}_{}_合_", 段.标识.read(), 段.序号.load(Ordering::Relaxed)), 0, ); ( - 段.虚_中枢序列.read().unwrap().clone(), - 段.实_中枢序列.read().unwrap().clone(), - 段.合_中枢序列.read().unwrap().clone(), + 段.虚_中枢序列.read().clone(), + 段.实_中枢序列.read().clone(), + 段.合_中枢序列.read().clone(), ) } @@ -760,20 +706,22 @@ impl 线段 { 线段序列: &mut Vec>, mut 待添加线段: Arc<虚线>, _配置: &缠论配置, - 行号: String, + 行号: u32, + 层级: i64, ) { { let seg = Arc::make_mut(&mut 待添加线段); - *seg.模式.write().unwrap() = "文武".into(); + *seg.模式.write() = "文武".into(); if !线段序列.is_empty() { if let Some(前一个) = 线段序列.last() && !前一个.之后是(seg) { panic!( - "线段.向序列中添加 不连续[{}] {} {}", + "线段.向序列中添加 不连续[{}, {}] {} {}", 行号, - 前一个.武.read().unwrap(), + 层级, + 前一个.武.read(), seg.文 ); } @@ -781,37 +729,39 @@ impl 线段 { let 之前线段 = 线段序列.last().unwrap(); assert!( - 之前线段.特征序列.read().unwrap()[2].is_some() + 之前线段.特征序列.read()[2].is_some() || 之前线段.短路修正.load(Ordering::Relaxed), - "线段._向序列中添加[{}], 之前线段.右 = None {}", + "线段._向序列中添加[{}, {}], 之前线段.右 = None {}", 行号, + 层级, 之前线段 ); - if !seg.基础序列.read().unwrap().iter().any(|x| { - Arc::as_ptr(x) == Arc::as_ptr(之前线段.基础序列.read().unwrap().last().unwrap()) - }) && !之前线段.短路修正.load(Ordering::Relaxed) + if !seg + .基础序列 + .read() + .iter() + .any(|x| Arc::ptr_eq(x, 之前线段.基础序列.read().last().unwrap())) + && !之前线段.短路修正.load(Ordering::Relaxed) { panic!( - "线段._向序列中添加[{}], 之前线段[-1] not in 待添加虚线! {}", - 行号, 之前线段 + "线段._向序列中添加[{}, {}], 之前线段[-1] not in 待添加虚线! {}", + 行号, 层级, 之前线段 ); } seg.序号 .store(之前线段.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - *seg.前一缺口.write().unwrap() = if 之前线段.短路修正.load(Ordering::Relaxed) - { + *seg.前一缺口.write() = if 之前线段.短路修正.load(Ordering::Relaxed) { None } else { Self::获取缺口(之前线段) }; - *seg.前一结束位置.write().unwrap() = Some(Arc::clone( - 之前线段.基础序列.read().unwrap().last().unwrap(), - )); + *seg.前一结束位置.write() = + Some(Arc::clone(之前线段.基础序列.read().last().unwrap())); if ["老阴", "老阳"].contains(&Self::四象(之前线段).as_str()) { - *seg.前一缺口.write().unwrap() = None; + *seg.前一缺口.write() = None; } } } @@ -823,35 +773,37 @@ impl 线段 { 线段序列: &mut Vec>, 待弹出线段: &Arc<虚线>, _配置: &缠论配置, - 行号: String, + 行号: u32, + 层级: i64, ) -> Option> { if 线段序列.is_empty() { return None; } - if Arc::as_ptr(线段序列.last().unwrap()) != Arc::as_ptr(待弹出线段) { + if !Arc::ptr_eq(线段序列.last().unwrap(), 待弹出线段) { panic!("线段._从序列中删除 弹出数据不在列表中 {}", 待弹出线段); } { - let 左 = &待弹出线段.特征序列.read().unwrap()[0]; - let 中 = &待弹出线段.特征序列.read().unwrap()[1]; - let 右 = &待弹出线段.特征序列.read().unwrap()[2]; + let 特序 = 待弹出线段.特征序列.read(); + let 左 = &特序[0]; + let 中 = &特序[1]; + let 右 = &特序[2]; if let (Some(l), Some(m), Some(r)) = (左, 中, 右) && let Some(结构) = 分型结构::分析(&**l, &**m, &**r, true, true) && matches!(结构, 分型结构::顶 | 分型结构::底) && !相对方向::分析(l.高(), l.低(), m.高(), m.低()).是否缺口() { warn!( - "[警告<{}>]: 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {}", - 行号, 待弹出线段 + "[警告<{}, {}>]: 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {}", + 行号, 层级, 待弹出线段 ); } } let 弹出 = 线段序列.pop().unwrap(); 弹出.有效性.store(false, Ordering::Relaxed); - *弹出.前一结束位置.write().unwrap() = None; + *弹出.前一结束位置.write() = None; Some(弹出) } @@ -865,13 +817,10 @@ impl 线段 { 线段序列: &mut Vec>, 配置: &缠论配置, 层级: i64 ) -> bool { let 当前线段 = Arc::clone(线段序列.last().unwrap()); - assert!( - !当前线段.基础序列.read().unwrap().is_empty(), - "缺口突破: 当前线段.基础序列为空!" - ); - let 当前虚线 = Arc::clone( - &当前线段.基础序列.read().unwrap()[当前线段.基础序列.read().unwrap().len() - 1], - ); + let 基础序列 = 当前线段.基础序列.read(); + assert!(!基础序列.is_empty(), "缺口突破: 当前线段.基础序列为空!"); + let 当前虚线 = Arc::clone(&基础序列[基础序列.len() - 1]); + drop(基础序列); let 四象 = Self::四象(&当前线段); let 同向 = 当前虚线.方向() == 当前线段.方向(); @@ -886,7 +835,7 @@ impl 线段 { } // 条件3:当前线段特征序列[2]必须为None - if 当前线段.特征序列.read().unwrap()[2].is_some() { + if 当前线段.特征序列.read()[2].is_some() { return false; } @@ -906,18 +855,19 @@ impl 线段 { } // 执行修正 - let 序列 = 当前线段.基础序列.read().unwrap().clone(); + let 序列 = 当前线段.基础序列.read().clone(); Self::_弹出线段( 线段序列, &Arc::clone(线段序列.last().unwrap()), 配置, - format!("{}, {}", line!(), 层级), + line!(), + 层级, ); assert!(!线段序列.is_empty(), "缺口突破: 线段序列为第二次空!"); let 当前线段 = Arc::clone(线段序列.last().unwrap()); - assert!(当前线段.特征序列.read().unwrap()[2].is_some()); + assert!(当前线段.特征序列.read()[2].is_some()); let (前, _, _, _) = Self::分割序列(&当前线段, None); let mut 当前线段基础序列 = 前; let 序列首 = Arc::clone(&序列[0]); @@ -928,7 +878,7 @@ impl 线段 { 当前线段基础序列.extend(序列); let idx = 线段序列.len() - 1; - *线段序列[idx].基础序列.write().unwrap() = 当前线段基础序列.clone(); + *线段序列[idx].基础序列.write() = 当前线段基础序列.clone(); Self::_刷新(&线段序列[idx], 配置); true } @@ -945,7 +895,7 @@ impl 线段 { // 外层条件 if !(配置.线段_非缺口下穿刺 && (四象 == "小阳" || 四象 == "少阴") - && 当前线段.特征序列.read().unwrap()[2].is_none()) + && 当前线段.特征序列.read()[2].is_none()) { return false; } @@ -958,16 +908,12 @@ impl 线段 { let 贯穿伤 = 贯穿伤.unwrap(); // 切割基础序列 - let 贯穿伤_idx = 当前线段 - .基础序列 - .read() - .unwrap() - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(&贯穿伤)); - - assert!(贯穿伤_idx.is_some(), "非缺口下穿刺: 贯穿伤不在基础序列中!"); - let 基础序列: Vec> = - 当前线段.基础序列.read().unwrap()[贯穿伤_idx.unwrap()..].to_vec(); + // Python: 基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :] + let 贯穿伤_idx = { + let guard = 当前线段.基础序列.read(); + 线段::_索引(&guard, &贯穿伤) + }; + let 基础序列: Vec> = 当前线段.基础序列.read()[贯穿伤_idx..].to_vec(); // 长度条件 if !(基础序列.len() == 4 && 线段序列.len() >= 2) { @@ -986,82 +932,60 @@ impl 线段 { // 执行修正 warn!( - "[警告<{}, {}>]: 线段.修复贯穿伤 {} {:?}", + "[警告<{}, {}>]: {}.修复贯穿伤, 序号:{} {} [{}]", line!(), 层级, + 当前线段.标识.read(), + 当前线段.序号.load(Ordering::Relaxed), 贯穿伤, 基础序列 + .iter() + .map(|s| format!("{}", s)) + .collect::>() + .join(", ") ); // Reassign to full copy (matching Python pattern) - let 基础序列 = 当前线段.基础序列.read().unwrap().clone(); + let 基础序列 = 当前线段.基础序列.read().clone(); Self::_弹出线段( 线段序列, &Arc::clone(线段序列.last().unwrap()), 配置, - format!("{}, {}", line!(), 层级), + line!(), + 层级, ); assert!(!线段序列.is_empty(), "非缺口下穿刺: 第二次线段序列为空!"); - assert!( - 基础序列.iter().any(|x| { - Arc::as_ptr(x) - == Arc::as_ptr( - 线段序列 - .last() - .unwrap() - .基础序列 - .read() - .unwrap() - .last() - .unwrap(), - ) - }), - "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" - ); - let 开始序号_opt; - let 待添加元素: Vec>; - { - let idx = 线段序列.len() - 1; - let cur = Arc::make_mut(&mut 线段序列[idx]); - cur.特征序列.write().unwrap()[2] = None; - - let 开始笔 = Arc::clone(cur.基础序列.read().unwrap().last().unwrap()); - let 开始序号 = 基础序列 - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(&开始笔)); - - 开始序号_opt = 开始序号; - if let Some(序号) = 开始序号 { - 待添加元素 = 基础序列[序号 + 1..].to_vec(); - } else { - 待添加元素 = Vec::new(); - } - } - - if 开始序号_opt.is_some() { - let idx = 线段序列.len() - 1; - let seg_rc = Arc::clone(&线段序列[idx]); - for 临时虚线 in &待添加元素 { - Self::_添加虚线(&seg_rc, Arc::clone(临时虚线)); - } - 线段序列[idx] = seg_rc; - } let idx = 线段序列.len() - 1; + 线段序列[idx].特征序列.write()[2] = None; + + // Python: if 当前线段.基础序列[-1] not in 基础序列: 序号 = 0 + // else: 序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1 + let 序号 = { + let 开始笔 = Arc::clone(线段序列[idx].基础序列.read().last().unwrap()); + if !基础序列.iter().any(|x| Arc::ptr_eq(x, &开始笔)) { + crate::error!("非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"); + 0 + } else { + 线段::_索引(&基础序列, &开始笔) + 1 + } + }; + + for 筆 in 基础序列.iter().skip(序号) { + Self::_添加虚线(&线段序列[idx], Arc::clone(筆)); + } Self::_刷新(&线段序列[idx], 配置); - let 当前线段 = Arc::clone(&线段序列[idx]); - 当前线段.短路修正.store(true, Ordering::Relaxed); - if 当前线段.特征序列.read().unwrap()[2].is_some() { + 线段序列[idx].短路修正.store(true, Ordering::Relaxed); + if 线段序列[idx].特征序列.read()[2].is_some() { let 段 = 虚线::创建线段(&[Arc::clone(&左), Arc::clone(&中), Arc::clone(&右)]); let 段_rc = Arc::new(段); - Self::_添加线段(线段序列, 段_rc, 配置, format!("{}, {}", line!(), 层级)); + Self::_添加线段(线段序列, 段_rc, 配置, line!(), 层级); // Set feature sequence [0] let 新段 = Self::取段(线段序列.last_mut().unwrap()); - 新段.特征序列.write().unwrap()[0] = - Some(Arc::new(线段特征::新建(vec![中], 新段.方向()))); + 新段.特征序列.write()[0] = Some(Arc::new(线段特征::新建(vec![中], 新段.方向()))); } true @@ -1082,7 +1006,7 @@ impl 线段 { if !(配置.线段_缺口后紧急修正 && !配置.线段_特征序列忽视老阴老阳 && (四象 == "小阳" || 四象 == "少阴") - && 当前线段.特征序列.read().unwrap()[2].is_none()) + && 当前线段.特征序列.read()[2].is_none()) { return false; } @@ -1134,7 +1058,7 @@ impl 线段 { let 新段 = 虚线::创建线段(&基础序列); let 新段_rc = Arc::new(新段); - Self::_添加线段(线段序列, 新段_rc, 配置, format!("{}, {}", line!(), 层级)); + Self::_添加线段(线段序列, 新段_rc, 配置, line!(), 层级); true } @@ -1147,7 +1071,7 @@ impl 线段 { let 当前线段 = Arc::clone(线段序列.last().unwrap()); // 条件1 - if !(配置.线段_修正 && 当前线段.基础序列.read().unwrap().len() >= 9) { + if !(配置.线段_修正 && 当前线段.基础序列.read().len() >= 9) { return false; } @@ -1179,20 +1103,17 @@ impl 线段 { let 新段1_rc = Arc::new(新段1); // Set 短路修正 新段1_rc.短路修正.store(true, Ordering::Relaxed); - Self::_添加线段(线段序列, 新段1_rc, 配置, format!("{}, {}", line!(), 层级)); + Self::_添加线段(线段序列, 新段1_rc, 配置, line!(), 层级); if ["老阴", "老阳"].contains(&Self::四象(&当前线段).as_str()) { - *Self::取段(线段序列.last_mut().unwrap()) - .前一缺口 - .write() - .unwrap() = None; + *Self::取段(线段序列.last_mut().unwrap()).前一缺口.write() = None; } // 第二个新段 let start = 之后基础序列.len() - 3; let 新段2 = 虚线::创建线段(&之后基础序列[start..]); let 新段2_rc = Arc::new(新段2); - Self::_添加线段(线段序列, 新段2_rc, 配置, format!("{}, {}", line!(), 层级)); + Self::_添加线段(线段序列, 新段2_rc, 配置, line!(), 层级); true } @@ -1204,6 +1125,7 @@ impl 线段 { /// 分析 — 从笔序列生成线段序列 /// /// 使用显式栈(loop + continue)模拟 Python 的递归调用,避免栈溢出。 + /// 线段划分核心递归算法 — 从笔序列递归生成线段(与 Python 线段.分析 对齐) pub fn 分析( 笔序列: &[Arc<虚线>], 线段序列: &mut Vec>, @@ -1211,246 +1133,202 @@ impl 线段 { 层级: i64, 关系序列: &[相对方向], ) { - let mut 当前层级 = 层级; + // 递归深度守卫(Python: if 层级 > 256: return) + if 层级 > 256 { + warn!("线段.分析 递归深度超出 256"); + return; + } - loop { - if 当前层级 > 256 { - warn!("线段.分析 递归深度超出 256"); - return; + // Python: try: 笔序列[2] except IndexError: return + if 笔序列.len() < 3 { + return; + } + + // -------------------- 1. 初始化第一个线段 -------------------- + // Python: if not 线段序列: for i in range(1, len-1): ... + if 线段序列.is_empty() { + for i in 1..笔序列.len() - 1 { + let 左 = &笔序列[i - 1]; + let 中 = &笔序列[i]; + let 右 = &笔序列[i + 1]; + + if !Self::_基础判断(左, 中, 右, 关系序列) { + continue; + } + let 段 = 虚线::创建线段(&[Arc::clone(左), Arc::clone(中), Arc::clone(右)]); + let 段_rc = Arc::new(段); + Self::_添加线段(线段序列, 段_rc, 配置, line!(), 层级); + // Python: 段.特征序列[0] = 线段特征.新建([中], 段.方向) + 线段序列.last().unwrap().特征序列.write()[0] = Some(Arc::new(线段特征::新建( + vec![Arc::clone(中)], + 线段序列.last().unwrap().方向(), + ))); + break; } - - if 笔序列.len() < 3 { - return; - } - - // ---- 1. 初始化第一个线段 ---- + // Python: if not 线段序列: return if 线段序列.is_empty() { - for i in 1..笔序列.len() - 1 { - let 左 = &笔序列[i - 1]; - let 中 = &笔序列[i]; - let 右 = &笔序列[i + 1]; - - if !Self::_基础判断(左, 中, 右, 关系序列) { - continue; - } - let 段 = - 虚线::创建线段(&[Arc::clone(左), Arc::clone(中), Arc::clone(右)]); - let 段_rc = Arc::new(段); - Self::_添加线段( - 线段序列, - 段_rc, - 配置, - format!("{}, {}", line!(), 当前层级), - ); - - // 段.特征序列.read().unwrap()[0] = 线段特征.新建([中], 段.方向) - let 段 = Self::取段(线段序列.last_mut().unwrap()); - 段.特征序列.write().unwrap()[0] = Some(Arc::new(线段特征::新建( - vec![Arc::clone(中)], - 段.方向(), - ))); - break; - } - if 线段序列.is_empty() { - return; - } + return; } + } - // ---- 2. 清理无效的尾部引用 ---- - while !线段序列.is_empty() - && 线段序列 + // -------------------- 2. 清理无效的尾部引用 -------------------- + // Python: while 线段序列 and 线段序列[-1].前一结束位置: ... + while !线段序列.is_empty() && 线段序列.last().unwrap().前一结束位置.read().is_some() + { + let 前一结束 = Arc::clone( + 线段序列 .last() .unwrap() .前一结束位置 .read() - .unwrap() - .is_some() - { - let 前一结束 = Arc::clone( - 线段序列 - .last() - .unwrap() - .前一结束位置 - .read() - .unwrap() - .as_ref() - .unwrap(), - ); - if !笔序列 - .iter() - .any(|x| Arc::as_ptr(x) == Arc::as_ptr(&前一结束)) - { - let 当前 = Arc::clone(线段序列.last().unwrap()); - Self::_弹出线段( - 线段序列, - &当前, - 配置, - format!("{}, {}", line!(), 当前层级), - ); - } else { - break; - } - } - - if 线段序列.is_empty() { - 当前层级 += 1; - continue; - } - - // ---- 3. 确保当前线段有效 ---- - let 当前线段_rc = Arc::clone(线段序列.last().unwrap()); - Self::序列重置(&当前线段_rc, 笔序列); - let seg_idx = 线段序列.len() - 1; - 线段序列[seg_idx] = 当前线段_rc; - - if 线段序列.last().unwrap().基础序列.read().unwrap().len() < 3 { + .as_ref() + .unwrap(), + ); + // Python: if 前一结束位置 not in 笔序列: _弹出线段(...) + let 首序号 = 笔序列[0].序号.load(Ordering::Relaxed); + let idx = (前一结束.序号.load(Ordering::Relaxed) - 首序号) as usize; + if idx >= 笔序列.len() || !Arc::ptr_eq(&笔序列[idx], &前一结束) { let 当前 = Arc::clone(线段序列.last().unwrap()); - Self::_弹出线段(线段序列, &当前, 配置, format!("{}, {}", line!(), 当前层级)); - if 线段序列.is_empty() { - 当前层级 += 1; - continue; + Self::_弹出线段(线段序列, &当前, 配置, line!(), 层级); + } else { + break; + } + } + + // Python: if not 线段序列: return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) + if 线段序列.is_empty() { + return Self::分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列); + } + + // -------------------- 3. 确保当前线段有效 -------------------- + // Python: 当前线段 = 线段序列[-1]; _序列重置(当前线段, 笔序列) + Self::序列重置(线段序列.last().unwrap(), 笔序列); + + // Python: if len(当前线段.基础序列) < 3: _弹出线段(...) + if 线段序列.last().unwrap().基础序列.read().len() < 3 { + let 当前 = Arc::clone(线段序列.last().unwrap()); + Self::_弹出线段(线段序列, &当前, 配置, line!(), 层级); + // Python: if not 线段序列: return 线段递归分析(...) + if 线段序列.is_empty() { + return Self::分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列); + } + } + + // -------------------- 4. 特征序列已完整时的处理 -------------------- + // Python: 当前线段 = 线段序列[-1] + // Python: if 当前线段.特征序列[2] is not None: ... + { + let 当前线段 = Arc::clone(线段序列.last().unwrap()); + if 当前线段.特征序列.read()[2].is_some() { + // Python: 基础序列 = 线段.分割序列(当前线段)[1] + let (_, 基础序列, _, _) = Self::分割序列(&当前线段, None); + let 四象 = Self::四象(&当前线段); + // Python: 新段 = 虚线.创建线段(基础序列) + let 新段 = Arc::new(虚线::创建线段(&基础序列)); + // Python: 线段._添加线段(线段序列, 新段, 配置, ...) + Self::_添加线段(线段序列, 新段, 配置, line!(), 层级); + // Python: if 线段.四象(当前线段) in ("老阴", "老阳"): 新段.前一缺口 = None + if ["老阴", "老阳"].contains(&四象.as_str()) { + *线段序列.last().unwrap().前一缺口.write() = None; } } + } - // ---- 4. 特征序列已完整时的处理 ---- - { - let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段.特征序列.read().unwrap()[2].is_some() { - let (_, 基础序列, _, _) = Self::分割序列(&当前线段, None); - let 新段 = 虚线::创建线段(&基础序列); - let 新段_rc = Arc::new(新段); - Self::_添加线段( - 线段序列, - 新段_rc, - 配置, - format!("{}, {}", line!(), 当前层级), - ); - if ["老阴", "老阳"].contains(&Self::四象(&当前线段).as_str()) { - *线段序列.last().unwrap().前一缺口.write().unwrap() = None; - } - } - } - - // Refresh current segment + // Python: 当前线段 = 线段序列[-1]; 线段._刷新(当前线段, 配置) + { let idx = 线段序列.len() - 1; Self::_刷新(&线段序列[idx], 配置); + } - // ---- 5. 调用一次全局修正 ---- - Self::_缺口突破(线段序列, 配置, 当前层级); - Self::_非缺口下穿刺(线段序列, 配置, 当前层级); - Self::_缺口后紧急修正(线段序列, 配置, 当前层级); - Self::_修正(线段序列, 配置, 当前层级); + // -------------------- 5. 调用一次全局修正(不循环) -------------------- + // Python: 四种修正各调用一次 + Self::_缺口突破(线段序列, 配置, 层级); + Self::_非缺口下穿刺(线段序列, 配置, 层级); + Self::_缺口后紧急修正(线段序列, 配置, 层级); + Self::_修正(线段序列, 配置, 层级); - // ---- 6. 循环处理后续的笔 ---- + // -------------------- 6. 循环处理后续的笔 -------------------- + // Python: 当前线段 = 线段序列[-1] + let 当前线段_rc = Arc::clone(线段序列.last().unwrap()); + if 当前线段_rc.基础序列.read().is_empty() { + panic!("线段.分析: 基础序列为空"); + } + // Python: 起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1 + let 起始索引 = { + let guard = 当前线段_rc.基础序列.read(); + 线段::_索引(笔序列, &guard[guard.len() - 1]) + 1 + }; + + // Python: for idx in range(起始索引, len(笔序列)): + for 当前虚线_ref in &笔序列[起始索引..] { + let 当前虚线 = Arc::clone(当前虚线_ref); + // Python: 当前线段 = 线段序列[-1]; 四象 = 线段.四象(当前线段) let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段.基础序列.read().unwrap().is_empty() { - panic!("线段.分析: 基础序列为空"); - } - let 最后笔 = Arc::clone(当前线段.基础序列.read().unwrap().last().unwrap()); - let 起始索引 = match 笔序列 - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(&最后笔)) + let 四象 = Self::四象(&当前线段); + + // Python: 线段._添加虚线(当前线段, 当前虚线) + Self::_添加虚线(线段序列.last().unwrap(), Arc::clone(&当前虚线)); + // Python: 线段._刷新(当前线段, 配置) { - Some(idx) => idx + 1, - None => { - // Last笔 not in 笔序列 — restart - 当前层级 += 1; - continue; - } - }; - - let mut 需要递归 = false; - - for 当前虚线_ref in &笔序列[起始索引..] { - let 当前虚线 = Arc::clone(当前虚线_ref); - let 当前线段 = Arc::clone(线段序列.last().unwrap()); - let 四象 = Self::四象(&当前线段); - - // 向当前线段添加笔 - let 线段_rc = Arc::clone(线段序列.last().unwrap()); - Self::_添加虚线(&线段_rc, Arc::clone(&当前虚线)); - let seg_idx = 线段序列.len() - 1; - 线段序列[seg_idx] = 线段_rc; - - // 刷新 - let idx = 线段序列.len() - 1; - Self::_刷新(&线段序列[idx], 配置); - - // 依次尝试四种修正(仅触发第一个匹配) - let mut 修正触发: Option<&str> = None; - if Self::_缺口突破(线段序列, 配置, 当前层级) { - 修正触发 = Some("缺口突破"); - } else if Self::_非缺口下穿刺(线段序列, 配置, 当前层级) { - 修正触发 = Some("非缺口下穿刺"); - } else if Self::_缺口后紧急修正(线段序列, 配置, 当前层级) { - 修正触发 = Some("缺口后紧急修正"); - } else if Self::_修正(线段序列, 配置, 当前层级) { - 修正触发 = Some("修正"); - } - if let Some(trigger) = 修正触发 { - warn!( - "分析.修正触发={}, 笔序列长度={}, 线段序列长度={}", - trigger, - 笔序列.len(), - 线段序列.len() - ); - continue; - } - - // 无修正触发,检查特征序列 - let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段.特征序列.read().unwrap()[2].is_none() { - continue; - } - - // 特征序列[2]存在 → 创建新段 - let (_, 基础序列, _, _) = Self::分割序列(&当前线段, None); - let 新段 = 虚线::创建线段(&基础序列); - let 新段_rc = Arc::new(新段); - Self::_添加线段( - 线段序列, - 新段_rc, - 配置, - format!("{}, {}", line!(), 当前层级), - ); - - if ["老阴", "老阳"].contains(&四象.as_str()) { - *线段序列.last().unwrap().前一缺口.write().unwrap() = None; - } - - // 检查新段与当前虚线的连续性 - let 新段 = Arc::clone(线段序列.last().unwrap()); - if Arc::as_ptr(新段.基础序列.read().unwrap().last().unwrap()) - != Arc::as_ptr(&当前虚线) - { - if !新段 - .基础序列 - .read() - .unwrap() - .last() - .unwrap() - .之后是(&当前虚线) - { - 需要递归 = true; - break; - } - // 向新段添加当前虚线 - let 新段_rc = Arc::clone(线段序列.last().unwrap()); - Self::_添加虚线(&新段_rc, Arc::clone(&当前虚线)); - let seg_idx = 线段序列.len() - 1; - 线段序列[seg_idx] = 新段_rc; - } - let idx = 线段序列.len() - 1; Self::_刷新(&线段序列[idx], 配置); } - if 需要递归 { - 当前层级 += 1; + // Python: 依次尝试四种修正,任意一个成功则跳过后续处理 + let mut 修正触发: Option<&str> = None; + if Self::_缺口突破(线段序列, 配置, 层级) { + 修正触发 = Some("缺口突破"); + } else if Self::_非缺口下穿刺(线段序列, 配置, 层级) { + 修正触发 = Some("非缺口下穿刺"); + } else if Self::_缺口后紧急修正(线段序列, 配置, 层级) { + 修正触发 = Some("缺口后紧急修正"); + } else if Self::_修正(线段序列, 配置, 层级) { + 修正触发 = Some("修正"); + } + if let Some(trigger) = 修正触发 { + warn!( + "分析.修正触发={}, 笔序列长度={}, 线段序列长度={}", + trigger, + 笔序列.len(), + 线段序列.len() + ); continue; } - break; + // Python: 无修正触发,且特征序列[2]已存在 → 创建新段 + let 当前线段 = Arc::clone(线段序列.last().unwrap()); + if 当前线段.特征序列.read()[2].is_none() { + continue; + } + + // Python: 基础序列 = 线段.分割序列(当前线段)[1]; 新段 = 虚线.创建线段(基础序列) + let (_, 基础序列, _, _) = Self::分割序列(&当前线段, None); + let 新段 = Arc::new(虚线::创建线段(&基础序列)); + // Python: 线段._添加线段(线段序列, 新段, 配置, ...) + Self::_添加线段(线段序列, 新段, 配置, line!(), 层级); + // Python: if 四象 in ("老阴", "老阳"): 新段.前一缺口 = None + if ["老阴", "老阳"].contains(&四象.as_str()) { + *线段序列.last().unwrap().前一缺口.write() = None; + } + + // Python: 检查新段与当前虚线的连续性 + let 新段_rc = Arc::clone(线段序列.last().unwrap()); + // Python: if 新段.基础序列[-1] is not 当前虚线 + if !Arc::ptr_eq(新段_rc.基础序列.read().last().unwrap(), &当前虚线) { + // Python: if not 新段.基础序列[-1].之后是(当前虚线): + // return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) + if !新段_rc.基础序列.read().last().unwrap().之后是(&当前虚线) { + return Self::分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列); + } + // Python: 线段._添加虚线(新段, 当前虚线) + Self::_添加虚线(线段序列.last().unwrap(), Arc::clone(&当前虚线)); + } + + // Python: 线段._刷新(新段, 配置) + { + let idx = 线段序列.len() - 1; + Self::_刷新(&线段序列[idx], 配置); + } } } @@ -1466,15 +1344,9 @@ impl 线段 { ) { { let seg = Arc::make_mut(&mut 待添加线段); - *seg.模式.write().unwrap() = "高低".into(); - *seg.标识.write().unwrap() = if seg.基础序列.read().unwrap()[0] - .标识 - .read() - .unwrap() - .as_str() - != "笔" - { - format!("扩展{}", seg.标识.read().unwrap()) + *seg.模式.write() = "高低".into(); + *seg.标识.write() = if seg.基础序列.read()[0].标识.read().as_str() != "笔" { + format!("扩展{}", seg.标识.read()) } else { "扩展线段".into() }; @@ -1484,7 +1356,7 @@ impl 线段 { panic!( "线段.向序列中添加 不连续[{}] {} {}", 行号, - 前一个.武.read().unwrap(), + 前一个.武.read(), seg.文 ); } @@ -1506,7 +1378,7 @@ impl 线段 { return None; } - if Arc::as_ptr(线段序列.last().unwrap()) == Arc::as_ptr(待弹出线段) { + if Arc::ptr_eq(线段序列.last().unwrap(), 待弹出线段) { let drop = 线段序列.pop().unwrap(); drop.有效性.store(false, Ordering::Relaxed); Some(drop) @@ -1515,170 +1387,173 @@ impl 线段 { } } - /// 扩展分析 — 将笔视为线段进行同级别分析 + /// 扩展分析 — 将笔视为线段进行同级别分析(与 Python 扩展分析 对齐,递归实现) pub fn 扩展分析( 虚线序列: &[Arc<虚线>], 线段序列: &mut Vec>, 配置: &缠论配置 ) { + Self::扩展分析_递归(虚线序列, 线段序列, 配置, 0); + } + + fn 扩展分析_递归( + 虚线序列: &[Arc<虚线>], + 线段序列: &mut Vec>, + 配置: &缠论配置, + 层级: i64, + ) { + // Python: if not 虚线序列: return; try: 虚线序列[2] except: return if 虚线序列.len() < 3 { return; } - let mut 当前层级 = 0i64; + // Python: if 层级 > 256: return + if 层级 > 256 { + warn!("线段.扩展分析 递归深度超出 256"); + return; + } - loop { - if 当前层级 > 256 { - warn!("线段.扩展分析 递归深度超出 256"); - return; - } - - // 初始化第一个扩展线段 - if 线段序列.is_empty() { - for i in 1..虚线序列.len() - 1 { - let 左 = &虚线序列[i - 1]; - let 中 = &虚线序列[i]; - let 右 = &虚线序列[i + 1]; - let 关系 = - 相对方向::分析(左.端点高(), 左.端点低(), 右.端点高(), 右.端点低()); - if !matches!( - 关系, - 相对方向::向下 - | 相对方向::向上 - | 相对方向::顺 - | 相对方向::逆 - | 相对方向::同 - ) { - continue; - } - - let 段 = - 虚线::创建线段(&[Arc::clone(左), Arc::clone(中), Arc::clone(右)]); - let 段_rc = Arc::new(段); - Self::_添加扩展线段(线段序列, 段_rc, line!()); - break; - } - - if 线段序列.is_empty() { - return; - } - } - - // 验证当前线段 - let 当前线段_rc = Arc::clone(线段序列.last().unwrap()); - Self::_验证序列(&当前线段_rc, 虚线序列); - let seg_idx = 线段序列.len() - 1; - 线段序列[seg_idx] = 当前线段_rc; - - if 线段序列.last().unwrap().基础序列.read().unwrap().len() < 3 { - let 当前 = Arc::clone(线段序列.last().unwrap()); - Self::_弹出扩展线段(线段序列, &当前, line!()); - 当前层级 += 1; - continue; - } - - if !配置.扩展线段_当下分析 { - let 当前线段 = Arc::clone(线段序列.last().unwrap()); - let 左 = Arc::clone(&当前线段.基础序列.read().unwrap()[0]); - let 右 = Arc::clone(&当前线段.基础序列.read().unwrap()[2]); - - if !相对方向::分析(左.端点高(), 左.端点低(), 右.端点高(), 右.端点低()).是否缺口() - { - { - let cur = Arc::make_mut(线段序列.last_mut().unwrap()); - let 前三个 = cur.基础序列.read().unwrap()[..3].to_vec(); - *cur.基础序列.write().unwrap() = 前三个; - } - let seg_idx = 线段序列.len() - 1; - Self::_武终(&线段序列[seg_idx], 0); - } else { - let 当前 = Arc::clone(线段序列.last().unwrap()); - Self::_弹出扩展线段(线段序列, &当前, line!()); - 当前层级 += 1; - continue; - } - } - - // 武终 - let idx = 线段序列.len() - 1; - Self::_武终(&线段序列[idx], 0); - - let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段 - .基础序列 - .read() - .unwrap() - .last() - .unwrap() - .序号 - .load(Ordering::Relaxed) - + 3 - > 虚线序列.last().unwrap().序号.load(Ordering::Relaxed) - { - return; - } - - let 最后笔 = Arc::clone(当前线段.基础序列.read().unwrap().last().unwrap()); - let 序号 = match 虚线序列 - .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(&最后笔)) - { - Some(idx) => idx + 1, - None => return, - }; - - if 序号 >= 虚线序列.len() { - return; - } - - let mut 需要递归 = false; - - for i in 序号 + 1..虚线序列.len() - 1 { + // -------------------- 初始化第一个扩展线段 -------------------- + // Python: if not 线段序列: for i in range(1, len-1): ... + if 线段序列.is_empty() { + for i in 1..虚线序列.len() - 1 { let 左 = &虚线序列[i - 1]; let 中 = &虚线序列[i]; let 右 = &虚线序列[i + 1]; - let 相对关系 = - 相对方向::分析(左.端点高(), 左.端点低(), 右.端点高(), 右.端点低()); - - if 相对关系.是否缺口() { - let 段_rc = Arc::clone(线段序列.last().unwrap()); - Self::_添加虚线(&段_rc, Arc::clone(左)); - let seg_idx = 线段序列.len() - 1; - 线段序列[seg_idx] = 段_rc; - - let 段_rc = Arc::clone(线段序列.last().unwrap()); - Self::_添加虚线(&段_rc, Arc::clone(中)); - let seg_idx = 线段序列.len() - 1; - 线段序列[seg_idx] = 段_rc; - - let seg_idx = 线段序列.len() - 1; - Self::_武终(&线段序列[seg_idx], 0); + // Python: 关系 = 相对方向.分析(左.端点高, 左.端点低, 右.端点高, 右.端点低) + let 关系 = 相对方向::分析(左.端点高(), 左.端点低(), 右.端点高(), 右.端点低()); + // Python: if 关系 not in (向下, 向上, 顺, 逆, 同): continue + if !matches!( + 关系, + 相对方向::向下 + | 相对方向::向上 + | 相对方向::顺 + | 相对方向::逆 + | 相对方向::同 + ) { continue; } - if 线段序列 - .last() - .unwrap() - .基础序列 - .read() - .unwrap() - .iter() - .any(|x| Arc::as_ptr(x) == Arc::as_ptr(左)) - { - continue; - } - - let 段 = 虚线::创建线段(&[Arc::clone(左), Arc::clone(中), Arc::clone(右)]); - let 段_rc = Arc::new(段); - Self::_添加扩展线段(线段序列, 段_rc, line!()); - 需要递归 = true; + // Python: 段 = 虚线.创建线段([左, 中, 右]); _添加扩展线段(...) + let 段 = Arc::new(虚线::创建线段(&[ + Arc::clone(左), + Arc::clone(中), + Arc::clone(右), + ])); + Self::_添加扩展线段(线段序列, 段, line!()); break; } - if 需要递归 { - 当前层级 += 1; + // Python: if not 线段序列: return + if 线段序列.is_empty() { + return; + } + } + + // -------------------- 验证当前线段 -------------------- + // Python: 当前线段 = 线段序列[-1]; _验证序列(当前线段, 虚线序列) + Self::_验证序列(线段序列.last().unwrap(), 虚线序列); + + // Python: if len(当前线段.基础序列) < 3: _弹出扩展线段(...); return 递归 + if 线段序列.last().unwrap().基础序列.read().len() < 3 { + let 当前 = Arc::clone(线段序列.last().unwrap()); + Self::_弹出扩展线段(线段序列, &当前, line!()); + return Self::扩展分析_递归(虚线序列, 线段序列, 配置, 层级 + 1); + } + + // -------------------- 非当下分析模式 -------------------- + // Python: if not 配置.扩展线段_当下分析: ... + if !配置.扩展线段_当下分析 { + let 当前线段 = 线段序列.last().unwrap(); + // Python: 左, 中, 右 = 当前线段.基础序列[:3] + let 左 = Arc::clone(&当前线段.基础序列.read()[0]); + let 右 = Arc::clone(&当前线段.基础序列.read()[2]); + + // Python: if not 相对方向.分析(左.端点高, 左.端点低, 右.端点高, 右.端点低).是否缺口(): + if !相对方向::分析(左.端点高(), 左.端点低(), 右.端点高(), 右.端点低()).是否缺口() + { + // Python: 当前线段.基础序列 = 当前线段.基础序列[:3]; _武终(当前线段, ...) + let cur = Arc::make_mut(线段序列.last_mut().unwrap()); + let 前三个 = cur.基础序列.read()[..3].to_vec(); + *cur.基础序列.write() = 前三个; + let seg_idx = 线段序列.len() - 1; + Self::_武终(&线段序列[seg_idx], 0); + } else { + // Python: _弹出扩展线段(线段序列, 当前线段, ...); return 递归 + let 当前 = Arc::clone(线段序列.last().unwrap()); + Self::_弹出扩展线段(线段序列, &当前, line!()); + return Self::扩展分析_递归(虚线序列, 线段序列, 配置, 层级 + 1); + } + } + + // -------------------- 武终 -------------------- + // Python: 线段._武终(当前线段, ...) + { + let seg_idx = 线段序列.len() - 1; + Self::_武终(&线段序列[seg_idx], 0); + } + + // -------------------- 检查剩余元素是否够 -------------------- + // Python: if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号: return + let 当前线段 = 线段序列.last().unwrap(); + if 当前线段 + .基础序列 + .read() + .last() + .unwrap() + .序号 + .load(Ordering::Relaxed) + + 3 + > 虚线序列.last().unwrap().序号.load(Ordering::Relaxed) + { + return; + } + + // -------------------- 计算起始序号 -------------------- + // Python: 序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1 + let 最后笔 = Arc::clone(当前线段.基础序列.read().last().unwrap()); + let 序号 = 线段::_索引(虚线序列, &最后笔) + 1; + + // Python: if 序号 >= len(虚线序列): return + if 序号 >= 虚线序列.len() { + return; + } + + // -------------------- 循环处理后续虚线 -------------------- + // Python: for i in range(序号 + 1, len(虚线序列) - 1): + for i in 序号 + 1..虚线序列.len() - 1 { + let 左 = &虚线序列[i - 1]; + let 中 = &虚线序列[i]; + let 右 = &虚线序列[i + 1]; + let 相对关系 = 相对方向::分析(左.端点高(), 左.端点低(), 右.端点高(), 右.端点低()); + + // Python: if 相对关系.是否缺口(): _添加虚线(当前线段, 左); _添加虚线(当前线段, 中); _武终; continue + if 相对关系.是否缺口() { + Self::_添加虚线(线段序列.last().unwrap(), Arc::clone(左)); + Self::_添加虚线(线段序列.last().unwrap(), Arc::clone(中)); + let seg_idx = 线段序列.len() - 1; + Self::_武终(&线段序列[seg_idx], 0); continue; } - break; + // Python: if 左 in 当前线段.基础序列: continue + if 线段序列 + .last() + .unwrap() + .基础序列 + .read() + .iter() + .any(|x| Arc::ptr_eq(x, 左)) + { + continue; + } + + // Python: 段 = 虚线.创建线段([左, 中, 右]); _添加扩展线段(...); return 递归 + let 段 = Arc::new(虚线::创建线段(&[ + Arc::clone(左), + Arc::clone(中), + Arc::clone(右), + ])); + Self::_添加扩展线段(线段序列, 段, line!()); + return Self::扩展分析_递归(虚线序列, 线段序列, 配置, 层级 + 1); } } @@ -1727,41 +1602,31 @@ impl 线段 { { let k线序列 = K线::截取rc( &观察员.普通K线序列, - &阳[阳.len() - 3].文.中.标的K线.read().unwrap(), - &阳[阳.len() - 1] - .武 - .read() - .unwrap() - .中 - .标的K线 - .read() - .unwrap(), + &阳[阳.len() - 3].文.中.标的K线.read(), + &阳[阳.len() - 1].武.read().中.标的K线.read(), ); if 虚线::计算MACD柱子分段(&k线序列).len() >= 3 { 盘整背驰 = true; } } - let 实_ref = 实.read().unwrap(); + let 实_ref = 实.read(); if !实_ref.is_empty() { let 最后中枢 = &实_ref[实_ref.len() - 1]; if 最后中枢 .基础序列 .read() - .unwrap() .iter() .any(|b| Arc::ptr_eq(b, &阳[阳.len() - 1])) { // 最后一笔在最后一个中枢内 - if let Some(序号) = 当前段 - .基础序列 - .read() - .unwrap() - .iter() - .position(|b| Arc::ptr_eq(b, &最后中枢.基础序列.read().unwrap()[0])) - && 序号 > 0 - { - let 进入段 = &当前段.基础序列.read().unwrap()[序号 - 1]; + // Python: 序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0]) + let 序号 = { + let guard = 当前段.基础序列.read(); + 线段::_索引(&guard, &最后中枢.基础序列.read()[0]) + }; + if 序号 > 0 && 序号 < 当前段.基础序列.read().len() { + let 进入段 = &当前段.基础序列.read()[序号 - 1]; let 离开段 = &阳[阳.len() - 1]; assert!( 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) @@ -1786,7 +1651,7 @@ impl 线段 { return true; } } - } else if 最后中枢.第三买卖线.read().unwrap().is_some() { + } else if 最后中枢.第三买卖线.read().is_some() { // 第三买卖点后盘整背驰 let 进入段 = &阳[阳.len() - 3]; let 离开段 = &阳[阳.len() - 1]; @@ -1822,9 +1687,9 @@ impl 线段 { /// 获取所有停顿位置 — 在线段范围内找出所有停顿位置 pub fn 获取所有停顿位置(段: &虚线, 观察员: &观察者) -> Vec<虚线> { - let mut 结果 = Vec::new(); - if 段.模式.read().unwrap().as_str() != "文武" || 段.标识.read().unwrap().as_str() != "线段" - { + let cap = 段.基础序列.read().len() / 2; + let mut 结果 = Vec::with_capacity(cap); + if 段.模式.read().as_str() != "文武" || 段.标识.read().as_str() != "线段" { return 结果; } @@ -1845,9 +1710,8 @@ impl 线段 { for 停顿 in &停顿列表 { 笔序列.push(Arc::clone(停顿)); - let 笔序列_slice: Vec> = 笔序列.iter().map(Arc::clone).collect(); Self::分析( - &笔序列_slice, + &笔序列, &mut 线段序列, &观察员.配置, 0, @@ -1861,14 +1725,14 @@ impl 线段 { ); let 重复 = match (&线段序列.last(), &当前停顿) { - (Some(a), Some(b)) => Arc::ptr_eq(&*a.武.read().unwrap(), b), + (Some(a), Some(b)) => Arc::ptr_eq(&*a.武.read(), b), _ => false, }; if !重复 && let Some(最后线段) = 线段序列.last() - && 最后线段.基础序列.read().unwrap().len() % 2 == 1 + && 最后线段.基础序列.read().len() % 2 == 1 { - let 新段 = 虚线::创建线段(&最后线段.基础序列.read().unwrap()); + let 新段 = 虚线::创建线段(&最后线段.基础序列.read()); 新段 .序号 .store(段.序号.load(Ordering::Relaxed), Ordering::Relaxed); @@ -1877,13 +1741,12 @@ impl 线段 { let 新段_inner = Arc::try_unwrap(新段_rc).unwrap_or_else(|rc| (*rc).clone()); if 新段_inner.方向() == 段.方向() { - 当前停顿 = - Some(Arc::clone(&*线段序列.last().unwrap().武.read().unwrap())); + 当前停顿 = Some(Arc::clone(&*线段序列.last().unwrap().武.read())); 结果.push(新段_inner); } } - if Arc::as_ptr(停顿) != Arc::as_ptr(筆) + if !Arc::ptr_eq(停顿, 筆) && let Some(popped) = 笔序列.pop() { popped.有效性.store(false, Ordering::Relaxed); @@ -1905,7 +1768,7 @@ impl 线段 { let 段_rc = Arc::new(段); Self::获取内部中枢序列(&段_rc, &观察员.配置); if Self::判断线段内部是否背驰(&段_rc, 观察员) { - 结果.push(Arc::clone(&段_rc.武.read().unwrap().中)); + 结果.push(Arc::clone(&段_rc.武.read().中)); } } @@ -1920,6 +1783,7 @@ mod tests { use crate::kline::chan_kline::缠论K线; use crate::structure::fractal_obj::分型; use crate::types::分型结构; + use crate::{error, info, warn}; fn 辅助_创建普K(时间戳: i64, 高: f64, 低: f64) -> Arc { Arc::new(K线 { @@ -2018,7 +1882,7 @@ mod tests { let 文 = 辅助_创建底分型(100, 90.0); let 武 = 辅助_创建顶分型(200, 110.0); let 段 = 辅助_创建笔(文, 武); - *段.前一缺口.write().unwrap() = Some(缺口 { + *段.前一缺口.write() = Some(缺口 { 高: 105.0, 低: 95.0, }); @@ -2030,7 +1894,7 @@ mod tests { let 文 = 辅助_创建顶分型(100, 110.0); let 武 = 辅助_创建底分型(200, 90.0); let 段 = 辅助_创建笔(文, 武); - *段.前一缺口.write().unwrap() = Some(缺口 { + *段.前一缺口.write() = Some(缺口 { 高: 105.0, 低: 95.0, }); @@ -2054,7 +1918,7 @@ mod tests { let 文 = 辅助_创建底分型(100, 90.0); let 武 = 辅助_创建顶分型(200, 110.0); let 段 = 辅助_创建笔(文, 武); - *段.模式.write().unwrap() = "其他".into(); + *段.模式.write() = "其他".into(); assert!(线段::获取缺口(&段).is_none()); } diff --git a/chanlun/src/business/bsp.rs b/chanlun/src/business/bsp.rs index ff5966f..0026aa5 100644 --- a/chanlun/src/business/bsp.rs +++ b/chanlun/src/business/bsp.rs @@ -347,7 +347,7 @@ impl 买卖点 { let 破位值 = 买卖点分型.分型特征值(); // 当前K线 — 从缠K获取其标的K线 - let 当前K线 = Arc::clone(&*当前缠K.标的K线.read().unwrap()); + let 当前K线 = Arc::clone(&*当前缠K.标的K线.read()); // 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算 let 当前缠K序号 = 当前缠K.序号.load(Ordering::Relaxed); diff --git a/chanlun/src/business/multi_frame.rs b/chanlun/src/business/multi_frame.rs index 0ff19df..17be222 100644 --- a/chanlun/src/business/multi_frame.rs +++ b/chanlun/src/business/multi_frame.rs @@ -26,10 +26,10 @@ use crate::business::observer::观察者; use crate::business::synthesizer::K线合成器; use crate::config::缠论配置; use crate::kline::bar::K线; +use crate::{error, warn}; +use parking_lot::RwLock; use std::collections::HashMap; use std::sync::Arc; -use std::sync::RwLock; -use tracing::{error, info}; /// 立体分析器 — 多周期协调器 pub struct 立体分析器 { @@ -72,7 +72,7 @@ impl 立体分析器 { // 显示周期特殊配置 { let 显示观察员 = 单体分析器.get(&显示周期).expect("显示周期观察者不存在"); - let mut guard = 显示观察员.write().unwrap(); + let mut guard = 显示观察员.write(); guard.配置.推送K线 = true; guard.配置.推送笔 = true; guard.配置.推送线段 = true; @@ -84,14 +84,14 @@ impl 立体分析器 { { let 显示缠K序列 = 单体分析器 .get(&显示周期) - .map(|o| o.read().unwrap().缠论K线序列.clone()) + .map(|o| o.read().缠论K线序列.clone()) .unwrap_or_default(); for &周期 in &周期组 { if 周期 != 显示周期 && let Some(观察员) = 单体分析器.get(&周期) { - 观察员.write().unwrap().基础缠K序列 = 显示缠K序列.clone(); + 观察员.write().基础缠K序列 = 显示缠K序列.clone(); } } } @@ -119,7 +119,7 @@ impl 立体分析器 { /// __K线回调 — 对应 Python 立体分析器.__K线回调 fn __K线回调(&self, _信号类型: String, _标识: String, 周期: i64, 完成K线: K线) { if let Some(观察员) = self.单体分析器.get(&周期) { - let mut obs = 观察员.write().unwrap(); + let mut obs = 观察员.write(); obs.增加原始K线(完成K线); // 对应 Python: if 当前K线 := self._K线合成器.获取当前K线(周期) // _完成K线刚清空当前K线,获取当前K线返回 None,所以这里不添加 @@ -133,7 +133,7 @@ impl 立体分析器 { 完成K线: K线, ) { if let Some(观察员) = 单体分析器.get(&周期) { - 观察员.write().unwrap().增加原始K线(完成K线); + 观察员.write().增加原始K线(完成K线); } } @@ -165,22 +165,22 @@ impl 立体分析器 { let 起始时间 = self .单体分析器 .get(&self.输入周期) - .and_then(|o| o.read().unwrap().普通K线序列.first().map(|k| k.时间戳)) + .and_then(|o| o.read().普通K线序列.first().map(|k| k.时间戳)) .unwrap_or(0); let 结束时间 = self .单体分析器 .get(&self.输入周期) - .and_then(|o| o.read().unwrap().普通K线序列.last().map(|k| k.时间戳)) + .and_then(|o| o.read().普通K线序列.last().map(|k| k.时间戳)) .unwrap_or(0); let 标识 = self .单体分析器 .get(&self.输入周期) - .map(|o| o.read().unwrap().符号.clone()) + .map(|o| o.read().符号.clone()) .unwrap_or_default(); let 周期 = self .单体分析器 .get(&self.输入周期) - .map(|o| o.read().unwrap().周期) + .map(|o| o.read().周期) .unwrap_or_default(); let 目录标识 = format!("RustM_{}:{}_{}_{}", 标识, 周期, 起始时间, 结束时间); @@ -195,12 +195,11 @@ impl 立体分析器 { if let Some(观察员) = self.单体分析器.get(周期) { 观察员 .read() - .unwrap() .测试_保存数据(Some(&保存路径.to_string_lossy())); } } - info!("多级别数据拆分保存完成,目录:{}", 保存路径.display()); + warn!("多级别数据拆分保存完成,目录:{}", 保存路径.display()); } /// 相等 — 各周期观察者全量比对,对应 Python `立体分析器相等` @@ -213,11 +212,11 @@ impl 立体分析器 { for 周期 in &self.周期组 { let a_obs = match self.单体分析器.get(周期) { - Some(o) => o.read().unwrap(), + Some(o) => o.read(), None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (A)")), }; let b_obs = match other.单体分析器.get(周期) { - Some(o) => o.read().unwrap(), + Some(o) => o.read(), None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (B)")), }; let (eq, msg) = a_obs.相等(&b_obs, 浮点容差); diff --git a/chanlun/src/business/observer.rs b/chanlun/src/business/observer.rs index 8c3ab18..108db0b 100644 --- a/chanlun/src/business/observer.rs +++ b/chanlun/src/business/observer.rs @@ -32,9 +32,10 @@ use crate::structure::dash_line::虚线; use crate::structure::fractal_obj::分型; use crate::types::相对方向; use crate::utils::datetime; +use crate::{error, warn}; +use parking_lot::RwLock; +use std::sync::Arc; use std::sync::atomic::Ordering; -use std::sync::{Arc, RwLock}; -use tracing::{error, info}; /// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算 pub struct 观察者 { @@ -298,17 +299,15 @@ impl 观察者 { &[相对方向::向上, 相对方向::向下], ); } - } else { - if self.配置.分析线段 { - let 源序列 = self.线段序列组[i - 1].clone(); - 线段::分析( - &源序列, - &mut self.线段序列组[i], - &self.配置, - 0, - &[相对方向::向上, 相对方向::向下], - ); - } + } else if self.配置.分析线段 { + let (left, right) = self.线段序列组.split_at_mut(i); + 线段::分析( + &left[i - 1], + &mut right[0], + &self.配置, + 0, + &[相对方向::向上, 相对方向::向下], + ); } if self.配置.分析线段中枢 { 中枢::分析(&self.线段序列组[i], &mut self.中枢序列组[i], true, "", 0); @@ -323,11 +322,9 @@ impl 观察者 { if self.配置.分析扩展线段 { 线段::扩展分析(&self.笔序列, &mut self.扩展线段序列组[i], &self.配置); } - } else { - if self.配置.分析扩展线段 { - let 源序列 = self.扩展线段序列组[i - 1].clone(); - 线段::扩展分析(&源序列, &mut self.扩展线段序列组[i], &self.配置); - } + } else if self.配置.分析扩展线段 { + let (left, right) = self.扩展线段序列组.split_at_mut(i); + 线段::扩展分析(&left[i - 1], &mut right[0], &self.配置); } if self.配置.分析线段中枢 { 中枢::分析( @@ -345,8 +342,11 @@ impl 观察者 { if self.配置.分析扩展线段 || self.配置.分析线段中枢 { for i in 0..self.混合扩展线段分析层次.min(self.线段序列组.len()) { if self.配置.分析扩展线段 { - let 源序列 = self.线段序列组[i].clone(); - 线段::扩展分析(&源序列, &mut self.混合扩展线段序列组[i], &self.配置); + 线段::扩展分析( + &self.线段序列组[i], + &mut self.混合扩展线段序列组[i], + &self.配置, + ); } if self.配置.分析线段中枢 { 中枢::分析( @@ -430,17 +430,15 @@ impl 观察者 { &[相对方向::向上, 相对方向::向下], ); } - } else { - if self.配置.分析线段 { - let 源序列 = self.线段序列组[i - 1].clone(); - 线段::分析( - &源序列, - &mut self.线段序列组[i], - &self.配置, - 0, - &[相对方向::向上, 相对方向::向下], - ); - } + } else if self.配置.分析线段 { + let (left, right) = self.线段序列组.split_at_mut(i); + 线段::分析( + &left[i - 1], + &mut right[0], + &self.配置, + 0, + &[相对方向::向上, 相对方向::向下], + ); } if self.配置.分析线段中枢 { 中枢::分析(&self.线段序列组[i], &mut self.中枢序列组[i], true, "", 0); @@ -454,11 +452,9 @@ impl 观察者 { if self.配置.分析扩展线段 { 线段::扩展分析(&self.笔序列, &mut self.扩展线段序列组[i], &self.配置); } - } else { - if self.配置.分析扩展线段 { - let 源序列 = self.扩展线段序列组[i - 1].clone(); - 线段::扩展分析(&源序列, &mut self.扩展线段序列组[i], &self.配置); - } + } else if self.配置.分析扩展线段 { + let (left, right) = self.扩展线段序列组.split_at_mut(i); + 线段::扩展分析(&left[i - 1], &mut right[0], &self.配置); } if self.配置.分析线段中枢 { 中枢::分析( @@ -475,8 +471,11 @@ impl 观察者 { if self.配置.分析扩展线段 || self.配置.分析线段中枢 { for i in 0..self.混合扩展线段分析层次.min(self.线段序列组.len()) { if self.配置.分析扩展线段 { - let 源序列 = self.线段序列组[i].clone(); - 线段::扩展分析(&源序列, &mut self.混合扩展线段序列组[i], &self.配置); + 线段::扩展分析( + &self.线段序列组[i], + &mut self.混合扩展线段序列组[i], + &self.配置, + ); } if self.配置.分析线段中枢 { 中枢::分析( @@ -517,7 +516,7 @@ impl 观察者 { return Ok(()); } let 数据列表: Vec = 序列.iter().map(|d| d.获取数据文本()).collect(); - let 文件名 = format!("{}.txt", 序列[0].标识.read().unwrap()); + let 文件名 = format!("{}.txt", 序列[0].标识.read()); std::fs::write(保存路径.join(&文件名), 数据列表.join("\n") + "\n")?; Ok(()) }; @@ -528,7 +527,7 @@ impl 观察者 { return Ok(()); } let 数据列表: Vec = 序列.iter().map(|h| h.获取数据文本()).collect(); - let 文件名 = format!("{}.txt", 序列[0].标识.read().unwrap()); + let 文件名 = format!("{}.txt", 序列[0].标识.read()); std::fs::write(保存路径.join(&文件名), 数据列表.join("\n") + "\n")?; Ok(()) }; @@ -563,7 +562,7 @@ impl 观察者 { ck.序号.load(Ordering::Relaxed), ck.时间戳.load(Ordering::Relaxed), ck.分型, - *ck.方向.read().unwrap(), + *ck.方向.read(), ck.高.get(), ck.低.get(), ck.原始起始序号, @@ -597,7 +596,7 @@ impl 观察者 { 分型序列_数据文本.join("\n") + "\n", ); - info!("全部数据拆分保存完成,目录:{}", 保存路径.display()); + warn!("全部数据拆分保存完成,目录:{}", 保存路径.display()); 保存路径.display().to_string() } @@ -734,6 +733,7 @@ impl 观察者 { mod tests { use super::*; use crate::config::缠论配置; + use crate::{error, info}; fn test_data_path() -> String { let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); @@ -749,10 +749,9 @@ mod tests { fn test_普k序列指针一致性() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); for (i, bi) in obs_ref.笔序列.iter().enumerate() { let pu_seq = bi.获取普K序列(&obs_ref.普通K线序列); @@ -761,12 +760,7 @@ mod tests { info!(" 文.中.标的K线 原始起始序号: {}", bi.文.中.原始起始序号); info!( " 武.中.标的K线 原始结束序号: {}", - bi.武 - .read() - .unwrap() - .中 - .原始结束序号 - .load(Ordering::Relaxed) + bi.武.read().中.原始结束序号.load(Ordering::Relaxed) ); info!(" 普通K线序列.len: {}", obs_ref.普通K线序列.len()); } else { @@ -777,7 +771,7 @@ mod tests { .any(|k| Arc::as_ptr(k) == first_ptr); if !found { info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i); - let wen_ptr = Arc::as_ptr(&*bi.文.中.标的K线.read().unwrap()); + let wen_ptr = Arc::as_ptr(&*bi.文.中.标的K线.read()); let wen_found = obs_ref .普通K线序列 .iter() @@ -802,11 +796,11 @@ mod tests { let offset = i * size; if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { let _k线_py_inner = Arc::new(k线.clone()); - obs_ref.write().unwrap().增加原始K线(k线); + obs_ref.write().增加原始K线(k线); } } - let obs = obs_ref.read().unwrap(); + let obs = obs_ref.read(); info!("普通K线序列.len: {}", obs.普通K线序列.len()); info!("笔序列.len: {}", obs.笔序列.len()); @@ -835,10 +829,9 @@ mod tests { fn test_分型到笔的文武Rc指针一致性() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); // 每个笔的文/武 分型 Rc 指针必须在 分型序列 中 for (i, bi) in obs_ref.笔序列.iter().enumerate() { @@ -848,13 +841,13 @@ mod tests { info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi.文.时间戳()); } - let 武_ptr = Arc::as_ptr(&*bi.武.read().unwrap()); + let 武_ptr = Arc::as_ptr(&*bi.武.read()); let 武_found = obs_ref.分型序列.iter().any(|f| Arc::as_ptr(f) == 武_ptr); if !武_found { info!( "笔 {}: 武(时间戳={}) 不在分型序列中!", i, - bi.武.read().unwrap().时间戳() + bi.武.read().时间戳() ); } } @@ -868,14 +861,13 @@ mod tests { fn test_笔到线段的基础序列Rc指针一致性() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); // 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中 for (i, seg) in obs_ref.线段序列().iter().enumerate() { - for (j, bi_in_seg) in seg.基础序列.read().unwrap().iter().enumerate() { + for (j, bi_in_seg) in seg.基础序列.read().iter().enumerate() { let bi_ptr = Arc::as_ptr(bi_in_seg); let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr); if !found { @@ -893,13 +885,12 @@ mod tests { fn test_中枢基础序列与笔序列Rc指针一致() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); for (i, hub) in obs_ref.笔_中枢序列.iter().enumerate() { - for (j, bi_in_hub) in hub.基础序列.read().unwrap().iter().enumerate() { + for (j, bi_in_hub) in hub.基础序列.read().iter().enumerate() { let bi_ptr = Arc::as_ptr(bi_in_hub); let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr); if !found { @@ -909,7 +900,7 @@ mod tests { } for (i, hub) in obs_ref.中枢序列().iter().enumerate() { - for (j, seg_in_hub) in hub.基础序列.read().unwrap().iter().enumerate() { + for (j, seg_in_hub) in hub.基础序列.read().iter().enumerate() { let seg_ptr = Arc::as_ptr(seg_in_hub); let found = obs_ref.线段序列().iter().any(|s| Arc::as_ptr(s) == seg_ptr); if !found { @@ -934,10 +925,10 @@ mod tests { for i in 0..data.len() / size { let offset = i * size; if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { - obs_ref.write().unwrap().增加原始K线(k线); + obs_ref.write().增加原始K线(k线); } } - let obs = obs_ref.read().unwrap(); + let obs = obs_ref.read(); ( obs.笔序列.len(), obs.线段序列().len(), @@ -975,28 +966,28 @@ mod tests { for i in 0..data.len() / size { let offset = i * size; if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { - obs_ref.write().unwrap().增加原始K线(k线); + obs_ref.write().增加原始K线(k线); } } - let 第一次笔数 = obs_ref.read().unwrap().笔序列.len(); - let 第一次段数 = obs_ref.read().unwrap().线段序列().len(); + let 第一次笔数 = obs_ref.read().笔序列.len(); + let 第一次段数 = obs_ref.read().线段序列().len(); // 重置 - obs_ref.write().unwrap().重置基础序列(); - assert_eq!(obs_ref.read().unwrap().笔序列.len(), 0); - assert_eq!(obs_ref.read().unwrap().线段序列().len(), 0); + obs_ref.write().重置基础序列(); + assert_eq!(obs_ref.read().笔序列.len(), 0); + assert_eq!(obs_ref.read().线段序列().len(), 0); // 重新投喂 for i in 0..data.len() / size { let offset = i * size; if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { - obs_ref.write().unwrap().增加原始K线(k线); + obs_ref.write().增加原始K线(k线); } } - let 第二次笔数 = obs_ref.read().unwrap().笔序列.len(); - let 第二次段数 = obs_ref.read().unwrap().线段序列().len(); + let 第二次笔数 = obs_ref.read().笔序列.len(); + let 第二次段数 = obs_ref.read().线段序列().len(); assert_eq!(第一次笔数, 第二次笔数, "重置后重新投喂笔数不一致"); assert_eq!(第一次段数, 第二次段数, "重置后重新投喂线段数不一致"); @@ -1011,31 +1002,30 @@ mod tests { fn test_RefCell借用安全性_连续读取不panic() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); // 连续大量读取所有 RefCell 字段,不应 panic for _ in 0..100 { for bi in &obs_ref.笔序列 { - let _标识 = bi.标识.read().unwrap().clone(); - let _wu = bi.武.read().unwrap().clone(); - let _基础序列 = bi.基础序列.read().unwrap().len(); - let _特征序列 = bi.特征序列.read().unwrap().len(); - let _模式 = bi.模式.read().unwrap().clone(); - let _实中枢 = bi.实_中枢序列.read().unwrap().len(); - let _虚中枢 = bi.虚_中枢序列.read().unwrap().len(); - let _合中枢 = bi.合_中枢序列.read().unwrap().len(); - let _确认K = bi.确认K线.read().unwrap().is_some(); + let _标识 = bi.标识.read().clone(); + let _wu = bi.武.read().clone(); + let _基础序列 = bi.基础序列.read().len(); + let _特征序列 = bi.特征序列.read().len(); + let _模式 = bi.模式.read().clone(); + let _实中枢 = bi.实_中枢序列.read().len(); + let _虚中枢 = bi.虚_中枢序列.read().len(); + let _合中枢 = bi.合_中枢序列.read().len(); + let _确认K = bi.确认K线.read().is_some(); let _序号 = bi.序号.load(Ordering::Relaxed); let _有效性 = bi.有效性.load(Ordering::Relaxed); let _短路 = bi.短路修正.load(Ordering::Relaxed); - let _前一缺口 = *bi.前一缺口.read().unwrap(); + let _前一缺口 = *bi.前一缺口.read(); } for seg in obs_ref.线段序列() { - let _ = seg.标识.read().unwrap().clone(); - let _ = seg.基础序列.read().unwrap().len(); + let _ = seg.标识.read().clone(); + let _ = seg.基础序列.read().len(); } } // 到达这里 = 无 panic @@ -1045,27 +1035,26 @@ mod tests { fn test_RefCell借用安全性_交替读写不panic() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); // 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域 if !obs_ref.笔序列.is_empty() { let bi = &obs_ref.笔序列[0]; // 读 - let old_mode = bi.模式.read().unwrap().clone(); + let old_mode = bi.模式.read().clone(); // Ref 已释放,可以写 - *bi.模式.write().unwrap() = "测试模式".into(); - let new_mode = bi.模式.read().unwrap().clone(); + *bi.模式.write() = "测试模式".into(); + let new_mode = bi.模式.read().clone(); assert_eq!(new_mode, "测试模式"); // 恢复 - *bi.模式.write().unwrap() = old_mode; + *bi.模式.write() = old_mode; // 读武 - let old_wu = bi.武.read().unwrap().clone(); + let old_wu = bi.武.read().clone(); // Ref 已释放,可以检查 - assert!(Arc::as_ptr(&old_wu) == Arc::as_ptr(&old_wu)); + assert!(Arc::ptr_eq(&old_wu, &old_wu)); } } @@ -1077,10 +1066,9 @@ mod tests { fn test_缠K到分型的Rc指针一致性() { let obs = 观察者::new("btcusd".into(), 300, Default::default()); obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); - let obs_ref = obs.read().unwrap(); + let obs_ref = obs.read(); // 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中 for (i, f) in obs_ref.分型序列.iter().enumerate() { @@ -1196,7 +1184,7 @@ mod tests { 99 }); assert_eq!(handle.join().unwrap(), 99); - assert_eq!(dash.标识.read().unwrap().as_str(), "笔"); + assert_eq!(dash.标识.read().as_str(), "笔"); } /// 测试:Arc<中枢> 可跨线程传递 @@ -1222,11 +1210,11 @@ mod tests { let obs3 = Arc::clone(&obs); let h1 = std::thread::spawn(move || { - let guard = obs2.read().unwrap(); + let guard = obs2.read(); guard.符号.clone() }); let h2 = std::thread::spawn(move || { - let guard = obs3.read().unwrap(); + let guard = obs3.read(); guard.周期 }); @@ -1274,7 +1262,7 @@ mod tests { let obs = 观察者::new("ethusd".into(), 7200, Default::default()); let handle = std::thread::spawn(move || { - let guard = obs.read().unwrap(); + let guard = obs.read(); (guard.符号.clone(), guard.周期) }); @@ -1288,7 +1276,7 @@ mod tests { let mut config = 缠论配置::default(); config.加载文件路径 = test_data_path(); let obs = 观察者::new("btcusd".into(), 300, config); - let mut obs_w = obs.write().unwrap(); + let mut obs_w = obs.write(); obs_w.线段分析层次 = 0; obs_w.重置基础序列(); drop(obs_w); @@ -1299,11 +1287,11 @@ mod tests { for i in 0..(data.len() / size).min(500) { let offset = i * size; if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { - obs.write().unwrap().增加原始K线(k线); + obs.write().增加原始K线(k线); } } - let obs_r = obs.read().unwrap(); + let obs_r = obs.read(); assert!(obs_r.缠论K线序列.len() > 0, "缠K序列应有数据"); assert!(obs_r.分型序列.len() > 0, "分型序列应有数据"); assert!(obs_r.线段序列组.is_empty(), "线段序列组应为空"); @@ -1329,17 +1317,16 @@ mod tests { // 先正常投喂数据 obs.write() - .unwrap() .读取数据文件(&test_data_path(), Default::default()) .unwrap(); // 设为0后执行静态重新分析,不应 panic - let mut obs_w = obs.write().unwrap(); + let mut obs_w = obs.write(); obs_w.线段分析层次 = 0; obs_w.静态重新分析(); drop(obs_w); - let obs_r = obs.read().unwrap(); + let obs_r = obs.read(); assert!(obs_r.分型序列.len() > 0, "静态重新分析后分型序列应有数据"); assert!(obs_r.线段序列组.is_empty(), "线段序列组应为空"); assert!( @@ -1352,4 +1339,104 @@ mod tests { obs_r.笔序列.len() ); } + + /// 创建随机配置(与 main.py 随机配置 对齐) + fn 随机配置() -> crate::config::缠论配置 { + let mut cfg = crate::config::缠论配置::default().不推送(); + cfg.缠K合并替换 = fastrand::bool(); + cfg.笔内元素数量 = fastrand::i64(3..=9); + cfg.笔内相同终点取舍 = fastrand::bool(); + cfg.笔内起始分型包含整笔 = fastrand::bool(); + cfg.笔内原始K线包含整笔 = fastrand::bool(); + cfg.笔次级成笔 = fastrand::bool(); + cfg.笔弱化 = fastrand::bool(); + cfg.笔弱化_原始数量 = fastrand::i64(3..=9); + cfg.线段_非缺口下穿刺 = fastrand::bool(); + cfg.线段_特征序列忽视老阴老阳 = fastrand::bool(); + cfg.线段_修正 = fastrand::bool(); + cfg.线段_缺口后紧急修正 = fastrand::bool(); + cfg.扩展线段_当下分析 = fastrand::bool(); + cfg.买卖点激进识别 = fastrand::bool(); + cfg.买卖点与MACD柱强相关 = fastrand::bool(); + cfg + } + + /// 单线程工作函数(与 main.py 运行单个回测 对齐) + fn 运行单个回测(线程编号: usize, limit: usize) { + let 配置 = 随机配置(); + let 观察员 = + crate::business::observer::观察者::new(format!("btcusd_{}", 线程编号), 300, 配置); + let 方向池 = [ + crate::types::相对方向::向上, + crate::types::相对方向::向上缺口, + crate::types::相对方向::衔接向上, + crate::types::相对方向::向下, + crate::types::相对方向::向下缺口, + crate::types::相对方向::衔接向下, + ]; + let mut 原始K线 = crate::kline::bar::K线::创建普K( + &format!("btcusd_{}", 线程编号), + 1218124800 + 线程编号 as i64 * 300, + 8888.55, + 10000.00, + 9000.22, + 9527.33, + 888.0, + 0, + 300, + ); + 观察员.write().增加原始K线(原始K线.clone()); + let 方向序列 = crate::types::相对方向::从序列中机选(limit, &方向池, true); + for 方向 in &方向序列 { + 原始K线 = 原始K线.根据当前K线生成新K线(*方向, false); + 观察员.write().增加原始K线(原始K线.clone()); + } + } + + #[test] + fn test_50线程压测_10000K线() { + let 线程数 = 50usize; + let 每线程K线数 = 10000usize; + let start = std::time::Instant::now(); + let mut 句柄列表 = Vec::with_capacity(线程数); + let 异常计数 = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0)); + for i in 1..=线程数 { + let 异常计数_ref = 异常计数.clone(); + 句柄列表.push(std::thread::spawn(move || { + let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| { + 运行单个回测(i, 每线程K线数); + })); + if result.is_err() { + 异常计数_ref.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + } + })); + } + for h in 句柄列表 { + let _ = h.join(); + } + let 耗时 = start.elapsed(); + let 总K线数 = 线程数 * (每线程K线数 + 1); + let 异常数 = 异常计数.load(std::sync::atomic::Ordering::Relaxed); + eprintln!( + "核心层 {}线程×{}K线 = {} 次 耗时 {:.2?} ({:.0} K/s) 异常线程: {}", + 线程数, + 每线程K线数, + 总K线数, + 耗时, + 总K线数 as f64 / 耗时.as_secs_f64(), + 异常数 + ); + } + + #[test] + fn test_单线程基准_10000K线() { + let start = std::time::Instant::now(); + 运行单个回测(0, 10000); + let 耗时 = start.elapsed(); + eprintln!( + "核心层 单线程 10000 K线 耗时 {:.2?} ({:.0} K/s)", + 耗时, + 10001.0 / 耗时.as_secs_f64() + ); + } } diff --git a/chanlun/src/business/synthesizer.rs b/chanlun/src/business/synthesizer.rs index 0d79191..96f671c 100644 --- a/chanlun/src/business/synthesizer.rs +++ b/chanlun/src/business/synthesizer.rs @@ -23,8 +23,8 @@ */ use crate::kline::bar::K线; +use crate::warn; use std::collections::HashMap; -use tracing; /// 事件回调类型 — fn(信号类型, 标识, 周期, 完成K线) type 合成器事件回调 = Box; @@ -178,7 +178,7 @@ impl K线合成器 { .map(|s| s.to_string()) .or_else(|| e.downcast_ref::().cloned()) .unwrap_or_else(|| "未知错误".into()); - tracing::error!("K线合成器 事件回调 异常: {}", msg); + warn!("K线合成器 事件回调 异常: {}", msg); } } } diff --git a/chanlun/src/config.rs b/chanlun/src/config.rs index c8b5c9f..fd4dce6 100644 --- a/chanlun/src/config.rs +++ b/chanlun/src/config.rs @@ -22,9 +22,9 @@ * SOFTWARE. */ +use crate::warn; use serde::{Deserialize, Deserializer, Serialize}; use std::collections::HashMap; -use tracing::warn; /// 缠论配置 —— 控制所有分析阶段的行为 /// diff --git a/chanlun/src/indicators/boll.rs b/chanlun/src/indicators/boll.rs index 783221e..b9589ee 100644 --- a/chanlun/src/indicators/boll.rs +++ b/chanlun/src/indicators/boll.rs @@ -24,6 +24,7 @@ use crate::kline::bar::K线; use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; /// 布林带(BOLL)— 基于移动平均和标准差的波动率通道 #[derive(Debug, Clone, Serialize, Deserialize)] @@ -43,7 +44,7 @@ pub struct 布林带 { pub 下轨: f64, /// 内部历史队列(不序列化) #[serde(skip)] - _历史队列: Vec, + _历史队列: VecDeque, /// 内部均值缓存(不序列化) #[serde(skip)] _均值: f64, @@ -61,7 +62,7 @@ impl Default for 布林带 { 上轨: 0.0, 中轨: 0.0, 下轨: 0.0, - _历史队列: Vec::new(), + _历史队列: VecDeque::new(), _均值: 0.0, _方差和: 0.0, } @@ -98,7 +99,7 @@ impl 布林带 { 上轨: 价格, 中轨: 价格, 下轨: 价格, - _历史队列: vec![价格], + _历史队列: VecDeque::from([价格]), _均值: 价格, _方差和: 0.0, } @@ -110,9 +111,9 @@ impl 布林带 { let 标准差倍数 = prev.标准差倍数; let mut q = prev._历史队列.clone(); - q.push(价格); + q.push_back(价格); if q.len() > 周期 { - q.remove(0); + q.pop_front(); } let (_均值, _方差和) = if q.len() < 周期 { diff --git a/chanlun/src/indicators/calculator.rs b/chanlun/src/indicators/calculator.rs index 1515809..1e0f706 100644 --- a/chanlun/src/indicators/calculator.rs +++ b/chanlun/src/indicators/calculator.rs @@ -45,7 +45,7 @@ impl 指标计算器 { let has_prev; { let prev_guard = if n > 1 { - Some(全序列[n - 2].指标.read().unwrap()) + Some(全序列[n - 2].指标.read()) } else { None }; @@ -100,9 +100,9 @@ impl 指标计算器 { 信号, )) }; - 当前K线.指标.write().unwrap().设置(&key, val.clone()); + 当前K线.指标.write().设置(&key, val.clone()); if i == 0 { - 当前K线.指标.write().unwrap().设置("macd", val); + 当前K线.指标.write().设置("macd", val); } } } @@ -142,9 +142,9 @@ impl 指标计算器 { Some(配置.相对强弱指数_移动平均线周期), )) }; - 当前K线.指标.write().unwrap().设置(&key, val.clone()); + 当前K线.指标.write().设置(&key, val.clone()); if i == 0 { - 当前K线.指标.write().unwrap().设置("rsi", val); + 当前K线.指标.write().设置("rsi", val); } } } @@ -177,9 +177,9 @@ impl 指标计算器 { 配置.随机指标_超卖阈值, )) }; - 当前K线.指标.write().unwrap().设置(&key, val.clone()); + 当前K线.指标.write().设置(&key, val.clone()); if i == 0 { - 当前K线.指标.write().unwrap().设置("kdj", val); + 当前K线.指标.write().设置("kdj", val); } } } @@ -218,9 +218,9 @@ impl 指标计算器 { 标准差倍数, )) }; - 当前K线.指标.write().unwrap().设置(&key, val.clone()); + 当前K线.指标.write().设置(&key, val.clone()); if i == 0 { - 当前K线.指标.write().unwrap().设置("boll", val); + 当前K线.指标.write().设置("boll", val); } } } @@ -245,7 +245,7 @@ impl 指标计算器 { "EMA" => Self::_增量EMA(现有序列, 当前价, *period, 计算方式, &key), _ => continue, }; - if let Some(均线_map) = 当前K线.指标.write().unwrap().均线_mut() { + if let Some(均线_map) = 当前K线.指标.write().均线_mut() { 均线_map.insert(key, 值); } } @@ -274,7 +274,7 @@ impl 指标计算器 { } // 尝试从前一根K线获取缓存的SMA if let Some(prev) = 现有序列.last().and_then(|k| { - let guard = k.指标.read().unwrap(); + let guard = k.指标.read(); guard.均线().and_then(|m| m.get(prev_key)).copied() }) { let oldest = super::K线取值( @@ -304,7 +304,7 @@ impl 指标计算器 { prev_key: &str, ) -> f64 { let 前值 = 现有序列.last().and_then(|k| { - let guard = k.指标.read().unwrap(); + let guard = k.指标.read(); guard.均线().and_then(|m| m.get(prev_key)).copied() }); match 前值 { @@ -320,8 +320,8 @@ impl 指标计算器 { fn _回填新指标(全序列: &[Arc], 配置: &缠论配置) { // 作用域化首尾读锁:在回填写循环之前释放,避免读锁与写锁冲突 let (新MACD, 新RSI, 新KDJ, 新BOLL) = { - let 首K_guard = 全序列[0].指标.read().unwrap(); - let 尾K_guard = 全序列[全序列.len() - 1].指标.read().unwrap(); + let 首K_guard = 全序列[0].指标.read(); + let 尾K_guard = 全序列[全序列.len() - 1].指标.read(); let 新MACD: Vec<_> = 配置 ._解析MACD参数列表() @@ -357,7 +357,7 @@ impl 指标计算器 { for i in 0..全序列.len() { let k线 = &全序列[i]; let prev_guard = if i > 0 { - Some(全序列[i - 1].指标.read().unwrap()) + Some(全序列[i - 1].指标.read()) } else { None }; @@ -388,7 +388,7 @@ impl 指标计算器 { *信号, )) }; - k线.指标.write().unwrap().设置(key, val); + k线.指标.write().设置(key, val); } for (key, 周期) in &新RSI { @@ -419,7 +419,7 @@ impl 指标计算器 { Some(配置.相对强弱指数_移动平均线周期), )) }; - k线.指标.write().unwrap().设置(key, val); + k线.指标.write().设置(key, val); } for (key, rsv, k平滑, d平滑) in &新KDJ { @@ -446,7 +446,7 @@ impl 指标计算器 { 配置.随机指标_超卖阈值, )) }; - k线.指标.write().unwrap().设置(key, val); + k线.指标.write().设置(key, val); } for (key, 周期, 标准差倍数) in &新BOLL { @@ -469,7 +469,7 @@ impl 指标计算器 { *标准差倍数, )) }; - k线.指标.write().unwrap().设置(key, val); + k线.指标.write().设置(key, val); } } } diff --git a/chanlun/src/indicators/container.rs b/chanlun/src/indicators/container.rs index acfc07d..080916d 100644 --- a/chanlun/src/indicators/container.rs +++ b/chanlun/src/indicators/container.rs @@ -70,9 +70,7 @@ impl 指标容器 { /// 预注册指标(不覆盖已有值) pub fn 注册(&mut self, 名称: &str, 默认值: Option<指标值>) { - if !self._数据.contains_key(名称) { - self._数据.insert(名称.to_string(), 默认值); - } + self._数据.entry(名称.to_string()).or_insert(默认值); } /// 按名称获取指标值 diff --git a/chanlun/src/indicators/kdj.rs b/chanlun/src/indicators/kdj.rs index 512ea42..dfb2e1a 100644 --- a/chanlun/src/indicators/kdj.rs +++ b/chanlun/src/indicators/kdj.rs @@ -24,6 +24,7 @@ use crate::kline::bar::K线; use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; /// 随机指标 (KDJ) /// @@ -58,9 +59,9 @@ pub struct 随机指标 { /// J 值 (3K - 2D) pub J: Option, /// 历史最高价队列(滑动窗口) - pub 历史最高价队列: Vec, + pub 历史最高价队列: VecDeque, /// 历史最低价队列(滑动窗口) - pub 历史最低价队列: Vec, + pub 历史最低价队列: VecDeque, /// 前一个 RSV(用于平滑递推) pub 前一个RSV: Option, /// 前一个 K(用于平滑递推) @@ -85,8 +86,8 @@ impl Default for 随机指标 { K: None, D: None, J: None, - 历史最高价队列: Vec::new(), - 历史最低价队列: Vec::new(), + 历史最高价队列: VecDeque::new(), + 历史最低价队列: VecDeque::new(), 前一个RSV: None, 前一个K: None, 前一个D: None, @@ -124,8 +125,8 @@ impl 随机指标 { K: None, D: None, J: None, - 历史最高价队列: vec![初始最高价], - 历史最低价队列: vec![初始最低价], + 历史最高价队列: VecDeque::from([初始最高价]), + 历史最低价队列: VecDeque::from([初始最低价]), 前一个RSV: None, 前一个K: None, 前一个D: None, @@ -181,16 +182,16 @@ impl 随机指标 { // 更新历史最高价队列 let mut 历史最高价 = 前一个KDJ.历史最高价队列.clone(); - 历史最高价.push(当前最高价); + 历史最高价.push_back(当前最高价); if 历史最高价.len() > N as usize { - 历史最高价.remove(0); + 历史最高价.pop_front(); } // 更新历史最低价队列 let mut 历史最低价 = 前一个KDJ.历史最低价队列.clone(); - 历史最低价.push(当前最低价); + 历史最低价.push_back(当前最低价); if 历史最低价.len() > N as usize { - 历史最低价.remove(0); + 历史最低价.pop_front(); } // RSV @@ -260,8 +261,8 @@ mod tests { #[test] fn test_first_calc() { let kdj = 随机指标::首次计算(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0); - assert_eq!(kdj.历史最高价队列, vec![110.0]); - assert_eq!(kdj.历史最低价队列, vec![90.0]); + assert_eq!(kdj.历史最高价队列, VecDeque::from([110.0])); + assert_eq!(kdj.历史最低价队列, VecDeque::from([90.0])); assert_eq!(kdj.K, None); } diff --git a/chanlun/src/indicators/rsi.rs b/chanlun/src/indicators/rsi.rs index 2f2ead0..69d14a7 100644 --- a/chanlun/src/indicators/rsi.rs +++ b/chanlun/src/indicators/rsi.rs @@ -24,6 +24,7 @@ use crate::kline::bar::K线; use serde::{Deserialize, Serialize}; +use std::collections::VecDeque; /// 相对强弱指数 (RSI) /// @@ -58,7 +59,9 @@ pub struct 相对强弱指数 { /// RSI SMA 值 pub RSI_SMA: Option, /// RSI 历史队列(用于滚动计算) - pub RSI历史队列: Vec, + pub RSI历史队列: VecDeque, + /// RSI 历史队列运行和(O(1) SMA) + pub RSI和: f64, } impl Default for 相对强弱指数 { @@ -77,7 +80,8 @@ impl Default for 相对强弱指数 { 下跌幅度: 0.0, 平滑系数: 0.0, RSI_SMA: None, - RSI历史队列: Vec::new(), + RSI历史队列: VecDeque::new(), + RSI和: 0.0, } } } @@ -106,7 +110,8 @@ impl 相对强弱指数 { 下跌幅度: 0.0, 平滑系数: 1.0 / 周期 as f64, RSI_SMA: None, - RSI历史队列: Vec::new(), + RSI历史队列: VecDeque::new(), + RSI和: 0.0, } } @@ -167,21 +172,25 @@ impl 相对强弱指数 { }; // RSI_SMA - let (RSI_SMA, RSI历史队列) = match RSI_SMA周期 { + let (RSI_SMA, RSI历史队列, RSI和) = match RSI_SMA周期 { Some(sma周期) if sma周期 > 0 => { let mut 队列 = 前一个RSI.RSI历史队列.clone(); - 队列.push(RSI); - if 队列.len() > sma周期 as usize { - 队列.remove(0); + let mut sum = 前一个RSI.RSI和; + 队列.push_back(RSI); + sum += RSI; + if 队列.len() > sma周期 as usize + && let Some(old) = 队列.pop_front() + { + sum -= old; } let sma = if 队列.is_empty() { None } else { - Some(队列.iter().sum::() / 队列.len() as f64) + Some(sum / 队列.len() as f64) }; - (sma, 队列) + (sma, 队列, sum) } - _ => (None, Vec::new()), + _ => (None, VecDeque::new(), 0.0), }; Self { @@ -199,6 +208,7 @@ impl 相对强弱指数 { 平滑系数, RSI_SMA, RSI历史队列, + RSI和, } } } diff --git a/chanlun/src/kline/bar.rs b/chanlun/src/kline/bar.rs index 230a940..c99951d 100644 --- a/chanlun/src/kline/bar.rs +++ b/chanlun/src/kline/bar.rs @@ -23,16 +23,18 @@ */ use crate::indicators::指标容器; +use crate::info; use crate::types::相对方向; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; +use parking_lot::RwLock; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::io::Write; -use std::sync::{Arc, RwLock}; +use std::sync::Arc; mod rwlock_container_serde { + use parking_lot::RwLock; use serde::{Deserialize, Deserializer, Serialize, Serializer}; - use std::sync::RwLock; /// Serde 序列化辅助(RwLock<指标容器> → 序列化器) pub fn serialize( @@ -42,7 +44,7 @@ mod rwlock_container_serde { where S: Serializer, { - val.read().unwrap().serialize(ser) + val.read().serialize(ser) } /// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>) @@ -115,7 +117,7 @@ impl Clone for K线 { 开盘价: self.开盘价, 收盘价: self.收盘价, 成交量: self.成交量, - 指标: RwLock::new(self.指标.read().unwrap().clone()), + 指标: RwLock::new(self.指标.read().clone()), } } } @@ -223,6 +225,7 @@ impl K线 { /// 保存K线序列到 DAT 文件 pub fn 保存到DAT文件(路径: &str, K线序列: &[&Self]) -> std::io::Result<()> { + info!("保存到DAT文件: {}", 路径); let mut f = std::fs::File::create(路径)?; for k in K线序列 { f.write_all(&k.to_bytes())?; @@ -245,7 +248,7 @@ impl K线 { let mut 阳 = 0.0f64; let mut 阴 = 0.0f64; for k in 基序 { - if let Some(macd) = k.指标.read().unwrap().macd() { + if let Some(macd) = k.指标.read().macd() { let hist = macd.MACD柱; if hist >= 0.0 { 阳 += hist; @@ -314,6 +317,73 @@ impl K线 { (true, "K线: 全部字段一致".into()) } + /// 根据当前K线和方向生成下一根K线(与 chan.py 对齐) + pub fn 根据当前K线生成新K线(&self, 方向: 相对方向, 居中: bool) -> Self { + let 高低差 = self.高 - self.低; + let 偏移 = if 居中 { + 高低差 * 0.5 + } else { + let lo = (高低差 * 0.1279) as i64; + let hi = (高低差 * 0.883) as i64; + if hi > lo { + fastrand::i64(lo..=hi) as f64 + } else { + lo as f64 + } + }; + let 缺口偏移 = if 居中 { + 高低差 * 1.5 + } else { + let lo = (高低差 * 1.1279) as i64; + let hi = (高低差 * 1.883) as i64; + if hi > lo { + fastrand::i64(lo..=hi) as f64 + } else { + lo as f64 + } + }; + let (高, 低) = match 方向 { + 相对方向::向上 => (self.高 + 偏移, self.低 + 偏移), + 相对方向::向下 => (self.高 - 偏移, self.低 - 偏移), + 相对方向::向上缺口 => (self.高 + 缺口偏移, self.低 + 缺口偏移), + 相对方向::向下缺口 => (self.高 - 缺口偏移, self.低 - 缺口偏移), + 相对方向::衔接向上 => { + let off = 高低差; + (self.高 + off, self.高) + } + 相对方向::衔接向下 => { + let off = 高低差; + (self.低, self.低 - off) + } + _ => (self.高, self.低), + }; + let 小数点 = [self.开盘价, self.高, self.低, self.收盘价] + .iter() + .map(|v| { + let s = format!("{v}"); + s.split('.').nth(1).map(|d| d.len()).unwrap_or(0) + }) + .max() + .unwrap_or(2); + let round = |v: f64| -> f64 { + let scale = 10_f64.powi(小数点 as i32); + (v * scale).round() / scale + }; + let 开 = round(低 + (高 - 低) * fastrand::f64()); + let 收 = round(低 + (高 - 低) * fastrand::f64()); + Self::创建普K( + &self.标识, + self.时间戳 + self.周期, + 开, + round(高), + round(低), + 收, + 998.0 * fastrand::f64(), + self.序号 + 1, + self.周期, + ) + } + /// 截取Arc序列中从始到终的片段 pub fn 截取rc(序列: &[Arc], 始: &Arc, 终: &Arc) -> Vec> { let 始_ptr = Arc::as_ptr(始); @@ -349,6 +419,7 @@ impl std::fmt::Display for K线 { #[cfg(test)] mod tests { use super::*; + use crate::{error, info, warn}; #[test] fn test_方向() { @@ -385,4 +456,118 @@ mod tests { assert_eq!(result.get("阴"), Some(&0.0)); assert_eq!(result.get("总"), Some(&0.0)); } + + // ---- 根据当前K线生成新K线 ---- + + #[test] + fn test_生成K线_居中向上() { + let bar = K线::创建普K( + "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300, + ); + let new = bar.根据当前K线生成新K线(相对方向::向上, true); + // 居中: 偏移 = (50200-49800)*0.5 = 200 + assert!((new.高 - 50400.0).abs() < 1.0); // 50200 + 200 + assert!((new.低 - 50000.0).abs() < 1.0); // 49800 + 200 + assert_eq!(new.序号, 1); + assert_eq!(new.时间戳, 1300); + } + + #[test] + fn test_生成K线_居中向下() { + let bar = K线::创建普K( + "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300, + ); + let new = bar.根据当前K线生成新K线(相对方向::向下, true); + assert!((new.高 - 50000.0).abs() < 1.0); // 50200 - 200 + assert!((new.低 - 49600.0).abs() < 1.0); // 49800 - 200 + } + + #[test] + fn test_生成K线_居中向上缺口() { + let bar = K线::创建普K( + "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300, + ); + let new = bar.根据当前K线生成新K线(相对方向::向上缺口, true); + // 居中缺口: 偏移 = 400*1.5 = 600 + assert!((new.高 - 50800.0).abs() < 1.0); // 50200 + 600 + assert!((new.低 - 50400.0).abs() < 1.0); // 49800 + 600 + } + + #[test] + fn test_生成K线_衔接向上() { + let bar = K线::创建普K( + "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300, + ); + let new = bar.根据当前K线生成新K线(相对方向::衔接向上, true); + let 高低差 = 50200.0 - 49800.0; + assert!((new.高 - (50200.0 + 高低差)).abs() < 1.0); + assert!((new.低 - 50200.0).abs() < 1.0); // 衔接向上: 低 = 原高 + } + + #[test] + fn test_生成K线_衔接向下() { + let bar = K线::创建普K( + "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300, + ); + let new = bar.根据当前K线生成新K线(相对方向::衔接向下, true); + assert!((new.高 - 49800.0).abs() < 1.0); // 衔接向下: 高 = 原低 + } + + #[test] + fn test_生成K线_非居中随机范围() { + let bar = K线::创建普K( + "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300, + ); + // 非居中:偏移在 [高低差*0.1279, 高低差*0.883] 范围内随机 + for _ in 0..20 { + let new = bar.根据当前K线生成新K线(相对方向::向上, false); + assert!(new.高 > bar.高, "向上:新高应高于原高"); + assert!(new.低 > bar.低, "向上:新低应高于原低"); + let 偏移 = new.高 - bar.高; + let 高低差 = bar.高 - bar.低; + let lo = 高低差 * 0.1279; + let hi = 高低差 * 0.883; + assert!( + 偏移 >= lo && 偏移 <= hi + 1.0, + "偏移 {偏移} 应在 [{lo}, {hi}] 范围内" + ); + } + } + + // ---- 从序列中机选 ---- + + #[test] + fn test_从序列中机选_可重复() { + let dirs = vec![相对方向::向上, 相对方向::向下, 相对方向::向上缺口]; + let result = 相对方向::从序列中机选(5, &dirs, true); + assert_eq!(result.len(), 5); + for d in &result { + assert!(dirs.contains(d)); + } + } + + #[test] + fn test_从序列中机选_不可重复() { + let dirs = vec![相对方向::向上, 相对方向::向下, 相对方向::向上缺口]; + let result = 相对方向::从序列中机选(3, &dirs, false); + assert_eq!(result.len(), 3); + for (i, d) in result.iter().enumerate() { + for prev in result[..i].iter() { + assert_ne!(prev, d, "重复方向: {:?}", d); + } + } + } + + #[test] + #[should_panic(expected = "数量超过可选方向数")] + fn test_从序列中机选_数量超限() { + let dirs = vec![相对方向::向上, 相对方向::向下]; + 相对方向::从序列中机选(3, &dirs, false); + } + + #[test] + fn test_从序列中机选_空序列() { + let result = 相对方向::从序列中机选(0, &[], true); + assert!(result.is_empty()); + } } diff --git a/chanlun/src/kline/chan_kline.rs b/chanlun/src/kline/chan_kline.rs index d93f2c1..dd9cb87 100644 --- a/chanlun/src/kline/chan_kline.rs +++ b/chanlun/src/kline/chan_kline.rs @@ -29,9 +29,10 @@ use crate::structure::fractal_obj::分型; use crate::types::SyncF64; use crate::types::分型结构; use crate::types::相对方向; +use parking_lot::RwLock; use std::collections::HashSet; +use std::sync::Arc; use std::sync::atomic::{AtomicI64, Ordering}; -use std::sync::{Arc, RwLock}; /// 缠论K线 — 经包含处理过后的K线 /// @@ -74,15 +75,15 @@ impl Clone for 缠论K线 { 时间戳: AtomicI64::new(self.时间戳.load(Ordering::Relaxed)), 高: SyncF64::new(self.高.get()), 低: SyncF64::new(self.低.get()), - 方向: RwLock::new(*self.方向.read().unwrap()), - 分型: RwLock::new(*self.分型.read().unwrap()), + 方向: RwLock::new(*self.方向.read()), + 分型: RwLock::new(*self.分型.read()), 周期: self.周期, 标识: self.标识.clone(), 分型特征值: SyncF64::new(self.高.get()), 原始起始序号: self.原始起始序号, 原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)), - 标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())), - 买卖点信息: RwLock::new(self.买卖点信息.read().unwrap().clone()), + 标的K线: RwLock::new(Arc::clone(&self.标的K线.read())), + 买卖点信息: RwLock::new(self.买卖点信息.read().clone()), } } } @@ -97,10 +98,9 @@ impl std::fmt::Display for 缠论K线 { self.序号.load(Ordering::Relaxed), self.分型 .read() - .unwrap() .map_or("None".to_string(), |fx| fx.to_string()), self.周期, - *self.方向.read().unwrap(), + *self.方向.read(), self.时间戳.load(Ordering::Relaxed), format_f64_g(self.高.get()), format_f64_g(self.低.get()) @@ -116,23 +116,23 @@ impl 缠论K线 { 时间戳: AtomicI64::new(self.时间戳.load(Ordering::Relaxed)), 高: SyncF64::new(self.高.get()), 低: SyncF64::new(self.低.get()), - 方向: RwLock::new(*self.方向.read().unwrap()), - 分型: RwLock::new(*self.分型.read().unwrap()), + 方向: RwLock::new(*self.方向.read()), + 分型: RwLock::new(*self.分型.read()), 周期: self.周期, 标识: self.标识.clone(), 分型特征值: SyncF64::new(self.高.get()), 原始起始序号: self.原始起始序号, 原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)), - 标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())), - 买卖点信息: RwLock::new(self.买卖点信息.read().unwrap().clone()), + 标的K线: RwLock::new(Arc::clone(&self.标的K线.read())), + 买卖点信息: RwLock::new(self.买卖点信息.read().clone()), } } /// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0 pub fn 与MACD柱子匹配(&self) -> bool { - let 标 = self.标的K线.read().unwrap(); - let 容器 = 标.指标.read().unwrap(); - match *self.分型.read().unwrap() { + let 标 = self.标的K线.read(); + let 容器 = 标.指标.read(); + match *self.分型.read() { Some(分型结构::底) | Some(分型结构::下) => { if let Some(macd) = 容器.macd() { macd.MACD柱 < 0.0 @@ -153,9 +153,9 @@ impl 缠论K线 { /// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA pub fn 与RSI匹配(&self) -> bool { - let 标 = self.标的K线.read().unwrap(); - let 容器 = 标.指标.read().unwrap(); - match *self.分型.read().unwrap() { + let 标 = self.标的K线.read(); + let 容器 = 标.指标.read(); + match *self.分型.read() { Some(分型结构::底) | Some(分型结构::下) => { if let Some(rsi) = 容器.rsi() { match (rsi.RSI, rsi.RSI_SMA) { @@ -182,9 +182,9 @@ impl 缠论K线 { /// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后) pub fn 与KDJ匹配(&self) -> bool { - let 标 = self.标的K线.read().unwrap(); - let 容器 = 标.指标.read().unwrap(); - match *self.分型.read().unwrap() { + let 标 = self.标的K线.read(); + let 容器 = 标.指标.read(); + match *self.分型.read() { Some(分型结构::底) | Some(分型结构::下) => { if let Some(kdj) = 容器.kdj() { match (kdj.K, kdj.D) { @@ -356,12 +356,12 @@ impl 缠论K线 { // 逆序包含时更新时间和标的K线 if 关系 != 相对方向::顺 { 当前缠K.时间戳.store(当前普K.时间戳, Ordering::Relaxed); - *当前缠K.标的K线.write().unwrap() = Arc::clone(当前普K); + *当前缠K.标的K线.write() = Arc::clone(当前普K); } 当前缠K.高.set(取值函数(当前缠K.高.get(), 当前普K.高)); 当前缠K.低.set(取值函数(当前缠K.低.get(), 当前普K.低)); 当前缠K.原始结束序号.store(当前普K.序号, Ordering::Relaxed); - *当前缠K.方向.write().unwrap() = 当前普K.方向(); + *当前缠K.方向.write() = 当前普K.方向(); if let Some(之前) = 之前缠K { 当前缠K @@ -466,29 +466,29 @@ impl 缠论K线 { let 结构 = 分型结构::分析(&*左, &*中, &*右, false, false); // 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型 - *缠K序列[idx - 2].分型.write().unwrap() = 结构; + *缠K序列[idx - 2].分型.write() = 结构; if let Some(结构) = 结构 { match 结构 { 分型结构::底 => { 缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].低.get()); 缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].高.get()); - *缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::顶); + *缠K序列[idx - 1].分型.write() = Some(分型结构::顶); } 分型结构::顶 => { 缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].高.get()); 缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].低.get()); - *缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::底); + *缠K序列[idx - 1].分型.write() = Some(分型结构::底); } 分型结构::上 => { 缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].高.get()); 缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].高.get()); - *缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::顶); + *缠K序列[idx - 1].分型.write() = Some(分型结构::顶); } 分型结构::下 => { 缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].低.get()); 缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].低.get()); - *缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::底); + *缠K序列[idx - 1].分型.write() = Some(分型结构::底); } 分型结构::散 => {} } @@ -573,23 +573,23 @@ impl 缠论K线 { ), ); } - if *self.方向.read().unwrap() != *other.方向.read().unwrap() { + if *self.方向.read() != *other.方向.read() { return ( false, format!( "缠论K线: [方向] 不等 A={},B={}", - self.方向.read().unwrap(), - other.方向.read().unwrap() + self.方向.read(), + other.方向.read() ), ); } - if *self.分型.read().unwrap() != *other.分型.read().unwrap() { + if *self.分型.read() != *other.分型.read() { return ( false, format!( "缠论K线: [分型] 不等 A={:?},B={:?}", - self.分型.read().unwrap(), - other.分型.read().unwrap() + self.分型.read(), + other.分型.read() ), ); } @@ -636,17 +636,13 @@ impl 缠论K线 { ); } // 标的K线 递归 - let (eq, msg) = self - .标的K线 - .read() - .unwrap() - .相等(&other.标的K线.read().unwrap(), 浮点容差); + let (eq, msg) = self.标的K线.read().相等(&other.标的K线.read(), 浮点容差); if !eq { return (false, format!("缠论K线: 标的K线子项异常 >> {msg}")); } // 买卖点信息 - let a_guard = self.买卖点信息.read().unwrap(); - let b_guard = other.买卖点信息.read().unwrap(); + let a_guard = self.买卖点信息.read(); + let b_guard = other.买卖点信息.read(); let a_set: std::collections::HashSet<&String> = a_guard.iter().collect(); let b_set: std::collections::HashSet<&String> = b_guard.iter().collect(); if a_set != b_set { @@ -654,8 +650,8 @@ impl 缠论K线 { false, format!( "缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}", - self.买卖点信息.read().unwrap(), - other.买卖点信息.read().unwrap() + self.买卖点信息.read(), + other.买卖点信息.read() ), ); } diff --git a/chanlun/src/lib.rs b/chanlun/src/lib.rs index b806bde..364bad3 100644 --- a/chanlun/src/lib.rs +++ b/chanlun/src/lib.rs @@ -30,6 +30,7 @@ pub mod business; pub mod config; pub mod indicators; pub mod kline; +pub mod log; pub mod structure; pub mod types; pub mod utils; diff --git a/chanlun/src/log.rs b/chanlun/src/log.rs new file mode 100644 index 0000000..8bc7875 --- /dev/null +++ b/chanlun/src/log.rs @@ -0,0 +1,68 @@ +use std::sync::atomic::{AtomicBool, AtomicU8, Ordering}; + +/// 日志模式: 0=Off, 1=Simple (eprintln), 2=Tracing (tracing subscriber) +pub static LOG_MODE: AtomicU8 = AtomicU8::new(0); + +/// 向后兼容:set_log_level 设置此标志 +pub static 日志启用: AtomicBool = AtomicBool::new(false); + +pub fn init_from_env() { + if let Ok(val) = std::env::var("CHANLUN_LOG_MODE") { + match val.to_lowercase().as_str() { + "simple" | "on" | "debug" | "1" => { + LOG_MODE.store(1, Ordering::Relaxed); + 日志启用.store(true, Ordering::Relaxed); + } + "tracing" | "2" => { + LOG_MODE.store(2, Ordering::Relaxed); + 日志启用.store(true, Ordering::Relaxed); + } + _ => {} + } + } +} + +pub fn set_log_mode(mode: u8) { + LOG_MODE.store(mode.min(2), Ordering::Relaxed); + 日志启用.store(mode > 0, Ordering::Relaxed); +} + +pub fn get_log_mode() -> u8 { + LOG_MODE.load(Ordering::Relaxed) +} + +#[macro_export] +macro_rules! warn { + ($($arg:tt)*) => { + if $crate::log::日志启用.load(std::sync::atomic::Ordering::Relaxed) { + match $crate::log::LOG_MODE.load(std::sync::atomic::Ordering::Relaxed) { + 2 => tracing::warn!($($arg)*), + _ => eprintln!($($arg)*), + } + } + }; +} + +#[macro_export] +macro_rules! error { + ($($arg:tt)*) => { + if $crate::log::日志启用.load(std::sync::atomic::Ordering::Relaxed) { + match $crate::log::LOG_MODE.load(std::sync::atomic::Ordering::Relaxed) { + 2 => tracing::error!($($arg)*), + _ => eprintln!($($arg)*), + } + } + }; +} + +#[macro_export] +macro_rules! info { + ($($arg:tt)*) => { + if $crate::log::日志启用.load(std::sync::atomic::Ordering::Relaxed) { + match $crate::log::LOG_MODE.load(std::sync::atomic::Ordering::Relaxed) { + 2 => tracing::info!($($arg)*), + _ => println!($($arg)*), + } + } + }; +} diff --git a/chanlun/src/main.rs b/chanlun/src/main.rs index d3910b0..0dbd829 100644 --- a/chanlun/src/main.rs +++ b/chanlun/src/main.rs @@ -80,10 +80,9 @@ fn 测试_读取数据(文件路径: &str) { let 观察员 = 观察者::new("".into(), 0, 缠论配置::default()); 观察员 .write() - .unwrap() .读取数据文件(文件路径, 配置) .expect("读取数据文件失败"); - let 观察员 = 观察员.read().unwrap(); + let 观察员 = 观察员.read(); let 消耗用时 = 启动时间.elapsed(); println!( "测试_读取数据 耗时 {:.2?} 普K数量 {}", @@ -160,7 +159,7 @@ fn 测试_周期合成(文件路径: &str) { // Display stats per period for &p in &[周期, 周期 * 5, 周期 * 5 * 6] { if let Some(观察员) = 多级别分析.获取观察者(p) { - let 观察员 = 观察员.read().unwrap(); + let 观察员 = 观察员.read(); println!( "周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}", p, diff --git a/chanlun/src/structure/dash_line.rs b/chanlun/src/structure/dash_line.rs index 8092ae2..3f56e12 100644 --- a/chanlun/src/structure/dash_line.rs +++ b/chanlun/src/structure/dash_line.rs @@ -29,10 +29,10 @@ use crate::kline::chan_kline::缠论K线; use crate::structure::fractal_obj::分型; use crate::structure::segment_feat::线段特征; use crate::types::{分型结构, 相对方向, 缺口}; -use cached::stores::LruCache; +use crate::warn; +use parking_lot::RwLock; +use std::sync::Arc; use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; -use std::sync::{Arc, LazyLock, Mutex, RwLock}; -use tracing::warn; /// 扩展线段模式 — 控制虚线高低取值方式 /// @@ -132,33 +132,27 @@ pub struct MACD行为统计 { impl Clone for 虚线 { fn clone(&self) -> Self { Self { - 标识: RwLock::new(self.标识.read().unwrap().clone()), + 标识: RwLock::new(self.标识.read().clone()), 序号: AtomicI64::new(self.序号.load(Ordering::Relaxed)), 级别: AtomicI64::new(self.级别.load(Ordering::Relaxed)), 文: Arc::clone(&self.文), - 武: RwLock::new(Arc::clone(&self.武.read().unwrap())), + 武: RwLock::new(Arc::clone(&self.武.read())), 有效性: AtomicBool::new(self.有效性.load(Ordering::Relaxed)), - 基础序列: RwLock::new(self.基础序列.read().unwrap().clone()), - 特征序列: RwLock::new(self.特征序列.read().unwrap().clone()), - 实_中枢序列: RwLock::new(self.实_中枢序列.read().unwrap().clone()), - 虚_中枢序列: RwLock::new(self.虚_中枢序列.read().unwrap().clone()), - 合_中枢序列: RwLock::new(self.合_中枢序列.read().unwrap().clone()), - 确认K线: RwLock::new(self.确认K线.read().unwrap().clone()), - 模式: RwLock::new(self.模式.read().unwrap().clone()), + 基础序列: RwLock::new(self.基础序列.read().clone()), + 特征序列: RwLock::new(self.特征序列.read().clone()), + 实_中枢序列: RwLock::new(self.实_中枢序列.read().clone()), + 虚_中枢序列: RwLock::new(self.虚_中枢序列.read().clone()), + 合_中枢序列: RwLock::new(self.合_中枢序列.read().clone()), + 确认K线: RwLock::new(self.确认K线.read().clone()), + 模式: RwLock::new(self.模式.read().clone()), _特征序列_显示: AtomicBool::new(self._特征序列_显示.load(Ordering::Relaxed)), - 前一缺口: RwLock::new(*self.前一缺口.read().unwrap()), - 前一结束位置: RwLock::new(self.前一结束位置.read().unwrap().clone()), + 前一缺口: RwLock::new(*self.前一缺口.read()), + 前一结束位置: RwLock::new(self.前一结束位置.read().clone()), 短路修正: AtomicBool::new(self.短路修正.load(Ordering::Relaxed)), } } } -type 买卖意义缓存类型 = LruCache<(usize, usize), (bool, String)>; - -/// 买卖意义 LRU 缓存(max 128 条目,对齐 Python @lru_cache(maxsize=128)) -static 买卖意义缓存: LazyLock> = - LazyLock::new(|| Mutex::new(LruCache::with_size(128))); - impl 虚线 { pub fn new( 序号: i64, @@ -195,14 +189,14 @@ impl 虚线 { "{}:{}:{}:{}", self.文.中.标识, self.文.中.周期, - self.标识.read().unwrap(), + self.标识.read(), self.序号.load(Ordering::Relaxed) ) } /// 方向 — 文到武的方向(对齐 Python:无法识别时 panic) pub fn 方向(&self) -> 相对方向 { - match (self.文.结构, self.武.read().unwrap().结构) { + match (self.文.结构, self.武.read().结构) { (分型结构::顶, 分型结构::底) => 相对方向::向下, (分型结构::顶, 分型结构::下) => 相对方向::向下, (分型结构::底, 分型结构::顶) => 相对方向::向上, @@ -210,7 +204,7 @@ impl 虚线 { _ => panic!( "虚线 方向 无法识别: 文.结构={:?}, 武.结构={:?}", self.文.结构, - self.武.read().unwrap().结构 + self.武.read().结构 ), } } @@ -218,7 +212,7 @@ impl 虚线 { /// 虚线端点高 — 始终取文/武分型端点的中.高,不扫描基础序列 pub fn 端点高(&self) -> f64 { if self.方向() == 相对方向::向上 { - self.武.read().unwrap().中.高.get() + self.武.read().中.高.get() } else { self.文.中.高.get() } @@ -227,7 +221,7 @@ impl 虚线 { /// 虚线端点低 — 始终取文/武分型端点的中.低,不扫描基础序列 pub fn 端点低(&self) -> f64 { if self.方向() == 相对方向::向下 { - self.武.read().unwrap().中.低.get() + self.武.read().中.低.get() } else { self.文.中.低.get() } @@ -236,20 +230,20 @@ impl 虚线 { /// 虚线高 /// 默认同端点高;扩展线段模式开启时,取基础序列中所有子段文分型 + 最后子段武分型的最高 pub fn 高(&self) -> f64 { - let 模式 = self.模式.read().unwrap(); - let 标识 = self.标识.read().unwrap(); + let 模式 = self.模式.read(); + let 标识 = self.标识.read(); if 扩展线段模式.load(Ordering::Relaxed) && *模式 != "文武" && *标识 != "笔" && 标识.contains("扩展") { - let 基础序列 = self.基础序列.read().unwrap(); + let 基础序列 = self.基础序列.read(); if !基础序列.is_empty() { return 基础序列 .iter() .map(|s| s.文.中.高.get()) .chain(std::iter::once( - 基础序列.last().unwrap().武.read().unwrap().中.高.get(), + 基础序列.last().unwrap().武.read().中.高.get(), )) .fold(f64::NEG_INFINITY, f64::max); } @@ -260,20 +254,20 @@ impl 虚线 { /// 虚线低 /// 默认同端点低;扩展线段模式开启时,取基础序列中所有子段文分型 + 最后子段武分型的最低 pub fn 低(&self) -> f64 { - let 模式 = self.模式.read().unwrap(); - let 标识 = self.标识.read().unwrap(); + let 模式 = self.模式.read(); + let 标识 = self.标识.read(); if 扩展线段模式.load(Ordering::Relaxed) && *模式 != "文武" && *标识 != "笔" && 标识.contains("扩展") { - let 基础序列 = self.基础序列.read().unwrap(); + let 基础序列 = self.基础序列.read(); if !基础序列.is_empty() { return 基础序列 .iter() .map(|s| s.文.中.低.get()) .chain(std::iter::once( - 基础序列.last().unwrap().武.read().unwrap().中.低.get(), + 基础序列.last().unwrap().武.read().中.低.get(), )) .fold(f64::INFINITY, f64::min); } @@ -283,18 +277,18 @@ impl 虚线 { /// 判断两个虚线是否首尾相连 pub fn 之前是(&self, 之前: &虚线) -> bool { - if *self.标识.read().unwrap() != *之前.标识.read().unwrap() { + if *self.标识.read() != *之前.标识.read() { return false; } - Arc::as_ptr(&*之前.武.read().unwrap()) == Arc::as_ptr(&self.文) + Arc::ptr_eq(&*之前.武.read(), &self.文) } /// 判断两个虚线是否首尾相连 pub fn 之后是(&self, 之后: &虚线) -> bool { - if *self.标识.read().unwrap() != *之后.标识.read().unwrap() { + if *self.标识.read() != *之后.标识.read() { return false; } - Arc::as_ptr(&*self.武.read().unwrap()) == Arc::as_ptr(&之后.文) + Arc::ptr_eq(&*self.武.read(), &之后.文) } /// 获取该虚线范围内的普K序列 @@ -304,23 +298,17 @@ impl 虚线 { // 而非序号切片——因为序号可能与实际位置不一致。 let 始 = 普K序列 .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(&*self.文.中.标的K线.read().unwrap())); - let 终 = 普K序列.iter().position(|k| { - Arc::as_ptr(k) == Arc::as_ptr(&*self.武.read().unwrap().中.标的K线.read().unwrap()) - }); + .position(|k| Arc::ptr_eq(k, &*self.文.中.标的K线.read())); + let 终 = 普K序列 + .iter() + .position(|k| Arc::ptr_eq(k, &*self.武.read().中.标的K线.read())); match (始, 终) { (Some(s), Some(e)) if s <= e => 普K序列[s..=e].to_vec(), _ => { // 指针查找失败时回退到序号方式 warn!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>"); let 始 = self.文.中.原始起始序号 as usize; - let 终 = self - .武 - .read() - .unwrap() - .中 - .原始结束序号 - .load(Ordering::Relaxed) as usize; + let 终 = self.武.read().中.原始结束序号.load(Ordering::Relaxed) as usize; if 始 < 普K序列.len() && 终 < 普K序列.len() && 始 <= 终 { 普K序列[始..=终].to_vec() } else { @@ -333,21 +321,21 @@ impl 虚线 { /// 获取该虚线范围内的缠K序列 /// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价 pub fn 获取缠K序列(&self, 缠K序列: &[Arc<缠论K线>]) -> Vec> { - 缠论K线::截取(缠K序列, &self.文.中, &self.武.read().unwrap().中).unwrap_or_default() + 缠论K线::截取(缠K序列, &self.文.中, &self.武.read().中).unwrap_or_default() } /// 获取_武 — 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武) /// 对齐说明:Python 是 @classmethod(cls, 实线),Rust 是实例方法(&self),逻辑等价 pub fn 获取_武(&self) -> Arc<分型> { - if *self.标识.read().unwrap() == "笔" { - return self.武.read().unwrap().clone(); + if *self.标识.read() == "笔" { + return self.武.read().clone(); } - let mut current_rc = Arc::clone(self.基础序列.read().unwrap().last().unwrap()); + let mut current_rc = Arc::clone(self.基础序列.read().last().unwrap()); loop { - if *current_rc.标识.read().unwrap() == "笔" { - return current_rc.武.read().unwrap().clone(); + if *current_rc.标识.read() == "笔" { + return current_rc.武.read().clone(); } - let next = Arc::clone(current_rc.基础序列.read().unwrap().last().unwrap()); + let next = Arc::clone(current_rc.基础序列.read().last().unwrap()); current_rc = next; } } @@ -355,16 +343,16 @@ impl 虚线 { /// 获取数据文本(用于保存/调试) pub fn 获取数据文本(&self) -> String { use crate::utils::format_f64_g; - if *self.标识.read().unwrap() == "笔" { + if *self.标识.read() == "笔" { return format!( "{}, {}, {}, 文:({},{}), 武:({},{}), {}", - self.标识.read().unwrap(), + self.标识.read(), self.序号.load(Ordering::Relaxed), self.级别.load(Ordering::Relaxed), self.文.时间戳(), format_f64_g(self.文.分型特征值), - self.武.read().unwrap().时间戳(), - format_f64_g(self.武.read().unwrap().分型特征值), + self.武.read().时间戳(), + format_f64_g(self.武.read().分型特征值), if self.有效性.load(Ordering::Relaxed) { "True" } else { @@ -378,11 +366,11 @@ impl 虚线 { let (特征_a, 特征_b, 特征_c) = crate::algorithm::segment::线段::特征序列状态(self); let 特征_bool = |b: bool| -> &str { if b { "True" } else { "False" } }; - let 前一缺口_str = match &*self.前一缺口.read().unwrap() { + let 前一缺口_str = match &*self.前一缺口.read() { Some(g) => format!("{}", g), None => "None".to_string(), }; - let 前一结束位置_str = match &*self.前一结束位置.read().unwrap() { + let 前一结束位置_str = match &*self.前一结束位置.read() { Some(d) => format!("{}", d), None => "None".to_string(), }; @@ -392,7 +380,6 @@ impl 虚线 { "[{}]", self.实_中枢序列 .read() - .unwrap() .iter() .map(|h| format!("{}", h)) .collect::>() @@ -402,7 +389,6 @@ impl 虚线 { "[{}]", self.虚_中枢序列 .read() - .unwrap() .iter() .map(|h| format!("{}", h)) .collect::>() @@ -412,7 +398,6 @@ impl 虚线 { "[{}]", self.合_中枢序列 .read() - .unwrap() .iter() .map(|h| format!("{}", h)) .collect::>() @@ -443,19 +428,19 @@ impl 虚线 { format!( "{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}, ({}, {}, {}), (前: {}, 后: {}, 三: {}, 伤: {}), 实: {}, 虚: {}, 合: {}, {}, {}, {}, {}", - self.标识.read().unwrap(), + self.标识.read(), self.序号.load(Ordering::Relaxed), self.级别.load(Ordering::Relaxed), self.文.时间戳(), format_f64_g(self.文.分型特征值), - self.武.read().unwrap().时间戳(), - format_f64_g(self.武.read().unwrap().分型特征值), + self.武.read().时间戳(), + format_f64_g(self.武.read().分型特征值), if self.有效性.load(Ordering::Relaxed) { "True" } else { "False" }, - self.基础序列.read().unwrap().len(), + self.基础序列.read().len(), 特征_bool(特征_a), 特征_bool(特征_b), 特征_bool(特征_c), @@ -469,7 +454,7 @@ impl 虚线 { 实_str, 虚_str, 合_str, - self.模式.read().unwrap(), + self.模式.read(), 前一缺口_str, 前一结束位置_str, if self.短路修正.load(Ordering::Relaxed) { @@ -497,26 +482,26 @@ impl 虚线 { panic!("创建线段 虚线序列 数量 {} 不是单数!", 序列数量); } let 文 = Arc::clone(&虚线序列[0].文); - let 武 = Arc::clone(&*虚线序列[虚线序列.len() - 1].武.read().unwrap()); + let 武 = Arc::clone(&*虚线序列[虚线序列.len() - 1].武.read()); assert!( 文.结构 != 武.结构, "创建线段: 文.结构 == 武.结构 文={}, 武={}", 文, 武 ); - let 标识: String = if *虚线序列[0].标识.read().unwrap() == "笔" { + let 标识: String = if *虚线序列[0].标识.read() == "笔" { "线段".into() } else { - format!("线段<{}>", 虚线序列[0].标识.read().unwrap()) + format!("线段<{}>", 虚线序列[0].标识.read()) }; let 级别 = 虚线序列[0].级别.load(Ordering::Relaxed) + 1; let 段 = Self::new(0, 标识, 文, 武, 级别, true); - *段.特征序列.write().unwrap() = vec![None, None, None]; - *段.实_中枢序列.write().unwrap() = Vec::new(); - *段.虚_中枢序列.write().unwrap() = Vec::new(); - *段.合_中枢序列.write().unwrap() = Vec::new(); - *段.基础序列.write().unwrap() = 虚线序列.to_vec(); - *段.模式.write().unwrap() = "文武".into(); + *段.特征序列.write() = vec![None, None, None]; + *段.实_中枢序列.write() = Vec::new(); + *段.虚_中枢序列.write() = Vec::new(); + *段.合_中枢序列.write() = Vec::new(); + *段.基础序列.write() = 虚线序列.to_vec(); + *段.模式.write() = "文武".into(); 段 } @@ -573,59 +558,56 @@ impl 虚线 { pub fn 计算MACD柱子均值(普K序列: &[Arc], 实线: &虚线) -> f64 { let K线序列 = K线::截取rc( 普K序列, - &实线.文.中.标的K线.read().unwrap(), - &实线.武.read().unwrap().中.标的K线.read().unwrap(), + &实线.文.中.标的K线.read(), + &实线.武.read().中.标的K线.read(), ); if K线序列.is_empty() { return 0.0; } let 总: f64 = K线序列 .iter() - .filter_map(|k| k.指标.read().unwrap().macd_cloned()) + .filter_map(|k| k.指标.read().macd_cloned()) .map(|m| m.MACD柱.abs()) .sum(); 总 / K线序列.len() as f64 } /// 计算MACD柱子均值_阴 — 负柱的绝对值均值 - /// 对齐说明:Python 无数据时返回 False(bool),Rust 返回 None,调用方处理等价 pub fn 计算MACD柱子均值_阴(普K序列: &[Arc], 实线: &虚线) -> Option { let K线序列 = K线::截取rc( 普K序列, - &实线.文.中.标的K线.read().unwrap(), - &实线.武.read().unwrap().中.标的K线.read().unwrap(), + &实线.文.中.标的K线.read(), + &实线.武.read().中.标的K线.read(), ); - let 总: Vec = K线序列 + let (sum, count) = K线序列 .iter() - .filter_map(|k| k.指标.read().unwrap().macd_cloned()) + .filter_map(|k| k.指标.read().macd_cloned()) .filter(|m| m.MACD柱 < 0.0) - .map(|m| m.MACD柱.abs()) - .collect(); - if 总.is_empty() { + .fold((0.0, 0usize), |(s, c), m| (s + m.MACD柱.abs(), c + 1)); + if count == 0 { None } else { - Some(总.iter().sum::() / 总.len() as f64) + Some(sum / count as f64) } } /// 计算MACD柱子均值_阳 — 正柱的绝对值均值 - /// 对齐说明:Python 无数据时返回 False(bool),Rust 返回 None,调用方处理等价 pub fn 计算MACD柱子均值_阳(普K序列: &[Arc], 实线: &虚线) -> Option { let K线序列 = K线::截取rc( 普K序列, - &实线.文.中.标的K线.read().unwrap(), - &实线.武.read().unwrap().中.标的K线.read().unwrap(), + &实线.文.中.标的K线.read(), + &实线.武.read().中.标的K线.read(), ); - let 总: Vec = K线序列 + let (sum, count) = K线序列 .iter() - .filter_map(|k| k.指标.read().unwrap().macd_cloned()) + .filter_map(|k| k.指标.read().macd_cloned()) .filter(|m| m.MACD柱 > 0.0) .map(|m| m.MACD柱.abs()) - .collect(); - if 总.is_empty() { + .fold((0.0, 0usize), |(s, c), v| (s + v, c + 1)); + if count == 0 { None } else { - Some(总.iter().sum::() / 总.len() as f64) + Some(sum / count as f64) } } @@ -633,9 +615,9 @@ impl 虚线 { /// 武之全量MACD均值 — 武端MACD柱是否小于均值(背驰) pub fn 武之全量MACD均值(普K序列: &[Arc], 实线: &虚线) -> bool { - let 武_ref = 实线.武.read().unwrap(); - let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.指标.read().unwrap().macd() { + let 武_ref = 实线.武.read(); + let 标 = 武_ref.中.标的K线.read(); + let 武_MACD = match 标.指标.read().macd() { Some(m) => m.MACD柱.abs(), None => return false, }; @@ -653,9 +635,9 @@ impl 虚线 { /// 武之MACD均值_阴 — 武端负柱是否小于阴均值 pub fn 武之MACD均值_阴(普K序列: &[Arc], 实线: &虚线) -> bool { - let 武_ref = 实线.武.read().unwrap(); - let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.指标.read().unwrap().macd() { + let 武_ref = 实线.武.read(); + let 标 = 武_ref.中.标的K线.read(); + let 武_MACD = match 标.指标.read().macd() { Some(m) => m.MACD柱.abs(), None => return false, }; @@ -667,9 +649,9 @@ impl 虚线 { /// 武之MACD均值_阳 — 武端正柱是否小于阳均值 pub fn 武之MACD均值_阳(普K序列: &[Arc], 实线: &虚线) -> bool { - let 武_ref = 实线.武.read().unwrap(); - let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.指标.read().unwrap().macd() { + let 武_ref = 实线.武.read(); + let 标 = 武_ref.中.标的K线.read(); + let 武_MACD = match 标.指标.read().macd() { Some(m) => m.MACD柱.abs(), None => return false, }; @@ -681,20 +663,20 @@ impl 虚线 { /// 武之MACD极值 — 武端MACD柱是否为区间极值 pub fn 武之MACD极值(普K序列: &[Arc], 实线: &虚线) -> bool { - let 武_ref = 实线.武.read().unwrap(); - let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.指标.read().unwrap().macd() { + let 武_ref = 实线.武.read(); + let 标 = 武_ref.中.标的K线.read(); + let 武_MACD = match 标.指标.read().macd() { Some(m) => m.MACD柱, None => return false, }; let K线序列 = K线::截取rc( 普K序列, - &实线.文.中.标的K线.read().unwrap(), - &实线.武.read().unwrap().中.标的K线.read().unwrap(), + &实线.文.中.标的K线.read(), + &实线.武.read().中.标的K线.read(), ); let 所有柱子: Vec = K线序列 .iter() - .filter_map(|k| k.指标.read().unwrap().macd_cloned()) + .filter_map(|k| k.指标.read().macd_cloned()) .map(|m| m.MACD柱) .collect(); if 所有柱子.is_empty() { @@ -709,6 +691,217 @@ impl 虚线 { } } + pub fn _计算K线序列MACD趋向背驰( + 普K序列: &[Arc], 方向: 相对方向 + ) -> [bool; 3] { + if 普K序列.is_empty() { + return [false, false, false]; + } + let 最后 = &普K序列[普K序列.len() - 1]; + + let mut 柱子列表: Vec> = Vec::new(); + let mut 离差列表: Vec> = Vec::new(); + let mut 信号列表: Vec> = Vec::new(); + + if 方向 == 相对方向::向上 { + for k in 普K序列 { + let guard = k.指标.read(); + if let Some(m) = guard.macd() { + if m.MACD柱 > 0.0 { + 柱子列表.push(Arc::clone(k)); + } + if m.DIF.is_some_and(|d| d > 0.0) { + 离差列表.push(Arc::clone(k)); + } + if m.DEA.is_some_and(|d| d > 0.0) { + 信号列表.push(Arc::clone(k)); + } + } + } + } else { + for k in 普K序列 { + let guard = k.指标.read(); + if let Some(m) = guard.macd() { + if m.MACD柱 < 0.0 { + 柱子列表.push(Arc::clone(k)); + } + if m.DIF.is_some_and(|d| d < 0.0) { + 离差列表.push(Arc::clone(k)); + } + if m.DEA.is_some_and(|d| d < 0.0) { + 信号列表.push(Arc::clone(k)); + } + } + } + } + + if 柱子列表.is_empty() { + return [false, false, false]; + } + + let mut 结果 = [false; 3]; + + if 方向 == 相对方向::向上 { + let 最高柱子 = 柱子列表 + .iter() + .max_by(|a, b| { + a.指标 + .read() + .macd() + .unwrap() + .MACD柱 + .partial_cmp(&b.指标.read().macd().unwrap().MACD柱) + .unwrap_or(std::cmp::Ordering::Equal) + }) + .unwrap(); + let mut 柱对 = [Arc::clone(最高柱子), Arc::clone(最后)]; + 柱对.sort_by_key(|k| k.时间戳); + { + let g0 = 柱对[0].指标.read(); + let g1 = 柱对[1].指标.read(); + let m0 = g0.macd().unwrap(); + let m1 = g1.macd().unwrap(); + if m0.MACD柱 > m1.MACD柱 && m0.MACD柱 > 0.0 && 柱对[0].高 < 柱对[1].高 { + 结果[0] = true; + } + } + + if !离差列表.is_empty() { + let 最高离差 = 离差列表 + .iter() + .max_by(|a, b| { + let da = a.指标.read().macd().and_then(|m| m.DIF).unwrap_or(0.0); + let db = b.指标.read().macd().and_then(|m| m.DIF).unwrap_or(0.0); + da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) + }) + .unwrap(); + let d0 = 最高离差 + .指标 + .read() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0); + let d1 = 最后.指标.read().macd().and_then(|m| m.DIF).unwrap_or(0.0); + if d0 > d1 && 最高离差.高 < 最后.高 { + 结果[1] = true; + } + } + + if !信号列表.is_empty() { + let 最高信号 = 信号列表 + .iter() + .max_by(|a, b| { + let ea = a.指标.read().macd().and_then(|m| m.DEA).unwrap_or(0.0); + let eb = b.指标.read().macd().and_then(|m| m.DEA).unwrap_or(0.0); + ea.partial_cmp(&eb).unwrap_or(std::cmp::Ordering::Equal) + }) + .unwrap(); + let e0 = 最高信号 + .指标 + .read() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0); + let e1 = 最后.指标.read().macd().and_then(|m| m.DEA).unwrap_or(0.0); + if e0 > e1 && 最高信号.高 < 最后.高 { + 结果[2] = true; + } + } + } else { + let 最高柱子 = 柱子列表 + .iter() + .max_by(|a, b| { + a.指标 + .read() + .macd() + .unwrap() + .MACD柱 + .abs() + .partial_cmp(&b.指标.read().macd().unwrap().MACD柱.abs()) + .unwrap_or(std::cmp::Ordering::Equal) + }) + .unwrap(); + let mut 柱对 = [Arc::clone(最高柱子), Arc::clone(最后)]; + 柱对.sort_by_key(|k| k.时间戳); + { + let g0 = 柱对[0].指标.read(); + let g1 = 柱对[1].指标.read(); + let m0 = g0.macd().unwrap(); + let m1 = g1.macd().unwrap(); + if m0.MACD柱 < m1.MACD柱 && m0.MACD柱 < 0.0 && 柱对[0].低 > 柱对[1].低 { + 结果[0] = true; + } + } + + if !离差列表.is_empty() { + let 最高离差 = 离差列表 + .iter() + .max_by(|a, b| { + let da = a + .指标 + .read() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0) + .abs(); + let db = b + .指标 + .read() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0) + .abs(); + da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) + }) + .unwrap(); + let d0 = 最高离差 + .指标 + .read() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0); + let d1 = 最后.指标.read().macd().and_then(|m| m.DIF).unwrap_or(0.0); + if d0 < d1 && 最高离差.低 > 最后.低 { + 结果[1] = true; + } + } + + if !信号列表.is_empty() { + let 最高信号 = 信号列表 + .iter() + .max_by(|a, b| { + let ea = a + .指标 + .read() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0) + .abs(); + let eb = b + .指标 + .read() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0) + .abs(); + ea.partial_cmp(&eb).unwrap_or(std::cmp::Ordering::Equal) + }) + .unwrap(); + let e0 = 最高信号 + .指标 + .read() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0); + let e1 = 最后.指标.read().macd().and_then(|m| m.DEA).unwrap_or(0.0); + if e0 < e1 && 最高信号.低 > 最后.低 { + 结果[2] = true; + } + } + } + + 结果 + } // ---- MACD趋向背驰 ---- /// 计算K线序列MACD趋向背驰 — 分析 MACD柱/DIF/DEA 三项背驰信号 @@ -723,13 +916,7 @@ impl 虚线 { if 方向 == 相对方向::向上 { let 柱子序列: Vec<&Arc> = 普K序列 .iter() - .filter(|k| { - k.指标 - .read() - .unwrap() - .macd() - .is_some_and(|m| m.MACD柱 > 0.0) - }) + .filter(|k| k.指标.read().macd().is_some_and(|m| m.MACD柱 > 0.0)) .collect(); if 柱子序列.is_empty() { return [false, false, false]; @@ -743,18 +930,17 @@ impl 虚线 { .max_by(|a, b| { a.指标 .read() - .unwrap() .macd() .unwrap() .MACD柱 - .partial_cmp(&b.指标.read().unwrap().macd().unwrap().MACD柱) + .partial_cmp(&b.指标.read().macd().unwrap().MACD柱) .unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); let mut 柱对 = [Arc::clone(*最高柱子), Arc::clone(最后)]; 柱对.sort_by_key(|k| k.时间戳); - let m0_g = 柱对[0].指标.read().unwrap(); - let m1_g = 柱对[1].指标.read().unwrap(); + let m0_g = 柱对[0].指标.read(); + let m1_g = 柱对[1].指标.read(); if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) && m0.MACD柱 > m1.MACD柱 && 柱对[0].高 < 柱对[1].高 @@ -766,25 +952,13 @@ impl 虚线 { let 最高离差值 = 柱子序列 .iter() .max_by(|a, b| { - let da = a - .指标 - .read() - .unwrap() - .macd() - .and_then(|m| m.DIF) - .unwrap_or(0.0); - let db = b - .指标 - .read() - .unwrap() - .macd() - .and_then(|m| m.DIF) - .unwrap_or(0.0); + let da = a.指标.read().macd().and_then(|m| m.DIF).unwrap_or(0.0); + let db = b.指标.read().macd().and_then(|m| m.DIF).unwrap_or(0.0); da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - let m0_g = 最高离差值.指标.read().unwrap(); - let m1_g = 最后.指标.read().unwrap(); + let m0_g = 最高离差值.指标.read(); + let m1_g = 最后.指标.read(); if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dif0 = m0.DIF.unwrap_or(0.0); let dif1 = m1.DIF.unwrap_or(0.0); @@ -797,25 +971,13 @@ impl 虚线 { let 最高信号线 = 柱子序列 .iter() .max_by(|a, b| { - let da = a - .指标 - .read() - .unwrap() - .macd() - .and_then(|m| m.DEA) - .unwrap_or(0.0); - let db = b - .指标 - .read() - .unwrap() - .macd() - .and_then(|m| m.DEA) - .unwrap_or(0.0); + let da = a.指标.read().macd().and_then(|m| m.DEA).unwrap_or(0.0); + let db = b.指标.read().macd().and_then(|m| m.DEA).unwrap_or(0.0); da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - let m0_g = 最高信号线.指标.read().unwrap(); - let m1_g = 最后.指标.read().unwrap(); + let m0_g = 最高信号线.指标.read(); + let m1_g = 最后.指标.read(); if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dea0 = m0.DEA.unwrap_or(0.0); let dea1 = m1.DEA.unwrap_or(0.0); @@ -828,13 +990,7 @@ impl 虚线 { } else { let 柱子序列: Vec<&Arc> = 普K序列 .iter() - .filter(|k| { - k.指标 - .read() - .unwrap() - .macd() - .is_some_and(|m| m.MACD柱 < 0.0) - }) + .filter(|k| k.指标.read().macd().is_some_and(|m| m.MACD柱 < 0.0)) .collect(); if 柱子序列.is_empty() { return [false, false, false]; @@ -848,19 +1004,18 @@ impl 虚线 { .max_by(|a, b| { a.指标 .read() - .unwrap() .macd() .unwrap() .MACD柱 .abs() - .partial_cmp(&b.指标.read().unwrap().macd().unwrap().MACD柱.abs()) + .partial_cmp(&b.指标.read().macd().unwrap().MACD柱.abs()) .unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); let mut 柱对 = [Arc::clone(*最高柱子), Arc::clone(最后)]; 柱对.sort_by_key(|k| k.时间戳); - let m0_g = 柱对[0].指标.read().unwrap(); - let m1_g = 柱对[1].指标.read().unwrap(); + let m0_g = 柱对[0].指标.read(); + let m1_g = 柱对[1].指标.read(); if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) && m0.MACD柱 < m1.MACD柱 && 柱对[0].低 > 柱对[1].低 @@ -875,7 +1030,6 @@ impl 虚线 { let da = a .指标 .read() - .unwrap() .macd() .and_then(|m| m.DIF) .unwrap_or(0.0) @@ -883,7 +1037,6 @@ impl 虚线 { let db = b .指标 .read() - .unwrap() .macd() .and_then(|m| m.DIF) .unwrap_or(0.0) @@ -891,8 +1044,8 @@ impl 虚线 { da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - let m0_g = 最高离差值.指标.read().unwrap(); - let m1_g = 最后.指标.read().unwrap(); + let m0_g = 最高离差值.指标.read(); + let m1_g = 最后.指标.read(); if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dif0 = m0.DIF.unwrap_or(0.0); let dif1 = m1.DIF.unwrap_or(0.0); @@ -908,7 +1061,6 @@ impl 虚线 { let da = a .指标 .read() - .unwrap() .macd() .and_then(|m| m.DEA) .unwrap_or(0.0) @@ -916,7 +1068,6 @@ impl 虚线 { let db = b .指标 .read() - .unwrap() .macd() .and_then(|m| m.DEA) .unwrap_or(0.0) @@ -924,8 +1075,8 @@ impl 虚线 { da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - let m0_g = 最高信号线.指标.read().unwrap(); - let m1_g = 最后.指标.read().unwrap(); + let m0_g = 最高信号线.指标.read(); + let m1_g = 最后.指标.read(); if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dea0 = m0.DEA.unwrap_or(0.0); let dea1 = m1.DEA.unwrap_or(0.0); @@ -949,7 +1100,7 @@ impl 虚线 { let 符号 = |x: f64| -> &str { if x > 0.0 { "正" } else { "负" } }; - let 首_MACD = match k线序列[0].指标.read().unwrap().macd() { + let 首_MACD = match k线序列[0].指标.read().macd() { Some(m) => m.MACD柱, None => return Vec::new(), }; @@ -958,7 +1109,7 @@ impl 虚线 { let mut 结果 = Vec::new(); for k线 in &k线序列[1..] { - let macd = match k线.指标.read().unwrap().macd() { + let macd = match k线.指标.read().macd() { Some(m) => m.MACD柱, None => continue, }; @@ -1028,12 +1179,17 @@ impl 虚线 { let mut dea_up = 0; let mut dea_down = 0; + let mut golden = 0; + let mut death = 0; + let mut 交叉标记 = vec![0i32]; + for i in 1..普K序列.len() { - let pre_guard = 普K序列[i - 1].指标.read().unwrap(); - let cur_guard = 普K序列[i].指标.read().unwrap(); + let pre_guard = 普K序列[i - 1].指标.read(); + let cur_guard = 普K序列[i].指标.read(); let pre = pre_guard.macd(); let cur = cur_guard.macd(); if pre.is_none() || cur.is_none() { + 交叉标记.push(0); continue; } let (pre_dif, cur_dif) = (pre.unwrap().DIF, cur.unwrap().DIF); @@ -1055,25 +1211,6 @@ impl 虚线 { dea_down += 1; } } - } - - let mut golden = 0; - let mut death = 0; - let mut 交叉标记 = vec![0i32]; - - for i in 1..普K序列.len() { - let pre_guard = 普K序列[i - 1].指标.read().unwrap(); - let cur_guard = 普K序列[i].指标.read().unwrap(); - let pre = pre_guard.macd(); - let cur = cur_guard.macd(); - if pre.is_none() || cur.is_none() { - 交叉标记.push(0); - continue; - } - let pre_dif = pre.unwrap().DIF; - let pre_dea = pre.unwrap().DEA; - let cur_dif = cur.unwrap().DIF; - let cur_dea = cur.unwrap().DEA; if let (Some(pd), Some(cd), Some(pe), Some(ce)) = (pre_dif, cur_dif, pre_dea, cur_dea) { if pd <= pe && cd > ce { @@ -1111,24 +1248,7 @@ impl 虚线 { pub fn 买卖意义( 实线: &虚线, 观察员: &crate::business::observer::观察者 ) -> (bool, String) { - // LRU 缓存查找 - let key = ( - std::ptr::from_ref(实线) as usize, - std::ptr::from_ref(观察员) as usize, - ); - { - let mut cache = 买卖意义缓存.lock().unwrap(); - if let Some(val) = cached::Cached::cache_get(&mut *cache, &key) { - return val.clone(); - } - } - - let result = Self::_买卖意义_计算(实线, 观察员); - { - let mut cache = 买卖意义缓存.lock().unwrap(); - cached::Cached::cache_set(&mut *cache, key, result.clone()); - } - result + Self::_买卖意义_计算(实线, 观察员) } /// 买卖意义 实际计算(无缓存) @@ -1139,39 +1259,38 @@ impl 虚线 { let 普K序列 = &观察员.普通K线序列; let 配置 = &观察员.配置; - if *实线.标识.read().unwrap() != "笔" - && *实线.标识.read().unwrap() != "线段" - && *实线.标识.read().unwrap() != "线段<线段>" + if *实线.标识.read() != "笔" + && *实线.标识.read() != "线段" + && *实线.标识.read() != "线段<线段>" { return (false, "标识不在范围内".into()); } // KDJ指标完整性检查 - let 武_ref = 实线.武.read().unwrap(); - let 标 = 武_ref.中.标的K线.read().unwrap(); - match 标.指标.read().unwrap().kdj() { + let 武_ref = 实线.武.read(); + let 标 = 武_ref.中.标的K线.read(); + match 标.指标.read().kdj() { Some(kdj) if kdj.K.is_some() && kdj.D.is_some() && kdj.J.is_some() => {} _ => return (false, "KDJ指标不完整".into()), } - let 意义 = - Self::缠K买卖点模式(&配置.买卖点_指标模式, &实线.武.read().unwrap().中, 配置); + let 意义 = Self::缠K买卖点模式(&配置.买卖点_指标模式, &实线.武.read().中, 配置); let 结果 = false; - let 背驰过: Vec> = if *实线.标识.read().unwrap() == "笔" { + let 背驰过: Vec> = if *实线.标识.read() == "笔" { crate::algorithm::bi::笔::是否背驰过(实线, 观察员) } else { crate::algorithm::segment::线段::是否背驰过(实线, 观察员) }; if 意义 { - if *实线.标识.read().unwrap() == "笔" { + if *实线.标识.read() == "笔" { if Self::武之MACD均值(普K序列, 实线) { return (true, "武之MACD均值".into()); } if Self::武之MACD极值(普K序列, 实线) && !背驰过.is_empty() { return (true, "背驰过且极值".into()); - } else if 实线.武.read().unwrap().与MACD柱子分型匹配() { + } else if 实线.武.read().与MACD柱子分型匹配() { return ( true, format!( @@ -1186,7 +1305,7 @@ impl 虚线 { ); } } - if *实线.标识.read().unwrap() != "笔" + if *实线.标识.read() != "笔" && crate::algorithm::segment::线段::判断线段内部是否背驰(实线, 观察员) { return (true, "线段内部背驰".into()); @@ -1195,7 +1314,7 @@ impl 虚线 { if !结果 && 意义 - && 实线.武.read().unwrap().中.与MACD柱子匹配() + && 实线.武.read().中.与MACD柱子匹配() && Self::武之MACD极值(普K序列, 实线) && 背驰过.len() > 2 { @@ -1207,13 +1326,13 @@ impl 虚线 { /// 结构化相等校验 — 递归校验所有子结构(分型/缺口/缠K/中枢/线段特征/虚线),返回 (是否相等, 差异描述) pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { - if *self.标识.read().unwrap() != *other.标识.read().unwrap() { + if *self.标识.read() != *other.标识.read() { return ( false, format!( "虚线: [标识] 不等 A={},B={}", - self.标识.read().unwrap(), - other.标识.read().unwrap() + self.标识.read(), + other.标识.read() ), ); } @@ -1246,11 +1365,7 @@ impl 虚线 { } // 武 { - let (eq, msg) = self - .武 - .read() - .unwrap() - .相等(&other.武.read().unwrap(), 浮点容差); + let (eq, msg) = self.武.read().相等(&other.武.read(), 浮点容差); if !eq { return (false, format!("虚线: [武]分型异常 >> {msg}")); } @@ -1267,8 +1382,8 @@ impl 虚线 { } // 基础序列 { - let a = self.基础序列.read().unwrap(); - let b = other.基础序列.read().unwrap(); + let a = self.基础序列.read(); + let b = other.基础序列.read(); if a.len() != b.len() { return ( false, @@ -1284,8 +1399,8 @@ impl 虚线 { } // 特征序列 { - let a = self.特征序列.read().unwrap(); - let b = other.特征序列.read().unwrap(); + let a = self.特征序列.read(); + let b = other.特征序列.read(); if a.len() != b.len() { return ( false, @@ -1325,33 +1440,30 @@ impl 虚线 { }; 检查中枢列表( "实_中枢序列", - &self.实_中枢序列.read().unwrap(), - &other.实_中枢序列.read().unwrap(), + &self.实_中枢序列.read(), + &other.实_中枢序列.read(), 浮点容差, ) .map_err(|e| (false, e)) .ok(); 检查中枢列表( "虚_中枢序列", - &self.虚_中枢序列.read().unwrap(), - &other.虚_中枢序列.read().unwrap(), + &self.虚_中枢序列.read(), + &other.虚_中枢序列.read(), 浮点容差, ) .map_err(|e| (false, e)) .ok(); 检查中枢列表( "合_中枢序列", - &self.合_中枢序列.read().unwrap(), - &other.合_中枢序列.read().unwrap(), + &self.合_中枢序列.read(), + &other.合_中枢序列.read(), 浮点容差, ) .map_err(|e| (false, e)) .ok(); // 确认K线 - match ( - &*self.确认K线.read().unwrap(), - &*other.确认K线.read().unwrap(), - ) { + match (&*self.确认K线.read(), &*other.确认K线.read()) { (None, None) => {} (Some(a), Some(b)) => { let (eq, msg) = a.相等(b, 浮点容差); @@ -1371,13 +1483,13 @@ impl 虚线 { } } // 模式 - if *self.模式.read().unwrap() != *other.模式.read().unwrap() { + if *self.模式.read() != *other.模式.read() { return ( false, format!( "虚线: [模式] 不等 A={},B={}", - self.模式.read().unwrap(), - other.模式.read().unwrap() + self.模式.read(), + other.模式.read() ), ); } @@ -1388,10 +1500,7 @@ impl 虚线 { return (false, "虚线: [_特征序列_显示] 不等".to_string()); } // 前一缺口 - match ( - &*self.前一缺口.read().unwrap(), - &*other.前一缺口.read().unwrap(), - ) { + match (&*self.前一缺口.read(), &*other.前一缺口.read()) { (None, None) => {} (Some(a), Some(b)) => { let (eq, msg) = a.相等(b, 浮点容差); @@ -1411,10 +1520,7 @@ impl 虚线 { } } // 前一结束位置 - match ( - &*self.前一结束位置.read().unwrap(), - &*other.前一结束位置.read().unwrap(), - ) { + match (&*self.前一结束位置.read(), &*other.前一结束位置.read()) { (None, None) => {} (Some(a), Some(b)) => { let (eq, msg) = a.相等(b, 浮点容差); @@ -1443,16 +1549,16 @@ impl 虚线 { impl std::fmt::Display for 虚线 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - if *self.标识.read().unwrap() == "笔" { + if *self.标识.read() == "笔" { write!( f, "笔({}, {}, {}, {}, 周期: {}, 数量: {})", self.序号.load(Ordering::Relaxed), self.方向(), self.文, - self.武.read().unwrap(), + self.武.read(), self.文.中.周期, - self.武.read().unwrap().中.序号.load(Ordering::Relaxed) + self.武.read().中.序号.load(Ordering::Relaxed) - self.文.中.序号.load(Ordering::Relaxed) + 1 ) @@ -1463,20 +1569,20 @@ impl std::fmt::Display for 虚线 { Some(g) => format!("{}", g), None => "None".to_string(), }; - let 确认K线_str = match &*self.确认K线.read().unwrap() { + let 确认K线_str = match &*self.确认K线.read() { Some(k) => format!("{}", k), None => "None".to_string(), }; write!( f, "{}<{}, {}, {}, {}, {}, 数量: {}, 缺口: {}, {}>", - self.标识.read().unwrap(), + self.标识.read(), self.序号.load(Ordering::Relaxed), 四象, self.方向(), self.文, - self.武.read().unwrap(), - self.基础序列.read().unwrap().len(), + self.武.read(), + self.基础序列.read().len(), 缺口_str, 确认K线_str, ) @@ -1490,6 +1596,7 @@ mod tests { use crate::kline::bar::K线; use crate::kline::chan_kline::缠论K线; use crate::types::分型结构; + use crate::{error, info, warn}; /// 辅助:创建一根最小化的原始K线 fn 辅助_创建K线(时间戳: i64, 高: f64, 低: f64, 开: f64, 收: f64) -> K线 { @@ -1585,18 +1692,19 @@ mod tests { assert_eq!(笔.序号.load(Ordering::Relaxed), 0); assert!(笔.有效性.load(Ordering::Relaxed)); assert!(!笔.短路修正.load(Ordering::Relaxed)); - assert!(笔.前一缺口.read().unwrap().is_none()); + assert!(笔.前一缺口.read().is_none()); // 修改 Cell 字段 笔.序号.store(42, Ordering::Relaxed); 笔.有效性.store(false, Ordering::Relaxed); 笔.短路修正.store(true, Ordering::Relaxed); - *笔.前一缺口.write().unwrap() = Some(缺口::new(200.0, 100.0)); + *笔.前一缺口.write() = Some(缺口::new(200.0, 100.0)); assert_eq!(笔.序号.load(Ordering::Relaxed), 42); assert!(!笔.有效性.load(Ordering::Relaxed)); assert!(笔.短路修正.load(Ordering::Relaxed)); - let qk = 笔.前一缺口.read().unwrap().unwrap(); + let qk = 笔.前一缺口.read(); + let qk = qk.as_ref().unwrap(); assert!((qk.高 - 200.0).abs() < 0.01); assert!((qk.低 - 100.0).abs() < 0.01); } @@ -1612,27 +1720,27 @@ mod tests { let 笔 = 虚线::创建笔(顶, 底, true); // 标识 - assert_eq!(*笔.标识.read().unwrap(), "笔"); - *笔.标识.write().unwrap() = "测试标识".into(); - assert_eq!(*笔.标识.read().unwrap(), "测试标识"); + assert_eq!(*笔.标识.read(), "笔"); + *笔.标识.write() = "测试标识".into(); + assert_eq!(*笔.标识.read(), "测试标识"); // 模式 - assert_eq!(*笔.模式.read().unwrap(), "文武"); - *笔.模式.write().unwrap() = "全量".into(); - assert_eq!(*笔.模式.read().unwrap(), "全量"); + assert_eq!(*笔.模式.read(), "文武"); + *笔.模式.write() = "全量".into(); + assert_eq!(*笔.模式.read(), "全量"); // 基础序列 - assert!(笔.基础序列.read().unwrap().is_empty()); + assert!(笔.基础序列.read().is_empty()); let 另一底 = 辅助_创建底分型(300, 20.0, 10.0, 15); - let 笔2 = 虚线::创建笔(Arc::clone(&*笔.武.read().unwrap()), 另一底, true); - 笔.基础序列.write().unwrap().push(Arc::new(笔2)); - assert_eq!(笔.基础序列.read().unwrap().len(), 1); + let 笔2 = 虚线::创建笔(Arc::clone(&*笔.武.read()), 另一底, true); + 笔.基础序列.write().push(Arc::new(笔2)); + assert_eq!(笔.基础序列.read().len(), 1); // 武 - Replace with new 分型 let 新底 = 辅助_创建底分型(400, 15.0, 5.0, 20); let 新底_ptr = Arc::as_ptr(&新底); - *笔.武.write().unwrap() = Arc::clone(&新底); - assert_eq!(Arc::as_ptr(&*笔.武.read().unwrap()), 新底_ptr); + *笔.武.write() = Arc::clone(&新底); + assert_eq!(Arc::as_ptr(&*笔.武.read()), 新底_ptr); } // ============================================================ @@ -1652,11 +1760,8 @@ mod tests { assert_eq!(Arc::as_ptr(&克隆笔.文), Arc::as_ptr(&笔.文)); // 武 Rc 指针应一致 - assert_eq!(Arc::as_ptr(&*笔.武.read().unwrap()), Arc::as_ptr(&底)); - assert_eq!( - Arc::as_ptr(&*克隆笔.武.read().unwrap()), - Arc::as_ptr(&*笔.武.read().unwrap()) - ); + assert_eq!(Arc::as_ptr(&*笔.武.read()), Arc::as_ptr(&底)); + assert_eq!(Arc::as_ptr(&*克隆笔.武.read()), Arc::as_ptr(&*笔.武.read())); } #[test] @@ -1693,14 +1798,14 @@ mod tests { assert_eq!(笔_rc2.序号.load(Ordering::Relaxed), 77); // 通过 rc1 修改 RefCell - *笔_rc1.模式.write().unwrap() = "配置".into(); - assert_eq!(*笔_rc2.模式.read().unwrap(), "配置"); + *笔_rc1.模式.write() = "配置".into(); + assert_eq!(*笔_rc2.模式.read(), "配置"); // 通过 rc1 修改 武 let 新底 = 辅助_创建底分型(400, 15.0, 5.0, 20); let 新底_ptr = Arc::as_ptr(&新底); - *笔_rc1.武.write().unwrap() = Arc::clone(&新底); - assert_eq!(Arc::as_ptr(&*笔_rc2.武.read().unwrap()), 新底_ptr); + *笔_rc1.武.write() = Arc::clone(&新底); + assert_eq!(Arc::as_ptr(&*笔_rc2.武.read()), 新底_ptr); } // ============================================================ @@ -1714,7 +1819,7 @@ mod tests { let 笔 = 虚线::创建笔(Arc::clone(&顶), Arc::clone(&底), true); let wu = 笔.获取_武(); - assert_eq!(Arc::as_ptr(&*笔.武.read().unwrap()), Arc::as_ptr(&wu)); + assert_eq!(Arc::as_ptr(&*笔.武.read()), Arc::as_ptr(&wu)); assert_eq!(Arc::as_ptr(&wu), Arc::as_ptr(&底)); } @@ -1754,13 +1859,13 @@ mod tests { let 文_ptr_before = Arc::as_ptr(&笔.文); // 修改武 - *笔.武.write().unwrap() = Arc::clone(&底2); + *笔.武.write() = Arc::clone(&底2); // 文指针不变 assert_eq!(Arc::as_ptr(&笔.文), 文_ptr_before); // 但方向变了(因为武从底1变成底2) - let 新武耗时 = 笔.武.read().unwrap().时间戳(); + let 新武耗时 = 笔.武.read().时间戳(); assert_eq!(新武耗时, 300); } } diff --git a/chanlun/src/structure/fractal_obj.rs b/chanlun/src/structure/fractal_obj.rs index 129bd96..8df91cb 100644 --- a/chanlun/src/structure/fractal_obj.rs +++ b/chanlun/src/structure/fractal_obj.rs @@ -25,10 +25,10 @@ use crate::kline::chan_kline::缠论K线; use crate::types::分型结构; use crate::types::相对方向; +use crate::warn; use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; -use tracing::warn; /// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取 pub static 分型模式: AtomicBool = AtomicBool::new(true); @@ -75,7 +75,7 @@ impl 分型 { 右.时间戳.load(Ordering::Relaxed), ); } - let 结构 = 中.分型.read().unwrap().unwrap_or(分型结构::散); + let 结构 = 中.分型.read().unwrap_or(分型结构::散); let 时间戳 = 中.时间戳.load(Ordering::Relaxed); let 分型特征值 = 中.分型特征值.get(); Self { @@ -102,7 +102,7 @@ impl 分型 { if 分型模式.load(Ordering::Relaxed) { self.结构 } else { - self.中.分型.read().unwrap().unwrap_or(分型结构::散) // FIXME 错误 + self.中.分型.read().unwrap_or(分型结构::散) // FIXME 错误 } } @@ -157,19 +157,17 @@ impl 分型 { if let (Some(左), Some(右)) = (&self.左, &self.右) { if self.结构() == 分型结构::底 { - if 右.标的K线.read().unwrap().收盘价 > 左.标的K线.read().unwrap().高 { + if 右.标的K线.read().收盘价 > 左.标的K线.read().高 { return "强"; - } else if 右.标的K线.read().unwrap().收盘价 > self.中.标的K线.read().unwrap().高 - { + } else if 右.标的K线.read().收盘价 > self.中.标的K线.read().高 { return "中"; } else { return "弱"; } } else if self.结构() == 分型结构::顶 { - if 右.标的K线.read().unwrap().收盘价 < 左.标的K线.read().unwrap().低 { + if 右.标的K线.read().收盘价 < 左.标的K线.read().低 { return "强"; - } else if 右.标的K线.read().unwrap().收盘价 < self.中.标的K线.read().unwrap().低 - { + } else if 右.标的K线.read().收盘价 < self.中.标的K线.read().低 { return "中"; } else { return "弱"; @@ -183,12 +181,12 @@ impl 分型 { pub fn 与MACD柱子分型匹配(&self) -> bool { if let (Some(左), Some(右)) = (&self.左, &self.右) { if self.结构() == 分型结构::底 { - let 左_k = 左.标的K线.read().unwrap(); - let 中_k = self.中.标的K线.read().unwrap(); - let 右_k = 右.标的K线.read().unwrap(); - let 左_m = 左_k.指标.read().unwrap(); - let 中_m = 中_k.指标.read().unwrap(); - let 右_m = 右_k.指标.read().unwrap(); + let 左_k = 左.标的K线.read(); + let 中_k = self.中.标的K线.read(); + let 右_k = 右.标的K线.read(); + let 左_m = 左_k.指标.read(); + let 中_m = 中_k.指标.read(); + let 右_m = 右_k.指标.read(); if let (Some(左macd), Some(中macd), Some(右macd)) = (左_m.macd(), 中_m.macd(), 右_m.macd()) { @@ -196,12 +194,12 @@ impl 分型 { } } if self.结构() == 分型结构::顶 { - let 左_k = 左.标的K线.read().unwrap(); - let 中_k = self.中.标的K线.read().unwrap(); - let 右_k = 右.标的K线.read().unwrap(); - let 左_m = 左_k.指标.read().unwrap(); - let 中_m = 中_k.指标.read().unwrap(); - let 右_m = 右_k.指标.read().unwrap(); + let 左_k = 左.标的K线.read(); + let 中_k = self.中.标的K线.read(); + let 右_k = 右.标的K线.read(); + let 左_m = 左_k.指标.read(); + let 中_m = 中_k.指标.read(); + let 右_m = 右_k.指标.read(); if let (Some(左macd), Some(中macd), Some(右macd)) = (左_m.macd(), 中_m.macd(), 右_m.macd()) { @@ -214,7 +212,7 @@ impl 分型 { /// 判断两个分型是否匹配 pub fn 判断分型(左: &Arc<分型>, 右: &Arc<分型>, _模式: &str) -> bool { - Arc::as_ptr(左) == Arc::as_ptr(右) + Arc::ptr_eq(左, 右) } /// 从缠K序列中获取以指定缠K为中元素的分型 @@ -222,9 +220,7 @@ impl 分型 { K线序列: &[Arc<缠论K线>], 中: &Arc<缠论K线>, ) -> Option { - let idx = K线序列 - .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(中))?; + let idx = K线序列.iter().position(|k| Arc::ptr_eq(k, 中))?; let 左 = if idx > 0 { Some(Arc::clone(&K线序列[idx - 1])) } else { diff --git a/chanlun/src/structure/segment_feat.rs b/chanlun/src/structure/segment_feat.rs index ba1edf0..4981def 100644 --- a/chanlun/src/structure/segment_feat.rs +++ b/chanlun/src/structure/segment_feat.rs @@ -26,9 +26,10 @@ use crate::structure::dash_line::虚线; use crate::structure::feat_fractal::特征分型; use crate::structure::fractal_obj::分型; use crate::types::{分型结构, 相对方向}; +use parking_lot::RwLock; +use std::sync::Arc; use std::sync::atomic::AtomicI64; use std::sync::atomic::Ordering; -use std::sync::{Arc, RwLock}; /// 线段特征 — 特征序列元素,内部是虚线的集合。 /// @@ -62,7 +63,7 @@ impl Clone for 线段特征 { fn clone(&self) -> Self { Self { 序号: AtomicI64::new(self.序号.load(Ordering::Relaxed)), - 标识: RwLock::new(self.标识.read().unwrap().clone()), + 标识: RwLock::new(self.标识.read().clone()), 线段方向: self.线段方向, 基础序列: self.基础序列.clone(), } @@ -82,7 +83,7 @@ impl 线段特征 { /// 图表标题 — 返回标识字符串 pub fn 图表标题(&self) -> String { - self.标识.read().unwrap().clone() + self.标识.read().clone() } /// 文 — 取特征序列元素中分型特征值最大/最小的文分型 @@ -118,47 +119,31 @@ impl 线段特征 { /// 武 — 取特征序列元素中分型特征值最大/最小的武分型 /// tiebreaker: later时间戳 wins when特征值 equal (matches Python) pub fn 武(&self) -> Arc<分型> { - if self.线段方向.是否向上() { - self.基础序列 - .iter() - .max_by(|a, b| { - a.武 - .read() - .unwrap() - .分型特征值 - .partial_cmp(&b.武.read().unwrap().分型特征值) - .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| { - a.武 - .read() - .unwrap() - .时间戳() - .cmp(&b.武.read().unwrap().时间戳()) - }) - }) - .map(|x| x.武.read().unwrap().clone()) - .unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone()) + let best = if self.线段方向.是否向上() { + self.基础序列.iter().max_by(|a, b| { + let a_武 = a.武.read(); + let b_武 = b.武.read(); + a_武 + .分型特征值 + .partial_cmp(&b_武.分型特征值) + .unwrap_or(std::cmp::Ordering::Equal) + .then_with(|| a_武.时间戳().cmp(&b_武.时间戳())) + }) } else { - self.基础序列 - .iter() - .max_by(|a, b| { - b.武 - .read() - .unwrap() - .分型特征值 - .partial_cmp(&a.武.read().unwrap().分型特征值) - .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| { - a.武 - .read() - .unwrap() - .时间戳() - .cmp(&b.武.read().unwrap().时间戳()) - }) - }) - .map(|x| x.武.read().unwrap().clone()) - .unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone()) - } + self.基础序列.iter().max_by(|a, b| { + let a_武 = a.武.read(); + let b_武 = b.武.read(); + b_武 + .分型特征值 + .partial_cmp(&a_武.分型特征值) + .unwrap_or(std::cmp::Ordering::Equal) + .then_with(|| a_武.时间戳().cmp(&b_武.时间戳())) + }) + }; + best.map_or_else( + || self.基础序列[0].武.read().clone(), + |x| x.武.read().clone(), + ) } /// 高 — 文和武中分型特征值的较大者 @@ -197,7 +182,7 @@ impl 线段特征 { if let Some(pos) = self .基础序列 .iter() - .position(|x| Arc::as_ptr(x) == Arc::as_ptr(待删除虚线)) + .position(|x| Arc::ptr_eq(x, 待删除虚线)) { self.基础序列.remove(pos); Ok(()) @@ -256,7 +241,7 @@ impl 线段特征 { .unwrap(); let fake = 虚线::创建笔( Arc::clone(&小号虚线.文), - 大号虚线.武.read().unwrap().clone(), + 大号虚线.武.read().clone(), false, ); 结果.pop(); @@ -330,13 +315,13 @@ impl 线段特征 { ), ); } - if *self.标识.read().unwrap() != *other.标识.read().unwrap() { + if *self.标识.read() != *other.标识.read() { return ( false, format!( "线段特征: [标识] 不等 A={},B={}", - self.标识.read().unwrap(), - other.标识.read().unwrap() + self.标识.read(), + other.标识.read() ), ); } @@ -381,12 +366,12 @@ impl crate::types::fractal::有高低 for 线段特征 { impl std::fmt::Display for 线段特征 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { if self.基础序列.is_empty() { - write!(f, "{}<{}, 空>", self.标识.read().unwrap(), self.线段方向) + write!(f, "{}<{}, 空>", self.标识.read(), self.线段方向) } else { write!( f, "{}<{}, {}, {}, {}>", - self.标识.read().unwrap(), + self.标识.read(), self.线段方向, self.文(), self.武(), diff --git a/chanlun/src/types/direction.rs b/chanlun/src/types/direction.rs index 5b119ec..e389c8b 100644 --- a/chanlun/src/types/direction.rs +++ b/chanlun/src/types/direction.rs @@ -122,6 +122,34 @@ impl 相对方向 { 前高, 前低, 后高, 后低 ); } + + /// 从可选方向序列中随机选取指定数量(与 chan.py 对齐) + pub fn 从序列中机选( + 数量: usize, + 可选方向: &[相对方向], + 可重复: bool, + ) -> Vec<相对方向> { + if 数量 == 0 || 可选方向.is_empty() { + return Vec::new(); + } + if !可重复 && 数量 > 可选方向.len() { + panic!("数量超过可选方向数"); + } + let mut result = Vec::with_capacity(数量); + if 可重复 { + for _ in 0..数量 { + let idx = fastrand::usize(..可选方向.len()); + result.push(可选方向[idx]); + } + } else { + let mut indices: Vec = (0..可选方向.len()).collect(); + fastrand::shuffle(&mut indices); + for &idx in indices.iter().take(数量) { + result.push(可选方向[idx]); + } + } + result + } } impl std::fmt::Display for 相对方向 { diff --git a/chanlun/src/types/fractal.rs b/chanlun/src/types/fractal.rs index aad4e75..8b28ac2 100644 --- a/chanlun/src/types/fractal.rs +++ b/chanlun/src/types/fractal.rs @@ -22,8 +22,8 @@ * SOFTWARE. */ +use crate::warn; use serde::{Deserialize, Serialize}; -use tracing::warn; /// 分型结构 —— 三根K线构成的结构形态 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]