2 Commits

Author SHA1 Message Date
YuWuKunCheng 1477cde86b 优化代码逻辑 2026-06-12 14:41:50 +08:00
YuWuKunCheng da5222d960 添加 全局变量“扩展线段模式” 默认为 True
线段 扩展分析采用“端点高, 端点低” 作为高低取值。中枢分析时高低取值不变,此举在中枢<扩展**> 中更贴合原文同级别分级时的中枢高低取值
2026-06-10 23:21:33 +08:00
44 changed files with 3744 additions and 2373 deletions
+292 -78
View File
@@ -31,12 +31,14 @@ from __future__ import annotations
import json import json
import math import math
import os import os
from collections import deque
import random
import struct import struct
import sys import sys
import tempfile import tempfile
import datetime as datetime_module import datetime as datetime_module
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime, timedelta
from enum import Enum from enum import Enum
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
@@ -52,6 +54,7 @@ from typing import (
Sequence, Sequence,
Callable, Callable,
Set, Set,
Generator,
) )
from loguru import logger from loguru import logger
@@ -1425,6 +1428,23 @@ class 相对方向(Enum):
return 相对方向. return 相对方向.
raise RuntimeError("无法识别的方向") 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): class 分型结构(Enum):
"""描述三根K线构成的顶底分型形态。 """描述三根K线构成的顶底分型形态。
@@ -1797,10 +1817,11 @@ class 相对强弱指数:
下跌幅度: float = 0.0, 下跌幅度: float = 0.0,
平滑系数: float = 0.0, 平滑系数: float = 0.0,
RSI_SMA: Optional[float] = None, RSI_SMA: Optional[float] = None,
RSI历史队列: List[float] = None, RSI历史队列: Optional[deque[float]] = None,
RSI和: float = 0.0,
): ):
if RSI历史队列 is None: if RSI历史队列 is None:
RSI历史队列 = [] RSI历史队列 = deque()
# 原始数据 # 原始数据
self.时间戳 = 时间戳 self.时间戳 = 时间戳
@@ -1829,6 +1850,7 @@ class 相对强弱指数:
# RSI的SMA(信号线)相关字段 # RSI的SMA(信号线)相关字段
self.RSI_SMA = RSI_SMA self.RSI_SMA = RSI_SMA
self.RSI历史队列 = RSI历史队列 self.RSI历史队列 = RSI历史队列
self.RSI和 = RSI和
@classmethod @classmethod
def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: 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(简单移动平均) -----
RSI_SMA = None 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: if RSI_SMA周期 is not None and RSI_SMA周期 > 0 and RSI is not None:
# 将当前RSI加入队列
历史队列.append(RSI) 历史队列.append(RSI)
# 保持队列长度不超过周期 RSI和 += RSI
if len(历史队列) > RSI_SMA周期: if len(历史队列) > RSI_SMA周期:
历史队列.pop(0) RSI和 -= 历史队列.popleft()
# 计算SMA(即使队列未满也计算当前平均值)
if 历史队列: if 历史队列:
RSI_SMA = sum(历史队列) / len(历史队列) RSI_SMA = RSI和 / len(历史队列)
else: else:
# 未启用SMA,清空队列 历史队列 = deque()
历史队列 = [] RSI和 = 0.0
return cls( return cls(
时间戳=当前时间, 时间戳=当前时间,
@@ -1945,6 +1966,7 @@ class 相对强弱指数:
RSI_SMA周期=RSI_SMA周期, RSI_SMA周期=RSI_SMA周期,
RSI_SMA=RSI_SMA, RSI_SMA=RSI_SMA,
RSI历史队列=历史队列, RSI历史队列=历史队列,
RSI和=RSI和,
) )
@classmethod @classmethod
@@ -1996,16 +2018,16 @@ class 随机指标:
K: Optional[float] = None, K: Optional[float] = None,
D: Optional[float] = None, D: Optional[float] = None,
J: Optional[float] = None, J: Optional[float] = None,
历史最高价队列: list[float] = None, 历史最高价队列: Optional[deque[float]] = None,
历史最低价队列: list[float] = None, 历史最低价队列: Optional[deque[float]] = None,
前一个RSV: Optional[float] = None, 前一个RSV: Optional[float] = None,
前一个K: Optional[float] = None, 前一个K: Optional[float] = None,
前一个D: Optional[float] = None, 前一个D: Optional[float] = None,
): ):
if 历史最高价队列 is None: if 历史最高价队列 is None:
历史最高价队列 = [] 历史最高价队列 = deque()
if 历史最低价队列 is None: if 历史最低价队列 is None:
历史最低价队列 = [] 历史最低价队列 = deque()
# 原始数据 # 原始数据
self.时间戳 = 时间戳 self.时间戳 = 时间戳
@@ -2065,8 +2087,8 @@ class 随机指标:
K=None, K=None,
D=None, D=None,
J=None, J=None,
历史最高价队列=[初始最高价], 历史最高价队列=deque([初始最高价]),
历史最低价队列=[初始最低价], 历史最低价队列=deque([初始最低价]),
前一个RSV=None, 前一个RSV=None,
前一个K=None, 前一个K=None,
前一个D=None, 前一个D=None,
@@ -2113,13 +2135,13 @@ class 随机指标:
历史最高价 = 前一个KDJ.历史最高价队列.copy() 历史最高价 = 前一个KDJ.历史最高价队列.copy()
历史最高价.append(当前最高价) 历史最高价.append(当前最高价)
if len(历史最高价) > N: if len(历史最高价) > N:
历史最高价.pop(0) 历史最高价.popleft()
# 更新历史最低价队列 # 更新历史最低价队列
历史最低价 = 前一个KDJ.历史最低价队列.copy() 历史最低价 = 前一个KDJ.历史最低价队列.copy()
历史最低价.append(当前最低价) 历史最低价.append(当前最低价)
if len(历史最低价) > N: if len(历史最低价) > N:
历史最低价.pop(0) 历史最低价.popleft()
# 计算RSV(需要队列长度达到N才能计算) # 计算RSV(需要队列长度达到N才能计算)
RSV = None RSV = None
@@ -2218,7 +2240,7 @@ class 布林带:
self.上轨 = 上轨 self.上轨 = 上轨
self.中轨 = 中轨 self.中轨 = 中轨
self.下轨 = 下轨 self.下轨 = 下轨
self._历史队列 = 历史队列 if 历史队列 is not None else [] self._历史队列 = 历史队列 if 历史队列 is not None else deque()
self._均值 = _均值 self._均值 = _均值
self._方差和 = _方差和 self._方差和 = _方差和
@@ -2233,7 +2255,7 @@ class 布林带:
:return: 初始的布林带实例 :return: 初始的布林带实例
""" """
价格 = 指标.K线取值(k线, 计算方式) 价格 = 指标.K线取值(k线, 计算方式)
return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格]) return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=deque([价格]))
@classmethod @classmethod
def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带: def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带:
@@ -2251,7 +2273,7 @@ class 布林带:
q = prev._历史队列.copy() q = prev._历史队列.copy()
q.append(当前价) q.append(当前价)
if len(q) > 周期: if len(q) > 周期:
q.pop(0) q.popleft()
# 增量均值和方差 # 增量均值和方差
if len(q) < 周期: if len(q) < 周期:
@@ -2969,6 +2991,58 @@ class K线:
""" """
return 序列[序列.index() : 序列.index() + 1] 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线: class 缠论K线:
"""经包含处理后的标准化K线,有方向和分型结构标记。 """经包含处理后的标准化K线,有方向和分型结构标记。
@@ -3488,6 +3562,9 @@ class 分型:
分型序列.append(当前分型) 分型序列.append(当前分型)
扩展线段模式 = True # TODO 虚线高低取值 暂定,此举将符合同级别分解时正确的高低取值涉及中枢等问题
class 虚线: class 虚线:
"""笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。 """笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。
@@ -3615,15 +3692,29 @@ class 虚线:
case _: case _:
raise RuntimeError("无法识别的方向", self..结构, self..结构) raise RuntimeError("无法识别的方向", self..结构, self..结构)
@property
def 端点高(self) -> float:
if self.方向 is 相对方向.向上:
return self...
return self...
@property
def 端点低(self) -> float:
if self.方向 is 相对方向.向下:
return self...
return self...
@property @property
def (self) -> float: def (self) -> float:
"""虚线区间的最高价。 """虚线区间的最高价。
:return: 向上虚线取武..向下虚线取文.. :return: 向上虚线取武..向下虚线取文..
""" """
if self.方向 is 相对方向.向上: if 扩展线段模式 and self.模式 != "文武" and self.标识 != "" and "扩展" in self.标识: # 扩展线段
return self... 端点序列 = [. for in self.基础序列]
return self... 端点序列.append(self.基础序列[-1].)
return max(端点序列, key=lambda o: o..)..
return self.端点高
@property @property
def (self) -> float: def (self) -> float:
@@ -3631,9 +3722,11 @@ class 虚线:
:return: 向下虚线取武..向上虚线取文.. :return: 向下虚线取武..向上虚线取文..
""" """
if self.方向 is 相对方向.向下: if 扩展线段模式 and self.模式 != "文武" and self.标识 != "" and "扩展" in self.标识: # 扩展线段
return self... 端点序列 = [. for in self.基础序列]
return self... 端点序列.append(self.基础序列[-1].)
return min(端点序列, key=lambda o: o..)..
return self.端点低
def 之前是(self, 之前: 虚线) -> bool: def 之前是(self, 之前: 虚线) -> bool:
""" """
@@ -3704,7 +3797,7 @@ class 虚线:
.实_中枢序列 = [] .实_中枢序列 = []
.虚_中枢序列 = [] .虚_中枢序列 = []
.合_中枢序列 = [] .合_中枢序列 = []
.基础序列 = 虚线序列 .基础序列 = 虚线序列[:]
return return
@classmethod @classmethod
@@ -3873,6 +3966,111 @@ class 虚线:
return True return True
return False 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 @classmethod
def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向):
"""计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断)
@@ -4411,8 +4609,7 @@ class 笔:
临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck) 临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck)
递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) 递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
if 分型序列 and 分型序列[-1] is 临时分型: if 分型序列 and 分型序列[-1] is 临时分型:
"""""" logger.warning(f"笔.分析 事后修复错过的笔:{临时分型}, 当前分型: {当前分型}")
# logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型)
递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) 递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
return 递归层次 return 递归层次
@@ -4570,14 +4767,14 @@ class 线段特征:
return self.标识 # f"{self.标识}:{self.序号}" return self.标识 # f"{self.标识}:{self.序号}"
def __str__(self): def __str__(self):
if not len(self): if not len(self.基础序列):
return f"{self.标识}<{self.线段方向}, 空>" return f"{self.标识}<{self.线段方向}, 空>"
return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self)}>" return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self.基础序列)}>"
def __repr__(self): def __repr__(self):
if not len(self): if not len(self.基础序列):
return f"{self.标识}<{self.线段方向}, 空>" return f"{self.标识}<{self.线段方向}, 空>"
return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self)}>" return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self.基础序列)}>"
@property @property
def (self) -> 分型: def (self) -> 分型:
@@ -4788,6 +4985,11 @@ class 线段:
__slots__ = [] __slots__ = []
@staticmethod
def _索引(序列: list, ) -> int:
"""O(1) index lookup — 序列元素序号连续递增。"""
return .序号 - 序列[0].序号
@classmethod @classmethod
def _添加虚线(cls, : 虚线, : 虚线): def _添加虚线(cls, : 虚线, : 虚线):
"""向线段中添加一笔 """向线段中添加一笔
@@ -4918,7 +5120,7 @@ class 线段:
break break
if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0): if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0):
.基础序列[:] = 基础序列[:] .基础序列[:] = 基础序列
else: else:
raise RuntimeError() raise RuntimeError()
else: else:
@@ -4935,7 +5137,7 @@ class 线段:
return return
基础序列 = .基础序列 基础序列 = .基础序列
if .前一结束位置 and .前一结束位置 in 基础序列: if .前一结束位置 and .前一结束位置 in 基础序列:
基础序列 = .基础序列[.基础序列.index(.前一结束位置) - 1 :] 基础序列 = .基础序列[cls._索引(.基础序列, .前一结束位置) - 1 :]
特征序列 = 线段特征.静态分析(基础序列, .方向, 线段.四象(), 配置.线段_特征序列忽视老阴老阳) 特征序列 = 线段特征.静态分析(基础序列, .方向, 线段.四象(), 配置.线段_特征序列忽视老阴老阳)
if len(特征序列) >= 3: if len(特征序列) >= 3:
@@ -5053,7 +5255,7 @@ class 线段:
特征后一笔 = 最近特征.基础序列[-1] 特征后一笔 = 最近特征.基础序列[-1]
if 特征后一笔 is not None: if 特征后一笔 is not None:
序号 = .基础序列.index(特征后一笔) 序号 = cls._索引(.基础序列, 特征后一笔)
if 序号 < len(.基础序列) - 1: if 序号 < len(.基础序列) - 1:
下一笔 = .基础序列[序号 + 1] 下一笔 = .基础序列[序号 + 1]
if (.方向 is 相对方向.向上 and . <= 下一笔.) or (.方向 is 相对方向.向下 and . >= 下一笔.): if (.方向 is 相对方向.向上 and . <= 下一笔.) or (.方向 is 相对方向.向下 and . >= 下一笔.):
@@ -5072,15 +5274,16 @@ class 线段:
:param 序列: 参考序列 :param 序列: 参考序列
""" """
基础序列 = [] 基础序列 = []
序列集 = set(序列) if not isinstance(序列, set) else 序列
for 元素 in .基础序列: for 元素 in .基础序列:
if 元素 not in 序列: if 元素 not in 序列:
break break
if 基础序列: if 基础序列:
if not 基础序列[-1].之后是(元素): if not 基础序列[-1].之后是(元素):
break break
基础序列.append(元素) 基础序列.append(元素)
.基础序列[:] = 基础序列[:] .基础序列[:] = 基础序列
.特征序列[2] = None .特征序列[2] = None
@classmethod @classmethod
@@ -5149,16 +5352,17 @@ class 线段:
return True return True
@classmethod @classmethod
def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: str): def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
"""内部方法:向线段序列添加新线段 """内部方法:向线段序列添加新线段
:param 线段序列: 线段列表 :param 线段序列: 线段列表
:param 待添加线段: 新线段 :param 待添加线段: 新线段
:param 配置: 缠论配置 :param 配置: 缠论配置
:param 行号: 调用行号 :param 行号: 调用行号
:param 层级: 递归层级
""" """
if 线段序列 and not 线段序列[-1].之后是(待添加线段): if 线段序列 and not 线段序列[-1].之后是(待添加线段):
raise ValueError(f"线段.向序列中添加 不连续[{行号}]", 线段序列[-1]., 待添加线段.) raise ValueError(f"线段.向序列中添加 不连续[{行号}, {层级}]", 线段序列[-1]., 待添加线段.)
待添加线段.模式 = "文武" 待添加线段.模式 = "文武"
if not 线段序列: if not 线段序列:
@@ -5169,10 +5373,10 @@ class 线段:
if not 之前线段.特征序列[2] and not 之前线段.短路修正: if not 之前线段.特征序列[2] and not 之前线段.短路修正:
assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列 assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段.右 = None", 之前线段) raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段.右 = None", 之前线段)
if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正: if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正:
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段[-1] not in 待添加虚线!", 之前线段) raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段[-1] not in 待添加虚线!", 之前线段)
待添加线段.序号 = 之前线段.序号 + 1 待添加线段.序号 = 之前线段.序号 + 1
待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None 待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None
@@ -5185,13 +5389,14 @@ class 线段:
# logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线) # logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线)
@classmethod @classmethod
def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str): def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
"""内部方法:从线段序列弹出最后一个线段 """内部方法:从线段序列弹出最后一个线段
:param 线段序列: 线段列表 :param 线段序列: 线段列表
:param 待弹出线段: 待弹出的线段 :param 待弹出线段: 待弹出的线段
:param 配置: 缠论配置 :param 配置: 缠论配置
:param 行号: 调用行号 :param 行号: 调用行号
:param 层级: 递归层级
:return: 弹出的线段或None :return: 弹出的线段或None
""" """
if not 线段序列: if not 线段序列:
@@ -5204,7 +5409,7 @@ class 线段:
if is not None: if is not None:
结构 = 分型结构.分析(, , , True, True) 结构 = 分型结构.分析(, , , True, True)
if 结构 in (分型结构., 分型结构.) and not 相对方向.分析(., ., ., .).是否缺口(): if 结构 in (分型结构., 分型结构.) and not 相对方向.分析(., ., ., .).是否缺口():
logger.warning(f"警告<{行号}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}") logger.warning(f"警告<{行号}, {层级}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}")
线段序列.pop() 线段序列.pop()
待弹出线段.前一结束位置 = None 待弹出线段.前一结束位置 = None
@@ -5249,7 +5454,7 @@ class 线段:
# 执行修正 # 执行修正
序列 = 当前线段.基础序列[:] 序列 = 当前线段.基础序列[:]
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
assert 线段序列, "缺口突破: 线段序列为第二次空!" assert 线段序列, "缺口突破: 线段序列为第二次空!"
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
@@ -5258,7 +5463,7 @@ class 线段:
assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!" assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!"
当前线段基础序列.extend(序列) 当前线段基础序列.extend(序列)
当前线段.基础序列[:] = 当前线段基础序列[:] 当前线段.基础序列[:] = 当前线段基础序列
线段._刷新(当前线段, 配置) 线段._刷新(当前线段, 配置)
return True return True
@@ -5286,7 +5491,7 @@ class 线段:
assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!" assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!"
# 切割基础序列 # 切割基础序列
基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :] 基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :]
# 长度条件 # 长度条件
if not (len(基础序列) == 4 and len(线段序列) >= 2): if not (len(基础序列) == 4 and len(线段序列) >= 2):
@@ -5302,19 +5507,25 @@ class 线段:
logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出 logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出
基础序列 = 当前线段.基础序列[:] 基础序列 = 当前线段.基础序列[:]
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!" assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!"
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
当前线段.特征序列[2] = None 当前线段.特征序列[2] = None
assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" # assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"
for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]: if 当前线段.基础序列[-1] not in 基础序列:
logger.error(f"非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!")
序号 = 0
else:
序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1
for 临时虚线 in 基础序列[序号:]:
线段._添加虚线(当前线段, 临时虚线) 线段._添加虚线(当前线段, 临时虚线)
线段._刷新(当前线段, 配置) 线段._刷新(当前线段, 配置)
当前线段.短路修正 = True 当前线段.短路修正 = True
if 当前线段.特征序列[2] is not None: if 当前线段.特征序列[2] is not None:
= 虚线.创建线段([, , ]) = 虚线.创建线段([, , ])
线段._添加线段(线段序列, , 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, , 配置, sys._getframe().f_lineno, 层级)
.特征序列[0] = 线段特征.新建([], .方向) .特征序列[0] = 线段特征.新建([], .方向)
return True return True
@@ -5360,7 +5571,7 @@ class 线段:
# 执行修正 # 执行修正
当前线段.短路修正 = True 当前线段.短路修正 = True
新段 = 虚线.创建线段(基础序列) 新段 = 虚线.创建线段(基础序列)
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
return True return True
@classmethod @classmethod
@@ -5403,7 +5614,7 @@ class 线段:
# 创建第一个新段(之后基础序列去掉最后3个) # 创建第一个新段(之后基础序列去掉最后3个)
新段 = 虚线.创建线段(之后基础序列[:-3]) 新段 = 虚线.创建线段(之后基础序列[:-3])
新段.短路修正 = True 新段.短路修正 = True
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
# 根据当前线段的四象决定是否清空前一个缺口 # 根据当前线段的四象决定是否清空前一个缺口
if 线段.四象(当前线段) in ("老阴", "老阳"): if 线段.四象(当前线段) in ("老阴", "老阳"):
@@ -5411,7 +5622,7 @@ class 线段:
# 创建第二个新段(最后3个元素) # 创建第二个新段(最后3个元素)
新段 = 虚线.创建线段(之后基础序列[-3:]) 新段 = 虚线.创建线段(之后基础序列[-3:])
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
return True return True
@@ -5447,7 +5658,7 @@ class 线段:
if not 线段._基础判断(, , , 关系序列): # FIXME 首个线段必须有明确方向 if not 线段._基础判断(, , , 关系序列): # FIXME 首个线段必须有明确方向
continue continue
= 虚线.创建线段([, , ]) = 虚线.创建线段([, , ])
线段._添加线段(线段序列, , 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, , 配置, sys._getframe().f_lineno, 层级)
.特征序列[0] = 线段特征.新建([], .方向) .特征序列[0] = 线段特征.新建([], .方向)
break break
if not 线段序列: if not 线段序列:
@@ -5456,7 +5667,7 @@ class 线段:
# -------------------- 2. 清理无效的尾部引用 -------------------- # -------------------- 2. 清理无效的尾部引用 --------------------
while 线段序列 and 线段序列[-1].前一结束位置: while 线段序列 and 线段序列[-1].前一结束位置:
if 线段序列[-1].前一结束位置 not in 笔序列: if 线段序列[-1].前一结束位置 not in 笔序列:
线段._弹出线段(线段序列, 线段序列[-1], 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 线段序列[-1], 配置, sys._getframe().f_lineno, 层级)
else: else:
break break
@@ -5468,7 +5679,7 @@ class 线段:
线段._序列重置(当前线段, 笔序列) 线段._序列重置(当前线段, 笔序列)
if len(当前线段.基础序列) < 3: if len(当前线段.基础序列) < 3:
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
if not 线段序列: if not 线段序列:
return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列)
@@ -5478,7 +5689,7 @@ class 线段:
if 当前线段.特征序列[2] is not None: if 当前线段.特征序列[2] is not None:
基础序列 = 线段.分割序列(当前线段)[1] 基础序列 = 线段.分割序列(当前线段)[1]
新段 = 虚线.创建线段(基础序列) 新段 = 虚线.创建线段(基础序列)
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
if 线段.四象(当前线段) in ("老阴", "老阳"): if 线段.四象(当前线段) in ("老阴", "老阳"):
新段.前一缺口 = None 新段.前一缺口 = None
@@ -5495,9 +5706,10 @@ class 线段:
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
if not 当前线段.基础序列: if not 当前线段.基础序列:
raise RuntimeError raise RuntimeError
起始索引 = 笔序列.index(当前线段.基础序列[-1]) + 1 起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1
for 当前虚线 in 笔序列[起始索引:]: for idx in range(起始索引, len(笔序列)):
当前虚线 = 笔序列[idx]
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
四象 = 线段.四象(当前线段) 四象 = 线段.四象(当前线段)
@@ -5524,7 +5736,7 @@ class 线段:
基础序列 = 线段.分割序列(当前线段)[1] 基础序列 = 线段.分割序列(当前线段)[1]
新段 = 虚线.创建线段(基础序列) 新段 = 虚线.创建线段(基础序列)
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
if 四象 in ("老阴", "老阳"): if 四象 in ("老阴", "老阳"):
新段.前一缺口 = None 新段.前一缺口 = None
@@ -5556,15 +5768,16 @@ class 线段:
:param 序列: 参考序列 :param 序列: 参考序列
""" """
基础序列 = [] 基础序列 = []
序列集 = set(序列) if not isinstance(序列, set) else 序列
for 元素 in .基础序列: for 元素 in .基础序列:
if 元素 not in 序列: if 元素 not in 序列:
break break
if 基础序列: if 基础序列:
if not 基础序列[-1].之后是(元素): if not 基础序列[-1].之后是(元素):
logger.warning(" 线段._验证序列 数据不连续") logger.warning(" 线段._验证序列 数据不连续")
break break
基础序列.append(元素) 基础序列.append(元素)
.基础序列[:] = 基础序列[:] .基础序列[:] = 基础序列
if len(.基础序列) % 2 == 0: if len(.基础序列) % 2 == 0:
.基础序列 and .基础序列.pop() .基础序列 and .基础序列.pop()
@@ -5626,7 +5839,7 @@ class 线段:
if not 线段序列: if not 线段序列:
for i in range(1, len(虚线序列) - 1): for i in range(1, len(虚线序列) - 1):
, , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] , , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
关系 = 相对方向.分析(., ., ., .) 关系 = 相对方向.分析(.端点, .端点, .端点, .端点)
if 关系 not in (相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.): # FIXME 此处为首个线段 if 关系 not in (相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.): # FIXME 此处为首个线段
continue continue
@@ -5646,7 +5859,7 @@ class 线段:
if not 配置.扩展线段_当下分析: if not 配置.扩展线段_当下分析:
, , = 当前线段.基础序列[:3] , , = 当前线段.基础序列[:3]
if not 相对方向.分析(., ., ., .).是否缺口(): if not 相对方向.分析(.端点, .端点, .端点, .端点).是否缺口():
当前线段.基础序列[:] = 当前线段.基础序列[:3] 当前线段.基础序列[:] = 当前线段.基础序列[:3]
线段._武终(当前线段, sys._getframe().f_lineno) 线段._武终(当前线段, sys._getframe().f_lineno)
else: else:
@@ -5657,13 +5870,13 @@ class 线段:
if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号: if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号:
return None return None
序号 = 虚线序列.index(当前线段.基础序列[-1]) + 1 序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1
if 序号 >= len(虚线序列): if 序号 >= len(虚线序列):
return None return None
for i in range(序号 + 1, len(虚线序列) - 1): for i in range(序号 + 1, len(虚线序列) - 1):
, , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] , , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
相对关系 = 相对方向.分析(., ., ., .) 相对关系 = 相对方向.分析(.端点, .端点, .端点, .端点)
if 相对关系.是否缺口(): if 相对关系.是否缺口():
线段._添加虚线(当前线段, ) 线段._添加虚线(当前线段, )
线段._添加虚线(当前线段, ) 线段._添加虚线(当前线段, )
@@ -5720,7 +5933,7 @@ class 线段:
if 当前段.实_中枢序列: if 当前段.实_中枢序列:
if [-1] in 当前段.实_中枢序列[-1].基础序列: if [-1] in 当前段.实_中枢序列[-1].基础序列:
# 当前最后一笔在最后一中枢里 # 当前最后一笔在最后一中枢里
序号 = 当前段.基础序列.index(当前段.实_中枢序列[-1].基础序列[0]) 序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0])
进入段 = 当前段.基础序列[序号 - 1] 进入段 = 当前段.基础序列[序号 - 1]
离开段 = [-1] 离开段 = [-1]
assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号) assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号)
@@ -5774,7 +5987,7 @@ class 线段:
笔序列.append(停顿) 笔序列.append(停顿)
线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.]) 线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.])
if 线段序列 and 线段序列[-1]. is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1: if 线段序列 and 线段序列[-1]. is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1:
新段 = 虚线.创建线段(线段序列[-1].基础序列[:]) 新段 = 虚线.创建线段(线段序列[-1].基础序列)
新段.序号 = self.序号 新段.序号 = self.序号
线段._刷新(新段, 观察员.配置) 线段._刷新(新段, 观察员.配置)
if 新段.方向 is self.方向: if 新段.方向 is self.方向:
@@ -5965,7 +6178,7 @@ class 中枢:
:return: 虚线列表 :return: 虚线列表
""" """
序列: List = self.基础序列[:] 序列: List = self.基础序列.copy()
if self.第三买卖线 is not None: if self.第三买卖线 is not None:
序列.append(self.第三买卖线) 序列.append(self.第三买卖线)
return 序列 return 序列
@@ -5989,13 +6202,14 @@ class 中枢:
""" """
有效序列 = self.基础序列[:] 有效序列 = self.基础序列[:]
无效序列 = [] 无效序列 = []
序列集 = set(序列)
for 元素 in self.基础序列: for 元素 in self.基础序列:
if 元素 not in 序列: if 元素 not in 序列:
无效序列.append(元素) 无效序列.append(元素)
if 无效序列: if 无效序列:
无效 = 无效序列[0] 无效 = 无效序列[0]
序号 = self.基础序列.index(无效) 序号 = 线段._索引(self.基础序列, 无效)
有效序列 = self.基础序列[:序号] 有效序列 = self.基础序列[:序号]
if len(有效序列) < 3: if len(有效序列) < 3:
@@ -6003,14 +6217,14 @@ class 中枢:
self.本级_第三买卖线 = None self.本级_第三买卖线 = None
return False return False
self.基础序列[:] = 有效序列 self.基础序列 = 有效序列
有效序列 = [] 有效序列 = []
for 元素 in self.基础序列: for 元素 in self.基础序列:
if 相对方向.分析(self., self., 元素., 元素.).是否缺口(): if 相对方向.分析(self., self., 元素., 元素.).是否缺口():
break break
有效序列.append(元素) 有效序列.append(元素)
self.基础序列[:] = 有效序列 self.基础序列 = 有效序列
if len(self.基础序列) < 3: if len(self.基础序列) < 3:
return False return False
@@ -6182,7 +6396,7 @@ class 中枢:
, , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] , , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
if 中枢.基础检查(, , ): if 中枢.基础检查(, , ):
新中枢 = 中枢.创建(, , , .级别, 标识) 新中枢 = 中枢.创建(, , , .级别, 标识)
序号 = 虚线序列.index() 序号 = 线段._索引(虚线序列, )
if 跳过首部 and (.序号 == 0 or 序号 == 0): if 跳过首部 and (.序号 == 0 or 序号 == 0):
continue # 方便计算走势 continue # 方便计算走势
if 序号 >= 2: if 序号 >= 2:
@@ -6203,7 +6417,7 @@ class 中枢:
中枢._从中枢序列尾部弹出(中枢序列, 当前中枢) 中枢._从中枢序列尾部弹出(中枢序列, 当前中枢)
return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1)
序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
基础序列 = [] 基础序列 = []
for 当前虚线 in 虚线序列[序号:]: for 当前虚线 in 虚线序列[序号:]:
+7 -6
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "chanlun-py" name = "chanlun-py"
version = "26.6.47" version = "26.6.73"
edition = "2024" edition = "2024"
description = "缠论技术分析库 — Rust 高性能 Python 绑定" description = "缠论技术分析库 — Rust 高性能 Python 绑定"
authors = ["YuYuKunKun"] authors = ["YuYuKunKun"]
@@ -12,11 +12,12 @@ crate-type = ["cdylib"]
name = "chanlun" name = "chanlun"
[dependencies] [dependencies]
chanlun = "26.6.3" # { path = "../chanlun" } chanlun = "26.6.4" # { path = "../chanlun" }
lru = "0.18" 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"] } pyo3 = { version = "0.28", features = ["experimental-inspect"] }
serde_json = "1" serde_json = "1"
chrono = "0.4" chrono = "0.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
tracing-core = "0.1"
+71
View File
@@ -72,6 +72,77 @@ pip install target/wheels/chanlun-*.whl
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致 - 类名 / 方法名 / 字段名与 `chan.py` 保持一致
- 支持 `.nb` 二进制文件格式(大端字节序) - 支持 `.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<Arc<K线>>` 转换:
```python
# 旧方式:构建 Python 列表
result = 背驰分析.MACD背驰(进入段, 离开段, obs.普通K线序列, "")
# 新方式:直接传观察者
result = 背驰分析.MACD背驰_OBS(进入段, 离开段, obs, "")
```
## 许可 ## 许可
本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parseMIT)、termcolorMIT)。 本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parseMIT)、termcolorMIT)。
+52 -4
View File
@@ -5,11 +5,18 @@ from typing import Any, ClassVar, Optional, List, Dict, Tuple, Union
from datetime import datetime from datetime import datetime
# ========== Module-level functions ========== # ========== Module-level functions ==========
def get_rs_log_level() -> str: ...
def set_rs_log_level(level: str) -> None: ...
def get_log_level() -> str: ... def get_log_level() -> str: ...
def set_log_level(level: str) -> None: ... 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 get_分型模式() -> bool: ...
def set_分型模式(value: bool) -> None: ... def set_分型模式(value: bool) -> None: ...
def get_扩展线段模式() -> bool: ...
def set_扩展线段模式(value: bool) -> None: ...
def 转化为时间戳(ts: Any) -> int: ... def 转化为时间戳(ts: Any) -> int: ...
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]: ...
@@ -75,6 +82,8 @@ class 相对方向:
def 翻转(self) -> 相对方向: ... def 翻转(self) -> 相对方向: ...
@classmethod @classmethod
def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
@classmethod
def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -326,6 +335,7 @@ class K线:
def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ... def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ...
def __bytes__(self) -> bytes: ... def __bytes__(self) -> bytes: ...
def __eq__(self, other: Any) -> bool: ... def __eq__(self, other: Any) -> bool: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -438,9 +448,9 @@ class 虚线:
@property @property
def 模式(self) -> str: ... def 模式(self) -> str: ...
@property @property
def 特征序列_显示(self) -> bool: ... def _特征序列_显示(self) -> bool: ...
@特征序列_显示.setter @_特征序列_显示.setter
def 特征序列_显示(self, value: bool) -> None: ... def _特征序列_显示(self, value: bool) -> None: ...
@property @property
def 特征序列(self) -> List[Optional[线段特征]]: ... def 特征序列(self) -> List[Optional[线段特征]]: ...
@property @property
@@ -526,6 +536,8 @@ class 虚线:
class 线段特征: class 线段特征:
@property @property
def 序号(self) -> int: ... def 序号(self) -> int: ...
@序号.setter
def 序号(self, value: int) -> None: ...
@property @property
def 标识(self) -> str: ... def 标识(self) -> str: ...
@标识.setter @标识.setter
@@ -570,6 +582,18 @@ class 背驰分析:
def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ...
@classmethod @classmethod
def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... 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 : class :
@classmethod @classmethod
@@ -793,6 +817,30 @@ class 观察者:
def 扩展线段序列_扩展线段(self) -> List[虚线]: ... def 扩展线段序列_扩展线段(self) -> List[虚线]: ...
@property @property
def 扩展中枢序列_扩展线段(self) -> List[中枢]: ... 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 重置基础序列(self) -> None: ...
def 增加原始K线(self, 普K: K线) -> None: ... def 增加原始K线(self, 普K: K线) -> None: ...
def 投喂原始数据(self, 时间戳: int, : float, : float, : float, : float, : float) -> None: ... def 投喂原始数据(self, 时间戳: int, : float, : float, : float, : float, : float) -> None: ...
+2
View File
@@ -32,6 +32,8 @@ __all__ = [
"布林带", "布林带",
"get_分型模式", "get_分型模式",
"set_分型模式", "set_分型模式",
"get_扩展线段模式",
"set_扩展线段模式",
"get_log_level", "get_log_level",
"set_log_level", "set_log_level",
"get_rs_log_level", "get_rs_log_level",
+23
View File
@@ -9,8 +9,16 @@ def get_rs_log_level() -> str: ...
def set_rs_log_level(level: str) -> None: ... def set_rs_log_level(level: str) -> None: ...
def get_log_level() -> str: ... def get_log_level() -> str: ...
def set_log_level(level: str) -> None: ... 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 get_分型模式() -> bool: ...
def set_分型模式(value: bool) -> None: ... 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 缠论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]: ... def 分型相等(A: 分型, B: 分型, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
@@ -74,6 +82,8 @@ class 相对方向:
def 翻转(self) -> 相对方向: ... def 翻转(self) -> 相对方向: ...
@classmethod @classmethod
def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
@classmethod
def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -325,6 +335,7 @@ class K线:
def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ... def 截取(序列: List[K线], : K线, : K线) -> List[K线]: ...
def __str__(self) -> str: ... def __str__(self) -> str: ...
def __repr__(self) -> str: ... def __repr__(self) -> str: ...
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ...
def __bytes__(self) -> bytes: ... def __bytes__(self) -> bytes: ...
def __eq__(self, other: Any) -> bool: ... def __eq__(self, other: Any) -> bool: ...
def __hash__(self) -> int: ... def __hash__(self) -> int: ...
@@ -571,6 +582,18 @@ class 背驰分析:
def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ...
@classmethod @classmethod
def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... 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 : class :
@classmethod @classmethod
+294 -80
View File
@@ -31,12 +31,14 @@ from __future__ import annotations
import json import json
import math import math
import os import os
from collections import deque
import random
import struct import struct
import sys import sys
import tempfile import tempfile
import datetime as datetime_module import datetime as datetime_module
from dataclasses import dataclass, field from dataclasses import dataclass, field
from datetime import datetime from datetime import datetime, timedelta
from enum import Enum from enum import Enum
from functools import lru_cache from functools import lru_cache
from pathlib import Path from pathlib import Path
@@ -52,6 +54,7 @@ from typing import (
Sequence, Sequence,
Callable, Callable,
Set, Set,
Generator,
) )
from loguru import logger from loguru import logger
@@ -1425,6 +1428,23 @@ class 相对方向(Enum):
return 相对方向. return 相对方向.
raise RuntimeError("无法识别的方向") 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): class 分型结构(Enum):
"""描述三根K线构成的顶底分型形态。 """描述三根K线构成的顶底分型形态。
@@ -1797,10 +1817,11 @@ class 相对强弱指数:
下跌幅度: float = 0.0, 下跌幅度: float = 0.0,
平滑系数: float = 0.0, 平滑系数: float = 0.0,
RSI_SMA: Optional[float] = None, RSI_SMA: Optional[float] = None,
RSI历史队列: List[float] = None, RSI历史队列: Optional[deque[float]] = None,
RSI和: float = 0.0,
): ):
if RSI历史队列 is None: if RSI历史队列 is None:
RSI历史队列 = [] RSI历史队列 = deque()
# 原始数据 # 原始数据
self.时间戳 = 时间戳 self.时间戳 = 时间戳
@@ -1829,6 +1850,7 @@ class 相对强弱指数:
# RSI的SMA(信号线)相关字段 # RSI的SMA(信号线)相关字段
self.RSI_SMA = RSI_SMA self.RSI_SMA = RSI_SMA
self.RSI历史队列 = RSI历史队列 self.RSI历史队列 = RSI历史队列
self.RSI和 = RSI和
@classmethod @classmethod
def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: 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(简单移动平均) -----
RSI_SMA = None 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: if RSI_SMA周期 is not None and RSI_SMA周期 > 0 and RSI is not None:
# 将当前RSI加入队列
历史队列.append(RSI) 历史队列.append(RSI)
# 保持队列长度不超过周期 RSI和 += RSI
if len(历史队列) > RSI_SMA周期: if len(历史队列) > RSI_SMA周期:
历史队列.pop(0) RSI和 -= 历史队列.popleft()
# 计算SMA(即使队列未满也计算当前平均值)
if 历史队列: if 历史队列:
RSI_SMA = sum(历史队列) / len(历史队列) RSI_SMA = RSI和 / len(历史队列)
else: else:
# 未启用SMA,清空队列 历史队列 = deque()
历史队列 = [] RSI和 = 0.0
return cls( return cls(
时间戳=当前时间, 时间戳=当前时间,
@@ -1945,6 +1966,7 @@ class 相对强弱指数:
RSI_SMA周期=RSI_SMA周期, RSI_SMA周期=RSI_SMA周期,
RSI_SMA=RSI_SMA, RSI_SMA=RSI_SMA,
RSI历史队列=历史队列, RSI历史队列=历史队列,
RSI和=RSI和,
) )
@classmethod @classmethod
@@ -1996,16 +2018,16 @@ class 随机指标:
K: Optional[float] = None, K: Optional[float] = None,
D: Optional[float] = None, D: Optional[float] = None,
J: Optional[float] = None, J: Optional[float] = None,
历史最高价队列: list[float] = None, 历史最高价队列: Optional[deque[float]] = None,
历史最低价队列: list[float] = None, 历史最低价队列: Optional[deque[float]] = None,
前一个RSV: Optional[float] = None, 前一个RSV: Optional[float] = None,
前一个K: Optional[float] = None, 前一个K: Optional[float] = None,
前一个D: Optional[float] = None, 前一个D: Optional[float] = None,
): ):
if 历史最高价队列 is None: if 历史最高价队列 is None:
历史最高价队列 = [] 历史最高价队列 = deque()
if 历史最低价队列 is None: if 历史最低价队列 is None:
历史最低价队列 = [] 历史最低价队列 = deque()
# 原始数据 # 原始数据
self.时间戳 = 时间戳 self.时间戳 = 时间戳
@@ -2065,8 +2087,8 @@ class 随机指标:
K=None, K=None,
D=None, D=None,
J=None, J=None,
历史最高价队列=[初始最高价], 历史最高价队列=deque([初始最高价]),
历史最低价队列=[初始最低价], 历史最低价队列=deque([初始最低价]),
前一个RSV=None, 前一个RSV=None,
前一个K=None, 前一个K=None,
前一个D=None, 前一个D=None,
@@ -2113,13 +2135,13 @@ class 随机指标:
历史最高价 = 前一个KDJ.历史最高价队列.copy() 历史最高价 = 前一个KDJ.历史最高价队列.copy()
历史最高价.append(当前最高价) 历史最高价.append(当前最高价)
if len(历史最高价) > N: if len(历史最高价) > N:
历史最高价.pop(0) 历史最高价.popleft()
# 更新历史最低价队列 # 更新历史最低价队列
历史最低价 = 前一个KDJ.历史最低价队列.copy() 历史最低价 = 前一个KDJ.历史最低价队列.copy()
历史最低价.append(当前最低价) 历史最低价.append(当前最低价)
if len(历史最低价) > N: if len(历史最低价) > N:
历史最低价.pop(0) 历史最低价.popleft()
# 计算RSV(需要队列长度达到N才能计算) # 计算RSV(需要队列长度达到N才能计算)
RSV = None RSV = None
@@ -2218,7 +2240,7 @@ class 布林带:
self.上轨 = 上轨 self.上轨 = 上轨
self.中轨 = 中轨 self.中轨 = 中轨
self.下轨 = 下轨 self.下轨 = 下轨
self._历史队列 = 历史队列 if 历史队列 is not None else [] self._历史队列 = 历史队列 if 历史队列 is not None else deque()
self._均值 = _均值 self._均值 = _均值
self._方差和 = _方差和 self._方差和 = _方差和
@@ -2233,7 +2255,7 @@ class 布林带:
:return: 初始的布林带实例 :return: 初始的布林带实例
""" """
价格 = 指标.K线取值(k线, 计算方式) 价格 = 指标.K线取值(k线, 计算方式)
return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格]) return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=deque([价格]))
@classmethod @classmethod
def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带: def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带:
@@ -2251,7 +2273,7 @@ class 布林带:
q = prev._历史队列.copy() q = prev._历史队列.copy()
q.append(当前价) q.append(当前价)
if len(q) > 周期: if len(q) > 周期:
q.pop(0) q.popleft()
# 增量均值和方差 # 增量均值和方差
if len(q) < 周期: if len(q) < 周期:
@@ -2969,6 +2991,58 @@ class K线:
""" """
return 序列[序列.index() : 序列.index() + 1] 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线: class 缠论K线:
"""经包含处理后的标准化K线,有方向和分型结构标记。 """经包含处理后的标准化K线,有方向和分型结构标记。
@@ -3488,6 +3562,9 @@ class 分型:
分型序列.append(当前分型) 分型序列.append(当前分型)
扩展线段模式 = True # TODO 虚线高低取值 暂定,此举将符合同级别分解时正确的高低取值涉及中枢等问题
class 虚线: class 虚线:
"""笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。 """笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。
@@ -3615,15 +3692,29 @@ class 虚线:
case _: case _:
raise RuntimeError("无法识别的方向", self..结构, self..结构) raise RuntimeError("无法识别的方向", self..结构, self..结构)
@property
def 端点高(self) -> float:
if self.方向 is 相对方向.向上:
return self...
return self...
@property
def 端点低(self) -> float:
if self.方向 is 相对方向.向下:
return self...
return self...
@property @property
def (self) -> float: def (self) -> float:
"""虚线区间的最高价。 """虚线区间的最高价。
:return: 向上虚线取武..向下虚线取文.. :return: 向上虚线取武..向下虚线取文..
""" """
if self.方向 is 相对方向.向上: if 扩展线段模式 and self.模式 != "文武" and self.标识 != "" and "扩展" in self.标识: # 扩展线段
return self... 端点序列 = [. for in self.基础序列]
return self... 端点序列.append(self.基础序列[-1].)
return max(端点序列, key=lambda o: o..)..
return self.端点高
@property @property
def (self) -> float: def (self) -> float:
@@ -3631,9 +3722,11 @@ class 虚线:
:return: 向下虚线取武..向上虚线取文.. :return: 向下虚线取武..向上虚线取文..
""" """
if self.方向 is 相对方向.向下: if 扩展线段模式 and self.模式 != "文武" and self.标识 != "" and "扩展" in self.标识: # 扩展线段
return self... 端点序列 = [. for in self.基础序列]
return self... 端点序列.append(self.基础序列[-1].)
return min(端点序列, key=lambda o: o..)..
return self.端点低
def 之前是(self, 之前: 虚线) -> bool: def 之前是(self, 之前: 虚线) -> bool:
""" """
@@ -3704,7 +3797,7 @@ class 虚线:
.实_中枢序列 = [] .实_中枢序列 = []
.虚_中枢序列 = [] .虚_中枢序列 = []
.合_中枢序列 = [] .合_中枢序列 = []
.基础序列 = 虚线序列 .基础序列 = 虚线序列[:]
return return
@classmethod @classmethod
@@ -3873,6 +3966,111 @@ class 虚线:
return True return True
return False 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 @classmethod
def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向):
"""计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断)
@@ -4411,8 +4609,7 @@ class 笔:
临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck) 临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck)
递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) 递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
if 分型序列 and 分型序列[-1] is 临时分型: if 分型序列 and 分型序列[-1] is 临时分型:
"""""" logger.warning(f"笔.分析 事后修复错过的笔:{临时分型}, 当前分型: {当前分型}")
# logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型)
递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) 递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
return 递归层次 return 递归层次
@@ -4570,14 +4767,14 @@ class 线段特征:
return self.标识 # f"{self.标识}:{self.序号}" return self.标识 # f"{self.标识}:{self.序号}"
def __str__(self): def __str__(self):
if not len(self): if not len(self.基础序列):
return f"{self.标识}<{self.线段方向}, 空>" return f"{self.标识}<{self.线段方向}, 空>"
return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self)}>" return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self.基础序列)}>"
def __repr__(self): def __repr__(self):
if not len(self): if not len(self.基础序列):
return f"{self.标识}<{self.线段方向}, 空>" return f"{self.标识}<{self.线段方向}, 空>"
return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self)}>" return f"{self.标识}<{self.线段方向}, {self.}, {self.}, {len(self.基础序列)}>"
@property @property
def (self) -> 分型: def (self) -> 分型:
@@ -4788,6 +4985,11 @@ class 线段:
__slots__ = [] __slots__ = []
@staticmethod
def _索引(序列: list, ) -> int:
"""O(1) index lookup — 序列元素序号连续递增。"""
return .序号 - 序列[0].序号
@classmethod @classmethod
def _添加虚线(cls, : 虚线, : 虚线): def _添加虚线(cls, : 虚线, : 虚线):
"""向线段中添加一笔 """向线段中添加一笔
@@ -4918,7 +5120,7 @@ class 线段:
break break
if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0): if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0):
.基础序列[:] = 基础序列[:] .基础序列[:] = 基础序列
else: else:
raise RuntimeError() raise RuntimeError()
else: else:
@@ -4935,7 +5137,7 @@ class 线段:
return return
基础序列 = .基础序列 基础序列 = .基础序列
if .前一结束位置 and .前一结束位置 in 基础序列: if .前一结束位置 and .前一结束位置 in 基础序列:
基础序列 = .基础序列[.基础序列.index(.前一结束位置) - 1 :] 基础序列 = .基础序列[cls._索引(.基础序列, .前一结束位置) - 1 :]
特征序列 = 线段特征.静态分析(基础序列, .方向, 线段.四象(), 配置.线段_特征序列忽视老阴老阳) 特征序列 = 线段特征.静态分析(基础序列, .方向, 线段.四象(), 配置.线段_特征序列忽视老阴老阳)
if len(特征序列) >= 3: if len(特征序列) >= 3:
@@ -5053,7 +5255,7 @@ class 线段:
特征后一笔 = 最近特征.基础序列[-1] 特征后一笔 = 最近特征.基础序列[-1]
if 特征后一笔 is not None: if 特征后一笔 is not None:
序号 = .基础序列.index(特征后一笔) 序号 = cls._索引(.基础序列, 特征后一笔)
if 序号 < len(.基础序列) - 1: if 序号 < len(.基础序列) - 1:
下一笔 = .基础序列[序号 + 1] 下一笔 = .基础序列[序号 + 1]
if (.方向 is 相对方向.向上 and . <= 下一笔.) or (.方向 is 相对方向.向下 and . >= 下一笔.): if (.方向 is 相对方向.向上 and . <= 下一笔.) or (.方向 is 相对方向.向下 and . >= 下一笔.):
@@ -5072,15 +5274,16 @@ class 线段:
:param 序列: 参考序列 :param 序列: 参考序列
""" """
基础序列 = [] 基础序列 = []
序列集 = set(序列) if not isinstance(序列, set) else 序列
for 元素 in .基础序列: for 元素 in .基础序列:
if 元素 not in 序列: if 元素 not in 序列:
break break
if 基础序列: if 基础序列:
if not 基础序列[-1].之后是(元素): if not 基础序列[-1].之后是(元素):
break break
基础序列.append(元素) 基础序列.append(元素)
.基础序列[:] = 基础序列[:] .基础序列[:] = 基础序列
.特征序列[2] = None .特征序列[2] = None
@classmethod @classmethod
@@ -5149,16 +5352,17 @@ class 线段:
return True return True
@classmethod @classmethod
def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: str): def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
"""内部方法:向线段序列添加新线段 """内部方法:向线段序列添加新线段
:param 线段序列: 线段列表 :param 线段序列: 线段列表
:param 待添加线段: 新线段 :param 待添加线段: 新线段
:param 配置: 缠论配置 :param 配置: 缠论配置
:param 行号: 调用行号 :param 行号: 调用行号
:param 层级: 递归层级
""" """
if 线段序列 and not 线段序列[-1].之后是(待添加线段): if 线段序列 and not 线段序列[-1].之后是(待添加线段):
raise ValueError(f"线段.向序列中添加 不连续[{行号}]", 线段序列[-1]., 待添加线段.) raise ValueError(f"线段.向序列中添加 不连续[{行号}, {层级}]", 线段序列[-1]., 待添加线段.)
待添加线段.模式 = "文武" 待添加线段.模式 = "文武"
if not 线段序列: if not 线段序列:
@@ -5169,10 +5373,10 @@ class 线段:
if not 之前线段.特征序列[2] and not 之前线段.短路修正: if not 之前线段.特征序列[2] and not 之前线段.短路修正:
assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列 assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段.右 = None", 之前线段) raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段.右 = None", 之前线段)
if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正: if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正:
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段[-1] not in 待添加虚线!", 之前线段) raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段[-1] not in 待添加虚线!", 之前线段)
待添加线段.序号 = 之前线段.序号 + 1 待添加线段.序号 = 之前线段.序号 + 1
待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None 待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None
@@ -5185,13 +5389,14 @@ class 线段:
# logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线) # logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线)
@classmethod @classmethod
def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str): def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
"""内部方法:从线段序列弹出最后一个线段 """内部方法:从线段序列弹出最后一个线段
:param 线段序列: 线段列表 :param 线段序列: 线段列表
:param 待弹出线段: 待弹出的线段 :param 待弹出线段: 待弹出的线段
:param 配置: 缠论配置 :param 配置: 缠论配置
:param 行号: 调用行号 :param 行号: 调用行号
:param 层级: 递归层级
:return: 弹出的线段或None :return: 弹出的线段或None
""" """
if not 线段序列: if not 线段序列:
@@ -5204,7 +5409,7 @@ class 线段:
if is not None: if is not None:
结构 = 分型结构.分析(, , , True, True) 结构 = 分型结构.分析(, , , True, True)
if 结构 in (分型结构., 分型结构.) and not 相对方向.分析(., ., ., .).是否缺口(): if 结构 in (分型结构., 分型结构.) and not 相对方向.分析(., ., ., .).是否缺口():
logger.warning(f"警告<{行号}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}") logger.warning(f"警告<{行号}, {层级}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}")
线段序列.pop() 线段序列.pop()
待弹出线段.前一结束位置 = None 待弹出线段.前一结束位置 = None
@@ -5249,7 +5454,7 @@ class 线段:
# 执行修正 # 执行修正
序列 = 当前线段.基础序列[:] 序列 = 当前线段.基础序列[:]
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
assert 线段序列, "缺口突破: 线段序列为第二次空!" assert 线段序列, "缺口突破: 线段序列为第二次空!"
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
@@ -5258,7 +5463,7 @@ class 线段:
assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!" assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!"
当前线段基础序列.extend(序列) 当前线段基础序列.extend(序列)
当前线段.基础序列[:] = 当前线段基础序列[:] 当前线段.基础序列[:] = 当前线段基础序列
线段._刷新(当前线段, 配置) 线段._刷新(当前线段, 配置)
return True return True
@@ -5286,7 +5491,7 @@ class 线段:
assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!" assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!"
# 切割基础序列 # 切割基础序列
基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :] 基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :]
# 长度条件 # 长度条件
if not (len(基础序列) == 4 and len(线段序列) >= 2): if not (len(基础序列) == 4 and len(线段序列) >= 2):
@@ -5302,19 +5507,25 @@ class 线段:
logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出 logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出
基础序列 = 当前线段.基础序列[:] 基础序列 = 当前线段.基础序列[:]
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!" assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!"
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
当前线段.特征序列[2] = None 当前线段.特征序列[2] = None
assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" # assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"
for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]: if 当前线段.基础序列[-1] not in 基础序列:
logger.error(f"非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!")
序号 = 0
else:
序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1
for 临时虚线 in 基础序列[序号:]:
线段._添加虚线(当前线段, 临时虚线) 线段._添加虚线(当前线段, 临时虚线)
线段._刷新(当前线段, 配置) 线段._刷新(当前线段, 配置)
当前线段.短路修正 = True 当前线段.短路修正 = True
if 当前线段.特征序列[2] is not None: if 当前线段.特征序列[2] is not None:
= 虚线.创建线段([, , ]) = 虚线.创建线段([, , ])
线段._添加线段(线段序列, , 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, , 配置, sys._getframe().f_lineno, 层级)
.特征序列[0] = 线段特征.新建([], .方向) .特征序列[0] = 线段特征.新建([], .方向)
return True return True
@@ -5360,7 +5571,7 @@ class 线段:
# 执行修正 # 执行修正
当前线段.短路修正 = True 当前线段.短路修正 = True
新段 = 虚线.创建线段(基础序列) 新段 = 虚线.创建线段(基础序列)
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
return True return True
@classmethod @classmethod
@@ -5403,7 +5614,7 @@ class 线段:
# 创建第一个新段(之后基础序列去掉最后3个) # 创建第一个新段(之后基础序列去掉最后3个)
新段 = 虚线.创建线段(之后基础序列[:-3]) 新段 = 虚线.创建线段(之后基础序列[:-3])
新段.短路修正 = True 新段.短路修正 = True
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
# 根据当前线段的四象决定是否清空前一个缺口 # 根据当前线段的四象决定是否清空前一个缺口
if 线段.四象(当前线段) in ("老阴", "老阳"): if 线段.四象(当前线段) in ("老阴", "老阳"):
@@ -5411,7 +5622,7 @@ class 线段:
# 创建第二个新段(最后3个元素) # 创建第二个新段(最后3个元素)
新段 = 虚线.创建线段(之后基础序列[-3:]) 新段 = 虚线.创建线段(之后基础序列[-3:])
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
return True return True
@@ -5447,7 +5658,7 @@ class 线段:
if not 线段._基础判断(, , , 关系序列): # FIXME 首个线段必须有明确方向 if not 线段._基础判断(, , , 关系序列): # FIXME 首个线段必须有明确方向
continue continue
= 虚线.创建线段([, , ]) = 虚线.创建线段([, , ])
线段._添加线段(线段序列, , 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, , 配置, sys._getframe().f_lineno, 层级)
.特征序列[0] = 线段特征.新建([], .方向) .特征序列[0] = 线段特征.新建([], .方向)
break break
if not 线段序列: if not 线段序列:
@@ -5456,7 +5667,7 @@ class 线段:
# -------------------- 2. 清理无效的尾部引用 -------------------- # -------------------- 2. 清理无效的尾部引用 --------------------
while 线段序列 and 线段序列[-1].前一结束位置: while 线段序列 and 线段序列[-1].前一结束位置:
if 线段序列[-1].前一结束位置 not in 笔序列: if 线段序列[-1].前一结束位置 not in 笔序列:
线段._弹出线段(线段序列, 线段序列[-1], 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 线段序列[-1], 配置, sys._getframe().f_lineno, 层级)
else: else:
break break
@@ -5468,7 +5679,7 @@ class 线段:
线段._序列重置(当前线段, 笔序列) 线段._序列重置(当前线段, 笔序列)
if len(当前线段.基础序列) < 3: if len(当前线段.基础序列) < 3:
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
if not 线段序列: if not 线段序列:
return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列)
@@ -5478,7 +5689,7 @@ class 线段:
if 当前线段.特征序列[2] is not None: if 当前线段.特征序列[2] is not None:
基础序列 = 线段.分割序列(当前线段)[1] 基础序列 = 线段.分割序列(当前线段)[1]
新段 = 虚线.创建线段(基础序列) 新段 = 虚线.创建线段(基础序列)
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
if 线段.四象(当前线段) in ("老阴", "老阳"): if 线段.四象(当前线段) in ("老阴", "老阳"):
新段.前一缺口 = None 新段.前一缺口 = None
@@ -5495,9 +5706,10 @@ class 线段:
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
if not 当前线段.基础序列: if not 当前线段.基础序列:
raise RuntimeError raise RuntimeError
起始索引 = 笔序列.index(当前线段.基础序列[-1]) + 1 起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1
for 当前虚线 in 笔序列[起始索引:]: for idx in range(起始索引, len(笔序列)):
当前虚线 = 笔序列[idx]
当前线段 = 线段序列[-1] 当前线段 = 线段序列[-1]
四象 = 线段.四象(当前线段) 四象 = 线段.四象(当前线段)
@@ -5524,7 +5736,7 @@ class 线段:
基础序列 = 线段.分割序列(当前线段)[1] 基础序列 = 线段.分割序列(当前线段)[1]
新段 = 虚线.创建线段(基础序列) 新段 = 虚线.创建线段(基础序列)
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}") 线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
if 四象 in ("老阴", "老阳"): if 四象 in ("老阴", "老阳"):
新段.前一缺口 = None 新段.前一缺口 = None
@@ -5556,15 +5768,16 @@ class 线段:
:param 序列: 参考序列 :param 序列: 参考序列
""" """
基础序列 = [] 基础序列 = []
序列集 = set(序列) if not isinstance(序列, set) else 序列
for 元素 in .基础序列: for 元素 in .基础序列:
if 元素 not in 序列: if 元素 not in 序列:
break break
if 基础序列: if 基础序列:
if not 基础序列[-1].之后是(元素): if not 基础序列[-1].之后是(元素):
logger.warning(" 线段._验证序列 数据不连续") logger.warning(" 线段._验证序列 数据不连续")
break break
基础序列.append(元素) 基础序列.append(元素)
.基础序列[:] = 基础序列[:] .基础序列[:] = 基础序列
if len(.基础序列) % 2 == 0: if len(.基础序列) % 2 == 0:
.基础序列 and .基础序列.pop() .基础序列 and .基础序列.pop()
@@ -5626,7 +5839,7 @@ class 线段:
if not 线段序列: if not 线段序列:
for i in range(1, len(虚线序列) - 1): for i in range(1, len(虚线序列) - 1):
, , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] , , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
关系 = 相对方向.分析(., ., ., .) 关系 = 相对方向.分析(.端点, .端点, .端点, .端点)
if 关系 not in (相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.): # FIXME 此处为首个线段 if 关系 not in (相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.): # FIXME 此处为首个线段
continue continue
@@ -5646,7 +5859,7 @@ class 线段:
if not 配置.扩展线段_当下分析: if not 配置.扩展线段_当下分析:
, , = 当前线段.基础序列[:3] , , = 当前线段.基础序列[:3]
if not 相对方向.分析(., ., ., .).是否缺口(): if not 相对方向.分析(.端点, .端点, .端点, .端点).是否缺口():
当前线段.基础序列[:] = 当前线段.基础序列[:3] 当前线段.基础序列[:] = 当前线段.基础序列[:3]
线段._武终(当前线段, sys._getframe().f_lineno) 线段._武终(当前线段, sys._getframe().f_lineno)
else: else:
@@ -5657,13 +5870,13 @@ class 线段:
if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号: if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号:
return None return None
序号 = 虚线序列.index(当前线段.基础序列[-1]) + 1 序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1
if 序号 >= len(虚线序列): if 序号 >= len(虚线序列):
return None return None
for i in range(序号 + 1, len(虚线序列) - 1): for i in range(序号 + 1, len(虚线序列) - 1):
, , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] , , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
相对关系 = 相对方向.分析(., ., ., .) 相对关系 = 相对方向.分析(.端点, .端点, .端点, .端点)
if 相对关系.是否缺口(): if 相对关系.是否缺口():
线段._添加虚线(当前线段, ) 线段._添加虚线(当前线段, )
线段._添加虚线(当前线段, ) 线段._添加虚线(当前线段, )
@@ -5720,7 +5933,7 @@ class 线段:
if 当前段.实_中枢序列: if 当前段.实_中枢序列:
if [-1] in 当前段.实_中枢序列[-1].基础序列: if [-1] in 当前段.实_中枢序列[-1].基础序列:
# 当前最后一笔在最后一中枢里 # 当前最后一笔在最后一中枢里
序号 = 当前段.基础序列.index(当前段.实_中枢序列[-1].基础序列[0]) 序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0])
进入段 = 当前段.基础序列[序号 - 1] 进入段 = 当前段.基础序列[序号 - 1]
离开段 = [-1] 离开段 = [-1]
assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号) assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号)
@@ -5774,7 +5987,7 @@ class 线段:
笔序列.append(停顿) 笔序列.append(停顿)
线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.]) 线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.])
if 线段序列 and 线段序列[-1]. is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1: if 线段序列 and 线段序列[-1]. is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1:
新段 = 虚线.创建线段(线段序列[-1].基础序列[:]) 新段 = 虚线.创建线段(线段序列[-1].基础序列)
新段.序号 = self.序号 新段.序号 = self.序号
线段._刷新(新段, 观察员.配置) 线段._刷新(新段, 观察员.配置)
if 新段.方向 is self.方向: if 新段.方向 is self.方向:
@@ -5965,7 +6178,7 @@ class 中枢:
:return: 虚线列表 :return: 虚线列表
""" """
序列: List = self.基础序列[:] 序列: List = self.基础序列.copy()
if self.第三买卖线 is not None: if self.第三买卖线 is not None:
序列.append(self.第三买卖线) 序列.append(self.第三买卖线)
return 序列 return 序列
@@ -5989,13 +6202,14 @@ class 中枢:
""" """
有效序列 = self.基础序列[:] 有效序列 = self.基础序列[:]
无效序列 = [] 无效序列 = []
序列集 = set(序列)
for 元素 in self.基础序列: for 元素 in self.基础序列:
if 元素 not in 序列: if 元素 not in 序列:
无效序列.append(元素) 无效序列.append(元素)
if 无效序列: if 无效序列:
无效 = 无效序列[0] 无效 = 无效序列[0]
序号 = self.基础序列.index(无效) 序号 = 线段._索引(self.基础序列, 无效)
有效序列 = self.基础序列[:序号] 有效序列 = self.基础序列[:序号]
if len(有效序列) < 3: if len(有效序列) < 3:
@@ -6003,14 +6217,14 @@ class 中枢:
self.本级_第三买卖线 = None self.本级_第三买卖线 = None
return False return False
self.基础序列[:] = 有效序列 self.基础序列 = 有效序列
有效序列 = [] 有效序列 = []
for 元素 in self.基础序列: for 元素 in self.基础序列:
if 相对方向.分析(self., self., 元素., 元素.).是否缺口(): if 相对方向.分析(self., self., 元素., 元素.).是否缺口():
break break
有效序列.append(元素) 有效序列.append(元素)
self.基础序列[:] = 有效序列 self.基础序列 = 有效序列
if len(self.基础序列) < 3: if len(self.基础序列) < 3:
return False return False
@@ -6182,7 +6396,7 @@ class 中枢:
, , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] , , = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
if 中枢.基础检查(, , ): if 中枢.基础检查(, , ):
新中枢 = 中枢.创建(, , , .级别, 标识) 新中枢 = 中枢.创建(, , , .级别, 标识)
序号 = 虚线序列.index() 序号 = 线段._索引(虚线序列, )
if 跳过首部 and (.序号 == 0 or 序号 == 0): if 跳过首部 and (.序号 == 0 or 序号 == 0):
continue # 方便计算走势 continue # 方便计算走势
if 序号 >= 2: if 序号 >= 2:
@@ -6203,7 +6417,7 @@ class 中枢:
中枢._从中枢序列尾部弹出(中枢序列, 当前中枢) 中枢._从中枢序列尾部弹出(中枢序列, 当前中枢)
return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1)
序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
基础序列 = [] 基础序列 = []
for 当前虚线 in 虚线序列[序号:]: for 当前虚线 in 虚线序列[序号:]:
@@ -6926,6 +7140,6 @@ if __name__ == "__main__":
当前配置 = 缠论配置.不推送() 当前配置 = 缠论配置.不推送()
当前配置.加载文件路径 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tests", "btcusd-300-1761327300-1776327900.nb") 当前配置.加载文件路径 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tests", "btcusd-300-1761327300-1776327900.nb")
with tempfile.TemporaryDirectory() as tmpdir: with tempfile.TemporaryDirectory() as tmpdir:
# 测试_读取数据(观察者("", 0, 当前配置), 当前配置)().测试_保存数据(tmpdir) 测试_读取数据(观察者("", 0, 当前配置), 当前配置)().测试_保存数据(tmpdir)
# 测试_周期合成(当前配置)().测试_保存数据(tmpdir) # 测试_周期合成(当前配置)().测试_保存数据(tmpdir)
测试_指标挂载(当前配置)().测试_保存数据(tmpdir) # 测试_指标挂载(当前配置)().测试_保存数据(tmpdir)
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project] [project]
name = "chanlun" name = "chanlun"
version = "2606.47" version = "2606.73"
description = "缠论技术分析库 — Rust 高性能实现" description = "缠论技术分析库 — Rust 高性能实现"
readme = { file = "README.md", content-type = "text/markdown" } readme = { file = "README.md", content-type = "text/markdown" }
license = { file = "LICENSE", content-type = "text/plain" } license = { file = "LICENSE", content-type = "text/plain" }
+266 -89
View File
@@ -26,33 +26,20 @@ use crate::kline_py::chan_kline_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py}; use crate::structure_py::{dashed_to_py, fractal_to_py};
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyList, PyType}; use pyo3::types::{PyDict, PyList, PyType};
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 // 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换)
static HUB_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
pub(crate) fn hub_to_py( pub(crate) fn hub_to_py(
py: Python<'_>, inner: Arc<chanlun::algorithm::hub::> py: Python<'_>, inner: Arc<chanlun::algorithm::hub::>
) -> Py<Py> { ) -> Py<Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = HUB_IDENTITY if let Some(cached) = crate::cache::hub_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
HUB_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, Py { inner }).unwrap(); 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 obj
} }
@@ -90,16 +77,21 @@ impl 背驰分析Py {
: &str, : &str,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let = .to_string();
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K线序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K线序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::MACD背驰( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::MACD背驰(
&.borrow().inner, &_inner,
&rc_list, &_inner,
, &rc_list,
) &,
)
})
} }
#[classmethod] #[classmethod]
@@ -137,15 +129,19 @@ impl 背驰分析Py {
K序列: Vec<Py<K线Py>>, K序列: Vec<Py<K线Py>>,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
) &rc_list,
)
})
} }
#[classmethod] #[classmethod]
@@ -157,15 +153,19 @@ impl 背驰分析Py {
K序列: Vec<Py<K线Py>>, K序列: Vec<Py<K线Py>>,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
) &rc_list,
)
})
} }
#[classmethod] #[classmethod]
@@ -178,17 +178,21 @@ impl 背驰分析Py {
: &Bound<'_, Py>, : &Bound<'_, Py>,
py: Python<'_>, py: Python<'_>,
) -> PyResult<bool> { ) -> PyResult<bool> {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::divergence::::( Ok(py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
&config, &rc_list,
)) &config,
)
}))
} }
#[classmethod] #[classmethod]
@@ -200,15 +204,19 @@ impl 背驰分析Py {
K序列: Vec<Py<K线Py>>, K序列: Vec<Py<K线Py>>,
py: Python<'_>, py: Python<'_>,
) -> bool { ) -> bool {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
chanlun::algorithm::divergence::::( py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&.borrow().inner, &_inner,
&rc_list, &_inner,
) &rc_list,
)
})
} }
#[classmethod] #[classmethod]
@@ -222,18 +230,169 @@ impl 背驰分析Py {
: &str, : &str,
py: Python<'_>, py: Python<'_>,
) -> PyResult<bool> { ) -> PyResult<bool> {
let _inner = Arc::clone(&.borrow().inner);
let _inner = Arc::clone(&.borrow().inner);
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列 let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter() .iter()
.map(|k| k.bind(py).borrow().inner.clone()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::divergence::::( let = .to_string();
&.borrow().inner, Ok(py.detach(move || {
&.borrow().inner, chanlun::algorithm::divergence::::(
&rc_list, &_inner,
&config, &_inner,
, &rc_list,
)) &config,
&,
)
}))
}
// ---- 观察者直传(跳过 Python list→Vec 转换,直接借用观察者内部 &[Arc<K线>] ----
#[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<bool> {
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<bool> {
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()) .map(|k| k.bind(py).borrow().inner.clone())
.collect(); .collect();
let config = .borrow().to_rust_config(py)?; let config = .borrow().to_rust_config(py)?;
let depth = match _rc { let depth = py.detach(|| match _rc {
Some(fr) => chanlun::algorithm::bi::::( Some(fr) => chanlun::algorithm::bi::::(
fr, fr,
&mut fr_seq, &mut fr_seq,
@@ -356,17 +515,21 @@ impl 笔Py {
&config, &config,
), ),
None => , None => ,
}; });
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let fr_items: Vec<Py<PyAny>> = fr_seq
.iter()
.map(|f| fractal_to_py(py, Arc::clone(f)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for f in fr_seq { .call_method1("extend", (PyList::new(py, &fr_items)?,))?;
.call_method1("append", (fractal_to_py(py, f),))?; let bi_items: Vec<Py<PyAny>> = bi_seq
} .iter()
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for d in bi_seq { .call_method1("extend", (PyList::new(py, &bi_items)?,))?;
.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(depth) Ok(depth)
} }
@@ -552,19 +715,23 @@ impl 线段Py {
let rel_list: Vec<chanlun::types::> = let rel_list: Vec<chanlun::types::> =
.map(|v| v.into_iter().map(|d| d.inner).collect()) .map(|v| v.into_iter().map(|d| d.inner).collect())
.unwrap_or(default_rel); .unwrap_or(default_rel);
chanlun::algorithm::segment::线::( py.detach(|| {
&bi_list, chanlun::algorithm::segment::线::(
&mut seg_seq, &bi_list,
&config, &mut seg_seq,
, &config,
&rel_list, ,
); &rel_list,
);
});
// 写回 Python 列表 // 写回 Python 列表 (bulk extend)
let items: Vec<Py<PyAny>> = seg_seq
.iter()
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
.collect();
线.call_method0("clear")?; 线.call_method0("clear")?;
for d in seg_seq { 线.call_method1("extend", (PyList::new(py, &items)?,))?;
线.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(()) Ok(())
} }
@@ -590,13 +757,17 @@ impl 线段Py {
} }
let config = .borrow().to_rust_config(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<Py<PyAny>> = seg_seq
.iter()
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
.collect();
线.call_method0("clear")?; 线.call_method0("clear")?;
for d in seg_seq { 线.call_method1("extend", (PyList::new(py, &items)?,))?;
线.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(()) Ok(())
} }
@@ -714,7 +885,7 @@ impl 中枢Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[getter] #[getter]
@@ -725,7 +896,7 @@ impl 中枢Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); 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)))?; list.append(dashed_to_py(py, Arc::clone(d)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -736,7 +907,6 @@ impl 中枢Py {
self.inner self.inner
.线 .线
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|d| dashed_to_py(py, Arc::clone(d))) .map(|d| dashed_to_py(py, Arc::clone(d)))
} }
@@ -746,7 +916,6 @@ impl 中枢Py {
self.inner self.inner
._第三买卖线 ._第三买卖线
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|d| dashed_to_py(py, Arc::clone(d))) .map(|d| dashed_to_py(py, Arc::clone(d)))
} }
@@ -845,13 +1014,17 @@ impl 中枢Py {
hub_seq.push(Arc::clone(&h.inner)); hub_seq.push(Arc::clone(&h.inner));
} }
let config = .borrow().to_rust_config(.py())?; 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<Py<PyAny>> = hub_seq
.iter()
.map(|h| hub_to_py(py, Arc::clone(h)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for h in hub_seq { .call_method1("extend", (PyList::new(py, &items)?,))?;
.call_method1("append", (hub_to_py(py, h),))?;
}
Ok(()) Ok(())
} }
@@ -958,13 +1131,17 @@ impl 中枢Py {
hub_seq.push(Arc::clone(&h.inner)); 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<Py<PyAny>> = hub_seq
.iter()
.map(|h| hub_to_py(py, Arc::clone(h)).into_any())
.collect();
.call_method0("clear")?; .call_method0("clear")?;
for h in hub_seq { .call_method1("extend", (PyList::new(py, &items)?,))?;
.call_method1("append", (hub_to_py(py, h),))?;
}
Ok(()) Ok(())
} }
+74 -25
View File
@@ -22,15 +22,16 @@
* SOFTWARE. * SOFTWARE.
*/ */
use parking_lot::RwLock;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType}; use pyo3::types::{PyDict, PyType};
use std::sync::RwLock;
use crate::algorithm_py::hub_to_py; use crate::algorithm_py::hub_to_py;
use crate::kline_py::bar_to_py; use crate::kline_py::bar_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py, Py}; use crate::structure_py::{dashed_to_py, fractal_to_py, Py};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};
use crate::config_py::Py; use crate::config_py::Py;
use crate::kline_py::{K线Py, K线Py}; use crate::kline_py::{K线Py, K线Py};
@@ -693,28 +694,27 @@ impl 买卖点Py {
#[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)] #[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)]
pub struct Py { pub struct Py {
pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::>>>, pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::>>>,
: std::sync::Mutex<Option<Py<Py>>>, : parking_lot::Mutex<Option<Py<Py>>>,
: AtomicU64,
} }
impl Py { impl Py {
pub(crate) fn obs( pub(crate) fn obs(
&self, &self,
) -> std::sync::RwLockReadGuard<'_, chanlun::business::observer::> { ) -> parking_lot::RwLockReadGuard<'_, chanlun::business::observer::> {
self.inner self.inner
.as_ref() .as_ref()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造") .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
.read() .read()
.unwrap_or_else(|e| e.into_inner())
} }
pub(crate) fn obs_mut( pub(crate) fn obs_mut(
&self, &self,
) -> std::sync::RwLockWriteGuard<'_, chanlun::business::observer::> { ) -> parking_lot::RwLockWriteGuard<'_, chanlun::business::observer::> {
self.inner self.inner
.as_ref() .as_ref()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造") .expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
.write() .write()
.unwrap_or_else(|e| e.into_inner())
} }
} }
@@ -770,7 +770,8 @@ impl 观察者Py {
inner: Some(chanlun::business::observer::::new( inner: Some(chanlun::business::observer::::new(
, , config, , , config,
)), )),
: std::sync::Mutex::new(None), : parking_lot::Mutex::new(None),
: AtomicU64::new(0),
}) })
} }
@@ -824,7 +825,7 @@ impl 观察者Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<Py>> { fn (&self, py: Python<'_>) -> PyResult<Py<Py>> {
let mut cache = self..lock().unwrap(); let mut cache = self..lock();
if let Some(ref cached) = *cache { if let Some(ref cached) = *cache {
Ok(cached.clone_ref(py)) Ok(cached.clone_ref(py))
} else { } else {
@@ -838,11 +839,8 @@ impl 观察者Py {
#[setter] #[setter]
fn set_配置(&self, value: &Bound<'_, Py>) -> PyResult<()> { fn set_配置(&self, value: &Bound<'_, Py>) -> PyResult<()> {
let config = value.borrow().to_rust_config(value.py())?; let config = value.borrow().to_rust_config(value.py())?;
*self..lock() = Some(value.clone().unbind());
self.obs_mut(). = config; self.obs_mut(). = config;
self.
.lock()
.unwrap()
.replace(value.clone().unbind());
Ok(()) Ok(())
} }
@@ -861,25 +859,53 @@ impl 观察者Py {
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现) /// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现)
#[pyo3(name = "_增加原始K线")] #[pyo3(name = "_增加原始K线")]
fn K线_impl(&mut self, K: &Bound<'_, K线Py>) -> PyResult<()> { fn K线_impl(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> {
self.obs_mut().K线((*K.borrow().inner).clone()); let kline = (*K.borrow().inner).clone();
Ok(()) 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::<String>().cloned())
.unwrap_or_else(|| "未知算法错误".into());
Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
"[{symbol}] 算法异常: {msg}"
)))
}
}
} }
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写) /// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写)
fn K线(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> { fn K线(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> {
// 同步缓存的 Python 配置到 Rust 观察者(支持 obs.配置 直接修改) // 版本对比,仅在配置变更时同步(支持 obs.配置.field = value 直接修改)
{ {
let me = slf.borrow(); let me = slf.borrow();
if let Some(ref cached) = *me..lock().unwrap() { if let Some(ref cached) = *me..lock() {
let py = slf.py(); let py = slf.py();
if let Ok(config) = cached.bind(py).borrow().to_rust_config(py) { let cfg_ref = cached.bind(py).borrow();
me.obs_mut(). = config; 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,))?; // 直接调用 Rust 实现,跳过 Python dispatch
Ok(()) Self::K线_impl(slf, K)
} }
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线, /// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线,
@@ -931,9 +957,31 @@ impl 观察者Py {
/// 静态重新分析(内部实现) /// 静态重新分析(内部实现)
#[pyo3(name = "_静态重新分析")] #[pyo3(name = "_静态重新分析")]
fn _impl(&mut self) -> PyResult<()> { fn _impl(slf: &Bound<'_, Self>) -> PyResult<()> {
self.obs_mut().(); let obs_arc = slf
Ok(()) .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::<String>().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. { for (, obs_rc) in &self.inner. {
let obs_py = Py { let obs_py = Py {
inner: Some(obs_rc.clone()), inner: Some(obs_rc.clone()),
: std::sync::Mutex::new(None), : parking_lot::Mutex::new(None),
: AtomicU64::new(0),
}; };
dict.set_item(, obs_py)?; dict.set_item(, obs_py)?;
} }
+199
View File
@@ -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<CacheMode> = 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<HashMap<usize, Py<super::kline_py::K线Py>>> = RefCell::new(HashMap::new());
}
static BAR_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<super::kline_py::K线Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn bar_get(py: Python<'_>, key: usize) -> Option<Py<super::kline_py::K线Py>> {
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<super::kline_py::K线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<HashMap<usize, Py<super::kline_py::K线Py>>> = RefCell::new(HashMap::new());
}
static KLINE_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<super::kline_py::K线Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn kline_get(py: Python<'_>, key: usize) -> Option<Py<super::kline_py::K线Py>> {
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<super::kline_py::K线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<HashMap<usize, Py<Py>>> = RefCell::new(HashMap::new());
}
static FRACTAL_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn fractal_get(py: Python<'_>, key: usize) -> Option<Py<Py>> {
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<HashMap<usize, Py<线Py>>> = RefCell::new(HashMap::new());
}
static DASHED_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<线Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn dashed_get(py: Python<'_>, key: usize) -> Option<Py<线Py>> {
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<HashMap<usize, Py<Py>>> = RefCell::new(HashMap::new());
}
static HUB_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<Py>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn hub_get(py: Python<'_>, key: usize) -> Option<Py<Py>> {
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<HashMap<usize, Py<PySet>>> = RefCell::new(HashMap::new());
}
static BSP_GLOBAL: std::sync::LazyLock<DashMap<usize, Py<PySet>>> =
std::sync::LazyLock::new(DashMap::new);
pub fn bsp_get(py: Python<'_>, key: usize) -> Option<Py<PySet>> {
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<PySet>) {
match get_mode() {
CacheMode::ThreadLocal => BSP_LOCAL.with(|m| {
m.borrow_mut().insert(key, obj);
}),
CacheMode::Global => {
BSP_GLOBAL.insert(key, obj);
}
}
}
+38 -7
View File
@@ -22,10 +22,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
use chanlun::warn;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType}; use pyo3::types::{PyDict, PyType};
use std::collections::HashMap; use std::collections::HashMap;
use tracing::warn; use std::sync::atomic::{AtomicU64, Ordering};
/// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 /// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。
/// ///
@@ -95,6 +96,8 @@ use tracing::warn;
#[pyclass(name = "缠论配置", module = "chanlun._chanlun")] #[pyclass(name = "缠论配置", module = "chanlun._chanlun")]
pub struct Py { pub struct Py {
fields: HashMap<String, Py<PyAny>>, fields: HashMap<String, Py<PyAny>>,
: parking_lot::Mutex<Option<chanlun::config::>>,
pub(crate) : AtomicU64,
} }
#[pymethods] #[pymethods]
@@ -120,7 +123,11 @@ impl 缠论配置Py {
// 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换 // 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换
let config = dict_to_rust_config(&fields)?; let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?; 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<Py<PyAny>> { fn __getattr__(&self, name: &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
@@ -135,10 +142,13 @@ impl 缠论配置Py {
fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> { fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> {
if self.fields.contains_key(name) { if self.fields.contains_key(name) {
self.fields.insert(name.to_string(), value.clone().unbind()); self.fields.insert(name.to_string(), value.clone().unbind());
*self..lock() = None;
self..fetch_add(1, Ordering::Relaxed);
// 通过 serde 往返验证类型 // 通过 serde 往返验证类型
match dict_to_rust_config(&self.fields) { match dict_to_rust_config(&self.fields) {
Ok(config) => { Ok(config) => {
self.fields = config_to_field_dict(&config)?; self.fields = config_to_field_dict(&config)?;
*self..lock() = Some(config);
Ok(()) Ok(())
} }
Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!( Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!(
@@ -217,7 +227,11 @@ impl 缠论配置Py {
let config = dict_to_rust_config(&fields)?; let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?; let fields = config_to_field_dict(&config)?;
Ok(Self { fields }) Ok(Self {
fields,
: parking_lot::Mutex::new(Some(config)),
: AtomicU64::new(1),
})
} }
#[classmethod] #[classmethod]
@@ -231,7 +245,11 @@ impl 缠论配置Py {
fn (_cls: &Bound<'_, PyType>) -> PyResult<Self> { fn (_cls: &Bound<'_, PyType>) -> PyResult<Self> {
let config = chanlun::config::::default().(); let config = chanlun::config::::default().();
let fields = config_to_field_dict(&config)?; let fields = config_to_field_dict(&config)?;
Ok(Self { fields }) Ok(Self {
fields,
: parking_lot::Mutex::new(Some(config)),
: AtomicU64::new(1),
})
} }
#[classmethod] #[classmethod]
@@ -302,18 +320,31 @@ impl 缠论配置Py {
let config = dict_to_rust_config(&fields)?; let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?; 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( pub(crate) fn to_rust_config(
&self, &self,
_py: Python<'_>, _py: Python<'_>,
) -> PyResult<chanlun::config::> { ) -> PyResult<chanlun::config::> {
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<Self> { pub(crate) fn from_rust_config(config: &chanlun::config::) -> PyResult<Self> {
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),
})
} }
} }
+291 -373
View File
@@ -22,33 +22,10 @@
* SOFTWARE. * SOFTWARE.
*/ */
use std::num::NonZeroUsize;
use crate::business_py::Py; use crate::business_py::Py;
use crate::business_py::Py; use crate::business_py::Py;
use std::sync::Mutex;
use lru::LruCache;
use pyo3::prelude::*; use pyo3::prelude::*;
/// 缓存辅助宏:在调用点创建静态 LruCache,先查后存
macro_rules! with_cache {
($cache:ident, $size:literal, $key_expr:expr, $compute:expr) => {{
use std::sync::LazyLock;
static $cache: LazyLock<Mutex<LruCache<(usize, usize, i64), (bool, String)>>> =
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 两种类型) /// 从 Python 值中提取时间戳(兼容 i64 和 datetime 两种类型)
fn (val: &Bound<'_, PyAny>) -> PyResult<i64> { fn (val: &Bound<'_, PyAny>) -> PyResult<i64> {
if let Ok(ts) = val.extract::<i64>() { if let Ok(ts) = val.extract::<i64>() {
@@ -129,71 +106,60 @@ fn K线相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( // 快速路径
C_KLINE, if let (Ok(a), Ok(b)) = (
128, A.cast::<crate::kline_py::K线Py>(),
( B.cast::<crate::kline_py::K线Py>(),
A.as_ptr() as usize, ) {
B.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
.to_bits() as i64 }
), // 回退路径
{ let = "K线校验";
// 快速路径 let = [
if let (Ok(a), Ok(b)) = ( "标识",
A.cast::<crate::kline_py::K线Py>(), "序号",
B.cast::<crate::kline_py::K线Py>(), "周期",
) { "时间戳",
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缺失属性")));
"",
"开盘价",
"收盘价",
"成交量",
];
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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 全部字段一致")))
} }
) 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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 全部字段一致")))
} }
// ========== 缠论K线相等 ========== // ========== 缠论K线相等 ==========
@@ -205,102 +171,91 @@ fn 缠论K线相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_CHAN_K, A.cast::<crate::kline_py::K线Py>(),
4096, B.cast::<crate::kline_py::K线Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "缠论K线校验";
), let = [
{ "序号",
if let (Ok(a), Ok(b)) = ( "时间戳",
A.cast::<crate::kline_py::K线Py>(), "",
B.cast::<crate::kline_py::K线Py>(), "",
) { "方向",
return Ok(a.borrow().inner.(&b.borrow().inner, )); "分型",
"周期",
"标识",
"分型特征值",
"原始起始序号",
"原始结束序号",
"标的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线校验"; } else if == "标的K线" {
let = [ if let Some(r) = (&valA, &valB, , ) {
"序号", if !r.0 {
"时间戳", return Ok((false, r.1));
"", } else {
"",
"方向",
"分型",
"周期",
"标识",
"分型特征值",
"原始起始序号",
"原始结束序号",
"标的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; 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::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().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::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
} }
) }
Ok((true, format!("{标签}: 全部字段嵌套校验一致")))
} }
// ========== 分型相等 ========== // ========== 分型相等 ==========
@@ -312,88 +267,77 @@ fn 分型相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_FRACTAL, A.cast::<crate::structure_py::Py>(),
4096, B.cast::<crate::structure_py::Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "分型校验";
), // Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter
{ for & in &["", "", ""] {
if let (Ok(a), Ok(b)) = ( let valA = A.getattr()?;
A.cast::<crate::structure_py::Py>(), let valB = B.getattr()?;
B.cast::<crate::structure_py::Py>(), if let Some(r) = (&valA, &valB, , ) {
) { if !r.0 {
return Ok(a.borrow().inner.(&b.borrow().inner, )); 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::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().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::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 自有字段+三根缠论K线全部校验一致")))
} }
// ========== 缺口相等 ========== // ========== 缺口相等 ==========
@@ -405,54 +349,42 @@ fn 缺口相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_GAP, A.cast::<crate::types_py::Py>(),
4096, B.cast::<crate::types_py::Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "缺口校验";
), for & in &["", ""] {
{ let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if let (Ok(a), Ok(b)) = ( if a有 && !b有 {
A.cast::<crate::types_py::Py>(), return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
B.cast::<crate::types_py::Py>(),
) {
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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 上下沿价格校验完全一致")))
} }
) 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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 上下沿价格校验完全一致")))
} }
// ========== 线段特征相等 ========== // ========== 线段特征相等 ==========
#[pyfunction] #[pyfunction]
@@ -462,74 +394,60 @@ fn 线段特征相等(
B: &Bound<'_, PyAny>, B: &Bound<'_, PyAny>,
: f64, : f64,
) -> PyResult<(bool, String)> { ) -> PyResult<(bool, String)> {
with_cache!( if let (Ok(a), Ok(b)) = (
C_SEG_FEAT, A.cast::<crate::structure_py::线Py>(),
4096, B.cast::<crate::structure_py::线Py>(),
( ) {
A.as_ptr() as usize, return Ok(a.borrow().inner.(&b.borrow().inner, ));
B.as_ptr() as usize, }
.to_bits() as i64 let = "线段特征校验";
), for & in &["序号", "标识", "线段方向", "基础序列"] {
{ let (a有, b有) = (A.hasattr()?, B.hasattr()?);
if let (Ok(a), Ok(b)) = ( if a有 && !b有 {
A.cast::<crate::structure_py::线Py>(), return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
B.cast::<crate::structure_py::线Py>(),
) {
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::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 字段与内部虚线序列全部一致")))
} }
) 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::<String>().unwrap_or_default();
let sb = valB.str()?.extract::<String>().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::<String>().unwrap_or_default();
let rb = valB.repr()?.extract::<String>().unwrap_or_default();
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}")));
}
}
}
Ok((true, format!("{标签}: 字段与内部虚线序列全部一致")))
} }
// ========== 中枢相等 ========== // ========== 中枢相等 ==========
@@ -790,8 +708,8 @@ fn 观察者相等(
.inner .inner
.clone() .clone()
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("观察者B 内部为空"))?; .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("观察者B 内部为空"))?;
let obs_a = arc_a.read().unwrap(); let obs_a = arc_a.read();
let obs_b = arc_b.read().unwrap(); let obs_b = arc_b.read();
Ok(obs_a.(&obs_b, )) Ok(obs_a.(&obs_b, ))
} }
+17 -21
View File
@@ -290,7 +290,7 @@ impl 相对强弱指数Py {
} }
#[getter] #[getter]
fn RSI历史队列(&self) -> Vec<f64> { fn RSI历史队列(&self) -> Vec<f64> {
self.inner.RSI历史队列.clone() self.inner.RSI历史队列.iter().copied().collect()
} }
fn __str__(&self) -> String { fn __str__(&self) -> String {
@@ -470,11 +470,11 @@ impl 随机指标Py {
} }
#[getter] #[getter]
fn (&self) -> Vec<f64> { fn (&self) -> Vec<f64> {
self.inner..clone() self.inner..iter().copied().collect()
} }
#[getter] #[getter]
fn (&self) -> Vec<f64> { fn (&self) -> Vec<f64> {
self.inner..clone() self.inner..iter().copied().collect()
} }
#[getter] #[getter]
fn RSV(&self) -> Option<f64> { fn RSV(&self) -> Option<f64> {
@@ -796,16 +796,12 @@ impl 指标容器Py {
} }
fn __getitem__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> { fn __getitem__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
if self.() { match self.inner.() {
match self.inner.() { Some(v) => _to_py(v, py),
Some(v) => _to_py(v, py), None => Err(pyo3::exceptions::PyKeyError::new_err(format!(
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyKeyError::new_err(format!(
"指标 '{}' 不存在", "指标 '{}' 不存在",
))) ))),
} }
} }
@@ -822,16 +818,12 @@ impl 指标容器Py {
} }
return Ok(dict.into()); return Ok(dict.into());
} }
if self.() { match self.inner.() {
match self.inner.() { Some(v) => _to_py(v, py),
Some(v) => _to_py(v, py), None => Err(pyo3::exceptions::PyAttributeError::new_err(format!(
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
"指标 '{}' 不存在于 指标容器 中", "指标 '{}' 不存在于 指标容器 中",
))) ))),
} }
} }
@@ -917,7 +909,12 @@ impl 均线工具Py {
return Ok(sum / (n.max(1)) as f64); 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线的均线缓存中读取 // 尝试从前一根K线的均线缓存中读取
let prev_cached = K序列[n - 2] let prev_cached = K序列[n - 2]
.bind(py) .bind(py)
@@ -925,7 +922,6 @@ impl 均线工具Py {
.inner .inner
. .
.read() .read()
.unwrap()
.线() .线()
.and_then(|m| m.get(&prev_key)) .and_then(|m| m.get(&prev_key))
.copied(); .copied();
+47 -56
View File
@@ -22,11 +22,11 @@
* SOFTWARE. * SOFTWARE.
*/ */
use parking_lot::RwLock;
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyList, PyType}; use pyo3::types::{PyBytes, PyDict, PyList, PyType};
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::config_py::Py; use crate::config_py::Py;
@@ -149,7 +149,6 @@ impl K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.macd_cloned() .macd_cloned()
.map(|m| 线Py { inner: m }) .map(|m| 线Py { inner: m })
} }
@@ -159,7 +158,6 @@ impl K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.rsi_cloned() .rsi_cloned()
.map(|r| Py { inner: r }) .map(|r| Py { inner: r })
} }
@@ -169,7 +167,6 @@ impl K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.kdj_cloned() .kdj_cloned()
.map(|k| Py { inner: k }) .map(|k| Py { inner: k })
} }
@@ -178,7 +175,7 @@ impl K线Py {
#[getter] #[getter]
fn (&self) -> Py { fn (&self) -> Py {
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) .take(end_idx - start_idx + 1)
.collect()) .collect())
} }
/// 根据当前K线和方向生成下一根K线(用于随机回测)
#[pyo3(signature = (方向, 居中 = false))]
fn K线生成新K线(
&self, : &Bound<'_, PyAny>, : bool
) -> PyResult<Self> {
let dir: chanlun::types:: = if let Ok(d) = .extract::<PyRef<'_, Py>>()
{
d.inner
} else if let Ok(i) = .extract::<i64>() {
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线 ========== // ========== 缠论K线 ==========
@@ -371,57 +401,29 @@ impl 缠论K线Py {
} }
} }
/// 对象标识缓存:Arc 地址 → 规范 Python 对象
/// 确保同一底层 Arc 指针在 Python 侧始终映射到同一 PyObject
/// 使用全局 static 而非 thread_local!,保证跨线程对象标识和买卖点信息一致性
static BAR_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<K线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static KLINE_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<K线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
/// 买卖点信息缓存 — 按 Arc 指针全局共享,确保所有 wrapper 看到同一 PySet
static BSP_CACHE: std::sync::LazyLock<RwLock<HashMap<usize, Py<pyo3::types::PySet>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
/// 将 Rc<K线> 转为 Py<K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
pub(crate) fn bar_to_py( pub(crate) fn bar_to_py(
py: Python<'_>, py: Python<'_>,
inner: std::sync::Arc<chanlun::kline::bar::K线>, inner: std::sync::Arc<chanlun::kline::bar::K线>,
) -> Py<K线Py> { ) -> Py<K线Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = BAR_IDENTITY if let Some(cached) = crate::cache::bar_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
let obj = Py::new(py, K线Py { inner }).unwrap(); 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 obj
} }
/// 将 Rc<缠论K线> 转为 Py<缠论K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
pub(crate) fn chan_kline_to_py( pub(crate) fn chan_kline_to_py(
py: Python<'_>, py: Python<'_>,
inner: std::sync::Arc<chanlun::kline::chan_kline::K线>, inner: std::sync::Arc<chanlun::kline::chan_kline::K线>,
) -> Py<K线Py> { ) -> Py<K线Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = KLINE_IDENTITY if let Some(cached) = crate::cache::kline_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
let obj = Py::new(py, K线Py::from_rc(inner)).unwrap(); let obj = Py::new(py, K线Py::from_rc(inner)).unwrap();
KLINE_IDENTITY crate::cache::kline_insert(py, key, &obj);
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj obj
} }
@@ -462,7 +464,7 @@ impl 缠论K线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> Py<Py> { fn (&self, py: Python<'_>) -> Py<Py> {
crate::types_py::(py, *self.inner..read().unwrap()) crate::types_py::(py, *self.inner..read())
} }
#[getter] #[getter]
@@ -470,7 +472,6 @@ impl 缠论K线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.map(|f| crate::types_py::(py, f)) .map(|f| crate::types_py::(py, f))
} }
@@ -501,7 +502,7 @@ impl 缠论K线Py {
#[getter] #[getter]
fn K线(&self, py: Python<'_>) -> Py<K线Py> { fn K线(&self, py: Python<'_>) -> Py<K线Py> {
bar_to_py(py, self.inner.K线.read().unwrap().clone()) bar_to_py(py, self.inner.K线.read().clone())
} }
/// pandas 兼容 — 返回所有字段构成的字典 /// pandas 兼容 — 返回所有字段构成的字典
@@ -556,11 +557,7 @@ impl 缠论K线Py {
// 复制买卖点信息到镜像 // 复制买卖点信息到镜像
let src_key = Arc::as_ptr(&self.inner) as usize; let src_key = Arc::as_ptr(&self.inner) as usize;
let dst_key = Arc::as_ptr(&mirror.inner) as usize; let dst_key = Arc::as_ptr(&mirror.inner) as usize;
let cached_src = BSP_CACHE let cached_src = crate::cache::bsp_get(py, src_key);
.read()
.unwrap()
.get(&src_key)
.map(|p| p.clone_ref(py));
if let Some(cached_src) = cached_src if let Some(cached_src) = cached_src
&& let Ok(new_set) = pyo3::types::PySet::empty(py) && let Ok(new_set) = pyo3::types::PySet::empty(py)
{ {
@@ -568,7 +565,7 @@ impl 缠论K线Py {
let _ = new_set.add(item); let _ = new_set.add(item);
} }
let py_set: Py<pyo3::types::PySet> = new_set.into(); let py_set: Py<pyo3::types::PySet> = new_set.into();
BSP_CACHE.write().unwrap().insert(dst_key, py_set); crate::cache::bsp_insert(py, dst_key, py_set);
} }
mirror mirror
} }
@@ -595,25 +592,19 @@ impl 缠论K线Py {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let key = Arc::as_ptr(&self.inner) as usize; 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 { if let Some(set) = cached {
return Ok(set.into_any()); return Ok(set.into_any());
} }
// 创建新的 PySet,从 Rust HashSet 同步已有内容 // 创建新的 PySet,从 Rust HashSet 同步已有内容
let set = pyo3::types::PySet::empty(py)?; 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() { for item in bsp_info.iter() {
set.add(item.as_str())?; set.add(item.as_str())?;
} }
drop(bsp_info); drop(bsp_info);
BSP_CACHE.write().unwrap().insert(key, set.into()); crate::cache::bsp_insert(py, key, set.into());
Ok(BSP_CACHE Ok(crate::cache::bsp_get(py, key).unwrap().into_any())
.read()
.unwrap()
.get(&key)
.unwrap()
.clone_ref(py)
.into_any())
} }
#[classmethod] #[classmethod]
+87 -15
View File
@@ -102,6 +102,7 @@ fn init_tracing() {
mod algorithm_py; mod algorithm_py;
mod business_py; mod business_py;
pub(crate) mod cache;
mod config_py; mod config_py;
mod equality_py; mod equality_py;
mod indicators_py; mod indicators_py;
@@ -121,15 +122,25 @@ fn set_分型模式(value: bool) {
chanlun::structure::fractal_obj::.store(value, Ordering::Relaxed); chanlun::structure::fractal_obj::.store(value, Ordering::Relaxed);
} }
/// 扩展线段模式 — 控制虚线高低取值方式,默认 False
#[pyfunction]
fn get_扩展线段模式() -> bool {
chanlun::structure::dash_line::线.load(Ordering::Relaxed)
}
/// 设置 扩展线段模式
#[pyfunction]
fn set_扩展线段模式(value: bool) {
chanlun::structure::dash_line::线.store(value, Ordering::Relaxed);
}
/// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off") /// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off")
#[pyfunction] #[pyfunction]
fn get_log_level() -> &'static str { fn get_log_level() -> &'static str {
(LOG_LEVEL.load(Ordering::Relaxed)) (LOG_LEVEL.load(Ordering::Relaxed))
} }
/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off") /// 设置日志级别 — 自动启用日志,同步更新 tracing subscriber
///
/// 设为 "off" 可完全关闭日志输出。
#[pyfunction] #[pyfunction]
fn set_log_level(level: &str) -> PyResult<()> { fn set_log_level(level: &str) -> PyResult<()> {
let = (level).ok_or_else(|| { let = (level).ok_or_else(|| {
@@ -138,30 +149,91 @@ fn set_log_level(level: &str) -> PyResult<()> {
level 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); 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(()) 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 高性能实现 /// 缠论技术分析库 — Rust 高性能实现
#[pymodule] #[pymodule]
/// 缠论技术分析库 — Rust 高性能实现 /// 缠论技术分析库 — Rust 高性能实现
fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { 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!(get_分型模式, m)?)?;
m.add_function(wrap_pyfunction!(set_分型模式, 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!(get_log_level, m)?)?;
m.add_function(wrap_pyfunction!(set_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: 枚举和基础类型 // 阶段 1: 枚举和基础类型
types_py::register(m)?; types_py::register(m)?;
// 阶段 2: 配置 // 阶段 2: 配置
+18 -75
View File
@@ -24,9 +24,7 @@
use pyo3::prelude::*; use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType}; use pyo3::types::{PyDict, PyType};
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use crate::algorithm_py::hub_to_py; use crate::algorithm_py::hub_to_py;
@@ -35,37 +33,18 @@ use crate::kline_py::{K线Py, bar_to_py, 缠论K线Py};
// ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ---- // ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ----
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 // 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换)
static FRACTAL_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static DASHED_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static SEGFEAT_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
pub(crate) fn fractal_to_py( pub(crate) fn fractal_to_py(
py: Python<'_>, py: Python<'_>,
inner: Arc<chanlun::structure::fractal_obj::>, inner: Arc<chanlun::structure::fractal_obj::>,
) -> Py<Py> { ) -> Py<Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = FRACTAL_IDENTITY if let Some(cached) = crate::cache::fractal_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
// 清理 refcnt==1 的过期条目(仅缓存持有,Python 侧已无引用)
FRACTAL_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, Py { inner }).unwrap(); let obj = Py::new(py, Py { inner }).unwrap();
FRACTAL_IDENTITY crate::cache::fractal_insert(py, key, &obj);
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj obj
} }
@@ -74,23 +53,11 @@ pub(crate) fn dashed_to_py(
inner: Arc<chanlun::structure::dash_line::线>, inner: Arc<chanlun::structure::dash_line::线>,
) -> Py<线Py> { ) -> Py<线Py> {
let key = Arc::as_ptr(&inner) as usize; let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = DASHED_IDENTITY if let Some(cached) = crate::cache::dashed_get(py, key) {
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached; return cached;
} }
DASHED_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, 线Py { inner }).unwrap(); let obj = Py::new(py, 线Py { inner }).unwrap();
DASHED_IDENTITY crate::cache::dashed_insert(py, key, &obj);
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj obj
} }
@@ -98,25 +65,7 @@ pub(crate) fn segfeat_to_py(
py: Python<'_>, py: Python<'_>,
inner: Arc<chanlun::structure::segment_feat::线>, inner: Arc<chanlun::structure::segment_feat::线>,
) -> Py<线Py> { ) -> Py<线Py> {
let key = Arc::as_ptr(&inner) as usize; Py::new(py, 线Py { inner }).unwrap()
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
} }
use crate::types_py::{Py, Py, Py}; use crate::types_py::{Py, Py, Py};
@@ -393,7 +342,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[getter] #[getter]
@@ -413,7 +362,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> Py<Py> { 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] #[getter]
@@ -423,7 +372,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[getter(_特征序列_显示)] #[getter(_特征序列_显示)]
@@ -439,7 +388,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); let list = pyo3::types::PyList::empty(py);
for item in self.inner..read().unwrap().iter() { for item in self.inner..read().iter() {
match item { match item {
Some(feat) => list.append(segfeat_to_py(py, Arc::clone(feat)))?, Some(feat) => list.append(segfeat_to_py(py, Arc::clone(feat)))?,
None => { None => {
@@ -460,18 +409,13 @@ impl 虚线Py {
self.inner self.inner
.K线 .K线
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k))) .map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))
} }
#[getter] #[getter]
fn (&self) -> Option<Py> { fn (&self) -> Option<Py> {
self.inner self.inner..read().map(|q| Py { inner: q })
.
.read()
.unwrap()
.map(|q| Py { inner: q })
} }
#[getter] #[getter]
@@ -479,7 +423,6 @@ impl 虚线Py {
self.inner self.inner
. .
.read() .read()
.unwrap()
.as_ref() .as_ref()
.map(|d| dashed_to_py(py, Arc::clone(d))) .map(|d| dashed_to_py(py, Arc::clone(d)))
} }
@@ -489,7 +432,7 @@ impl 虚线Py {
#[getter] #[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); 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)))?; list.append(dashed_to_py(py, Arc::clone(d)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -498,7 +441,7 @@ impl 虚线Py {
#[getter] #[getter]
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); 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)))?; list.append(hub_to_py(py, Arc::clone(h)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -507,7 +450,7 @@ impl 虚线Py {
#[getter] #[getter]
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); 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)))?; list.append(hub_to_py(py, Arc::clone(h)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -516,7 +459,7 @@ impl 虚线Py {
#[getter] #[getter]
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); 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)))?; list.append(hub_to_py(py, Arc::clone(h)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -528,7 +471,7 @@ impl 虚线Py {
/// 笔序列 /// 笔序列
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> { fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py); 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)))?; list.append(dashed_to_py(py, Arc::clone(d)))?;
} }
Ok(list.into()) Ok(list.into())
@@ -1005,12 +948,12 @@ impl 线段特征Py {
#[getter] #[getter]
fn (&self) -> String { fn (&self) -> String {
self.inner..read().unwrap().clone() self.inner..read().clone()
} }
#[setter] #[setter]
fn set_标识(&self, value: String) { fn set_标识(&self, value: String) {
*self.inner..write().unwrap() = value; *self.inner..write() = value;
} }
#[getter] #[getter]
+19 -3
View File
@@ -22,8 +22,8 @@
* SOFTWARE. * SOFTWARE.
*/ */
use parking_lot::Mutex;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Mutex;
use pyo3::basic::CompareOp; use pyo3::basic::CompareOp;
use pyo3::prelude::*; use pyo3::prelude::*;
@@ -37,7 +37,7 @@ pub fn 获取分型结构单例(
py: Python<'_>, py: Python<'_>,
inner: chanlun::types::, inner: chanlun::types::,
) -> Py<Py> { ) -> Py<Py> {
let mut guard = _单例缓存.lock().unwrap(); let mut guard = _单例缓存.lock();
if let Some(ref map) = *guard { if let Some(ref map) = *guard {
return map[&(inner as u8)].clone_ref(py); return map[&(inner as u8)].clone_ref(py);
} }
@@ -67,7 +67,7 @@ pub fn 获取相对方向单例(
py: Python<'_>, py: Python<'_>,
inner: chanlun::types::, inner: chanlun::types::,
) -> Py<Py> { ) -> Py<Py> {
let mut guard = _单例缓存.lock().unwrap(); let mut guard = _单例缓存.lock();
if let Some(ref map) = *guard { if let Some(ref map) = *guard {
return map[&(inner as u8)].clone_ref(py); return map[&(inner as u8)].clone_ref(py);
} }
@@ -312,6 +312,22 @@ impl 相对方向Py {
chanlun::types::::(, , , ), chanlun::types::::(, , , ),
) )
} }
/// 从可选方向序列中随机选取指定数量
#[classmethod]
#[pyo3(signature = (数量, 可选方向, 可重复 = true))]
fn (
_cls: &Bound<'_, PyType>,
: usize,
: Vec<Py<Self>>,
: bool,
py: Python<'_>,
) -> Vec<Py<Self>> {
let dirs: Vec<chanlun::types::> =
.iter().map(|d| d.borrow(py).inner).collect();
let result = chanlun::types::::(, &dirs, );
result.iter().map(|d| (py, *d)).collect()
}
} }
// ========== 分型结构 ========== // ========== 分型结构 ==========
+69
View File
@@ -2776,5 +2776,74 @@ class Test缠论配置双端一致(unittest.TestCase):
self.assertFalse(diff_py[k], f"不推送差异字段 {k} 应为 False") 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__": if __name__ == "__main__":
unittest.main() unittest.main()
+3 -3
View File
@@ -1,6 +1,6 @@
[package] [package]
name = "chanlun" name = "chanlun"
version = "26.6.3" version = "26.6.4"
edition = "2024" edition = "2024"
license = "MIT" license = "MIT"
description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。" description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。"
@@ -17,6 +17,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1" serde_json = "1"
byteorder = "1" byteorder = "1"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
cached = "1" parking_lot = "0.12"
tracing = "0.1" tracing = "0.1"
tracing-subscriber = "0.3" fastrand = "2"
+39 -42
View File
@@ -29,9 +29,9 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::dash_line::线; use crate::structure::dash_line::线;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::{, }; use crate::types::{, };
use crate::{error, warn};
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tracing::{error, warn};
/// 笔 — 从分型生成笔的算法集合(静态方法命名空间) /// 笔 — 从分型生成笔的算法集合(静态方法命名空间)
pub struct ; pub struct ;
@@ -54,8 +54,7 @@ impl 笔 {
if let (Some(), Some()) = (&, &) { if let (Some(), Some()) = (&, &) {
let = 1 let = 1
+ (.K线.read().unwrap(). - .K线.read().unwrap().) + (.K线.read(). - .K线.read().).unsigned_abs() as usize;
.unsigned_abs() as usize;
if >= . as usize { if >= . as usize {
return . as usize; return . as usize;
} }
@@ -76,8 +75,8 @@ impl 笔 {
&& let (Some(_k), Some(_k)) = (&, &) && let (Some(_k), Some(_k)) = (&, &)
{ {
let = 1 let = 1
+ (_k.K线.read().unwrap(). - _k.K线.read().unwrap().) + (_k.K线.read(). - _k.K线.read().).unsigned_abs()
.unsigned_abs() as usize; as usize;
// 向上笔 // 向上笔
if .().() if .().()
&& _k..get() < .() && _k..get() < .()
@@ -219,7 +218,7 @@ impl 笔 {
/// 判断笔的相对关系是否合理 /// 判断笔的相对关系是否合理
pub fn _相对关系(: &线, : &) -> bool { pub fn _相对关系(: &线, : &) -> bool {
let = &.; let = &.;
let = ..read().unwrap(); let = ..read();
let = if . { let = if . {
let _rc = Arc::clone(&.); let _rc = Arc::clone(&.);
@@ -261,8 +260,8 @@ impl 笔 {
...get(), ...get(),
); );
if .K线包含整笔 { if .K线包含整笔 {
let = ..K线.read().unwrap(); let = ..K线.read();
let = ..K线.read().unwrap(); let = ..K线.read();
if crate::types::::(., ., ., .) if crate::types::::(., ., ., .)
.() .()
{ {
@@ -280,10 +279,7 @@ impl 笔 {
/// 以文会友 — 根据起点分型找笔 /// 以文会友 — 根据起点分型找笔
pub fn (: &[Arc<线>], : &Arc<>) -> Option<Arc<线>> { pub fn (: &[Arc<线>], : &Arc<>) -> Option<Arc<线>> {
.iter().find(|b| Arc::ptr_eq(&b., )).cloned()
.iter()
.find(|b| Arc::as_ptr(&b.) == Arc::as_ptr())
.cloned()
} }
/// 以武会友 — 根据终点分型找笔 /// 以武会友 — 根据终点分型找笔
@@ -291,7 +287,7 @@ impl 笔 {
.iter() .iter()
.rev() .rev()
.find(|b| Arc::as_ptr(&*b..read().unwrap()) == Arc::as_ptr()) .find(|b| Arc::ptr_eq(&*b..read(), ))
.cloned() .cloned()
} }
@@ -305,8 +301,7 @@ impl 笔 {
for b in .iter().rev() { for b in .iter().rev() {
// Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号 // Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号
if b....load(Ordering::Relaxed) - <= K..load(Ordering::Relaxed) if b....load(Ordering::Relaxed) - <= K..load(Ordering::Relaxed)
&& K..load(Ordering::Relaxed) && K..load(Ordering::Relaxed) <= b..read()...load(Ordering::Relaxed)
<= b..read().unwrap()...load(Ordering::Relaxed)
&& b... == K. && b... == K.
&& b... == K. && b... == K.
{ {
@@ -323,7 +318,7 @@ impl 笔 {
let = .pop(); let = .pop();
if let (Some(), Some()) = (.pop(), ) { if let (Some(), Some()) = (.pop(), ) {
assert!( assert!(
Arc::as_ptr(&..read().unwrap()) == Arc::as_ptr(&), Arc::ptr_eq(&..read(), &),
"最后一笔终点错误{}", "最后一笔终点错误{}",
); );
@@ -388,7 +383,7 @@ impl 笔 {
// Python line 2343-2348: 笔弱化模式 // Python line 2343-2348: 笔弱化模式
if . && !.is_empty() { if . && !.is_empty() {
let = .last().unwrap(); let = .last().unwrap();
let K数 = ..read().unwrap()...load(Ordering::Relaxed) let K数 = ..read()...load(Ordering::Relaxed)
- ....load(Ordering::Relaxed) - ....load(Ordering::Relaxed)
+ 1; + 1;
if K数 == 3 { if K数 == 3 {
@@ -434,7 +429,7 @@ impl 笔 {
// Python line 2359-2367: 文官调整 // Python line 2359-2367: 文官调整
if let Some(ref _k) = if let Some(ref _k) =
&& Arc::as_ptr(_k) != Arc::as_ptr(&.) && !Arc::ptr_eq(_k, &.)
&& let Some() = && let Some() =
::K序列中获取分型(K序列, _k) ::K序列中获取分型(K序列, _k)
{ {
@@ -476,7 +471,7 @@ impl 笔 {
if Self::_相对关系(&, ) if Self::_相对关系(&, )
&& let Some(ref _k) = && let Some(ref _k) =
&& Arc::as_ptr(_k) == Arc::as_ptr(&.) && Arc::ptr_eq(_k, &.)
{ {
// 直接添加(对照 Python _添加新笔:直接 append // 直接添加(对照 Python _添加新笔:直接 append
Self::_添加新笔(, , , , line!()); Self::_添加新笔(, , , , line!());
@@ -490,7 +485,7 @@ impl 笔 {
_ => Self::_次高(&, .), _ => Self::_次高(&, .),
}; };
if let Some(ref _k) = if let Some(ref _k) =
&& Arc::as_ptr(_k) == Arc::as_ptr(&.) && Arc::ptr_eq(_k, &.)
&& Self::_相对关系(&, ) && Self::_相对关系(&, )
{ {
Self::_添加新笔(, , , , line!()); Self::_添加新笔(, , , , line!());
@@ -557,13 +552,12 @@ impl 笔 {
if !.is_empty() if !.is_empty()
&& Arc::as_ptr(.last().unwrap()) && Arc::as_ptr(.last().unwrap())
== Arc::as_ptr(&_rc) == Arc::as_ptr(&_rc)
&& let Some(_idx) = K序列 && let Some(_idx) =
.iter() K序列.iter().position(|k| Arc::ptr_eq(k, _k))
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(_k))
{ {
for ck in &K序列[_idx..] { for ck in &K序列[_idx..] {
if (*ck..read().unwrap() == Some(::) if (*ck..read() == Some(::)
|| *ck..read().unwrap() == Some(::)) || *ck..read() == Some(::))
&& let Some() = && let Some() =
::K序列中获取分型(K序列, ck) ::K序列中获取分型(K序列, ck)
{ {
@@ -577,6 +571,15 @@ impl 笔 {
+ 1, + 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); .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); ..store(false, Ordering::Relaxed);
} }
if matches!( if matches!(..read().(), :: | ::) {
..read().unwrap().(),
:: | ::
) {
error!("_添加新笔[{}] 出现无效分型 {}", , ); error!("_添加新笔[{}] 出现无效分型 {}", , );
} }
} }
@@ -676,7 +675,7 @@ impl 笔 {
Self::_实际低点(&, .), Self::_实际低点(&, .),
) )
&& Arc::ptr_eq(&.., &) && Arc::ptr_eq(&.., &)
&& Arc::ptr_eq(&..read().unwrap()., &) && Arc::ptr_eq(&..read()., &)
{ {
return true; return true;
} }
@@ -686,7 +685,7 @@ impl 笔 {
Self::_实际高点(&, .), Self::_实际高点(&, .),
) )
&& Arc::ptr_eq(&.., &) && Arc::ptr_eq(&.., &)
&& Arc::ptr_eq(&..read().unwrap()., &) && Arc::ptr_eq(&..read()., &)
{ {
return true; return true;
} }
@@ -696,9 +695,9 @@ impl 笔 {
/// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合 /// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合
pub fn (: &线, : &) -> Vec<线> { pub fn (: &线, : &) -> Vec<线> {
let mut = Vec::new();
let = Arc::clone(&.);
let = .K序列(&.K线序列); let = .K序列(&.K线序列);
let mut = Vec::with_capacity(.len() / 2);
let = Arc::clone(&.);
if .len() < 5 { if .len() < 5 {
return ; return ;
@@ -707,10 +706,8 @@ impl 笔 {
for i in 3...len() - 1 { for i in 3...len() - 1 {
let k = &[i]; let k = &[i];
let = let = *k..read() == Some(::) && .() == ::;
*k..read().unwrap() == Some(::) && .() == ::; let = *k..read() == Some(::) && .() == ::;
let =
*k..read().unwrap() == Some(::) && .() == ::;
if || { if || {
let = Arc::clone(&[i - 1]); let = Arc::clone(&[i - 1]);
let = Arc::clone(k); let = Arc::clone(k);
@@ -737,12 +734,12 @@ impl 笔 {
for in & { for in & {
let k线范围 = K线::rc( let k线范围 = K线::rc(
&.K线序列, &.K线序列,
&...K线.read().unwrap().clone(), &...K线.read().clone(),
&..read().unwrap()..K线.read().unwrap().clone(), &..read()..K线.read().clone(),
); );
let = 线::K线序列MACD趋向背驰(&k线范围, .()); let = 线::K线序列MACD趋向背驰(&k线范围, .());
if .iter().all(|&x| x) { if .iter().all(|&x| x) {
.push(Arc::clone(&..read().unwrap().)); .push(Arc::clone(&..read().));
} }
} }
+15 -19
View File
@@ -39,13 +39,13 @@ impl 背驰分析 {
) -> bool { ) -> bool {
let MACD = Self::_获取MACD面积( let MACD = Self::_获取MACD面积(
K线序列, K线序列,
&...K线.read().unwrap(), &...K线.read(),
&..read().unwrap()..K线.read().unwrap(), &..read()..K线.read(),
); );
let MACD = Self::_获取MACD面积( let MACD = Self::_获取MACD面积(
K线序列, K线序列,
&...K线.read().unwrap(), &...K线.read(),
&..read().unwrap()..K线.read().unwrap(), &..read()..K线.read(),
); );
// 计算面积(绝对值求和) // 计算面积(绝对值求和)
@@ -69,18 +69,18 @@ impl 背驰分析 {
/// 斜率背驰 — 价格斜率背驰 /// 斜率背驰 — 价格斜率背驰
pub fn (: &线, : &线) -> bool { pub fn (: &线, : &线) -> bool {
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
if dx == 0.0 { if dx == 0.0 {
return false; return false;
} }
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = dy / dx; let = dy / dx;
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
if dx == 0.0 { if dx == 0.0 {
return false; return false;
} }
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = dy / dx; let = dy / dx;
if .() == :: { if .() == :: {
@@ -92,12 +92,12 @@ impl 背驰分析 {
/// 测度背驰 — 价格时间测度背驰 /// 测度背驰 — 价格时间测度背驰
pub fn (: &线, : &线) -> bool { pub fn (: &线, : &线) -> bool {
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = (dx * dx + dy * dy).sqrt(); let = (dx * dx + dy * dy).sqrt();
let dx = (..read().unwrap().() - ..()) as f64; let dx = (..read().() - ..()) as f64;
let dy = ..read().unwrap(). - ..; let dy = ..read(). - ..;
let = (dx * dx + dy * dy).sqrt(); let = (dx * dx + dy * dy).sqrt();
if .() == :: { if .() == :: {
@@ -187,12 +187,8 @@ impl 背驰分析 {
// ---- 内部辅助 ---- // ---- 内部辅助 ----
fn _获取MACD面积(K线序列: &[Arc<K线>], : &Arc<K线>, : &Arc<K线>) -> MACD面积 { fn _获取MACD面积(K线序列: &[Arc<K线>], : &Arc<K线>, : &Arc<K线>) -> MACD面积 {
let _idx = K线序列 let _idx = K线序列.iter().position(|k| Arc::ptr_eq(k, ));
.iter() let _idx = K线序列.iter().position(|k| Arc::ptr_eq(k, ));
.position(|k| Arc::as_ptr(k) == Arc::as_ptr());
let _idx = K线序列
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr());
let mut = 0.0f64; let mut = 0.0f64;
let mut = 0.0f64; let mut = 0.0f64;
@@ -200,7 +196,7 @@ impl 背驰分析 {
if let (Some(), Some()) = (_idx, _idx) { if let (Some(), Some()) = (_idx, _idx) {
let (, ) = if <= { (, ) } else { (, ) }; let (, ) = if <= { (, ) } else { (, ) };
for k in &K线序列[..=] { for k in &K线序列[..=] {
if let Some(macd) = k..read().unwrap().macd() { if let Some(macd) = k..read().macd() {
let hist = macd.MACD柱; let hist = macd.MACD柱;
if hist >= 0.0 { if hist >= 0.0 {
+= hist; += hist;
+134 -169
View File
@@ -25,8 +25,9 @@
use crate::structure::dash_line::线; use crate::structure::dash_line::线;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::; use crate::types::;
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 中枢 — 三段虚线重叠区间构成的价格中枢 /// 中枢 — 三段虚线重叠区间构成的价格中枢
/// ///
@@ -59,11 +60,11 @@ impl Clone for 中枢 {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
线: RwLock::new(self.线.read().unwrap().clone()), 线: RwLock::new(self.线.read().clone()),
_第三买卖线: RwLock::new(self._第三买卖线.read().unwrap().clone()), _第三买卖线: RwLock::new(self._第三买卖线.read().clone()),
} }
} }
} }
@@ -83,9 +84,9 @@ impl 中枢 {
/// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线 /// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线
pub fn _添加虚线(&self, 线: Arc<线>) { pub fn _添加虚线(&self, 线: Arc<线>) {
self..write().unwrap().push(线); self..write().push(线);
*self._第三买卖线.write().unwrap() = None; *self._第三买卖线.write() = None;
*self.线.write().unwrap() = None; *self.线.write() = None;
} }
/// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号" /// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号"
@@ -94,24 +95,25 @@ impl 中枢 {
"{}:{}:{}:{}", "{}:{}:{}:{}",
self.().., self.()..,
self.().., self.()..,
self..read().unwrap(), self..read(),
self..load(Ordering::Relaxed) self..load(Ordering::Relaxed)
) )
} }
/// 返回基础序列的最后一根虚线(当前离开段) /// 返回基础序列的最后一根虚线(当前离开段)
pub fn (&self) -> Arc<线> { 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) -> { pub fn (&self) -> {
self..read().unwrap()[0].().() self..read()[0].().()
} }
/// 中枢上沿 = min(前三段的高) /// 中枢上沿 = min(前三段的高)
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self..read().unwrap()[..3] self..read()[..3]
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -120,7 +122,7 @@ impl 中枢 {
/// 中枢下沿 = max(前三段的低) /// 中枢下沿 = max(前三段的低)
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self..read().unwrap()[..3] self..read()[..3]
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -131,7 +133,6 @@ impl 中枢 {
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self. self.
.read() .read()
.unwrap()
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -142,7 +143,6 @@ impl 中枢 {
pub fn (&self) -> f64 { pub fn (&self) -> f64 {
self. self.
.read() .read()
.unwrap()
.iter() .iter()
.map(|x| x.()) .map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal)) .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -151,46 +151,47 @@ impl 中枢 {
/// 返回基础序列第一段的起点分型 /// 返回基础序列第一段的起点分型
pub fn (&self) -> Arc<> { pub fn (&self) -> Arc<> {
Arc::clone(&self..read().unwrap()[0].) Arc::clone(&self..read()[0].)
} }
/// 返回基础序列最后一段的终点分型 /// 返回基础序列最后一段的终点分型
pub fn (&self) -> Arc<> { pub fn (&self) -> Arc<> {
Arc::clone( let guard = self..read();
&*self..read().unwrap()[self..read().unwrap().len() - 1] Arc::clone(&*guard[guard.len() - 1]..read())
.
.read()
.unwrap(),
)
} }
/// 设置第三类买卖点对应的虚线 /// 设置第三类买卖点对应的虚线
pub fn 线(&self, 线: Option<Arc<线>>) { pub fn 线(&self, 线: Option<Arc<线>>) {
*self.线.write().unwrap() = 线; *self.线.write() = 线;
} }
/// 获取序列 — 基础序列 + 第三买卖线(若有) /// 获取序列 — 基础序列 + 第三买卖线(若有)
pub fn (&self) -> Vec<Arc<线>> { pub fn (&self) -> Vec<Arc<线>> {
let mut : Vec<Arc<线>> = self..read().unwrap().clone(); let mut : Vec<Arc<线>> = self..read().clone();
if let Some(ref ) = *self.线.read().unwrap() { if let Some(ref ) = *self.线.read() {
.push(Arc::clone()); .push(Arc::clone());
} }
} }
/// 获取基础序列最后一个元素
pub fn (&self) -> Option<Arc<线>> {
self..read().last().cloned()
}
/// 返回序列化数据文本,用于调试和存储 /// 返回序列化数据文本,用于调试和存储
pub fn (&self) -> String { pub fn (&self) -> String {
let 线_str = match &*self.线.read().unwrap() { let 线_str = match &*self.线.read() {
Some(x) => format!("{}", x), Some(x) => format!("{}", x),
None => "None".to_string(), None => "None".to_string(),
}; };
let _第三买卖线_str = match &*self._第三买卖线.read().unwrap() { let _第三买卖线_str = match &*self._第三买卖线.read() {
Some(x) => format!("{}", x), Some(x) => format!("{}", x),
None => "None".to_string(), None => "None".to_string(),
}; };
format!( format!(
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}", "{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}",
self..read().unwrap(), self..read(),
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
self.().(), self.().(),
@@ -204,67 +205,63 @@ impl 中枢 {
/// 校验中枢合法性 /// 校验中枢合法性
pub fn _校验合法性(&self, : &[Arc<线>]) -> bool { pub fn _校验合法性(&self, : &[Arc<线>]) -> bool {
let mut = self..read().unwrap().clone(); let guard = self..read();
let mut = guard.clone();
let mut : Vec<Arc<线>> = Vec::new(); let mut : Vec<Arc<线>> = Vec::new();
for in self..read().unwrap().iter() { let = [0]..load(Ordering::Relaxed);
if !.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr()) { for in guard.iter() {
let idx = (..load(Ordering::Relaxed) - ) as usize;
if idx >= .len() || !Arc::ptr_eq(&[idx], ) {
.push(Arc::clone()); .push(Arc::clone());
} }
} }
if !.is_empty() { if !.is_empty() {
let = &[0]; let = &[0];
if let Some(pos) = self // Python: 序号 = 线段._索引(self.基础序列, 无效)
. let pos = crate::algorithm::segment::线::_索引(&guard, );
.read() = guard[..pos].to_vec();
.unwrap()
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
{
= self..read().unwrap()[..pos].to_vec();
}
} }
drop(guard);
if .len() < 3 { if .len() < 3 {
self.线(None); self.线(None);
*self._第三买卖线.write().unwrap() = None; *self._第三买卖线.write() = None;
return false; return false;
} }
*self..write().unwrap() = ; *self..write() = ;
let = self.(); let = self.();
let = self.(); let = self.();
= Vec::new(); = Vec::new();
for in self..read().unwrap().iter() { for in self..read().iter() {
if crate::types::::(, , .(), .()).() if crate::types::::(, , .(), .()).()
{ {
break; break;
} }
.push(Arc::clone()); .push(Arc::clone());
} }
*self..write().unwrap() = ; *self..write() = ;
if self..read().unwrap().len() < 3 { let = {
return false; let guard = self..read();
} if guard.len() < 3 {
for i in 1..self..read().unwrap().len() {
let = &self..read().unwrap()[i - 1];
let = &self..read().unwrap()[i];
if !.() {
return false; return false;
} }
} for i in 1..guard.len() {
if !guard[i - 1].(&guard[i]) {
if !crate::types::::( return false;
self..read().unwrap()[0].(), }
self..read().unwrap()[0].(), }
self..read().unwrap()[2].(), crate::types::::(
self..read().unwrap()[2].(), guard[0].(),
) guard[0].(),
.() guard[2].(),
{ guard[2].(),
)
.()
};
if ! {
let = self.(); let = self.();
let = self.(); let = self.();
if > { if > {
@@ -272,10 +269,12 @@ impl 中枢 {
} }
} }
let 线_opt = self.线.read().unwrap().clone(); let 线_opt = self.线.read().clone();
if let Some(ref 线) = 线_opt { if let Some(ref 线) = 线_opt {
if .iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(线)) { let = [0]..load(Ordering::Relaxed);
if !self..read().unwrap().last().unwrap().(线) { let idx = (线..load(Ordering::Relaxed) - ) as usize;
if idx < .len() && Arc::ptr_eq(&[idx], 线) {
if !self..read().last().unwrap().(线) {
self.线(None); self.线(None);
} else if !crate::types::::( } else if !crate::types::::(
self.(), self.(),
@@ -298,16 +297,16 @@ impl 中枢 {
/// 完整性 — 详见教你炒股票43:有关背驰的补习课 /// 完整性 — 详见教你炒股票43:有关背驰的补习课
/// 不完整时下一个中枢大概率会与当前中枢发生扩展 /// 不完整时下一个中枢大概率会与当前中枢发生扩展
pub fn (&self, : &str) -> bool { pub fn (&self, : &str) -> bool {
if *self..read().unwrap()[0]..read().unwrap() == "" { if *self..read()[0]..read() == "" {
return self.线.read().unwrap().is_some(); return self.线.read().is_some();
} }
let _ref = self..read().unwrap(); let _ref = self..read();
let = _ref.last().unwrap(); let = _ref.last().unwrap();
let _vec = if == "" { let _vec = if == "" {
._中枢序列.read().unwrap() ._中枢序列.read()
} else { } else {
._中枢序列.read().unwrap() ._中枢序列.read()
}; };
for in _vec.iter() { for in _vec.iter() {
if crate::types::::( if crate::types::::(
@@ -330,24 +329,19 @@ impl 中枢 {
: &mut Vec<Arc<>>, : &mut Vec<Arc<>>,
: &crate::config::, : &crate::config::,
) { ) {
if self..read().unwrap().len() >= 9 { if self..read().len() >= 9 {
let mut 线: Vec<Arc<线>> = Vec::new(); let mut 线: Vec<Arc<线>> = Vec::new();
let _ref = self..read().unwrap(); let _ref = self..read();
crate::algorithm::segment::线::(&_ref, &mut 线, ); crate::algorithm::segment::线::(&_ref, &mut 线, );
::( let = format!("{}_扩展中枢_", self..read());
&线, ::(&线, , false, &, 0);
,
false,
&format!("{}_扩展中枢_", self..read().unwrap()),
0,
);
} }
} }
/// 当前状态 — 详见教你炒股票49:利润率最大的操作模式 /// 当前状态 — 详见教你炒股票49:利润率最大的操作模式
/// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下 /// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下
pub fn (&self) -> &str { pub fn (&self) -> &str {
let _ref = self..read().unwrap(); let _ref = self..read();
let = Arc::clone(_ref.last().unwrap()); let = Arc::clone(_ref.last().unwrap());
let = ._武(); let = ._武();
let = crate::types::::( let = crate::types::::(
@@ -390,7 +384,7 @@ impl 中枢 {
assert!(Self::(&, &, &), "中枢.创建 基础检查失败"); assert!(Self::(&, &, &), "中枢.创建 基础检查失败");
Self::new( Self::new(
0, 0,
format!("{}中枢<{}>", , ..read().unwrap()), format!("{}中枢<{}>", , ..read()),
, ,
vec![, , ], vec![, , ],
) )
@@ -422,18 +416,8 @@ impl 中枢 {
. .
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed); .store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
let _last_序号 = let _last_序号 = .().unwrap()..load(Ordering::Relaxed);
.() let new_last_序号 = .().unwrap()..load(Ordering::Relaxed);
.last()
.unwrap()
.
.load(Ordering::Relaxed);
let new_last_序号 =
.()
.last()
.unwrap()
.
.load(Ordering::Relaxed);
if _last_序号 > new_last_序号 { if _last_序号 > new_last_序号 {
panic!( panic!(
"向中枢序列尾部添加 序号错误 前last={} > new_last={}", "向中枢序列尾部添加 序号错误 前last={} > new_last={}",
@@ -478,11 +462,8 @@ impl 中枢 {
let = &线[i + 1]; let = &线[i + 1];
if Self::(, , ) { if Self::(, , ) {
// Python: 序号 = 虚线序列.index(左) // Python: 序号 = 线段._索引(虚线序列, 左)
let = 线 let : usize = crate::algorithm::segment::线::_索引(线, );
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
.expect("中枢.分析: 左元素不在虚线序列中");
if && (..load(Ordering::Relaxed) == 0 || == 0) { if && (..load(Ordering::Relaxed) == 0 || == 0) {
continue; continue;
} }
@@ -525,22 +506,16 @@ impl 中枢 {
return; return;
} }
// 找到当前中枢最后一个元素在虚线序列中的位置 // Python: 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
let = { let = {
let cur = &[_idx]; let cur = &[_idx];
let = &cur..read().unwrap()[cur..read().unwrap().len() - 1]; let guard = cur..read();
match 线 crate::algorithm::segment::线::_索引(线, &guard[guard.len() - 1]) + 1
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
{
Some(idx) => idx + 1,
None => return,
}
}; };
let mut = [_idx].(); let mut = [_idx].();
let mut = [_idx].(); let mut = [_idx].();
let mut : Vec<Arc<线>> = Vec::new(); let mut = Vec::with_capacity(3);
for 线_ref in &线[..] { for 线_ref in &线[..] {
let 线 = Arc::clone(线_ref); let 线 = Arc::clone(线_ref);
@@ -553,12 +528,7 @@ impl 中枢 {
// Python: if 当前中枢.基础序列[-1].之后是(当前虚线): // Python: if 当前中枢.基础序列[-1].之后是(当前虚线):
let needs_三买 = { let needs_三买 = {
let cur = &[_idx]; let cur = &[_idx];
cur. cur..read().last().unwrap().(&线)
.read()
.unwrap()
.last()
.unwrap()
.(&线)
}; };
if needs_三买 { if needs_三买 {
[_idx].线(Some(线.clone())); [_idx].线(Some(线.clone()));
@@ -570,17 +540,11 @@ impl 中枢 {
[_idx] [_idx]
. .
.read() .read()
.unwrap()
.last() .last()
.unwrap() .unwrap()
.(&线), .(&线),
"中枢延伸: 不连续 {}, {}", "中枢延伸: 不连续 {}, {}",
[_idx] [_idx]..read().last().unwrap(),
.
.read()
.unwrap()
.last()
.unwrap(),
线 线
); );
[_idx]._添加虚线(线); [_idx]._添加虚线(线);
@@ -594,7 +558,6 @@ impl 中枢 {
let = [_idx] let = [_idx]
. .
.read() .read()
.unwrap()
.last() .last()
.unwrap() .unwrap()
.() .()
@@ -628,13 +591,13 @@ impl 中枢 {
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"中枢: [标识] 不等 A={},B={}", "中枢: [标识] 不等 A={},B={}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
@@ -649,8 +612,8 @@ impl 中枢 {
); );
} }
// 基础序列 // 基础序列
let a_seq = self..read().unwrap(); let a_seq = self..read();
let b_seq = other..read().unwrap(); let b_seq = other..read();
if a_seq.len() != b_seq.len() { if a_seq.len() != b_seq.len() {
return ( return (
false, false,
@@ -692,16 +655,16 @@ impl 中枢 {
}; };
( (
"第三买卖线", "第三买卖线",
&self.线.read().unwrap(), &self.线.read(),
&other.线.read().unwrap(), &other.线.read(),
, ,
) )
.map_err(|e| (false, e)) .map_err(|e| (false, e))
.ok(); .ok();
( (
"本级_第三买卖线", "本级_第三买卖线",
&self._第三买卖线.read().unwrap(), &self._第三买卖线.read(),
&other._第三买卖线.read().unwrap(), &other._第三买卖线.read(),
, ,
) )
.map_err(|e| (false, e)) .map_err(|e| (false, e))
@@ -712,21 +675,26 @@ impl 中枢 {
impl std::fmt::Display for { impl std::fmt::Display for {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let _str = self let guard = self..read();
. let len = guard.len();
.read() let _str = if let Some((first, rest)) = guard.split_first() {
.unwrap() let mut s = format!("{}", first);
.iter() for d in rest {
.map(|d| format!("{}", d)) use std::fmt::Write;
.collect::<Vec<_>>() write!(&mut s, ", {}", d).unwrap();
.join(", "); }
s
} else {
String::new()
};
drop(guard);
write!( write!(
f, f,
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})", "{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
self..read().unwrap(), self..read(),
crate::utils::format_f64_g(self.()), crate::utils::format_f64_g(self.()),
crate::utils::format_f64_g(self.()), crate::utils::format_f64_g(self.()),
self..read().unwrap().len(), len,
_str, _str,
self.(), self.(),
self.(), self.(),
@@ -851,11 +819,11 @@ mod tests {
); );
assert_eq!(..load(Ordering::Relaxed), 1); assert_eq!(..load(Ordering::Relaxed), 1);
assert_eq!(*..read().unwrap(), "测试中枢"); assert_eq!(*..read(), "测试中枢");
assert_eq!(..load(Ordering::Relaxed), 1); assert_eq!(..load(Ordering::Relaxed), 1);
assert_eq!(..read().unwrap().len(), 3); assert_eq!(..read().len(), 3);
assert!(.线.read().unwrap().is_none()); assert!(.线.read().is_none());
assert!(._第三买卖线.read().unwrap().is_none()); assert!(._第三买卖线.read().is_none());
} }
#[test] #[test]
@@ -877,16 +845,16 @@ mod tests {
// RefCell 第三买卖线读写 // RefCell 第三买卖线读写
.线(Some(Arc::clone(&1))); .线(Some(Arc::clone(&1)));
assert!(.线.read().unwrap().is_some()); assert!(.线.read().is_some());
assert_eq!( assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()), Arc::as_ptr(.线.read().as_ref().unwrap()),
Arc::as_ptr(&1) Arc::as_ptr(&1)
); );
// 本级_第三买卖线 // 本级_第三买卖线
assert!(._第三买卖线.read().unwrap().is_none()); assert!(._第三买卖线.read().is_none());
*._第三买卖线.write().unwrap() = Some(Arc::clone(&3)); *._第三买卖线.write() = Some(Arc::clone(&3));
assert!(._第三买卖线.read().unwrap().is_some()); assert!(._第三买卖线.read().is_some());
} }
// ============================================================ // ============================================================
@@ -906,14 +874,11 @@ mod tests {
1, 1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)], vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
); );
assert_eq!(..read().unwrap().len(), 3); assert_eq!(..read().len(), 3);
._添加虚线(Arc::clone(&4)); ._添加虚线(Arc::clone(&4));
assert_eq!(..read().unwrap().len(), 4); assert_eq!(..read().len(), 4);
assert_eq!( assert_eq!(Arc::as_ptr(&..read()[3]), Arc::as_ptr(&4));
Arc::as_ptr(&..read().unwrap()[3]),
Arc::as_ptr(&4)
);
} }
#[test] #[test]
@@ -930,14 +895,14 @@ mod tests {
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)], vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
); );
.线(Some(Arc::clone(&1))); .线(Some(Arc::clone(&1)));
*._第三买卖线.write().unwrap() = Some(Arc::clone(&2)); *._第三买卖线.write() = Some(Arc::clone(&2));
assert!(.线.read().unwrap().is_some()); assert!(.线.read().is_some());
assert!(._第三买卖线.read().unwrap().is_some()); assert!(._第三买卖线.read().is_some());
._添加虚线(Arc::clone(&4)); ._添加虚线(Arc::clone(&4));
// 添加虚线后第三买卖线被清除 // 添加虚线后第三买卖线被清除
assert!(.线.read().unwrap().is_none()); assert!(.线.read().is_none());
assert!(._第三买卖线.read().unwrap().is_none()); assert!(._第三买卖线.read().is_none());
} }
// ============================================================ // ============================================================
@@ -963,15 +928,15 @@ mod tests {
// 基础序列中的 Rc 指针应一致 // 基础序列中的 Rc 指针应一致
for i in 0..3 { for i in 0..3 {
assert_eq!( assert_eq!(
Arc::as_ptr(&..read().unwrap()[i]), Arc::as_ptr(&..read()[i]),
Arc::as_ptr(&..read().unwrap()[i]) Arc::as_ptr(&..read()[i])
); );
} }
// 第三买卖线 Rc 指针应一致 // 第三买卖线 Rc 指针应一致
assert_eq!( assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()), Arc::as_ptr(.线.read().as_ref().unwrap()),
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()) Arc::as_ptr(.线.read().as_ref().unwrap())
); );
} }
@@ -1030,12 +995,12 @@ mod tests {
// 通过 rc1 添加虚线 // 通过 rc1 添加虚线
1._线(Arc::clone(&4)); 1._线(Arc::clone(&4));
assert_eq!(2..read().unwrap().len(), 4); assert_eq!(2..read().len(), 4);
// 验证共享的 Arc<虚线> 指针一致 // 验证共享的 Arc<虚线> 指针一致
assert_eq!( assert_eq!(
Arc::as_ptr(&1..read().unwrap()[3]), Arc::as_ptr(&1..read()[3]),
Arc::as_ptr(&2..read().unwrap()[3]) Arc::as_ptr(&2..read()[3])
); );
} }
} }
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -347,7 +347,7 @@ impl 买卖点 {
let = .(); let = .();
// 当前K线 — 从缠K获取其标的K线 // 当前K线 — 从缠K获取其标的K线
let K线 = Arc::clone(&*K.K线.read().unwrap()); let K线 = Arc::clone(&*K.K线.read());
// 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算 // 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算
let K序号 = K..load(Ordering::Relaxed); let K序号 = K..load(Ordering::Relaxed);
+14 -15
View File
@@ -26,10 +26,10 @@ use crate::business::observer::观察者;
use crate::business::synthesizer::K线合成器; use crate::business::synthesizer::K线合成器;
use crate::config::; use crate::config::;
use crate::kline::bar::K线; use crate::kline::bar::K线;
use crate::{error, warn};
use parking_lot::RwLock;
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use std::sync::RwLock;
use tracing::{error, info};
/// 立体分析器 — 多周期协调器 /// 立体分析器 — 多周期协调器
pub struct { pub struct {
@@ -72,7 +72,7 @@ impl 立体分析器 {
// 显示周期特殊配置 // 显示周期特殊配置
{ {
let = .get(&).expect("显示周期观察者不存在"); let = .get(&).expect("显示周期观察者不存在");
let mut guard = .write().unwrap(); let mut guard = .write();
guard..K线 = true; guard..K线 = true;
guard.. = true; guard.. = true;
guard..线 = true; guard..线 = true;
@@ -84,14 +84,14 @@ impl 立体分析器 {
{ {
let K序列 = let K序列 =
.get(&) .get(&)
.map(|o| o.read().unwrap().K线序列.clone()) .map(|o| o.read().K线序列.clone())
.unwrap_or_default(); .unwrap_or_default();
for & in & { for & in & {
if != if !=
&& let Some() = .get(&) && let Some() = .get(&)
{ {
.write().unwrap().K序列 = K序列.clone(); .write().K序列 = K序列.clone();
} }
} }
} }
@@ -119,7 +119,7 @@ impl 立体分析器 {
/// __K线回调 — 对应 Python 立体分析器.__K线回调 /// __K线回调 — 对应 Python 立体分析器.__K线回调
fn __K线回调(&self, _信号类型: String, _标识: String, : i64, K线: K线) { fn __K线回调(&self, _信号类型: String, _标识: String, : i64, K线: K线) {
if let Some() = self..get(&) { if let Some() = self..get(&) {
let mut obs = .write().unwrap(); let mut obs = .write();
obs.K线(K线); obs.K线(K线);
// 对应 Python: if 当前K线 := self._K线合成器.获取当前K线(周期) // 对应 Python: if 当前K线 := self._K线合成器.获取当前K线(周期)
// _完成K线刚清空当前K线,获取当前K线返回 None,所以这里不添加 // _完成K线刚清空当前K线,获取当前K线返回 None,所以这里不添加
@@ -133,7 +133,7 @@ impl 立体分析器 {
K线: K线, K线: K线,
) { ) {
if let Some() = .get(&) { if let Some() = .get(&) {
.write().unwrap().K线(K线); .write().K线(K线);
} }
} }
@@ -165,22 +165,22 @@ impl 立体分析器 {
let = self let = self
. .
.get(&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); .unwrap_or(0);
let = self let = self
. .
.get(&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); .unwrap_or(0);
let = self let = self
. .
.get(&self.) .get(&self.)
.map(|o| o.read().unwrap()..clone()) .map(|o| o.read()..clone())
.unwrap_or_default(); .unwrap_or_default();
let = self let = self
. .
.get(&self.) .get(&self.)
.map(|o| o.read().unwrap().) .map(|o| o.read().)
.unwrap_or_default(); .unwrap_or_default();
let = format!("RustM_{}:{}_{}_{}", , , , ); let = format!("RustM_{}:{}_{}_{}", , , , );
@@ -195,12 +195,11 @@ impl 立体分析器 {
if let Some() = self..get() { if let Some() = self..get() {
.read() .read()
.unwrap()
._保存数据(Some(&.to_string_lossy())); ._保存数据(Some(&.to_string_lossy()));
} }
} }
info!("多级别数据拆分保存完成,目录:{}", .display()); warn!("多级别数据拆分保存完成,目录:{}", .display());
} }
/// 相等 — 各周期观察者全量比对,对应 Python `立体分析器相等` /// 相等 — 各周期观察者全量比对,对应 Python `立体分析器相等`
@@ -213,11 +212,11 @@ impl 立体分析器 {
for in &self. { for in &self. {
let a_obs = match self..get() { let a_obs = match self..get() {
Some(o) => o.read().unwrap(), Some(o) => o.read(),
None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (A)")), None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (A)")),
}; };
let b_obs = match other..get() { let b_obs = match other..get() {
Some(o) => o.read().unwrap(), Some(o) => o.read(),
None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (B)")), None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (B)")),
}; };
let (eq, msg) = a_obs.(&b_obs, ); let (eq, msg) = a_obs.(&b_obs, );
+196 -109
View File
@@ -32,9 +32,10 @@ use crate::structure::dash_line::虚线;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::; use crate::types::;
use crate::utils::datetime; use crate::utils::datetime;
use crate::{error, warn};
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
use tracing::{error, info};
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算 /// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
pub struct { pub struct {
@@ -298,17 +299,15 @@ impl 观察者 {
&[::, ::], &[::, ::],
); );
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(
线::( &left[i - 1],
&, &mut right[0],
&mut self.线[i], &self.,
&self., 0,
0, &[::, ::],
&[::, ::], );
);
}
} }
if self..线 { if self..线 {
::(&self.线[i], &mut self.[i], true, "", 0); ::(&self.线[i], &mut self.[i], true, "", 0);
@@ -323,11 +322,9 @@ impl 观察者 {
if self..线 { if self..线 {
线::(&self., &mut self.线[i], &self.); 线::(&self., &mut self.线[i], &self.);
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(&left[i - 1], &mut right[0], &self.);
线::(&, &mut self.线[i], &self.);
}
} }
if self..线 { if self..线 {
::( ::(
@@ -345,8 +342,11 @@ impl 观察者 {
if self..线 || self..线 { if self..线 || self..线 {
for i in 0..self.线.min(self.线.len()) { for i in 0..self.线.min(self.线.len()) {
if self..线 { if self..线 {
let = self.线[i].clone(); 线::(
线::(&, &mut self.线[i], &self.); &self.线[i],
&mut self.线[i],
&self.,
);
} }
if self..线 { if self..线 {
::( ::(
@@ -430,17 +430,15 @@ impl 观察者 {
&[::, ::], &[::, ::],
); );
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(
线::( &left[i - 1],
&, &mut right[0],
&mut self.线[i], &self.,
&self., 0,
0, &[::, ::],
&[::, ::], );
);
}
} }
if self..线 { if self..线 {
::(&self.线[i], &mut self.[i], true, "", 0); ::(&self.线[i], &mut self.[i], true, "", 0);
@@ -454,11 +452,9 @@ impl 观察者 {
if self..线 { if self..线 {
线::(&self., &mut self.线[i], &self.); 线::(&self., &mut self.线[i], &self.);
} }
} else { } else if self..线 {
if self..线 { let (left, right) = self.线.split_at_mut(i);
let = self.线[i - 1].clone(); 线::(&left[i - 1], &mut right[0], &self.);
线::(&, &mut self.线[i], &self.);
}
} }
if self..线 { if self..线 {
::( ::(
@@ -475,8 +471,11 @@ impl 观察者 {
if self..线 || self..线 { if self..线 || self..线 {
for i in 0..self.线.min(self.线.len()) { for i in 0..self.线.min(self.线.len()) {
if self..线 { if self..线 {
let = self.线[i].clone(); 线::(
线::(&, &mut self.线[i], &self.); &self.线[i],
&mut self.线[i],
&self.,
);
} }
if self..线 { if self..线 {
::( ::(
@@ -517,7 +516,7 @@ impl 观察者 {
return Ok(()); return Ok(());
} }
let : Vec<String> = .iter().map(|d| d.()).collect(); let : Vec<String> = .iter().map(|d| d.()).collect();
let = format!("{}.txt", [0]..read().unwrap()); let = format!("{}.txt", [0]..read());
std::fs::write(.join(&), .join("\n") + "\n")?; std::fs::write(.join(&), .join("\n") + "\n")?;
Ok(()) Ok(())
}; };
@@ -528,7 +527,7 @@ impl 观察者 {
return Ok(()); return Ok(());
} }
let : Vec<String> = .iter().map(|h| h.()).collect(); let : Vec<String> = .iter().map(|h| h.()).collect();
let = format!("{}.txt", [0]..read().unwrap()); let = format!("{}.txt", [0]..read());
std::fs::write(.join(&), .join("\n") + "\n")?; std::fs::write(.join(&), .join("\n") + "\n")?;
Ok(()) Ok(())
}; };
@@ -563,7 +562,7 @@ impl 观察者 {
ck..load(Ordering::Relaxed), ck..load(Ordering::Relaxed),
ck..load(Ordering::Relaxed), ck..load(Ordering::Relaxed),
ck., ck.,
*ck..read().unwrap(), *ck..read(),
ck..get(), ck..get(),
ck..get(), ck..get(),
ck., ck.,
@@ -597,7 +596,7 @@ impl 观察者 {
_数据文本.join("\n") + "\n", _数据文本.join("\n") + "\n",
); );
info!("全部数据拆分保存完成,目录:{}", .display()); warn!("全部数据拆分保存完成,目录:{}", .display());
.display().to_string() .display().to_string()
} }
@@ -734,6 +733,7 @@ impl 观察者 {
mod tests { mod tests {
use super::*; use super::*;
use crate::config::; use crate::config::;
use crate::{error, info};
fn test_data_path() -> String { fn test_data_path() -> String {
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR")); let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
@@ -749,10 +749,9 @@ mod tests {
fn test_普k序列指针一致性() { fn test_普k序列指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
for (i, bi) in obs_ref..iter().enumerate() { for (i, bi) in obs_ref..iter().enumerate() {
let pu_seq = bi.K序列(&obs_ref.K线序列); let pu_seq = bi.K序列(&obs_ref.K线序列);
@@ -761,12 +760,7 @@ mod tests {
info!(" 文.中.标的K线 原始起始序号: {}", bi...); info!(" 文.中.标的K线 原始起始序号: {}", bi...);
info!( info!(
" 武.中.标的K线 原始结束序号: {}", " 武.中.标的K线 原始结束序号: {}",
bi. bi..read()...load(Ordering::Relaxed)
.read()
.unwrap()
.
.
.load(Ordering::Relaxed)
); );
info!(" 普通K线序列.len: {}", obs_ref.K线序列.len()); info!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
} else { } else {
@@ -777,7 +771,7 @@ mod tests {
.any(|k| Arc::as_ptr(k) == first_ptr); .any(|k| Arc::as_ptr(k) == first_ptr);
if !found { if !found {
info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i); 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 let wen_found = obs_ref
.K线序列 .K线序列
.iter() .iter()
@@ -802,11 +796,11 @@ mod tests {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
let _k线_py_inner = Arc::new(k线.clone()); 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!("普通K线序列.len: {}", obs.K线序列.len());
info!("笔序列.len: {}", obs..len()); info!("笔序列.len: {}", obs..len());
@@ -835,10 +829,9 @@ mod tests {
fn test_分型到笔的文武Rc指针一致性() { fn test_分型到笔的文武Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 每个笔的文/武 分型 Rc 指针必须在 分型序列 中 // 每个笔的文/武 分型 Rc 指针必须在 分型序列 中
for (i, bi) in obs_ref..iter().enumerate() { for (i, bi) in obs_ref..iter().enumerate() {
@@ -848,13 +841,13 @@ mod tests {
info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi..()); 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); let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr);
if !_found { if !_found {
info!( info!(
"笔 {}: 武(时间戳={}) 不在分型序列中!", "笔 {}: 武(时间戳={}) 不在分型序列中!",
i, i,
bi..read().unwrap().() bi..read().()
); );
} }
} }
@@ -868,14 +861,13 @@ mod tests {
fn test_笔到线段的基础序列Rc指针一致性() { fn test_笔到线段的基础序列Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中 // 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中
for (i, seg) in obs_ref.线().iter().enumerate() { for (i, seg) in obs_ref.线().iter().enumerate() {
for (j, bi_in_seg) in seg..read().unwrap().iter().enumerate() { for (j, bi_in_seg) in seg..read().iter().enumerate() {
let bi_ptr = Arc::as_ptr(bi_in_seg); let bi_ptr = Arc::as_ptr(bi_in_seg);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr); let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found { if !found {
@@ -893,13 +885,12 @@ mod tests {
fn test_中枢基础序列与笔序列Rc指针一致() { fn test_中枢基础序列与笔序列Rc指针一致() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
for (i, hub) in obs_ref._中枢序列.iter().enumerate() { 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 bi_ptr = Arc::as_ptr(bi_in_hub);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr); let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found { if !found {
@@ -909,7 +900,7 @@ mod tests {
} }
for (i, hub) in obs_ref.().iter().enumerate() { for (i, hub) in obs_ref.().iter().enumerate() {
for (j, seg_in_hub) in hub..read().unwrap().iter().enumerate() { for (j, seg_in_hub) in hub..read().iter().enumerate() {
let seg_ptr = Arc::as_ptr(seg_in_hub); let seg_ptr = Arc::as_ptr(seg_in_hub);
let found = obs_ref.线().iter().any(|s| Arc::as_ptr(s) == seg_ptr); let found = obs_ref.线().iter().any(|s| Arc::as_ptr(s) == seg_ptr);
if !found { if !found {
@@ -934,10 +925,10 @@ mod tests {
for i in 0..data.len() / size { for i in 0..data.len() / size {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { 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(),
obs.线().len(), obs.线().len(),
@@ -975,28 +966,28 @@ mod tests {
for i in 0..data.len() / size { for i in 0..data.len() / size {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { 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()..len();
let = obs_ref.read().unwrap().线().len(); let = obs_ref.read().线().len();
// 重置 // 重置
obs_ref.write().unwrap().(); obs_ref.write().();
assert_eq!(obs_ref.read().unwrap()..len(), 0); assert_eq!(obs_ref.read()..len(), 0);
assert_eq!(obs_ref.read().unwrap().线().len(), 0); assert_eq!(obs_ref.read().线().len(), 0);
// 重新投喂 // 重新投喂
for i in 0..data.len() / size { for i in 0..data.len() / size {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { 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()..len();
let = obs_ref.read().unwrap().线().len(); let = obs_ref.read().线().len();
assert_eq!(, , "重置后重新投喂笔数不一致"); assert_eq!(, , "重置后重新投喂笔数不一致");
assert_eq!(, , "重置后重新投喂线段数不一致"); assert_eq!(, , "重置后重新投喂线段数不一致");
@@ -1011,31 +1002,30 @@ mod tests {
fn test_RefCell借用安全性_连续读取不panic() { fn test_RefCell借用安全性_连续读取不panic() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 连续大量读取所有 RefCell 字段,不应 panic // 连续大量读取所有 RefCell 字段,不应 panic
for _ in 0..100 { for _ in 0..100 {
for bi in &obs_ref. { for bi in &obs_ref. {
let _标识 = bi..read().unwrap().clone(); let _标识 = bi..read().clone();
let _wu = bi..read().unwrap().clone(); let _wu = bi..read().clone();
let _基础序列 = bi..read().unwrap().len(); let _基础序列 = bi..read().len();
let _特征序列 = bi..read().unwrap().len(); let _特征序列 = bi..read().len();
let _模式 = bi..read().unwrap().clone(); let _模式 = bi..read().clone();
let _实中枢 = bi._中枢序列.read().unwrap().len(); let _实中枢 = bi._中枢序列.read().len();
let _虚中枢 = bi._中枢序列.read().unwrap().len(); let _虚中枢 = bi._中枢序列.read().len();
let _合中枢 = bi._中枢序列.read().unwrap().len(); let _合中枢 = bi._中枢序列.read().len();
let _确认K = bi.K线.read().unwrap().is_some(); let _确认K = bi.K线.read().is_some();
let _序号 = bi..load(Ordering::Relaxed); let _序号 = bi..load(Ordering::Relaxed);
let _有效性 = bi..load(Ordering::Relaxed); 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.线() { for seg in obs_ref.线() {
let _ = seg..read().unwrap().clone(); let _ = seg..read().clone();
let _ = seg..read().unwrap().len(); let _ = seg..read().len();
} }
} }
// 到达这里 = 无 panic // 到达这里 = 无 panic
@@ -1045,27 +1035,26 @@ mod tests {
fn test_RefCell借用安全性_交替读写不panic() { fn test_RefCell借用安全性_交替读写不panic() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域 // 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域
if !obs_ref..is_empty() { if !obs_ref..is_empty() {
let bi = &obs_ref.[0]; let bi = &obs_ref.[0];
// 读 // 读
let old_mode = bi..read().unwrap().clone(); let old_mode = bi..read().clone();
// Ref 已释放,可以写 // Ref 已释放,可以写
*bi..write().unwrap() = "测试模式".into(); *bi..write() = "测试模式".into();
let new_mode = bi..read().unwrap().clone(); let new_mode = bi..read().clone();
assert_eq!(new_mode, "测试模式"); 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 已释放,可以检查 // 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指针一致性() { fn test_缠K到分型的Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default()); let obs = ::new("btcusd".into(), 300, Default::default());
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
let obs_ref = obs.read().unwrap(); let obs_ref = obs.read();
// 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中 // 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中
for (i, f) in obs_ref..iter().enumerate() { for (i, f) in obs_ref..iter().enumerate() {
@@ -1196,7 +1184,7 @@ mod tests {
99 99
}); });
assert_eq!(handle.join().unwrap(), 99); assert_eq!(handle.join().unwrap(), 99);
assert_eq!(dash..read().unwrap().as_str(), ""); assert_eq!(dash..read().as_str(), "");
} }
/// 测试:Arc<中枢> 可跨线程传递 /// 测试:Arc<中枢> 可跨线程传递
@@ -1222,11 +1210,11 @@ mod tests {
let obs3 = Arc::clone(&obs); let obs3 = Arc::clone(&obs);
let h1 = std::thread::spawn(move || { let h1 = std::thread::spawn(move || {
let guard = obs2.read().unwrap(); let guard = obs2.read();
guard..clone() guard..clone()
}); });
let h2 = std::thread::spawn(move || { let h2 = std::thread::spawn(move || {
let guard = obs3.read().unwrap(); let guard = obs3.read();
guard. guard.
}); });
@@ -1274,7 +1262,7 @@ mod tests {
let obs = ::new("ethusd".into(), 7200, Default::default()); let obs = ::new("ethusd".into(), 7200, Default::default());
let handle = std::thread::spawn(move || { let handle = std::thread::spawn(move || {
let guard = obs.read().unwrap(); let guard = obs.read();
(guard..clone(), guard.) (guard..clone(), guard.)
}); });
@@ -1288,7 +1276,7 @@ mod tests {
let mut config = ::default(); let mut config = ::default();
config. = test_data_path(); config. = test_data_path();
let obs = ::new("btcusd".into(), 300, config); 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.线 = 0;
obs_w.(); obs_w.();
drop(obs_w); drop(obs_w);
@@ -1299,11 +1287,11 @@ mod tests {
for i in 0..(data.len() / size).min(500) { for i in 0..(data.len() / size).min(500) {
let offset = i * size; let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") { 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.K线序列.len() > 0, "缠K序列应有数据");
assert!(obs_r..len() > 0, "分型序列应有数据"); assert!(obs_r..len() > 0, "分型序列应有数据");
assert!(obs_r.线.is_empty(), "线段序列组应为空"); assert!(obs_r.线.is_empty(), "线段序列组应为空");
@@ -1329,17 +1317,16 @@ mod tests {
// 先正常投喂数据 // 先正常投喂数据
obs.write() obs.write()
.unwrap()
.(&test_data_path(), Default::default()) .(&test_data_path(), Default::default())
.unwrap(); .unwrap();
// 设为0后执行静态重新分析,不应 panic // 设为0后执行静态重新分析,不应 panic
let mut obs_w = obs.write().unwrap(); let mut obs_w = obs.write();
obs_w.线 = 0; obs_w.线 = 0;
obs_w.(); obs_w.();
drop(obs_w); drop(obs_w);
let obs_r = obs.read().unwrap(); let obs_r = obs.read();
assert!(obs_r..len() > 0, "静态重新分析后分型序列应有数据"); assert!(obs_r..len() > 0, "静态重新分析后分型序列应有数据");
assert!(obs_r.线.is_empty(), "线段序列组应为空"); assert!(obs_r.线.is_empty(), "线段序列组应为空");
assert!( assert!(
@@ -1352,4 +1339,104 @@ mod tests {
obs_r..len() 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()
);
}
} }
+2 -2
View File
@@ -23,8 +23,8 @@
*/ */
use crate::kline::bar::K线; use crate::kline::bar::K线;
use crate::warn;
use std::collections::HashMap; use std::collections::HashMap;
use tracing;
/// 事件回调类型 — fn(信号类型, 标识, 周期, 完成K线) /// 事件回调类型 — fn(信号类型, 标识, 周期, 完成K线)
type = Box<dyn Fn(String, String, i64, K线) + Send + Sync>; type = Box<dyn Fn(String, String, i64, K线) + Send + Sync>;
@@ -178,7 +178,7 @@ impl K线合成器 {
.map(|s| s.to_string()) .map(|s| s.to_string())
.or_else(|| e.downcast_ref::<String>().cloned()) .or_else(|| e.downcast_ref::<String>().cloned())
.unwrap_or_else(|| "未知错误".into()); .unwrap_or_else(|| "未知错误".into());
tracing::error!("K线合成器 事件回调 异常: {}", msg); warn!("K线合成器 事件回调 异常: {}", msg);
} }
} }
} }
+3 -3
View File
@@ -22,9 +22,9 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::warn;
use serde::{Deserialize, Deserializer, Serialize}; use serde::{Deserialize, Deserializer, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use tracing::warn;
/// 缠论配置 —— 控制所有分析阶段的行为 /// 缠论配置 —— 控制所有分析阶段的行为
/// ///
@@ -795,7 +795,7 @@ mod tests {
#[test] #[test]
fn test_对比_有差异() { fn test_对比_有差异() {
let mut a = ::default(); let a = ::default();
let mut b = ::default(); let mut b = ::default();
b. = "changed".into(); b. = "changed".into();
b. = 99; b. = 99;
@@ -854,7 +854,7 @@ mod tests {
#[test] #[test]
fn test_对比_boolean_difference() { fn test_对比_boolean_difference() {
let mut a = ::default(); let a = ::default();
let mut b = ::default(); let mut b = ::default();
b.K线 = false; b.K线 = false;
b. = false; b. = false;
+6 -5
View File
@@ -24,6 +24,7 @@
use crate::kline::bar::K线; use crate::kline::bar::K线;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道 /// 布林带(BOLL)— 基于移动平均和标准差的波动率通道
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize)]
@@ -43,7 +44,7 @@ pub struct 布林带 {
pub : f64, pub : f64,
/// 内部历史队列(不序列化) /// 内部历史队列(不序列化)
#[serde(skip)] #[serde(skip)]
_历史队列: Vec<f64>, _历史队列: VecDeque<f64>,
/// 内部均值缓存(不序列化) /// 内部均值缓存(不序列化)
#[serde(skip)] #[serde(skip)]
_均值: f64, _均值: f64,
@@ -61,7 +62,7 @@ impl Default for 布林带 {
: 0.0, : 0.0,
: 0.0, : 0.0,
: 0.0, : 0.0,
_历史队列: Vec::new(), _历史队列: VecDeque::new(),
_均值: 0.0, _均值: 0.0,
_方差和: 0.0, _方差和: 0.0,
} }
@@ -98,7 +99,7 @@ impl 布林带 {
: , : ,
: , : ,
: , : ,
_历史队列: vec![], _历史队列: VecDeque::from([]),
_均值: , _均值: ,
_方差和: 0.0, _方差和: 0.0,
} }
@@ -110,9 +111,9 @@ impl 布林带 {
let = prev.; let = prev.;
let mut q = prev._历史队列.clone(); let mut q = prev._历史队列.clone();
q.push(); q.push_back();
if q.len() > { if q.len() > {
q.remove(0); q.pop_front();
} }
let (_均值, _方差和) = if q.len() < { let (_均值, _方差和) = if q.len() < {
+19 -19
View File
@@ -45,7 +45,7 @@ impl 指标计算器 {
let has_prev; let has_prev;
{ {
let prev_guard = if n > 1 { let prev_guard = if n > 1 {
Some([n - 2]..read().unwrap()) Some([n - 2]..read())
} else { } else {
None None
}; };
@@ -100,9 +100,9 @@ impl 指标计算器 {
, ,
)) ))
}; };
K线..write().unwrap().(&key, val.clone()); K线..write().(&key, val.clone());
if i == 0 { if i == 0 {
K线..write().unwrap().("macd", val); K线..write().("macd", val);
} }
} }
} }
@@ -142,9 +142,9 @@ impl 指标计算器 {
Some(._移动平均线周期), Some(._移动平均线周期),
)) ))
}; };
K线..write().unwrap().(&key, val.clone()); K线..write().(&key, val.clone());
if i == 0 { 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 { 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 { if i == 0 {
K线..write().unwrap().("boll", val); K线..write().("boll", val);
} }
} }
} }
@@ -245,7 +245,7 @@ impl 指标计算器 {
"EMA" => Self::_增量EMA(, , *period, , &key), "EMA" => Self::_增量EMA(, , *period, , &key),
_ => continue, _ => continue,
}; };
if let Some(线_map) = K线..write().unwrap().线_mut() { if let Some(线_map) = K线..write().线_mut() {
线_map.insert(key, ); 线_map.insert(key, );
} }
} }
@@ -274,7 +274,7 @@ impl 指标计算器 {
} }
// 尝试从前一根K线获取缓存的SMA // 尝试从前一根K线获取缓存的SMA
if let Some(prev) = .last().and_then(|k| { 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() guard.线().and_then(|m| m.get(prev_key)).copied()
}) { }) {
let oldest = super::K线取值( let oldest = super::K线取值(
@@ -304,7 +304,7 @@ impl 指标计算器 {
prev_key: &str, prev_key: &str,
) -> f64 { ) -> f64 {
let = .last().and_then(|k| { let = .last().and_then(|k| {
let guard = k..read().unwrap(); let guard = k..read();
guard.线().and_then(|m| m.get(prev_key)).copied() guard.线().and_then(|m| m.get(prev_key)).copied()
}); });
match { match {
@@ -320,8 +320,8 @@ impl 指标计算器 {
fn _回填新指标(: &[Arc<K线>], : &) { fn _回填新指标(: &[Arc<K线>], : &) {
// 作用域化首尾读锁:在回填写循环之前释放,避免读锁与写锁冲突 // 作用域化首尾读锁:在回填写循环之前释放,避免读锁与写锁冲突
let (MACD, RSI, KDJ, BOLL) = { let (MACD, RSI, KDJ, BOLL) = {
let K_guard = [0]..read().unwrap(); let K_guard = [0]..read();
let K_guard = [.len() - 1]..read().unwrap(); let K_guard = [.len() - 1]..read();
let MACD: Vec<_> = let MACD: Vec<_> =
._解析MACD参数列表() ._解析MACD参数列表()
@@ -357,7 +357,7 @@ impl 指标计算器 {
for i in 0...len() { for i in 0...len() {
let k线 = &[i]; let k线 = &[i];
let prev_guard = if i > 0 { let prev_guard = if i > 0 {
Some([i - 1]..read().unwrap()) Some([i - 1]..read())
} else { } else {
None None
}; };
@@ -388,7 +388,7 @@ impl 指标计算器 {
*, *,
)) ))
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
for (key, ) in &RSI { for (key, ) in &RSI {
@@ -419,7 +419,7 @@ impl 指标计算器 {
Some(._移动平均线周期), Some(._移动平均线周期),
)) ))
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
for (key, rsv, k平滑, d平滑) in &KDJ { 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 { for (key, , ) in &BOLL {
@@ -469,7 +469,7 @@ impl 指标计算器 {
*, *,
)) ))
}; };
k线..write().unwrap().(key, val); k线..write().(key, val);
} }
} }
} }
+1 -3
View File
@@ -70,9 +70,7 @@ impl 指标容器 {
/// 预注册指标(不覆盖已有值) /// 预注册指标(不覆盖已有值)
pub fn (&mut self, : &str, : Option<>) { pub fn (&mut self, : &str, : Option<>) {
if !self._数据.contains_key() { self._数据.entry(.to_string()).or_insert();
self._数据.insert(.to_string(), );
}
} }
/// 按名称获取指标值 /// 按名称获取指标值
+13 -12
View File
@@ -24,6 +24,7 @@
use crate::kline::bar::K线; use crate::kline::bar::K线;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// 随机指标 (KDJ) /// 随机指标 (KDJ)
/// ///
@@ -58,9 +59,9 @@ pub struct 随机指标 {
/// J 值 (3K - 2D) /// J 值 (3K - 2D)
pub J: Option<f64>, pub J: Option<f64>,
/// 历史最高价队列(滑动窗口) /// 历史最高价队列(滑动窗口)
pub : Vec<f64>, pub : VecDeque<f64>,
/// 历史最低价队列(滑动窗口) /// 历史最低价队列(滑动窗口)
pub : Vec<f64>, pub : VecDeque<f64>,
/// 前一个 RSV(用于平滑递推) /// 前一个 RSV(用于平滑递推)
pub RSV: Option<f64>, pub RSV: Option<f64>,
/// 前一个 K(用于平滑递推) /// 前一个 K(用于平滑递推)
@@ -85,8 +86,8 @@ impl Default for 随机指标 {
K: None, K: None,
D: None, D: None,
J: None, J: None,
: Vec::new(), : VecDeque::new(),
: Vec::new(), : VecDeque::new(),
RSV: None, RSV: None,
K: None, K: None,
D: None, D: None,
@@ -124,8 +125,8 @@ impl 随机指标 {
K: None, K: None,
D: None, D: None,
J: None, J: None,
: vec![], : VecDeque::from([]),
: vec![], : VecDeque::from([]),
RSV: None, RSV: None,
K: None, K: None,
D: None, D: None,
@@ -181,16 +182,16 @@ impl 随机指标 {
// 更新历史最高价队列 // 更新历史最高价队列
let mut = KDJ..clone(); let mut = KDJ..clone();
.push(); .push_back();
if .len() > N as usize { if .len() > N as usize {
.remove(0); .pop_front();
} }
// 更新历史最低价队列 // 更新历史最低价队列
let mut = KDJ..clone(); let mut = KDJ..clone();
.push(); .push_back();
if .len() > N as usize { if .len() > N as usize {
.remove(0); .pop_front();
} }
// RSV // RSV
@@ -260,8 +261,8 @@ mod tests {
#[test] #[test]
fn test_first_calc() { fn test_first_calc() {
let kdj = ::(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0); 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., VecDeque::from([110.0]));
assert_eq!(kdj., vec![90.0]); assert_eq!(kdj., VecDeque::from([90.0]));
assert_eq!(kdj.K, None); assert_eq!(kdj.K, None);
} }
+20 -10
View File
@@ -24,6 +24,7 @@
use crate::kline::bar::K线; use crate::kline::bar::K线;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
/// 相对强弱指数 (RSI) /// 相对强弱指数 (RSI)
/// ///
@@ -58,7 +59,9 @@ pub struct 相对强弱指数 {
/// RSI SMA 值 /// RSI SMA 值
pub RSI_SMA: Option<f64>, pub RSI_SMA: Option<f64>,
/// RSI 历史队列(用于滚动计算) /// RSI 历史队列(用于滚动计算)
pub RSI历史队列: Vec<f64>, pub RSI历史队列: VecDeque<f64>,
/// RSI 历史队列运行和(O(1) SMA
pub RSI和: f64,
} }
impl Default for { impl Default for {
@@ -77,7 +80,8 @@ impl Default for 相对强弱指数 {
: 0.0, : 0.0,
: 0.0, : 0.0,
RSI_SMA: None, RSI_SMA: None,
RSI历史队列: Vec::new(), RSI历史队列: VecDeque::new(),
RSI和: 0.0,
} }
} }
} }
@@ -106,7 +110,8 @@ impl 相对强弱指数 {
: 0.0, : 0.0,
: 1.0 / as f64, : 1.0 / as f64,
RSI_SMA: None, RSI_SMA: None,
RSI历史队列: Vec::new(), RSI历史队列: VecDeque::new(),
RSI和: 0.0,
} }
} }
@@ -167,21 +172,25 @@ impl 相对强弱指数 {
}; };
// RSI_SMA // RSI_SMA
let (RSI_SMA, RSI历史队列) = match RSI_SMA周期 { let (RSI_SMA, RSI历史队列, RSI和) = match RSI_SMA周期 {
Some(sma周期) if sma周期 > 0 => { Some(sma周期) if sma周期 > 0 => {
let mut = RSI.RSI历史队列.clone(); let mut = RSI.RSI历史队列.clone();
.push(RSI); let mut sum = RSI.RSI和;
if .len() > sma周期 as usize { .push_back(RSI);
.remove(0); sum += RSI;
if .len() > sma周期 as usize
&& let Some(old) = .pop_front()
{
sum -= old;
} }
let sma = if .is_empty() { let sma = if .is_empty() {
None None
} else { } else {
Some(.iter().sum::<f64>() / .len() as f64) Some(sum / .len() as f64)
}; };
(sma, ) (sma, , sum)
} }
_ => (None, Vec::new()), _ => (None, VecDeque::new(), 0.0),
}; };
Self { Self {
@@ -199,6 +208,7 @@ impl 相对强弱指数 {
, ,
RSI_SMA, RSI_SMA,
RSI历史队列, RSI历史队列,
RSI和,
} }
} }
} }
+190 -5
View File
@@ -23,16 +23,18 @@
*/ */
use crate::indicators::; use crate::indicators::;
use crate::info;
use crate::types::; use crate::types::;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::collections::HashMap; use std::collections::HashMap;
use std::io::Write; use std::io::Write;
use std::sync::{Arc, RwLock}; use std::sync::Arc;
mod rwlock_container_serde { mod rwlock_container_serde {
use parking_lot::RwLock;
use serde::{Deserialize, Deserializer, Serialize, Serializer}; use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::sync::RwLock;
/// Serde 序列化辅助(RwLock<指标容器> → 序列化器) /// Serde 序列化辅助(RwLock<指标容器> → 序列化器)
pub fn serialize<S>( pub fn serialize<S>(
@@ -42,7 +44,7 @@ mod rwlock_container_serde {
where where
S: Serializer, S: Serializer,
{ {
val.read().unwrap().serialize(ser) val.read().serialize(ser)
} }
/// Serde 反序列化辅助(反序列化器 → RwLock<指标容器> /// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>
@@ -115,7 +117,7 @@ impl Clone for K线 {
: self., : self.,
: self., : self.,
: self., : self.,
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
} }
} }
} }
@@ -223,6 +225,7 @@ impl K线 {
/// 保存K线序列到 DAT 文件 /// 保存K线序列到 DAT 文件
pub fn DAT文件(: &str, K线序列: &[&Self]) -> std::io::Result<()> { pub fn DAT文件(: &str, K线序列: &[&Self]) -> std::io::Result<()> {
info!("保存到DAT文件: {}", );
let mut f = std::fs::File::create()?; let mut f = std::fs::File::create()?;
for k in K线序列 { for k in K线序列 {
f.write_all(&k.to_bytes())?; f.write_all(&k.to_bytes())?;
@@ -245,7 +248,7 @@ impl K线 {
let mut = 0.0f64; let mut = 0.0f64;
let mut = 0.0f64; let mut = 0.0f64;
for k in { for k in {
if let Some(macd) = k..read().unwrap().macd() { if let Some(macd) = k..read().macd() {
let hist = macd.MACD柱; let hist = macd.MACD柱;
if hist >= 0.0 { if hist >= 0.0 {
+= hist; += hist;
@@ -314,6 +317,73 @@ impl K线 {
(true, "K线: 全部字段一致".into()) (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<K线>序列中从始到终的片段 /// 截取Arc<K线>序列中从始到终的片段
pub fn rc(: &[Arc<Self>], : &Arc<Self>, : &Arc<Self>) -> Vec<Arc<Self>> { pub fn rc(: &[Arc<Self>], : &Arc<Self>, : &Arc<Self>) -> Vec<Arc<Self>> {
let _ptr = Arc::as_ptr(); let _ptr = Arc::as_ptr();
@@ -349,6 +419,7 @@ impl std::fmt::Display for K线 {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::{error, info, warn};
#[test] #[test]
fn test_方向() { fn test_方向() {
@@ -385,4 +456,118 @@ mod tests {
assert_eq!(result.get(""), Some(&0.0)); assert_eq!(result.get(""), Some(&0.0));
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());
}
} }
+38 -42
View File
@@ -29,9 +29,10 @@ use crate::structure::fractal_obj::分型;
use crate::types::SyncF64; use crate::types::SyncF64;
use crate::types::; use crate::types::;
use crate::types::; use crate::types::;
use parking_lot::RwLock;
use std::collections::HashSet; use std::collections::HashSet;
use std::sync::Arc;
use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 缠论K线 — 经包含处理过后的K线 /// 缠论K线 — 经包含处理过后的K线
/// ///
@@ -74,15 +75,15 @@ impl Clone for 缠论K线 {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: self., : self.,
: self..clone(), : self..clone(),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: self., : self.,
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())), K线: RwLock::new(Arc::clone(&self.K线.read())),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
} }
} }
} }
@@ -97,10 +98,9 @@ impl std::fmt::Display for 缠论K线 {
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
self. self.
.read() .read()
.unwrap()
.map_or("None".to_string(), |fx| fx.to_string()), .map_or("None".to_string(), |fx| fx.to_string()),
self., self.,
*self..read().unwrap(), *self..read(),
self..load(Ordering::Relaxed), self..load(Ordering::Relaxed),
format_f64_g(self..get()), format_f64_g(self..get()),
format_f64_g(self..get()) format_f64_g(self..get())
@@ -116,23 +116,23 @@ impl 缠论K线 {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: RwLock::new(*self..read().unwrap()), : RwLock::new(*self..read()),
: self., : self.,
: self..clone(), : self..clone(),
: SyncF64::new(self..get()), : SyncF64::new(self..get()),
: self., : self.,
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())), K线: RwLock::new(Arc::clone(&self.K线.read())),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
} }
} }
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0 /// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
pub fn MACD柱子匹配(&self) -> bool { pub fn MACD柱子匹配(&self) -> bool {
let = self.K线.read().unwrap(); let = self.K线.read();
let = ..read().unwrap(); let = ..read();
match *self..read().unwrap() { match *self..read() {
Some(::) | Some(::) => { Some(::) | Some(::) => {
if let Some(macd) = .macd() { if let Some(macd) = .macd() {
macd.MACD柱 < 0.0 macd.MACD柱 < 0.0
@@ -153,9 +153,9 @@ impl 缠论K线 {
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA /// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
pub fn RSI匹配(&self) -> bool { pub fn RSI匹配(&self) -> bool {
let = self.K线.read().unwrap(); let = self.K线.read();
let = ..read().unwrap(); let = ..read();
match *self..read().unwrap() { match *self..read() {
Some(::) | Some(::) => { Some(::) | Some(::) => {
if let Some(rsi) = .rsi() { if let Some(rsi) = .rsi() {
match (rsi.RSI, rsi.RSI_SMA) { match (rsi.RSI, rsi.RSI_SMA) {
@@ -182,9 +182,9 @@ impl 缠论K线 {
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后) /// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
pub fn KDJ匹配(&self) -> bool { pub fn KDJ匹配(&self) -> bool {
let = self.K线.read().unwrap(); let = self.K线.read();
let = ..read().unwrap(); let = ..read();
match *self..read().unwrap() { match *self..read() {
Some(::) | Some(::) => { Some(::) | Some(::) => {
if let Some(kdj) = .kdj() { if let Some(kdj) = .kdj() {
match (kdj.K, kdj.D) { match (kdj.K, kdj.D) {
@@ -356,12 +356,12 @@ impl 缠论K线 {
// 逆序包含时更新时间和标的K线 // 逆序包含时更新时间和标的K线
if != :: { if != :: {
K..store(K., Ordering::Relaxed); 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..set((K..get(), K.)); K..set((K..get(), K.));
K..store(K., Ordering::Relaxed); K..store(K., Ordering::Relaxed);
*K..write().unwrap() = K.(); *K..write() = K.();
if let Some() = K { if let Some() = K {
K K
@@ -466,29 +466,29 @@ impl 缠论K线 {
let = ::(&*, &*, &*, false, false); let = ::(&*, &*, &*, false, false);
// 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型 // 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型
*K序列[idx - 2]..write().unwrap() = ; *K序列[idx - 2]..write() = ;
if let Some() = { if let Some() = {
match { match {
:: => { :: => {
K序列[idx - 2]..set(K序列[idx - 2]..get()); K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..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 - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..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 - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..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 - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..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 ( return (
false, false,
format!( format!(
"缠论K线: [方向] 不等 A={},B={}", "缠论K线: [方向] 不等 A={},B={}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"缠论K线: [分型] 不等 A={:?},B={:?}", "缠论K线: [分型] 不等 A={:?},B={:?}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
@@ -636,17 +636,13 @@ impl 缠论K线 {
); );
} }
// 标的K线 递归 // 标的K线 递归
let (eq, msg) = self let (eq, msg) = self.K线.read().(&other.K线.read(), );
.K线
.read()
.unwrap()
.(&other.K线.read().unwrap(), );
if !eq { if !eq {
return (false, format!("缠论K线: 标的K线子项异常 >> {msg}")); return (false, format!("缠论K线: 标的K线子项异常 >> {msg}"));
} }
// 买卖点信息 // 买卖点信息
let a_guard = self..read().unwrap(); let a_guard = self..read();
let b_guard = other..read().unwrap(); let b_guard = other..read();
let a_set: std::collections::HashSet<&String> = a_guard.iter().collect(); let a_set: std::collections::HashSet<&String> = a_guard.iter().collect();
let b_set: std::collections::HashSet<&String> = b_guard.iter().collect(); let b_set: std::collections::HashSet<&String> = b_guard.iter().collect();
if a_set != b_set { if a_set != b_set {
@@ -654,8 +650,8 @@ impl 缠论K线 {
false, false,
format!( format!(
"缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}", "缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
+1
View File
@@ -30,6 +30,7 @@ pub mod business;
pub mod config; pub mod config;
pub mod indicators; pub mod indicators;
pub mod kline; pub mod kline;
pub mod log;
pub mod structure; pub mod structure;
pub mod types; pub mod types;
pub mod utils; pub mod utils;
+68
View File
@@ -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)*),
}
}
};
}
+2 -3
View File
@@ -80,10 +80,9 @@ fn 测试_读取数据(文件路径: &str) {
let = ::new("".into(), 0, ::default()); let = ::new("".into(), 0, ::default());
.write() .write()
.unwrap()
.(, ) .(, )
.expect("读取数据文件失败"); .expect("读取数据文件失败");
let = .read().unwrap(); let = .read();
let = .elapsed(); let = .elapsed();
println!( println!(
"测试_读取数据 耗时 {:.2?} 普K数量 {}", "测试_读取数据 耗时 {:.2?} 普K数量 {}",
@@ -160,7 +159,7 @@ fn 测试_周期合成(文件路径: &str) {
// Display stats per period // Display stats per period
for &p in &[, * 5, * 5 * 6] { for &p in &[, * 5, * 5 * 6] {
if let Some() = .(p) { if let Some() = .(p) {
let = .read().unwrap(); let = .read();
println!( println!(
"周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}", "周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}",
p, p,
File diff suppressed because it is too large Load Diff
+21 -25
View File
@@ -25,10 +25,10 @@
use crate::kline::chan_kline::K线; use crate::kline::chan_kline::K线;
use crate::types::; use crate::types::;
use crate::types::; use crate::types::;
use crate::warn;
use std::sync::Arc; use std::sync::Arc;
use std::sync::atomic::AtomicBool; use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use tracing::warn;
/// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取 /// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取
pub static : AtomicBool = AtomicBool::new(true); pub static : AtomicBool = AtomicBool::new(true);
@@ -75,7 +75,7 @@ impl 分型 {
..load(Ordering::Relaxed), ..load(Ordering::Relaxed),
); );
} }
let = ..read().unwrap().unwrap_or(::); let = ..read().unwrap_or(::);
let = ..load(Ordering::Relaxed); let = ..load(Ordering::Relaxed);
let = ..get(); let = ..get();
Self { Self {
@@ -102,7 +102,7 @@ impl 分型 {
if .load(Ordering::Relaxed) { if .load(Ordering::Relaxed) {
self. self.
} else { } else {
self...read().unwrap().unwrap_or(::) // FIXME 错误 self...read().unwrap_or(::) // FIXME 错误
} }
} }
@@ -157,19 +157,17 @@ impl 分型 {
if let (Some(), Some()) = (&self., &self.) { if let (Some(), Some()) = (&self., &self.) {
if self.() == :: { if self.() == :: {
if .K线.read().unwrap(). > .K线.read().unwrap(). { if .K线.read(). > .K线.read(). {
return ""; return "";
} else if .K线.read().unwrap(). > self..K线.read().unwrap(). } else if .K线.read(). > self..K线.read(). {
{
return ""; return "";
} else { } else {
return ""; return "";
} }
} else if self.() == :: { } else if self.() == :: {
if .K线.read().unwrap(). < .K线.read().unwrap(). { if .K线.read(). < .K线.read(). {
return ""; return "";
} else if .K线.read().unwrap(). < self..K线.read().unwrap(). } else if .K线.read(). < self..K线.read(). {
{
return ""; return "";
} else { } else {
return ""; return "";
@@ -183,12 +181,12 @@ impl 分型 {
pub fn MACD柱子分型匹配(&self) -> bool { pub fn MACD柱子分型匹配(&self) -> bool {
if let (Some(), Some()) = (&self., &self.) { if let (Some(), Some()) = (&self., &self.) {
if self.() == :: { if self.() == :: {
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _k = self..K线.read().unwrap(); let _k = self..K线.read();
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
if let (Some(macd), Some(macd), Some(macd)) = if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd()) (_m.macd(), _m.macd(), _m.macd())
{ {
@@ -196,12 +194,12 @@ impl 分型 {
} }
} }
if self.() == :: { if self.() == :: {
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _k = self..K线.read().unwrap(); let _k = self..K线.read();
let _k = .K线.read().unwrap(); let _k = .K线.read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
let _m = _k..read().unwrap(); let _m = _k..read();
if let (Some(macd), Some(macd), Some(macd)) = if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd()) (_m.macd(), _m.macd(), _m.macd())
{ {
@@ -214,7 +212,7 @@ impl 分型 {
/// 判断两个分型是否匹配 /// 判断两个分型是否匹配
pub fn (: &Arc<>, : &Arc<>, _模式: &str) -> bool { pub fn (: &Arc<>, : &Arc<>, _模式: &str) -> bool {
Arc::as_ptr() == Arc::as_ptr() Arc::ptr_eq(, )
} }
/// 从缠K序列中获取以指定缠K为中元素的分型 /// 从缠K序列中获取以指定缠K为中元素的分型
@@ -222,9 +220,7 @@ impl 分型 {
K线序列: &[Arc<K线>], K线序列: &[Arc<K线>],
: &Arc<K线>, : &Arc<K线>,
) -> Option<Self> { ) -> Option<Self> {
let idx = K线序列 let idx = K线序列.iter().position(|k| Arc::ptr_eq(k, ))?;
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr())?;
let = if idx > 0 { let = if idx > 0 {
Some(Arc::clone(&K线序列[idx - 1])) Some(Arc::clone(&K线序列[idx - 1]))
} else { } else {
+35 -50
View File
@@ -26,9 +26,10 @@ use crate::structure::dash_line::虚线;
use crate::structure::feat_fractal::; use crate::structure::feat_fractal::;
use crate::structure::fractal_obj::; use crate::structure::fractal_obj::;
use crate::types::{, }; use crate::types::{, };
use parking_lot::RwLock;
use std::sync::Arc;
use std::sync::atomic::AtomicI64; use std::sync::atomic::AtomicI64;
use std::sync::atomic::Ordering; use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
/// 线段特征 — 特征序列元素,内部是虚线的集合。 /// 线段特征 — 特征序列元素,内部是虚线的集合。
/// ///
@@ -62,7 +63,7 @@ impl Clone for 线段特征 {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
: AtomicI64::new(self..load(Ordering::Relaxed)), : AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()), : RwLock::new(self..read().clone()),
线: self.线, 线: self.线,
: self..clone(), : self..clone(),
} }
@@ -82,7 +83,7 @@ impl 线段特征 {
/// 图表标题 — 返回标识字符串 /// 图表标题 — 返回标识字符串
pub fn (&self) -> String { pub fn (&self) -> String {
self..read().unwrap().clone() self..read().clone()
} }
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型 /// 文 — 取特征序列元素中分型特征值最大/最小的文分型
@@ -118,47 +119,31 @@ impl 线段特征 {
/// 武 — 取特征序列元素中分型特征值最大/最小的武分型 /// 武 — 取特征序列元素中分型特征值最大/最小的武分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python) /// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Arc<> { pub fn (&self) -> Arc<> {
if self.线.() { let best = if self.线.() {
self. self..iter().max_by(|a, b| {
.iter() let a_武 = a..read();
.max_by(|a, b| { let b_武 = b..read();
a. a_武
.read() .
.unwrap() .partial_cmp(&b_武.)
. .unwrap_or(std::cmp::Ordering::Equal)
.partial_cmp(&b..read().unwrap().) .then_with(|| a_武.().cmp(&b_武.()))
.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())
} else { } else {
self. self..iter().max_by(|a, b| {
.iter() let a_武 = a..read();
.max_by(|a, b| { let b_武 = b..read();
b. b_武
.read() .
.unwrap() .partial_cmp(&a_武.)
. .unwrap_or(std::cmp::Ordering::Equal)
.partial_cmp(&a..read().unwrap().) .then_with(|| a_武.().cmp(&b_武.()))
.unwrap_or(std::cmp::Ordering::Equal) })
.then_with(|| { };
a. best.map_or_else(
.read() || self.[0]..read().clone(),
.unwrap() |x| x..read().clone(),
.() )
.cmp(&b..read().unwrap().())
})
})
.map(|x| x..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
}
} }
/// 高 — 文和武中分型特征值的较大者 /// 高 — 文和武中分型特征值的较大者
@@ -197,7 +182,7 @@ impl 线段特征 {
if let Some(pos) = self if let Some(pos) = self
. .
.iter() .iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(线)) .position(|x| Arc::ptr_eq(x, 线))
{ {
self..remove(pos); self..remove(pos);
Ok(()) Ok(())
@@ -256,7 +241,7 @@ impl 线段特征 {
.unwrap(); .unwrap();
let fake = 线::( let fake = 线::(
Arc::clone(&线.), Arc::clone(&线.),
线..read().unwrap().clone(), 线..read().clone(),
false, false,
); );
.pop(); .pop();
@@ -330,13 +315,13 @@ impl 线段特征 {
), ),
); );
} }
if *self..read().unwrap() != *other..read().unwrap() { if *self..read() != *other..read() {
return ( return (
false, false,
format!( format!(
"线段特征: [标识] 不等 A={},B={}", "线段特征: [标识] 不等 A={},B={}",
self..read().unwrap(), self..read(),
other..read().unwrap() other..read()
), ),
); );
} }
@@ -381,12 +366,12 @@ impl crate::types::fractal::有高低 for 线段特征 {
impl std::fmt::Display for 线 { impl std::fmt::Display for 线 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self..is_empty() { if self..is_empty() {
write!(f, "{}<{}, 空>", self..read().unwrap(), self.线) write!(f, "{}<{}, 空>", self..read(), self.线)
} else { } else {
write!( write!(
f, f,
"{}<{}, {}, {}, {}>", "{}<{}, {}, {}, {}>",
self..read().unwrap(), self..read(),
self.线, self.线,
self.(), self.(),
self.(), self.(),
+28
View File
@@ -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<usize> = (0...len()).collect();
fastrand::shuffle(&mut indices);
for &idx in indices.iter().take() {
result.push([idx]);
}
}
result
}
} }
impl std::fmt::Display for { impl std::fmt::Display for {
+1 -1
View File
@@ -22,8 +22,8 @@
* SOFTWARE. * SOFTWARE.
*/ */
use crate::warn;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::warn;
/// 分型结构 —— 三根K线构成的结构形态 /// 分型结构 —— 三根K线构成的结构形态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]