优化代码逻辑
This commit is contained in:
@@ -31,12 +31,14 @@ from __future__ import annotations
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from collections import deque
|
||||
import random
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import datetime as datetime_module
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
@@ -52,6 +54,7 @@ from typing import (
|
||||
Sequence,
|
||||
Callable,
|
||||
Set,
|
||||
Generator,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
@@ -1425,6 +1428,23 @@ class 相对方向(Enum):
|
||||
return 相对方向.逆
|
||||
raise RuntimeError("无法识别的方向")
|
||||
|
||||
@classmethod
|
||||
def 从序列中机选(
|
||||
cls,
|
||||
数量: int,
|
||||
可选方向: List["相对方向"],
|
||||
可重复: bool = True, # 是否允许重复选择
|
||||
) -> Generator["相对方向", None, None]:
|
||||
if not 可重复 and 数量 > len(可选方向):
|
||||
raise ValueError("数量超过可选方向数")
|
||||
|
||||
if 可重复:
|
||||
while 数量 > 0:
|
||||
yield random.choice(可选方向)
|
||||
数量 -= 1
|
||||
else:
|
||||
yield from random.sample(可选方向, 数量)
|
||||
|
||||
|
||||
class 分型结构(Enum):
|
||||
"""描述三根K线构成的顶底分型形态。
|
||||
@@ -1797,10 +1817,11 @@ class 相对强弱指数:
|
||||
下跌幅度: float = 0.0,
|
||||
平滑系数: float = 0.0,
|
||||
RSI_SMA: Optional[float] = None,
|
||||
RSI历史队列: List[float] = None,
|
||||
RSI历史队列: Optional[deque[float]] = None,
|
||||
RSI和: float = 0.0,
|
||||
):
|
||||
if RSI历史队列 is None:
|
||||
RSI历史队列 = []
|
||||
RSI历史队列 = deque()
|
||||
|
||||
# 原始数据
|
||||
self.时间戳 = 时间戳
|
||||
@@ -1829,6 +1850,7 @@ class 相对强弱指数:
|
||||
# RSI的SMA(信号线)相关字段
|
||||
self.RSI_SMA = RSI_SMA
|
||||
self.RSI历史队列 = RSI历史队列
|
||||
self.RSI和 = RSI和
|
||||
|
||||
@classmethod
|
||||
def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数:
|
||||
@@ -1916,19 +1938,18 @@ class 相对强弱指数:
|
||||
|
||||
# ----- 计算RSI的SMA(简单移动平均) -----
|
||||
RSI_SMA = None
|
||||
历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else []
|
||||
历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else deque()
|
||||
RSI和 = 前一个RSI.RSI和
|
||||
if RSI_SMA周期 is not None and RSI_SMA周期 > 0 and RSI is not None:
|
||||
# 将当前RSI加入队列
|
||||
历史队列.append(RSI)
|
||||
# 保持队列长度不超过周期
|
||||
RSI和 += RSI
|
||||
if len(历史队列) > RSI_SMA周期:
|
||||
历史队列.pop(0)
|
||||
# 计算SMA(即使队列未满也计算当前平均值)
|
||||
RSI和 -= 历史队列.popleft()
|
||||
if 历史队列:
|
||||
RSI_SMA = sum(历史队列) / len(历史队列)
|
||||
RSI_SMA = RSI和 / len(历史队列)
|
||||
else:
|
||||
# 未启用SMA,清空队列
|
||||
历史队列 = []
|
||||
历史队列 = deque()
|
||||
RSI和 = 0.0
|
||||
|
||||
return cls(
|
||||
时间戳=当前时间,
|
||||
@@ -1945,6 +1966,7 @@ class 相对强弱指数:
|
||||
RSI_SMA周期=RSI_SMA周期,
|
||||
RSI_SMA=RSI_SMA,
|
||||
RSI历史队列=历史队列,
|
||||
RSI和=RSI和,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1996,16 +2018,16 @@ class 随机指标:
|
||||
K: Optional[float] = None,
|
||||
D: Optional[float] = None,
|
||||
J: Optional[float] = None,
|
||||
历史最高价队列: list[float] = None,
|
||||
历史最低价队列: list[float] = None,
|
||||
历史最高价队列: Optional[deque[float]] = None,
|
||||
历史最低价队列: Optional[deque[float]] = None,
|
||||
前一个RSV: Optional[float] = None,
|
||||
前一个K: Optional[float] = None,
|
||||
前一个D: Optional[float] = None,
|
||||
):
|
||||
if 历史最高价队列 is None:
|
||||
历史最高价队列 = []
|
||||
历史最高价队列 = deque()
|
||||
if 历史最低价队列 is None:
|
||||
历史最低价队列 = []
|
||||
历史最低价队列 = deque()
|
||||
|
||||
# 原始数据
|
||||
self.时间戳 = 时间戳
|
||||
@@ -2065,8 +2087,8 @@ class 随机指标:
|
||||
K=None,
|
||||
D=None,
|
||||
J=None,
|
||||
历史最高价队列=[初始最高价],
|
||||
历史最低价队列=[初始最低价],
|
||||
历史最高价队列=deque([初始最高价]),
|
||||
历史最低价队列=deque([初始最低价]),
|
||||
前一个RSV=None,
|
||||
前一个K=None,
|
||||
前一个D=None,
|
||||
@@ -2113,13 +2135,13 @@ class 随机指标:
|
||||
历史最高价 = 前一个KDJ.历史最高价队列.copy()
|
||||
历史最高价.append(当前最高价)
|
||||
if len(历史最高价) > N:
|
||||
历史最高价.pop(0)
|
||||
历史最高价.popleft()
|
||||
|
||||
# 更新历史最低价队列
|
||||
历史最低价 = 前一个KDJ.历史最低价队列.copy()
|
||||
历史最低价.append(当前最低价)
|
||||
if len(历史最低价) > N:
|
||||
历史最低价.pop(0)
|
||||
历史最低价.popleft()
|
||||
|
||||
# 计算RSV(需要队列长度达到N才能计算)
|
||||
RSV = None
|
||||
@@ -2218,7 +2240,7 @@ class 布林带:
|
||||
self.上轨 = 上轨
|
||||
self.中轨 = 中轨
|
||||
self.下轨 = 下轨
|
||||
self._历史队列 = 历史队列 if 历史队列 is not None else []
|
||||
self._历史队列 = 历史队列 if 历史队列 is not None else deque()
|
||||
self._均值 = _均值
|
||||
self._方差和 = _方差和
|
||||
|
||||
@@ -2233,7 +2255,7 @@ class 布林带:
|
||||
:return: 初始的布林带实例
|
||||
"""
|
||||
价格 = 指标.K线取值(k线, 计算方式)
|
||||
return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格])
|
||||
return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=deque([价格]))
|
||||
|
||||
@classmethod
|
||||
def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带:
|
||||
@@ -2251,7 +2273,7 @@ class 布林带:
|
||||
q = prev._历史队列.copy()
|
||||
q.append(当前价)
|
||||
if len(q) > 周期:
|
||||
q.pop(0)
|
||||
q.popleft()
|
||||
|
||||
# 增量均值和方差
|
||||
if len(q) < 周期:
|
||||
@@ -2969,6 +2991,58 @@ class K线:
|
||||
"""
|
||||
return 序列[序列.index(始) : 序列.index(终) + 1]
|
||||
|
||||
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> "K线":
|
||||
时间偏移 = timedelta(seconds=self.周期)
|
||||
时间戳: datetime = self.时间戳 + 时间偏移
|
||||
成交量: float = 998
|
||||
高: float = 0
|
||||
低: float = 0
|
||||
高低差 = self.高 - self.低
|
||||
match 方向:
|
||||
case 相对方向.向上:
|
||||
偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883))
|
||||
低 = self.低 + 偏移
|
||||
高 = self.高 + 偏移
|
||||
case 相对方向.向下:
|
||||
偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883))
|
||||
低 = self.低 - 偏移
|
||||
高 = self.高 - 偏移
|
||||
case 相对方向.向上缺口:
|
||||
偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883))
|
||||
低 = self.低 + 偏移
|
||||
高 = self.高 + 偏移
|
||||
case 相对方向.向下缺口:
|
||||
偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883))
|
||||
低 = self.低 - 偏移
|
||||
高 = self.高 - 偏移
|
||||
case 相对方向.衔接向上:
|
||||
偏移 = self.高 - self.低
|
||||
高 = self.高 + 偏移
|
||||
低 = self.高
|
||||
case 相对方向.衔接向下:
|
||||
偏移 = self.高 - self.低
|
||||
高 = self.低
|
||||
低 = self.低 - 偏移
|
||||
|
||||
try:
|
||||
小数点 = [len(str(n).split(".")[-1]) for n in (self.开盘价, self.高, self.低, self.收盘价)]
|
||||
except:
|
||||
小数点 = [2, 1]
|
||||
新K线 = K线.创建普K(
|
||||
标识=self.标识,
|
||||
时间戳=时间戳,
|
||||
开盘价=round(random.uniform(高, 低), max(小数点)),
|
||||
最高价=round(高, max(小数点)),
|
||||
最低价=round(低, max(小数点)),
|
||||
收盘价=round(random.uniform(高, 低), max(小数点)),
|
||||
成交量=成交量 * random.random(),
|
||||
序号=self.序号 + 1,
|
||||
周期=self.周期,
|
||||
)
|
||||
|
||||
# assert 相对方向.分析(self, 新K线) is 方向, (方向, 相对方向.分析(self, 新K线))
|
||||
return 新K线
|
||||
|
||||
|
||||
class 缠论K线:
|
||||
"""经包含处理后的标准化K线,有方向和分型结构标记。
|
||||
@@ -3723,7 +3797,7 @@ class 虚线:
|
||||
段.实_中枢序列 = []
|
||||
段.虚_中枢序列 = []
|
||||
段.合_中枢序列 = []
|
||||
段.基础序列 = 虚线序列
|
||||
段.基础序列 = 虚线序列[:]
|
||||
return 段
|
||||
|
||||
@classmethod
|
||||
@@ -3892,6 +3966,111 @@ class 虚线:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向):
|
||||
"""计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断)
|
||||
|
||||
:param 普K序列: K线序列
|
||||
:param 方向: 运行方向
|
||||
:return: [柱子背驰, DIF背驰, DEA背驰]
|
||||
"""
|
||||
if 方向 is 相对方向.向上:
|
||||
柱子序列 = []
|
||||
离差值序列 = []
|
||||
信号线序列 = []
|
||||
for k线 in 普K序列:
|
||||
m = k线.macd
|
||||
if m.MACD柱 > 0:
|
||||
柱子序列.append(k线)
|
||||
if m.DIF > 0:
|
||||
离差值序列.append(k线)
|
||||
if m.DEA > 0:
|
||||
信号线序列.append(k线)
|
||||
|
||||
if not 柱子序列:
|
||||
return [False, False, False]
|
||||
最高柱子 = max(柱子序列, key=lambda k线: k线.macd.MACD柱)
|
||||
最高离差值 = max(离差值序列, key=lambda k线: k线.macd.DIF) if 离差值序列 else None
|
||||
最高信号线 = max(信号线序列, key=lambda k线: k线.macd.DEA) if 信号线序列 else None
|
||||
|
||||
结果 = []
|
||||
柱子 = [最高柱子, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.MACD柱 > 柱子[1].macd.MACD柱 and 柱子[0].高 < 柱子[1].高:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高离差值 is not None:
|
||||
柱子 = [最高离差值, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DIF > 柱子[1].macd.DIF and 柱子[0].高 < 柱子[1].高:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高信号线 is not None:
|
||||
柱子 = [最高信号线, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DEA > 柱子[1].macd.DEA and 柱子[0].高 < 柱子[1].高:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
return 结果
|
||||
else:
|
||||
柱子序列 = []
|
||||
离差值序列 = []
|
||||
信号线序列 = []
|
||||
for k线 in 普K序列:
|
||||
m = k线.macd
|
||||
if m.MACD柱 < 0:
|
||||
柱子序列.append(k线)
|
||||
if m.DIF < 0:
|
||||
离差值序列.append(k线)
|
||||
if m.DEA < 0:
|
||||
信号线序列.append(k线)
|
||||
|
||||
if not 柱子序列:
|
||||
return [False, False, False]
|
||||
最高柱子 = max(柱子序列, key=lambda k线: abs(k线.macd.MACD柱))
|
||||
最高离差值 = max(离差值序列, key=lambda k线: abs(k线.macd.DIF)) if 离差值序列 else None
|
||||
最高信号线 = max(信号线序列, key=lambda k线: abs(k线.macd.DEA)) if 信号线序列 else None
|
||||
|
||||
结果 = []
|
||||
柱子 = [最高柱子, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.MACD柱 < 柱子[1].macd.MACD柱 and 柱子[0].低 > 柱子[1].低:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高离差值 is not None:
|
||||
柱子 = [最高离差值, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DIF < 柱子[1].macd.DIF and 柱子[0].低 > 柱子[1].低:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高信号线 is not None:
|
||||
柱子 = [最高信号线, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DEA < 柱子[1].macd.DEA and 柱子[0].低 > 柱子[1].低:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
return 结果
|
||||
|
||||
@classmethod
|
||||
def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向):
|
||||
"""计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断)
|
||||
@@ -4430,8 +4609,7 @@ class 笔:
|
||||
临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck)
|
||||
递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
|
||||
if 分型序列 and 分型序列[-1] is 临时分型:
|
||||
""""""
|
||||
# logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型)
|
||||
logger.warning(f"笔.分析 事后修复错过的笔:{临时分型}, 当前分型: {当前分型}")
|
||||
|
||||
递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
|
||||
return 递归层次
|
||||
@@ -4589,14 +4767,14 @@ class 线段特征:
|
||||
return self.标识 # f"{self.标识}:{self.序号}"
|
||||
|
||||
def __str__(self):
|
||||
if not len(self):
|
||||
if not len(self.基础序列):
|
||||
return f"{self.标识}<{self.线段方向}, 空>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>"
|
||||
|
||||
def __repr__(self):
|
||||
if not len(self):
|
||||
if not len(self.基础序列):
|
||||
return f"{self.标识}<{self.线段方向}, 空>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>"
|
||||
|
||||
@property
|
||||
def 文(self) -> 分型:
|
||||
@@ -4807,6 +4985,11 @@ class 线段:
|
||||
|
||||
__slots__ = []
|
||||
|
||||
@staticmethod
|
||||
def _索引(序列: list, 项) -> int:
|
||||
"""O(1) index lookup — 序列元素序号连续递增。"""
|
||||
return 项.序号 - 序列[0].序号
|
||||
|
||||
@classmethod
|
||||
def _添加虚线(cls, 段: 虚线, 筆: 虚线):
|
||||
"""向线段中添加一笔
|
||||
@@ -4937,7 +5120,7 @@ class 线段:
|
||||
break
|
||||
|
||||
if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0):
|
||||
段.基础序列[:] = 基础序列[:]
|
||||
段.基础序列[:] = 基础序列
|
||||
else:
|
||||
raise RuntimeError()
|
||||
else:
|
||||
@@ -4954,7 +5137,7 @@ class 线段:
|
||||
return
|
||||
基础序列 = 段.基础序列
|
||||
if 段.前一结束位置 and 段.前一结束位置 in 基础序列:
|
||||
基础序列 = 段.基础序列[段.基础序列.index(段.前一结束位置) - 1 :]
|
||||
基础序列 = 段.基础序列[cls._索引(段.基础序列, 段.前一结束位置) - 1 :]
|
||||
|
||||
特征序列 = 线段特征.静态分析(基础序列, 段.方向, 线段.四象(段), 配置.线段_特征序列忽视老阴老阳)
|
||||
if len(特征序列) >= 3:
|
||||
@@ -5072,7 +5255,7 @@ class 线段:
|
||||
特征后一笔 = 最近特征.基础序列[-1]
|
||||
|
||||
if 特征后一笔 is not None:
|
||||
序号 = 段.基础序列.index(特征后一笔)
|
||||
序号 = cls._索引(段.基础序列, 特征后一笔)
|
||||
if 序号 < len(段.基础序列) - 1:
|
||||
下一笔 = 段.基础序列[序号 + 1]
|
||||
if (段.方向 is 相对方向.向上 and 段.高 <= 下一笔.高) or (段.方向 is 相对方向.向下 and 段.低 >= 下一笔.低):
|
||||
@@ -5091,15 +5274,16 @@ class 线段:
|
||||
:param 序列: 参考序列
|
||||
"""
|
||||
基础序列 = []
|
||||
序列集 = set(序列) if not isinstance(序列, set) else 序列
|
||||
for 元素 in 段.基础序列:
|
||||
if 元素 not in 序列:
|
||||
if 元素 not in 序列集:
|
||||
break
|
||||
if 基础序列:
|
||||
if not 基础序列[-1].之后是(元素):
|
||||
break
|
||||
基础序列.append(元素)
|
||||
|
||||
段.基础序列[:] = 基础序列[:]
|
||||
段.基础序列[:] = 基础序列
|
||||
段.特征序列[2] = None
|
||||
|
||||
@classmethod
|
||||
@@ -5168,16 +5352,17 @@ class 线段:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: str):
|
||||
def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
|
||||
"""内部方法:向线段序列添加新线段
|
||||
|
||||
:param 线段序列: 线段列表
|
||||
:param 待添加线段: 新线段
|
||||
:param 配置: 缠论配置
|
||||
:param 行号: 调用行号
|
||||
:param 层级: 递归层级
|
||||
"""
|
||||
if 线段序列 and not 线段序列[-1].之后是(待添加线段):
|
||||
raise ValueError(f"线段.向序列中添加 不连续[{行号}]", 线段序列[-1].武, 待添加线段.文)
|
||||
raise ValueError(f"线段.向序列中添加 不连续[{行号}, {层级}]", 线段序列[-1].武, 待添加线段.文)
|
||||
待添加线段.模式 = "文武"
|
||||
|
||||
if not 线段序列:
|
||||
@@ -5188,10 +5373,10 @@ class 线段:
|
||||
|
||||
if not 之前线段.特征序列[2] and not 之前线段.短路修正:
|
||||
assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段.右 = None", 之前线段)
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段.右 = None", 之前线段)
|
||||
|
||||
if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正:
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段[-1] not in 待添加虚线!", 之前线段)
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段[-1] not in 待添加虚线!", 之前线段)
|
||||
|
||||
待添加线段.序号 = 之前线段.序号 + 1
|
||||
待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None
|
||||
@@ -5204,13 +5389,14 @@ class 线段:
|
||||
# logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线)
|
||||
|
||||
@classmethod
|
||||
def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str):
|
||||
def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
|
||||
"""内部方法:从线段序列弹出最后一个线段
|
||||
|
||||
:param 线段序列: 线段列表
|
||||
:param 待弹出线段: 待弹出的线段
|
||||
:param 配置: 缠论配置
|
||||
:param 行号: 调用行号
|
||||
:param 层级: 递归层级
|
||||
:return: 弹出的线段或None
|
||||
"""
|
||||
if not 线段序列:
|
||||
@@ -5223,7 +5409,7 @@ class 线段:
|
||||
if 右 is not None:
|
||||
结构 = 分型结构.分析(左, 中, 右, True, True)
|
||||
if 结构 in (分型结构.顶, 分型结构.底) and not 相对方向.分析(左.高, 左.低, 中.高, 中.低).是否缺口():
|
||||
logger.warning(f"警告<{行号}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}")
|
||||
logger.warning(f"警告<{行号}, {层级}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}")
|
||||
|
||||
线段序列.pop()
|
||||
待弹出线段.前一结束位置 = None
|
||||
@@ -5268,7 +5454,7 @@ class 线段:
|
||||
|
||||
# 执行修正
|
||||
序列 = 当前线段.基础序列[:]
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
|
||||
assert 线段序列, "缺口突破: 线段序列为第二次空!"
|
||||
当前线段 = 线段序列[-1]
|
||||
|
||||
@@ -5277,7 +5463,7 @@ class 线段:
|
||||
assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!"
|
||||
当前线段基础序列.extend(序列)
|
||||
|
||||
当前线段.基础序列[:] = 当前线段基础序列[:]
|
||||
当前线段.基础序列[:] = 当前线段基础序列
|
||||
线段._刷新(当前线段, 配置)
|
||||
return True
|
||||
|
||||
@@ -5305,7 +5491,7 @@ class 线段:
|
||||
|
||||
assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!"
|
||||
# 切割基础序列
|
||||
基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :]
|
||||
基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :]
|
||||
|
||||
# 长度条件
|
||||
if not (len(基础序列) == 4 and len(线段序列) >= 2):
|
||||
@@ -5321,19 +5507,25 @@ class 线段:
|
||||
logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出
|
||||
|
||||
基础序列 = 当前线段.基础序列[:]
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
|
||||
assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!"
|
||||
当前线段 = 线段序列[-1]
|
||||
当前线段.特征序列[2] = None
|
||||
assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"
|
||||
for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]:
|
||||
# assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"
|
||||
if 当前线段.基础序列[-1] not in 基础序列:
|
||||
logger.error(f"非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!")
|
||||
序号 = 0
|
||||
else:
|
||||
序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1
|
||||
|
||||
for 临时虚线 in 基础序列[序号:]:
|
||||
线段._添加虚线(当前线段, 临时虚线)
|
||||
线段._刷新(当前线段, 配置)
|
||||
当前线段.短路修正 = True
|
||||
|
||||
if 当前线段.特征序列[2] is not None:
|
||||
段 = 虚线.创建线段([左, 中, 右])
|
||||
线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级)
|
||||
段.特征序列[0] = 线段特征.新建([中], 段.方向)
|
||||
|
||||
return True
|
||||
@@ -5379,7 +5571,7 @@ class 线段:
|
||||
# 执行修正
|
||||
当前线段.短路修正 = True
|
||||
新段 = 虚线.创建线段(基础序列)
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
@@ -5422,7 +5614,7 @@ class 线段:
|
||||
# 创建第一个新段(之后基础序列去掉最后3个)
|
||||
新段 = 虚线.创建线段(之后基础序列[:-3])
|
||||
新段.短路修正 = True
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
|
||||
# 根据当前线段的四象决定是否清空前一个缺口
|
||||
if 线段.四象(当前线段) in ("老阴", "老阳"):
|
||||
@@ -5430,7 +5622,7 @@ class 线段:
|
||||
|
||||
# 创建第二个新段(最后3个元素)
|
||||
新段 = 虚线.创建线段(之后基础序列[-3:])
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
|
||||
return True
|
||||
|
||||
@@ -5466,7 +5658,7 @@ class 线段:
|
||||
if not 线段._基础判断(左, 中, 右, 关系序列): # FIXME 首个线段必须有明确方向
|
||||
continue
|
||||
段 = 虚线.创建线段([左, 中, 右])
|
||||
线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级)
|
||||
段.特征序列[0] = 线段特征.新建([中], 段.方向)
|
||||
break
|
||||
if not 线段序列:
|
||||
@@ -5475,7 +5667,7 @@ class 线段:
|
||||
# -------------------- 2. 清理无效的尾部引用 --------------------
|
||||
while 线段序列 and 线段序列[-1].前一结束位置:
|
||||
if 线段序列[-1].前一结束位置 not in 笔序列:
|
||||
线段._弹出线段(线段序列, 线段序列[-1], 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 线段序列[-1], 配置, sys._getframe().f_lineno, 层级)
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -5487,7 +5679,7 @@ class 线段:
|
||||
线段._序列重置(当前线段, 笔序列)
|
||||
|
||||
if len(当前线段.基础序列) < 3:
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
|
||||
if not 线段序列:
|
||||
return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列)
|
||||
|
||||
@@ -5497,7 +5689,7 @@ class 线段:
|
||||
if 当前线段.特征序列[2] is not None:
|
||||
基础序列 = 线段.分割序列(当前线段)[1]
|
||||
新段 = 虚线.创建线段(基础序列)
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
if 线段.四象(当前线段) in ("老阴", "老阳"):
|
||||
新段.前一缺口 = None
|
||||
|
||||
@@ -5514,9 +5706,10 @@ class 线段:
|
||||
当前线段 = 线段序列[-1]
|
||||
if not 当前线段.基础序列:
|
||||
raise RuntimeError
|
||||
起始索引 = 笔序列.index(当前线段.基础序列[-1]) + 1
|
||||
起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1
|
||||
|
||||
for 当前虚线 in 笔序列[起始索引:]:
|
||||
for idx in range(起始索引, len(笔序列)):
|
||||
当前虚线 = 笔序列[idx]
|
||||
当前线段 = 线段序列[-1]
|
||||
四象 = 线段.四象(当前线段)
|
||||
|
||||
@@ -5543,7 +5736,7 @@ class 线段:
|
||||
|
||||
基础序列 = 线段.分割序列(当前线段)[1]
|
||||
新段 = 虚线.创建线段(基础序列)
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
if 四象 in ("老阴", "老阳"):
|
||||
新段.前一缺口 = None
|
||||
|
||||
@@ -5575,15 +5768,16 @@ class 线段:
|
||||
:param 序列: 参考序列
|
||||
"""
|
||||
基础序列 = []
|
||||
序列集 = set(序列) if not isinstance(序列, set) else 序列
|
||||
for 元素 in 段.基础序列:
|
||||
if 元素 not in 序列:
|
||||
if 元素 not in 序列集:
|
||||
break
|
||||
if 基础序列:
|
||||
if not 基础序列[-1].之后是(元素):
|
||||
logger.warning(" 线段._验证序列 数据不连续")
|
||||
break
|
||||
基础序列.append(元素)
|
||||
段.基础序列[:] = 基础序列[:]
|
||||
段.基础序列[:] = 基础序列
|
||||
if len(段.基础序列) % 2 == 0:
|
||||
段.基础序列 and 段.基础序列.pop()
|
||||
|
||||
@@ -5676,7 +5870,7 @@ class 线段:
|
||||
if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号:
|
||||
return None
|
||||
|
||||
序号 = 虚线序列.index(当前线段.基础序列[-1]) + 1
|
||||
序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1
|
||||
if 序号 >= len(虚线序列):
|
||||
return None
|
||||
|
||||
@@ -5739,7 +5933,7 @@ class 线段:
|
||||
if 当前段.实_中枢序列:
|
||||
if 阳[-1] in 当前段.实_中枢序列[-1].基础序列:
|
||||
# 当前最后一笔在最后一中枢里
|
||||
序号 = 当前段.基础序列.index(当前段.实_中枢序列[-1].基础序列[0])
|
||||
序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0])
|
||||
进入段 = 当前段.基础序列[序号 - 1]
|
||||
离开段 = 阳[-1]
|
||||
assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号)
|
||||
@@ -5793,7 +5987,7 @@ class 线段:
|
||||
笔序列.append(停顿)
|
||||
线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向.顺, 相对方向.逆, 相对方向.同])
|
||||
if 线段序列 and 线段序列[-1].武 is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1:
|
||||
新段 = 虚线.创建线段(线段序列[-1].基础序列[:])
|
||||
新段 = 虚线.创建线段(线段序列[-1].基础序列)
|
||||
新段.序号 = self.序号
|
||||
线段._刷新(新段, 观察员.配置)
|
||||
if 新段.方向 is self.方向:
|
||||
@@ -5984,7 +6178,7 @@ class 中枢:
|
||||
|
||||
:return: 虚线列表
|
||||
"""
|
||||
序列: List = self.基础序列[:]
|
||||
序列: List = self.基础序列.copy()
|
||||
if self.第三买卖线 is not None:
|
||||
序列.append(self.第三买卖线)
|
||||
return 序列
|
||||
@@ -6008,13 +6202,14 @@ class 中枢:
|
||||
"""
|
||||
有效序列 = self.基础序列[:]
|
||||
无效序列 = []
|
||||
序列集 = set(序列)
|
||||
for 元素 in self.基础序列:
|
||||
if 元素 not in 序列:
|
||||
if 元素 not in 序列集:
|
||||
无效序列.append(元素)
|
||||
|
||||
if 无效序列:
|
||||
无效 = 无效序列[0]
|
||||
序号 = self.基础序列.index(无效)
|
||||
序号 = 线段._索引(self.基础序列, 无效)
|
||||
有效序列 = self.基础序列[:序号]
|
||||
|
||||
if len(有效序列) < 3:
|
||||
@@ -6022,14 +6217,14 @@ class 中枢:
|
||||
self.本级_第三买卖线 = None
|
||||
return False
|
||||
|
||||
self.基础序列[:] = 有效序列
|
||||
self.基础序列 = 有效序列
|
||||
|
||||
有效序列 = []
|
||||
for 元素 in self.基础序列:
|
||||
if 相对方向.分析(self.高, self.低, 元素.高, 元素.低).是否缺口():
|
||||
break
|
||||
有效序列.append(元素)
|
||||
self.基础序列[:] = 有效序列
|
||||
self.基础序列 = 有效序列
|
||||
|
||||
if len(self.基础序列) < 3:
|
||||
return False
|
||||
@@ -6201,7 +6396,7 @@ class 中枢:
|
||||
左, 中, 右 = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
|
||||
if 中枢.基础检查(左, 中, 右):
|
||||
新中枢 = 中枢.创建(左, 中, 右, 中.级别, 标识)
|
||||
序号 = 虚线序列.index(左)
|
||||
序号 = 线段._索引(虚线序列, 左)
|
||||
if 跳过首部 and (左.序号 == 0 or 序号 == 0):
|
||||
continue # 方便计算走势
|
||||
if 序号 >= 2:
|
||||
@@ -6222,7 +6417,7 @@ class 中枢:
|
||||
中枢._从中枢序列尾部弹出(中枢序列, 当前中枢)
|
||||
return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1)
|
||||
|
||||
序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1
|
||||
序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
|
||||
|
||||
基础序列 = []
|
||||
for 当前虚线 in 虚线序列[序号:]:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun-py"
|
||||
version = "26.6.47"
|
||||
version = "26.6.73"
|
||||
edition = "2024"
|
||||
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
|
||||
authors = ["YuYuKunKun"]
|
||||
@@ -12,11 +12,12 @@ crate-type = ["cdylib"]
|
||||
name = "chanlun"
|
||||
|
||||
[dependencies]
|
||||
chanlun = { path = "../chanlun" }
|
||||
lru = "0.18"
|
||||
chanlun = "26.6.4" # { path = "../chanlun" }
|
||||
parking_lot = "0.12"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
|
||||
tracing-core = "0.1"
|
||||
dashmap = "6"
|
||||
tracing = "0.1"
|
||||
pyo3 = { version = "0.28", features = ["experimental-inspect"] }
|
||||
serde_json = "1"
|
||||
chrono = "0.4"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
|
||||
tracing-core = "0.1"
|
||||
|
||||
@@ -72,6 +72,77 @@ pip install target/wheels/chanlun-*.whl
|
||||
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致
|
||||
- 支持 `.nb` 二进制文件格式(大端字节序)
|
||||
|
||||
## 性能配置
|
||||
|
||||
### 缓存模式
|
||||
|
||||
Python 对象缓存有两种模式,通过环境变量 `CHANLUN_CACHE_MODE` 或函数调用切换:
|
||||
|
||||
```python
|
||||
from chanlun._chanlun import set_cache_mode, get_cache_mode
|
||||
|
||||
# 默认:thread_local,每线程独立缓存,零锁,多线程场景最佳
|
||||
print(get_cache_mode()) # "thread_local"
|
||||
|
||||
# 全局:dashmap 分片哈希表,跨线程 Python `is` 身份一致
|
||||
set_cache_mode("global") # 必须在创建任何观察者之前调用
|
||||
```
|
||||
|
||||
```bash
|
||||
# 环境变量方式
|
||||
CHANLUN_CACHE_MODE=global python main.py # 全局缓存
|
||||
python main.py # 默认:线程局部缓存
|
||||
```
|
||||
|
||||
| 模式 | 性能 | Python `is` 跨线程 | 适用场景 |
|
||||
|------|------|---------------------|----------|
|
||||
| `thread_local`(默认) | 零锁,最快 | 否 | 批量回测、多线程独立分析 |
|
||||
| `global` | dashmap 分片锁 | 是 | 测试验证、跨线程对象共享 |
|
||||
|
||||
### 日志模式
|
||||
|
||||
日志输出有三种模式,通过环境变量 `CHANLUN_LOG_MODE` 或函数调用切换:
|
||||
|
||||
```python
|
||||
from chanlun._chanlun import set_log_mode, set_log_level, get_log_mode
|
||||
|
||||
# 默认:off,不输出,零开销
|
||||
print(get_log_mode()) # "off"
|
||||
|
||||
# 简单模式:直接 eprintln/println
|
||||
set_log_mode("simple")
|
||||
set_log_level("debug") # 必需:设置日志级别启用输出
|
||||
|
||||
# Tracing 模式:带时间戳和文件位置格式化输出
|
||||
set_log_mode("tracing")
|
||||
set_log_level("debug")
|
||||
```
|
||||
|
||||
```bash
|
||||
# 环境变量方式
|
||||
CHANLUN_LOG_MODE=simple python main.py # 简单输出
|
||||
CHANLUN_LOG_MODE=tracing python main.py # 格式化输出
|
||||
python main.py # 默认:静默
|
||||
```
|
||||
|
||||
| 模式 | 输出方式 | 性能 | 格式 |
|
||||
|------|---------|------|------|
|
||||
| `off`(默认) | 无 | 零开销 | — |
|
||||
| `simple` | `eprintln!` / `println!` | 极轻 | 纯文本 |
|
||||
| `tracing` | tracing-subscriber | 稍重 | `2026-06-12 01:57:59.942 WARN file.rs:line` |
|
||||
|
||||
### 观察者直传(避免 Python list 转换)
|
||||
|
||||
背驰分析新增 `_OBS` 后缀方法,直接接受观察者引用,跳过 `list[K线]` ↔ `Vec<Arc<K线>>` 转换:
|
||||
|
||||
```python
|
||||
# 旧方式:构建 Python 列表
|
||||
result = 背驰分析.MACD背驰(进入段, 离开段, obs.普通K线序列, "总")
|
||||
|
||||
# 新方式:直接传观察者
|
||||
result = 背驰分析.MACD背驰_OBS(进入段, 离开段, obs, "总")
|
||||
```
|
||||
|
||||
## 许可
|
||||
|
||||
本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parse(MIT)、termcolor(MIT)。
|
||||
|
||||
+52
-4
@@ -5,11 +5,18 @@ from typing import Any, ClassVar, Optional, List, Dict, Tuple, Union
|
||||
from datetime import datetime
|
||||
|
||||
# ========== Module-level functions ==========
|
||||
|
||||
def get_rs_log_level() -> str: ...
|
||||
def set_rs_log_level(level: str) -> None: ...
|
||||
def get_log_level() -> str: ...
|
||||
def set_log_level(level: str) -> None: ...
|
||||
def get_log_mode() -> str: ...
|
||||
def set_log_mode(mode: str) -> None: ...
|
||||
def get_cache_mode() -> str: ...
|
||||
def set_cache_mode(mode: str) -> None: ...
|
||||
def get_分型模式() -> bool: ...
|
||||
def set_分型模式(value: bool) -> None: ...
|
||||
def get_扩展线段模式() -> bool: ...
|
||||
def set_扩展线段模式(value: bool) -> None: ...
|
||||
def 转化为时间戳(ts: Any) -> int: ...
|
||||
def 转化为时间戳_数字(ts: Any) -> int: ...
|
||||
def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
|
||||
@@ -75,6 +82,8 @@ class 相对方向:
|
||||
def 翻转(self) -> 相对方向: ...
|
||||
@classmethod
|
||||
def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
|
||||
@classmethod
|
||||
def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ...
|
||||
def __str__(self) -> str: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@@ -326,6 +335,7 @@ class K线:
|
||||
def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: ...
|
||||
def __str__(self) -> str: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ...
|
||||
def __bytes__(self) -> bytes: ...
|
||||
def __eq__(self, other: Any) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@@ -438,9 +448,9 @@ class 虚线:
|
||||
@property
|
||||
def 模式(self) -> str: ...
|
||||
@property
|
||||
def 特征序列_显示(self) -> bool: ...
|
||||
@特征序列_显示.setter
|
||||
def 特征序列_显示(self, value: bool) -> None: ...
|
||||
def _特征序列_显示(self) -> bool: ...
|
||||
@_特征序列_显示.setter
|
||||
def _特征序列_显示(self, value: bool) -> None: ...
|
||||
@property
|
||||
def 特征序列(self) -> List[Optional[线段特征]]: ...
|
||||
@property
|
||||
@@ -526,6 +536,8 @@ class 虚线:
|
||||
class 线段特征:
|
||||
@property
|
||||
def 序号(self) -> int: ...
|
||||
@序号.setter
|
||||
def 序号(self, value: int) -> None: ...
|
||||
@property
|
||||
def 标识(self) -> str: ...
|
||||
@标识.setter
|
||||
@@ -570,6 +582,18 @@ class 背驰分析:
|
||||
def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ...
|
||||
@classmethod
|
||||
def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ...
|
||||
@classmethod
|
||||
def MACD背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 方式: str = "总") -> bool: ...
|
||||
@classmethod
|
||||
def 全量背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
|
||||
@classmethod
|
||||
def 任意背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
|
||||
@classmethod
|
||||
def 配置背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置) -> bool: ...
|
||||
@classmethod
|
||||
def 任选背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
|
||||
@classmethod
|
||||
def 背驰模式_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置, 模式: str) -> bool: ...
|
||||
|
||||
class 笔:
|
||||
@classmethod
|
||||
@@ -793,6 +817,30 @@ class 观察者:
|
||||
def 扩展线段序列_扩展线段(self) -> List[虚线]: ...
|
||||
@property
|
||||
def 扩展中枢序列_扩展线段(self) -> List[中枢]: ...
|
||||
@property
|
||||
def 线段分析层次(self) -> int: ...
|
||||
@线段分析层次.setter
|
||||
def 线段分析层次(self, value: int) -> None: ...
|
||||
@property
|
||||
def 扩展线段分析层次(self) -> int: ...
|
||||
@扩展线段分析层次.setter
|
||||
def 扩展线段分析层次(self, value: int) -> None: ...
|
||||
@property
|
||||
def 混合扩展线段分析层次(self) -> int: ...
|
||||
@混合扩展线段分析层次.setter
|
||||
def 混合扩展线段分析层次(self, value: int) -> None: ...
|
||||
@property
|
||||
def 线段序列组(self) -> List[List[虚线]]: ...
|
||||
@property
|
||||
def 中枢序列组(self) -> List[List[中枢]]: ...
|
||||
@property
|
||||
def 扩展线段序列组(self) -> List[List[虚线]]: ...
|
||||
@property
|
||||
def 扩展中枢序列组(self) -> List[List[中枢]]: ...
|
||||
@property
|
||||
def 混合扩展线段序列组(self) -> List[List[虚线]]: ...
|
||||
@property
|
||||
def 混合扩展中枢序列组(self) -> List[List[中枢]]: ...
|
||||
def 重置基础序列(self) -> None: ...
|
||||
def 增加原始K线(self, 普K: K线) -> None: ...
|
||||
def 投喂原始数据(self, 时间戳: int, 开: float, 高: float, 低: float, 收: float, 量: float) -> None: ...
|
||||
|
||||
@@ -9,10 +9,16 @@ def get_rs_log_level() -> str: ...
|
||||
def set_rs_log_level(level: str) -> None: ...
|
||||
def get_log_level() -> str: ...
|
||||
def set_log_level(level: str) -> None: ...
|
||||
def get_log_mode() -> str: ...
|
||||
def set_log_mode(mode: str) -> None: ...
|
||||
def get_cache_mode() -> str: ...
|
||||
def set_cache_mode(mode: str) -> None: ...
|
||||
def get_分型模式() -> bool: ...
|
||||
def set_分型模式(value: bool) -> None: ...
|
||||
def get_扩展线段模式() -> bool: ...
|
||||
def set_扩展线段模式(value: bool) -> None: ...
|
||||
def 转化为时间戳(ts: Any) -> int: ...
|
||||
def 转化为时间戳_数字(ts: Any) -> int: ...
|
||||
def K线相等(A: K线, B: K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
|
||||
def 缠论K线相等(A: 缠论K线, B: 缠论K线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
|
||||
def 分型相等(A: 分型, B: 分型, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ...
|
||||
@@ -76,6 +82,8 @@ class 相对方向:
|
||||
def 翻转(self) -> 相对方向: ...
|
||||
@classmethod
|
||||
def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
|
||||
@classmethod
|
||||
def 从序列中机选(cls, 数量: int, 可选方向: List[相对方向], 可重复: bool = True) -> List[相对方向]: ...
|
||||
def __str__(self) -> str: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@@ -327,6 +335,7 @@ class K线:
|
||||
def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: ...
|
||||
def __str__(self) -> str: ...
|
||||
def __repr__(self) -> str: ...
|
||||
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> K线: ...
|
||||
def __bytes__(self) -> bytes: ...
|
||||
def __eq__(self, other: Any) -> bool: ...
|
||||
def __hash__(self) -> int: ...
|
||||
@@ -573,6 +582,18 @@ class 背驰分析:
|
||||
def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ...
|
||||
@classmethod
|
||||
def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ...
|
||||
@classmethod
|
||||
def MACD背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 方式: str = "总") -> bool: ...
|
||||
@classmethod
|
||||
def 全量背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
|
||||
@classmethod
|
||||
def 任意背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
|
||||
@classmethod
|
||||
def 配置背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置) -> bool: ...
|
||||
@classmethod
|
||||
def 任选背驰_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者) -> bool: ...
|
||||
@classmethod
|
||||
def 背驰模式_OBS(cls, 进入段: 虚线, 离开段: 虚线, 观察员: 观察者, 配置: 缠论配置, 模式: str) -> bool: ...
|
||||
|
||||
class 笔:
|
||||
@classmethod
|
||||
|
||||
+264
-69
@@ -31,12 +31,14 @@ from __future__ import annotations
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
from collections import deque
|
||||
import random
|
||||
import struct
|
||||
import sys
|
||||
import tempfile
|
||||
import datetime as datetime_module
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from enum import Enum
|
||||
from functools import lru_cache
|
||||
from pathlib import Path
|
||||
@@ -52,6 +54,7 @@ from typing import (
|
||||
Sequence,
|
||||
Callable,
|
||||
Set,
|
||||
Generator,
|
||||
)
|
||||
|
||||
from loguru import logger
|
||||
@@ -1425,6 +1428,23 @@ class 相对方向(Enum):
|
||||
return 相对方向.逆
|
||||
raise RuntimeError("无法识别的方向")
|
||||
|
||||
@classmethod
|
||||
def 从序列中机选(
|
||||
cls,
|
||||
数量: int,
|
||||
可选方向: List["相对方向"],
|
||||
可重复: bool = True, # 是否允许重复选择
|
||||
) -> Generator["相对方向", None, None]:
|
||||
if not 可重复 and 数量 > len(可选方向):
|
||||
raise ValueError("数量超过可选方向数")
|
||||
|
||||
if 可重复:
|
||||
while 数量 > 0:
|
||||
yield random.choice(可选方向)
|
||||
数量 -= 1
|
||||
else:
|
||||
yield from random.sample(可选方向, 数量)
|
||||
|
||||
|
||||
class 分型结构(Enum):
|
||||
"""描述三根K线构成的顶底分型形态。
|
||||
@@ -1797,10 +1817,11 @@ class 相对强弱指数:
|
||||
下跌幅度: float = 0.0,
|
||||
平滑系数: float = 0.0,
|
||||
RSI_SMA: Optional[float] = None,
|
||||
RSI历史队列: List[float] = None,
|
||||
RSI历史队列: Optional[deque[float]] = None,
|
||||
RSI和: float = 0.0,
|
||||
):
|
||||
if RSI历史队列 is None:
|
||||
RSI历史队列 = []
|
||||
RSI历史队列 = deque()
|
||||
|
||||
# 原始数据
|
||||
self.时间戳 = 时间戳
|
||||
@@ -1829,6 +1850,7 @@ class 相对强弱指数:
|
||||
# RSI的SMA(信号线)相关字段
|
||||
self.RSI_SMA = RSI_SMA
|
||||
self.RSI历史队列 = RSI历史队列
|
||||
self.RSI和 = RSI和
|
||||
|
||||
@classmethod
|
||||
def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数:
|
||||
@@ -1916,19 +1938,18 @@ class 相对强弱指数:
|
||||
|
||||
# ----- 计算RSI的SMA(简单移动平均) -----
|
||||
RSI_SMA = None
|
||||
历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else []
|
||||
历史队列 = 前一个RSI.RSI历史队列.copy() if 前一个RSI.RSI历史队列 else deque()
|
||||
RSI和 = 前一个RSI.RSI和
|
||||
if RSI_SMA周期 is not None and RSI_SMA周期 > 0 and RSI is not None:
|
||||
# 将当前RSI加入队列
|
||||
历史队列.append(RSI)
|
||||
# 保持队列长度不超过周期
|
||||
RSI和 += RSI
|
||||
if len(历史队列) > RSI_SMA周期:
|
||||
历史队列.pop(0)
|
||||
# 计算SMA(即使队列未满也计算当前平均值)
|
||||
RSI和 -= 历史队列.popleft()
|
||||
if 历史队列:
|
||||
RSI_SMA = sum(历史队列) / len(历史队列)
|
||||
RSI_SMA = RSI和 / len(历史队列)
|
||||
else:
|
||||
# 未启用SMA,清空队列
|
||||
历史队列 = []
|
||||
历史队列 = deque()
|
||||
RSI和 = 0.0
|
||||
|
||||
return cls(
|
||||
时间戳=当前时间,
|
||||
@@ -1945,6 +1966,7 @@ class 相对强弱指数:
|
||||
RSI_SMA周期=RSI_SMA周期,
|
||||
RSI_SMA=RSI_SMA,
|
||||
RSI历史队列=历史队列,
|
||||
RSI和=RSI和,
|
||||
)
|
||||
|
||||
@classmethod
|
||||
@@ -1996,16 +2018,16 @@ class 随机指标:
|
||||
K: Optional[float] = None,
|
||||
D: Optional[float] = None,
|
||||
J: Optional[float] = None,
|
||||
历史最高价队列: list[float] = None,
|
||||
历史最低价队列: list[float] = None,
|
||||
历史最高价队列: Optional[deque[float]] = None,
|
||||
历史最低价队列: Optional[deque[float]] = None,
|
||||
前一个RSV: Optional[float] = None,
|
||||
前一个K: Optional[float] = None,
|
||||
前一个D: Optional[float] = None,
|
||||
):
|
||||
if 历史最高价队列 is None:
|
||||
历史最高价队列 = []
|
||||
历史最高价队列 = deque()
|
||||
if 历史最低价队列 is None:
|
||||
历史最低价队列 = []
|
||||
历史最低价队列 = deque()
|
||||
|
||||
# 原始数据
|
||||
self.时间戳 = 时间戳
|
||||
@@ -2065,8 +2087,8 @@ class 随机指标:
|
||||
K=None,
|
||||
D=None,
|
||||
J=None,
|
||||
历史最高价队列=[初始最高价],
|
||||
历史最低价队列=[初始最低价],
|
||||
历史最高价队列=deque([初始最高价]),
|
||||
历史最低价队列=deque([初始最低价]),
|
||||
前一个RSV=None,
|
||||
前一个K=None,
|
||||
前一个D=None,
|
||||
@@ -2113,13 +2135,13 @@ class 随机指标:
|
||||
历史最高价 = 前一个KDJ.历史最高价队列.copy()
|
||||
历史最高价.append(当前最高价)
|
||||
if len(历史最高价) > N:
|
||||
历史最高价.pop(0)
|
||||
历史最高价.popleft()
|
||||
|
||||
# 更新历史最低价队列
|
||||
历史最低价 = 前一个KDJ.历史最低价队列.copy()
|
||||
历史最低价.append(当前最低价)
|
||||
if len(历史最低价) > N:
|
||||
历史最低价.pop(0)
|
||||
历史最低价.popleft()
|
||||
|
||||
# 计算RSV(需要队列长度达到N才能计算)
|
||||
RSV = None
|
||||
@@ -2218,7 +2240,7 @@ class 布林带:
|
||||
self.上轨 = 上轨
|
||||
self.中轨 = 中轨
|
||||
self.下轨 = 下轨
|
||||
self._历史队列 = 历史队列 if 历史队列 is not None else []
|
||||
self._历史队列 = 历史队列 if 历史队列 is not None else deque()
|
||||
self._均值 = _均值
|
||||
self._方差和 = _方差和
|
||||
|
||||
@@ -2233,7 +2255,7 @@ class 布林带:
|
||||
:return: 初始的布林带实例
|
||||
"""
|
||||
价格 = 指标.K线取值(k线, 计算方式)
|
||||
return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格])
|
||||
return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=deque([价格]))
|
||||
|
||||
@classmethod
|
||||
def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带:
|
||||
@@ -2251,7 +2273,7 @@ class 布林带:
|
||||
q = prev._历史队列.copy()
|
||||
q.append(当前价)
|
||||
if len(q) > 周期:
|
||||
q.pop(0)
|
||||
q.popleft()
|
||||
|
||||
# 增量均值和方差
|
||||
if len(q) < 周期:
|
||||
@@ -2969,6 +2991,58 @@ class K线:
|
||||
"""
|
||||
return 序列[序列.index(始) : 序列.index(终) + 1]
|
||||
|
||||
def 根据当前K线生成新K线(self, 方向: 相对方向, 居中: bool = False) -> "K线":
|
||||
时间偏移 = timedelta(seconds=self.周期)
|
||||
时间戳: datetime = self.时间戳 + 时间偏移
|
||||
成交量: float = 998
|
||||
高: float = 0
|
||||
低: float = 0
|
||||
高低差 = self.高 - self.低
|
||||
match 方向:
|
||||
case 相对方向.向上:
|
||||
偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883))
|
||||
低 = self.低 + 偏移
|
||||
高 = self.高 + 偏移
|
||||
case 相对方向.向下:
|
||||
偏移 = 高低差 * 0.5 if 居中 else random.randint(int(高低差 * 0.1279), int(高低差 * 0.883))
|
||||
低 = self.低 - 偏移
|
||||
高 = self.高 - 偏移
|
||||
case 相对方向.向上缺口:
|
||||
偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883))
|
||||
低 = self.低 + 偏移
|
||||
高 = self.高 + 偏移
|
||||
case 相对方向.向下缺口:
|
||||
偏移 = 高低差 * 1.5 if 居中 else random.randint(int(高低差 * 1.1279), int(高低差 * 1.883))
|
||||
低 = self.低 - 偏移
|
||||
高 = self.高 - 偏移
|
||||
case 相对方向.衔接向上:
|
||||
偏移 = self.高 - self.低
|
||||
高 = self.高 + 偏移
|
||||
低 = self.高
|
||||
case 相对方向.衔接向下:
|
||||
偏移 = self.高 - self.低
|
||||
高 = self.低
|
||||
低 = self.低 - 偏移
|
||||
|
||||
try:
|
||||
小数点 = [len(str(n).split(".")[-1]) for n in (self.开盘价, self.高, self.低, self.收盘价)]
|
||||
except:
|
||||
小数点 = [2, 1]
|
||||
新K线 = K线.创建普K(
|
||||
标识=self.标识,
|
||||
时间戳=时间戳,
|
||||
开盘价=round(random.uniform(高, 低), max(小数点)),
|
||||
最高价=round(高, max(小数点)),
|
||||
最低价=round(低, max(小数点)),
|
||||
收盘价=round(random.uniform(高, 低), max(小数点)),
|
||||
成交量=成交量 * random.random(),
|
||||
序号=self.序号 + 1,
|
||||
周期=self.周期,
|
||||
)
|
||||
|
||||
# assert 相对方向.分析(self, 新K线) is 方向, (方向, 相对方向.分析(self, 新K线))
|
||||
return 新K线
|
||||
|
||||
|
||||
class 缠论K线:
|
||||
"""经包含处理后的标准化K线,有方向和分型结构标记。
|
||||
@@ -3723,7 +3797,7 @@ class 虚线:
|
||||
段.实_中枢序列 = []
|
||||
段.虚_中枢序列 = []
|
||||
段.合_中枢序列 = []
|
||||
段.基础序列 = 虚线序列
|
||||
段.基础序列 = 虚线序列[:]
|
||||
return 段
|
||||
|
||||
@classmethod
|
||||
@@ -3892,6 +3966,111 @@ class 虚线:
|
||||
return True
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def _计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向):
|
||||
"""计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断)
|
||||
|
||||
:param 普K序列: K线序列
|
||||
:param 方向: 运行方向
|
||||
:return: [柱子背驰, DIF背驰, DEA背驰]
|
||||
"""
|
||||
if 方向 is 相对方向.向上:
|
||||
柱子序列 = []
|
||||
离差值序列 = []
|
||||
信号线序列 = []
|
||||
for k线 in 普K序列:
|
||||
m = k线.macd
|
||||
if m.MACD柱 > 0:
|
||||
柱子序列.append(k线)
|
||||
if m.DIF > 0:
|
||||
离差值序列.append(k线)
|
||||
if m.DEA > 0:
|
||||
信号线序列.append(k线)
|
||||
|
||||
if not 柱子序列:
|
||||
return [False, False, False]
|
||||
最高柱子 = max(柱子序列, key=lambda k线: k线.macd.MACD柱)
|
||||
最高离差值 = max(离差值序列, key=lambda k线: k线.macd.DIF) if 离差值序列 else None
|
||||
最高信号线 = max(信号线序列, key=lambda k线: k线.macd.DEA) if 信号线序列 else None
|
||||
|
||||
结果 = []
|
||||
柱子 = [最高柱子, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.MACD柱 > 柱子[1].macd.MACD柱 and 柱子[0].高 < 柱子[1].高:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高离差值 is not None:
|
||||
柱子 = [最高离差值, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DIF > 柱子[1].macd.DIF and 柱子[0].高 < 柱子[1].高:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高信号线 is not None:
|
||||
柱子 = [最高信号线, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DEA > 柱子[1].macd.DEA and 柱子[0].高 < 柱子[1].高:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
return 结果
|
||||
else:
|
||||
柱子序列 = []
|
||||
离差值序列 = []
|
||||
信号线序列 = []
|
||||
for k线 in 普K序列:
|
||||
m = k线.macd
|
||||
if m.MACD柱 < 0:
|
||||
柱子序列.append(k线)
|
||||
if m.DIF < 0:
|
||||
离差值序列.append(k线)
|
||||
if m.DEA < 0:
|
||||
信号线序列.append(k线)
|
||||
|
||||
if not 柱子序列:
|
||||
return [False, False, False]
|
||||
最高柱子 = max(柱子序列, key=lambda k线: abs(k线.macd.MACD柱))
|
||||
最高离差值 = max(离差值序列, key=lambda k线: abs(k线.macd.DIF)) if 离差值序列 else None
|
||||
最高信号线 = max(信号线序列, key=lambda k线: abs(k线.macd.DEA)) if 信号线序列 else None
|
||||
|
||||
结果 = []
|
||||
柱子 = [最高柱子, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.MACD柱 < 柱子[1].macd.MACD柱 and 柱子[0].低 > 柱子[1].低:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高离差值 is not None:
|
||||
柱子 = [最高离差值, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DIF < 柱子[1].macd.DIF and 柱子[0].低 > 柱子[1].低:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
if 最高信号线 is not None:
|
||||
柱子 = [最高信号线, 普K序列[-1]]
|
||||
柱子.sort(key=lambda k线: k线.时间戳)
|
||||
if 柱子[0].macd.DEA < 柱子[1].macd.DEA and 柱子[0].低 > 柱子[1].低:
|
||||
结果.append(True)
|
||||
else:
|
||||
结果.append(False)
|
||||
else:
|
||||
结果.append(False)
|
||||
|
||||
return 结果
|
||||
|
||||
@classmethod
|
||||
def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向):
|
||||
"""计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断)
|
||||
@@ -4430,8 +4609,7 @@ class 笔:
|
||||
临时分型 = 分型.从缠K序列中获取分型(缠K序列, ck)
|
||||
递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
|
||||
if 分型序列 and 分型序列[-1] is 临时分型:
|
||||
""""""
|
||||
# logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型)
|
||||
logger.warning(f"笔.分析 事后修复错过的笔:{临时分型}, 当前分型: {当前分型}")
|
||||
|
||||
递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置)
|
||||
return 递归层次
|
||||
@@ -4589,14 +4767,14 @@ class 线段特征:
|
||||
return self.标识 # f"{self.标识}:{self.序号}"
|
||||
|
||||
def __str__(self):
|
||||
if not len(self):
|
||||
if not len(self.基础序列):
|
||||
return f"{self.标识}<{self.线段方向}, 空>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>"
|
||||
|
||||
def __repr__(self):
|
||||
if not len(self):
|
||||
if not len(self.基础序列):
|
||||
return f"{self.标识}<{self.线段方向}, 空>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self)}>"
|
||||
return f"{self.标识}<{self.线段方向}, {self.文}, {self.武}, {len(self.基础序列)}>"
|
||||
|
||||
@property
|
||||
def 文(self) -> 分型:
|
||||
@@ -4807,6 +4985,11 @@ class 线段:
|
||||
|
||||
__slots__ = []
|
||||
|
||||
@staticmethod
|
||||
def _索引(序列: list, 项) -> int:
|
||||
"""O(1) index lookup — 序列元素序号连续递增。"""
|
||||
return 项.序号 - 序列[0].序号
|
||||
|
||||
@classmethod
|
||||
def _添加虚线(cls, 段: 虚线, 筆: 虚线):
|
||||
"""向线段中添加一笔
|
||||
@@ -4937,7 +5120,7 @@ class 线段:
|
||||
break
|
||||
|
||||
if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0):
|
||||
段.基础序列[:] = 基础序列[:]
|
||||
段.基础序列[:] = 基础序列
|
||||
else:
|
||||
raise RuntimeError()
|
||||
else:
|
||||
@@ -4954,7 +5137,7 @@ class 线段:
|
||||
return
|
||||
基础序列 = 段.基础序列
|
||||
if 段.前一结束位置 and 段.前一结束位置 in 基础序列:
|
||||
基础序列 = 段.基础序列[段.基础序列.index(段.前一结束位置) - 1 :]
|
||||
基础序列 = 段.基础序列[cls._索引(段.基础序列, 段.前一结束位置) - 1 :]
|
||||
|
||||
特征序列 = 线段特征.静态分析(基础序列, 段.方向, 线段.四象(段), 配置.线段_特征序列忽视老阴老阳)
|
||||
if len(特征序列) >= 3:
|
||||
@@ -5072,7 +5255,7 @@ class 线段:
|
||||
特征后一笔 = 最近特征.基础序列[-1]
|
||||
|
||||
if 特征后一笔 is not None:
|
||||
序号 = 段.基础序列.index(特征后一笔)
|
||||
序号 = cls._索引(段.基础序列, 特征后一笔)
|
||||
if 序号 < len(段.基础序列) - 1:
|
||||
下一笔 = 段.基础序列[序号 + 1]
|
||||
if (段.方向 is 相对方向.向上 and 段.高 <= 下一笔.高) or (段.方向 is 相对方向.向下 and 段.低 >= 下一笔.低):
|
||||
@@ -5091,15 +5274,16 @@ class 线段:
|
||||
:param 序列: 参考序列
|
||||
"""
|
||||
基础序列 = []
|
||||
序列集 = set(序列) if not isinstance(序列, set) else 序列
|
||||
for 元素 in 段.基础序列:
|
||||
if 元素 not in 序列:
|
||||
if 元素 not in 序列集:
|
||||
break
|
||||
if 基础序列:
|
||||
if not 基础序列[-1].之后是(元素):
|
||||
break
|
||||
基础序列.append(元素)
|
||||
|
||||
段.基础序列[:] = 基础序列[:]
|
||||
段.基础序列[:] = 基础序列
|
||||
段.特征序列[2] = None
|
||||
|
||||
@classmethod
|
||||
@@ -5168,16 +5352,17 @@ class 线段:
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: str):
|
||||
def _添加线段(cls, 线段序列: List[虚线], 待添加线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
|
||||
"""内部方法:向线段序列添加新线段
|
||||
|
||||
:param 线段序列: 线段列表
|
||||
:param 待添加线段: 新线段
|
||||
:param 配置: 缠论配置
|
||||
:param 行号: 调用行号
|
||||
:param 层级: 递归层级
|
||||
"""
|
||||
if 线段序列 and not 线段序列[-1].之后是(待添加线段):
|
||||
raise ValueError(f"线段.向序列中添加 不连续[{行号}]", 线段序列[-1].武, 待添加线段.文)
|
||||
raise ValueError(f"线段.向序列中添加 不连续[{行号}, {层级}]", 线段序列[-1].武, 待添加线段.文)
|
||||
待添加线段.模式 = "文武"
|
||||
|
||||
if not 线段序列:
|
||||
@@ -5188,10 +5373,10 @@ class 线段:
|
||||
|
||||
if not 之前线段.特征序列[2] and not 之前线段.短路修正:
|
||||
assert not 待添加线段.短路修正 and 之前线段.特征序列[2][-1] in 待添加线段.基础序列
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段.右 = None", 之前线段)
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段.右 = None", 之前线段)
|
||||
|
||||
if 之前线段.基础序列[-1] not in 待添加线段.基础序列 and not 之前线段.短路修正:
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}], 之前线段[-1] not in 待添加虚线!", 之前线段)
|
||||
raise RuntimeError(f"线段._向序列中添加[{行号}, {层级}], 之前线段[-1] not in 待添加虚线!", 之前线段)
|
||||
|
||||
待添加线段.序号 = 之前线段.序号 + 1
|
||||
待添加线段.前一缺口 = 线段.获取缺口(之前线段) if not 之前线段.短路修正 else None
|
||||
@@ -5204,13 +5389,14 @@ class 线段:
|
||||
# logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线)
|
||||
|
||||
@classmethod
|
||||
def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str):
|
||||
def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: int, 层级: int):
|
||||
"""内部方法:从线段序列弹出最后一个线段
|
||||
|
||||
:param 线段序列: 线段列表
|
||||
:param 待弹出线段: 待弹出的线段
|
||||
:param 配置: 缠论配置
|
||||
:param 行号: 调用行号
|
||||
:param 层级: 递归层级
|
||||
:return: 弹出的线段或None
|
||||
"""
|
||||
if not 线段序列:
|
||||
@@ -5223,7 +5409,7 @@ class 线段:
|
||||
if 右 is not None:
|
||||
结构 = 分型结构.分析(左, 中, 右, True, True)
|
||||
if 结构 in (分型结构.顶, 分型结构.底) and not 相对方向.分析(左.高, 左.低, 中.高, 中.低).是否缺口():
|
||||
logger.warning(f"警告<{行号}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}")
|
||||
logger.warning(f"警告<{行号}, {层级}>] 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {待弹出线段}")
|
||||
|
||||
线段序列.pop()
|
||||
待弹出线段.前一结束位置 = None
|
||||
@@ -5268,7 +5454,7 @@ class 线段:
|
||||
|
||||
# 执行修正
|
||||
序列 = 当前线段.基础序列[:]
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
|
||||
assert 线段序列, "缺口突破: 线段序列为第二次空!"
|
||||
当前线段 = 线段序列[-1]
|
||||
|
||||
@@ -5277,7 +5463,7 @@ class 线段:
|
||||
assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!"
|
||||
当前线段基础序列.extend(序列)
|
||||
|
||||
当前线段.基础序列[:] = 当前线段基础序列[:]
|
||||
当前线段.基础序列[:] = 当前线段基础序列
|
||||
线段._刷新(当前线段, 配置)
|
||||
return True
|
||||
|
||||
@@ -5305,7 +5491,7 @@ class 线段:
|
||||
|
||||
assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!"
|
||||
# 切割基础序列
|
||||
基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :]
|
||||
基础序列 = 当前线段.基础序列[cls._索引(当前线段.基础序列, 贯穿伤) :]
|
||||
|
||||
# 长度条件
|
||||
if not (len(基础序列) == 4 and len(线段序列) >= 2):
|
||||
@@ -5321,19 +5507,25 @@ class 线段:
|
||||
logger.warning(f"[警告<{sys._getframe().f_lineno}, {层级}>]: {当前线段.标识}.修复贯穿伤, 序号:{当前线段.序号} {贯穿伤} {基础序列}") # 异常弹出
|
||||
|
||||
基础序列 = 当前线段.基础序列[:]
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
|
||||
assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!"
|
||||
当前线段 = 线段序列[-1]
|
||||
当前线段.特征序列[2] = None
|
||||
assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"
|
||||
for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]:
|
||||
# assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!"
|
||||
if 当前线段.基础序列[-1] not in 基础序列:
|
||||
logger.error(f"非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!")
|
||||
序号 = 0
|
||||
else:
|
||||
序号 = cls._索引(基础序列, 当前线段.基础序列[-1]) + 1
|
||||
|
||||
for 临时虚线 in 基础序列[序号:]:
|
||||
线段._添加虚线(当前线段, 临时虚线)
|
||||
线段._刷新(当前线段, 配置)
|
||||
当前线段.短路修正 = True
|
||||
|
||||
if 当前线段.特征序列[2] is not None:
|
||||
段 = 虚线.创建线段([左, 中, 右])
|
||||
线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级)
|
||||
段.特征序列[0] = 线段特征.新建([中], 段.方向)
|
||||
|
||||
return True
|
||||
@@ -5379,7 +5571,7 @@ class 线段:
|
||||
# 执行修正
|
||||
当前线段.短路修正 = True
|
||||
新段 = 虚线.创建线段(基础序列)
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
@@ -5422,7 +5614,7 @@ class 线段:
|
||||
# 创建第一个新段(之后基础序列去掉最后3个)
|
||||
新段 = 虚线.创建线段(之后基础序列[:-3])
|
||||
新段.短路修正 = True
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
|
||||
# 根据当前线段的四象决定是否清空前一个缺口
|
||||
if 线段.四象(当前线段) in ("老阴", "老阳"):
|
||||
@@ -5430,7 +5622,7 @@ class 线段:
|
||||
|
||||
# 创建第二个新段(最后3个元素)
|
||||
新段 = 虚线.创建线段(之后基础序列[-3:])
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
|
||||
return True
|
||||
|
||||
@@ -5466,7 +5658,7 @@ class 线段:
|
||||
if not 线段._基础判断(左, 中, 右, 关系序列): # FIXME 首个线段必须有明确方向
|
||||
continue
|
||||
段 = 虚线.创建线段([左, 中, 右])
|
||||
线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 段, 配置, sys._getframe().f_lineno, 层级)
|
||||
段.特征序列[0] = 线段特征.新建([中], 段.方向)
|
||||
break
|
||||
if not 线段序列:
|
||||
@@ -5475,7 +5667,7 @@ class 线段:
|
||||
# -------------------- 2. 清理无效的尾部引用 --------------------
|
||||
while 线段序列 and 线段序列[-1].前一结束位置:
|
||||
if 线段序列[-1].前一结束位置 not in 笔序列:
|
||||
线段._弹出线段(线段序列, 线段序列[-1], 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 线段序列[-1], 配置, sys._getframe().f_lineno, 层级)
|
||||
else:
|
||||
break
|
||||
|
||||
@@ -5487,7 +5679,7 @@ class 线段:
|
||||
线段._序列重置(当前线段, 笔序列)
|
||||
|
||||
if len(当前线段.基础序列) < 3:
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._弹出线段(线段序列, 当前线段, 配置, sys._getframe().f_lineno, 层级)
|
||||
if not 线段序列:
|
||||
return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列)
|
||||
|
||||
@@ -5497,7 +5689,7 @@ class 线段:
|
||||
if 当前线段.特征序列[2] is not None:
|
||||
基础序列 = 线段.分割序列(当前线段)[1]
|
||||
新段 = 虚线.创建线段(基础序列)
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
if 线段.四象(当前线段) in ("老阴", "老阳"):
|
||||
新段.前一缺口 = None
|
||||
|
||||
@@ -5514,9 +5706,10 @@ class 线段:
|
||||
当前线段 = 线段序列[-1]
|
||||
if not 当前线段.基础序列:
|
||||
raise RuntimeError
|
||||
起始索引 = 笔序列.index(当前线段.基础序列[-1]) + 1
|
||||
起始索引 = cls._索引(笔序列, 当前线段.基础序列[-1]) + 1
|
||||
|
||||
for 当前虚线 in 笔序列[起始索引:]:
|
||||
for idx in range(起始索引, len(笔序列)):
|
||||
当前虚线 = 笔序列[idx]
|
||||
当前线段 = 线段序列[-1]
|
||||
四象 = 线段.四象(当前线段)
|
||||
|
||||
@@ -5543,7 +5736,7 @@ class 线段:
|
||||
|
||||
基础序列 = 线段.分割序列(当前线段)[1]
|
||||
新段 = 虚线.创建线段(基础序列)
|
||||
线段._添加线段(线段序列, 新段, 配置, f"{sys._getframe().f_lineno}, {层级}")
|
||||
线段._添加线段(线段序列, 新段, 配置, sys._getframe().f_lineno, 层级)
|
||||
if 四象 in ("老阴", "老阳"):
|
||||
新段.前一缺口 = None
|
||||
|
||||
@@ -5575,15 +5768,16 @@ class 线段:
|
||||
:param 序列: 参考序列
|
||||
"""
|
||||
基础序列 = []
|
||||
序列集 = set(序列) if not isinstance(序列, set) else 序列
|
||||
for 元素 in 段.基础序列:
|
||||
if 元素 not in 序列:
|
||||
if 元素 not in 序列集:
|
||||
break
|
||||
if 基础序列:
|
||||
if not 基础序列[-1].之后是(元素):
|
||||
logger.warning(" 线段._验证序列 数据不连续")
|
||||
break
|
||||
基础序列.append(元素)
|
||||
段.基础序列[:] = 基础序列[:]
|
||||
段.基础序列[:] = 基础序列
|
||||
if len(段.基础序列) % 2 == 0:
|
||||
段.基础序列 and 段.基础序列.pop()
|
||||
|
||||
@@ -5676,7 +5870,7 @@ class 线段:
|
||||
if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号:
|
||||
return None
|
||||
|
||||
序号 = 虚线序列.index(当前线段.基础序列[-1]) + 1
|
||||
序号 = cls._索引(虚线序列, 当前线段.基础序列[-1]) + 1
|
||||
if 序号 >= len(虚线序列):
|
||||
return None
|
||||
|
||||
@@ -5739,7 +5933,7 @@ class 线段:
|
||||
if 当前段.实_中枢序列:
|
||||
if 阳[-1] in 当前段.实_中枢序列[-1].基础序列:
|
||||
# 当前最后一笔在最后一中枢里
|
||||
序号 = 当前段.基础序列.index(当前段.实_中枢序列[-1].基础序列[0])
|
||||
序号 = cls._索引(当前段.基础序列, 当前段.实_中枢序列[-1].基础序列[0])
|
||||
进入段 = 当前段.基础序列[序号 - 1]
|
||||
离开段 = 阳[-1]
|
||||
assert 进入段.序号 < 离开段.序号, (进入段.序号, 离开段.序号)
|
||||
@@ -5793,7 +5987,7 @@ class 线段:
|
||||
笔序列.append(停顿)
|
||||
线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向.顺, 相对方向.逆, 相对方向.同])
|
||||
if 线段序列 and 线段序列[-1].武 is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1:
|
||||
新段 = 虚线.创建线段(线段序列[-1].基础序列[:])
|
||||
新段 = 虚线.创建线段(线段序列[-1].基础序列)
|
||||
新段.序号 = self.序号
|
||||
线段._刷新(新段, 观察员.配置)
|
||||
if 新段.方向 is self.方向:
|
||||
@@ -5984,7 +6178,7 @@ class 中枢:
|
||||
|
||||
:return: 虚线列表
|
||||
"""
|
||||
序列: List = self.基础序列[:]
|
||||
序列: List = self.基础序列.copy()
|
||||
if self.第三买卖线 is not None:
|
||||
序列.append(self.第三买卖线)
|
||||
return 序列
|
||||
@@ -6008,13 +6202,14 @@ class 中枢:
|
||||
"""
|
||||
有效序列 = self.基础序列[:]
|
||||
无效序列 = []
|
||||
序列集 = set(序列)
|
||||
for 元素 in self.基础序列:
|
||||
if 元素 not in 序列:
|
||||
if 元素 not in 序列集:
|
||||
无效序列.append(元素)
|
||||
|
||||
if 无效序列:
|
||||
无效 = 无效序列[0]
|
||||
序号 = self.基础序列.index(无效)
|
||||
序号 = 线段._索引(self.基础序列, 无效)
|
||||
有效序列 = self.基础序列[:序号]
|
||||
|
||||
if len(有效序列) < 3:
|
||||
@@ -6022,14 +6217,14 @@ class 中枢:
|
||||
self.本级_第三买卖线 = None
|
||||
return False
|
||||
|
||||
self.基础序列[:] = 有效序列
|
||||
self.基础序列 = 有效序列
|
||||
|
||||
有效序列 = []
|
||||
for 元素 in self.基础序列:
|
||||
if 相对方向.分析(self.高, self.低, 元素.高, 元素.低).是否缺口():
|
||||
break
|
||||
有效序列.append(元素)
|
||||
self.基础序列[:] = 有效序列
|
||||
self.基础序列 = 有效序列
|
||||
|
||||
if len(self.基础序列) < 3:
|
||||
return False
|
||||
@@ -6201,7 +6396,7 @@ class 中枢:
|
||||
左, 中, 右 = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1]
|
||||
if 中枢.基础检查(左, 中, 右):
|
||||
新中枢 = 中枢.创建(左, 中, 右, 中.级别, 标识)
|
||||
序号 = 虚线序列.index(左)
|
||||
序号 = 线段._索引(虚线序列, 左)
|
||||
if 跳过首部 and (左.序号 == 0 or 序号 == 0):
|
||||
continue # 方便计算走势
|
||||
if 序号 >= 2:
|
||||
@@ -6222,7 +6417,7 @@ class 中枢:
|
||||
中枢._从中枢序列尾部弹出(中枢序列, 当前中枢)
|
||||
return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1)
|
||||
|
||||
序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1
|
||||
序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
|
||||
|
||||
基础序列 = []
|
||||
for 当前虚线 in 虚线序列[序号:]:
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "chanlun"
|
||||
version = "2606.47"
|
||||
version = "2606.73"
|
||||
description = "缠论技术分析库 — Rust 高性能实现"
|
||||
readme = { file = "README.md", content-type = "text/markdown" }
|
||||
license = { file = "LICENSE", content-type = "text/plain" }
|
||||
|
||||
+266
-89
@@ -26,33 +26,20 @@ use crate::kline_py::chan_kline_to_py;
|
||||
use crate::structure_py::{dashed_to_py, fractal_to_py};
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyList, PyType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性
|
||||
static HUB_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<中枢Py>>>> =
|
||||
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
|
||||
// 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换)
|
||||
|
||||
pub(crate) fn hub_to_py(
|
||||
py: Python<'_>, inner: Arc<chanlun::algorithm::hub::中枢>
|
||||
) -> Py<中枢Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) = HUB_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
if let Some(cached) = crate::cache::hub_get(py, key) {
|
||||
return cached;
|
||||
}
|
||||
HUB_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 中枢Py { inner }).unwrap();
|
||||
HUB_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
crate::cache::hub_insert(py, key, &obj);
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -90,16 +77,21 @@ impl 背驰分析Py {
|
||||
方式: &str,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let 进入段_inner = Arc::clone(&进入段.borrow().inner);
|
||||
let 离开段_inner = Arc::clone(&离开段.borrow().inner);
|
||||
let 方式 = 方式.to_string();
|
||||
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = K线序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
chanlun::algorithm::divergence::背驰分析::MACD背驰(
|
||||
&进入段.borrow().inner,
|
||||
&离开段.borrow().inner,
|
||||
&rc_list,
|
||||
方式,
|
||||
)
|
||||
py.detach(move || {
|
||||
chanlun::algorithm::divergence::背驰分析::MACD背驰(
|
||||
&进入段_inner,
|
||||
&离开段_inner,
|
||||
&rc_list,
|
||||
&方式,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -137,15 +129,19 @@ impl 背驰分析Py {
|
||||
普K序列: Vec<Py<K线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let 进入段_inner = Arc::clone(&进入段.borrow().inner);
|
||||
let 离开段_inner = Arc::clone(&离开段.borrow().inner);
|
||||
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
chanlun::algorithm::divergence::背驰分析::全量背驰(
|
||||
&进入段.borrow().inner,
|
||||
&离开段.borrow().inner,
|
||||
&rc_list,
|
||||
)
|
||||
py.detach(move || {
|
||||
chanlun::algorithm::divergence::背驰分析::全量背驰(
|
||||
&进入段_inner,
|
||||
&离开段_inner,
|
||||
&rc_list,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -157,15 +153,19 @@ impl 背驰分析Py {
|
||||
普K序列: Vec<Py<K线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let 进入段_inner = Arc::clone(&进入段.borrow().inner);
|
||||
let 离开段_inner = Arc::clone(&离开段.borrow().inner);
|
||||
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
chanlun::algorithm::divergence::背驰分析::任意背驰(
|
||||
&进入段.borrow().inner,
|
||||
&离开段.borrow().inner,
|
||||
&rc_list,
|
||||
)
|
||||
py.detach(move || {
|
||||
chanlun::algorithm::divergence::背驰分析::任意背驰(
|
||||
&进入段_inner,
|
||||
&离开段_inner,
|
||||
&rc_list,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -178,17 +178,21 @@ impl 背驰分析Py {
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let 进入段_inner = Arc::clone(&进入段.borrow().inner);
|
||||
let 离开段_inner = Arc::clone(&离开段.borrow().inner);
|
||||
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::divergence::背驰分析::配置背驰(
|
||||
&进入段.borrow().inner,
|
||||
&离开段.borrow().inner,
|
||||
&rc_list,
|
||||
&config,
|
||||
))
|
||||
Ok(py.detach(move || {
|
||||
chanlun::algorithm::divergence::背驰分析::配置背驰(
|
||||
&进入段_inner,
|
||||
&离开段_inner,
|
||||
&rc_list,
|
||||
&config,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -200,15 +204,19 @@ impl 背驰分析Py {
|
||||
普K序列: Vec<Py<K线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let 进入段_inner = Arc::clone(&进入段.borrow().inner);
|
||||
let 离开段_inner = Arc::clone(&离开段.borrow().inner);
|
||||
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
chanlun::algorithm::divergence::背驰分析::任选背驰(
|
||||
&进入段.borrow().inner,
|
||||
&离开段.borrow().inner,
|
||||
&rc_list,
|
||||
)
|
||||
py.detach(move || {
|
||||
chanlun::algorithm::divergence::背驰分析::任选背驰(
|
||||
&进入段_inner,
|
||||
&离开段_inner,
|
||||
&rc_list,
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -222,18 +230,169 @@ impl 背驰分析Py {
|
||||
模式: &str,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<bool> {
|
||||
let 进入段_inner = Arc::clone(&进入段.borrow().inner);
|
||||
let 离开段_inner = Arc::clone(&离开段.borrow().inner);
|
||||
let rc_list: Vec<Arc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| k.bind(py).borrow().inner.clone())
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
Ok(chanlun::algorithm::divergence::背驰分析::背驰模式(
|
||||
&进入段.borrow().inner,
|
||||
&离开段.borrow().inner,
|
||||
&rc_list,
|
||||
&config,
|
||||
模式,
|
||||
))
|
||||
let 模式 = 模式.to_string();
|
||||
Ok(py.detach(move || {
|
||||
chanlun::algorithm::divergence::背驰分析::背驰模式(
|
||||
&进入段_inner,
|
||||
&离开段_inner,
|
||||
&rc_list,
|
||||
&config,
|
||||
&模式,
|
||||
)
|
||||
}))
|
||||
}
|
||||
|
||||
// ---- 观察者直传(跳过 Python list→Vec 转换,直接借用观察者内部 &[Arc<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())
|
||||
.collect();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let depth = match 当前分型_rc {
|
||||
let depth = py.detach(|| match 当前分型_rc {
|
||||
Some(fr) => chanlun::algorithm::bi::笔::分析(
|
||||
fr,
|
||||
&mut fr_seq,
|
||||
@@ -356,17 +515,21 @@ impl 笔Py {
|
||||
&config,
|
||||
),
|
||||
None => 递归层次,
|
||||
};
|
||||
});
|
||||
|
||||
// 写回 Python 列表
|
||||
// 写回 Python 列表 (bulk extend)
|
||||
let fr_items: Vec<Py<PyAny>> = fr_seq
|
||||
.iter()
|
||||
.map(|f| fractal_to_py(py, Arc::clone(f)).into_any())
|
||||
.collect();
|
||||
分型序列.call_method0("clear")?;
|
||||
for f in fr_seq {
|
||||
分型序列.call_method1("append", (fractal_to_py(py, f),))?;
|
||||
}
|
||||
分型序列.call_method1("extend", (PyList::new(py, &fr_items)?,))?;
|
||||
let bi_items: Vec<Py<PyAny>> = bi_seq
|
||||
.iter()
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
|
||||
.collect();
|
||||
笔序列.call_method0("clear")?;
|
||||
for d in bi_seq {
|
||||
笔序列.call_method1("append", (dashed_to_py(py, d),))?;
|
||||
}
|
||||
笔序列.call_method1("extend", (PyList::new(py, &bi_items)?,))?;
|
||||
|
||||
Ok(depth)
|
||||
}
|
||||
@@ -552,19 +715,23 @@ impl 线段Py {
|
||||
let rel_list: Vec<chanlun::types::相对方向> = 关系序列
|
||||
.map(|v| v.into_iter().map(|d| d.inner).collect())
|
||||
.unwrap_or(default_rel);
|
||||
chanlun::algorithm::segment::线段::分析(
|
||||
&bi_list,
|
||||
&mut seg_seq,
|
||||
&config,
|
||||
层级,
|
||||
&rel_list,
|
||||
);
|
||||
py.detach(|| {
|
||||
chanlun::algorithm::segment::线段::分析(
|
||||
&bi_list,
|
||||
&mut seg_seq,
|
||||
&config,
|
||||
层级,
|
||||
&rel_list,
|
||||
);
|
||||
});
|
||||
|
||||
// 写回 Python 列表
|
||||
// 写回 Python 列表 (bulk extend)
|
||||
let items: Vec<Py<PyAny>> = seg_seq
|
||||
.iter()
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
|
||||
.collect();
|
||||
线段序列.call_method0("clear")?;
|
||||
for d in seg_seq {
|
||||
线段序列.call_method1("append", (dashed_to_py(py, d),))?;
|
||||
}
|
||||
线段序列.call_method1("extend", (PyList::new(py, &items)?,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -590,13 +757,17 @@ impl 线段Py {
|
||||
}
|
||||
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
chanlun::algorithm::segment::线段::扩展分析(&dash_list, &mut seg_seq, &config);
|
||||
py.detach(|| {
|
||||
chanlun::algorithm::segment::线段::扩展分析(&dash_list, &mut seg_seq, &config);
|
||||
});
|
||||
|
||||
// 写回 Python 列表
|
||||
// 写回 Python 列表 (bulk extend)
|
||||
let items: Vec<Py<PyAny>> = seg_seq
|
||||
.iter()
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)).into_any())
|
||||
.collect();
|
||||
线段序列.call_method0("clear")?;
|
||||
for d in seg_seq {
|
||||
线段序列.call_method1("append", (dashed_to_py(py, d),))?;
|
||||
}
|
||||
线段序列.call_method1("extend", (PyList::new(py, &items)?,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -714,7 +885,7 @@ impl 中枢Py {
|
||||
|
||||
#[getter]
|
||||
fn 标识(&self) -> String {
|
||||
self.inner.标识.read().unwrap().clone()
|
||||
self.inner.标识.read().clone()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -725,7 +896,7 @@ impl 中枢Py {
|
||||
#[getter]
|
||||
fn 基础序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in self.inner.基础序列.read().unwrap().iter() {
|
||||
for d in self.inner.基础序列.read().iter() {
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -736,7 +907,6 @@ impl 中枢Py {
|
||||
self.inner
|
||||
.第三买卖线
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)))
|
||||
}
|
||||
@@ -746,7 +916,6 @@ impl 中枢Py {
|
||||
self.inner
|
||||
.本级_第三买卖线
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)))
|
||||
}
|
||||
@@ -845,13 +1014,17 @@ impl 中枢Py {
|
||||
hub_seq.push(Arc::clone(&h.inner));
|
||||
}
|
||||
let config = 配置.borrow().to_rust_config(配置.py())?;
|
||||
self.inner.获取扩展中枢(&mut hub_seq, &config);
|
||||
py.detach(|| {
|
||||
self.inner.获取扩展中枢(&mut hub_seq, &config);
|
||||
});
|
||||
|
||||
// 写回 Python 列表
|
||||
// 写回 Python 列表 (bulk extend)
|
||||
let items: Vec<Py<PyAny>> = hub_seq
|
||||
.iter()
|
||||
.map(|h| hub_to_py(py, Arc::clone(h)).into_any())
|
||||
.collect();
|
||||
扩展中枢.call_method0("clear")?;
|
||||
for h in hub_seq {
|
||||
扩展中枢.call_method1("append", (hub_to_py(py, h),))?;
|
||||
}
|
||||
扩展中枢.call_method1("extend", (PyList::new(py, &items)?,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -958,13 +1131,17 @@ impl 中枢Py {
|
||||
hub_seq.push(Arc::clone(&h.inner));
|
||||
}
|
||||
|
||||
chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级);
|
||||
py.detach(|| {
|
||||
chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级);
|
||||
});
|
||||
|
||||
// 写回 Python 列表
|
||||
// 写回 Python 列表 (bulk extend)
|
||||
let items: Vec<Py<PyAny>> = hub_seq
|
||||
.iter()
|
||||
.map(|h| hub_to_py(py, Arc::clone(h)).into_any())
|
||||
.collect();
|
||||
中枢序列.call_method0("clear")?;
|
||||
for h in hub_seq {
|
||||
中枢序列.call_method1("append", (hub_to_py(py, h),))?;
|
||||
}
|
||||
中枢序列.call_method1("extend", (PyList::new(py, &items)?,))?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@@ -22,15 +22,16 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use std::sync::RwLock;
|
||||
|
||||
use crate::algorithm_py::hub_to_py;
|
||||
use crate::kline_py::bar_to_py;
|
||||
use crate::structure_py::{dashed_to_py, fractal_to_py, 分型Py};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
use crate::config_py::缠论配置Py;
|
||||
use crate::kline_py::{K线Py, 缠论K线Py};
|
||||
@@ -693,28 +694,27 @@ impl 买卖点Py {
|
||||
#[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)]
|
||||
pub struct 观察者Py {
|
||||
pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::观察者>>>,
|
||||
配置缓存: std::sync::Mutex<Option<Py<缠论配置Py>>>,
|
||||
配置缓存: parking_lot::Mutex<Option<Py<缠论配置Py>>>,
|
||||
最后配置版本: AtomicU64,
|
||||
}
|
||||
|
||||
impl 观察者Py {
|
||||
pub(crate) fn obs(
|
||||
&self,
|
||||
) -> std::sync::RwLockReadGuard<'_, chanlun::business::observer::观察者> {
|
||||
) -> parking_lot::RwLockReadGuard<'_, chanlun::business::observer::观察者> {
|
||||
self.inner
|
||||
.as_ref()
|
||||
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
|
||||
.read()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
|
||||
pub(crate) fn obs_mut(
|
||||
&self,
|
||||
) -> std::sync::RwLockWriteGuard<'_, chanlun::business::observer::观察者> {
|
||||
) -> parking_lot::RwLockWriteGuard<'_, chanlun::business::observer::观察者> {
|
||||
self.inner
|
||||
.as_ref()
|
||||
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
|
||||
.write()
|
||||
.unwrap_or_else(|e| e.into_inner())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -770,7 +770,8 @@ impl 观察者Py {
|
||||
inner: Some(chanlun::business::observer::观察者::new(
|
||||
符号, 周期, config,
|
||||
)),
|
||||
配置缓存: std::sync::Mutex::new(None),
|
||||
配置缓存: parking_lot::Mutex::new(None),
|
||||
最后配置版本: AtomicU64::new(0),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -824,7 +825,7 @@ impl 观察者Py {
|
||||
|
||||
#[getter]
|
||||
fn 配置(&self, py: Python<'_>) -> PyResult<Py<缠论配置Py>> {
|
||||
let mut cache = self.配置缓存.lock().unwrap();
|
||||
let mut cache = self.配置缓存.lock();
|
||||
if let Some(ref cached) = *cache {
|
||||
Ok(cached.clone_ref(py))
|
||||
} else {
|
||||
@@ -838,11 +839,8 @@ impl 观察者Py {
|
||||
#[setter]
|
||||
fn set_配置(&self, value: &Bound<'_, 缠论配置Py>) -> PyResult<()> {
|
||||
let config = value.borrow().to_rust_config(value.py())?;
|
||||
*self.配置缓存.lock() = Some(value.clone().unbind());
|
||||
self.obs_mut().配置 = config;
|
||||
self.配置缓存
|
||||
.lock()
|
||||
.unwrap()
|
||||
.replace(value.clone().unbind());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -861,25 +859,53 @@ impl 观察者Py {
|
||||
|
||||
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现)
|
||||
#[pyo3(name = "_增加原始K线")]
|
||||
fn 增加原始K线_impl(&mut self, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
|
||||
self.obs_mut().增加原始K线((*普K.borrow().inner).clone());
|
||||
Ok(())
|
||||
fn 增加原始K线_impl(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
|
||||
let kline = (*普K.borrow().inner).clone();
|
||||
let obs_arc = slf
|
||||
.borrow()
|
||||
.inner
|
||||
.clone()
|
||||
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造");
|
||||
let symbol = obs_arc.read().符号.clone();
|
||||
let result = slf.py().detach(move || {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
obs_arc.write().增加原始K线(kline);
|
||||
}))
|
||||
});
|
||||
match result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
let msg = e
|
||||
.downcast_ref::<&str>()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| e.downcast_ref::<String>().cloned())
|
||||
.unwrap_or_else(|| "未知算法错误".into());
|
||||
Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
|
||||
"[{symbol}] 算法异常: {msg}"
|
||||
)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写)
|
||||
fn 增加原始K线(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
|
||||
// 同步缓存的 Python 配置到 Rust 观察者(支持 obs.配置 直接修改)
|
||||
// 版本对比,仅在配置变更时同步(支持 obs.配置.field = value 直接修改)
|
||||
{
|
||||
let me = slf.borrow();
|
||||
if let Some(ref cached) = *me.配置缓存.lock().unwrap() {
|
||||
if let Some(ref cached) = *me.配置缓存.lock() {
|
||||
let py = slf.py();
|
||||
if let Ok(config) = cached.bind(py).borrow().to_rust_config(py) {
|
||||
me.obs_mut().配置 = config;
|
||||
let cfg_ref = cached.bind(py).borrow();
|
||||
let current_version = cfg_ref.版本.load(Ordering::Relaxed);
|
||||
if current_version != me.最后配置版本.load(Ordering::Relaxed) {
|
||||
if let Ok(config) = cfg_ref.to_rust_config(py) {
|
||||
me.obs_mut().配置 = config;
|
||||
}
|
||||
me.最后配置版本.store(current_version, Ordering::Relaxed);
|
||||
}
|
||||
}
|
||||
}
|
||||
slf.call_method1("_增加原始K线", (普K,))?;
|
||||
Ok(())
|
||||
// 直接调用 Rust 实现,跳过 Python dispatch
|
||||
Self::增加原始K线_impl(slf, 普K)
|
||||
}
|
||||
|
||||
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线,
|
||||
@@ -931,9 +957,31 @@ impl 观察者Py {
|
||||
|
||||
/// 静态重新分析(内部实现)
|
||||
#[pyo3(name = "_静态重新分析")]
|
||||
fn 静态重新分析_impl(&mut self) -> PyResult<()> {
|
||||
self.obs_mut().静态重新分析();
|
||||
Ok(())
|
||||
fn 静态重新分析_impl(slf: &Bound<'_, Self>) -> PyResult<()> {
|
||||
let obs_arc = slf
|
||||
.borrow()
|
||||
.inner
|
||||
.clone()
|
||||
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造");
|
||||
let symbol = obs_arc.read().符号.clone();
|
||||
let result = slf.py().detach(move || {
|
||||
std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
obs_arc.write().静态重新分析();
|
||||
}))
|
||||
});
|
||||
match result {
|
||||
Ok(()) => Ok(()),
|
||||
Err(e) => {
|
||||
let msg = e
|
||||
.downcast_ref::<&str>()
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| e.downcast_ref::<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.单体分析器 {
|
||||
let obs_py = 观察者Py {
|
||||
inner: Some(obs_rc.clone()),
|
||||
配置缓存: std::sync::Mutex::new(None),
|
||||
配置缓存: parking_lot::Mutex::new(None),
|
||||
最后配置版本: AtomicU64::new(0),
|
||||
};
|
||||
dict.set_item(周期, obs_py)?;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -22,10 +22,11 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use chanlun::warn;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use std::collections::HashMap;
|
||||
use tracing::warn;
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
|
||||
/// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。
|
||||
///
|
||||
@@ -95,6 +96,8 @@ use tracing::warn;
|
||||
#[pyclass(name = "缠论配置", module = "chanlun._chanlun")]
|
||||
pub struct 缠论配置Py {
|
||||
fields: HashMap<String, Py<PyAny>>,
|
||||
缓存: parking_lot::Mutex<Option<chanlun::config::缠论配置>>,
|
||||
pub(crate) 版本: AtomicU64,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
@@ -120,7 +123,11 @@ impl 缠论配置Py {
|
||||
// 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换
|
||||
let config = dict_to_rust_config(&fields)?;
|
||||
let fields = config_to_field_dict(&config)?;
|
||||
Ok(Self { fields })
|
||||
Ok(Self {
|
||||
fields,
|
||||
缓存: parking_lot::Mutex::new(Some(config)),
|
||||
版本: AtomicU64::new(1),
|
||||
})
|
||||
}
|
||||
|
||||
fn __getattr__(&self, name: &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
@@ -135,10 +142,13 @@ impl 缠论配置Py {
|
||||
fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> {
|
||||
if self.fields.contains_key(name) {
|
||||
self.fields.insert(name.to_string(), value.clone().unbind());
|
||||
*self.缓存.lock() = None;
|
||||
self.版本.fetch_add(1, Ordering::Relaxed);
|
||||
// 通过 serde 往返验证类型
|
||||
match dict_to_rust_config(&self.fields) {
|
||||
Ok(config) => {
|
||||
self.fields = config_to_field_dict(&config)?;
|
||||
*self.缓存.lock() = Some(config);
|
||||
Ok(())
|
||||
}
|
||||
Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!(
|
||||
@@ -217,7 +227,11 @@ impl 缠论配置Py {
|
||||
|
||||
let config = dict_to_rust_config(&fields)?;
|
||||
let fields = config_to_field_dict(&config)?;
|
||||
Ok(Self { fields })
|
||||
Ok(Self {
|
||||
fields,
|
||||
缓存: parking_lot::Mutex::new(Some(config)),
|
||||
版本: AtomicU64::new(1),
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -231,7 +245,11 @@ impl 缠论配置Py {
|
||||
fn 不推送(_cls: &Bound<'_, PyType>) -> PyResult<Self> {
|
||||
let config = chanlun::config::缠论配置::default().不推送();
|
||||
let fields = config_to_field_dict(&config)?;
|
||||
Ok(Self { fields })
|
||||
Ok(Self {
|
||||
fields,
|
||||
缓存: parking_lot::Mutex::new(Some(config)),
|
||||
版本: AtomicU64::new(1),
|
||||
})
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
@@ -302,18 +320,31 @@ impl 缠论配置Py {
|
||||
|
||||
let config = dict_to_rust_config(&fields)?;
|
||||
let fields = config_to_field_dict(&config)?;
|
||||
Ok(Self { fields })
|
||||
Ok(Self {
|
||||
fields,
|
||||
缓存: parking_lot::Mutex::new(Some(config)),
|
||||
版本: AtomicU64::new(1),
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn to_rust_config(
|
||||
&self,
|
||||
_py: Python<'_>,
|
||||
) -> PyResult<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> {
|
||||
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
@@ -22,33 +22,10 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::business_py::立体分析器Py;
|
||||
use crate::business_py::观察者Py;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use lru::LruCache;
|
||||
use pyo3::prelude::*;
|
||||
|
||||
/// 缓存辅助宏:在调用点创建静态 LruCache,先查后存
|
||||
macro_rules! with_cache {
|
||||
($cache:ident, $size:literal, $key_expr:expr, $compute:expr) => {{
|
||||
use std::sync::LazyLock;
|
||||
static $cache: LazyLock<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 两种类型)
|
||||
fn 提取时间戳(val: &Bound<'_, PyAny>) -> PyResult<i64> {
|
||||
if let Ok(ts) = val.extract::<i64>() {
|
||||
@@ -129,71 +106,60 @@ fn K线相等(
|
||||
B: &Bound<'_, PyAny>,
|
||||
浮点容差: f64,
|
||||
) -> PyResult<(bool, String)> {
|
||||
with_cache!(
|
||||
C_KLINE,
|
||||
128,
|
||||
(
|
||||
A.as_ptr() as usize,
|
||||
B.as_ptr() as usize,
|
||||
浮点容差.to_bits() as i64
|
||||
),
|
||||
{
|
||||
// 快速路径
|
||||
if let (Ok(a), Ok(b)) = (
|
||||
A.cast::<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缺失属性")));
|
||||
}
|
||||
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 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缺失属性")));
|
||||
}
|
||||
)
|
||||
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线相等 ==========
|
||||
@@ -205,102 +171,91 @@ fn 缠论K线相等(
|
||||
B: &Bound<'_, PyAny>,
|
||||
浮点容差: f64,
|
||||
) -> PyResult<(bool, String)> {
|
||||
with_cache!(
|
||||
C_CHAN_K,
|
||||
4096,
|
||||
(
|
||||
A.as_ptr() as usize,
|
||||
B.as_ptr() as usize,
|
||||
浮点容差.to_bits() as i64
|
||||
),
|
||||
{
|
||||
if let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::kline_py::缠论K线Py>(),
|
||||
B.cast::<crate::kline_py::缠论K线Py>(),
|
||||
) {
|
||||
return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差));
|
||||
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 比对字段 = [
|
||||
"序号",
|
||||
"时间戳",
|
||||
"高",
|
||||
"低",
|
||||
"方向",
|
||||
"分型",
|
||||
"周期",
|
||||
"标识",
|
||||
"分型特征值",
|
||||
"原始起始序号",
|
||||
"原始结束序号",
|
||||
"标的K线",
|
||||
"买卖点信息",
|
||||
];
|
||||
for &字段 in &比对字段 {
|
||||
let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?);
|
||||
if a有 && !b有 {
|
||||
return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
|
||||
}
|
||||
if !a有 && b有 {
|
||||
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
|
||||
}
|
||||
if !a有 && !b有 {
|
||||
continue;
|
||||
}
|
||||
let valA = A.getattr(字段)?;
|
||||
let valB = B.getattr(字段)?;
|
||||
|
||||
if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) {
|
||||
let (ok, m) = r?;
|
||||
if !ok {
|
||||
return Ok((false, format!("{标签}: [{字段}]{m}")));
|
||||
}
|
||||
let 标签 = "缠论K线校验";
|
||||
let 比对字段 = [
|
||||
"序号",
|
||||
"时间戳",
|
||||
"高",
|
||||
"低",
|
||||
"方向",
|
||||
"分型",
|
||||
"周期",
|
||||
"标识",
|
||||
"分型特征值",
|
||||
"原始起始序号",
|
||||
"原始结束序号",
|
||||
"标的K线",
|
||||
"买卖点信息",
|
||||
];
|
||||
for &字段 in &比对字段 {
|
||||
let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?);
|
||||
if a有 && !b有 {
|
||||
return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性")));
|
||||
}
|
||||
if !a有 && b有 {
|
||||
return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性")));
|
||||
}
|
||||
if !a有 && !b有 {
|
||||
} else if 字段 == "标的K线" {
|
||||
if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) {
|
||||
if !r.0 {
|
||||
return Ok((false, r.1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
let valA = A.getattr(字段)?;
|
||||
let valB = B.getattr(字段)?;
|
||||
|
||||
if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) {
|
||||
let (ok, m) = r?;
|
||||
if !ok {
|
||||
return Ok((false, format!("{标签}: [{字段}]{m}")));
|
||||
}
|
||||
} else if 字段 == "标的K线" {
|
||||
if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) {
|
||||
if !r.0 {
|
||||
return Ok((false, r.1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let (eq, msg) = K线相等(&valA, &valB, 浮点容差)?;
|
||||
if !eq {
|
||||
return Ok((false, format!("{标签}: 标的K线子项异常 >> {msg}")));
|
||||
}
|
||||
} else if 字段 == "时间戳" {
|
||||
let a = 提取时间戳(&valA).unwrap_or(0);
|
||||
let b = 提取时间戳(&valB).unwrap_or(0);
|
||||
if a != b {
|
||||
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
|
||||
}
|
||||
} else if 字段 == "方向" || 字段 == "分型" {
|
||||
let sa = valA.str()?.extract::<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>,
|
||||
浮点容差: f64,
|
||||
) -> PyResult<(bool, String)> {
|
||||
with_cache!(
|
||||
C_FRACTAL,
|
||||
4096,
|
||||
(
|
||||
A.as_ptr() as usize,
|
||||
B.as_ptr() as usize,
|
||||
浮点容差.to_bits() as i64
|
||||
),
|
||||
{
|
||||
if let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::structure_py::分型Py>(),
|
||||
B.cast::<crate::structure_py::分型Py>(),
|
||||
) {
|
||||
return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差));
|
||||
if let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::structure_py::分型Py>(),
|
||||
B.cast::<crate::structure_py::分型Py>(),
|
||||
) {
|
||||
return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差));
|
||||
}
|
||||
let 标签 = "分型校验";
|
||||
// Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter
|
||||
for &字段 in &["左", "中", "右"] {
|
||||
let valA = A.getattr(字段)?;
|
||||
let valB = B.getattr(字段)?;
|
||||
if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) {
|
||||
if !r.0 {
|
||||
return Ok((false, r.1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
let 标签 = "分型校验";
|
||||
// Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter
|
||||
for &字段 in &["左", "中", "右"] {
|
||||
let valA = A.getattr(字段)?;
|
||||
let valB = B.getattr(字段)?;
|
||||
if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) {
|
||||
if !r.0 {
|
||||
return Ok((false, r.1));
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let (eq, msg) = 缠论K线相等(&valA, &valB, 浮点容差)?;
|
||||
if !eq {
|
||||
return Ok((false, format!("{标签}: [{字段}]缠论K线子项异常 >> {msg}")));
|
||||
}
|
||||
}
|
||||
for &(字段, 字段别名) in &[
|
||||
("_结构", "结构"),
|
||||
("_时间戳", "时间戳"),
|
||||
("_分型特征值", "分型特征值"),
|
||||
] {
|
||||
// 先尝试 Python 侧的下划线名,再尝试 Rust 侧的无下划线名
|
||||
let valA = 获取属性任意(A, &[字段, 字段别名])?;
|
||||
let valB = 获取属性任意(B, &[字段, 字段别名])?;
|
||||
let (a有, b有) = (valA.is_some(), valB.is_some());
|
||||
if a有 && !b有 {
|
||||
return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性")));
|
||||
}
|
||||
if !a有 && b有 {
|
||||
return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性")));
|
||||
}
|
||||
if !a有 && !b有 {
|
||||
continue;
|
||||
}
|
||||
let valA = valA.unwrap();
|
||||
let valB = valB.unwrap();
|
||||
|
||||
if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) {
|
||||
let (ok, m) = r?;
|
||||
if !ok {
|
||||
return Ok((false, format!("{标签}: [{字段}]{m}")));
|
||||
}
|
||||
} else if 字段 == "_时间戳" {
|
||||
let a = 提取时间戳(&valA).unwrap_or(0);
|
||||
let b = 提取时间戳(&valB).unwrap_or(0);
|
||||
if a != b {
|
||||
return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}")));
|
||||
}
|
||||
} else if 字段 == "_结构" {
|
||||
let sa = valA.str()?.extract::<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>,
|
||||
浮点容差: f64,
|
||||
) -> PyResult<(bool, String)> {
|
||||
with_cache!(
|
||||
C_GAP,
|
||||
4096,
|
||||
(
|
||||
A.as_ptr() as usize,
|
||||
B.as_ptr() as usize,
|
||||
浮点容差.to_bits() as i64
|
||||
),
|
||||
{
|
||||
if let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::types_py::缺口Py>(),
|
||||
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 let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::types_py::缺口Py>(),
|
||||
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!("{标签}: 上下沿价格校验完全一致")))
|
||||
}
|
||||
|
||||
// ========== 线段特征相等 ==========
|
||||
|
||||
#[pyfunction]
|
||||
@@ -462,74 +394,60 @@ fn 线段特征相等(
|
||||
B: &Bound<'_, PyAny>,
|
||||
浮点容差: f64,
|
||||
) -> PyResult<(bool, String)> {
|
||||
with_cache!(
|
||||
C_SEG_FEAT,
|
||||
4096,
|
||||
(
|
||||
A.as_ptr() as usize,
|
||||
B.as_ptr() as usize,
|
||||
浮点容差.to_bits() as i64
|
||||
),
|
||||
{
|
||||
if let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::structure_py::线段特征Py>(),
|
||||
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 let (Ok(a), Ok(b)) = (
|
||||
A.cast::<crate::structure_py::线段特征Py>(),
|
||||
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!("{标签}: 字段与内部虚线序列全部一致")))
|
||||
}
|
||||
|
||||
// ========== 中枢相等 ==========
|
||||
@@ -790,8 +708,8 @@ fn 观察者相等(
|
||||
.inner
|
||||
.clone()
|
||||
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("观察者B 内部为空"))?;
|
||||
let obs_a = arc_a.read().unwrap();
|
||||
let obs_b = arc_b.read().unwrap();
|
||||
let obs_a = arc_a.read();
|
||||
let obs_b = arc_b.read();
|
||||
Ok(obs_a.相等(&obs_b, 容差))
|
||||
}
|
||||
|
||||
|
||||
@@ -290,7 +290,7 @@ impl 相对强弱指数Py {
|
||||
}
|
||||
#[getter]
|
||||
fn RSI历史队列(&self) -> Vec<f64> {
|
||||
self.inner.RSI历史队列.clone()
|
||||
self.inner.RSI历史队列.iter().copied().collect()
|
||||
}
|
||||
|
||||
fn __str__(&self) -> String {
|
||||
@@ -470,11 +470,11 @@ impl 随机指标Py {
|
||||
}
|
||||
#[getter]
|
||||
fn 历史最高价队列(&self) -> Vec<f64> {
|
||||
self.inner.历史最高价队列.clone()
|
||||
self.inner.历史最高价队列.iter().copied().collect()
|
||||
}
|
||||
#[getter]
|
||||
fn 历史最低价队列(&self) -> Vec<f64> {
|
||||
self.inner.历史最低价队列.clone()
|
||||
self.inner.历史最低价队列.iter().copied().collect()
|
||||
}
|
||||
#[getter]
|
||||
fn 前一个RSV(&self) -> Option<f64> {
|
||||
@@ -796,16 +796,12 @@ impl 指标容器Py {
|
||||
}
|
||||
|
||||
fn __getitem__(&self, 名称: &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
if self.包含(名称) {
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py),
|
||||
None => Ok(py.None()),
|
||||
}
|
||||
} else {
|
||||
Err(pyo3::exceptions::PyKeyError::new_err(format!(
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py),
|
||||
None => Err(pyo3::exceptions::PyKeyError::new_err(format!(
|
||||
"指标 '{}' 不存在",
|
||||
名称
|
||||
)))
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -822,16 +818,12 @@ impl 指标容器Py {
|
||||
}
|
||||
return Ok(dict.into());
|
||||
}
|
||||
if self.包含(名称) {
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py),
|
||||
None => Ok(py.None()),
|
||||
}
|
||||
} else {
|
||||
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
|
||||
match self.inner.获取(名称) {
|
||||
Some(v) => 指标值_to_py(v, py),
|
||||
None => Err(pyo3::exceptions::PyAttributeError::new_err(format!(
|
||||
"指标 '{}' 不存在于 指标容器 中",
|
||||
名称
|
||||
)))
|
||||
))),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -917,7 +909,12 @@ impl 均线工具Py {
|
||||
return Ok(sum / (n.max(1)) as f64);
|
||||
}
|
||||
|
||||
let prev_key = format!("SMA_{}", period);
|
||||
let prev_key = {
|
||||
let mut s = String::with_capacity(8);
|
||||
use std::fmt::Write;
|
||||
write!(&mut s, "SMA_{}", period).unwrap();
|
||||
s
|
||||
};
|
||||
// 尝试从前一根K线的均线缓存中读取
|
||||
let prev_cached = 普K序列[n - 2]
|
||||
.bind(py)
|
||||
@@ -925,7 +922,6 @@ impl 均线工具Py {
|
||||
.inner
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.均线()
|
||||
.and_then(|m| m.get(&prev_key))
|
||||
.copied();
|
||||
|
||||
+47
-56
@@ -22,11 +22,11 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use parking_lot::RwLock;
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyBytes, PyDict, PyList, PyType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::config_py::缠论配置Py;
|
||||
@@ -149,7 +149,6 @@ impl K线Py {
|
||||
self.inner
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.macd_cloned()
|
||||
.map(|m| 平滑异同移动平均线Py { inner: m })
|
||||
}
|
||||
@@ -159,7 +158,6 @@ impl K线Py {
|
||||
self.inner
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.rsi_cloned()
|
||||
.map(|r| 相对强弱指数Py { inner: r })
|
||||
}
|
||||
@@ -169,7 +167,6 @@ impl K线Py {
|
||||
self.inner
|
||||
.指标
|
||||
.read()
|
||||
.unwrap()
|
||||
.kdj_cloned()
|
||||
.map(|k| 随机指标Py { inner: k })
|
||||
}
|
||||
@@ -178,7 +175,7 @@ impl K线Py {
|
||||
#[getter]
|
||||
fn 指标(&self) -> 指标容器Py {
|
||||
指标容器Py {
|
||||
inner: self.inner.指标.read().unwrap().clone(),
|
||||
inner: self.inner.指标.read().clone(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -339,6 +336,39 @@ impl K线Py {
|
||||
.take(end_idx - start_idx + 1)
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// 根据当前K线和方向生成下一根K线(用于随机回测)
|
||||
#[pyo3(signature = (方向, 居中 = false))]
|
||||
fn 根据当前K线生成新K线(
|
||||
&self, 方向: &Bound<'_, PyAny>, 居中: bool
|
||||
) -> PyResult<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线 ==========
|
||||
@@ -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(
|
||||
py: Python<'_>,
|
||||
inner: std::sync::Arc<chanlun::kline::bar::K线>,
|
||||
) -> Py<K线Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) = BAR_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
if let Some(cached) = crate::cache::bar_get(py, key) {
|
||||
return cached;
|
||||
}
|
||||
let obj = Py::new(py, K线Py { inner }).unwrap();
|
||||
BAR_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py));
|
||||
crate::cache::bar_insert(py, key, &obj);
|
||||
obj
|
||||
}
|
||||
|
||||
/// 将 Rc<缠论K线> 转为 Py<缠论K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
|
||||
pub(crate) fn chan_kline_to_py(
|
||||
py: Python<'_>,
|
||||
inner: std::sync::Arc<chanlun::kline::chan_kline::缠论K线>,
|
||||
) -> Py<缠论K线Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) = KLINE_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
if let Some(cached) = crate::cache::kline_get(py, key) {
|
||||
return cached;
|
||||
}
|
||||
let obj = Py::new(py, 缠论K线Py::from_rc(inner)).unwrap();
|
||||
KLINE_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
crate::cache::kline_insert(py, key, &obj);
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -462,7 +464,7 @@ impl 缠论K线Py {
|
||||
|
||||
#[getter]
|
||||
fn 方向(&self, py: Python<'_>) -> Py<相对方向Py> {
|
||||
crate::types_py::获取相对方向单例(py, *self.inner.方向.read().unwrap())
|
||||
crate::types_py::获取相对方向单例(py, *self.inner.方向.read())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -470,7 +472,6 @@ impl 缠论K线Py {
|
||||
self.inner
|
||||
.分型
|
||||
.read()
|
||||
.unwrap()
|
||||
.map(|f| crate::types_py::获取分型结构单例(py, f))
|
||||
}
|
||||
|
||||
@@ -501,7 +502,7 @@ impl 缠论K线Py {
|
||||
|
||||
#[getter]
|
||||
fn 标的K线(&self, py: Python<'_>) -> Py<K线Py> {
|
||||
bar_to_py(py, self.inner.标的K线.read().unwrap().clone())
|
||||
bar_to_py(py, self.inner.标的K线.read().clone())
|
||||
}
|
||||
|
||||
/// pandas 兼容 — 返回所有字段构成的字典
|
||||
@@ -556,11 +557,7 @@ impl 缠论K线Py {
|
||||
// 复制买卖点信息到镜像
|
||||
let src_key = Arc::as_ptr(&self.inner) as usize;
|
||||
let dst_key = Arc::as_ptr(&mirror.inner) as usize;
|
||||
let cached_src = BSP_CACHE
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&src_key)
|
||||
.map(|p| p.clone_ref(py));
|
||||
let cached_src = crate::cache::bsp_get(py, src_key);
|
||||
if let Some(cached_src) = cached_src
|
||||
&& let Ok(new_set) = pyo3::types::PySet::empty(py)
|
||||
{
|
||||
@@ -568,7 +565,7 @@ impl 缠论K线Py {
|
||||
let _ = new_set.add(item);
|
||||
}
|
||||
let py_set: Py<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
|
||||
}
|
||||
@@ -595,25 +592,19 @@ impl 缠论K线Py {
|
||||
fn 买卖点信息(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let key = Arc::as_ptr(&self.inner) as usize;
|
||||
// 检查全局缓存
|
||||
let cached = BSP_CACHE.read().unwrap().get(&key).map(|p| p.clone_ref(py));
|
||||
let cached = crate::cache::bsp_get(py, key);
|
||||
if let Some(set) = cached {
|
||||
return Ok(set.into_any());
|
||||
}
|
||||
// 创建新的 PySet,从 Rust HashSet 同步已有内容
|
||||
let set = pyo3::types::PySet::empty(py)?;
|
||||
let bsp_info = self.inner.买卖点信息.read().unwrap();
|
||||
let bsp_info = self.inner.买卖点信息.read();
|
||||
for item in bsp_info.iter() {
|
||||
set.add(item.as_str())?;
|
||||
}
|
||||
drop(bsp_info);
|
||||
BSP_CACHE.write().unwrap().insert(key, set.into());
|
||||
Ok(BSP_CACHE
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.unwrap()
|
||||
.clone_ref(py)
|
||||
.into_any())
|
||||
crate::cache::bsp_insert(py, key, set.into());
|
||||
Ok(crate::cache::bsp_get(py, key).unwrap().into_any())
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
|
||||
+73
-15
@@ -102,6 +102,7 @@ fn init_tracing() {
|
||||
|
||||
mod algorithm_py;
|
||||
mod business_py;
|
||||
pub(crate) mod cache;
|
||||
mod config_py;
|
||||
mod equality_py;
|
||||
mod indicators_py;
|
||||
@@ -139,9 +140,7 @@ fn get_log_level() -> &'static str {
|
||||
级别数字转名称(LOG_LEVEL.load(Ordering::Relaxed))
|
||||
}
|
||||
|
||||
/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off")
|
||||
///
|
||||
/// 设为 "off" 可完全关闭日志输出。
|
||||
/// 设置日志级别 — 自动启用日志,同步更新 tracing subscriber
|
||||
#[pyfunction]
|
||||
fn set_log_level(level: &str) -> PyResult<()> {
|
||||
let 数字 = 级别名称转数字(level).ok_or_else(|| {
|
||||
@@ -150,32 +149,91 @@ fn set_log_level(level: &str) -> PyResult<()> {
|
||||
level
|
||||
))
|
||||
})?;
|
||||
|
||||
let guard = 过滤器句柄锁
|
||||
.get()
|
||||
.ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("日志系统尚未初始化"))?;
|
||||
let handle = guard.lock().unwrap();
|
||||
let 名称 = 级别数字转名称(数字);
|
||||
let filter = tracing_subscriber::EnvFilter::new(名称);
|
||||
handle
|
||||
.reload(filter)
|
||||
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("切换日志级别失败"))?;
|
||||
|
||||
LOG_LEVEL.store(数字, Ordering::Relaxed);
|
||||
chanlun::log::日志启用.store(数字 < 5, Ordering::Relaxed);
|
||||
// 同步更新 tracing subscriber
|
||||
if let Some(guard) = 过滤器句柄锁.get() {
|
||||
let handle = guard.lock().unwrap();
|
||||
let 名称 = 级别数字转名称(数字);
|
||||
let filter = tracing_subscriber::EnvFilter::new(名称);
|
||||
let _ = handle.reload(filter);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取日志输出模式 ("off", "simple", "tracing")
|
||||
#[pyfunction]
|
||||
fn get_log_mode() -> &'static str {
|
||||
match chanlun::log::get_log_mode() {
|
||||
0 => "off",
|
||||
1 => "simple",
|
||||
2 => "tracing",
|
||||
_ => "unknown",
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置日志输出模式(必须在任何日志输出之前调用)
|
||||
/// - "off": 不输出
|
||||
/// - "simple": 直接 eprintln/println(默认)
|
||||
/// - "tracing": 带时间戳和格式化的 tracing subscriber
|
||||
#[pyfunction]
|
||||
fn set_log_mode(mode: &str) -> PyResult<()> {
|
||||
let m = match mode.to_lowercase().as_str() {
|
||||
"off" | "0" => 0u8,
|
||||
"simple" | "on" | "1" => 1u8,
|
||||
"tracing" | "2" => 2u8,
|
||||
_ => {
|
||||
return Err(pyo3::exceptions::PyValueError::new_err(
|
||||
"无效日志模式,有效值: 'off', 'simple', 'tracing'",
|
||||
));
|
||||
}
|
||||
};
|
||||
if m == 2 {
|
||||
init_tracing();
|
||||
}
|
||||
chanlun::log::set_log_mode(m);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取缓存模式 ("thread_local" 或 "global")
|
||||
#[pyfunction]
|
||||
fn get_cache_mode() -> &'static str {
|
||||
match crate::cache::peek_mode().unwrap_or(&crate::cache::CacheMode::ThreadLocal) {
|
||||
crate::cache::CacheMode::ThreadLocal => "thread_local",
|
||||
crate::cache::CacheMode::Global => "global",
|
||||
}
|
||||
}
|
||||
|
||||
/// 设置缓存模式(必须在创建任何观察者之前调用)
|
||||
#[pyfunction]
|
||||
fn set_cache_mode(mode: &str) -> PyResult<()> {
|
||||
let m = match mode.to_lowercase().as_str() {
|
||||
"thread_local" | "local" => crate::cache::CacheMode::ThreadLocal,
|
||||
"global" => crate::cache::CacheMode::Global,
|
||||
_ => {
|
||||
return Err(pyo3::exceptions::PyValueError::new_err(
|
||||
"无效缓存模式,有效值: 'thread_local', 'global'",
|
||||
));
|
||||
}
|
||||
};
|
||||
crate::cache::set_mode(m).map_err(|e| pyo3::exceptions::PyRuntimeError::new_err(e))
|
||||
}
|
||||
|
||||
/// 缠论技术分析库 — Rust 高性能实现
|
||||
#[pymodule]
|
||||
/// 缠论技术分析库 — Rust 高性能实现
|
||||
fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
init_tracing();
|
||||
chanlun::log::init_from_env();
|
||||
m.add_function(wrap_pyfunction!(get_分型模式, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_分型模式, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(get_扩展线段模式, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_扩展线段模式, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(get_log_level, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_log_level, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(get_log_mode, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_log_mode, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(get_cache_mode, m)?)?;
|
||||
m.add_function(wrap_pyfunction!(set_cache_mode, m)?)?;
|
||||
// 阶段 1: 枚举和基础类型
|
||||
types_py::register(m)?;
|
||||
// 阶段 2: 配置
|
||||
|
||||
@@ -24,9 +24,7 @@
|
||||
|
||||
use pyo3::prelude::*;
|
||||
use pyo3::types::{PyDict, PyType};
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use std::sync::atomic::Ordering;
|
||||
|
||||
use crate::algorithm_py::hub_to_py;
|
||||
@@ -35,37 +33,18 @@ use crate::kline_py::{K线Py, bar_to_py, 缠论K线Py};
|
||||
|
||||
// ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ----
|
||||
|
||||
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性
|
||||
static FRACTAL_IDENTITY: std::sync::LazyLock<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()));
|
||||
// 缓存通过 crate::cache 模块管理(支持 thread_local / global 运行时切换)
|
||||
|
||||
pub(crate) fn fractal_to_py(
|
||||
py: Python<'_>,
|
||||
inner: Arc<chanlun::structure::fractal_obj::分型>,
|
||||
) -> Py<分型Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) = FRACTAL_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
if let Some(cached) = crate::cache::fractal_get(py, key) {
|
||||
return cached;
|
||||
}
|
||||
// 清理 refcnt==1 的过期条目(仅缓存持有,Python 侧已无引用)
|
||||
FRACTAL_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 分型Py { inner }).unwrap();
|
||||
FRACTAL_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
crate::cache::fractal_insert(py, key, &obj);
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -74,23 +53,11 @@ pub(crate) fn dashed_to_py(
|
||||
inner: Arc<chanlun::structure::dash_line::虚线>,
|
||||
) -> Py<虚线Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) = DASHED_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
if let Some(cached) = crate::cache::dashed_get(py, key) {
|
||||
return cached;
|
||||
}
|
||||
DASHED_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 虚线Py { inner }).unwrap();
|
||||
DASHED_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
crate::cache::dashed_insert(py, key, &obj);
|
||||
obj
|
||||
}
|
||||
|
||||
@@ -98,25 +65,7 @@ pub(crate) fn segfeat_to_py(
|
||||
py: Python<'_>,
|
||||
inner: Arc<chanlun::structure::segment_feat::线段特征>,
|
||||
) -> Py<线段特征Py> {
|
||||
let key = Arc::as_ptr(&inner) as usize;
|
||||
if let Some(cached) = SEGFEAT_IDENTITY
|
||||
.read()
|
||||
.unwrap()
|
||||
.get(&key)
|
||||
.map(|p| p.clone_ref(py))
|
||||
{
|
||||
return cached;
|
||||
}
|
||||
SEGFEAT_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.retain(|_, v| v.get_refcnt(py) > 1);
|
||||
let obj = Py::new(py, 线段特征Py { inner }).unwrap();
|
||||
SEGFEAT_IDENTITY
|
||||
.write()
|
||||
.unwrap()
|
||||
.insert(key, obj.clone_ref(py));
|
||||
obj
|
||||
Py::new(py, 线段特征Py { inner }).unwrap()
|
||||
}
|
||||
|
||||
use crate::types_py::{分型结构Py, 相对方向Py, 缺口Py};
|
||||
@@ -393,7 +342,7 @@ impl 虚线Py {
|
||||
|
||||
#[getter]
|
||||
fn 标识(&self) -> String {
|
||||
self.inner.标识.read().unwrap().clone()
|
||||
self.inner.标识.read().clone()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -413,7 +362,7 @@ impl 虚线Py {
|
||||
|
||||
#[getter]
|
||||
fn 武(&self, py: Python<'_>) -> Py<分型Py> {
|
||||
fractal_to_py(py, Arc::clone(&*self.inner.武.read().unwrap()))
|
||||
fractal_to_py(py, Arc::clone(&*self.inner.武.read()))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -423,7 +372,7 @@ impl 虚线Py {
|
||||
|
||||
#[getter]
|
||||
fn 模式(&self) -> String {
|
||||
self.inner.模式.read().unwrap().clone()
|
||||
self.inner.模式.read().clone()
|
||||
}
|
||||
|
||||
#[getter(_特征序列_显示)]
|
||||
@@ -439,7 +388,7 @@ impl 虚线Py {
|
||||
#[getter]
|
||||
fn 特征序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for item in self.inner.特征序列.read().unwrap().iter() {
|
||||
for item in self.inner.特征序列.read().iter() {
|
||||
match item {
|
||||
Some(feat) => list.append(segfeat_to_py(py, Arc::clone(feat)))?,
|
||||
None => {
|
||||
@@ -460,18 +409,13 @@ impl 虚线Py {
|
||||
self.inner
|
||||
.确认K线
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 前一缺口(&self) -> Option<缺口Py> {
|
||||
self.inner
|
||||
.前一缺口
|
||||
.read()
|
||||
.unwrap()
|
||||
.map(|q| 缺口Py { inner: q })
|
||||
self.inner.前一缺口.read().map(|q| 缺口Py { inner: q })
|
||||
}
|
||||
|
||||
#[getter]
|
||||
@@ -479,7 +423,6 @@ impl 虚线Py {
|
||||
self.inner
|
||||
.前一结束位置
|
||||
.read()
|
||||
.unwrap()
|
||||
.as_ref()
|
||||
.map(|d| dashed_to_py(py, Arc::clone(d)))
|
||||
}
|
||||
@@ -489,7 +432,7 @@ impl 虚线Py {
|
||||
#[getter]
|
||||
fn 基础序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in self.inner.基础序列.read().unwrap().iter() {
|
||||
for d in self.inner.基础序列.read().iter() {
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -498,7 +441,7 @@ impl 虚线Py {
|
||||
#[getter]
|
||||
fn 实_中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for h in self.inner.实_中枢序列.read().unwrap().iter() {
|
||||
for h in self.inner.实_中枢序列.read().iter() {
|
||||
list.append(hub_to_py(py, Arc::clone(h)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -507,7 +450,7 @@ impl 虚线Py {
|
||||
#[getter]
|
||||
fn 虚_中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for h in self.inner.虚_中枢序列.read().unwrap().iter() {
|
||||
for h in self.inner.虚_中枢序列.read().iter() {
|
||||
list.append(hub_to_py(py, Arc::clone(h)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -516,7 +459,7 @@ impl 虚线Py {
|
||||
#[getter]
|
||||
fn 合_中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for h in self.inner.合_中枢序列.read().unwrap().iter() {
|
||||
for h in self.inner.合_中枢序列.read().iter() {
|
||||
list.append(hub_to_py(py, Arc::clone(h)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -528,7 +471,7 @@ impl 虚线Py {
|
||||
/// 笔序列
|
||||
fn 笔序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for d in self.inner.基础序列.read().unwrap().iter() {
|
||||
for d in self.inner.基础序列.read().iter() {
|
||||
list.append(dashed_to_py(py, Arc::clone(d)))?;
|
||||
}
|
||||
Ok(list.into())
|
||||
@@ -1005,12 +948,12 @@ impl 线段特征Py {
|
||||
|
||||
#[getter]
|
||||
fn 标识(&self) -> String {
|
||||
self.inner.标识.read().unwrap().clone()
|
||||
self.inner.标识.read().clone()
|
||||
}
|
||||
|
||||
#[setter]
|
||||
fn set_标识(&self, value: String) {
|
||||
*self.inner.标识.write().unwrap() = value;
|
||||
*self.inner.标识.write() = value;
|
||||
}
|
||||
|
||||
#[getter]
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use parking_lot::Mutex;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Mutex;
|
||||
|
||||
use pyo3::basic::CompareOp;
|
||||
use pyo3::prelude::*;
|
||||
@@ -37,7 +37,7 @@ pub fn 获取分型结构单例(
|
||||
py: Python<'_>,
|
||||
inner: chanlun::types::分型结构,
|
||||
) -> Py<分型结构Py> {
|
||||
let mut guard = 分型结构_单例缓存.lock().unwrap();
|
||||
let mut guard = 分型结构_单例缓存.lock();
|
||||
if let Some(ref map) = *guard {
|
||||
return map[&(inner as u8)].clone_ref(py);
|
||||
}
|
||||
@@ -67,7 +67,7 @@ pub fn 获取相对方向单例(
|
||||
py: Python<'_>,
|
||||
inner: chanlun::types::相对方向,
|
||||
) -> Py<相对方向Py> {
|
||||
let mut guard = 相对方向_单例缓存.lock().unwrap();
|
||||
let mut guard = 相对方向_单例缓存.lock();
|
||||
if let Some(ref map) = *guard {
|
||||
return map[&(inner as u8)].clone_ref(py);
|
||||
}
|
||||
@@ -312,6 +312,22 @@ impl 相对方向Py {
|
||||
chanlun::types::相对方向::分析(前高, 前低, 后高, 后低),
|
||||
)
|
||||
}
|
||||
|
||||
/// 从可选方向序列中随机选取指定数量
|
||||
#[classmethod]
|
||||
#[pyo3(signature = (数量, 可选方向, 可重复 = true))]
|
||||
fn 从序列中机选(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
数量: usize,
|
||||
可选方向: Vec<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()
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 分型结构 ==========
|
||||
|
||||
@@ -2776,5 +2776,74 @@ class Test缠论配置双端一致(unittest.TestCase):
|
||||
self.assertFalse(diff_py[k], f"不推送差异字段 {k} 应为 False")
|
||||
|
||||
|
||||
class Test生成K线双端一致(unittest.TestCase):
|
||||
"""根据当前K线生成新K线 Rust vs chan.py 输出一致."""
|
||||
|
||||
def test_生成K线_居中各方向双端一致(self):
|
||||
"""居中模式下各方向生成K线双端OHLC一致(居中=确定性输出)"""
|
||||
import chanlun
|
||||
from chanlun import chan
|
||||
|
||||
bar_rs = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300)
|
||||
bar_py = chan.K线.创建普K("btcusd", chan.转化为时间戳(1000000000), 50000, 50200, 49800, 50100, 100, 0, 300)
|
||||
|
||||
directions = {
|
||||
"向上": 0,
|
||||
"向下": 1,
|
||||
"向上缺口": 2,
|
||||
"向下缺口": 3,
|
||||
"衔接向上": 4,
|
||||
"衔接向下": 5,
|
||||
}
|
||||
py_dirs = {
|
||||
"向上": chan.相对方向.向上,
|
||||
"向下": chan.相对方向.向下,
|
||||
"向上缺口": chan.相对方向.向上缺口,
|
||||
"向下缺口": chan.相对方向.向下缺口,
|
||||
"衔接向上": chan.相对方向.衔接向上,
|
||||
"衔接向下": chan.相对方向.衔接向下,
|
||||
}
|
||||
|
||||
for name in directions:
|
||||
new_rs = bar_rs.根据当前K线生成新K线(directions[name], 居中=True)
|
||||
new_py = bar_py.根据当前K线生成新K线(py_dirs[name], 居中=True)
|
||||
|
||||
# 居中模式下,高和低是确定性的(偏移=高低差*0.5)
|
||||
# 开盘价/收盘价/成交量含随机,不比较
|
||||
tol = 1.0 + abs(new_py.高) * 1e-6
|
||||
self.assertAlmostEqual(new_rs.高, new_py.高, delta=tol, msg=f"{name}: 最高价不一致 (R={new_rs.高}, P={new_py.高})")
|
||||
self.assertAlmostEqual(new_rs.低, new_py.低, delta=tol, msg=f"{name}: 最低价不一致 (R={new_rs.低}, P={new_py.低})")
|
||||
|
||||
# 时间戳和序号
|
||||
self.assertEqual(new_rs.序号, new_py.序号)
|
||||
self.assertEqual(int(new_rs.时间戳), int(chan.转化为时间戳_数字(new_py.时间戳)))
|
||||
|
||||
def test_生成K线_居中外推验证(self):
|
||||
"""居中向上生成:新K线的高/低应整体高于原K线."""
|
||||
import chanlun
|
||||
|
||||
bar = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300)
|
||||
new = bar.根据当前K线生成新K线(0, 居中=True)
|
||||
|
||||
self.assertGreater(new.高, bar.高, "向上:新高应高于原高")
|
||||
self.assertGreater(new.低, bar.低, "向上:新低应高于原低")
|
||||
|
||||
new_down = bar.根据当前K线生成新K线(1, 居中=True)
|
||||
self.assertLess(new_down.高, bar.高, "向下:新高应低于原高")
|
||||
self.assertLess(new_down.低, bar.低, "向下:新低应低于原低")
|
||||
|
||||
def test_生成K线_衔接验证(self):
|
||||
"""衔接向上:新K线的低 = 原K线的高(无缝衔接)."""
|
||||
import chanlun
|
||||
|
||||
bar = chanlun.K线.创建普K("btcusd", 1000000000, 50000, 50200, 49800, 50100, 100, 0, 300)
|
||||
new = bar.根据当前K线生成新K线(4, 居中=True)
|
||||
|
||||
self.assertAlmostEqual(new.低, bar.高, delta=1e-6, msg="衔接向上:新低应=原高")
|
||||
|
||||
new_down = bar.根据当前K线生成新K线(5, 居中=True)
|
||||
self.assertAlmostEqual(new_down.高, bar.低, delta=1e-6, msg="衔接向下:新高应=原低")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
+3
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "26.6.3"
|
||||
version = "26.6.4"
|
||||
edition = "2024"
|
||||
license = "MIT"
|
||||
description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。"
|
||||
@@ -17,6 +17,6 @@ serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
byteorder = "1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
cached = "1"
|
||||
parking_lot = "0.12"
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.3"
|
||||
fastrand = "2"
|
||||
|
||||
+39
-42
@@ -29,9 +29,9 @@ use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::dash_line::虚线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::{分型结构, 相对方向};
|
||||
use crate::{error, warn};
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::{error, warn};
|
||||
|
||||
/// 笔 — 从分型生成笔的算法集合(静态方法命名空间)
|
||||
pub struct 笔;
|
||||
@@ -54,8 +54,7 @@ impl 笔 {
|
||||
|
||||
if let (Some(高点), Some(低点)) = (&实际高点, &实际低点) {
|
||||
let 原始数量 = 1
|
||||
+ (低点.标的K线.read().unwrap().序号 - 高点.标的K线.read().unwrap().序号)
|
||||
.unsigned_abs() as usize;
|
||||
+ (低点.标的K线.read().序号 - 高点.标的K线.read().序号).unsigned_abs() as usize;
|
||||
if 原始数量 >= 配置.笔内元素数量 as usize {
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
@@ -76,8 +75,8 @@ impl 笔 {
|
||||
&& let (Some(高_k), Some(低_k)) = (&实际高点, &实际低点)
|
||||
{
|
||||
let 原始数量 = 1
|
||||
+ (低_k.标的K线.read().unwrap().序号 - 高_k.标的K线.read().unwrap().序号)
|
||||
.unsigned_abs() as usize;
|
||||
+ (低_k.标的K线.read().序号 - 高_k.标的K线.read().序号).unsigned_abs()
|
||||
as usize;
|
||||
// 向上笔
|
||||
if 筆.方向().是否向上()
|
||||
&& 低_k.低.get() < 筆.低()
|
||||
@@ -219,7 +218,7 @@ impl 笔 {
|
||||
/// 判断笔的相对关系是否合理
|
||||
pub fn _相对关系(筆: &虚线, 配置: &缠论配置) -> bool {
|
||||
let 文分型 = &筆.文;
|
||||
let 武分型 = 筆.武.read().unwrap();
|
||||
let 武分型 = 筆.武.read();
|
||||
|
||||
let 相对关系 = if 配置.笔内起始分型包含整笔 {
|
||||
let 文中_rc = Arc::clone(&文分型.中);
|
||||
@@ -261,8 +260,8 @@ impl 笔 {
|
||||
武分型.中.低.get(),
|
||||
);
|
||||
if 配置.笔内原始K线包含整笔 {
|
||||
let 文标的 = 文分型.中.标的K线.read().unwrap();
|
||||
let 武标的 = 武分型.中.标的K线.read().unwrap();
|
||||
let 文标的 = 文分型.中.标的K线.read();
|
||||
let 武标的 = 武分型.中.标的K线.read();
|
||||
if crate::types::相对方向::分析(文标的.高, 文标的.低, 武标的.高, 武标的.低)
|
||||
.是否包含()
|
||||
{
|
||||
@@ -280,10 +279,7 @@ impl 笔 {
|
||||
|
||||
/// 以文会友 — 根据起点分型找笔
|
||||
pub fn 以文会友(笔序列: &[Arc<虚线>], 文: &Arc<分型>) -> Option<Arc<虚线>> {
|
||||
笔序列
|
||||
.iter()
|
||||
.find(|b| Arc::as_ptr(&b.文) == Arc::as_ptr(文))
|
||||
.cloned()
|
||||
笔序列.iter().find(|b| Arc::ptr_eq(&b.文, 文)).cloned()
|
||||
}
|
||||
|
||||
/// 以武会友 — 根据终点分型找笔
|
||||
@@ -291,7 +287,7 @@ impl 笔 {
|
||||
笔序列
|
||||
.iter()
|
||||
.rev()
|
||||
.find(|b| Arc::as_ptr(&*b.武.read().unwrap()) == Arc::as_ptr(武))
|
||||
.find(|b| Arc::ptr_eq(&*b.武.read(), 武))
|
||||
.cloned()
|
||||
}
|
||||
|
||||
@@ -305,8 +301,7 @@ impl 笔 {
|
||||
for b in 笔序列.iter().rev() {
|
||||
// Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号
|
||||
if b.文.中.序号.load(Ordering::Relaxed) - 偏移 <= 缠K.序号.load(Ordering::Relaxed)
|
||||
&& 缠K.序号.load(Ordering::Relaxed)
|
||||
<= b.武.read().unwrap().中.序号.load(Ordering::Relaxed)
|
||||
&& 缠K.序号.load(Ordering::Relaxed) <= b.武.read().中.序号.load(Ordering::Relaxed)
|
||||
&& b.文.中.周期 == 缠K.周期
|
||||
&& b.文.中.标识 == 缠K.标识
|
||||
{
|
||||
@@ -323,7 +318,7 @@ impl 笔 {
|
||||
let 旧分型 = 分型序列.pop();
|
||||
if let (Some(旧笔), Some(旧分型)) = (笔序列.pop(), 旧分型) {
|
||||
assert!(
|
||||
Arc::as_ptr(&旧笔.武.read().unwrap()) == Arc::as_ptr(&旧分型),
|
||||
Arc::ptr_eq(&旧笔.武.read(), &旧分型),
|
||||
"最后一笔终点错误{}",
|
||||
行号
|
||||
);
|
||||
@@ -388,7 +383,7 @@ impl 笔 {
|
||||
// Python line 2343-2348: 笔弱化模式
|
||||
if 配置.笔弱化 && !笔序列.is_empty() {
|
||||
let 前一笔 = 笔序列.last().unwrap();
|
||||
let 前一笔缠K数 = 前一笔.武.read().unwrap().中.序号.load(Ordering::Relaxed)
|
||||
let 前一笔缠K数 = 前一笔.武.read().中.序号.load(Ordering::Relaxed)
|
||||
- 前一笔.文.中.序号.load(Ordering::Relaxed)
|
||||
+ 1;
|
||||
if 前一笔缠K数 == 3 {
|
||||
@@ -434,7 +429,7 @@ impl 笔 {
|
||||
|
||||
// Python line 2359-2367: 文官调整
|
||||
if let Some(ref 文官_k) = 文官
|
||||
&& Arc::as_ptr(文官_k) != Arc::as_ptr(&之前分型.中)
|
||||
&& !Arc::ptr_eq(文官_k, &之前分型.中)
|
||||
&& let Some(临时分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, 文官_k)
|
||||
{
|
||||
@@ -476,7 +471,7 @@ impl 笔 {
|
||||
|
||||
if Self::_相对关系(&当前笔, 配置)
|
||||
&& let Some(ref 武将_k) = 武将
|
||||
&& Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中)
|
||||
&& Arc::ptr_eq(武将_k, &当前分型.中)
|
||||
{
|
||||
// 直接添加(对照 Python _添加新笔:直接 append)
|
||||
Self::_添加新笔(分型序列, 笔序列, 当前分型, 当前笔, line!());
|
||||
@@ -490,7 +485,7 @@ impl 笔 {
|
||||
_ => Self::_次高(&基础序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
if let Some(ref 武将_k) = 武将
|
||||
&& Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中)
|
||||
&& Arc::ptr_eq(武将_k, &当前分型.中)
|
||||
&& Self::_相对关系(&当前笔, 配置)
|
||||
{
|
||||
Self::_添加新笔(分型序列, 笔序列, 当前分型, 当前笔, line!());
|
||||
@@ -557,13 +552,12 @@ impl 笔 {
|
||||
if !分型序列.is_empty()
|
||||
&& Arc::as_ptr(分型序列.last().unwrap())
|
||||
== Arc::as_ptr(&临时分型_rc)
|
||||
&& let Some(武_idx) = 缠K序列
|
||||
.iter()
|
||||
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(武将_k))
|
||||
&& let Some(武_idx) =
|
||||
缠K序列.iter().position(|k| Arc::ptr_eq(k, 武将_k))
|
||||
{
|
||||
for ck in &缠K序列[武_idx..] {
|
||||
if (*ck.分型.read().unwrap() == Some(分型结构::底)
|
||||
|| *ck.分型.read().unwrap() == Some(分型结构::顶))
|
||||
if (*ck.分型.read() == Some(分型结构::底)
|
||||
|| *ck.分型.read() == Some(分型结构::顶))
|
||||
&& let Some(错过分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, ck)
|
||||
{
|
||||
@@ -577,6 +571,15 @@ impl 笔 {
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
if !分型序列.is_empty()
|
||||
&& Arc::as_ptr(分型序列.last().unwrap())
|
||||
== Arc::as_ptr(&错过分型_rc)
|
||||
{
|
||||
warn!(
|
||||
"笔.分析 事后修复错过的笔:{}, 当前分型: {}",
|
||||
错过分型_rc, 当前分型
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -649,14 +652,10 @@ impl 笔 {
|
||||
新筆
|
||||
.序号
|
||||
.store(前一笔.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
if 新筆.武.read().unwrap().左.is_none() || 新筆.武.read().unwrap().右.is_none()
|
||||
{
|
||||
if 新筆.武.read().左.is_none() || 新筆.武.read().右.is_none() {
|
||||
新筆.有效性.store(false, Ordering::Relaxed);
|
||||
}
|
||||
if matches!(
|
||||
前一笔.武.read().unwrap().结构(),
|
||||
分型结构::上 | 分型结构::下
|
||||
) {
|
||||
if matches!(前一笔.武.read().结构(), 分型结构::上 | 分型结构::下) {
|
||||
error!("_添加新笔[{}] 出现无效分型 {}", 行号, 前一笔);
|
||||
}
|
||||
}
|
||||
@@ -676,7 +675,7 @@ impl 笔 {
|
||||
Self::_实际低点(&基础序列, 配置.笔内相同终点取舍),
|
||||
)
|
||||
&& Arc::ptr_eq(&筆.文.中, &实际高)
|
||||
&& Arc::ptr_eq(&筆.武.read().unwrap().中, &实际低)
|
||||
&& Arc::ptr_eq(&筆.武.read().中, &实际低)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -686,7 +685,7 @@ impl 笔 {
|
||||
Self::_实际高点(&基础序列, 配置.笔内相同终点取舍),
|
||||
)
|
||||
&& Arc::ptr_eq(&筆.文.中, &实际低)
|
||||
&& Arc::ptr_eq(&筆.武.read().unwrap().中, &实际高)
|
||||
&& Arc::ptr_eq(&筆.武.read().中, &实际高)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
@@ -696,9 +695,9 @@ impl 笔 {
|
||||
|
||||
/// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合
|
||||
pub fn 获取所有停顿位置(筆: &虚线, 观察员: &观察者) -> Vec<虚线> {
|
||||
let mut 笔序列 = Vec::new();
|
||||
let 文 = Arc::clone(&筆.文);
|
||||
let 基础序列 = 筆.获取缠K序列(&观察员.缠论K线序列);
|
||||
let mut 笔序列 = Vec::with_capacity(基础序列.len() / 2);
|
||||
let 文 = Arc::clone(&筆.文);
|
||||
|
||||
if 基础序列.len() < 5 {
|
||||
return 笔序列;
|
||||
@@ -707,10 +706,8 @@ impl 笔 {
|
||||
for i in 3..基础序列.len() - 1 {
|
||||
let k = &基础序列[i];
|
||||
|
||||
let 匹配顶 =
|
||||
*k.分型.read().unwrap() == Some(分型结构::顶) && 筆.方向() == 相对方向::向上;
|
||||
let 匹配底 =
|
||||
*k.分型.read().unwrap() == Some(分型结构::底) && 筆.方向() == 相对方向::向下;
|
||||
let 匹配顶 = *k.分型.read() == Some(分型结构::顶) && 筆.方向() == 相对方向::向上;
|
||||
let 匹配底 = *k.分型.read() == Some(分型结构::底) && 筆.方向() == 相对方向::向下;
|
||||
if 匹配顶 || 匹配底 {
|
||||
let 左 = Arc::clone(&基础序列[i - 1]);
|
||||
let 中 = Arc::clone(k);
|
||||
@@ -737,12 +734,12 @@ impl 笔 {
|
||||
for 筆 in &停顿位置 {
|
||||
let k线范围 = K线::截取rc(
|
||||
&观察员.普通K线序列,
|
||||
&当前筆.文.中.标的K线.read().unwrap().clone(),
|
||||
&当前筆.武.read().unwrap().中.标的K线.read().unwrap().clone(),
|
||||
&当前筆.文.中.标的K线.read().clone(),
|
||||
&当前筆.武.read().中.标的K线.read().clone(),
|
||||
);
|
||||
let 背驰信号 = 虚线::计算K线序列MACD趋向背驰(&k线范围, 筆.方向());
|
||||
if 背驰信号.iter().all(|&x| x) {
|
||||
结果.push(Arc::clone(&筆.武.read().unwrap().中));
|
||||
结果.push(Arc::clone(&筆.武.read().中));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -39,13 +39,13 @@ impl 背驰分析 {
|
||||
) -> bool {
|
||||
let 进入MACD = Self::_获取MACD面积(
|
||||
K线序列,
|
||||
&进入段.文.中.标的K线.read().unwrap(),
|
||||
&进入段.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&进入段.文.中.标的K线.read(),
|
||||
&进入段.武.read().中.标的K线.read(),
|
||||
);
|
||||
let 离开MACD = Self::_获取MACD面积(
|
||||
K线序列,
|
||||
&离开段.文.中.标的K线.read().unwrap(),
|
||||
&离开段.武.read().unwrap().中.标的K线.read().unwrap(),
|
||||
&离开段.文.中.标的K线.read(),
|
||||
&离开段.武.read().中.标的K线.read(),
|
||||
);
|
||||
|
||||
// 计算面积(绝对值求和)
|
||||
@@ -69,18 +69,18 @@ impl 背驰分析 {
|
||||
|
||||
/// 斜率背驰 — 价格斜率背驰
|
||||
pub fn 斜率背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let dx = (进入段.武.read().unwrap().时间戳() - 进入段.文.时间戳()) as f64;
|
||||
let dx = (进入段.武.read().时间戳() - 进入段.文.时间戳()) as f64;
|
||||
if dx == 0.0 {
|
||||
return false;
|
||||
}
|
||||
let dy = 进入段.武.read().unwrap().分型特征值 - 进入段.文.分型特征值;
|
||||
let dy = 进入段.武.read().分型特征值 - 进入段.文.分型特征值;
|
||||
let 进入斜率 = dy / dx;
|
||||
|
||||
let dx = (离开段.武.read().unwrap().时间戳() - 离开段.文.时间戳()) as f64;
|
||||
let dx = (离开段.武.read().时间戳() - 离开段.文.时间戳()) as f64;
|
||||
if dx == 0.0 {
|
||||
return false;
|
||||
}
|
||||
let dy = 离开段.武.read().unwrap().分型特征值 - 离开段.文.分型特征值;
|
||||
let dy = 离开段.武.read().分型特征值 - 离开段.文.分型特征值;
|
||||
let 离开斜率 = dy / dx;
|
||||
|
||||
if 进入段.方向() == 相对方向::向上 {
|
||||
@@ -92,12 +92,12 @@ impl 背驰分析 {
|
||||
|
||||
/// 测度背驰 — 价格时间测度背驰
|
||||
pub fn 测度背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let dx = (进入段.武.read().unwrap().时间戳() - 进入段.文.时间戳()) as f64;
|
||||
let dy = 进入段.武.read().unwrap().分型特征值 - 进入段.文.分型特征值;
|
||||
let dx = (进入段.武.read().时间戳() - 进入段.文.时间戳()) as f64;
|
||||
let dy = 进入段.武.read().分型特征值 - 进入段.文.分型特征值;
|
||||
let 进入测度 = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
let dx = (离开段.武.read().unwrap().时间戳() - 离开段.文.时间戳()) as f64;
|
||||
let dy = 离开段.武.read().unwrap().分型特征值 - 离开段.文.分型特征值;
|
||||
let dx = (离开段.武.read().时间戳() - 离开段.文.时间戳()) as f64;
|
||||
let dy = 离开段.武.read().分型特征值 - 离开段.文.分型特征值;
|
||||
let 离开测度 = (dx * dx + dy * dy).sqrt();
|
||||
|
||||
if 进入段.方向() == 相对方向::向上 {
|
||||
@@ -187,12 +187,8 @@ impl 背驰分析 {
|
||||
// ---- 内部辅助 ----
|
||||
|
||||
fn _获取MACD面积(K线序列: &[Arc<K线>], 始: &Arc<K线>, 终: &Arc<K线>) -> MACD面积 {
|
||||
let 始_idx = K线序列
|
||||
.iter()
|
||||
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(始));
|
||||
let 终_idx = K线序列
|
||||
.iter()
|
||||
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(终));
|
||||
let 始_idx = K线序列.iter().position(|k| Arc::ptr_eq(k, 始));
|
||||
let 终_idx = K线序列.iter().position(|k| Arc::ptr_eq(k, 终));
|
||||
|
||||
let mut 阳 = 0.0f64;
|
||||
let mut 阴 = 0.0f64;
|
||||
@@ -200,7 +196,7 @@ impl 背驰分析 {
|
||||
if let (Some(始), Some(终)) = (始_idx, 终_idx) {
|
||||
let (始, 终) = if 始 <= 终 { (始, 终) } else { (终, 始) };
|
||||
for k in &K线序列[始..=终] {
|
||||
if let Some(macd) = k.指标.read().unwrap().macd() {
|
||||
if let Some(macd) = k.指标.read().macd() {
|
||||
let hist = macd.MACD柱;
|
||||
if hist >= 0.0 {
|
||||
阳 += hist;
|
||||
|
||||
+134
-169
@@ -25,8 +25,9 @@
|
||||
use crate::structure::dash_line::虚线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::相对方向;
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 中枢 — 三段虚线重叠区间构成的价格中枢
|
||||
///
|
||||
@@ -59,11 +60,11 @@ impl Clone for 中枢 {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
序号: AtomicI64::new(self.序号.load(Ordering::Relaxed)),
|
||||
标识: RwLock::new(self.标识.read().unwrap().clone()),
|
||||
标识: RwLock::new(self.标识.read().clone()),
|
||||
级别: AtomicI64::new(self.级别.load(Ordering::Relaxed)),
|
||||
基础序列: RwLock::new(self.基础序列.read().unwrap().clone()),
|
||||
第三买卖线: RwLock::new(self.第三买卖线.read().unwrap().clone()),
|
||||
本级_第三买卖线: RwLock::new(self.本级_第三买卖线.read().unwrap().clone()),
|
||||
基础序列: RwLock::new(self.基础序列.read().clone()),
|
||||
第三买卖线: RwLock::new(self.第三买卖线.read().clone()),
|
||||
本级_第三买卖线: RwLock::new(self.本级_第三买卖线.read().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -83,9 +84,9 @@ impl 中枢 {
|
||||
|
||||
/// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线
|
||||
pub fn _添加虚线(&self, 实线: Arc<虚线>) {
|
||||
self.基础序列.write().unwrap().push(实线);
|
||||
*self.本级_第三买卖线.write().unwrap() = None;
|
||||
*self.第三买卖线.write().unwrap() = None;
|
||||
self.基础序列.write().push(实线);
|
||||
*self.本级_第三买卖线.write() = None;
|
||||
*self.第三买卖线.write() = None;
|
||||
}
|
||||
|
||||
/// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号"
|
||||
@@ -94,24 +95,25 @@ impl 中枢 {
|
||||
"{}:{}:{}:{}",
|
||||
self.文().中.标识,
|
||||
self.文().中.周期,
|
||||
self.标识.read().unwrap(),
|
||||
self.标识.read(),
|
||||
self.序号.load(Ordering::Relaxed)
|
||||
)
|
||||
}
|
||||
|
||||
/// 返回基础序列的最后一根虚线(当前离开段)
|
||||
pub fn 离开段(&self) -> Arc<虚线> {
|
||||
Arc::clone(&self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1])
|
||||
let guard = self.基础序列.read();
|
||||
Arc::clone(&guard[guard.len() - 1])
|
||||
}
|
||||
|
||||
/// 返回中枢方向(与基础序列第一段方向相反)
|
||||
pub fn 方向(&self) -> 相对方向 {
|
||||
self.基础序列.read().unwrap()[0].方向().翻转()
|
||||
self.基础序列.read()[0].方向().翻转()
|
||||
}
|
||||
|
||||
/// 中枢上沿 = min(前三段的高)
|
||||
pub fn 高(&self) -> f64 {
|
||||
self.基础序列.read().unwrap()[..3]
|
||||
self.基础序列.read()[..3]
|
||||
.iter()
|
||||
.map(|x| x.高())
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
@@ -120,7 +122,7 @@ impl 中枢 {
|
||||
|
||||
/// 中枢下沿 = max(前三段的低)
|
||||
pub fn 低(&self) -> f64 {
|
||||
self.基础序列.read().unwrap()[..3]
|
||||
self.基础序列.read()[..3]
|
||||
.iter()
|
||||
.map(|x| x.低())
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
@@ -131,7 +133,6 @@ impl 中枢 {
|
||||
pub fn 高高(&self) -> f64 {
|
||||
self.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| x.高())
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
@@ -142,7 +143,6 @@ impl 中枢 {
|
||||
pub fn 低低(&self) -> f64 {
|
||||
self.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|x| x.低())
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
@@ -151,46 +151,47 @@ impl 中枢 {
|
||||
|
||||
/// 返回基础序列第一段的起点分型
|
||||
pub fn 文(&self) -> Arc<分型> {
|
||||
Arc::clone(&self.基础序列.read().unwrap()[0].文)
|
||||
Arc::clone(&self.基础序列.read()[0].文)
|
||||
}
|
||||
|
||||
/// 返回基础序列最后一段的终点分型
|
||||
pub fn 武(&self) -> Arc<分型> {
|
||||
Arc::clone(
|
||||
&*self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1]
|
||||
.武
|
||||
.read()
|
||||
.unwrap(),
|
||||
)
|
||||
let guard = self.基础序列.read();
|
||||
Arc::clone(&*guard[guard.len() - 1].武.read())
|
||||
}
|
||||
|
||||
/// 设置第三类买卖点对应的虚线
|
||||
pub fn 设置第三买卖线(&self, 线: Option<Arc<虚线>>) {
|
||||
*self.第三买卖线.write().unwrap() = 线;
|
||||
*self.第三买卖线.write() = 线;
|
||||
}
|
||||
|
||||
/// 获取序列 — 基础序列 + 第三买卖线(若有)
|
||||
pub fn 获取序列(&self) -> Vec<Arc<虚线>> {
|
||||
let mut 序列: Vec<Arc<虚线>> = self.基础序列.read().unwrap().clone();
|
||||
if let Some(ref 三买) = *self.第三买卖线.read().unwrap() {
|
||||
let mut 序列: Vec<Arc<虚线>> = self.基础序列.read().clone();
|
||||
if let Some(ref 三买) = *self.第三买卖线.read() {
|
||||
序列.push(Arc::clone(三买));
|
||||
}
|
||||
序列
|
||||
}
|
||||
|
||||
/// 获取基础序列最后一个元素
|
||||
pub fn 最后(&self) -> Option<Arc<虚线>> {
|
||||
self.基础序列.read().last().cloned()
|
||||
}
|
||||
|
||||
/// 返回序列化数据文本,用于调试和存储
|
||||
pub fn 获取数据文本(&self) -> String {
|
||||
let 第三买卖线_str = match &*self.第三买卖线.read().unwrap() {
|
||||
let 第三买卖线_str = match &*self.第三买卖线.read() {
|
||||
Some(x) => format!("{}", x),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
let 本级_第三买卖线_str = match &*self.本级_第三买卖线.read().unwrap() {
|
||||
let 本级_第三买卖线_str = match &*self.本级_第三买卖线.read() {
|
||||
Some(x) => format!("{}", x),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
format!(
|
||||
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}",
|
||||
self.标识.read().unwrap(),
|
||||
self.标识.read(),
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.级别.load(Ordering::Relaxed),
|
||||
self.文().时间戳(),
|
||||
@@ -204,67 +205,63 @@ impl 中枢 {
|
||||
|
||||
/// 校验中枢合法性
|
||||
pub fn _校验合法性(&self, 序列: &[Arc<虚线>]) -> bool {
|
||||
let mut 有效序列 = self.基础序列.read().unwrap().clone();
|
||||
let guard = self.基础序列.read();
|
||||
let mut 有效序列 = guard.clone();
|
||||
let mut 无效序列: Vec<Arc<虚线>> = Vec::new();
|
||||
for 元素 in self.基础序列.read().unwrap().iter() {
|
||||
if !序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(元素)) {
|
||||
let 首序号 = 序列[0].序号.load(Ordering::Relaxed);
|
||||
for 元素 in guard.iter() {
|
||||
let idx = (元素.序号.load(Ordering::Relaxed) - 首序号) as usize;
|
||||
if idx >= 序列.len() || !Arc::ptr_eq(&序列[idx], 元素) {
|
||||
无效序列.push(Arc::clone(元素));
|
||||
}
|
||||
}
|
||||
|
||||
if !无效序列.is_empty() {
|
||||
let 无效 = &无效序列[0];
|
||||
if let Some(pos) = self
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(无效))
|
||||
{
|
||||
有效序列 = self.基础序列.read().unwrap()[..pos].to_vec();
|
||||
}
|
||||
// Python: 序号 = 线段._索引(self.基础序列, 无效)
|
||||
let pos = crate::algorithm::segment::线段::_索引(&guard, 无效);
|
||||
有效序列 = guard[..pos].to_vec();
|
||||
}
|
||||
drop(guard);
|
||||
|
||||
if 有效序列.len() < 3 {
|
||||
self.设置第三买卖线(None);
|
||||
*self.本级_第三买卖线.write().unwrap() = None;
|
||||
*self.本级_第三买卖线.write() = None;
|
||||
return false;
|
||||
}
|
||||
|
||||
*self.基础序列.write().unwrap() = 有效序列;
|
||||
*self.基础序列.write() = 有效序列;
|
||||
|
||||
let 中枢高 = self.高();
|
||||
let 中枢低 = self.低();
|
||||
有效序列 = Vec::new();
|
||||
for 元素 in self.基础序列.read().unwrap().iter() {
|
||||
for 元素 in self.基础序列.read().iter() {
|
||||
if crate::types::相对方向::分析(中枢高, 中枢低, 元素.高(), 元素.低()).是否缺口()
|
||||
{
|
||||
break;
|
||||
}
|
||||
有效序列.push(Arc::clone(元素));
|
||||
}
|
||||
*self.基础序列.write().unwrap() = 有效序列;
|
||||
*self.基础序列.write() = 有效序列;
|
||||
|
||||
if self.基础序列.read().unwrap().len() < 3 {
|
||||
return false;
|
||||
}
|
||||
|
||||
for i in 1..self.基础序列.read().unwrap().len() {
|
||||
let 前 = &self.基础序列.read().unwrap()[i - 1];
|
||||
let 后 = &self.基础序列.read().unwrap()[i];
|
||||
if !前.之后是(后) {
|
||||
let 是缺口 = {
|
||||
let guard = self.基础序列.read();
|
||||
if guard.len() < 3 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if !crate::types::相对方向::分析(
|
||||
self.基础序列.read().unwrap()[0].高(),
|
||||
self.基础序列.read().unwrap()[0].低(),
|
||||
self.基础序列.read().unwrap()[2].高(),
|
||||
self.基础序列.read().unwrap()[2].低(),
|
||||
)
|
||||
.是否缺口()
|
||||
{
|
||||
for i in 1..guard.len() {
|
||||
if !guard[i - 1].之后是(&guard[i]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
crate::types::相对方向::分析(
|
||||
guard[0].高(),
|
||||
guard[0].低(),
|
||||
guard[2].高(),
|
||||
guard[2].低(),
|
||||
)
|
||||
.是否缺口()
|
||||
};
|
||||
if !是缺口 {
|
||||
let 重叠高 = self.高();
|
||||
let 重叠低 = self.低();
|
||||
if 重叠低 > 重叠高 {
|
||||
@@ -272,10 +269,12 @@ impl 中枢 {
|
||||
}
|
||||
}
|
||||
|
||||
let 三买线_opt = self.第三买卖线.read().unwrap().clone();
|
||||
let 三买线_opt = self.第三买卖线.read().clone();
|
||||
if let Some(ref 三买线) = 三买线_opt {
|
||||
if 序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(三买线)) {
|
||||
if !self.基础序列.read().unwrap().last().unwrap().之后是(三买线) {
|
||||
let 首序号 = 序列[0].序号.load(Ordering::Relaxed);
|
||||
let idx = (三买线.序号.load(Ordering::Relaxed) - 首序号) as usize;
|
||||
if idx < 序列.len() && Arc::ptr_eq(&序列[idx], 三买线) {
|
||||
if !self.基础序列.read().last().unwrap().之后是(三买线) {
|
||||
self.设置第三买卖线(None);
|
||||
} else if !crate::types::相对方向::分析(
|
||||
self.高(),
|
||||
@@ -298,16 +297,16 @@ impl 中枢 {
|
||||
/// 完整性 — 详见教你炒股票43:有关背驰的补习课
|
||||
/// 不完整时下一个中枢大概率会与当前中枢发生扩展
|
||||
pub fn 完整性(&self, 虚实: &str) -> bool {
|
||||
if *self.基础序列.read().unwrap()[0].标识.read().unwrap() == "笔" {
|
||||
return self.第三买卖线.read().unwrap().is_some();
|
||||
if *self.基础序列.read()[0].标识.read() == "笔" {
|
||||
return self.第三买卖线.read().is_some();
|
||||
}
|
||||
|
||||
let 基础序列_ref = self.基础序列.read().unwrap();
|
||||
let 基础序列_ref = self.基础序列.read();
|
||||
let 最后段 = 基础序列_ref.last().unwrap();
|
||||
let 内部中枢_vec = if 虚实 == "合" {
|
||||
最后段.合_中枢序列.read().unwrap()
|
||||
最后段.合_中枢序列.read()
|
||||
} else {
|
||||
最后段.实_中枢序列.read().unwrap()
|
||||
最后段.实_中枢序列.read()
|
||||
};
|
||||
for 内部中枢 in 内部中枢_vec.iter() {
|
||||
if crate::types::相对方向::分析(
|
||||
@@ -330,24 +329,19 @@ impl 中枢 {
|
||||
扩展中枢: &mut Vec<Arc<中枢>>,
|
||||
配置: &crate::config::缠论配置,
|
||||
) {
|
||||
if self.基础序列.read().unwrap().len() >= 9 {
|
||||
if self.基础序列.read().len() >= 9 {
|
||||
let mut 扩展线段: Vec<Arc<虚线>> = Vec::new();
|
||||
let 基础序列_ref = self.基础序列.read().unwrap();
|
||||
let 基础序列_ref = self.基础序列.read();
|
||||
crate::algorithm::segment::线段::扩展分析(&基础序列_ref, &mut 扩展线段, 配置);
|
||||
中枢::分析(
|
||||
&扩展线段,
|
||||
扩展中枢,
|
||||
false,
|
||||
&format!("{}_扩展中枢_", self.标识.read().unwrap()),
|
||||
0,
|
||||
);
|
||||
let 标识 = format!("{}_扩展中枢_", self.标识.read());
|
||||
中枢::分析(&扩展线段, 扩展中枢, false, &标识, 0);
|
||||
}
|
||||
}
|
||||
|
||||
/// 当前状态 — 详见教你炒股票49:利润率最大的操作模式
|
||||
/// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下
|
||||
pub fn 当前状态(&self) -> &str {
|
||||
let 基础序列_ref = self.基础序列.read().unwrap();
|
||||
let 基础序列_ref = self.基础序列.read();
|
||||
let 最后 = Arc::clone(基础序列_ref.last().unwrap());
|
||||
let 尾部 = 最后.获取_武();
|
||||
let 关系 = crate::types::相对方向::分析(
|
||||
@@ -390,7 +384,7 @@ impl 中枢 {
|
||||
assert!(Self::基础检查(&左, &中, &右), "中枢.创建 基础检查失败");
|
||||
Self::new(
|
||||
0,
|
||||
format!("{}中枢<{}>", 标识, 中.标识.read().unwrap()),
|
||||
format!("{}中枢<{}>", 标识, 中.标识.read()),
|
||||
级别,
|
||||
vec![左, 中, 右],
|
||||
)
|
||||
@@ -422,18 +416,8 @@ impl 中枢 {
|
||||
待添加中枢
|
||||
.序号
|
||||
.store(前一个.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed);
|
||||
let 前_last_序号 = 前一个
|
||||
.获取序列()
|
||||
.last()
|
||||
.unwrap()
|
||||
.序号
|
||||
.load(Ordering::Relaxed);
|
||||
let new_last_序号 = 待添加中枢
|
||||
.获取序列()
|
||||
.last()
|
||||
.unwrap()
|
||||
.序号
|
||||
.load(Ordering::Relaxed);
|
||||
let 前_last_序号 = 前一个.最后().unwrap().序号.load(Ordering::Relaxed);
|
||||
let new_last_序号 = 待添加中枢.最后().unwrap().序号.load(Ordering::Relaxed);
|
||||
if 前_last_序号 > new_last_序号 {
|
||||
panic!(
|
||||
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
|
||||
@@ -478,11 +462,8 @@ impl 中枢 {
|
||||
let 右 = &虚线序列[i + 1];
|
||||
|
||||
if Self::基础检查(左, 中, 右) {
|
||||
// Python: 序号 = 虚线序列.index(左)
|
||||
let 序号 = 虚线序列
|
||||
.iter()
|
||||
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(左))
|
||||
.expect("中枢.分析: 左元素不在虚线序列中");
|
||||
// Python: 序号 = 线段._索引(虚线序列, 左)
|
||||
let 序号: usize = crate::algorithm::segment::线段::_索引(虚线序列, 左);
|
||||
if 跳过首部 && (左.序号.load(Ordering::Relaxed) == 0 || 序号 == 0) {
|
||||
continue;
|
||||
}
|
||||
@@ -525,22 +506,16 @@ impl 中枢 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到当前中枢最后一个元素在虚线序列中的位置
|
||||
// Python: 序号 = 线段._索引(虚线序列, 当前中枢.基础序列[-1]) + 1
|
||||
let 起始索引 = {
|
||||
let cur = &中枢序列[当前中枢_idx];
|
||||
let 最后元素 = &cur.基础序列.read().unwrap()[cur.基础序列.read().unwrap().len() - 1];
|
||||
match 虚线序列
|
||||
.iter()
|
||||
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(最后元素))
|
||||
{
|
||||
Some(idx) => idx + 1,
|
||||
None => return,
|
||||
}
|
||||
let guard = cur.基础序列.read();
|
||||
crate::algorithm::segment::线段::_索引(虚线序列, &guard[guard.len() - 1]) + 1
|
||||
};
|
||||
|
||||
let mut 中枢高 = 中枢序列[当前中枢_idx].高();
|
||||
let mut 中枢低 = 中枢序列[当前中枢_idx].低();
|
||||
let mut 候选序列: Vec<Arc<虚线>> = Vec::new();
|
||||
let mut 候选序列 = Vec::with_capacity(3);
|
||||
|
||||
for 当前虚线_ref in &虚线序列[起始索引..] {
|
||||
let 当前虚线 = Arc::clone(当前虚线_ref);
|
||||
@@ -553,12 +528,7 @@ impl 中枢 {
|
||||
// Python: if 当前中枢.基础序列[-1].之后是(当前虚线):
|
||||
let needs_三买 = {
|
||||
let cur = &中枢序列[当前中枢_idx];
|
||||
cur.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.unwrap()
|
||||
.之后是(&当前虚线)
|
||||
cur.基础序列.read().last().unwrap().之后是(&当前虚线)
|
||||
};
|
||||
if needs_三买 {
|
||||
中枢序列[当前中枢_idx].设置第三买卖线(Some(当前虚线.clone()));
|
||||
@@ -570,17 +540,11 @@ impl 中枢 {
|
||||
中枢序列[当前中枢_idx]
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.unwrap()
|
||||
.之后是(&当前虚线),
|
||||
"中枢延伸: 不连续 {}, {}",
|
||||
中枢序列[当前中枢_idx]
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.unwrap(),
|
||||
中枢序列[当前中枢_idx].基础序列.read().last().unwrap(),
|
||||
当前虚线
|
||||
);
|
||||
中枢序列[当前中枢_idx]._添加虚线(当前虚线);
|
||||
@@ -594,7 +558,6 @@ impl 中枢 {
|
||||
let 起始方向 = 中枢序列[当前中枢_idx]
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.last()
|
||||
.unwrap()
|
||||
.方向()
|
||||
@@ -628,13 +591,13 @@ impl 中枢 {
|
||||
),
|
||||
);
|
||||
}
|
||||
if *self.标识.read().unwrap() != *other.标识.read().unwrap() {
|
||||
if *self.标识.read() != *other.标识.read() {
|
||||
return (
|
||||
false,
|
||||
format!(
|
||||
"中枢: [标识] 不等 A={},B={}",
|
||||
self.标识.read().unwrap(),
|
||||
other.标识.read().unwrap()
|
||||
self.标识.read(),
|
||||
other.标识.read()
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -649,8 +612,8 @@ impl 中枢 {
|
||||
);
|
||||
}
|
||||
// 基础序列
|
||||
let a_seq = self.基础序列.read().unwrap();
|
||||
let b_seq = other.基础序列.read().unwrap();
|
||||
let a_seq = self.基础序列.read();
|
||||
let b_seq = other.基础序列.read();
|
||||
if a_seq.len() != b_seq.len() {
|
||||
return (
|
||||
false,
|
||||
@@ -692,16 +655,16 @@ impl 中枢 {
|
||||
};
|
||||
检查单个(
|
||||
"第三买卖线",
|
||||
&self.第三买卖线.read().unwrap(),
|
||||
&other.第三买卖线.read().unwrap(),
|
||||
&self.第三买卖线.read(),
|
||||
&other.第三买卖线.read(),
|
||||
浮点容差,
|
||||
)
|
||||
.map_err(|e| (false, e))
|
||||
.ok();
|
||||
检查单个(
|
||||
"本级_第三买卖线",
|
||||
&self.本级_第三买卖线.read().unwrap(),
|
||||
&other.本级_第三买卖线.read().unwrap(),
|
||||
&self.本级_第三买卖线.read(),
|
||||
&other.本级_第三买卖线.read(),
|
||||
浮点容差,
|
||||
)
|
||||
.map_err(|e| (false, e))
|
||||
@@ -712,21 +675,26 @@ impl 中枢 {
|
||||
|
||||
impl std::fmt::Display for 中枢 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let 序列_str = self
|
||||
.基础序列
|
||||
.read()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|d| format!("{}", d))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
let guard = self.基础序列.read();
|
||||
let len = guard.len();
|
||||
let 序列_str = if let Some((first, rest)) = guard.split_first() {
|
||||
let mut s = format!("{}", first);
|
||||
for d in rest {
|
||||
use std::fmt::Write;
|
||||
write!(&mut s, ", {}", d).unwrap();
|
||||
}
|
||||
s
|
||||
} else {
|
||||
String::new()
|
||||
};
|
||||
drop(guard);
|
||||
write!(
|
||||
f,
|
||||
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
|
||||
self.标识.read().unwrap(),
|
||||
self.标识.read(),
|
||||
crate::utils::format_f64_g(self.高()),
|
||||
crate::utils::format_f64_g(self.低()),
|
||||
self.基础序列.read().unwrap().len(),
|
||||
len,
|
||||
序列_str,
|
||||
self.文(),
|
||||
self.武(),
|
||||
@@ -851,11 +819,11 @@ mod tests {
|
||||
);
|
||||
|
||||
assert_eq!(中枢.序号.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(*中枢.标识.read().unwrap(), "测试中枢");
|
||||
assert_eq!(*中枢.标识.read(), "测试中枢");
|
||||
assert_eq!(中枢.级别.load(Ordering::Relaxed), 1);
|
||||
assert_eq!(中枢.基础序列.read().unwrap().len(), 3);
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_none());
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_none());
|
||||
assert_eq!(中枢.基础序列.read().len(), 3);
|
||||
assert!(中枢.第三买卖线.read().is_none());
|
||||
assert!(中枢.本级_第三买卖线.read().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -877,16 +845,16 @@ mod tests {
|
||||
|
||||
// RefCell 第三买卖线读写
|
||||
中枢.设置第三买卖线(Some(Arc::clone(&笔1)));
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_some());
|
||||
assert!(中枢.第三买卖线.read().is_some());
|
||||
assert_eq!(
|
||||
Arc::as_ptr(中枢.第三买卖线.read().unwrap().as_ref().unwrap()),
|
||||
Arc::as_ptr(中枢.第三买卖线.read().as_ref().unwrap()),
|
||||
Arc::as_ptr(&笔1)
|
||||
);
|
||||
|
||||
// 本级_第三买卖线
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_none());
|
||||
*中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&笔3));
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_some());
|
||||
assert!(中枢.本级_第三买卖线.read().is_none());
|
||||
*中枢.本级_第三买卖线.write() = Some(Arc::clone(&笔3));
|
||||
assert!(中枢.本级_第三买卖线.read().is_some());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -906,14 +874,11 @@ mod tests {
|
||||
1,
|
||||
vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)],
|
||||
);
|
||||
assert_eq!(中枢.基础序列.read().unwrap().len(), 3);
|
||||
assert_eq!(中枢.基础序列.read().len(), 3);
|
||||
|
||||
中枢._添加虚线(Arc::clone(&笔4));
|
||||
assert_eq!(中枢.基础序列.read().unwrap().len(), 4);
|
||||
assert_eq!(
|
||||
Arc::as_ptr(&中枢.基础序列.read().unwrap()[3]),
|
||||
Arc::as_ptr(&笔4)
|
||||
);
|
||||
assert_eq!(中枢.基础序列.read().len(), 4);
|
||||
assert_eq!(Arc::as_ptr(&中枢.基础序列.read()[3]), Arc::as_ptr(&笔4));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -930,14 +895,14 @@ mod tests {
|
||||
vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)],
|
||||
);
|
||||
中枢.设置第三买卖线(Some(Arc::clone(&笔1)));
|
||||
*中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&笔2));
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_some());
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_some());
|
||||
*中枢.本级_第三买卖线.write() = Some(Arc::clone(&笔2));
|
||||
assert!(中枢.第三买卖线.read().is_some());
|
||||
assert!(中枢.本级_第三买卖线.read().is_some());
|
||||
|
||||
中枢._添加虚线(Arc::clone(&笔4));
|
||||
// 添加虚线后第三买卖线被清除
|
||||
assert!(中枢.第三买卖线.read().unwrap().is_none());
|
||||
assert!(中枢.本级_第三买卖线.read().unwrap().is_none());
|
||||
assert!(中枢.第三买卖线.read().is_none());
|
||||
assert!(中枢.本级_第三买卖线.read().is_none());
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
@@ -963,15 +928,15 @@ mod tests {
|
||||
// 基础序列中的 Rc 指针应一致
|
||||
for i in 0..3 {
|
||||
assert_eq!(
|
||||
Arc::as_ptr(&中枢.基础序列.read().unwrap()[i]),
|
||||
Arc::as_ptr(&克隆.基础序列.read().unwrap()[i])
|
||||
Arc::as_ptr(&中枢.基础序列.read()[i]),
|
||||
Arc::as_ptr(&克隆.基础序列.read()[i])
|
||||
);
|
||||
}
|
||||
|
||||
// 第三买卖线 Rc 指针应一致
|
||||
assert_eq!(
|
||||
Arc::as_ptr(中枢.第三买卖线.read().unwrap().as_ref().unwrap()),
|
||||
Arc::as_ptr(克隆.第三买卖线.read().unwrap().as_ref().unwrap())
|
||||
Arc::as_ptr(中枢.第三买卖线.read().as_ref().unwrap()),
|
||||
Arc::as_ptr(克隆.第三买卖线.read().as_ref().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1030,12 +995,12 @@ mod tests {
|
||||
|
||||
// 通过 rc1 添加虚线
|
||||
中枢1._添加虚线(Arc::clone(&笔4));
|
||||
assert_eq!(中枢2.基础序列.read().unwrap().len(), 4);
|
||||
assert_eq!(中枢2.基础序列.read().len(), 4);
|
||||
|
||||
// 验证共享的 Arc<虚线> 指针一致
|
||||
assert_eq!(
|
||||
Arc::as_ptr(&中枢1.基础序列.read().unwrap()[3]),
|
||||
Arc::as_ptr(&中枢2.基础序列.read().unwrap()[3])
|
||||
Arc::as_ptr(&中枢1.基础序列.read()[3]),
|
||||
Arc::as_ptr(&中枢2.基础序列.read()[3])
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+573
-709
File diff suppressed because it is too large
Load Diff
@@ -347,7 +347,7 @@ impl 买卖点 {
|
||||
let 破位值 = 买卖点分型.分型特征值();
|
||||
|
||||
// 当前K线 — 从缠K获取其标的K线
|
||||
let 当前K线 = Arc::clone(&*当前缠K.标的K线.read().unwrap());
|
||||
let 当前K线 = Arc::clone(&*当前缠K.标的K线.read());
|
||||
// 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算
|
||||
let 当前缠K序号 = 当前缠K.序号.load(Ordering::Relaxed);
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ use crate::business::observer::观察者;
|
||||
use crate::business::synthesizer::K线合成器;
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::{error, warn};
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
use tracing::{error, info};
|
||||
|
||||
/// 立体分析器 — 多周期协调器
|
||||
pub struct 立体分析器 {
|
||||
@@ -72,7 +72,7 @@ impl 立体分析器 {
|
||||
// 显示周期特殊配置
|
||||
{
|
||||
let 显示观察员 = 单体分析器.get(&显示周期).expect("显示周期观察者不存在");
|
||||
let mut guard = 显示观察员.write().unwrap();
|
||||
let mut guard = 显示观察员.write();
|
||||
guard.配置.推送K线 = true;
|
||||
guard.配置.推送笔 = true;
|
||||
guard.配置.推送线段 = true;
|
||||
@@ -84,14 +84,14 @@ impl 立体分析器 {
|
||||
{
|
||||
let 显示缠K序列 = 单体分析器
|
||||
.get(&显示周期)
|
||||
.map(|o| o.read().unwrap().缠论K线序列.clone())
|
||||
.map(|o| o.read().缠论K线序列.clone())
|
||||
.unwrap_or_default();
|
||||
|
||||
for &周期 in &周期组 {
|
||||
if 周期 != 显示周期
|
||||
&& let Some(观察员) = 单体分析器.get(&周期)
|
||||
{
|
||||
观察员.write().unwrap().基础缠K序列 = 显示缠K序列.clone();
|
||||
观察员.write().基础缠K序列 = 显示缠K序列.clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,7 +119,7 @@ impl 立体分析器 {
|
||||
/// __K线回调 — 对应 Python 立体分析器.__K线回调
|
||||
fn __K线回调(&self, _信号类型: String, _标识: String, 周期: i64, 完成K线: K线) {
|
||||
if let Some(观察员) = self.单体分析器.get(&周期) {
|
||||
let mut obs = 观察员.write().unwrap();
|
||||
let mut obs = 观察员.write();
|
||||
obs.增加原始K线(完成K线);
|
||||
// 对应 Python: if 当前K线 := self._K线合成器.获取当前K线(周期)
|
||||
// _完成K线刚清空当前K线,获取当前K线返回 None,所以这里不添加
|
||||
@@ -133,7 +133,7 @@ impl 立体分析器 {
|
||||
完成K线: K线,
|
||||
) {
|
||||
if let Some(观察员) = 单体分析器.get(&周期) {
|
||||
观察员.write().unwrap().增加原始K线(完成K线);
|
||||
观察员.write().增加原始K线(完成K线);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -165,22 +165,22 @@ impl 立体分析器 {
|
||||
let 起始时间 = self
|
||||
.单体分析器
|
||||
.get(&self.输入周期)
|
||||
.and_then(|o| o.read().unwrap().普通K线序列.first().map(|k| k.时间戳))
|
||||
.and_then(|o| o.read().普通K线序列.first().map(|k| k.时间戳))
|
||||
.unwrap_or(0);
|
||||
let 结束时间 = self
|
||||
.单体分析器
|
||||
.get(&self.输入周期)
|
||||
.and_then(|o| o.read().unwrap().普通K线序列.last().map(|k| k.时间戳))
|
||||
.and_then(|o| o.read().普通K线序列.last().map(|k| k.时间戳))
|
||||
.unwrap_or(0);
|
||||
let 标识 = self
|
||||
.单体分析器
|
||||
.get(&self.输入周期)
|
||||
.map(|o| o.read().unwrap().符号.clone())
|
||||
.map(|o| o.read().符号.clone())
|
||||
.unwrap_or_default();
|
||||
let 周期 = self
|
||||
.单体分析器
|
||||
.get(&self.输入周期)
|
||||
.map(|o| o.read().unwrap().周期)
|
||||
.map(|o| o.read().周期)
|
||||
.unwrap_or_default();
|
||||
|
||||
let 目录标识 = format!("RustM_{}:{}_{}_{}", 标识, 周期, 起始时间, 结束时间);
|
||||
@@ -195,12 +195,11 @@ impl 立体分析器 {
|
||||
if let Some(观察员) = self.单体分析器.get(周期) {
|
||||
观察员
|
||||
.read()
|
||||
.unwrap()
|
||||
.测试_保存数据(Some(&保存路径.to_string_lossy()));
|
||||
}
|
||||
}
|
||||
|
||||
info!("多级别数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
warn!("多级别数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
}
|
||||
|
||||
/// 相等 — 各周期观察者全量比对,对应 Python `立体分析器相等`
|
||||
@@ -213,11 +212,11 @@ impl 立体分析器 {
|
||||
|
||||
for 周期 in &self.周期组 {
|
||||
let a_obs = match self.单体分析器.get(周期) {
|
||||
Some(o) => o.read().unwrap(),
|
||||
Some(o) => o.read(),
|
||||
None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (A)")),
|
||||
};
|
||||
let b_obs = match other.单体分析器.get(周期) {
|
||||
Some(o) => o.read().unwrap(),
|
||||
Some(o) => o.read(),
|
||||
None => return (false, format!("{标签}: 周期{周期} 观察者不存在 (B)")),
|
||||
};
|
||||
let (eq, msg) = a_obs.相等(&b_obs, 浮点容差);
|
||||
|
||||
+196
-109
@@ -32,9 +32,10 @@ use crate::structure::dash_line::虚线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::相对方向;
|
||||
use crate::utils::datetime;
|
||||
use crate::{error, warn};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use tracing::{error, info};
|
||||
|
||||
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
|
||||
pub struct 观察者 {
|
||||
@@ -298,17 +299,15 @@ impl 观察者 {
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if self.配置.分析线段 {
|
||||
let 源序列 = self.线段序列组[i - 1].clone();
|
||||
线段::分析(
|
||||
&源序列,
|
||||
&mut self.线段序列组[i],
|
||||
&self.配置,
|
||||
0,
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
} else if self.配置.分析线段 {
|
||||
let (left, right) = self.线段序列组.split_at_mut(i);
|
||||
线段::分析(
|
||||
&left[i - 1],
|
||||
&mut right[0],
|
||||
&self.配置,
|
||||
0,
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(&self.线段序列组[i], &mut self.中枢序列组[i], true, "", 0);
|
||||
@@ -323,11 +322,9 @@ impl 观察者 {
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(&self.笔序列, &mut self.扩展线段序列组[i], &self.配置);
|
||||
}
|
||||
} else {
|
||||
if self.配置.分析扩展线段 {
|
||||
let 源序列 = self.扩展线段序列组[i - 1].clone();
|
||||
线段::扩展分析(&源序列, &mut self.扩展线段序列组[i], &self.配置);
|
||||
}
|
||||
} else if self.配置.分析扩展线段 {
|
||||
let (left, right) = self.扩展线段序列组.split_at_mut(i);
|
||||
线段::扩展分析(&left[i - 1], &mut right[0], &self.配置);
|
||||
}
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
@@ -345,8 +342,11 @@ impl 观察者 {
|
||||
if self.配置.分析扩展线段 || self.配置.分析线段中枢 {
|
||||
for i in 0..self.混合扩展线段分析层次.min(self.线段序列组.len()) {
|
||||
if self.配置.分析扩展线段 {
|
||||
let 源序列 = self.线段序列组[i].clone();
|
||||
线段::扩展分析(&源序列, &mut self.混合扩展线段序列组[i], &self.配置);
|
||||
线段::扩展分析(
|
||||
&self.线段序列组[i],
|
||||
&mut self.混合扩展线段序列组[i],
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
@@ -430,17 +430,15 @@ impl 观察者 {
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if self.配置.分析线段 {
|
||||
let 源序列 = self.线段序列组[i - 1].clone();
|
||||
线段::分析(
|
||||
&源序列,
|
||||
&mut self.线段序列组[i],
|
||||
&self.配置,
|
||||
0,
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
} else if self.配置.分析线段 {
|
||||
let (left, right) = self.线段序列组.split_at_mut(i);
|
||||
线段::分析(
|
||||
&left[i - 1],
|
||||
&mut right[0],
|
||||
&self.配置,
|
||||
0,
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(&self.线段序列组[i], &mut self.中枢序列组[i], true, "", 0);
|
||||
@@ -454,11 +452,9 @@ impl 观察者 {
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(&self.笔序列, &mut self.扩展线段序列组[i], &self.配置);
|
||||
}
|
||||
} else {
|
||||
if self.配置.分析扩展线段 {
|
||||
let 源序列 = self.扩展线段序列组[i - 1].clone();
|
||||
线段::扩展分析(&源序列, &mut self.扩展线段序列组[i], &self.配置);
|
||||
}
|
||||
} else if self.配置.分析扩展线段 {
|
||||
let (left, right) = self.扩展线段序列组.split_at_mut(i);
|
||||
线段::扩展分析(&left[i - 1], &mut right[0], &self.配置);
|
||||
}
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
@@ -475,8 +471,11 @@ impl 观察者 {
|
||||
if self.配置.分析扩展线段 || self.配置.分析线段中枢 {
|
||||
for i in 0..self.混合扩展线段分析层次.min(self.线段序列组.len()) {
|
||||
if self.配置.分析扩展线段 {
|
||||
let 源序列 = self.线段序列组[i].clone();
|
||||
线段::扩展分析(&源序列, &mut self.混合扩展线段序列组[i], &self.配置);
|
||||
线段::扩展分析(
|
||||
&self.线段序列组[i],
|
||||
&mut self.混合扩展线段序列组[i],
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
@@ -517,7 +516,7 @@ impl 观察者 {
|
||||
return Ok(());
|
||||
}
|
||||
let 数据列表: Vec<String> = 序列.iter().map(|d| d.获取数据文本()).collect();
|
||||
let 文件名 = format!("{}.txt", 序列[0].标识.read().unwrap());
|
||||
let 文件名 = format!("{}.txt", 序列[0].标识.read());
|
||||
std::fs::write(保存路径.join(&文件名), 数据列表.join("\n") + "\n")?;
|
||||
Ok(())
|
||||
};
|
||||
@@ -528,7 +527,7 @@ impl 观察者 {
|
||||
return Ok(());
|
||||
}
|
||||
let 数据列表: Vec<String> = 序列.iter().map(|h| h.获取数据文本()).collect();
|
||||
let 文件名 = format!("{}.txt", 序列[0].标识.read().unwrap());
|
||||
let 文件名 = format!("{}.txt", 序列[0].标识.read());
|
||||
std::fs::write(保存路径.join(&文件名), 数据列表.join("\n") + "\n")?;
|
||||
Ok(())
|
||||
};
|
||||
@@ -563,7 +562,7 @@ impl 观察者 {
|
||||
ck.序号.load(Ordering::Relaxed),
|
||||
ck.时间戳.load(Ordering::Relaxed),
|
||||
ck.分型,
|
||||
*ck.方向.read().unwrap(),
|
||||
*ck.方向.read(),
|
||||
ck.高.get(),
|
||||
ck.低.get(),
|
||||
ck.原始起始序号,
|
||||
@@ -597,7 +596,7 @@ impl 观察者 {
|
||||
分型序列_数据文本.join("\n") + "\n",
|
||||
);
|
||||
|
||||
info!("全部数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
warn!("全部数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
保存路径.display().to_string()
|
||||
}
|
||||
|
||||
@@ -734,6 +733,7 @@ impl 观察者 {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::缠论配置;
|
||||
use crate::{error, info};
|
||||
|
||||
fn test_data_path() -> String {
|
||||
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
|
||||
@@ -749,10 +749,9 @@ mod tests {
|
||||
fn test_普k序列指针一致性() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
for (i, bi) in obs_ref.笔序列.iter().enumerate() {
|
||||
let pu_seq = bi.获取普K序列(&obs_ref.普通K线序列);
|
||||
@@ -761,12 +760,7 @@ mod tests {
|
||||
info!(" 文.中.标的K线 原始起始序号: {}", bi.文.中.原始起始序号);
|
||||
info!(
|
||||
" 武.中.标的K线 原始结束序号: {}",
|
||||
bi.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.中
|
||||
.原始结束序号
|
||||
.load(Ordering::Relaxed)
|
||||
bi.武.read().中.原始结束序号.load(Ordering::Relaxed)
|
||||
);
|
||||
info!(" 普通K线序列.len: {}", obs_ref.普通K线序列.len());
|
||||
} else {
|
||||
@@ -777,7 +771,7 @@ mod tests {
|
||||
.any(|k| Arc::as_ptr(k) == first_ptr);
|
||||
if !found {
|
||||
info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
|
||||
let wen_ptr = Arc::as_ptr(&*bi.文.中.标的K线.read().unwrap());
|
||||
let wen_ptr = Arc::as_ptr(&*bi.文.中.标的K线.read());
|
||||
let wen_found = obs_ref
|
||||
.普通K线序列
|
||||
.iter()
|
||||
@@ -802,11 +796,11 @@ mod tests {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
|
||||
let _k线_py_inner = Arc::new(k线.clone());
|
||||
obs_ref.write().unwrap().增加原始K线(k线);
|
||||
obs_ref.write().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
let obs = obs_ref.read().unwrap();
|
||||
let obs = obs_ref.read();
|
||||
info!("普通K线序列.len: {}", obs.普通K线序列.len());
|
||||
info!("笔序列.len: {}", obs.笔序列.len());
|
||||
|
||||
@@ -835,10 +829,9 @@ mod tests {
|
||||
fn test_分型到笔的文武Rc指针一致性() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
// 每个笔的文/武 分型 Rc 指针必须在 分型序列 中
|
||||
for (i, bi) in obs_ref.笔序列.iter().enumerate() {
|
||||
@@ -848,13 +841,13 @@ mod tests {
|
||||
info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi.文.时间戳());
|
||||
}
|
||||
|
||||
let 武_ptr = Arc::as_ptr(&*bi.武.read().unwrap());
|
||||
let 武_ptr = Arc::as_ptr(&*bi.武.read());
|
||||
let 武_found = obs_ref.分型序列.iter().any(|f| Arc::as_ptr(f) == 武_ptr);
|
||||
if !武_found {
|
||||
info!(
|
||||
"笔 {}: 武(时间戳={}) 不在分型序列中!",
|
||||
i,
|
||||
bi.武.read().unwrap().时间戳()
|
||||
bi.武.read().时间戳()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -868,14 +861,13 @@ mod tests {
|
||||
fn test_笔到线段的基础序列Rc指针一致性() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
// 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中
|
||||
for (i, seg) in obs_ref.线段序列().iter().enumerate() {
|
||||
for (j, bi_in_seg) in seg.基础序列.read().unwrap().iter().enumerate() {
|
||||
for (j, bi_in_seg) in seg.基础序列.read().iter().enumerate() {
|
||||
let bi_ptr = Arc::as_ptr(bi_in_seg);
|
||||
let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr);
|
||||
if !found {
|
||||
@@ -893,13 +885,12 @@ mod tests {
|
||||
fn test_中枢基础序列与笔序列Rc指针一致() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
for (i, hub) in obs_ref.笔_中枢序列.iter().enumerate() {
|
||||
for (j, bi_in_hub) in hub.基础序列.read().unwrap().iter().enumerate() {
|
||||
for (j, bi_in_hub) in hub.基础序列.read().iter().enumerate() {
|
||||
let bi_ptr = Arc::as_ptr(bi_in_hub);
|
||||
let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr);
|
||||
if !found {
|
||||
@@ -909,7 +900,7 @@ mod tests {
|
||||
}
|
||||
|
||||
for (i, hub) in obs_ref.中枢序列().iter().enumerate() {
|
||||
for (j, seg_in_hub) in hub.基础序列.read().unwrap().iter().enumerate() {
|
||||
for (j, seg_in_hub) in hub.基础序列.read().iter().enumerate() {
|
||||
let seg_ptr = Arc::as_ptr(seg_in_hub);
|
||||
let found = obs_ref.线段序列().iter().any(|s| Arc::as_ptr(s) == seg_ptr);
|
||||
if !found {
|
||||
@@ -934,10 +925,10 @@ mod tests {
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
|
||||
obs_ref.write().unwrap().增加原始K线(k线);
|
||||
obs_ref.write().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
let obs = obs_ref.read().unwrap();
|
||||
let obs = obs_ref.read();
|
||||
(
|
||||
obs.笔序列.len(),
|
||||
obs.线段序列().len(),
|
||||
@@ -975,28 +966,28 @@ mod tests {
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
|
||||
obs_ref.write().unwrap().增加原始K线(k线);
|
||||
obs_ref.write().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
let 第一次笔数 = obs_ref.read().unwrap().笔序列.len();
|
||||
let 第一次段数 = obs_ref.read().unwrap().线段序列().len();
|
||||
let 第一次笔数 = obs_ref.read().笔序列.len();
|
||||
let 第一次段数 = obs_ref.read().线段序列().len();
|
||||
|
||||
// 重置
|
||||
obs_ref.write().unwrap().重置基础序列();
|
||||
assert_eq!(obs_ref.read().unwrap().笔序列.len(), 0);
|
||||
assert_eq!(obs_ref.read().unwrap().线段序列().len(), 0);
|
||||
obs_ref.write().重置基础序列();
|
||||
assert_eq!(obs_ref.read().笔序列.len(), 0);
|
||||
assert_eq!(obs_ref.read().线段序列().len(), 0);
|
||||
|
||||
// 重新投喂
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
|
||||
obs_ref.write().unwrap().增加原始K线(k线);
|
||||
obs_ref.write().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
let 第二次笔数 = obs_ref.read().unwrap().笔序列.len();
|
||||
let 第二次段数 = obs_ref.read().unwrap().线段序列().len();
|
||||
let 第二次笔数 = obs_ref.read().笔序列.len();
|
||||
let 第二次段数 = obs_ref.read().线段序列().len();
|
||||
|
||||
assert_eq!(第一次笔数, 第二次笔数, "重置后重新投喂笔数不一致");
|
||||
assert_eq!(第一次段数, 第二次段数, "重置后重新投喂线段数不一致");
|
||||
@@ -1011,31 +1002,30 @@ mod tests {
|
||||
fn test_RefCell借用安全性_连续读取不panic() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
// 连续大量读取所有 RefCell 字段,不应 panic
|
||||
for _ in 0..100 {
|
||||
for bi in &obs_ref.笔序列 {
|
||||
let _标识 = bi.标识.read().unwrap().clone();
|
||||
let _wu = bi.武.read().unwrap().clone();
|
||||
let _基础序列 = bi.基础序列.read().unwrap().len();
|
||||
let _特征序列 = bi.特征序列.read().unwrap().len();
|
||||
let _模式 = bi.模式.read().unwrap().clone();
|
||||
let _实中枢 = bi.实_中枢序列.read().unwrap().len();
|
||||
let _虚中枢 = bi.虚_中枢序列.read().unwrap().len();
|
||||
let _合中枢 = bi.合_中枢序列.read().unwrap().len();
|
||||
let _确认K = bi.确认K线.read().unwrap().is_some();
|
||||
let _标识 = bi.标识.read().clone();
|
||||
let _wu = bi.武.read().clone();
|
||||
let _基础序列 = bi.基础序列.read().len();
|
||||
let _特征序列 = bi.特征序列.read().len();
|
||||
let _模式 = bi.模式.read().clone();
|
||||
let _实中枢 = bi.实_中枢序列.read().len();
|
||||
let _虚中枢 = bi.虚_中枢序列.read().len();
|
||||
let _合中枢 = bi.合_中枢序列.read().len();
|
||||
let _确认K = bi.确认K线.read().is_some();
|
||||
let _序号 = bi.序号.load(Ordering::Relaxed);
|
||||
let _有效性 = bi.有效性.load(Ordering::Relaxed);
|
||||
let _短路 = bi.短路修正.load(Ordering::Relaxed);
|
||||
let _前一缺口 = *bi.前一缺口.read().unwrap();
|
||||
let _前一缺口 = *bi.前一缺口.read();
|
||||
}
|
||||
for seg in obs_ref.线段序列() {
|
||||
let _ = seg.标识.read().unwrap().clone();
|
||||
let _ = seg.基础序列.read().unwrap().len();
|
||||
let _ = seg.标识.read().clone();
|
||||
let _ = seg.基础序列.read().len();
|
||||
}
|
||||
}
|
||||
// 到达这里 = 无 panic
|
||||
@@ -1045,27 +1035,26 @@ mod tests {
|
||||
fn test_RefCell借用安全性_交替读写不panic() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
// 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域
|
||||
if !obs_ref.笔序列.is_empty() {
|
||||
let bi = &obs_ref.笔序列[0];
|
||||
// 读
|
||||
let old_mode = bi.模式.read().unwrap().clone();
|
||||
let old_mode = bi.模式.read().clone();
|
||||
// Ref 已释放,可以写
|
||||
*bi.模式.write().unwrap() = "测试模式".into();
|
||||
let new_mode = bi.模式.read().unwrap().clone();
|
||||
*bi.模式.write() = "测试模式".into();
|
||||
let new_mode = bi.模式.read().clone();
|
||||
assert_eq!(new_mode, "测试模式");
|
||||
// 恢复
|
||||
*bi.模式.write().unwrap() = old_mode;
|
||||
*bi.模式.write() = old_mode;
|
||||
|
||||
// 读武
|
||||
let old_wu = bi.武.read().unwrap().clone();
|
||||
let old_wu = bi.武.read().clone();
|
||||
// Ref 已释放,可以检查
|
||||
assert!(Arc::as_ptr(&old_wu) == Arc::as_ptr(&old_wu));
|
||||
assert!(Arc::ptr_eq(&old_wu, &old_wu));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1077,10 +1066,9 @@ mod tests {
|
||||
fn test_缠K到分型的Rc指针一致性() {
|
||||
let obs = 观察者::new("btcusd".into(), 300, Default::default());
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
let obs_ref = obs.read().unwrap();
|
||||
let obs_ref = obs.read();
|
||||
|
||||
// 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中
|
||||
for (i, f) in obs_ref.分型序列.iter().enumerate() {
|
||||
@@ -1196,7 +1184,7 @@ mod tests {
|
||||
99
|
||||
});
|
||||
assert_eq!(handle.join().unwrap(), 99);
|
||||
assert_eq!(dash.标识.read().unwrap().as_str(), "笔");
|
||||
assert_eq!(dash.标识.read().as_str(), "笔");
|
||||
}
|
||||
|
||||
/// 测试:Arc<中枢> 可跨线程传递
|
||||
@@ -1222,11 +1210,11 @@ mod tests {
|
||||
let obs3 = Arc::clone(&obs);
|
||||
|
||||
let h1 = std::thread::spawn(move || {
|
||||
let guard = obs2.read().unwrap();
|
||||
let guard = obs2.read();
|
||||
guard.符号.clone()
|
||||
});
|
||||
let h2 = std::thread::spawn(move || {
|
||||
let guard = obs3.read().unwrap();
|
||||
let guard = obs3.read();
|
||||
guard.周期
|
||||
});
|
||||
|
||||
@@ -1274,7 +1262,7 @@ mod tests {
|
||||
let obs = 观察者::new("ethusd".into(), 7200, Default::default());
|
||||
|
||||
let handle = std::thread::spawn(move || {
|
||||
let guard = obs.read().unwrap();
|
||||
let guard = obs.read();
|
||||
(guard.符号.clone(), guard.周期)
|
||||
});
|
||||
|
||||
@@ -1288,7 +1276,7 @@ mod tests {
|
||||
let mut config = 缠论配置::default();
|
||||
config.加载文件路径 = test_data_path();
|
||||
let obs = 观察者::new("btcusd".into(), 300, config);
|
||||
let mut obs_w = obs.write().unwrap();
|
||||
let mut obs_w = obs.write();
|
||||
obs_w.线段分析层次 = 0;
|
||||
obs_w.重置基础序列();
|
||||
drop(obs_w);
|
||||
@@ -1299,11 +1287,11 @@ mod tests {
|
||||
for i in 0..(data.len() / size).min(500) {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
|
||||
obs.write().unwrap().增加原始K线(k线);
|
||||
obs.write().增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
let obs_r = obs.read().unwrap();
|
||||
let obs_r = obs.read();
|
||||
assert!(obs_r.缠论K线序列.len() > 0, "缠K序列应有数据");
|
||||
assert!(obs_r.分型序列.len() > 0, "分型序列应有数据");
|
||||
assert!(obs_r.线段序列组.is_empty(), "线段序列组应为空");
|
||||
@@ -1329,17 +1317,16 @@ mod tests {
|
||||
|
||||
// 先正常投喂数据
|
||||
obs.write()
|
||||
.unwrap()
|
||||
.读取数据文件(&test_data_path(), Default::default())
|
||||
.unwrap();
|
||||
|
||||
// 设为0后执行静态重新分析,不应 panic
|
||||
let mut obs_w = obs.write().unwrap();
|
||||
let mut obs_w = obs.write();
|
||||
obs_w.线段分析层次 = 0;
|
||||
obs_w.静态重新分析();
|
||||
drop(obs_w);
|
||||
|
||||
let obs_r = obs.read().unwrap();
|
||||
let obs_r = obs.read();
|
||||
assert!(obs_r.分型序列.len() > 0, "静态重新分析后分型序列应有数据");
|
||||
assert!(obs_r.线段序列组.is_empty(), "线段序列组应为空");
|
||||
assert!(
|
||||
@@ -1352,4 +1339,104 @@ mod tests {
|
||||
obs_r.笔序列.len()
|
||||
);
|
||||
}
|
||||
|
||||
/// 创建随机配置(与 main.py 随机配置 对齐)
|
||||
fn 随机配置() -> crate::config::缠论配置 {
|
||||
let mut cfg = crate::config::缠论配置::default().不推送();
|
||||
cfg.缠K合并替换 = fastrand::bool();
|
||||
cfg.笔内元素数量 = fastrand::i64(3..=9);
|
||||
cfg.笔内相同终点取舍 = fastrand::bool();
|
||||
cfg.笔内起始分型包含整笔 = fastrand::bool();
|
||||
cfg.笔内原始K线包含整笔 = fastrand::bool();
|
||||
cfg.笔次级成笔 = fastrand::bool();
|
||||
cfg.笔弱化 = fastrand::bool();
|
||||
cfg.笔弱化_原始数量 = fastrand::i64(3..=9);
|
||||
cfg.线段_非缺口下穿刺 = fastrand::bool();
|
||||
cfg.线段_特征序列忽视老阴老阳 = fastrand::bool();
|
||||
cfg.线段_修正 = fastrand::bool();
|
||||
cfg.线段_缺口后紧急修正 = fastrand::bool();
|
||||
cfg.扩展线段_当下分析 = fastrand::bool();
|
||||
cfg.买卖点激进识别 = fastrand::bool();
|
||||
cfg.买卖点与MACD柱强相关 = fastrand::bool();
|
||||
cfg
|
||||
}
|
||||
|
||||
/// 单线程工作函数(与 main.py 运行单个回测 对齐)
|
||||
fn 运行单个回测(线程编号: usize, limit: usize) {
|
||||
let 配置 = 随机配置();
|
||||
let 观察员 =
|
||||
crate::business::observer::观察者::new(format!("btcusd_{}", 线程编号), 300, 配置);
|
||||
let 方向池 = [
|
||||
crate::types::相对方向::向上,
|
||||
crate::types::相对方向::向上缺口,
|
||||
crate::types::相对方向::衔接向上,
|
||||
crate::types::相对方向::向下,
|
||||
crate::types::相对方向::向下缺口,
|
||||
crate::types::相对方向::衔接向下,
|
||||
];
|
||||
let mut 原始K线 = crate::kline::bar::K线::创建普K(
|
||||
&format!("btcusd_{}", 线程编号),
|
||||
1218124800 + 线程编号 as i64 * 300,
|
||||
8888.55,
|
||||
10000.00,
|
||||
9000.22,
|
||||
9527.33,
|
||||
888.0,
|
||||
0,
|
||||
300,
|
||||
);
|
||||
观察员.write().增加原始K线(原始K线.clone());
|
||||
let 方向序列 = crate::types::相对方向::从序列中机选(limit, &方向池, true);
|
||||
for 方向 in &方向序列 {
|
||||
原始K线 = 原始K线.根据当前K线生成新K线(*方向, false);
|
||||
观察员.write().增加原始K线(原始K线.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_50线程压测_10000K线() {
|
||||
let 线程数 = 50usize;
|
||||
let 每线程K线数 = 10000usize;
|
||||
let start = std::time::Instant::now();
|
||||
let mut 句柄列表 = Vec::with_capacity(线程数);
|
||||
let 异常计数 = std::sync::Arc::new(std::sync::atomic::AtomicUsize::new(0));
|
||||
for i in 1..=线程数 {
|
||||
let 异常计数_ref = 异常计数.clone();
|
||||
句柄列表.push(std::thread::spawn(move || {
|
||||
let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
|
||||
运行单个回测(i, 每线程K线数);
|
||||
}));
|
||||
if result.is_err() {
|
||||
异常计数_ref.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
|
||||
}
|
||||
}));
|
||||
}
|
||||
for h in 句柄列表 {
|
||||
let _ = h.join();
|
||||
}
|
||||
let 耗时 = start.elapsed();
|
||||
let 总K线数 = 线程数 * (每线程K线数 + 1);
|
||||
let 异常数 = 异常计数.load(std::sync::atomic::Ordering::Relaxed);
|
||||
eprintln!(
|
||||
"核心层 {}线程×{}K线 = {} 次 耗时 {:.2?} ({:.0} K/s) 异常线程: {}",
|
||||
线程数,
|
||||
每线程K线数,
|
||||
总K线数,
|
||||
耗时,
|
||||
总K线数 as f64 / 耗时.as_secs_f64(),
|
||||
异常数
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_单线程基准_10000K线() {
|
||||
let start = std::time::Instant::now();
|
||||
运行单个回测(0, 10000);
|
||||
let 耗时 = start.elapsed();
|
||||
eprintln!(
|
||||
"核心层 单线程 10000 K线 耗时 {:.2?} ({:.0} K/s)",
|
||||
耗时,
|
||||
10001.0 / 耗时.as_secs_f64()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,8 +23,8 @@
|
||||
*/
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use crate::warn;
|
||||
use std::collections::HashMap;
|
||||
use tracing;
|
||||
|
||||
/// 事件回调类型 — fn(信号类型, 标识, 周期, 完成K线)
|
||||
type 合成器事件回调 = Box<dyn Fn(String, String, i64, K线) + Send + Sync>;
|
||||
@@ -178,7 +178,7 @@ impl K线合成器 {
|
||||
.map(|s| s.to_string())
|
||||
.or_else(|| e.downcast_ref::<String>().cloned())
|
||||
.unwrap_or_else(|| "未知错误".into());
|
||||
tracing::error!("K线合成器 事件回调 异常: {}", msg);
|
||||
warn!("K线合成器 事件回调 异常: {}", msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,9 +22,9 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::warn;
|
||||
use serde::{Deserialize, Deserializer, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use tracing::warn;
|
||||
|
||||
/// 缠论配置 —— 控制所有分析阶段的行为
|
||||
///
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -43,7 +44,7 @@ pub struct 布林带 {
|
||||
pub 下轨: f64,
|
||||
/// 内部历史队列(不序列化)
|
||||
#[serde(skip)]
|
||||
_历史队列: Vec<f64>,
|
||||
_历史队列: VecDeque<f64>,
|
||||
/// 内部均值缓存(不序列化)
|
||||
#[serde(skip)]
|
||||
_均值: f64,
|
||||
@@ -61,7 +62,7 @@ impl Default for 布林带 {
|
||||
上轨: 0.0,
|
||||
中轨: 0.0,
|
||||
下轨: 0.0,
|
||||
_历史队列: Vec::new(),
|
||||
_历史队列: VecDeque::new(),
|
||||
_均值: 0.0,
|
||||
_方差和: 0.0,
|
||||
}
|
||||
@@ -98,7 +99,7 @@ impl 布林带 {
|
||||
上轨: 价格,
|
||||
中轨: 价格,
|
||||
下轨: 价格,
|
||||
_历史队列: vec![价格],
|
||||
_历史队列: VecDeque::from([价格]),
|
||||
_均值: 价格,
|
||||
_方差和: 0.0,
|
||||
}
|
||||
@@ -110,9 +111,9 @@ impl 布林带 {
|
||||
let 标准差倍数 = prev.标准差倍数;
|
||||
|
||||
let mut q = prev._历史队列.clone();
|
||||
q.push(价格);
|
||||
q.push_back(价格);
|
||||
if q.len() > 周期 {
|
||||
q.remove(0);
|
||||
q.pop_front();
|
||||
}
|
||||
|
||||
let (_均值, _方差和) = if q.len() < 周期 {
|
||||
|
||||
@@ -45,7 +45,7 @@ impl 指标计算器 {
|
||||
let has_prev;
|
||||
{
|
||||
let prev_guard = if n > 1 {
|
||||
Some(全序列[n - 2].指标.read().unwrap())
|
||||
Some(全序列[n - 2].指标.read())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -100,9 +100,9 @@ impl 指标计算器 {
|
||||
信号,
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
当前K线.指标.write().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("macd", val);
|
||||
当前K线.指标.write().设置("macd", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -142,9 +142,9 @@ impl 指标计算器 {
|
||||
Some(配置.相对强弱指数_移动平均线周期),
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
当前K线.指标.write().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("rsi", val);
|
||||
当前K线.指标.write().设置("rsi", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -177,9 +177,9 @@ impl 指标计算器 {
|
||||
配置.随机指标_超卖阈值,
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
当前K线.指标.write().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("kdj", val);
|
||||
当前K线.指标.write().设置("kdj", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -218,9 +218,9 @@ impl 指标计算器 {
|
||||
标准差倍数,
|
||||
))
|
||||
};
|
||||
当前K线.指标.write().unwrap().设置(&key, val.clone());
|
||||
当前K线.指标.write().设置(&key, val.clone());
|
||||
if i == 0 {
|
||||
当前K线.指标.write().unwrap().设置("boll", val);
|
||||
当前K线.指标.write().设置("boll", val);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -245,7 +245,7 @@ impl 指标计算器 {
|
||||
"EMA" => Self::_增量EMA(现有序列, 当前价, *period, 计算方式, &key),
|
||||
_ => continue,
|
||||
};
|
||||
if let Some(均线_map) = 当前K线.指标.write().unwrap().均线_mut() {
|
||||
if let Some(均线_map) = 当前K线.指标.write().均线_mut() {
|
||||
均线_map.insert(key, 值);
|
||||
}
|
||||
}
|
||||
@@ -274,7 +274,7 @@ impl 指标计算器 {
|
||||
}
|
||||
// 尝试从前一根K线获取缓存的SMA
|
||||
if let Some(prev) = 现有序列.last().and_then(|k| {
|
||||
let guard = k.指标.read().unwrap();
|
||||
let guard = k.指标.read();
|
||||
guard.均线().and_then(|m| m.get(prev_key)).copied()
|
||||
}) {
|
||||
let oldest = super::K线取值(
|
||||
@@ -304,7 +304,7 @@ impl 指标计算器 {
|
||||
prev_key: &str,
|
||||
) -> f64 {
|
||||
let 前值 = 现有序列.last().and_then(|k| {
|
||||
let guard = k.指标.read().unwrap();
|
||||
let guard = k.指标.read();
|
||||
guard.均线().and_then(|m| m.get(prev_key)).copied()
|
||||
});
|
||||
match 前值 {
|
||||
@@ -320,8 +320,8 @@ impl 指标计算器 {
|
||||
fn _回填新指标(全序列: &[Arc<K线>], 配置: &缠论配置) {
|
||||
// 作用域化首尾读锁:在回填写循环之前释放,避免读锁与写锁冲突
|
||||
let (新MACD, 新RSI, 新KDJ, 新BOLL) = {
|
||||
let 首K_guard = 全序列[0].指标.read().unwrap();
|
||||
let 尾K_guard = 全序列[全序列.len() - 1].指标.read().unwrap();
|
||||
let 首K_guard = 全序列[0].指标.read();
|
||||
let 尾K_guard = 全序列[全序列.len() - 1].指标.read();
|
||||
|
||||
let 新MACD: Vec<_> = 配置
|
||||
._解析MACD参数列表()
|
||||
@@ -357,7 +357,7 @@ impl 指标计算器 {
|
||||
for i in 0..全序列.len() {
|
||||
let k线 = &全序列[i];
|
||||
let prev_guard = if i > 0 {
|
||||
Some(全序列[i - 1].指标.read().unwrap())
|
||||
Some(全序列[i - 1].指标.read())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
@@ -388,7 +388,7 @@ impl 指标计算器 {
|
||||
*信号,
|
||||
))
|
||||
};
|
||||
k线.指标.write().unwrap().设置(key, val);
|
||||
k线.指标.write().设置(key, val);
|
||||
}
|
||||
|
||||
for (key, 周期) in &新RSI {
|
||||
@@ -419,7 +419,7 @@ impl 指标计算器 {
|
||||
Some(配置.相对强弱指数_移动平均线周期),
|
||||
))
|
||||
};
|
||||
k线.指标.write().unwrap().设置(key, val);
|
||||
k线.指标.write().设置(key, val);
|
||||
}
|
||||
|
||||
for (key, rsv, k平滑, d平滑) in &新KDJ {
|
||||
@@ -446,7 +446,7 @@ impl 指标计算器 {
|
||||
配置.随机指标_超卖阈值,
|
||||
))
|
||||
};
|
||||
k线.指标.write().unwrap().设置(key, val);
|
||||
k线.指标.write().设置(key, val);
|
||||
}
|
||||
|
||||
for (key, 周期, 标准差倍数) in &新BOLL {
|
||||
@@ -469,7 +469,7 @@ impl 指标计算器 {
|
||||
*标准差倍数,
|
||||
))
|
||||
};
|
||||
k线.指标.write().unwrap().设置(key, val);
|
||||
k线.指标.write().设置(key, val);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,9 +70,7 @@ impl 指标容器 {
|
||||
|
||||
/// 预注册指标(不覆盖已有值)
|
||||
pub fn 注册(&mut self, 名称: &str, 默认值: Option<指标值>) {
|
||||
if !self._数据.contains_key(名称) {
|
||||
self._数据.insert(名称.to_string(), 默认值);
|
||||
}
|
||||
self._数据.entry(名称.to_string()).or_insert(默认值);
|
||||
}
|
||||
|
||||
/// 按名称获取指标值
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// 随机指标 (KDJ)
|
||||
///
|
||||
@@ -58,9 +59,9 @@ pub struct 随机指标 {
|
||||
/// J 值 (3K - 2D)
|
||||
pub J: Option<f64>,
|
||||
/// 历史最高价队列(滑动窗口)
|
||||
pub 历史最高价队列: Vec<f64>,
|
||||
pub 历史最高价队列: VecDeque<f64>,
|
||||
/// 历史最低价队列(滑动窗口)
|
||||
pub 历史最低价队列: Vec<f64>,
|
||||
pub 历史最低价队列: VecDeque<f64>,
|
||||
/// 前一个 RSV(用于平滑递推)
|
||||
pub 前一个RSV: Option<f64>,
|
||||
/// 前一个 K(用于平滑递推)
|
||||
@@ -85,8 +86,8 @@ impl Default for 随机指标 {
|
||||
K: None,
|
||||
D: None,
|
||||
J: None,
|
||||
历史最高价队列: Vec::new(),
|
||||
历史最低价队列: Vec::new(),
|
||||
历史最高价队列: VecDeque::new(),
|
||||
历史最低价队列: VecDeque::new(),
|
||||
前一个RSV: None,
|
||||
前一个K: None,
|
||||
前一个D: None,
|
||||
@@ -124,8 +125,8 @@ impl 随机指标 {
|
||||
K: None,
|
||||
D: None,
|
||||
J: None,
|
||||
历史最高价队列: vec![初始最高价],
|
||||
历史最低价队列: vec![初始最低价],
|
||||
历史最高价队列: VecDeque::from([初始最高价]),
|
||||
历史最低价队列: VecDeque::from([初始最低价]),
|
||||
前一个RSV: None,
|
||||
前一个K: None,
|
||||
前一个D: None,
|
||||
@@ -181,16 +182,16 @@ impl 随机指标 {
|
||||
|
||||
// 更新历史最高价队列
|
||||
let mut 历史最高价 = 前一个KDJ.历史最高价队列.clone();
|
||||
历史最高价.push(当前最高价);
|
||||
历史最高价.push_back(当前最高价);
|
||||
if 历史最高价.len() > N as usize {
|
||||
历史最高价.remove(0);
|
||||
历史最高价.pop_front();
|
||||
}
|
||||
|
||||
// 更新历史最低价队列
|
||||
let mut 历史最低价 = 前一个KDJ.历史最低价队列.clone();
|
||||
历史最低价.push(当前最低价);
|
||||
历史最低价.push_back(当前最低价);
|
||||
if 历史最低价.len() > N as usize {
|
||||
历史最低价.remove(0);
|
||||
历史最低价.pop_front();
|
||||
}
|
||||
|
||||
// RSV
|
||||
@@ -260,8 +261,8 @@ mod tests {
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let kdj = 随机指标::首次计算(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0);
|
||||
assert_eq!(kdj.历史最高价队列, vec![110.0]);
|
||||
assert_eq!(kdj.历史最低价队列, vec![90.0]);
|
||||
assert_eq!(kdj.历史最高价队列, VecDeque::from([110.0]));
|
||||
assert_eq!(kdj.历史最低价队列, VecDeque::from([90.0]));
|
||||
assert_eq!(kdj.K, None);
|
||||
}
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
|
||||
use crate::kline::bar::K线;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
/// 相对强弱指数 (RSI)
|
||||
///
|
||||
@@ -58,7 +59,9 @@ pub struct 相对强弱指数 {
|
||||
/// RSI SMA 值
|
||||
pub RSI_SMA: Option<f64>,
|
||||
/// RSI 历史队列(用于滚动计算)
|
||||
pub RSI历史队列: Vec<f64>,
|
||||
pub RSI历史队列: VecDeque<f64>,
|
||||
/// RSI 历史队列运行和(O(1) SMA)
|
||||
pub RSI和: f64,
|
||||
}
|
||||
|
||||
impl Default for 相对强弱指数 {
|
||||
@@ -77,7 +80,8 @@ impl Default for 相对强弱指数 {
|
||||
下跌幅度: 0.0,
|
||||
平滑系数: 0.0,
|
||||
RSI_SMA: None,
|
||||
RSI历史队列: Vec::new(),
|
||||
RSI历史队列: VecDeque::new(),
|
||||
RSI和: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -106,7 +110,8 @@ impl 相对强弱指数 {
|
||||
下跌幅度: 0.0,
|
||||
平滑系数: 1.0 / 周期 as f64,
|
||||
RSI_SMA: None,
|
||||
RSI历史队列: Vec::new(),
|
||||
RSI历史队列: VecDeque::new(),
|
||||
RSI和: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -167,21 +172,25 @@ impl 相对强弱指数 {
|
||||
};
|
||||
|
||||
// RSI_SMA
|
||||
let (RSI_SMA, RSI历史队列) = match RSI_SMA周期 {
|
||||
let (RSI_SMA, RSI历史队列, RSI和) = match RSI_SMA周期 {
|
||||
Some(sma周期) if sma周期 > 0 => {
|
||||
let mut 队列 = 前一个RSI.RSI历史队列.clone();
|
||||
队列.push(RSI);
|
||||
if 队列.len() > sma周期 as usize {
|
||||
队列.remove(0);
|
||||
let mut sum = 前一个RSI.RSI和;
|
||||
队列.push_back(RSI);
|
||||
sum += RSI;
|
||||
if 队列.len() > sma周期 as usize
|
||||
&& let Some(old) = 队列.pop_front()
|
||||
{
|
||||
sum -= old;
|
||||
}
|
||||
let sma = if 队列.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(队列.iter().sum::<f64>() / 队列.len() as f64)
|
||||
Some(sum / 队列.len() as f64)
|
||||
};
|
||||
(sma, 队列)
|
||||
(sma, 队列, sum)
|
||||
}
|
||||
_ => (None, Vec::new()),
|
||||
_ => (None, VecDeque::new(), 0.0),
|
||||
};
|
||||
|
||||
Self {
|
||||
@@ -199,6 +208,7 @@ impl 相对强弱指数 {
|
||||
平滑系数,
|
||||
RSI_SMA,
|
||||
RSI历史队列,
|
||||
RSI和,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+190
-5
@@ -23,16 +23,18 @@
|
||||
*/
|
||||
|
||||
use crate::indicators::指标容器;
|
||||
use crate::info;
|
||||
use crate::types::相对方向;
|
||||
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::io::Write;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::sync::Arc;
|
||||
|
||||
mod rwlock_container_serde {
|
||||
use parking_lot::RwLock;
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
use std::sync::RwLock;
|
||||
|
||||
/// Serde 序列化辅助(RwLock<指标容器> → 序列化器)
|
||||
pub fn serialize<S>(
|
||||
@@ -42,7 +44,7 @@ mod rwlock_container_serde {
|
||||
where
|
||||
S: Serializer,
|
||||
{
|
||||
val.read().unwrap().serialize(ser)
|
||||
val.read().serialize(ser)
|
||||
}
|
||||
|
||||
/// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>)
|
||||
@@ -115,7 +117,7 @@ impl Clone for K线 {
|
||||
开盘价: self.开盘价,
|
||||
收盘价: self.收盘价,
|
||||
成交量: self.成交量,
|
||||
指标: RwLock::new(self.指标.read().unwrap().clone()),
|
||||
指标: RwLock::new(self.指标.read().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -223,6 +225,7 @@ impl K线 {
|
||||
|
||||
/// 保存K线序列到 DAT 文件
|
||||
pub fn 保存到DAT文件(路径: &str, K线序列: &[&Self]) -> std::io::Result<()> {
|
||||
info!("保存到DAT文件: {}", 路径);
|
||||
let mut f = std::fs::File::create(路径)?;
|
||||
for k in K线序列 {
|
||||
f.write_all(&k.to_bytes())?;
|
||||
@@ -245,7 +248,7 @@ impl K线 {
|
||||
let mut 阳 = 0.0f64;
|
||||
let mut 阴 = 0.0f64;
|
||||
for k in 基序 {
|
||||
if let Some(macd) = k.指标.read().unwrap().macd() {
|
||||
if let Some(macd) = k.指标.read().macd() {
|
||||
let hist = macd.MACD柱;
|
||||
if hist >= 0.0 {
|
||||
阳 += hist;
|
||||
@@ -314,6 +317,73 @@ impl K线 {
|
||||
(true, "K线: 全部字段一致".into())
|
||||
}
|
||||
|
||||
/// 根据当前K线和方向生成下一根K线(与 chan.py 对齐)
|
||||
pub fn 根据当前K线生成新K线(&self, 方向: 相对方向, 居中: bool) -> Self {
|
||||
let 高低差 = self.高 - self.低;
|
||||
let 偏移 = if 居中 {
|
||||
高低差 * 0.5
|
||||
} else {
|
||||
let lo = (高低差 * 0.1279) as i64;
|
||||
let hi = (高低差 * 0.883) as i64;
|
||||
if hi > lo {
|
||||
fastrand::i64(lo..=hi) as f64
|
||||
} else {
|
||||
lo as f64
|
||||
}
|
||||
};
|
||||
let 缺口偏移 = if 居中 {
|
||||
高低差 * 1.5
|
||||
} else {
|
||||
let lo = (高低差 * 1.1279) as i64;
|
||||
let hi = (高低差 * 1.883) as i64;
|
||||
if hi > lo {
|
||||
fastrand::i64(lo..=hi) as f64
|
||||
} else {
|
||||
lo as f64
|
||||
}
|
||||
};
|
||||
let (高, 低) = match 方向 {
|
||||
相对方向::向上 => (self.高 + 偏移, self.低 + 偏移),
|
||||
相对方向::向下 => (self.高 - 偏移, self.低 - 偏移),
|
||||
相对方向::向上缺口 => (self.高 + 缺口偏移, self.低 + 缺口偏移),
|
||||
相对方向::向下缺口 => (self.高 - 缺口偏移, self.低 - 缺口偏移),
|
||||
相对方向::衔接向上 => {
|
||||
let off = 高低差;
|
||||
(self.高 + off, self.高)
|
||||
}
|
||||
相对方向::衔接向下 => {
|
||||
let off = 高低差;
|
||||
(self.低, self.低 - off)
|
||||
}
|
||||
_ => (self.高, self.低),
|
||||
};
|
||||
let 小数点 = [self.开盘价, self.高, self.低, self.收盘价]
|
||||
.iter()
|
||||
.map(|v| {
|
||||
let s = format!("{v}");
|
||||
s.split('.').nth(1).map(|d| d.len()).unwrap_or(0)
|
||||
})
|
||||
.max()
|
||||
.unwrap_or(2);
|
||||
let round = |v: f64| -> f64 {
|
||||
let scale = 10_f64.powi(小数点 as i32);
|
||||
(v * scale).round() / scale
|
||||
};
|
||||
let 开 = round(低 + (高 - 低) * fastrand::f64());
|
||||
let 收 = round(低 + (高 - 低) * fastrand::f64());
|
||||
Self::创建普K(
|
||||
&self.标识,
|
||||
self.时间戳 + self.周期,
|
||||
开,
|
||||
round(高),
|
||||
round(低),
|
||||
收,
|
||||
998.0 * fastrand::f64(),
|
||||
self.序号 + 1,
|
||||
self.周期,
|
||||
)
|
||||
}
|
||||
|
||||
/// 截取Arc<K线>序列中从始到终的片段
|
||||
pub fn 截取rc(序列: &[Arc<Self>], 始: &Arc<Self>, 终: &Arc<Self>) -> Vec<Arc<Self>> {
|
||||
let 始_ptr = Arc::as_ptr(始);
|
||||
@@ -349,6 +419,7 @@ impl std::fmt::Display for K线 {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::{error, info, warn};
|
||||
|
||||
#[test]
|
||||
fn test_方向() {
|
||||
@@ -385,4 +456,118 @@ mod tests {
|
||||
assert_eq!(result.get("阴"), Some(&0.0));
|
||||
assert_eq!(result.get("总"), Some(&0.0));
|
||||
}
|
||||
|
||||
// ---- 根据当前K线生成新K线 ----
|
||||
|
||||
#[test]
|
||||
fn test_生成K线_居中向上() {
|
||||
let bar = K线::创建普K(
|
||||
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
|
||||
);
|
||||
let new = bar.根据当前K线生成新K线(相对方向::向上, true);
|
||||
// 居中: 偏移 = (50200-49800)*0.5 = 200
|
||||
assert!((new.高 - 50400.0).abs() < 1.0); // 50200 + 200
|
||||
assert!((new.低 - 50000.0).abs() < 1.0); // 49800 + 200
|
||||
assert_eq!(new.序号, 1);
|
||||
assert_eq!(new.时间戳, 1300);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成K线_居中向下() {
|
||||
let bar = K线::创建普K(
|
||||
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
|
||||
);
|
||||
let new = bar.根据当前K线生成新K线(相对方向::向下, true);
|
||||
assert!((new.高 - 50000.0).abs() < 1.0); // 50200 - 200
|
||||
assert!((new.低 - 49600.0).abs() < 1.0); // 49800 - 200
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成K线_居中向上缺口() {
|
||||
let bar = K线::创建普K(
|
||||
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
|
||||
);
|
||||
let new = bar.根据当前K线生成新K线(相对方向::向上缺口, true);
|
||||
// 居中缺口: 偏移 = 400*1.5 = 600
|
||||
assert!((new.高 - 50800.0).abs() < 1.0); // 50200 + 600
|
||||
assert!((new.低 - 50400.0).abs() < 1.0); // 49800 + 600
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成K线_衔接向上() {
|
||||
let bar = K线::创建普K(
|
||||
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
|
||||
);
|
||||
let new = bar.根据当前K线生成新K线(相对方向::衔接向上, true);
|
||||
let 高低差 = 50200.0 - 49800.0;
|
||||
assert!((new.高 - (50200.0 + 高低差)).abs() < 1.0);
|
||||
assert!((new.低 - 50200.0).abs() < 1.0); // 衔接向上: 低 = 原高
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成K线_衔接向下() {
|
||||
let bar = K线::创建普K(
|
||||
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
|
||||
);
|
||||
let new = bar.根据当前K线生成新K线(相对方向::衔接向下, true);
|
||||
assert!((new.高 - 49800.0).abs() < 1.0); // 衔接向下: 高 = 原低
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_生成K线_非居中随机范围() {
|
||||
let bar = K线::创建普K(
|
||||
"test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
|
||||
);
|
||||
// 非居中:偏移在 [高低差*0.1279, 高低差*0.883] 范围内随机
|
||||
for _ in 0..20 {
|
||||
let new = bar.根据当前K线生成新K线(相对方向::向上, false);
|
||||
assert!(new.高 > bar.高, "向上:新高应高于原高");
|
||||
assert!(new.低 > bar.低, "向上:新低应高于原低");
|
||||
let 偏移 = new.高 - bar.高;
|
||||
let 高低差 = bar.高 - bar.低;
|
||||
let lo = 高低差 * 0.1279;
|
||||
let hi = 高低差 * 0.883;
|
||||
assert!(
|
||||
偏移 >= lo && 偏移 <= hi + 1.0,
|
||||
"偏移 {偏移} 应在 [{lo}, {hi}] 范围内"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 从序列中机选 ----
|
||||
|
||||
#[test]
|
||||
fn test_从序列中机选_可重复() {
|
||||
let dirs = vec![相对方向::向上, 相对方向::向下, 相对方向::向上缺口];
|
||||
let result = 相对方向::从序列中机选(5, &dirs, true);
|
||||
assert_eq!(result.len(), 5);
|
||||
for d in &result {
|
||||
assert!(dirs.contains(d));
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_从序列中机选_不可重复() {
|
||||
let dirs = vec![相对方向::向上, 相对方向::向下, 相对方向::向上缺口];
|
||||
let result = 相对方向::从序列中机选(3, &dirs, false);
|
||||
assert_eq!(result.len(), 3);
|
||||
for (i, d) in result.iter().enumerate() {
|
||||
for prev in result[..i].iter() {
|
||||
assert_ne!(prev, d, "重复方向: {:?}", d);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "数量超过可选方向数")]
|
||||
fn test_从序列中机选_数量超限() {
|
||||
let dirs = vec![相对方向::向上, 相对方向::向下];
|
||||
相对方向::从序列中机选(3, &dirs, false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_从序列中机选_空序列() {
|
||||
let result = 相对方向::从序列中机选(0, &[], true);
|
||||
assert!(result.is_empty());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,9 +29,10 @@ use crate::structure::fractal_obj::分型;
|
||||
use crate::types::SyncF64;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use parking_lot::RwLock;
|
||||
use std::collections::HashSet;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicI64, Ordering};
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 缠论K线 — 经包含处理过后的K线
|
||||
///
|
||||
@@ -74,15 +75,15 @@ impl Clone for 缠论K线 {
|
||||
时间戳: AtomicI64::new(self.时间戳.load(Ordering::Relaxed)),
|
||||
高: SyncF64::new(self.高.get()),
|
||||
低: SyncF64::new(self.低.get()),
|
||||
方向: RwLock::new(*self.方向.read().unwrap()),
|
||||
分型: RwLock::new(*self.分型.read().unwrap()),
|
||||
方向: RwLock::new(*self.方向.read()),
|
||||
分型: RwLock::new(*self.分型.read()),
|
||||
周期: self.周期,
|
||||
标识: self.标识.clone(),
|
||||
分型特征值: SyncF64::new(self.高.get()),
|
||||
原始起始序号: self.原始起始序号,
|
||||
原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)),
|
||||
标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())),
|
||||
买卖点信息: RwLock::new(self.买卖点信息.read().unwrap().clone()),
|
||||
标的K线: RwLock::new(Arc::clone(&self.标的K线.read())),
|
||||
买卖点信息: RwLock::new(self.买卖点信息.read().clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,10 +98,9 @@ impl std::fmt::Display for 缠论K线 {
|
||||
self.序号.load(Ordering::Relaxed),
|
||||
self.分型
|
||||
.read()
|
||||
.unwrap()
|
||||
.map_or("None".to_string(), |fx| fx.to_string()),
|
||||
self.周期,
|
||||
*self.方向.read().unwrap(),
|
||||
*self.方向.read(),
|
||||
self.时间戳.load(Ordering::Relaxed),
|
||||
format_f64_g(self.高.get()),
|
||||
format_f64_g(self.低.get())
|
||||
@@ -116,23 +116,23 @@ impl 缠论K线 {
|
||||
时间戳: AtomicI64::new(self.时间戳.load(Ordering::Relaxed)),
|
||||
高: SyncF64::new(self.高.get()),
|
||||
低: SyncF64::new(self.低.get()),
|
||||
方向: RwLock::new(*self.方向.read().unwrap()),
|
||||
分型: RwLock::new(*self.分型.read().unwrap()),
|
||||
方向: RwLock::new(*self.方向.read()),
|
||||
分型: RwLock::new(*self.分型.read()),
|
||||
周期: self.周期,
|
||||
标识: self.标识.clone(),
|
||||
分型特征值: SyncF64::new(self.高.get()),
|
||||
原始起始序号: self.原始起始序号,
|
||||
原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)),
|
||||
标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())),
|
||||
买卖点信息: RwLock::new(self.买卖点信息.read().unwrap().clone()),
|
||||
标的K线: RwLock::new(Arc::clone(&self.标的K线.read())),
|
||||
买卖点信息: RwLock::new(self.买卖点信息.read().clone()),
|
||||
}
|
||||
}
|
||||
|
||||
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
|
||||
pub fn 与MACD柱子匹配(&self) -> bool {
|
||||
let 标 = self.标的K线.read().unwrap();
|
||||
let 容器 = 标.指标.read().unwrap();
|
||||
match *self.分型.read().unwrap() {
|
||||
let 标 = self.标的K线.read();
|
||||
let 容器 = 标.指标.read();
|
||||
match *self.分型.read() {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(macd) = 容器.macd() {
|
||||
macd.MACD柱 < 0.0
|
||||
@@ -153,9 +153,9 @@ impl 缠论K线 {
|
||||
|
||||
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
|
||||
pub fn 与RSI匹配(&self) -> bool {
|
||||
let 标 = self.标的K线.read().unwrap();
|
||||
let 容器 = 标.指标.read().unwrap();
|
||||
match *self.分型.read().unwrap() {
|
||||
let 标 = self.标的K线.read();
|
||||
let 容器 = 标.指标.read();
|
||||
match *self.分型.read() {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(rsi) = 容器.rsi() {
|
||||
match (rsi.RSI, rsi.RSI_SMA) {
|
||||
@@ -182,9 +182,9 @@ impl 缠论K线 {
|
||||
|
||||
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
|
||||
pub fn 与KDJ匹配(&self) -> bool {
|
||||
let 标 = self.标的K线.read().unwrap();
|
||||
let 容器 = 标.指标.read().unwrap();
|
||||
match *self.分型.read().unwrap() {
|
||||
let 标 = self.标的K线.read();
|
||||
let 容器 = 标.指标.read();
|
||||
match *self.分型.read() {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(kdj) = 容器.kdj() {
|
||||
match (kdj.K, kdj.D) {
|
||||
@@ -356,12 +356,12 @@ impl 缠论K线 {
|
||||
// 逆序包含时更新时间和标的K线
|
||||
if 关系 != 相对方向::顺 {
|
||||
当前缠K.时间戳.store(当前普K.时间戳, Ordering::Relaxed);
|
||||
*当前缠K.标的K线.write().unwrap() = Arc::clone(当前普K);
|
||||
*当前缠K.标的K线.write() = Arc::clone(当前普K);
|
||||
}
|
||||
当前缠K.高.set(取值函数(当前缠K.高.get(), 当前普K.高));
|
||||
当前缠K.低.set(取值函数(当前缠K.低.get(), 当前普K.低));
|
||||
当前缠K.原始结束序号.store(当前普K.序号, Ordering::Relaxed);
|
||||
*当前缠K.方向.write().unwrap() = 当前普K.方向();
|
||||
*当前缠K.方向.write() = 当前普K.方向();
|
||||
|
||||
if let Some(之前) = 之前缠K {
|
||||
当前缠K
|
||||
@@ -466,29 +466,29 @@ impl 缠论K线 {
|
||||
let 结构 = 分型结构::分析(&*左, &*中, &*右, false, false);
|
||||
|
||||
// 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型
|
||||
*缠K序列[idx - 2].分型.write().unwrap() = 结构;
|
||||
*缠K序列[idx - 2].分型.write() = 结构;
|
||||
|
||||
if let Some(结构) = 结构 {
|
||||
match 结构 {
|
||||
分型结构::底 => {
|
||||
缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].低.get());
|
||||
缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].高.get());
|
||||
*缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::顶);
|
||||
*缠K序列[idx - 1].分型.write() = Some(分型结构::顶);
|
||||
}
|
||||
分型结构::顶 => {
|
||||
缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].高.get());
|
||||
缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].低.get());
|
||||
*缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::底);
|
||||
*缠K序列[idx - 1].分型.write() = Some(分型结构::底);
|
||||
}
|
||||
分型结构::上 => {
|
||||
缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].高.get());
|
||||
缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].高.get());
|
||||
*缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::顶);
|
||||
*缠K序列[idx - 1].分型.write() = Some(分型结构::顶);
|
||||
}
|
||||
分型结构::下 => {
|
||||
缠K序列[idx - 2].分型特征值.set(缠K序列[idx - 2].低.get());
|
||||
缠K序列[idx - 1].分型特征值.set(缠K序列[idx - 1].低.get());
|
||||
*缠K序列[idx - 1].分型.write().unwrap() = Some(分型结构::底);
|
||||
*缠K序列[idx - 1].分型.write() = Some(分型结构::底);
|
||||
}
|
||||
分型结构::散 => {}
|
||||
}
|
||||
@@ -573,23 +573,23 @@ impl 缠论K线 {
|
||||
),
|
||||
);
|
||||
}
|
||||
if *self.方向.read().unwrap() != *other.方向.read().unwrap() {
|
||||
if *self.方向.read() != *other.方向.read() {
|
||||
return (
|
||||
false,
|
||||
format!(
|
||||
"缠论K线: [方向] 不等 A={},B={}",
|
||||
self.方向.read().unwrap(),
|
||||
other.方向.read().unwrap()
|
||||
self.方向.read(),
|
||||
other.方向.read()
|
||||
),
|
||||
);
|
||||
}
|
||||
if *self.分型.read().unwrap() != *other.分型.read().unwrap() {
|
||||
if *self.分型.read() != *other.分型.read() {
|
||||
return (
|
||||
false,
|
||||
format!(
|
||||
"缠论K线: [分型] 不等 A={:?},B={:?}",
|
||||
self.分型.read().unwrap(),
|
||||
other.分型.read().unwrap()
|
||||
self.分型.read(),
|
||||
other.分型.read()
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -636,17 +636,13 @@ impl 缠论K线 {
|
||||
);
|
||||
}
|
||||
// 标的K线 递归
|
||||
let (eq, msg) = self
|
||||
.标的K线
|
||||
.read()
|
||||
.unwrap()
|
||||
.相等(&other.标的K线.read().unwrap(), 浮点容差);
|
||||
let (eq, msg) = self.标的K线.read().相等(&other.标的K线.read(), 浮点容差);
|
||||
if !eq {
|
||||
return (false, format!("缠论K线: 标的K线子项异常 >> {msg}"));
|
||||
}
|
||||
// 买卖点信息
|
||||
let a_guard = self.买卖点信息.read().unwrap();
|
||||
let b_guard = other.买卖点信息.read().unwrap();
|
||||
let a_guard = self.买卖点信息.read();
|
||||
let b_guard = other.买卖点信息.read();
|
||||
let a_set: std::collections::HashSet<&String> = a_guard.iter().collect();
|
||||
let b_set: std::collections::HashSet<&String> = b_guard.iter().collect();
|
||||
if a_set != b_set {
|
||||
@@ -654,8 +650,8 @@ impl 缠论K线 {
|
||||
false,
|
||||
format!(
|
||||
"缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}",
|
||||
self.买卖点信息.read().unwrap(),
|
||||
other.买卖点信息.read().unwrap()
|
||||
self.买卖点信息.read(),
|
||||
other.买卖点信息.read()
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ pub mod business;
|
||||
pub mod config;
|
||||
pub mod indicators;
|
||||
pub mod kline;
|
||||
pub mod log;
|
||||
pub mod structure;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
|
||||
@@ -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
@@ -80,10 +80,9 @@ fn 测试_读取数据(文件路径: &str) {
|
||||
let 观察员 = 观察者::new("".into(), 0, 缠论配置::default());
|
||||
观察员
|
||||
.write()
|
||||
.unwrap()
|
||||
.读取数据文件(文件路径, 配置)
|
||||
.expect("读取数据文件失败");
|
||||
let 观察员 = 观察员.read().unwrap();
|
||||
let 观察员 = 观察员.read();
|
||||
let 消耗用时 = 启动时间.elapsed();
|
||||
println!(
|
||||
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
|
||||
@@ -160,7 +159,7 @@ fn 测试_周期合成(文件路径: &str) {
|
||||
// Display stats per period
|
||||
for &p in &[周期, 周期 * 5, 周期 * 5 * 6] {
|
||||
if let Some(观察员) = 多级别分析.获取观察者(p) {
|
||||
let 观察员 = 观察员.read().unwrap();
|
||||
let 观察员 = 观察员.read();
|
||||
println!(
|
||||
"周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}",
|
||||
p,
|
||||
|
||||
+401
-296
File diff suppressed because it is too large
Load Diff
@@ -25,10 +25,10 @@
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use crate::warn;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tracing::warn;
|
||||
|
||||
/// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取
|
||||
pub static 分型模式: AtomicBool = AtomicBool::new(true);
|
||||
@@ -75,7 +75,7 @@ impl 分型 {
|
||||
右.时间戳.load(Ordering::Relaxed),
|
||||
);
|
||||
}
|
||||
let 结构 = 中.分型.read().unwrap().unwrap_or(分型结构::散);
|
||||
let 结构 = 中.分型.read().unwrap_or(分型结构::散);
|
||||
let 时间戳 = 中.时间戳.load(Ordering::Relaxed);
|
||||
let 分型特征值 = 中.分型特征值.get();
|
||||
Self {
|
||||
@@ -102,7 +102,7 @@ impl 分型 {
|
||||
if 分型模式.load(Ordering::Relaxed) {
|
||||
self.结构
|
||||
} else {
|
||||
self.中.分型.read().unwrap().unwrap_or(分型结构::散) // FIXME 错误
|
||||
self.中.分型.read().unwrap_or(分型结构::散) // FIXME 错误
|
||||
}
|
||||
}
|
||||
|
||||
@@ -157,19 +157,17 @@ impl 分型 {
|
||||
|
||||
if let (Some(左), Some(右)) = (&self.左, &self.右) {
|
||||
if self.结构() == 分型结构::底 {
|
||||
if 右.标的K线.read().unwrap().收盘价 > 左.标的K线.read().unwrap().高 {
|
||||
if 右.标的K线.read().收盘价 > 左.标的K线.read().高 {
|
||||
return "强";
|
||||
} else if 右.标的K线.read().unwrap().收盘价 > self.中.标的K线.read().unwrap().高
|
||||
{
|
||||
} else if 右.标的K线.read().收盘价 > self.中.标的K线.read().高 {
|
||||
return "中";
|
||||
} else {
|
||||
return "弱";
|
||||
}
|
||||
} else if self.结构() == 分型结构::顶 {
|
||||
if 右.标的K线.read().unwrap().收盘价 < 左.标的K线.read().unwrap().低 {
|
||||
if 右.标的K线.read().收盘价 < 左.标的K线.read().低 {
|
||||
return "强";
|
||||
} else if 右.标的K线.read().unwrap().收盘价 < self.中.标的K线.read().unwrap().低
|
||||
{
|
||||
} else if 右.标的K线.read().收盘价 < self.中.标的K线.read().低 {
|
||||
return "中";
|
||||
} else {
|
||||
return "弱";
|
||||
@@ -183,12 +181,12 @@ impl 分型 {
|
||||
pub fn 与MACD柱子分型匹配(&self) -> bool {
|
||||
if let (Some(左), Some(右)) = (&self.左, &self.右) {
|
||||
if self.结构() == 分型结构::底 {
|
||||
let 左_k = 左.标的K线.read().unwrap();
|
||||
let 中_k = self.中.标的K线.read().unwrap();
|
||||
let 右_k = 右.标的K线.read().unwrap();
|
||||
let 左_m = 左_k.指标.read().unwrap();
|
||||
let 中_m = 中_k.指标.read().unwrap();
|
||||
let 右_m = 右_k.指标.read().unwrap();
|
||||
let 左_k = 左.标的K线.read();
|
||||
let 中_k = self.中.标的K线.read();
|
||||
let 右_k = 右.标的K线.read();
|
||||
let 左_m = 左_k.指标.read();
|
||||
let 中_m = 中_k.指标.read();
|
||||
let 右_m = 右_k.指标.read();
|
||||
if let (Some(左macd), Some(中macd), Some(右macd)) =
|
||||
(左_m.macd(), 中_m.macd(), 右_m.macd())
|
||||
{
|
||||
@@ -196,12 +194,12 @@ impl 分型 {
|
||||
}
|
||||
}
|
||||
if self.结构() == 分型结构::顶 {
|
||||
let 左_k = 左.标的K线.read().unwrap();
|
||||
let 中_k = self.中.标的K线.read().unwrap();
|
||||
let 右_k = 右.标的K线.read().unwrap();
|
||||
let 左_m = 左_k.指标.read().unwrap();
|
||||
let 中_m = 中_k.指标.read().unwrap();
|
||||
let 右_m = 右_k.指标.read().unwrap();
|
||||
let 左_k = 左.标的K线.read();
|
||||
let 中_k = self.中.标的K线.read();
|
||||
let 右_k = 右.标的K线.read();
|
||||
let 左_m = 左_k.指标.read();
|
||||
let 中_m = 中_k.指标.read();
|
||||
let 右_m = 右_k.指标.read();
|
||||
if let (Some(左macd), Some(中macd), Some(右macd)) =
|
||||
(左_m.macd(), 中_m.macd(), 右_m.macd())
|
||||
{
|
||||
@@ -214,7 +212,7 @@ impl 分型 {
|
||||
|
||||
/// 判断两个分型是否匹配
|
||||
pub fn 判断分型(左: &Arc<分型>, 右: &Arc<分型>, _模式: &str) -> bool {
|
||||
Arc::as_ptr(左) == Arc::as_ptr(右)
|
||||
Arc::ptr_eq(左, 右)
|
||||
}
|
||||
|
||||
/// 从缠K序列中获取以指定缠K为中元素的分型
|
||||
@@ -222,9 +220,7 @@ impl 分型 {
|
||||
K线序列: &[Arc<缠论K线>],
|
||||
中: &Arc<缠论K线>,
|
||||
) -> Option<Self> {
|
||||
let idx = K线序列
|
||||
.iter()
|
||||
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(中))?;
|
||||
let idx = K线序列.iter().position(|k| Arc::ptr_eq(k, 中))?;
|
||||
let 左 = if idx > 0 {
|
||||
Some(Arc::clone(&K线序列[idx - 1]))
|
||||
} else {
|
||||
|
||||
@@ -26,9 +26,10 @@ use crate::structure::dash_line::虚线;
|
||||
use crate::structure::feat_fractal::特征分型;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::{分型结构, 相对方向};
|
||||
use parking_lot::RwLock;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::AtomicI64;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::{Arc, RwLock};
|
||||
|
||||
/// 线段特征 — 特征序列元素,内部是虚线的集合。
|
||||
///
|
||||
@@ -62,7 +63,7 @@ impl Clone for 线段特征 {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
序号: AtomicI64::new(self.序号.load(Ordering::Relaxed)),
|
||||
标识: RwLock::new(self.标识.read().unwrap().clone()),
|
||||
标识: RwLock::new(self.标识.read().clone()),
|
||||
线段方向: self.线段方向,
|
||||
基础序列: self.基础序列.clone(),
|
||||
}
|
||||
@@ -82,7 +83,7 @@ impl 线段特征 {
|
||||
|
||||
/// 图表标题 — 返回标识字符串
|
||||
pub fn 图表标题(&self) -> String {
|
||||
self.标识.read().unwrap().clone()
|
||||
self.标识.read().clone()
|
||||
}
|
||||
|
||||
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型
|
||||
@@ -118,47 +119,31 @@ impl 线段特征 {
|
||||
/// 武 — 取特征序列元素中分型特征值最大/最小的武分型
|
||||
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
|
||||
pub fn 武(&self) -> Arc<分型> {
|
||||
if self.线段方向.是否向上() {
|
||||
self.基础序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.分型特征值
|
||||
.partial_cmp(&b.武.read().unwrap().分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| {
|
||||
a.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.时间戳()
|
||||
.cmp(&b.武.read().unwrap().时间戳())
|
||||
})
|
||||
})
|
||||
.map(|x| x.武.read().unwrap().clone())
|
||||
.unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone())
|
||||
let best = if self.线段方向.是否向上() {
|
||||
self.基础序列.iter().max_by(|a, b| {
|
||||
let a_武 = a.武.read();
|
||||
let b_武 = b.武.read();
|
||||
a_武
|
||||
.分型特征值
|
||||
.partial_cmp(&b_武.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| a_武.时间戳().cmp(&b_武.时间戳()))
|
||||
})
|
||||
} else {
|
||||
self.基础序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
b.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.分型特征值
|
||||
.partial_cmp(&a.武.read().unwrap().分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| {
|
||||
a.武
|
||||
.read()
|
||||
.unwrap()
|
||||
.时间戳()
|
||||
.cmp(&b.武.read().unwrap().时间戳())
|
||||
})
|
||||
})
|
||||
.map(|x| x.武.read().unwrap().clone())
|
||||
.unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone())
|
||||
}
|
||||
self.基础序列.iter().max_by(|a, b| {
|
||||
let a_武 = a.武.read();
|
||||
let b_武 = b.武.read();
|
||||
b_武
|
||||
.分型特征值
|
||||
.partial_cmp(&a_武.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
.then_with(|| a_武.时间戳().cmp(&b_武.时间戳()))
|
||||
})
|
||||
};
|
||||
best.map_or_else(
|
||||
|| self.基础序列[0].武.read().clone(),
|
||||
|x| x.武.read().clone(),
|
||||
)
|
||||
}
|
||||
|
||||
/// 高 — 文和武中分型特征值的较大者
|
||||
@@ -197,7 +182,7 @@ impl 线段特征 {
|
||||
if let Some(pos) = self
|
||||
.基础序列
|
||||
.iter()
|
||||
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(待删除虚线))
|
||||
.position(|x| Arc::ptr_eq(x, 待删除虚线))
|
||||
{
|
||||
self.基础序列.remove(pos);
|
||||
Ok(())
|
||||
@@ -256,7 +241,7 @@ impl 线段特征 {
|
||||
.unwrap();
|
||||
let fake = 虚线::创建笔(
|
||||
Arc::clone(&小号虚线.文),
|
||||
大号虚线.武.read().unwrap().clone(),
|
||||
大号虚线.武.read().clone(),
|
||||
false,
|
||||
);
|
||||
结果.pop();
|
||||
@@ -330,13 +315,13 @@ impl 线段特征 {
|
||||
),
|
||||
);
|
||||
}
|
||||
if *self.标识.read().unwrap() != *other.标识.read().unwrap() {
|
||||
if *self.标识.read() != *other.标识.read() {
|
||||
return (
|
||||
false,
|
||||
format!(
|
||||
"线段特征: [标识] 不等 A={},B={}",
|
||||
self.标识.read().unwrap(),
|
||||
other.标识.read().unwrap()
|
||||
self.标识.read(),
|
||||
other.标识.read()
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -381,12 +366,12 @@ impl crate::types::fractal::有高低 for 线段特征 {
|
||||
impl std::fmt::Display for 线段特征 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.基础序列.is_empty() {
|
||||
write!(f, "{}<{}, 空>", self.标识.read().unwrap(), self.线段方向)
|
||||
write!(f, "{}<{}, 空>", self.标识.read(), self.线段方向)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}>",
|
||||
self.标识.read().unwrap(),
|
||||
self.标识.read(),
|
||||
self.线段方向,
|
||||
self.文(),
|
||||
self.武(),
|
||||
|
||||
@@ -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 相对方向 {
|
||||
|
||||
@@ -22,8 +22,8 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
use crate::warn;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tracing::warn;
|
||||
|
||||
/// 分型结构 —— 三根K线构成的结构形态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
|
||||
Reference in New Issue
Block a user