diff --git a/chanlun-py/Cargo.toml b/chanlun-py/Cargo.toml index 36cd243..eb97c49 100644 --- a/chanlun-py/Cargo.toml +++ b/chanlun-py/Cargo.toml @@ -12,7 +12,8 @@ crate-type = ["cdylib"] name = "chanlun" [dependencies] -chanlun = "26.6.1" # { path = "../chanlun" } +chanlun = { path = "../chanlun" } +lru = "0.18" pyo3 = { version = "0.28", features = ["experimental-inspect"] } serde_json = "1" chrono = "0.4" diff --git a/chanlun-py/chanlun.pyi b/chanlun-py/chanlun.pyi index 15fd7a2..c581f47 100644 --- a/chanlun-py/chanlun.pyi +++ b/chanlun-py/chanlun.pyi @@ -6,12 +6,21 @@ from datetime import datetime # ========== Module-level functions ========== +def get_rs_log_level() -> str: ... +def set_rs_log_level(level: str) -> None: ... def get_log_level() -> str: ... def set_log_level(level: str) -> None: ... def get_分型模式() -> bool: ... 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]: ... +def 缺口相等(A: 缺口, B: 缺口, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... +def 线段特征相等(A: 线段特征, B: 线段特征, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... +def 中枢相等(A: 中枢, B: 中枢, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... +def 虚线相等(A: 虚线, B: 虚线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... # ========== Enum-like types ========== diff --git a/chanlun-py/chanlun/__init__.py b/chanlun-py/chanlun/__init__.py index 4eb1244..d79315b 100644 --- a/chanlun-py/chanlun/__init__.py +++ b/chanlun-py/chanlun/__init__.py @@ -37,9 +37,15 @@ __all__ = [ "set_log_level", "get_rs_log_level", "set_rs_log_level", + "K线相等", + "缠论K线相等", + "分型相等", + "缺口相等", + "线段特征相等", + "中枢相等", + "虚线相等", "chan", ] - from ._chanlun import * from ._chanlun import set_log_level as _rs_set_log_level, get_log_level as _rs_get_log_level from . import chan diff --git a/chanlun-py/chanlun/__init__.pyi b/chanlun-py/chanlun/__init__.pyi index 15fd7a2..1a5a5be 100644 --- a/chanlun-py/chanlun/__init__.pyi +++ b/chanlun-py/chanlun/__init__.pyi @@ -5,13 +5,21 @@ 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_分型模式() -> 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]: ... +def 缺口相等(A: 缺口, B: 缺口, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... +def 线段特征相等(A: 线段特征, B: 线段特征, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... +def 中枢相等(A: 中枢, B: 中枢, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... +def 虚线相等(A: 虚线, B: 虚线, 浮点容差: float = 1e-9) -> Tuple[bool, str]: ... # ========== Enum-like types ========== diff --git a/chanlun-py/chanlun/chan.py b/chanlun-py/chanlun/chan.py index fd104ea..9bb3f88 100644 --- a/chanlun-py/chanlun/chan.py +++ b/chanlun-py/chanlun/chan.py @@ -89,6 +89,13 @@ __all__ = [ "布林带", "set_log_level", "get_log_level", + "K线相等", + "缠论K线相等", + "分型相等", + "缺口相等", + "线段特征相等", + "中枢相等", + "虚线相等", ] # 日志级别映射: 名称 → loguru 级别名 @@ -6377,6 +6384,317 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置] return 魔法 +@lru_cache(128) +def K线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """原始K线相等校验:字段完备→浮点容错→普通全等""" + 比对字段 = ["标识", "序号", "周期", "时间戳", "高", "低", "开盘价", "收盘价", "成交量"] + + for 字段 in 比对字段: + a有 = hasattr(A, 字段) + b有 = hasattr(B, 字段) + if a有 and not b有: + return False, f"K线校验:字段[{字段}],A存在属性、B缺失属性" + if not a有 and b有: + return False, f"K线校验:字段[{字段}],B存在属性、A缺失属性" + if not (a有 and b有): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + # 双浮点容错对比 + if isinstance(valA, float) and isinstance(valB, float): + 差值 = abs(valA - valB) + if 差值 > 浮点容差: + return False, f"K线校验:字段[{字段}]浮点超限,容差={浮点容差:.2e},A={valA:.10f},B={valB:.10f},差值={差值:.10f}" + + elif 字段 == "时间戳": + if int(valA) != int(valB): + return False, f"K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + else: + if valA != valB: + return False, f"K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + return True, "K线:全部字段结构、数值校验完全一致" + + +@lru_cache(4096) +def 缠论K线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """缠论K线:基础字段+标的K线递归校验""" + 比对字段 = ["序号", "时间戳", "高", "低", "方向", "分型", "周期", "标识", "分型特征值", "原始起始序号", "原始结束序号", "标的K线", "买卖点信息"] + + for 字段 in 比对字段: + a有 = hasattr(A, 字段) + b有 = hasattr(B, 字段) + if a有 and not b有: + return False, f"缠论K线校验:字段[{字段}],A存在、B缺失属性" + if not a有 and b有: + return False, f"缠论K线校验:字段[{字段}],B存在、A缺失属性" + if not (a有 and b有): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + + if isinstance(valA, float) and isinstance(valB, float): + 差值 = abs(valA - valB) + if 差值 > 浮点容差: + return False, f"缠论K线校验:[{字段}]浮点超限,容差={浮点容差:.2e},A={valA:.10f},B={valB:.10f},差值={差值:.10f}" + elif 字段 == "标的K线": + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"缠论K线校验:[标的K线]单边空值,A={valA is None},B={valB is None}" + eq_flag, msg = K线相等(valA, valB, 浮点容差) + if not eq_flag: + return False, f"缠论K线校验:标的K线子项异常 >> {msg}" + + elif 字段 == "时间戳": + if int(valA) != int(valB): + return False, f"缠论K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + elif 字段 == "方向": + if str(valA) != str(valB): + return False, f"缠论K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + elif 字段 == "分型": + if str(valA) != str(valB): + return False, f"缠论K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + elif 字段 == "买卖点信息": + if set(valA) != set(valB): + return False, f"缠论K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + else: + if valA != valB: + return False, f"缠论K线校验:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + return True, "缠论K线:全部字段、嵌套原始K线校验一致" + + +@lru_cache(4096) +def 分型相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """分型:左/中/右缠论K线递归 + 自有字段""" + 比对字段 = ["左", "中", "右", "_结构", "_时间戳", "_分型特征值"] + + for 字段 in 比对字段: + a存在 = hasattr(A, 字段) + b存在 = hasattr(B, 字段) + if a存在 and not b存在: + return False, f"分型校验:[{字段}]A有属性、B缺失" + if not a存在 and b存在: + return False, f"分型校验:[{字段}]B有属性、A缺失" + if not (a存在 and b存在): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + + if isinstance(valA, float) and isinstance(valB, float): + 差值 = abs(valA - valB) + if 差值 > 浮点容差: + return False, f"分型校验:[{字段}]浮点超限,容差={浮点容差:.2e},A={valA:.10f},B={valB:.10f},差值={差值:.10f}" + elif 字段 in ("左", "中", "右"): + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"分型校验:[{字段}]空值不一致,A={valA is None},B={valB is None}" + eq_ok, msg = 缠论K线相等(valA, valB, 浮点容差) + if not eq_ok: + return False, f"分型校验:[{字段}]缠论K线子项异常 >> {msg}" + + elif 字段 == "_结构": + if str(valA) != str(valB): + return False, f"分型K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + elif 字段 == "_时间戳": + if int(valA) != int(valB): + return False, f"分型K线校验:字段[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + else: + if valA != valB: + return False, f"分型校验:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + return True, "分型:自有字段+三根缠论K线全部校验一致" + + +@lru_cache(4096) +def 缺口相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """缺口:高、低浮点校验""" + 比对字段 = ["高", "低"] + for 字段 in 比对字段: + a有 = hasattr(A, 字段) + b有 = hasattr(B, 字段) + if a有 and not b有: + return False, f"缺口校验:[{字段}]A存在、B缺失属性" + if not a有 and b有: + return False, f"缺口校验:[{字段}]B存在、A缺失属性" + if not (a有 and b有): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + if isinstance(valA, float) and isinstance(valB, float): + 差值 = abs(valA - valB) + if 差值 > 浮点容差: + return False, f"缺口校验:[{字段}]浮点超限,容差={浮点容差:.2e},A={valA:.10f},B={valB:.10f},差值={差值:.10f}" + else: + if valA != valB: + return False, f"缺口校验:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + return True, "缺口:上下沿价格校验完全一致" + + +@lru_cache(4096) +def 线段特征相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """线段特征:基础序列虚线列表逐项校验""" + 比对字段 = ["序号", "标识", "线段方向", "基础序列"] + 标签 = f"线段特征校验[A标识={A.标识}, B标识={B.标识}]" + for 字段 in 比对字段: + a有 = hasattr(A, 字段) + b有 = hasattr(B, 字段) + if a有 and not b有: + return False, f"{标签}:[{字段}]A存在、B缺失属性" + if not a有 and b有: + return False, f"{标签}:[{字段}]B存在、A缺失属性" + if not (a有 and b有): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + if 字段 == "基础序列": + if len(valA) != len(valB): + return False, f"{标签}:[基础序列]列表长度不一致,A长度={len(valA)},B长度={len(valB)}" + for idx, (itemA, itemB) in enumerate(zip(valA, valB)): + eq, msg = 虚线相等(itemA, itemB, 浮点容差) + if not eq: + return False, f"{标签}:基础序列[{idx}]子虚线异常 >> {msg}" + + elif 字段 == "线段方向": + if str(valA) != str(valB): + return False, f"{标签}:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + else: + if valA != valB: + return False, f"{标签}:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + return True, f"{标签}:字段与内部虚线序列全部一致" + + +@lru_cache(4096) +def 中枢相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """中枢:基础序列虚线列表+第三买卖线单虚线""" + 比对字段 = ["序号", "标识", "级别", "基础序列", "第三买卖线", "本级_第三买卖线"] + 标签 = f"中枢校验[A标识={A.标识}, B标识={B.标识}]" + for 字段 in 比对字段: + a有 = hasattr(A, 字段) + b有 = hasattr(B, 字段) + if a有 and not b有: + return False, f"{标签}:[{字段}]A存在、B缺失属性" + if not a有 and b有: + return False, f"{标签}:[{字段}]B存在、A缺失属性" + if not (a有 and b有): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + + if 字段 == "基础序列": + if len(valA) != len(valB): + return False, f"{标签}:[基础序列]长度不一致 A={len(valA)},B={len(valB)}" + for idx, (itemA, itemB) in enumerate(zip(valA, valB)): + eq, msg = 虚线相等(itemA, itemB, 浮点容差) + if not eq: + return False, f"{标签}:基础序列[{idx}]虚线异常 >> {msg}" + elif 字段 in ("第三买卖线", "本级_第三买卖线"): + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"{标签}:[{字段}]空值不一致 A={valA is None},B={valB is None}" + eq, msg = 虚线相等(valA, valB, 浮点容差) + if not eq: + return False, f"{标签}:[{字段}]子虚线异常 >> {msg}" + else: + if valA != valB: + return False, f"{标签}:[{字段}]数值不等,A={repr(valA)},B={repr(valB)}" + + return True, f"{标签}:基础序列+第三买卖线全部校验一致" + + +@lru_cache(4096) +def 虚线相等(A, B, 浮点容差: float = 1e-9) -> tuple[bool, str]: + """虚线(笔/线段):全量字段、分型/缺口/K线/列表嵌套精细化报错""" + 比对字段 = ["标识", "序号", "级别", "文", "武", "有效性", "基础序列", "特征序列", "实_中枢序列", "虚_中枢序列", "合_中枢序列", "确认K线", "模式", "_特征序列_显示", "前一缺口", "前一结束位置", "短路修正"] + 标签 = f"虚线校验[A标识={A.标识}, B标识={B.标识}]" + for 字段 in 比对字段: + a有 = hasattr(A, 字段) + b有 = hasattr(B, 字段) + if a有 and not b有: + return False, f"{标签}:[{字段}]A存在属性、B缺失属性" + if not a有 and b有: + return False, f"{标签}:[{字段}]B存在属性、A缺失属性" + if not (a有 and b有): + continue + + valA = getattr(A, 字段) + valB = getattr(B, 字段) + + # 文/武:分型 + if 字段 in ("文", "武"): + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"{标签}:[{字段}]分型空值不一致 A={valA is None},B={valB is None}" + eq, msg = 分型相等(valA, valB, 浮点容差) + if not eq: + return False, f"{标签}:[{字段}]子分型异常 >> {msg}" + # 前一缺口 + elif 字段 == "前一缺口": + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"{标签}:[前一缺口]空值不一致 A={valA is None},B={valB is None}" + eq, msg = 缺口相等(valA, valB, 浮点容差) + if not eq: + return False, f"{标签}:[前一缺口]子缺口异常 >> {msg}" + # 前一缺口 + elif 字段 == "前一结束位置": + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"{标签}:[前一结束位置]空值不一致 A={valA is None},B={valB is None}" + eq, msg = 虚线相等(valA, valB, 浮点容差) + if not eq: + return False, f"{标签}:[前一结束位置]异常 >> {msg}" + # 确认K线 + elif 字段 == "确认K线": + if valA is None and valB is None: + continue + if valA is None or valB is None: + return False, f"{标签}:[确认K线]空值不一致 A={valA is None},B={valB is None}" + eq, msg = 缠论K线相等(valA, valB, 浮点容差) + if not eq: + return False, f"{标签}:[确认K线]子缠论K线异常 >> {msg}" + # 各类列表 + elif 字段 in ("基础序列", "实_中枢序列", "虚_中枢序列", "合_中枢序列", "特征序列"): + if len(valA) != len(valB): + return False, f"{标签}:[{字段}]列表长度不一致 A={len(valA)},B={len(valB)}" + for idx, (itemA, itemB) in enumerate(zip(valA, valB)): + if itemA is None and itemB is None: + continue + if itemA is None or itemB is None: + return False, f"{标签}:[{字段}][{idx}]单项空值不一致 A={itemA is None},B={itemB is None}" + if 字段 == "基础序列": + eq, msg = 虚线相等(itemA, itemB, 浮点容差) + elif "中枢" in 字段: + eq, msg = 中枢相等(itemA, itemB, 浮点容差) + else: + eq, msg = 线段特征相等(itemA, itemB, 浮点容差) + if not eq: + return False, f"{标签}:[{字段}][{idx}]子项异常 >> {msg}" + # 普通字段 + else: + if valA != valB: + return False, f"{标签}:[{字段}]数值不等 A={repr(valA)},B={repr(valB)}" + + return True, f"{标签}:全字段、所有嵌套子结构校验全部一致" + + if __name__ == "__main__": 当前配置 = 缠论配置.不推送() 当前配置.加载文件路径 = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "tests", "btcusd-300-1761327300-1776327900.nb") diff --git a/chanlun-py/pyproject.toml b/chanlun-py/pyproject.toml index 3ce0773..275a7f8 100644 --- a/chanlun-py/pyproject.toml +++ b/chanlun-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "chanlun" -version = "2606.17" +version = "2606.21" description = "缠论技术分析库 — Rust 高性能实现" readme = { file = "README.md", content-type = "text/markdown" } license = { file = "LICENSE", content-type = "text/plain" } diff --git a/chanlun-py/src/equality_py.rs b/chanlun-py/src/equality_py.rs new file mode 100644 index 0000000..3eb057e --- /dev/null +++ b/chanlun-py/src/equality_py.rs @@ -0,0 +1,808 @@ +/* + * MIT License + * + * Copyright (c) 2026 YuYuKunKun + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +use std::num::NonZeroUsize; +use std::sync::Mutex; + +use lru::LruCache; +use pyo3::prelude::*; + +/// 缓存辅助宏:在调用点创建静态 LruCache,先查后存 +macro_rules! with_cache { + ($cache:ident, $size:literal, $key_expr:expr, $compute:expr) => {{ + use std::sync::LazyLock; + static $cache: LazyLock>> = + LazyLock::new(|| Mutex::new(LruCache::new(NonZeroUsize::new($size).unwrap()))); + let key = $key_expr; + if let Some(cached) = $cache.lock().unwrap().get(&key) { + return Ok(cached.clone()); + } + let result: PyResult<(bool, String)> = $compute; + if let Ok(ref r) = result { + $cache.lock().unwrap().put(key, r.clone()); + } + result + }}; +} + +/// 从 Python 值中提取时间戳(兼容 i64 和 datetime 两种类型) +fn 提取时间戳(val: &Bound<'_, PyAny>) -> PyResult { + if let Ok(ts) = val.extract::() { + return Ok(ts); + } + let ts_f: f64 = val.call_method0("timestamp")?.extract()?; + Ok(ts_f as i64) +} + +/// 从对象获取属性,依次尝试多个候选名 +fn 获取属性任意<'a>( + obj: &'a Bound<'_, PyAny>, + 候选名: &[&str], +) -> PyResult>> { + for name in 候选名 { + if obj.hasattr(name)? { + return Ok(Some(obj.getattr(name)?)); + } + } + Ok(None) +} + +/// 比较两个 Python 值是否为 float(容差比较) +fn 尝试浮点比较( + valA: &Bound<'_, PyAny>, + valB: &Bound<'_, PyAny>, + 容差: f64, +) -> Option> { + if let (Ok(a), Ok(b)) = (valA.extract::(), valB.extract::()) { + if (a - b).abs() > 容差 { + return Some(Ok(( + false, + format!("浮点超限 容差={:.2e} A={:.10},B={:.10}", 容差, a, b), + ))); + } + return Some(Ok((true, String::new()))); + } + None +} + +/// 尝试从对象获取 `标识` 字段,失败返回空字符串 +fn 尝试获取标识(obj: &Bound<'_, PyAny>) -> String { + if let Ok(val) = obj.getattr("标识") + && let Ok(py_str) = val.str() + { + return py_str.extract::().unwrap_or_default(); + } + String::new() +} + +/// None 检查辅助:双方为 None 返回 true,单方为 None 返回 false+消息 +fn 检查空值一致( + valA: &Bound<'_, PyAny>, + valB: &Bound<'_, PyAny>, + 字段: &str, + 标签: &str, +) -> Option<(bool, String)> { + let a_none = valA.is_none(); + let b_none = valB.is_none(); + if a_none && b_none { + return Some((true, String::new())); + } + if a_none || b_none { + return Some(( + false, + format!("{标签}: [{字段}] 空值不一致 A=None={a_none},B=None={b_none}"), + )); + } + None +} + +// ========== K线相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn K线相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_KLINE, + 128, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + // 快速路径 + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + // 回退路径 + let 标签 = "K线校验"; + let 比对字段 = [ + "标识", + "序号", + "周期", + "时间戳", + "高", + "低", + "开盘价", + "收盘价", + "成交量", + ]; + for &字段 in &比对字段 { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{}", m))); + } + } else if 字段 == "时间戳" { + let a = 提取时间戳(&valA).unwrap_or(0); + let b = 提取时间戳(&valB).unwrap_or(0); + if a != b { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 全部字段一致"))) + } + ) +} + +// ========== 缠论K线相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn 缠论K线相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_CHAN_K, + 4096, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + 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}"))); + } + } else if 字段 == "标的K线" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = K线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: 标的K线子项异常 >> {msg}"))); + } + } else if 字段 == "时间戳" { + let a = 提取时间戳(&valA).unwrap_or(0); + let b = 提取时间戳(&valB).unwrap_or(0); + if a != b { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); + } + } else if 字段 == "方向" || 字段 == "分型" { + let sa = valA.str()?.extract::().unwrap_or_default(); + let sb = valB.str()?.extract::().unwrap_or_default(); + if sa != sb { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); + } + } else if 字段 == "买卖点信息" { + let py = A.py(); + let set_a = py.import("builtins")?.getattr("set")?.call1((&valA,))?; + let set_b = py.import("builtins")?.getattr("set")?.call1((&valB,))?; + let eq: bool = set_a.eq(set_b)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 全部字段嵌套校验一致"))) + } + ) +} + +// ========== 分型相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn 分型相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_FRACTAL, + 4096, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let 标签 = "分型校验"; + // Python 分型内部用 _结构/_时间戳/_分型特征值 作为 slot 名,Rust 用 结构/时间戳/分型特征值 作为 getter + for &字段 in &["左", "中", "右"] { + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + if let Some(r) = 检查空值一致(&valA, &valB, 字段, 标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 缠论K线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [{字段}]缠论K线子项异常 >> {msg}"))); + } + } + for &(字段, 字段别名) in &[ + ("_结构", "结构"), + ("_时间戳", "时间戳"), + ("_分型特征值", "分型特征值"), + ] { + // 先尝试 Python 侧的下划线名,再尝试 Rust 侧的无下划线名 + let valA = 获取属性任意(A, &[字段, 字段别名])?; + let valB = 获取属性任意(B, &[字段, 字段别名])?; + let (a有, b有) = (valA.is_some(), valB.is_some()); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = valA.unwrap(); + let valB = valB.unwrap(); + + if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{m}"))); + } + } else if 字段 == "_时间戳" { + let a = 提取时间戳(&valA).unwrap_or(0); + let b = 提取时间戳(&valB).unwrap_or(0); + if a != b { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={a},B={b}"))); + } + } else if 字段 == "_结构" { + let sa = valA.str()?.extract::().unwrap_or_default(); + let sb = valB.str()?.extract::().unwrap_or_default(); + if sa != sb { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 自有字段+三根缠论K线全部校验一致"))) + } + ) +} + +// ========== 缺口相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn 缺口相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_GAP, + 4096, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let 标签 = "缺口校验"; + for &字段 in &["高", "低"] { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + if let Some(r) = 尝试浮点比较(&valA, &valB, 浮点容差) { + let (ok, m) = r?; + if !ok { + return Ok((false, format!("{标签}: [{字段}]{m}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 上下沿价格校验完全一致"))) + } + ) +} + +// ========== 线段特征相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn 线段特征相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_SEG_FEAT, + 4096, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let 标签 = "线段特征校验"; + for &字段 in &["序号", "标识", "线段方向", "基础序列"] { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + + if 字段 == "基础序列" { + let len_a: usize = valA.len()?; + let len_b: usize = valB.len()?; + if len_a != len_b { + return Ok(( + false, + format!("{标签}: [基础序列] 列表长度不一致 A={len_a},B={len_b}"), + )); + } + for idx in 0..len_a { + let itemA = valA.get_item(idx)?; + let itemB = valB.get_item(idx)?; + let (eq, msg) = 虚线相等(&itemA, &itemB, 浮点容差)?; + if !eq { + return Ok(( + false, + format!("{标签}: 基础序列[{idx}]子虚线异常 >> {msg}"), + )); + } + } + } else if 字段 == "线段方向" { + let sa = valA.str()?.extract::().unwrap_or_default(); + let sb = valB.str()?.extract::().unwrap_or_default(); + if sa != sb { + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={sa},B={sb}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 字段与内部虚线序列全部一致"))) + } + ) +} + +// ========== 中枢相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn 中枢相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_HUB, + 4096, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let a标识 = 尝试获取标识(A); + let b标识 = 尝试获取标识(B); + let 标签 = format!("中枢校验[A标识={a标识},B标识={b标识}]"); + for &字段 in &[ + "序号", + "标识", + "级别", + "基础序列", + "第三买卖线", + "本级_第三买卖线", + ] { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + + if 字段 == "基础序列" { + let len_a: usize = valA.len()?; + let len_b: usize = valB.len()?; + if len_a != len_b { + return Ok(( + false, + format!("{标签}: [基础序列] 长度不一致 A={len_a},B={len_b}"), + )); + } + for idx in 0..len_a { + let itemA = valA.get_item(idx)?; + let itemB = valB.get_item(idx)?; + let (eq, msg) = 虚线相等(&itemA, &itemB, 浮点容差)?; + if !eq { + return Ok(( + false, + format!("{标签}: 基础序列[{idx}]虚线异常 >> {msg}"), + )); + } + } + } else if 字段 == "第三买卖线" || 字段 == "本级_第三买卖线" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 虚线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [{字段}]子虚线异常 >> {msg}"))); + } + } else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}] 数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 基础序列+第三买卖线全部校验一致"))) + } + ) +} + +// ========== 虚线相等 ========== + +#[pyfunction] +#[pyo3(signature = (A, B, 浮点容差 = 1e-9))] +fn 虚线相等( + A: &Bound<'_, PyAny>, + B: &Bound<'_, PyAny>, + 浮点容差: f64, +) -> PyResult<(bool, String)> { + with_cache!( + C_DASH, + 4096, + ( + A.as_ptr() as usize, + B.as_ptr() as usize, + 浮点容差.to_bits() as i64 + ), + { + if let (Ok(a), Ok(b)) = ( + A.cast::(), + B.cast::(), + ) { + return Ok(a.borrow().inner.相等(&b.borrow().inner, 浮点容差)); + } + let a标识 = 尝试获取标识(A); + let b标识 = 尝试获取标识(B); + let 标签 = format!("虚线校验[A标识={a标识},B标识={b标识}]"); + let 比对字段 = [ + "标识", + "序号", + "级别", + "文", + "武", + "有效性", + "基础序列", + "特征序列", + "实_中枢序列", + "虚_中枢序列", + "合_中枢序列", + "确认K线", + "模式", + "_特征序列_显示", + "前一缺口", + "前一结束位置", + "短路修正", + ]; + for &字段 in &比对字段 { + let (a有, b有) = (A.hasattr(字段)?, B.hasattr(字段)?); + if a有 && !b有 { + return Ok((false, format!("{标签}: [{字段}] A存在属性 B缺失属性"))); + } + if !a有 && b有 { + return Ok((false, format!("{标签}: [{字段}] B存在属性 A缺失属性"))); + } + if !a有 && !b有 { + continue; + } + let valA = A.getattr(字段)?; + let valB = B.getattr(字段)?; + + // 文/武:分型 + if 字段 == "文" || 字段 == "武" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 分型相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [{字段}]子分型异常 >> {msg}"))); + } + } + // 前一缺口 + else if 字段 == "前一缺口" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 缺口相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [前一缺口]子缺口异常 >> {msg}"))); + } + } + // 前一结束位置 + else if 字段 == "前一结束位置" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 虚线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [前一结束位置]异常 >> {msg}"))); + } + } + // 确认K线 + else if 字段 == "确认K线" { + if let Some(r) = 检查空值一致(&valA, &valB, 字段, &标签) { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = 缠论K线相等(&valA, &valB, 浮点容差)?; + if !eq { + return Ok((false, format!("{标签}: [确认K线]子缠论K线异常 >> {msg}"))); + } + } + // 各类列表 + else if 字段 == "基础序列" + || 字段 == "实_中枢序列" + || 字段 == "虚_中枢序列" + || 字段 == "合_中枢序列" + || 字段 == "特征序列" + { + let len_a: usize = valA.len()?; + let len_b: usize = valB.len()?; + if len_a != len_b { + return Ok(( + false, + format!("{标签}: [{字段}]列表长度不一致 A={len_a},B={len_b}"), + )); + } + for idx in 0..len_a { + let itemA = valA.get_item(idx)?; + let itemB = valB.get_item(idx)?; + if let Some(r) = + 检查空值一致(&itemA, &itemB, &format!("{字段}[{idx}]"), &标签) + { + if !r.0 { + return Ok((false, r.1)); + } else { + continue; + } + } + let (eq, msg) = if 字段 == "基础序列" { + 虚线相等(&itemA, &itemB, 浮点容差)? + } else if 字段.contains("中枢") { + 中枢相等(&itemA, &itemB, 浮点容差)? + } else { + 线段特征相等(&itemA, &itemB, 浮点容差)? + }; + if !eq { + return Ok(( + false, + format!("{标签}: [{字段}][{idx}]子项异常 >> {msg}"), + )); + } + } + } + // 普通字段 + else { + let eq: bool = valA.eq(&valB)?; + if !eq { + let ra = valA.repr()?.extract::().unwrap_or_default(); + let rb = valB.repr()?.extract::().unwrap_or_default(); + return Ok((false, format!("{标签}: [{字段}]数值不等 A={ra},B={rb}"))); + } + } + } + Ok((true, format!("{标签}: 全字段所有嵌套子结构校验一致"))) + } + ) +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_function(wrap_pyfunction!(K线相等, m)?)?; + m.add_function(wrap_pyfunction!(缠论K线相等, m)?)?; + m.add_function(wrap_pyfunction!(分型相等, m)?)?; + m.add_function(wrap_pyfunction!(缺口相等, m)?)?; + m.add_function(wrap_pyfunction!(线段特征相等, m)?)?; + m.add_function(wrap_pyfunction!(中枢相等, m)?)?; + m.add_function(wrap_pyfunction!(虚线相等, m)?)?; + Ok(()) +} diff --git a/chanlun-py/src/lib.rs b/chanlun-py/src/lib.rs index c29af8b..101439f 100644 --- a/chanlun-py/src/lib.rs +++ b/chanlun-py/src/lib.rs @@ -103,6 +103,7 @@ fn init_tracing() { mod algorithm_py; mod business_py; mod config_py; +mod equality_py; mod indicators_py; mod kline_py; mod structure_py; @@ -175,6 +176,8 @@ fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { algorithm_py::register(m)?; // 阶段 7: 业务 business_py::register(m)?; + // 阶段 8: 相等校验函数 + equality_py::register(m)?; Ok(()) } diff --git a/chanlun/src/algorithm/bi.rs b/chanlun/src/algorithm/bi.rs index e68e25b..44cdc03 100644 --- a/chanlun/src/algorithm/bi.rs +++ b/chanlun/src/algorithm/bi.rs @@ -645,11 +645,13 @@ impl 笔 { } if let Some(前一笔) = 笔序列.last() { - let seg = Arc::make_mut(&mut 新笔); - seg.序号 + let 新筆 = Arc::make_mut(&mut 新笔); + 新筆 + .序号 .store(前一笔.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - if seg.武.read().unwrap().左.is_none() || seg.武.read().unwrap().右.is_none() { - seg.有效性.store(false, Ordering::Relaxed); + if 新筆.武.read().unwrap().左.is_none() || 新筆.武.read().unwrap().右.is_none() + { + 新筆.有效性.store(false, Ordering::Relaxed); } if matches!( 前一笔.武.read().unwrap().结构(), diff --git a/chanlun/src/algorithm/hub.rs b/chanlun/src/algorithm/hub.rs index 7107967..efda218 100644 --- a/chanlun/src/algorithm/hub.rs +++ b/chanlun/src/algorithm/hub.rs @@ -387,7 +387,7 @@ impl 中枢 { pub fn 创建( 左: Arc<虚线>, 中: Arc<虚线>, 右: Arc<虚线>, 级别: i64, 标识: &str ) -> Self { - debug_assert!(Self::基础检查(&左, &中, &右), "中枢.创建 基础检查失败"); + assert!(Self::基础检查(&左, &中, &右), "中枢.创建 基础检查失败"); Self::new( 0, format!("{}中枢<{}>", 标识, 中.标识.read().unwrap()), @@ -566,7 +566,7 @@ impl 中枢 { } else { if 候选序列.is_empty() { // 仍在范围内:延伸中枢 - debug_assert!( + assert!( 中枢序列[当前中枢_idx] .基础序列 .read() @@ -615,6 +615,99 @@ impl 中枢 { } } } + + /// 结构化相等校验 — 递归校验基础序列虚线和第三买卖线,返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + if self.序号.load(Ordering::Relaxed) != other.序号.load(Ordering::Relaxed) { + return ( + false, + format!( + "中枢: [序号] 不等 A={},B={}", + self.序号.load(Ordering::Relaxed), + other.序号.load(Ordering::Relaxed) + ), + ); + } + if *self.标识.read().unwrap() != *other.标识.read().unwrap() { + return ( + false, + format!( + "中枢: [标识] 不等 A={},B={}", + self.标识.read().unwrap(), + other.标识.read().unwrap() + ), + ); + } + if self.级别.load(Ordering::Relaxed) != other.级别.load(Ordering::Relaxed) { + return ( + false, + format!( + "中枢: [级别] 不等 A={},B={}", + self.级别.load(Ordering::Relaxed), + other.级别.load(Ordering::Relaxed) + ), + ); + } + // 基础序列 + let a_seq = self.基础序列.read().unwrap(); + let b_seq = other.基础序列.read().unwrap(); + if a_seq.len() != b_seq.len() { + return ( + false, + format!( + "中枢: [基础序列] 长度不一致 A={},B={}", + a_seq.len(), + b_seq.len() + ), + ); + } + for (idx, (a, b)) in a_seq.iter().zip(b_seq.iter()).enumerate() { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("中枢: 基础序列[{idx}]虚线异常 >> {msg}")); + } + } + // 第三买卖线 + let 检查单个 = |名: &str, + a: &Option>, + b: &Option>, + 容差: f64| + -> Result<(), String> { + match (a, b) { + (None, None) => Ok(()), + (Some(x), Some(y)) => { + let (eq, msg) = x.相等(y, 容差); + if eq { + Ok(()) + } else { + Err(format!("中枢: [{名}]子虚线异常 >> {msg}")) + } + } + _ => Err(format!( + "中枢: [{名}]空值不一致 A={},B={}", + a.is_some(), + b.is_some() + )), + } + }; + 检查单个( + "第三买卖线", + &self.第三买卖线.read().unwrap(), + &other.第三买卖线.read().unwrap(), + 浮点容差, + ) + .map_err(|e| (false, e)) + .ok(); + 检查单个( + "本级_第三买卖线", + &self.本级_第三买卖线.read().unwrap(), + &other.本级_第三买卖线.read().unwrap(), + 浮点容差, + ) + .map_err(|e| (false, e)) + .ok(); + (true, "中枢: 全部字段一致".into()) + } } impl std::fmt::Display for 中枢 { diff --git a/chanlun/src/algorithm/segment.rs b/chanlun/src/algorithm/segment.rs index 041b680..f78d9c5 100644 --- a/chanlun/src/algorithm/segment.rs +++ b/chanlun/src/algorithm/segment.rs @@ -785,7 +785,7 @@ impl 线段 { let 之前线段 = 线段序列.last().unwrap(); - debug_assert!( + assert!( 之前线段.特征序列.read().unwrap()[2].is_some() || 之前线段.短路修正.load(Ordering::Relaxed), "线段._向序列中添加[{}], 之前线段.右 = None {}", @@ -849,10 +849,9 @@ impl 线段 { } } - let mut 弹出 = 线段序列.pop().unwrap(); - let seg = Arc::make_mut(&mut 弹出); - *seg.前一结束位置.write().unwrap() = None; - seg.有效性.store(false, Ordering::Relaxed); + let 弹出 = 线段序列.pop().unwrap(); + 弹出.有效性.store(false, Ordering::Relaxed); + *弹出.前一结束位置.write().unwrap() = None; Some(弹出) } @@ -1510,9 +1509,8 @@ impl 线段 { } if Arc::as_ptr(线段序列.last().unwrap()) == Arc::as_ptr(待弹出线段) { - let mut drop = 线段序列.pop().unwrap(); - let seg = Arc::make_mut(&mut drop); - seg.有效性.store(false, Ordering::Relaxed); + let drop = 线段序列.pop().unwrap(); + drop.有效性.store(false, Ordering::Relaxed); Some(drop) } else { panic!("线段._从序列中删除 弹出数据不在列表中 {}", 待弹出线段); @@ -1724,7 +1722,7 @@ impl 线段 { let 进入段 = &阳[阳.len() - 3]; let 离开段 = &阳[阳.len() - 1]; - debug_assert!( + assert!( 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed), "进入段.序号 >= 离开段.序号" ); @@ -1780,7 +1778,7 @@ impl 线段 { { let 进入段 = &当前段.基础序列.read().unwrap()[序号 - 1]; let 离开段 = &阳[阳.len() - 1]; - debug_assert!( + assert!( 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) ); if 进入段.方向() != 离开段.方向() { @@ -1807,9 +1805,7 @@ impl 线段 { // 第三买卖点后盘整背驰 let 进入段 = &阳[阳.len() - 3]; let 离开段 = &阳[阳.len() - 1]; - debug_assert!( - 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) - ); + assert!(进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed)); if 进入段.方向() != 离开段.方向() { return crate::algorithm::divergence::背驰分析::测度背驰( 进入段, 离开段, diff --git a/chanlun/src/kline/bar.rs b/chanlun/src/kline/bar.rs index 136b8f7..230a940 100644 --- a/chanlun/src/kline/bar.rs +++ b/chanlun/src/kline/bar.rs @@ -270,6 +270,50 @@ impl K线 { Some(&序列[始_idx..=终_idx]) } + /// 结构化相等校验 — 比对各字段,浮点字段使用容差比较,返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + if self.标识 != other.标识 { + return ( + false, + format!("K线: [标识] 不等 A={},B={}", self.标识, other.标识), + ); + } + if self.序号 != other.序号 { + return ( + false, + format!("K线: [序号] 不等 A={},B={}", self.序号, other.序号), + ); + } + if self.周期 != other.周期 { + return ( + false, + format!("K线: [周期] 不等 A={},B={}", self.周期, other.周期), + ); + } + if self.时间戳 != other.时间戳 { + return ( + false, + format!("K线: [时间戳] 不等 A={},B={}", self.时间戳, other.时间戳), + ); + } + let 浮点字段 = [ + ("高", self.高, other.高), + ("低", self.低, other.低), + ("开盘价", self.开盘价, other.开盘价), + ("收盘价", self.收盘价, other.收盘价), + ("成交量", self.成交量, other.成交量), + ]; + for (名, a, b) in &浮点字段 { + if (a - b).abs() > 浮点容差 { + return ( + false, + format!("K线: [{名}] 浮点超限 容差={浮点容差:.2e} A={a:.10},B={b:.10}"), + ); + } + } + (true, "K线: 全部字段一致".into()) + } + /// 截取Arc序列中从始到终的片段 pub fn 截取rc(序列: &[Arc], 始: &Arc, 终: &Arc) -> Vec> { let 始_ptr = Arc::as_ptr(始); diff --git a/chanlun/src/kline/chan_kline.rs b/chanlun/src/kline/chan_kline.rs index be76d85..b33a900 100644 --- a/chanlun/src/kline/chan_kline.rs +++ b/chanlun/src/kline/chan_kline.rs @@ -531,6 +531,137 @@ impl 缠论K线 { let 终_idx = 序列.iter().position(|k| std::ptr::eq(Arc::as_ptr(k), 终))?; Some(序列[始_idx..=终_idx].to_vec()) } + + /// 结构化相等校验 — 比对所有字段,浮点容差,递归校验标的K线,返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + if self.序号.load(Ordering::Relaxed) != other.序号.load(Ordering::Relaxed) { + return ( + false, + format!( + "缠论K线: [序号] 不等 A={},B={}", + self.序号.load(Ordering::Relaxed), + other.序号.load(Ordering::Relaxed) + ), + ); + } + if self.时间戳.load(Ordering::Relaxed) != other.时间戳.load(Ordering::Relaxed) { + return ( + false, + format!( + "缠论K线: [时间戳] 不等 A={},B={}", + self.时间戳.load(Ordering::Relaxed), + other.时间戳.load(Ordering::Relaxed) + ), + ); + } + if (self.高.get() - other.高.get()).abs() > 浮点容差 { + return ( + false, + format!( + "缠论K线: [高] 浮点超限 容差={浮点容差:.2e} A={:.10},B={:.10}", + self.高.get(), + other.高.get() + ), + ); + } + if (self.低.get() - other.低.get()).abs() > 浮点容差 { + return ( + false, + format!( + "缠论K线: [低] 浮点超限 容差={浮点容差:.2e} A={:.10},B={:.10}", + self.低.get(), + other.低.get() + ), + ); + } + if *self.方向.read().unwrap() != *other.方向.read().unwrap() { + return ( + false, + format!( + "缠论K线: [方向] 不等 A={},B={}", + self.方向.read().unwrap(), + other.方向.read().unwrap() + ), + ); + } + if *self.分型.read().unwrap() != *other.分型.read().unwrap() { + return ( + false, + format!( + "缠论K线: [分型] 不等 A={:?},B={:?}", + self.分型.read().unwrap(), + other.分型.read().unwrap() + ), + ); + } + if self.周期 != other.周期 { + return ( + false, + format!("缠论K线: [周期] 不等 A={},B={}", self.周期, other.周期), + ); + } + if self.标识 != other.标识 { + return ( + false, + format!("缠论K线: [标识] 不等 A={},B={}", self.标识, other.标识), + ); + } + if (self.分型特征值.get() - other.分型特征值.get()).abs() > 浮点容差 { + return ( + false, + format!( + "缠论K线: [分型特征值] 浮点超限 A={:.10},B={:.10}", + self.分型特征值.get(), + other.分型特征值.get() + ), + ); + } + if self.原始起始序号 != other.原始起始序号 { + return ( + false, + format!( + "缠论K线: [原始起始序号] 不等 A={},B={}", + self.原始起始序号, other.原始起始序号 + ), + ); + } + if self.原始结束序号.load(Ordering::Relaxed) != other.原始结束序号.load(Ordering::Relaxed) + { + return ( + false, + format!( + "缠论K线: [原始结束序号] 不等 A={},B={}", + self.原始结束序号.load(Ordering::Relaxed), + other.原始结束序号.load(Ordering::Relaxed) + ), + ); + } + // 标的K线 递归 + let (eq, msg) = self + .标的K线 + .read() + .unwrap() + .相等(&other.标的K线.read().unwrap(), 浮点容差); + if !eq { + return (false, format!("缠论K线: 标的K线子项异常 >> {msg}")); + } + // 买卖点信息 + let a_guard = self.买卖点信息.read().unwrap(); + let b_guard = other.买卖点信息.read().unwrap(); + 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 { + return ( + false, + format!( + "缠论K线: [买卖点信息] 集合不等 A={:?},B={:?}", + self.买卖点信息.read().unwrap(), + other.买卖点信息.read().unwrap() + ), + ); + } + (true, "缠论K线: 全部字段一致".into()) + } } impl crate::types::fractal::有高低 for 缠论K线 { diff --git a/chanlun/src/structure/dash_line.rs b/chanlun/src/structure/dash_line.rs index 689070c..a2cc6da 100644 --- a/chanlun/src/structure/dash_line.rs +++ b/chanlun/src/structure/dash_line.rs @@ -1150,6 +1150,241 @@ impl 虚线 { (结果, "".into()) } + + /// 结构化相等校验 — 递归校验所有子结构(分型/缺口/缠K/中枢/线段特征/虚线),返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + if *self.标识.read().unwrap() != *other.标识.read().unwrap() { + return ( + false, + format!( + "虚线: [标识] 不等 A={},B={}", + self.标识.read().unwrap(), + other.标识.read().unwrap() + ), + ); + } + if self.序号.load(Ordering::Relaxed) != other.序号.load(Ordering::Relaxed) { + return ( + false, + format!( + "虚线: [序号] 不等 A={},B={}", + self.序号.load(Ordering::Relaxed), + other.序号.load(Ordering::Relaxed) + ), + ); + } + if self.级别.load(Ordering::Relaxed) != other.级别.load(Ordering::Relaxed) { + return ( + false, + format!( + "虚线: [级别] 不等 A={},B={}", + self.级别.load(Ordering::Relaxed), + other.级别.load(Ordering::Relaxed) + ), + ); + } + // 文 + { + let (eq, msg) = self.文.相等(&other.文, 浮点容差); + if !eq { + return (false, format!("虚线: [文]分型异常 >> {msg}")); + } + } + // 武 + { + let (eq, msg) = self + .武 + .read() + .unwrap() + .相等(&other.武.read().unwrap(), 浮点容差); + if !eq { + return (false, format!("虚线: [武]分型异常 >> {msg}")); + } + } + if self.有效性.load(Ordering::Relaxed) != other.有效性.load(Ordering::Relaxed) { + return ( + false, + format!( + "虚线: [有效性] 不等 A={},B={}", + self.有效性.load(Ordering::Relaxed), + other.有效性.load(Ordering::Relaxed) + ), + ); + } + // 基础序列 + { + let a = self.基础序列.read().unwrap(); + let b = other.基础序列.read().unwrap(); + if a.len() != b.len() { + return ( + false, + format!("虚线: [基础序列] 长度不一致 A={},B={}", a.len(), b.len()), + ); + } + for (idx, (x, y)) in a.iter().zip(b.iter()).enumerate() { + let (eq, msg) = x.相等(y, 浮点容差); + if !eq { + return (false, format!("虚线: 基础序列[{idx}]虚线异常 >> {msg}")); + } + } + } + // 特征序列 + { + let a = self.特征序列.read().unwrap(); + let b = other.特征序列.read().unwrap(); + if a.len() != b.len() { + return ( + false, + format!("虚线: [特征序列] 长度不一致 A={},B={}", a.len(), b.len()), + ); + } + for (idx, (x, y)) in a.iter().zip(b.iter()).enumerate() { + match (x, y) { + (None, None) => {} + (Some(xx), Some(yy)) => { + let (eq, msg) = xx.相等(yy, 浮点容差); + if !eq { + return (false, format!("虚线: 特征序列[{idx}]线段特征异常 >> {msg}")); + } + } + _ => return (false, format!("虚线: 特征序列[{idx}]空值不一致")), + } + } + } + // 中枢序列 + let 检查中枢列表 = + |名: &str, a: &[Arc<中枢>], b: &[Arc<中枢>], 容差: f64| -> Result<(), String> { + if a.len() != b.len() { + return Err(format!( + "虚线: [{名}] 长度不一致 A={},B={}", + a.len(), + b.len() + )); + } + for (idx, (x, y)) in a.iter().zip(b.iter()).enumerate() { + let (eq, msg) = x.相等(y, 容差); + if !eq { + return Err(format!("虚线: {名}[{idx}]中枢异常 >> {msg}")); + } + } + Ok(()) + }; + 检查中枢列表( + "实_中枢序列", + &self.实_中枢序列.read().unwrap(), + &other.实_中枢序列.read().unwrap(), + 浮点容差, + ) + .map_err(|e| (false, e)) + .ok(); + 检查中枢列表( + "虚_中枢序列", + &self.虚_中枢序列.read().unwrap(), + &other.虚_中枢序列.read().unwrap(), + 浮点容差, + ) + .map_err(|e| (false, e)) + .ok(); + 检查中枢列表( + "合_中枢序列", + &self.合_中枢序列.read().unwrap(), + &other.合_中枢序列.read().unwrap(), + 浮点容差, + ) + .map_err(|e| (false, e)) + .ok(); + // 确认K线 + match ( + &*self.确认K线.read().unwrap(), + &*other.确认K线.read().unwrap(), + ) { + (None, None) => {} + (Some(a), Some(b)) => { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("虚线: [确认K线]缠论K线异常 >> {msg}")); + } + } + (a, b) => { + return ( + false, + format!( + "虚线: [确认K线]空值不一致 A={},B={}", + a.is_some(), + b.is_some() + ), + ); + } + } + // 模式 + if *self.模式.read().unwrap() != *other.模式.read().unwrap() { + return ( + false, + format!( + "虚线: [模式] 不等 A={},B={}", + self.模式.read().unwrap(), + other.模式.read().unwrap() + ), + ); + } + // _特征序列_显示 + if self._特征序列_显示.load(Ordering::Relaxed) + != other._特征序列_显示.load(Ordering::Relaxed) + { + return (false, "虚线: [_特征序列_显示] 不等".to_string()); + } + // 前一缺口 + match ( + &*self.前一缺口.read().unwrap(), + &*other.前一缺口.read().unwrap(), + ) { + (None, None) => {} + (Some(a), Some(b)) => { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("虚线: [前一缺口]缺口异常 >> {msg}")); + } + } + (a, b) => { + return ( + false, + format!( + "虚线: [前一缺口]空值不一致 A={},B={}", + a.is_some(), + b.is_some() + ), + ); + } + } + // 前一结束位置 + match ( + &*self.前一结束位置.read().unwrap(), + &*other.前一结束位置.read().unwrap(), + ) { + (None, None) => {} + (Some(a), Some(b)) => { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("虚线: [前一结束位置]虚线异常 >> {msg}")); + } + } + (a, b) => { + return ( + false, + format!( + "虚线: [前一结束位置]空值不一致 A={},B={}", + a.is_some(), + b.is_some() + ), + ); + } + } + // 短路修正 + if self.短路修正.load(Ordering::Relaxed) != other.短路修正.load(Ordering::Relaxed) { + return (false, "虚线: [短路修正] 不等".to_string()); + } + (true, "虚线: 全量字段嵌套校验一致".into()) + } } impl std::fmt::Display for 虚线 { diff --git a/chanlun/src/structure/fractal_obj.rs b/chanlun/src/structure/fractal_obj.rs index c97c954..129bd96 100644 --- a/chanlun/src/structure/fractal_obj.rs +++ b/chanlun/src/structure/fractal_obj.rs @@ -66,7 +66,7 @@ impl 分型 { 左: Option>, 中: Arc<缠论K线>, 右: Option> ) -> Self { if let (Some(左), Some(右)) = (&左, &右) { - debug_assert!( + assert!( 左.时间戳.load(Ordering::Relaxed) < 中.时间戳.load(Ordering::Relaxed) && 中.时间戳.load(Ordering::Relaxed) < 右.时间戳.load(Ordering::Relaxed), "分型时间戳断言失败: 左={}, 中={}, 右={}", @@ -256,6 +256,68 @@ impl 分型 { } 分型序列.push(当前分型); } + + /// 结构化相等校验 — 递归校验左/中/右缠伦K线 + 自有缓存字段,返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + match (&self.左, &other.左) { + (None, None) => {} + (Some(a), Some(b)) => { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("分型: [左]缠论K线异常 >> {msg}")); + } + } + (a, b) => { + return ( + false, + format!("分型: [左]空值不一致 A={},B={}", a.is_some(), b.is_some()), + ); + } + } + { + let (eq, msg) = self.中.相等(&other.中, 浮点容差); + if !eq { + return (false, format!("分型: [中]缠论K线异常 >> {msg}")); + } + } + match (&self.右, &other.右) { + (None, None) => {} + (Some(a), Some(b)) => { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("分型: [右]缠论K线异常 >> {msg}")); + } + } + (a, b) => { + return ( + false, + format!("分型: [右]空值不一致 A={},B={}", a.is_some(), b.is_some()), + ); + } + } + if self.结构 != other.结构 { + return ( + false, + format!("分型: [结构] 不等 A={},B={}", self.结构, other.结构), + ); + } + if self.时间戳 != other.时间戳 { + return ( + false, + format!("分型: [时间戳] 不等 A={},B={}", self.时间戳, other.时间戳), + ); + } + if (self.分型特征值 - other.分型特征值).abs() > 浮点容差 { + return ( + false, + format!( + "分型: [分型特征值] 浮点超限 A={:.10},B={:.10}", + self.分型特征值, other.分型特征值 + ), + ); + } + (true, "分型: 全部字段一致".into()) + } } impl crate::types::fractal::有高低 for 分型 { diff --git a/chanlun/src/structure/segment_feat.rs b/chanlun/src/structure/segment_feat.rs index 14f4896..3009607 100644 --- a/chanlun/src/structure/segment_feat.rs +++ b/chanlun/src/structure/segment_feat.rs @@ -316,6 +316,52 @@ impl 线段特征 { } 结果 } + + /// 结构化相等校验 — 逐项递归校验基础序列中的虚线,返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + if self.序号 != other.序号 { + return ( + false, + format!("线段特征: [序号] 不等 A={},B={}", self.序号, other.序号), + ); + } + if *self.标识.read().unwrap() != *other.标识.read().unwrap() { + return ( + false, + format!( + "线段特征: [标识] 不等 A={},B={}", + self.标识.read().unwrap(), + other.标识.read().unwrap() + ), + ); + } + if self.线段方向 != other.线段方向 { + return ( + false, + format!( + "线段特征: [线段方向] 不等 A={},B={}", + self.线段方向, other.线段方向 + ), + ); + } + if self.基础序列.len() != other.基础序列.len() { + return ( + false, + format!( + "线段特征: [基础序列] 长度不一致 A={},B={}", + self.基础序列.len(), + other.基础序列.len() + ), + ); + } + for (idx, (a, b)) in self.基础序列.iter().zip(other.基础序列.iter()).enumerate() { + let (eq, msg) = a.相等(b, 浮点容差); + if !eq { + return (false, format!("线段特征: 基础序列[{idx}]虚线异常 >> {msg}")); + } + } + (true, "线段特征: 全部字段一致".into()) + } } impl crate::types::fractal::有高低 for 线段特征 { diff --git a/chanlun/src/types/gap.rs b/chanlun/src/types/gap.rs index 744112b..5cbbc84 100644 --- a/chanlun/src/types/gap.rs +++ b/chanlun/src/types/gap.rs @@ -64,6 +64,29 @@ impl 缺口 { Some(Self::new(上界.max(下界), 上界.min(下界))) } + + /// 结构化相等校验 — 浮点容差比较高/低,返回 (是否相等, 差异描述) + pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) { + if (self.高 - other.高).abs() > 浮点容差 { + return ( + false, + format!( + "缺口: [高] 浮点超限 容差={浮点容差:.2e} A={:.10},B={:.10}", + self.高, other.高 + ), + ); + } + if (self.低 - other.低).abs() > 浮点容差 { + return ( + false, + format!( + "缺口: [低] 浮点超限 容差={浮点容差:.2e} A={:.10},B={:.10}", + self.低, other.低 + ), + ); + } + (true, "缺口: 高低价格一致".into()) + } } impl std::fmt::Display for 缺口 {