From 823a24d36407173bc349534b837f78a1fc5d9826 Mon Sep 17 00:00:00 2001 From: YuWuKunCheng Date: Fri, 5 Jun 2026 01:55:43 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B9=9D=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chanlun-py/.gitignore | 2 + chanlun-py/Cargo.toml | 9 +- chanlun-py/LICENSE | 22 +- chanlun-py/README.md | 4 +- chanlun-py/build.rs | 8 +- chanlun-py/build.sh | 0 chanlun-py/bump_version.py | 0 chanlun-py/chanlun.pyi | 2985 +++++++------------------ chanlun-py/chanlun/__init__.py | 28 +- chanlun-py/chanlun/__init__.pyi | 2985 +++++++------------------ chanlun-py/chanlun/chan.py | 2256 +++++++++++++------ chanlun-py/pyproject.toml | 15 +- chanlun-py/src/algorithm_py.rs | 598 +---- chanlun-py/src/business_py.rs | 347 ++- chanlun-py/src/config_py.rs | 39 +- chanlun-py/src/indicators_py.rs | 392 +++- chanlun-py/src/kline_py.rs | 93 +- chanlun-py/src/lib.rs | 110 +- chanlun-py/src/structure_py.rs | 315 +-- chanlun-py/src/types_py.rs | 199 +- chanlun/Cargo.toml | 8 +- chanlun/src/algorithm/bi.rs | 957 ++++---- chanlun/src/algorithm/divergence.rs | 2 +- chanlun/src/algorithm/hub.rs | 133 +- chanlun/src/algorithm/segment.rs | 712 +++--- chanlun/src/business/bsp.rs | 467 +++- chanlun/src/business/multi_frame.rs | 51 +- chanlun/src/business/observer.rs | 75 +- chanlun/src/business/synthesizer.rs | 17 +- chanlun/src/config.rs | 193 +- chanlun/src/indicators/boll.rs | 169 ++ chanlun/src/indicators/calculator.rs | 301 +++ chanlun/src/indicators/container.rs | 202 ++ chanlun/src/indicators/kdj.rs | 54 + chanlun/src/indicators/macd.rs | 36 + chanlun/src/indicators/mod.rs | 6 + chanlun/src/indicators/rsi.rs | 46 +- chanlun/src/kline/bar.rs | 80 +- chanlun/src/kline/chan_kline.rs | 184 +- chanlun/src/main.rs | 2 +- chanlun/src/structure/dash_line.rs | 348 ++- chanlun/src/structure/feat_fractal.rs | 12 +- chanlun/src/structure/fractal_obj.rs | 103 +- chanlun/src/structure/segment_feat.rs | 114 +- chanlun/src/types/bsp_type.rs | 2 + chanlun/src/types/direction.rs | 10 +- chanlun/src/types/fractal.rs | 24 +- chanlun/src/types/gap.rs | 3 + chanlun/src/types/sync_f64.rs | 4 + 49 files changed, 7280 insertions(+), 7442 deletions(-) mode change 120000 => 100644 chanlun-py/LICENSE mode change 100755 => 100644 chanlun-py/build.sh mode change 100755 => 100644 chanlun-py/bump_version.py create mode 100644 chanlun/src/indicators/boll.rs create mode 100644 chanlun/src/indicators/calculator.rs create mode 100644 chanlun/src/indicators/container.rs diff --git a/chanlun-py/.gitignore b/chanlun-py/.gitignore index 2631f06..b62e615 100644 --- a/chanlun-py/.gitignore +++ b/chanlun-py/.gitignore @@ -3,3 +3,5 @@ __pycache__/ *.egg-info/ dist/ build/ +Cargo.lock + diff --git a/chanlun-py/Cargo.toml b/chanlun-py/Cargo.toml index 337d4c0..36cd243 100644 --- a/chanlun-py/Cargo.toml +++ b/chanlun-py/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "chanlun-py" -version = "26.5.103" -edition = "2021" +version = "26.6.17" +edition = "2024" description = "缠论技术分析库 — Rust 高性能 Python 绑定" authors = ["YuYuKunKun"] license = "MIT" @@ -12,7 +12,10 @@ crate-type = ["cdylib"] name = "chanlun" [dependencies] -chanlun = "26.5.6" # { path = "../chanlun" } +chanlun = "26.6.1" # { path = "../chanlun" } pyo3 = { version = "0.28", features = ["experimental-inspect"] } serde_json = "1" chrono = "0.4" +tracing = "0.1" +tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] } +tracing-core = "0.1" diff --git a/chanlun-py/LICENSE b/chanlun-py/LICENSE deleted file mode 120000 index ea5b606..0000000 --- a/chanlun-py/LICENSE +++ /dev/null @@ -1 +0,0 @@ -../LICENSE \ No newline at end of file diff --git a/chanlun-py/LICENSE b/chanlun-py/LICENSE new file mode 100644 index 0000000..9cbe015 --- /dev/null +++ b/chanlun-py/LICENSE @@ -0,0 +1,21 @@ +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. diff --git a/chanlun-py/README.md b/chanlun-py/README.md index 6be8615..cc71e28 100644 --- a/chanlun-py/README.md +++ b/chanlun-py/README.md @@ -74,4 +74,6 @@ pip install target/wheels/chanlun-*.whl ## 许可 -MIT +本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parse(MIT)、termcolor(MIT)。 + +详见 [NOTICE](../NOTICE) 和 [LICENSES/](../LICENSES/) 目录。 diff --git a/chanlun-py/build.rs b/chanlun-py/build.rs index 29d0c44..435ef02 100644 --- a/chanlun-py/build.rs +++ b/chanlun-py/build.rs @@ -58,10 +58,10 @@ fn read_version(path: &std::path::Path, section: &str) -> String { if !in_section { continue; } - if trimmed.starts_with("version") { - if let Some(v) = trimmed.split('=').nth(1) { - return v.trim().trim_matches('"').trim().to_string(); - } + if trimmed.starts_with("version") + && let Some(v) = trimmed.split('=').nth(1) + { + return v.trim().trim_matches('"').trim().to_string(); } } panic!("Cannot parse version from {:?}", path); diff --git a/chanlun-py/build.sh b/chanlun-py/build.sh old mode 100755 new mode 100644 diff --git a/chanlun-py/bump_version.py b/chanlun-py/bump_version.py old mode 100755 new mode 100644 diff --git a/chanlun-py/chanlun.pyi b/chanlun-py/chanlun.pyi index 2ab0178..15fd7a2 100644 --- a/chanlun-py/chanlun.pyi +++ b/chanlun-py/chanlun.pyi @@ -1,438 +1,91 @@ -# Generated from chan.py — type stubs for chanlun Rust bindings -# Auto-generated, do not edit manually +# Type stubs for chanlun Rust bindings (_chanlun.so) +# Auto-generated — do not edit manually +from typing import Any, ClassVar, Optional, List, Dict, Tuple, Union from datetime import datetime -from functools import lru_cache -from typing import Any, Callable, ClassVar, Dict, Iterator, List, Optional, Self, Sequence, Tuple, Union -__all__ = [ - "K线", - "K线合成器", - "中枢", - "买卖点", - "买卖点类型", - "分型", - "分型结构", - "基础买卖点", - "平滑异同移动平均线", - "指标", - "特征分型", - "相对强弱指数", - "相对方向", - "立体分析器", - "笔", - "线段", - "线段特征", - "缠论K线", - "缠论配置", - "缺口", - "背驰分析", - "虚线", - "观察者", - "转化为时间戳", - "转化为时间戳_数字", - "随机指标", -] +# ========== Module-level functions ========== -class 买卖点类型(str, Enum): - """ - 买卖点类型 — 缠论的三类买卖点及扩展类型。 +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: ... - :ivar 是买点: 是否为买入类型 - :ivar 是卖点: 是否为卖出类型 - """ - def __str__(self) -> str: - """返回买卖点类型名称""" - ... - def __repr__(self) -> str: - """返回买卖点类型名称""" - ... - def 是买点(self) -> bool: - """ - 判断是否为买入类型(名称中含"买"字) +# ========== Enum-like types ========== - :return: 是否为买入类型 - """ - ... - def 是卖点(self) -> bool: - """ - 判断是否为卖出类型(名称中含"卖"字) - - :return: 是否为卖出类型 - """ - ... - -class 基础买卖点: - """ - 基础买卖点 — 描述偏离买入/卖出位置的程度。 - - :ivar 备注: 描述文本 - :ivar 偏移: 当前K线相对于买卖点K线的偏移量 - :ivar 失效偏移: 失效K线相对于买卖点K线的偏移量(-1表示未失效) - :ivar 有效性: 是否已失效(存在失效K线) - :ivar 破位值: 中枢破位价格 - :ivar 与MACD柱子匹配: MACD柱子是否匹配买卖点方向 - :ivar 与MACD柱子分型匹配: MACD柱子分型是否匹配 - """ +class 买卖点类型: + 一买: ClassVar[买卖点类型] + 一卖: ClassVar[买卖点类型] + 二买: ClassVar[买卖点类型] + 二卖: ClassVar[买卖点类型] + 三买: ClassVar[买卖点类型] + 三卖: ClassVar[买卖点类型] + T1买: ClassVar[买卖点类型] + T1卖: ClassVar[买卖点类型] + T1P买: ClassVar[买卖点类型] + T1P卖: ClassVar[买卖点类型] + T2买: ClassVar[买卖点类型] + T2卖: ClassVar[买卖点类型] + T2S买: ClassVar[买卖点类型] + T2S卖: ClassVar[买卖点类型] + T3A买: ClassVar[买卖点类型] + T3A卖: ClassVar[买卖点类型] + T3B买: ClassVar[买卖点类型] + T3B卖: ClassVar[买卖点类型] @property - def 类型(self) -> 买卖点类型: ... - @类型.setter - def 类型(self, value: 买卖点类型) -> None: ... + def 是买点(self) -> bool: ... @property - def 买卖点分型(self) -> "分型": ... - @买卖点分型.setter - def 买卖点分型(self, value: "分型") -> None: ... - @property - def 备注(self) -> str: ... - @备注.setter - def 备注(self, value: str) -> None: ... - @property - def 中枢破位值(self) -> float: ... - @中枢破位值.setter - def 中枢破位值(self, value: float) -> None: ... - def __init__(self, 类型: 买卖点类型, 当前K线: "K线", 买卖点分型: "分型", 备注: str, 中枢破位值: float) -> Any: - """ - :param 类型: 买卖点类型 - :param 当前K线: 触发买卖点的K线 - :param 买卖点分型: 买卖点对应的分型 - :param 备注: 描述文本(如"笔_1买") - :param 中枢破位值: 中枢破位价格 - """ - ... + def 是卖点(self) -> bool: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - def 当前K线(self) -> Any: - """当前K线""" - ... - def 破位值(self) -> float: - """ - 破位值 + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... - :return: float - """ - ... - def 偏移(self) -> int: - """ - 偏移 - - :return: int - """ - ... - def 失效偏移(self) -> int: - """ - 失效偏移 - - :return: int - """ - ... - def 有效性(self) -> bool: - """ - 有效性 - - :return: bool - """ - ... - def 与MACD柱子匹配(self) -> bool: - """ - 与MACD柱子匹配 - - :return: bool - """ - ... - def 与MACD柱子分型匹配(self) -> bool: - """ - 与MACD柱子分型匹配 - - :return: bool - """ - ... - -class 买卖点(基础买卖点): - """ - 买卖点 — 一二三类买卖点构造器。 - - 类方法: - 一卖点 / 一买点 / 二卖点 / 二买点 / 三卖点 / 三买点 — 创建对应买卖点实例 - 生成买卖点(特征, 序号, 级别, 分型, 当前缠K) — 根据特征路由到对应构造函数 - """ +class 相对方向: + 向上: ClassVar[相对方向] + 向下: ClassVar[相对方向] + 向上缺口: ClassVar[相对方向] + 向下缺口: ClassVar[相对方向] + 衔接向上: ClassVar[相对方向] + 衔接向下: ClassVar[相对方向] + 顺: ClassVar[相对方向] + 逆: ClassVar[相对方向] + 同: ClassVar[相对方向] + @property + def 是否向上(self) -> bool: ... + @property + def 是否向下(self) -> bool: ... + @property + def 是否包含(self) -> bool: ... + @property + def 是否缺口(self) -> bool: ... + @property + def 是否衔接(self) -> bool: ... + def 翻转(self) -> 相对方向: ... @classmethod - def 一卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 一买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 二卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 二买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 三卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 三买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 生成买卖点(cls, 特征: str, 序号: str, 级别: str, 买卖点分型: "分型", 当前缠K: "缠论K线") -> Any: - """ - :param 特征: 特征字符串 - :param 序号: 序号(如"一"、"二"、"三") - :param 级别: 级别字符串 - :param 买卖点分型: 买卖点对应的分型 - :param 当前缠K: 当前缠论K线 - """ - ... - -class 缠论配置: - """ - 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 - - 字段分组: - [基础] 标识 - [缠K] 缠K合并替换 - [笔] 笔内元素数量, 笔弱化, 笔次成笔, 笔内相同终点取舍 等 - [线段] 线段_特征序列忽视老阴老阳, 线段_缺口后紧急修正, 线段内部中枢图显 等 - [分析开关] 分析笔, 分析线段, 分析扩展线段, 分析笔中枢, 分析线段中枢 - [指标] 计算指标, 指标计算方式, MACD/RSI/KDJ 参数 - [推送/显示] 图表展示, 推送K线/笔/线段/中枢, 图表展示_笔 等细分开关 - [买卖点] 买卖点偏移, 买卖点激进识别, 买卖点_背离率, 买卖点_计算方式 等 - [背驰] 线段内部背驰_MACD, 线段内部背驰_斜率 等 - [其他] 手动终止, 加载文件路径 - - 方法: - to_dict() / to_json() / 保存配置(path?) / 加载配置(path?) (classmethod) - from_dict(data) (classmethod) / from_json(json_str) (classmethod) - 不推送() (classmethod) / 对比(other) -> dict - """ - @model_validator(mode="before") - def 兼容旧版本配置(self, values: Dict[str, Any]) -> Dict[str, Any]: - """ - 自动兼容: - 1. 旧版本少字段 → 使用默认值 - 2. 新版本多字段 → 自动忽略多余字段 - 3. 字段改名/删除 → 不报错 - - :param values: 原始配置字典 - :return: 兼容后的字典 - """ - ... - @field_validator("*", mode="wrap") - def bool_parse_fallback_default(self, value: Any, handler: Any, info: Any) -> Any: - """ - :param value: 待验证的值 - :param handler: 默认验证器 - :param info: 字段信息 - :return: 验证后的值 - """ - ... - def to_dict(self) -> dict: - """对象 → 字典""" - ... - def to_json(self) -> str: - """对象 → JSON字符串""" - ... - def 保存配置(self, path: Any = "缠论配置.json") -> Any: - """ - 将配置保存为JSON文件 - :param path: 保存路径,默认"缠论配置.json" - """ - ... - @staticmethod - def 加载配置(path: Any = "缠论配置.json") -> "缠论配置": - """ - 从JSON文件加载配置 - :param path: 配置文件路径 - :return: 缠论配置实例 - """ - ... - @classmethod - def from_dict(cls, data: dict) -> "缠论配置": - """ - :param data: 字典数据 - :return: 缠论配置实例 - """ - ... - @classmethod - def from_json(cls, json_str: str) -> "缠论配置": - """ - :param json_str: JSON字符串 - :return: 缠论配置实例 - """ - ... - @classmethod - def 不推送(cls) -> Any: - """ - 创建不推送任何图表的静默配置(用于纯计算场景) - - :return: 新缠论配置实例 - """ - ... - @classmethod - def 按序号重组字典(cls, 默认配置: Any, 原始字典: dict) -> dict: - """ - 将形如 "1_open", "1_close", "2_open", "name" 的字典重组为嵌套结构 - { - "1_open": 10, - "1_close": 11, - "2_open": 20, - "name": "BTC", # 无法拆分 - "time": 123456 # 无法拆分 - } - 转化为 - { - 1: {"open": 10, "close": 11}, - 2: {"open": 20}, - "无法拆分": { - "name": "BTC", - "time": 123456 - } - } - - :param 默认配置: 默认配置实例 - :param 原始字典: 待重组的原始字典 - :return: 重组后的字典 - """ - ... - def 对比(self, other: "缠论配置") -> dict: - """ - 比较当前配置与另一个配置的差异 - - :param other: 另一个配置实例 - :return: { - "字段名": { - "旧值": 当前配置的值, - "新值": 另一个配置的值 - }, - ... - } - 仅当值不同时才包含该字段 - """ - ... - -class 相对方向(Enum): - """ - 相对方向 — 描述两个K线/分型之间相对位置关系的枚举。 - - 类属性: 向上, 向下, 向上缺口, 向下缺口, 衔接向上, 衔接向下, 顺, 逆, 同 - - :ivar 是否向上: 判断是否为向上方向 - :ivar 是否向下: 判断是否为向下方向 - :ivar 是否包含: 判断是否为包含关系 - :ivar 是否缺口: 判断是否有缺口 - :ivar 是否衔接: 判断是否为首尾衔接 - """ + def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - def 翻转(self) -> "相对方向": - """返回方向的对立面:向上↔向下, 向上缺口↔向下缺口, 顺↔逆, 衔接向上↔衔接向下, 同不变""" - ... - def 是否向上(self) -> bool: - """判断是否为向上方向(向上/向上缺口/衔接向上)""" - ... - def 是否向下(self) -> bool: - """判断是否为向下方向(向下/向下缺口/衔接向下)""" - ... - def 是否包含(self) -> bool: - """判断是否为包含关系(顺/逆/同)""" - ... - def 是否缺口(self) -> bool: - """判断是否有缺口(向下缺口/向上缺口)""" - ... - def 是否衔接(self) -> bool: - """判断是否为首尾衔接(衔接向下/衔接向上)""" - ... + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... + +class 分型结构: + 上: ClassVar[分型结构] + 下: ClassVar[分型结构] + 顶: ClassVar[分型结构] + 底: ClassVar[分型结构] + 散: ClassVar[分型结构] @classmethod - def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> "相对方向": - """ - 分析两个价格区间(前、后)的相对位置关系。 - - :param 前高: 前方价格区间的最高价 - :param 前低: 前方价格区间的最低价 - :param 后高: 后方价格区间的最高价 - :param 后低: 后方价格区间的最低价 - :return: 相对方向枚举值(向上/向下/向上缺口/向下缺口/衔接向上/衔接向下/顺/逆/同) - :raises RuntimeError: 无法识别的方向 - """ - ... - -class 分型结构(Enum): - """ - 分型结构 — 描述三根K线构成的顶底分型形态。 - - 类属性: 上, 下, 顶, 底, 散 - """ + def 分析(cls, 左: Any, 中: Any, 右: Any, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional[分型结构]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - @classmethod - def 分析(cls, 左: Any, 中: Any, 右: Any, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional["分型结构"]: - """ - 分析左中右三个元素构成的分型形态。 - - :param 左: 左侧元素(必须有 高/低 属性) - :param 中: 中间元素 - :param 右: 右侧元素 - :param 可以逆序包含: True 时允许逆序包含关系(如 右>左>中) - :param 忽视顺序包含: True 时跳过顺序包含检查(如 左>中>右) - :return: 分型结构(顶/底/上/下/散),无法判定返回 None - """ - ... + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... class 缺口: - """ - 缺口 — 描述价格区间之间的缺口(未重叠部分)。 - - :ivar 高: 缺口上沿 - :ivar 低: 缺口下沿 - """ + def __init__(self, 高: float, 低: float) -> None: ... @property def 高(self) -> float: ... @高.setter @@ -441,1876 +94,740 @@ class 缺口: def 低(self) -> float: ... @低.setter def 低(self, value: float) -> None: ... - def __init__(self, 高: float, 低: float) -> None: - """ - :param 高: 缺口上沿 - :param 低: 缺口下沿 - """ - ... + @classmethod + def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional[缺口]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - @classmethod - def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional["缺口"]: - """ - 以原区间中心为基准,向两侧各取总长度的 `比例` 作为新区间。 - 如果新区间超出原边界则裁剪到边界;若完全越界则返回 None。 + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... - :param 起点: 区间起始值 - :param 终点: 区间结束值 - :param 比例: 向两侧扩展的长度占总长度的比例(0~1 之间) - :return: Optional[缺口] - """ - ... - -class 指标: - """指标 — 静态工具类,提供K线取值的辅助方法。""" - @classmethod - def K线取值(cls, k线: "K线", 指标计算方式: Any) -> Any: - """ - 根据计算方式从K线中取值 - - :param k线: K线对象 - :param 指标计算方式: "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值" - :return: 对应价格 - """ - ... +# ========== Indicators ========== class 平滑异同移动平均线: - """ - 平滑异同移动平均线(MACD)— 基于EMA快慢线差值的趋势指标。 - - 计算字段(输入): - 时间戳: datetime / 收盘价: float - 快线周期: int (默认12) / 慢线周期: int (默认26) / 信号周期: int (默认9) - - 输出字段: - EMA快线: float — 短期EMA值 - EMA慢线: float — 长期EMA值 - DIF: float — 快线减慢线 - DEA: float — DIF的信号线EMA - MACD柱子: float — DIF减DEA - """ - @classmethod - def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> "平滑异同移动平均线": - """ - 首次计算MACD指标(没有历史数据时使用) - - :param 初始收盘价: 第一个数据点的收盘价 - :param 初始时间: 第一个数据点的时间戳 - :param 快线周期: 快线EMA周期 - :param 慢线周期: 慢线EMA周期 - :param 信号周期: 信号线EMA周期 - :return: MACD指标对象 - """ - ... - @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> "平滑异同移动平均线": - """ - :param k线: 原始K线 - :param 计算方式: 指标计算方式(开/高/低/收/均值等) - :param 快线周期: 快线EMA周期 - :param 慢线周期: 慢线EMA周期 - :param 信号周期: 信号线EMA周期 - :return: MACD指标对象 - """ - ... - @classmethod - def 增量计算(cls, 前一个MACD: "平滑异同移动平均线", 当前收盘价: float, 当前时间: datetime) -> "平滑异同移动平均线": - """ - 基于前一个MACD指标增量计算当前MACD指标 - 适用于实时交易系统或流式数据处理 - - :param 前一个MACD: 前一个周期的MACD指标对象 - :param 当前收盘价: 当前K线的收盘价 - :param 当前时间: 当前K线的时间戳 - :return: 当前MACD指标对象 - :raises RuntimeError: 前一个MACD中快线EMA或慢线EMA为None时抛出 - """ - ... - @classmethod - def 增量计算_K线(cls, 前一个MACD: "平滑异同移动平均线", 当前K线: "K线", 计算方式: "str") -> "平滑异同移动平均线": - """ - :param 前一个MACD: 前一个MACD指标对象 - :param 当前K线: 当前K线 - :param 计算方式: 指标计算方式 - :return: 当前MACD指标对象 - """ - ... - -class 相对强弱指数: - """ - 相对强弱指数 (RSI) 指标 - 使用 Wilder 平滑(RMA)进行增量计算,提供完整的中间平滑值, - 并支持对RSI值计算SMA(简单移动平均) - """ - @classmethod - def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> "相对强弱指数": - """ - 首次计算RSI(没有足够历史数据时使用) - 此时无法计算真实RSI,设为 None,但记录初始收盘价作为起点 - - :param 初始收盘价: 第一个数据点的收盘价 - :param 初始时间: 第一个数据点的时间戳 - :param 周期: RSI周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :param RSI_SMA周期: RSI的SMA周期(可选) - :return: RSI指标对象 - """ - ... - @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> "相对强弱指数": - """ - :param k线: 原始K线 - :param 计算方式: 指标计算方式 - :param 周期: RSI周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :param RSI_SMA周期: RSI的SMA周期 - :return: RSI指标对象 - """ - ... - @classmethod - def 增量计算(cls, 前一个RSI: "相对强弱指数", 当前收盘价: float, 当前时间: datetime) -> "相对强弱指数": - """ - 基于前一个RSI指标增量计算当前RSI - 支持可选的RSI_SMA(简单移动平均) - - :param 前一个RSI: 前一个RSI指标对象 - :param 当前收盘价: 当前收盘价 - :param 当前时间: 当前时间戳 - :return: 当前RSI指标对象 - """ - ... - @classmethod - def 增量计算_K线(cls, 前一个RSI: "相对强弱指数", 当前K线: "K线", 计算方式: "str") -> "相对强弱指数": - """ - :param 前一个RSI: 前一个RSI指标对象 - :param 当前K线: 当前K线 - :param 计算方式: 指标计算方式 - :return: 当前RSI指标对象 - """ - ... - -class 随机指标: - """ - KDJ 随机指标 (Stochastic Oscillator) - 使用标准参数:N=9, M1=3, M2=3 - 支持增量计算,需提供当前K线的最高价、最低价、收盘价 - """ - @classmethod - def 首次计算(cls, 初始最高价: float, 初始最低价: float, 初始收盘价: float, 初始时间: datetime, N: int = 9, M1: int = 3, M2: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> "随机指标": - """ - 首次计算KDJ(无历史数据时) - 此时无法计算RSV和K/D/J,仅记录初始三价,初始化队列 - - :param 初始最高价: 第一个数据点的最高价 - :param 初始最低价: 第一个数据点的最低价 - :param 初始收盘价: 第一个数据点的收盘价 - :param 初始时间: 第一个数据点的时间戳 - :param N: RSV周期 - :param M1: K值平滑周期 - :param M2: D值平滑周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :return: KDJ指标对象 - """ - ... - @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, RSV周期: int = 9, K值平滑周期: int = 3, D值平滑周期: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> "随机指标": - """ - :param k线: 原始K线 - :param 计算方式: 指标计算方式(未使用,仅为接口一致) - :param RSV周期: RSV周期 - :param K值平滑周期: K值平滑周期 - :param D值平滑周期: D值平滑周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :return: KDJ指标对象 - """ - ... - @classmethod - def 增量计算(cls, 前一个KDJ: "随机指标", 当前最高价: float, 当前最低价: float, 当前收盘价: float, 当前时间: datetime) -> "随机指标": - """ - 基于前一个KDJ对象和当前三价,增量计算当前KDJ值 - - :param 前一个KDJ: 前一个KDJ指标对象 - :param 当前最高价: 当前最高价 - :param 当前最低价: 当前最低价 - :param 当前收盘价: 当前收盘价 - :param 当前时间: 当前时间戳 - :return: 当前KDJ指标对象 - """ - ... - @classmethod - def 增量计算_K线(cls, 前一个KDJ: "随机指标", 当前K线: "K线", 计算方式: "str") -> "随机指标": - """ - :param 前一个KDJ: 前一个KDJ指标对象 - :param 当前K线: 当前K线 - :param 计算方式: 指标计算方式(未使用,仅为接口一致) - :return: 当前KDJ指标对象 - """ - ... - -class 背驰分析: - """ - 背驰分析 — 静态方法容器,提供背驰/背离检测算法。 - - 方法: - MACD背驰(进入段, 离开段, K线序列, 方式?) — MACD柱状线面积背驰 - 斜率背驰(进入段, 离开段, 序列, 配置?) — 线段斜率背驰 - 测度背驰(进入段, 离开段, 序列, 配置?) — 线段测度背驰 - 全量背驰(进入段, 离开段, ...) — 综合所有背驰检测方式 - 配置背驰(进入段, 离开段, ...) — 根据配置选择检测方式 - """ - @staticmethod - def MACD背驰(进入段: "虚线", 离开段: "虚线", K线序列: List["K线"], 方式: str = "总") -> bool: - """ - MACD柱状线面积背驰 - - :param 进入段: 进入中枢的虚线 - :param 离开段: 离开中枢的虚线 - :param K线序列: 完整K线序列 - :param 方式: "总" 或 "阳"/"阴" - :return: 背驰为True - """ - ... - @staticmethod - def 斜率背驰(进入段: "虚线", 离开段: "虚线") -> bool: - """ - 价格斜率背驰 - - :param 进入段: 进入中枢的虚线 - :param 离开段: 离开中枢的虚线 - :return: 背驰为True - """ - ... - @staticmethod - def 测度背驰(进入段: "虚线", 离开段: "虚线") -> bool: - """ - 价格测度背驰(欧氏距离) - - :param 进入段: 进入中枢的虚线 - :param 离开段: 离开中枢的虚线 - :return: 背驰为True - """ - ... - @staticmethod - def 全量背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: - """ - 判断是否满足全部三种背驰条件(MACD + 测度 + 斜率) - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :return: 三者全满足返回True - """ - ... - @staticmethod - def 任意背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: - """ - 判断是否满足任一背驰条件 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :return: 任一满足返回True - """ - ... - @staticmethod - def 配置背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"], 配置: 缠论配置) -> bool: - """ - 根据配置选择对应的背驰检测组合 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :param 配置: 缠论配置(控制MACD/测度/斜率开关) - :return: 背驰结果 - """ - ... - @staticmethod - def 任选背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: - """ - 三个背驰条件中至少两个满足即视为背驰 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :return: 至少两个满足返回True - """ - ... - @staticmethod - def 背驰模式(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"], 配置: 缠论配置, 模式: str) -> bool: - """ - 根据模式字符串选择背驰检测策略 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :param 配置: 缠论配置 - :param 模式: "全量"/"任意"/"配置"/"相对" - :return: 背驰结果 - """ - ... - -class K线(object): - """ - 原始 K 线 — OHLCV 数据,可内嵌 MACD/RSI/KDJ 指标。 - - :ivar 标识: 标识符 - :ivar 序号: 序号 - :ivar 周期: K线周期(秒) - :ivar 时间戳: 时间戳 - :ivar 高: 最高价 - :ivar 低: 最低价 - :ivar 开盘价: 开盘价 - :ivar 收盘价: 收盘价 - :ivar 成交量: 成交量 - :ivar macd: MACD指标对象 - :ivar rsi: RSI指标对象 - :ivar kdj: KDJ指标对象 - :ivar 方向: 涨跌方向(只读) - """ @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... - @property - def 周期(self) -> int: ... - @周期.setter - def 周期(self, value: int) -> None: ... - @property - def 时间戳(self) -> datetime: ... - @时间戳.setter - def 时间戳(self, value: datetime) -> None: ... - @property - def 开盘价(self) -> float: ... - @开盘价.setter - def 开盘价(self, value: float) -> None: ... - @property - def 最高价(self) -> float: ... - @最高价.setter - def 最高价(self, value: float) -> None: ... - @property - def 最低价(self) -> float: ... - @最低价.setter - def 最低价(self, value: float) -> None: ... + def 时间戳(self) -> str: ... @property def 收盘价(self) -> float: ... - @收盘价.setter - def 收盘价(self, value: float) -> None: ... @property - def 成交量(self) -> float: ... - @成交量.setter - def 成交量(self, value: float) -> None: ... + def 快线周期(self) -> int: ... @property - def macd(self) -> 平滑异同移动平均线: ... - @macd.setter - def macd(self, value: 平滑异同移动平均线) -> None: ... + def 慢线周期(self) -> int: ... @property - def rsi(self) -> 相对强弱指数: ... - @rsi.setter - def rsi(self, value: 相对强弱指数) -> None: ... + def 信号周期(self) -> int: ... @property - def kdj(self) -> 随机指标: ... - @kdj.setter - def kdj(self, value: 随机指标) -> None: ... - def __init__(self, 标识: str, 序号: int, 周期: int, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, macd: 平滑异同移动平均线 = None, rsi: 相对强弱指数 = None, kdj: 随机指标 = None) -> Any: - """ - :param 标识: K线标识符 - :param 序号: K线序号 - :param 周期: K线周期(秒) - :param 时间戳: K线时间 - :param 开盘价: 开盘价 - :param 最高价: 最高价 - :param 最低价: 最低价 - :param 收盘价: 收盘价 - :param 成交量: 成交量 - :param macd: MACD指标对象(可选) - :param rsi: RSI指标对象(可选) - :param kdj: KDJ指标对象(可选) - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 方向(self) -> 相对方向: - """:return: 相对方向.向上(开盘<收盘)或 相对方向.向下(开盘>收盘)""" - ... - def __bytes__(self) -> bytes: ... - @classmethod - def 创建普K(cls, 标识: str, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: int, 周期: int) -> "K线": - """ - 快捷构造普通K线 - - :param 标识: K线标识符 - :param 时间戳: K线时间 - :param 开盘价: 开盘价 - :param 最高价: 最高价 - :param 最低价: 最低价 - :param 收盘价: 收盘价 - :param 成交量: 成交量 - :param 序号: K线序号 - :param 周期: K线周期(秒) - :return: K线实例 - """ - ... - @classmethod - def 保存到DAT文件(cls, 路径: str, K线序列: List["K线"]) -> Any: - """ - 将K线序列保存为二进制DAT文件 - - :param 路径: 保存路径 - :param K线序列: K线列表 - """ - ... - @classmethod - def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> "K线": - """ - 从大端字节序二进制数据反序列化K线(兼容.dat/.nb文件格式) - - :param 字节组: 二进制数据(48字节) - :param 周期: 周期(秒) - :param 标识: K线标识 - :return: K线实例 - """ - ... - @classmethod - def 获取MACD(cls, K线序列: List["K线"], 始: "K线", 终: "K线") -> Dict[str, float]: - """ - 计算指定K线区间的MACD柱面积 - - :param K线序列: 完整K线序列 - :param 始: 起始K线 - :param 终: 终点K线 - :return: {"阳": 正值面积和, "阴": 负值面积和, "合": 净面积, "总": 绝对面积和} - """ - ... - @staticmethod - def 截取(序列: List["K线"], 始: "K线", 终: "K线") -> List["K线"]: - """ - 按起止K线截取K线子序列 - - :param 序列: 完整K线序列 - :param 始: 起始K线 - :param 终: 终点K线 - :return: K线子列表 - """ - ... - -class 缠论K线(object): - """ - 缠论K线 — 经包含处理后的标准化K线,有方向和分型结构标记。 - - :ivar 序号: 序号 - :ivar 时间戳: 时间戳 - :ivar 高: 最高价 - :ivar 低: 最低价 - :ivar 方向: 运行方向 - :ivar 分型: 分型结构(顶/底/上/下等) - :ivar 周期: 周期(秒) - :ivar 标识: 标识符 - :ivar 分型特征值: 分型特征值(顶取高,底取低) - :ivar 原始起始序号: 合并前的原始K线起始序号 - :ivar 原始结束序号: 合并前的原始K线结束序号 - :ivar 标的K线: 对应的原始K线 - :ivar 买卖点信息: 买卖点信息(预留) - """ + def DIF(self) -> Optional[float]: ... @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... + def DEA(self) -> Optional[float]: ... @property - def 时间戳(self) -> datetime: ... - @时间戳.setter - def 时间戳(self, value: datetime) -> None: ... + def MACD柱(self) -> float: ... @property - def 最高价(self) -> float: ... - @最高价.setter - def 最高价(self, value: float) -> None: ... + def 快线EMA(self) -> Optional[float]: ... @property - def 最低价(self) -> float: ... - @最低价.setter - def 最低价(self, value: float) -> None: ... + def 慢线EMA(self) -> Optional[float]: ... @property - def 最终方向(self) -> 相对方向: ... - @最终方向.setter - def 最终方向(self, value: 相对方向) -> None: ... - @property - def 普K(self) -> "K线": ... - @普K.setter - def 普K(self, value: "K线") -> None: ... - @property - def 原始起始序号(self) -> int: ... - @原始起始序号.setter - def 原始起始序号(self, value: int) -> None: ... - @property - def 原始结束序号(self) -> int: ... - @原始结束序号.setter - def 原始结束序号(self, value: int) -> None: ... - @property - def 分型(self) -> Optional[分型结构]: ... - @分型.setter - def 分型(self, value: Optional[分型结构]) -> None: ... - def __init__(self, 序号: int, 时间戳: datetime, 最高价: float, 最低价: float, 最终方向: 相对方向, 普K: "K线", 原始起始序号: int, 原始结束序号: int, 分型: Optional[分型结构] = None) -> Any: - """ - :param 序号: 缠论K线序号 - :param 时间戳: 时间戳 - :param 最高价: 最高价 - :param 最低价: 最低价 - :param 最终方向: 方向 - :param 普K: 对应的原始K线 - :param 原始起始序号: 合并起始序号 - :param 原始结束序号: 合并结束序号 - :param 分型: 分型结构(可选) - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 镜像(self) -> Any: - """ - 创建当前缠K的浅拷贝副本 - - :return: 新的缠论K线实例 - """ - ... - def 与MACD柱子匹配(self) -> bool: - """:return: 底分型时MACD柱<0,顶分型时MACD柱>0""" - ... - def 与RSI匹配(self) -> bool: - """:return: 底分型时RSI < RSI_SMA,顶分型时RSI > RSI_SMA""" - ... - def 与KDJ匹配(self) -> bool: - """:return: 底分型时KD""" - ... + def DEA_EMA(self) -> Optional[float]: ... @classmethod - def 时间戳对齐(cls, 基线: List["缠论K线"], k线: "缠论K线") -> Any: - """ - 在基线序列中找到与k线时间戳对齐的时间戳 - - :param 基线: 参照缠K序列 - :param k线: 需要对齐的缠K - :return: 对齐后的时间戳 - """ - ... + def 首次计算(cls, 初始收盘价: float, 初始时间: int, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ... @classmethod - def 创建缠K(cls, 时间戳: datetime, 高: float, 低: float, 方向: 相对方向, 结构: 分型结构, 原始序号: int, 普k: "K线", 之前: Optional["缠论K线"] = None) -> "缠论K线": - """ - 创建新的缠论K线 - - :param 时间戳: K线时间 - :param 高: 最高价 - :param 低: 最低价 - :param 方向: K线运行方向 - :param 结构: 分型结构 - :param 原始序号: 原始K线序号 - :param 普k: 标的原始K线 - :param 之前: 前一缠K(可选,自动校验包含关系) - :return: 缠论K线实例 - :raises ValueError: 包含关系错误 - """ - ... + def 首次计算_K线(cls, k线: Any, 计算方式: str, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ... @classmethod - def 兼并(cls, 之前缠K: Optional["缠论K线"], 当前缠K: "缠论K线", 当前普K: "K线", 配置: 缠论配置) -> Tuple[Optional["缠论K线"], Optional[str]]: - """ - K线包含处理(合并) - - :param 之前缠K: 前一缠K - :param 当前缠K: 当前待处理的缠K - :param 当前普K: 当前原始K线 - :param 配置: 缠论配置 - :return: (新缠K或None, 操作模式:"添加"/"替换"/None) - """ - ... + def 增量计算(cls, 前一个MACD: 平滑异同移动平均线, 当前收盘价: float, 当前时间: int) -> 平滑异同移动平均线: ... @classmethod - def 分析(cls, 当前K线: "K线", 缠K序列: List["缠论K线"], 普K序列: List["K线"], 配置: 缠论配置) -> tuple[str, Optional["分型"]]: - """ - 分析K线,执行指标计算+包含处理+分型判定 - - :param 当前K线: 新到的原始K线 - :param 缠K序列: 现有缠K序列(会被原地修改) - :param 普K序列: 现有普K序列(会被原地修改) - :param 配置: 缠论配置 - :return: (操作状态, 新形成的分型或None) - """ - ... - @staticmethod - def 截取(序列: List["缠论K线"], 始: "缠论K线", 终: "缠论K线") -> List["缠论K线"]: - """ - :param 序列: 缠K序列 - :param 始: 起始缠K - :param 终: 终点缠K - :return: 缠K子列表 - """ - ... - -class 分型(object): - """ - 分型 — 由左中右三根缠论K线构成的顶/底分型结构。 - - :ivar 左: 左侧缠K(可能为None) - :ivar 中: 中间缠K(分型顶点/底点) - :ivar 右: 右侧缠K(可能为None) - :ivar 结构: 分型结构(顶/底/上/下/包含) - :ivar 时间戳: 分型时间戳 - :ivar 分型特征值: 顶分型取最高价,底分型取最低价 - :ivar 关系组: 左中、中右、左右三对相对方向 - :ivar 强度: 分型强度(强/中/弱/未知) - :ivar 与MACD柱子分型匹配: 是否与MACD柱子分型匹配 - """ - @property - def 左(self) -> Optional[缠论K线]: ... - @左.setter - def 左(self, value: Optional[缠论K线]) -> None: ... - @property - def 中(self) -> 缠论K线: ... - @中.setter - def 中(self, value: 缠论K线) -> None: ... - @property - def 右(self) -> Optional[缠论K线]: ... - @右.setter - def 右(self, value: Optional[缠论K线]) -> None: ... - def __init__(self, 左: Optional[缠论K线], 中: 缠论K线, 右: Optional[缠论K线]) -> Any: - """ - :param 左: 左侧缠K - :param 中: 中间缠K - :param 右: 右侧缠K - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 关系组(self) -> Optional[Tuple[相对方向, 相对方向, 相对方向]]: - """ - 左、中、右三对相对方向关系 - - :return: (左中关系, 中右关系, 左右关系) 或 None - """ - ... - def 强度(self) -> Any: - """ - 分型强度(强/中/弱/未知) - - :return: 强度字符串 - """ - ... - def 与MACD柱子分型匹配(self) -> bool: - """:return: 底分型时左右MACD柱 > 中MACD柱,顶分型时左右MACD柱 < 中MACD柱""" - ... - @classmethod - def 判断分型(cls, 左: "分型", 右: "分型", 模式: str = "中") -> bool: - """ - 判断两个分型是否相同(identity比较) - - :param 左: 左分型 - :param 右: 右分型 - :param 模式: 比较模式(默认"中") - :return: 是否为同一对象 - """ - ... - @staticmethod - def 从缠K序列中获取分型(K线序列: List[缠论K线], 中: 缠论K线) -> "分型": - """ - 从缠K序列中提取以指定缠K为中元素的分型 - - :param K线序列: 缠K列表 - :param 中: 中间K线 - :return: 分型实例(右可能为None) - """ - ... - @staticmethod - def 向序列中添加(分型序列: List["分型"], 当前分型: "分型") -> Any: - """ - 向分型序列尾部添加,自动校验顶底交替 - - :param 分型序列: 现有分型列表 - :param 当前分型: 待添加分型 - :raises ValueError: 首元素非顶底、连续同向分型时抛出 - """ - ... - -class 虚线(object): - """ - 虚线 — 笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。 - - :ivar 标识: 类型标识("笔"/"线段"/"扩展线段"等) - :ivar 序号: 序号 - :ivar 级别: 级别 - :ivar 文: 起点分型 - :ivar 武: 终点分型 - :ivar 有效性: 是否有效 - :ivar 基础序列: 内部虚线序列(笔序列/线段序列) - :ivar 特征序列: 特征序列列表(线段专用) - :ivar 实_中枢序列: 实中枢列表 - :ivar 虚_中枢序列: 虚中枢列表 - :ivar 合_中枢序列: 合中枢列表 - :ivar 确认K线: 确认K线 - :ivar 模式: 买卖点模式 - :ivar 前一缺口: 前一缺口 - :ivar 前一结束位置: 前一笔/线段的终点 - :ivar 短路修正: 是否短路修正 - :ivar 方向: 运行方向(只读) - :ivar 高: 最高价(只读) - :ivar 低: 最低价(只读) - :ivar 笔序列: 所有笔(只读) - :ivar 图表标题: 图表显示标题(只读) - """ - @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... - @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 文(self) -> 分型: ... - @文.setter - def 文(self, value: 分型) -> None: ... - @property - def 武(self) -> 分型: ... - @武.setter - def 武(self, value: 分型) -> None: ... - @property - def 级别(self) -> int: ... - @级别.setter - def 级别(self, value: int) -> None: ... - @property - def 有效性(self) -> bool: ... - @有效性.setter - def 有效性(self, value: bool) -> None: ... - def __init__(self, 序号: int, 标识: str, 文: 分型, 武: 分型, 级别: int, 有效性: bool = True) -> Any: - """ - :param 序号: 序号 - :param 标识: 类型标识 - :param 文: 起点分型 - :param 武: 终点分型 - :param 级别: 级别 - :param 有效性: 是否有效 - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 笔序列(self) -> Any: - """笔序列""" - ... - def 图表标题(self) -> str: - """:return: 图表显示标题""" - ... - def 方向(self) -> "相对方向": - """ - :return: 运行方向 - :raises RuntimeError: 无法识别的方向 - """ - ... - def 高(self) -> float: - """虚线区间的最高价(向上线段为终点分型最高价,向下线段为起点分型最高价)""" - ... - def 低(self) -> float: - """虚线区间的最低价(向下线段为终点分型最低价,向上线段为起点分型最低价)""" - ... - def 之前是(self, 之前: "虚线") -> bool: - """ - :param 之前: 前一条虚线 - :return: 当前虚线的起点是否为前一条虚线的终点 - """ - ... - def 之后是(self, 之后: "虚线") -> bool: - """ - :param 之后: 后一条虚线 - :return: 当前虚线的终点是否为后一条虚线的起点 - """ - ... - def 获取普K序列(self, 观察员: "观察者") -> List[K线]: - """ - :param 观察员: 观察者实例 - :return: 区间内的原始K线列表 - """ - ... - def 获取缠K序列(self, 观察员: "观察者") -> List[缠论K线]: - """ - :param 观察员: 观察者实例 - :return: 区间内的缠K列表 - """ - ... - def 获取数据文本(self) -> str: - """获取用于保存的数据文本""" - ... - @classmethod - def 创建笔(cls, 文: 分型, 武: 分型, 有效性: bool = True) -> "虚线": - """ - :param 文: 起点分型 - :param 武: 终点分型 - :param 有效性: 是否有效 - :return: 虚线实例(标识="笔") - """ - ... - @classmethod - def 创建线段(cls, 虚线序列: List["虚线"]) -> "虚线": - """ - :param 虚线序列: 构成线段的虚线列表(笔) - :return: 虚线实例(标识="线段"或"线段<...>") - """ - ... - @classmethod - def 缠K买卖点模式(cls, 模式: str, 缠K: "缠论K线", 配置: 缠论配置) -> Any: - """ - :param 模式: "全量"/"任意"/"配置"/"相对" - :param 缠K: 待检测的缠论K线 - :param 配置: 缠论配置 - :return: 是否满足买卖点条件 - """ - ... - @classmethod - def 买卖点配置匹配(cls, 缠K: "缠论K线", 配置: 缠论配置) -> bool: - """根据配置中的指标开关检测缠K匹配情况(MACD/KDJ/RSI组合)""" - ... - @classmethod - def 买卖点任意匹配(cls, 缠K: "缠论K线") -> bool: - """ - :param 缠K: 缠论K线 - :return: MACD/KDJ/RSI 任一匹配 - """ - ... - @classmethod - def 买卖点全量匹配(cls, csl: Any, 缠K: "缠论K线") -> bool: - """ - :param 缠K: 缠论K线 - :return: MACD/KDJ/RSI 全部匹配 - """ - ... - @classmethod - def 买卖点相对匹配(cls, 缠K: "缠论K线") -> bool: - """ - :param 缠K: 缠论K线 - :return: 三个指标中至少两个匹配 - """ - ... - @classmethod - def 计算MACD柱子均值(cls, 普K序列: List[K线], 实线: "虚线") -> float: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线(笔/线段) - :return: 区间内MACD柱绝对值的平均值 - """ - ... - @classmethod - def 计算MACD柱子均值_阴(cls, 普K序列: List[K线], 实线: "虚线") -> float: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 区间内负数MACD柱绝对值的平均值,无负数时返回False - """ - ... - @classmethod - def 计算MACD柱子均值_阳(cls, 普K序列: List[K线], 实线: "虚线") -> float: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 区间内正数MACD柱绝对值的平均值,无正数时返回False - """ - ... - @classmethod - def 武之全量MACD均值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点K线的MACD柱是否小于区间均值(全量背驰信号) - """ - ... - @classmethod - def 武之MACD均值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 根据方向选择阳/阴均值判断 - """ - ... - @classmethod - def 武之MACD均值_阴(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点MACD柱绝对值小于阴均值时True(背驰) - """ - ... - @classmethod - def 武之MACD均值_阳(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点MACD柱绝对值小于阳均值时True(背驰) - """ - ... - @classmethod - def 武之MACD极值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点MACD柱是否为区间内极值 - """ - ... - @classmethod - def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence["K线"], 方向: 相对方向) -> Any: - """ - 计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) - - :param 普K序列: K线序列 - :param 方向: 运行方向 - :return: [柱子背驰, DIF背驰, DEA背驰] - """ - ... - @classmethod - @lru_cache(maxsize=128) - def 买卖意义(cls, 实线: "虚线", 观察员: "观察者") -> Tuple[bool, str]: - """ - 静止是相对的,而运动是绝对的 - - :param 实线: 虚线(笔/线段) - :param 观察员: 观察者实例 - :return: (是否具有买卖意义, 原因字符串) - """ - ... - @classmethod - def 计算MACD柱子分段(cls, k线序列: List["K线"]) -> Tuple[List[List["K线"]], ...]: - """ - :param k线序列: K线序列 - :return: 按正负分段的MACD柱列表 - """ - ... - @classmethod - def 密集区域按间隔(cls, 交叉标记: List[int], 最大间隔: int = 5, 最少交叉数: int = 3) -> List[Tuple[int, int, int]]: - """ - 交叉标记: 长度为len(macd_list)的列表,0=无交叉, 1=金叉, -1=死叉 - 最大间隔: 相邻交叉索引差 ≤ 此值则归入同一密集区 - 最少交叉数: 一个密集区内至少包含的交叉次数 - - :return: [(起始交叉索引, 结束交叉索引, 区内交叉次数), ...] - """ - ... - @classmethod - def 统计MACD行为(cls, 普K序列: List[K线], 最大间隔: int = 8, 最少交叉数: int = 3) -> dict: - """ - :param 普K序列: K线序列 - :param 最大间隔: 最大间隔 8 - :param 最少交叉数: 最少交叉数 3 - :return: 统计字典 - """ - ... - @classmethod - def 获取_武(cls, 实线: 虚线) -> 分型: - """ - 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武) - :param 实线: 虚线 - :return: 分型 - """ - ... - -class 笔(object): - """ - 笔 — 纯静态方法容器,提供笔划分算法的所有函数。 - - 主要方法: - 获取缠K数量 — 获取笔内缠K数量 - 停顿 — 检测笔的停顿点 - 自检 — 校验笔的有效性 - 获取所有停顿位置 — 获取笔内所有停顿位置 - 获取起始分型 — 获取笔的起始分型 - 生成笔序列 — 从分型序列生成笔序列 - 是否背驰过 — 判断笔是否经过背驰 - """ - @staticmethod - def 获取缠K数量(缠K序列: List[缠论K线], 笔序列: List[虚线], 配置: 缠论配置) -> int: - """ - 获取笔内有效缠K数量(考虑笔弱化等配置) - - :param 缠K序列: 候选笔的基础缠K序列 - :param 笔序列: 已有笔序列 - :param 配置: 缠论配置 - :return: 有效缠K数量 - """ - ... - @staticmethod - def 次高(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 次高 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 次高缠K - """ - ... - @staticmethod - def 次低(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 次低 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 次低缠K - """ - ... - @staticmethod - def 实际高点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 实际高点 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 实际高点缠K - """ - ... - @staticmethod - def 实际低点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 实际低点 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 实际低点缠K - """ - ... - @staticmethod - def 相对关系(筆: 虚线, 配置: 缠论配置) -> bool: - """ - 相对关系 - - :param 筆: 笔虚线 - :param 配置: 缠论配置 - :return: 方向是否匹配 - """ - ... - @classmethod - def 分析(cls, 当前分型: Optional[分型], 分型序列: List[分型], 笔序列: List[虚线], 缠K序列: List[缠论K线], 普K序列: List[K线], 递归层次: int, 配置: 缠论配置) -> Any: - """ - 笔划分核心递归算法 - - :param 当前分型: 新形成的分型(可能为None) - :param 分型序列: 现有分型列表(原地修改) - :param 笔序列: 现有笔列表(原地修改) - :param 缠K序列: 缠K序列 - :param 普K序列: 普K序列 - :param 递归层次: 递归深度计数 - :param 配置: 缠论配置 - :return: 递归层次(int) - """ - ... - @staticmethod - def 以文会友(笔序列: List[虚线], 文: 分型) -> Optional[虚线]: - """ - 以文会友 - - :param 笔序列: 笔列表 - :param 文: 起点分型 - :return: 匹配的笔或None - """ - ... - @staticmethod - def 以武会友(笔序列: List[虚线], 武: 分型) -> Optional[虚线]: - """ - 以武会友 - - :param 笔序列: 笔列表 - :param 武: 终点分型 - :return: 匹配的笔或None - """ - ... - @staticmethod - def 根据缠K找笔(笔序列: List[虚线], 缠K: "缠论K线", 偏移: int = 1) -> Any: - """ - 根据缠K找笔 - - :param 笔序列: 笔列表 - :param 缠K: 缠论K线 - :param 偏移: 序号偏移量 - :return: 包含该缠K的笔或None - """ - ... - @classmethod - def 自检(cls, 筆: 虚线, 观察员: "观察者") -> bool: - """ - 校验笔的有效性:高/低点是否为实际极值 - - :param 筆: 待校验的笔 - :param 观察员: 观察者 - :return: 有效为True - """ - ... - @classmethod - def 获取所有停顿位置(cls, 筆: 虚线, 观察员: "观察者") -> List[虚线]: - """ - 获取笔内所有可能的停顿位置(用于背驰检测) - - :param 筆: 笔 - :param 观察员: 观察者 - :return: 停顿位置的笔列表 - """ - ... - @classmethod - def 是否背驰过(cls, 当前筆: 虚线, 观察员: "观察者") -> List[缠论K线]: - """ - 判断笔内是否发生过MACD趋向背驰 - - :param 当前筆: 笔 - :param 观察员: 观察者 - :return: 发生背驰的停顿位置列表 - """ - ... - -class 线段特征(list): - """ - 线段特征 — list 子类,持有一组同向虚线(笔),是线段划分的中间结构。 - - 继承 list,元素为虚线。特征序列用于线段划分算法中的包含处理。 - - :ivar 序号: 序号 - :ivar 标识: 标识 - :ivar 线段方向: 线段运行方向(特征序列方向为其翻转) - :ivar 文: 第一个元素的起点分型(根据方向选取极值) - :ivar 武: 最后一个元素的终点分型(根据方向选取极值) - :ivar 高: 最高价 - :ivar 低: 最低价 - :ivar 方向: 特征序列方向(线段方向的翻转) - :ivar 图表标题: 图表标题 - """ - @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 基础序列(self) -> List[虚线]: ... - @基础序列.setter - def 基础序列(self, value: List[虚线]) -> None: ... - @property - def 线段方向(self) -> 相对方向: ... - @线段方向.setter - def 线段方向(self, value: 相对方向) -> None: ... - def __init__(self, 标识: str, 基础序列: List[虚线], 线段方向: 相对方向) -> Any: - """ - :param 标识: 标识符 - :param 基础序列: 基础虚线列表 - :param 线段方向: 线段运行方向 - """ - ... - def 图表标题(self) -> str: - """:return: 图表标题""" - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 文(self) -> 分型: - """起点分型(向上线段取高高中的最大者,向下线段取低低中的最小者)""" - ... - def 武(self) -> 分型: - """终点分型(向上线段取高高中的最大者,向下线段取低低中的最小者)""" - ... - def 高(self) -> float: - """:return: 文和武中分型特征值的较大者""" - ... - def 低(self) -> float: - """:return: 文和武中分型特征值的较小者""" - ... - def 方向(self) -> 相对方向: - """:return: 特征序列方向(线段方向的翻转)""" - ... - def 添加(self, 待添加虚线: Union[虚线]) -> Any: - """ - :param 待添加虚线: 待添加的虚线 - :raises ValueError: 方向不匹配时抛出 - """ - ... - def 删除(self, 待删除虚线: Union[虚线]) -> Any: - """ - :param 待删除虚线: 待删除的虚线 - :raises ValueError: 方向不匹配时抛出 - """ - ... - @classmethod - def 新建(cls, 虚线序列: List[虚线], 线段方向: 相对方向) -> "线段特征": - """ - :param 虚线序列: 基础虚线列表 - :param 线段方向: 线段方向 - :return: 线段特征实例 - """ - ... - @classmethod - def 静态分析(cls, 虚线序列: List[虚线], 线段方向: 相对方向, 四象: str, 是否忽视: bool = False) -> List["线段特征"]: - """ - 静态分析虚线序列,生成特征序列 - - :param 虚线序列: 笔/虚线列表 - :param 线段方向: 线段运行方向 - :param 四象: "老阳"/"老阴"/"小阳"/"少阴" - :param 是否忽视: True时不严格处理缺口包含 - :return: 特征序列列表 - """ - ... - @classmethod - def 获取分型序列(cls, 特征序列: List) -> List[Any]: - """ - 从特征序列提取特征分型序列 - - :param 特征序列: 线段特征列表 - :return: 特征分型列表 - """ - ... - -class 特征分型: - """ - 特征分型 — 由左右中三个线段特征构成的顶/底分型,用于线段划分算法。 - - :ivar 左: 左侧特征序列 - :ivar 中: 中间特征序列 - :ivar 右: 右侧特征序列 - :ivar 结构: 分型结构(顶/底) - """ - @property - def 左(self) -> 线段特征: ... - @左.setter - def 左(self, value: 线段特征) -> None: ... - @property - def 中(self) -> 线段特征: ... - @中.setter - def 中(self, value: 线段特征) -> None: ... - @property - def 右(self) -> 线段特征: ... - @右.setter - def 右(self, value: 线段特征) -> None: ... - @property - def 结构(self) -> 分型结构: ... - @结构.setter - def 结构(self, value: 分型结构) -> None: ... - def __init__(self, 左: 线段特征, 中: 线段特征, 右: 线段特征, 结构: 分型结构) -> Any: - """ - :param 左: 左侧特征序列 - :param 中: 中间特征序列 - :param 右: 右侧特征序列 - :param 结构: 分型结构 - """ - ... + def 增量计算_K线(cls, 前一个MACD: 平滑异同移动平均线, 当前K线: Any, 计算方式: str) -> 平滑异同移动平均线: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... -class 线段(object): - """ - 线段 — 纯静态方法容器,提供线段划分算法的所有函数。 - - 主要方法: - 检查线段破坏(前一线段, 当前线段) — 检查线段是否被破坏 - 获取线段特征序列(笔序列, 线段方向, 需要被合并方向序列) — 提取特征序列 - 线段有缺口(特征序列, 线段方向, 当前分型序列, 特征分型序列) — 判断线段是否有缺口 - 判断新线段(笔序列) — 判断是否形成新线段 - 静态分析(观察员) — 静态线段分析 - 生成线段序列(笔序列, 观察员) — 从笔序列生成线段序列 - """ - @classmethod - def 添加虚线(cls, 段: 虚线, 筆: 虚线) -> Any: - """ - 向线段中添加一笔 - - :param 段: 线段 - :param 筆: 待添加的笔 - :raises ValueError: 不连续或标识不符时抛出 - """ - ... - @classmethod - def 武斗(cls, 段: 虚线, 武: 分型, 行号: int) -> Any: - """ - 更新线段的终点分型(武) - - :param 段: 线段 - :param 武: 新的终点分型 - :param 行号: 调用行号(用于调试) - """ - ... - @classmethod - def 特征分型终结(cls, 段: 虚线) -> bool: - """ - 是否符合特征序列正常分型终结 - - :param 段: 线段 - :return: 是否终结 - """ - ... - @classmethod - def 特征序列状态(cls, 段: 虚线) -> Tuple[bool, bool, bool]: - """ - :param 段: 线段 - :return: (左是否存在, 中是否存在, 右是否存在) - """ - ... - @classmethod - def 获取缺口(cls, 段: 虚线) -> Optional[缺口]: - """ - 获取线段特征序列第一二元素间的缺口 - - :param 段: 线段 - :return: 缺口或None - """ - ... - @classmethod - def 四象(cls, 段: 虚线) -> str: - """ - 判断线段的四象属性 - - :param 段: 线段 - :return: "老阳"(向下线段后继缺口向上线段) / "老阴"(向上线段后继缺口向下线段) / "小阳"(普通向上) / "少阴"(普通向下) - """ - ... - @classmethod - def 设置特征序列(cls, 段: 虚线, 序列: Any, 行号: Any) -> Any: - """ - 设置特征序列 - - :param 段: 线段 - :param 序列: 特征序列三元组 (左,中,右) - :param 行号: 调用行号 - """ - ... - @classmethod - def 刷新特征序列(cls, 段: 虚线, 配置: 缠论配置) -> Any: - """ - 刷新特征序列 - - :param 段: 线段 - :param 配置: 缠论配置 - """ - ... - @classmethod - def 分割序列(cls, 段: 虚线, 所属中枢: Optional["中枢"] = None) -> Tuple[List[虚线], List[虚线], List[虚线], Optional[虚线]]: - """ - 将线段基础序列分割为前/后/第三买卖/贯穿伤四部分 - - :param 段: 线段 - :param 所属中枢: 所属中枢(用于第三买卖点检测) - :return: (前序列, 后序列, 第三买卖线, 贯穿伤) - """ - ... - @classmethod - def 刷新(cls, 段: 虚线, 配置: 缠论配置) -> Any: - """ - 刷新线段的特征序列和内部中枢序列 - - :param 段: 线段 - :param 配置: 缠论配置 - """ - ... - @classmethod - def 序列重置(cls, 段: 虚线, 序列: Sequence) -> Any: - """ - 序列重置 - - :param 段: 线段 - :param 序列: 参考序列 - """ - ... - @classmethod - def 查找贯穿伤(cls, 段: 虚线) -> Optional[虚线]: - """ - 查找贯穿伤 - - :param 段: 线段 - :return: 贯穿伤虚线或None - """ - ... - @classmethod - def 获取内部中枢序列(cls, 段: 虚线, 配置: 缠论配置) -> Tuple[List["中枢"], List["中枢"], List["中枢"]]: - """ - 获取内部中枢序列 - - :param 段: 线段 - :param 配置: 缠论配置 - :return: (虚中枢列表, 实中枢列表, 合中枢列表) - """ - ... - @classmethod - def 基础判断(cls, 左: 虚线, 中: 虚线, 右: 虚线, 关系序列: List[相对方向]) -> bool: - """ - 连续三笔且重叠 - - :param 左: 左侧虚线 - :param 中: 中间虚线 - :param 右: 右侧虚线 - :param 关系序列: 允许的方向关系 - :return: 是否满足基础条件 - """ - ... - @classmethod - def 分析(cls, 笔序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置, 层级: int = 0, 关系序列: Any = [相对方向.向上, 相对方向.向下]) -> None: - """ - 线段划分核心递归算法 - - :param 笔序列: 笔列表 - :param 线段序列: 线段列表(原地修改) - :param 配置: 缠论配置 - :param 层级: 递归深度 - :param 关系序列: 允许的方向关系 - """ - ... - @classmethod - def 武终(cls, 段: 虚线, 行号: int) -> Any: - """ - 武终 - - :param 段: 线段 - :param 行号: 调用行号 - """ - ... - @classmethod - def 验证序列(cls, 段: 虚线, 序列: Sequence) -> Any: - """ - 验证序列 - - :param 段: 线段 - :param 序列: 参考序列 - """ - ... - @classmethod - def 扩展分析(cls, 虚线序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置) -> None: - """ - 即同级别分析 - 将笔看成线段 - - :param 虚线序列: 基础虚线列表 - :param 线段序列: 扩展线段列表(原地修改) - :param 配置: 缠论配置 - """ - ... - @classmethod - @lru_cache(maxsize=128) - def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者) -> bool: - """ - 判断线段内部是否发生背驰(基于内部中枢和MACD) - - :param 当前段: 线段 - :param 观察员: 观察者 - :return: bool - """ - ... - @classmethod - def 获取所有停顿位置(cls, 段: 虚线, 观察员: "观察者") -> List[虚线]: - """ - 获取所有停顿位置 - - :param 段: 线段 - :param 观察员: 观察者 - :return: 停顿位置的线段列表 - """ - ... - @classmethod - def 是否背驰过(cls, 当前段: 虚线, 观察员: "观察者") -> List[缠论K线]: - """ - 判断线段内是否发生过背驰(遍历所有停顿位置) - - :param 当前段: 线段 - :param 观察员: 观察者 - :return: 背驰点列表 - """ - ... - -class 中枢(object): - """ - 中枢 — 三段虚线重叠区间构成的价格中枢,支持延伸和扩展。 - - :ivar 序号: 序号 - :ivar 标识: 标识 - :ivar 级别: 级别 - :ivar 基础序列: 构成中枢的基础虚线(至少3条) - :ivar 第三买卖线: 第三类买卖点关联虚线 - :ivar 本级_第三买卖线: 本级第三类买卖点虚线 - :ivar 高: 中枢上沿(虚线低点的最大值) - :ivar 低: 中枢下沿(虚线高点的最小值) - :ivar 高高: 全区间最高价 - :ivar 低低: 全区间最低价 - :ivar 文: 起点分型 - :ivar 武: 终点分型 - :ivar 方向: 中枢方向(首条虚线的方向翻转) - :ivar 离开段: 最后一条虚线 - """ +class 相对强弱指数: @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... + def 时间戳(self) -> str: ... @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 级别(self) -> int: ... - @级别.setter - def 级别(self, value: int) -> None: ... - @property - def 基础序列(self) -> List[虚线]: ... - @基础序列.setter - def 基础序列(self, value: List[虚线]) -> None: ... - def __init__(self, 序号: int, 标识: str, 级别: int, 基础序列: List[虚线]) -> Any: - """ - :param 序号: 序号 - :param 标识: 标识 - :param 级别: 级别 - :param 基础序列: 基础虚线列表 - """ - ... - def 添加虚线(self, 实线: 虚线) -> Any: - """ - 向中枢添加新虚线(延伸),重置第三买卖线 - - :param 实线: 新虚线 - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 图表标题(self) -> str: - """:return: 图表标题""" - ... - def 离开段(self) -> 虚线: - """:return: 最后一条虚线""" - ... - def 方向(self) -> 相对方向: - """:return: 中枢方向(首条虚线的方向翻转)""" - ... - def 高(self) -> float: - """:return: 中枢上沿(前三段中虚线高点的最小值)""" - ... - def 低(self) -> float: - """:return: 中枢下沿(前三段中虚线低点的最大值)""" - ... - def 高高(self) -> float: - """:return: 全区间最高价""" - ... - def 低低(self) -> float: - """:return: 全区间最低价""" - ... - def 文(self) -> 分型: - """:return: 起点分型""" - ... - def 武(self) -> 分型: - """:return: 终点分型""" - ... - def 获取数据文本(self) -> str: - """获取用于保存的数据文本""" - ... - def 完整性(self, 虚实: str) -> Any: - """ - 判断中枢是否完整(是否有第三买卖点或内部中枢离开) - - 详情见 教你炒股票 43:有关背驰的补习课(2007-04-06 15:31:28) - 不完整时 下一个中枢大概率会与当前中枢发生扩展! - - :param 虚实: "实"/"虚"/"合" - :return: 完整为True - """ - ... - def 获取序列(self) -> List[虚线]: - """ - 获取中枢的完整虚线序列(基础序列+第三买卖线) - - :return: 虚线列表 - """ - ... - def 获取扩展中枢(self, 扩展中枢: List, 配置: 缠论配置) -> Any: - """ - 当基础序列>=9时,从中枢中提取扩展线段中枢 - - :param 扩展中枢: 存放扩展中枢的列表 - :param 配置: 缠论配置 - """ - ... - def 校验合法性(self, 序列: Sequence[虚线], 中枢序列: List["中枢"]) -> bool: - """ - 校验当前中枢在给定序列中是否仍然合法 - - :param 序列: 基础虚线序列 - :param 中枢序列: 中枢列表 - :return: 合法为True,不合法会原地裁剪基础序列 - """ - ... - def 设置第三买卖线(self, 线: Union[虚线, None]) -> Any: - """ - 设置第三类买卖点关联虚线 - - :param 线: 第三买卖虚线或None - """ - ... - def 当前状态(self) -> Any: - """ - 获取中枢当前状态:中枢之中/中枢之上/中枢之下 - - 详情见 教你炒股票 49:利润率最大的操作模式(2007-04-26 08:16:56) - 当前中枢最后一段所处的位置关系 - - 一、当下在该中枢之中。 - 因为在中枢里,由于这时候怎么演化都是对的,不操作是最好的操作,等待其演化第二、三类, - 当然,如果你技术好点,可以判断出次级别的第二类买点,这些买点很多情况下都是在中枢中出现的,那当然也是可以参与的。 - 但如果没有这种技术,那就有了再说了。只把握你自己当下技术水平能把握的机会,这才是最重要的。 - 二、当下在该中枢之下。 - 1.当下之前未出现该中枢第三类卖点。 - 2.当下之前已出现该中枢第三类卖点(正出现也包括在这种情况下,按最严格的定义,这最精确的卖点,是瞬间完成的,而具有操作意义的第三类卖点,其实是一个包含该最精确卖点的足够小区间) - 三、当下在该中枢之上。 - 1.当下之前未出现该中枢第三类买点。 - 2.当下之前已出现该中枢第三类买点。 - - :return: "中枢之中" / "中枢之上" / "中枢之下" - """ - ... - @classmethod - def 基础检查(cls, 左: 虚线, 中: 虚线, 右: 虚线) -> bool: - """ - 检查三条虚线是否构成中枢(连续且重叠) - - :param 左: 左侧虚线 - :param 中: 中间虚线 - :param 右: 右侧虚线 - :return: bool - """ - ... - @classmethod - def 创建(cls, 左: 虚线, 中: 虚线, 右: 虚线, 级别: int, 标识: str = "") -> "中枢": - """ - 从三条连续且重叠的虚线创建中枢 - - :param 左: 左侧虚线 - :param 中: 中间虚线 - :param 右: 右侧虚线 - :param 级别: 中枢级别 - :param 标识: 中枢标识前缀 - :return: 中枢实例 - """ - ... - @classmethod - def 从序列中获取中枢(cls, 虚线序列: Sequence[虚线], 起始方向: 相对方向, 标识: str) -> Optional["中枢"]: - """ - 从虚线序列中按起始方向查找第一个中枢 - - :param 虚线序列: 虚线列表 - :param 起始方向: 第一条虚线的方向 - :param 标识: 中枢标识前缀 - :return: 中枢或None - """ - ... - @classmethod - def 向中枢序列尾部添加(cls, 中枢序列: List["中枢"], 待添加中枢: "中枢") -> Any: - """ - :param 中枢序列: 中枢列表 - :param 待添加中枢: 待添加的中枢 - """ - ... - @classmethod - def 从中枢序列尾部弹出(cls, 中枢序列: List["中枢"], 待弹出中枢: "中枢") -> Optional["中枢"]: - """ - :param 中枢序列: 中枢列表 - :param 待弹出中枢: 待弹出的中枢 - :return: 弹出的中枢或None - """ - ... - @classmethod - def 分析(cls, 虚线序列: Sequence[虚线], 中枢序列: List["中枢"], 跳过首部: bool = True, 标识: str = "", 层级: int = 0) -> None: - """ - 中枢识别核心递归算法 - - :param 虚线序列: 基础虚线列表 - :param 中枢序列: 中枢列表(原地修改) - :param 跳过首部: True: 跳过首元素中枢 - :param 标识: 中枢标识前缀 - :param 层级: 递归深度 - """ - ... - -class 观察者: - """ - 观察者 — 单周期缠论分析器,接收K线流式输入并逐层计算所有层级序列。 - - 核心入口为 增加原始K线(普K),每收到一根新K线就增量更新: - 原始K线 → 缠论K线(包含处理+合并)→ 分型 → 笔 → 线段 → 中枢 → 买卖点 - - :ivar 标识: "{符号}:{周期}" - :ivar 当前K线: 最后一根原始K线 - :ivar 当前缠K: 最后一根缠论K线 - :ivar 普通K线序列 / 缠论K线序列 / 分型序列 / 笔序列 / 线段序列 / 中枢序列 等 - """ - @property - def 符号(self) -> str: ... - @符号.setter - def 符号(self, value: str) -> None: ... + def 收盘价(self) -> float: ... @property def 周期(self) -> int: ... - @周期.setter - def 周期(self, value: int) -> None: ... @property - def 配置(self) -> 缠论配置: ... - @配置.setter - def 配置(self, value: 缠论配置) -> None: ... - def __init__(self, 符号: str, 周期: int, 配置: 缠论配置) -> Any: - """ - 初始化观察者 - - :param 符号: 交易对符号 - :param 周期: K线周期(秒) - :param 配置: 缠论配置 - """ - ... - def 观察员(self) -> Any: - """观察员(自引用)""" - ... - def 标识(self) -> str: - ''':return: "{符号}:{周期}"''' - ... - def 当前K线(self) -> Optional["K线"]: - """:return: 最后一根原始K线""" - ... - def 当前缠K(self) -> Optional["缠论K线"]: - """:return: 最后一根缠论K线""" - ... - def 重置基础序列(self) -> Any: - """清空所有分析序列,重置为初始状态""" - ... - @final - def 增加原始K线(self, 普K: K线) -> Any: - """ - 核心入口 — 投喂一根原始K线,增量更新所有层级 - - :param 普K: 新到的原始K线 - - 处理流程: 原始K线→缠论K线(包含处理+合并)→分型→笔→线段→中枢→买卖点 - """ - ... - def 测试_保存数据(self, root: str = None) -> Any: - """ - 拆分各序列数据,单独存文件,文件名为对应变量名 - - :param root: 保存根目录(可选) - """ - ... - def 识别买卖点(self) -> Any: - """识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现)""" - ... - def 静态重新分析(self) -> Any: - """静态重新分析(占位方法)""" - ... - def 加载本地数据(self, 文件路径: str) -> Any: ... + def 超买阈值(self) -> float: ... + @property + def 超卖阈值(self) -> float: ... + @property + def RSI_SMA周期(self) -> Optional[int]: ... + @property + def RSI(self) -> Optional[float]: ... + @property + def 平均上涨(self) -> Optional[float]: ... + @property + def 平均下跌(self) -> Optional[float]: ... + @property + def 上涨幅度(self) -> float: ... + @property + def 下跌幅度(self) -> float: ... + @property + def 平滑系数(self) -> float: ... + @property + def RSI_SMA(self) -> Optional[float]: ... + @property + def RSI历史队列(self) -> List[float]: ... @classmethod - def 读取数据文件(cls, 文件路径: str, 配置: Any = 缠论配置()) -> Self: - """ - :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb - :param 配置: 缠论配置 - :return: 观察者实例 - """ - ... + def 首次计算(cls, 初始收盘价: float, 初始时间: int, 周期: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: ... + @classmethod + def 首次计算_K线(cls, k线: Any, 计算方式: str, 周期: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: ... + @classmethod + def 增量计算(cls, 前一个RSI: 相对强弱指数, 当前收盘价: float, 当前时间: int) -> 相对强弱指数: ... + @classmethod + def 增量计算_K线(cls, 前一个RSI: 相对强弱指数, 当前K线: Any, 计算方式: str) -> 相对强弱指数: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... -class K线合成器: - """ - K线合成器 — 将小周期K线合成为大周期K线,支持多周期级联合成。 +class 随机指标: + @property + def 时间戳(self) -> str: ... + @property + def 最高价(self) -> float: ... + @property + def 最低价(self) -> float: ... + @property + def 收盘价(self) -> float: ... + @property + def N(self) -> int: ... + @property + def M1(self) -> int: ... + @property + def M2(self) -> int: ... + @property + def 超买阈值(self) -> float: ... + @property + def 超卖阈值(self) -> float: ... + @property + def RSV(self) -> Optional[float]: ... + @property + def K(self) -> Optional[float]: ... + @property + def D(self) -> Optional[float]: ... + @property + def J(self) -> Optional[float]: ... + @property + def 历史最高价队列(self) -> List[float]: ... + @property + def 历史最低价队列(self) -> List[float]: ... + @property + def 前一个RSV(self) -> Optional[float]: ... + @property + def 前一个K(self) -> Optional[float]: ... + @property + def 前一个D(self) -> Optional[float]: ... + @classmethod + def 首次计算(cls, 初始最高价: float, 初始最低价: float, 初始收盘价: float, 初始时间: int, N: Optional[int] = None, M1: Optional[int] = None, M2: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None) -> 随机指标: ... + @classmethod + def 首次计算_K线(cls, k线: Any, _计算方式: str, RSV周期: Optional[int] = None, K值平滑周期: Optional[int] = None, D值平滑周期: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None) -> 随机指标: ... + @classmethod + def 增量计算(cls, 前一个KDJ: 随机指标, 当前最高价: float, 当前最低价: float, 当前收盘价: float, 当前时间: int) -> 随机指标: ... + @classmethod + def 增量计算_K线(cls, 前一个KDJ: 随机指标, 当前K线: Any, _计算方式: str) -> 随机指标: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... - :ivar 标识: 合成器标识 - :ivar 周期组: 从小到大排列的周期组 - :ivar 当前K线: 各周期当前K线 - :ivar 合成K线列表: 各周期已合成的K线列表 - """ +class 布林带: + @property + def 时间戳(self) -> int: ... + @property + def 周期(self) -> int: ... + @property + def 标准差倍数(self) -> float: ... + @property + def 上轨(self) -> float: ... + @property + def 中轨(self) -> float: ... + @property + def 下轨(self) -> float: ... + @classmethod + def 首次计算(cls, k线: Any, 计算方式: str, 周期: int = 20, 标准差倍数: float = 2.0) -> 布林带: ... + @classmethod + def 增量计算(cls, 前一个布林带: 布林带, 当前K线: Any, 计算方式: str) -> 布林带: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class 指标容器: + def __init__(self) -> None: ... + def 获取(self, 名称: str) -> Optional[Any]: ... + def 设置(self, 名称: str, 值: Any) -> None: ... + def 包含(self, 名称: str) -> bool: ... + def keys(self) -> List[str]: ... + def __getitem__(self, 名称: str) -> Any: ... + def __getattr__(self, 名称: str) -> Any: ... + def __contains__(self, 名称: str) -> bool: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class 指标: + @classmethod + def K线取值(cls, k线: Any, 指标计算方式: str) -> float: ... + +class 均线工具: + @staticmethod + def 增量SMA(普K序列: List[Any], period: int, 计算方式: str) -> float: ... + @staticmethod + def 增量EMA(普K序列: List[Any], period: int, 计算方式: str, 前值: Optional[float] = None) -> float: ... + +class 指标计算器: + @staticmethod + def 计算并挂载(当前K线: Any, 全序列: List[Any], 配置: 缠论配置) -> None: ... + +# ========== K线 ========== + +class K线: + def __init__(self, 标识: str = "bar", 序号: int = 0, 周期: int = 60, 时间戳: int = 0, 高: float = 0.0, 低: float = 0.0, 开盘价: float = 0.0, 收盘价: float = 0.0, 成交量: float = 0.0) -> None: ... + @property + def 标识(self) -> str: ... + @property + def 序号(self) -> int: ... + @property + def 周期(self) -> int: ... + @property + def 时间戳(self) -> int: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def 开盘价(self) -> float: ... + @property + def 收盘价(self) -> float: ... + @property + def 成交量(self) -> float: ... + @property + def 方向(self) -> 相对方向: ... + @property + def macd(self) -> Optional[平滑异同移动平均线]: ... + @property + def rsi(self) -> Optional[相对强弱指数]: ... + @property + def kdj(self) -> Optional[随机指标]: ... + @property + def 指标(self) -> 指标容器: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + @classmethod + def 创建普K(cls, 标识: str, 时间戳: int, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: Optional[int] = None, 周期: Optional[int] = None) -> K线: ... + @classmethod + def 保存到DAT文件(cls, 路径: str, K线序列: List[K线]) -> None: ... + @classmethod + def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> Optional[K线]: ... + @classmethod + def 获取MACD(cls, k线序列: List[K线], 始: K线, 终: K线) -> Dict[str, float]: ... + @staticmethod + def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __bytes__(self) -> bytes: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +class 缠论K线: + @property + def 序号(self) -> int: ... + @property + def 时间戳(self) -> int: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 分型(self) -> Optional[分型结构]: ... + @property + def 周期(self) -> int: ... + @property + def 标识(self) -> str: ... + @property + def 分型特征值(self) -> float: ... + @property + def 原始起始序号(self) -> int: ... + @property + def 原始结束序号(self) -> int: ... + @property + def 标的K线(self) -> K线: ... + @property + def 镜像(self) -> 缠论K线: ... + @property + def 与MACD柱子匹配(self) -> bool: ... + @property + def 与RSI匹配(self) -> bool: ... + @property + def 与KDJ匹配(self) -> bool: ... + @property + def 买卖点信息(self) -> Any: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + @classmethod + def 时间戳对齐(cls, 基线: List[缠论K线], k线: 缠论K线) -> int: ... + @classmethod + def 创建缠K(cls, 时间戳: int, 高: float, 低: float, 方向: 相对方向, 结构: Optional[分型结构], 原始序号: int, 普k: K线, 之前: Optional[缠论K线] = None) -> 缠论K线: ... + @classmethod + def 分析(cls, 当前K线: K线, 缠K序列: List[缠论K线], 普K序列: List[K线], 配置: 缠论配置) -> Tuple[str, Optional[分型]]: ... + @staticmethod + def 截取(序列: List[缠论K线], 始: 缠论K线, 终: 缠论K线) -> List[缠论K线]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +# ========== Structure ========== + +class 分型: + def __init__(self, 左: Optional[缠论K线], 中: 缠论K线, 右: Optional[缠论K线]) -> None: ... + @property + def 左(self) -> Optional[缠论K线]: ... + @property + def 中(self) -> 缠论K线: ... + @property + def 右(self) -> Optional[缠论K线]: ... + @property + def 结构(self) -> 分型结构: ... + @property + def 时间戳(self) -> int: ... + @property + def 分型特征值(self) -> float: ... + @property + def _结构(self) -> 分型结构: ... + @property + def _时间戳(self) -> int: ... + @property + def _分型特征值(self) -> float: ... + @property + def 关系组(self) -> Optional[Tuple[相对方向, 相对方向, 相对方向]]: ... + @property + def 强度(self) -> str: ... + @property + def 与MACD柱子分型匹配(self) -> bool: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + @classmethod + def 判断分型(cls, 左: 分型, 右: 分型, 模式: str = "中") -> bool: ... + @staticmethod + def 从缠K序列中获取分型(K线序列: List[缠论K线], 中: 缠论K线) -> Optional[分型]: ... + @staticmethod + def 向序列中添加(分型序列: Any, 当前分型: 分型) -> None: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +class 虚线: + def __init__(self, 序号: int, 标识: str, 文: 分型, 武: 分型, 级别: int, 有效性: bool = True) -> None: ... + @property + def 标识(self) -> str: ... + @property + def 序号(self) -> int: ... + @property + def 级别(self) -> int: ... + @property + def 文(self) -> 分型: ... + @property + def 武(self) -> 分型: ... + @property + def 有效性(self) -> bool: ... + @property + def 模式(self) -> str: ... + @property + def 特征序列_显示(self) -> bool: ... + @特征序列_显示.setter + def 特征序列_显示(self, value: bool) -> None: ... + @property + def 特征序列(self) -> List[Optional[线段特征]]: ... + @property + def 短路修正(self) -> bool: ... + @property + def 确认K线(self) -> Optional[缠论K线]: ... + @property + def 前一缺口(self) -> Optional[缺口]: ... + @property + def 前一结束位置(self) -> Optional[虚线]: ... + @property + def 基础序列(self) -> List[虚线]: ... + @property + def 实_中枢序列(self) -> List[中枢]: ... + @property + def 虚_中枢序列(self) -> List[中枢]: ... + @property + def 合_中枢序列(self) -> List[中枢]: ... + @property + def 笔序列(self) -> List[虚线]: ... + @property + def 图表标题(self) -> str: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + def 之前是(self, 之前: 虚线) -> bool: ... + def 之后是(self, 之后: 虚线) -> bool: ... + def 获取普K序列(self, 观察员: 观察者) -> List[K线]: ... + def 获取缠K序列(self, 观察员: 观察者) -> List[缠论K线]: ... + def 获取数据文本(self) -> str: ... + @classmethod + def 获取_武(cls, 实线: 虚线) -> 分型: ... + @classmethod + def 创建笔(cls, 文: 分型, 武: 分型, 有效性: bool = True) -> 虚线: ... + @classmethod + def 创建线段(cls, 虚线序列: List[虚线]) -> 虚线: ... + @classmethod + def 缠K买卖点模式(cls, 模式: str, 缠K: 缠论K线, 配置: 缠论配置) -> bool: ... + @classmethod + def 买卖点配置匹配(cls, 缠K: 缠论K线, 配置: 缠论配置) -> bool: ... + @classmethod + def 买卖点任意匹配(cls, 缠K: 缠论K线) -> bool: ... + @classmethod + def 买卖点全量匹配(cls, 缠K: 缠论K线) -> bool: ... + @classmethod + def 买卖点相对匹配(cls, 缠K: 缠论K线) -> bool: ... + @classmethod + def 计算MACD柱子均值(cls, 普K序列: List[K线], 实线: 虚线) -> float: ... + @classmethod + def 武之全量MACD均值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 武之MACD均值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 武之MACD极值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 计算MACD柱子均值_阴(cls, 普K序列: List[K线], 实线: 虚线) -> Optional[float]: ... + @classmethod + def 计算MACD柱子均值_阳(cls, 普K序列: List[K线], 实线: 虚线) -> Optional[float]: ... + @classmethod + def 武之MACD均值_阴(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 武之MACD均值_阳(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 计算K线序列MACD趋向背驰(cls, 普K序列: List[K线], 方向: 相对方向) -> Tuple[bool, bool, bool]: ... + @classmethod + def 计算MACD柱子分段(cls, k线序列: List[K线]) -> List[List[float]]: ... + @classmethod + def 密集区域按间隔(cls, 交叉标记: List[int], 最大间隔: int = 5, 最少交叉数: int = 3) -> List[Tuple[int, int, int]]: ... + @classmethod + def 统计MACD行为(cls, 普K序列: List[K线], 最大间隔: int = 8, 最少交叉数: int = 3) -> Dict[str, Any]: ... + @classmethod + def 买卖意义(cls, 实线: 虚线, 观察员: 观察者) -> Tuple[bool, str]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +class 线段特征: + @property + def 序号(self) -> int: ... @property def 标识(self) -> str: ... @标识.setter def 标识(self, value: str) -> None: ... @property - def 周期组(self) -> List[int]: ... - @周期组.setter - def 周期组(self, value: List[int]) -> None: ... + def 线段方向(self) -> 相对方向: ... @property - def 事件回调(self) -> Optional[Callable]: ... - @事件回调.setter - def 事件回调(self, value: Optional[Callable]) -> None: ... - def __init__(self, 标识: str, 周期组: List[int], 事件回调: Optional[Callable] = None) -> Any: - """ - 初始化K线合成器 + def 基础序列(self) -> List[虚线]: ... + @property + def 图表标题(self) -> str: ... + @property + def 文(self) -> 分型: ... + @property + def 武(self) -> 分型: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @classmethod + def 静态分析(cls, 虚线序列: List[虚线], 线段方向: 相对方向, 四象: str, 是否忽视: bool = False) -> List[线段特征]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... - :param 标识: 合成器标识 - :param 周期组: 从小到大排列的周期列表 - :param 事件回调: 可选,K线合成完成时的回调函数(信号类型, 标识, 周期, 完成K线) - """ - ... - def 设置事件回调(self, 回调函数: Callable) -> Any: - """ - 设置事件回调函数 +# ========== Algorithm ========== - :param 回调函数: 回调函数 - """ - ... - def 投喂(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float) -> Any: - """ - 投喂原始tick数据 +class 背驰分析: + @classmethod + def MACD背驰(cls, 进入段: 虚线, 离开段: 虚线, K线序列: List[K线], 方式: str = "总") -> bool: ... + @classmethod + def 斜率背驰(cls, 进入段: 虚线, 离开段: 虚线) -> bool: ... + @classmethod + def 测度背驰(cls, 进入段: 虚线, 离开段: 虚线) -> bool: ... + @classmethod + def 全量背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... + @classmethod + def 任意背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... + @classmethod + def 配置背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置) -> bool: ... + @classmethod + def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... + @classmethod + def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... - :param 时间戳: 时间戳 - :param 开: 开盘价 - :param 高: 最高价 - :param 低: 最低价 - :param 收: 收盘价 - :param 量: 成交量 - """ - ... - def 投喂K线(self, 普K: K线) -> Any: - """ - 统一入口 — 投喂最小周期K线,自动合成大周期并分发给各周期观察者 +class 笔: + @classmethod + def 以文会友(cls, 笔序列: List[虚线], 文: 分型) -> Optional[虚线]: ... + @classmethod + def 以武会友(cls, 笔序列: List[虚线], 武: 分型) -> Optional[虚线]: ... + @classmethod + def 根据缠K找笔(cls, 笔序列: List[虚线], 缠K: 缠论K线, 偏移: int = 1) -> Optional[虚线]: ... + @classmethod + def 分析(cls, 当前分型: Optional[分型], 分型序列: List[分型], 笔序列: List[虚线], 缠K序列: List[缠论K线], 普K序列: List[K线], 递归层次: int, 配置: 缠论配置) -> int: ... + @classmethod + def 获取所有停顿位置(cls, 筆: 虚线, 观察员: 观察者) -> List[虚线]: ... + @classmethod + def 是否背驰过(cls, 当前筆: 虚线, 观察员: 观察者) -> List[缠论K线]: ... - :param 普K: 最小周期原始K线 - """ - ... - def 获取当前K线(self, 周期: int) -> Optional[K线]: - """ - 获取指定周期当前正在合成的K线 +class 线段: + @classmethod + def 四象(cls, 段: 虚线) -> str: ... + @classmethod + def 获取缺口(cls, 段: 虚线) -> Optional[缺口]: ... + @classmethod + def 特征分型终结(cls, 段: 虚线) -> bool: ... + @classmethod + def 特征序列状态(cls, 段: 虚线) -> Tuple[bool, bool, bool]: ... + @classmethod + def 查找贯穿伤(cls, 段: 虚线) -> Optional[虚线]: ... + @classmethod + def 分割序列(cls, 段: 虚线, 所属中枢: Optional[中枢] = None) -> Tuple[List[虚线], List[虚线], List[虚线], Optional[虚线]]: ... + @classmethod + def 获取内部中枢序列(cls, 段: 虚线, 配置: 缠论配置) -> Tuple[List[中枢], List[中枢], List[中枢]]: ... + @classmethod + def 分析(cls, 笔序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置, 层级: int, 关系序列: Optional[List[相对方向]] = None) -> None: ... + @classmethod + def 扩展分析(cls, 虚线序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置) -> None: ... + @classmethod + def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 获取所有停顿位置(cls, 段: 虚线, 观察员: 观察者) -> List[虚线]: ... + @classmethod + def 是否背驰过(cls, 当前段: 虚线, 观察员: 观察者) -> List[缠论K线]: ... - :param 周期: 目标周期 - :return: 当前K线或None - """ - ... +class 中枢: + def __init__(self, 序号: int, 标识: str, 级别: int, 基础序列: List[虚线]) -> None: ... + @property + def 序号(self) -> int: ... + @property + def 标识(self) -> str: ... + @property + def 级别(self) -> int: ... + @property + def 基础序列(self) -> List[虚线]: ... + @property + def 第三买卖线(self) -> Optional[虚线]: ... + @property + def 本级_第三买卖线(self) -> Optional[虚线]: ... + @property + def 图表标题(self) -> str: ... + @property + def 离开段(self) -> 虚线: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def 高高(self) -> float: ... + @property + def 低低(self) -> float: ... + @property + def 文(self) -> 分型: ... + @property + def 武(self) -> 分型: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + def 设置第三买卖线(self, 线: 虚线) -> None: ... + def 获取序列(self) -> List[虚线]: ... + def 获取数据文本(self) -> str: ... + def 完整性(self, 虚实: str = "合") -> bool: ... + def 获取扩展中枢(self, 扩展中枢: List[中枢], 配置: 缠论配置) -> None: ... + def 当前状态(self) -> str: ... + @classmethod + def 基础检查(cls, 左: 虚线, 中: 虚线, 右: 虚线) -> bool: ... + @classmethod + def 创建(cls, 左: 虚线, 中: 虚线, 右: 虚线, 级别: int, 标识: str = "") -> 中枢: ... + @classmethod + def 分析(cls, 虚线序列: List[虚线], 中枢序列: List[中枢], 跳过首部: bool = True, 标识: str = "", 层级: int = 0) -> None: ... + @classmethod + def 从序列中获取中枢(cls, 虚线序列: List[虚线], 起始方向: 相对方向, 标识: str) -> Optional[中枢]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... -class 立体分析器: - """ - 立体分析器 — 多周期缠论分析器,内部包含K线合成器 + 每周期一个观察者。 +# ========== Business ========== - 通过最小周期合成大周期K线,各周期观察者独立分析,实现多周期联立。 +class 基础买卖点: + def __init__(self, 类型: 买卖点类型, 当前K线: K线, 买卖点分型: 分型, 备注: str, 中枢破位值: float) -> None: ... + @property + def 备注(self) -> str: ... + @备注.setter + def 备注(self, value: str) -> None: ... + @property + def 类型(self) -> 买卖点类型: ... + @property + def 买卖点分型(self) -> 分型: ... + @property + def 买卖点K线(self) -> 缠论K线: ... + @property + def 当前K线(self) -> K线: ... + @property + def 失效K线(self) -> Optional[K线]: ... + @property + def 终结K线(self) -> Optional[K线]: ... + @property + def 破位值(self) -> float: ... + @property + def 结构(self) -> Optional[分型结构]: ... + @property + def 偏移(self) -> int: ... + @property + def 失效偏移(self) -> int: ... + @property + def 有效性(self) -> bool: ... + @property + def 与MACD柱子匹配(self) -> bool: ... + @property + def 与MACD柱子分型匹配(self) -> bool: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... - :ivar 周期组: 所有分析周期 - :ivar 观察员: 各周期对应的观察者 - :ivar K线合成器: 内部K线合成器 - """ +class 买卖点(基础买卖点): + @classmethod + def 一卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 一买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 二卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 二买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 三卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 三买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1P卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1P买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2S卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2S买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3A卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3A买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3B卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3B买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 生成买卖点(cls, 特征: str, 序号: str, 级别: str, 买卖点分型: 分型, 当前缠K: 缠论K线) -> 买卖点: ... + +class 观察者: + def __init__(self, 符号: str, 周期: int, 配置: 缠论配置 = ...) -> None: ... + @property + def 观察员(self) -> 观察者: ... + @property + def 标识(self) -> str: ... + @property + def 当前K线(self) -> Optional[K线]: ... + @property + def 当前缠K(self) -> Optional[缠论K线]: ... @property def 符号(self) -> str: ... - @符号.setter - def 符号(self, value: str) -> None: ... @property - def 周期组(self) -> List[int]: ... - @周期组.setter - def 周期组(self, value: List[int]) -> None: ... + def 周期(self) -> int: ... @property def 配置(self) -> 缠论配置: ... - @配置.setter - def 配置(self, value: 缠论配置) -> None: ... @property - def 配置组(self) -> Dict[int, 缠论配置]: ... - @配置组.setter - def 配置组(self, value: Dict[int, 缠论配置]) -> None: ... - def __init__(self, 符号: str, 周期组: List[int], 配置: 缠论配置 = 缠论配置(), 配置组: Dict[int, 缠论配置] = dict()) -> Any: - """ - 初始化多周期立体分析器 + def 普通K线序列(self) -> List[K线]: ... + @property + def 基础缠K序列(self) -> List[缠论K线]: ... + @基础缠K序列.setter + def 基础缠K序列(self, value: List[缠论K线]) -> None: ... + @property + def 缠论K线序列(self) -> List[缠论K线]: ... + @property + def 分型序列(self) -> List[分型]: ... + @property + def 笔序列(self) -> List[虚线]: ... + @property + def 笔_中枢序列(self) -> List[中枢]: ... + @property + def 线段序列(self) -> List[虚线]: ... + @property + def 中枢序列(self) -> List[中枢]: ... + @property + def 扩展线段序列(self) -> List[虚线]: ... + @property + def 扩展中枢序列(self) -> List[中枢]: ... + @property + def 扩展线段序列_线段(self) -> List[虚线]: ... + @property + def 扩展中枢序列_线段(self) -> List[中枢]: ... + @property + def 线段_线段序列(self) -> List[虚线]: ... + @property + def 线段_中枢序列(self) -> List[中枢]: ... + @property + def 扩展线段序列_扩展线段(self) -> List[虚线]: ... + @property + def 扩展中枢序列_扩展线段(self) -> List[中枢]: ... + def 重置基础序列(self) -> None: ... + def 增加原始K线(self, 普K: K线) -> None: ... + def 投喂原始数据(self, 时间戳: int, 开: float, 高: float, 低: float, 收: float, 量: float) -> None: ... + def 加载本地数据(self, 文件路径: str) -> None: ... + def 静态重新分析(self) -> None: ... + def 测试_保存数据(self, root: Optional[str] = None) -> str: ... + @classmethod + def 读取数据文件(cls, 观察员: 观察者, 文件路径: str, 配置: Optional[缠论配置] = None) -> 观察者: ... - :param 符号: 交易对符号 - :param 周期组: 所有分析周期列表(第一个为最小输入周期) - :param 配置: 默认配置 - :param 配置组: 各周期独立配置 {周期: 缠论配置} - """ - ... - def 投喂K线(self, 普K: K线) -> Any: - """ - 统一入口 — 投喂最小周期K线,自动合成大周期并分发给各周期观察者 +class K线合成器: + def __init__(self, 标识: str, 周期组: List[int]) -> None: ... + @property + def 标识(self) -> str: ... + @property + def 周期组(self) -> List[int]: ... + def 投喂K线(self, 普K: K线) -> List[Tuple[int, K线]]: ... + def 投喂(self, 时间戳: int, 开: float, 高: float, 低: float, 收: float, 量: float) -> List[Tuple[int, K线]]: ... + def 获取当前K线(self, 周期: int) -> Optional[K线]: ... - :param 普K: 最小周期原始K线 - :raises RuntimeError: 当投喂的K线周期与输入周期不匹配时 - """ - ... - def 测试_保存数据(self) -> Any: - """拆分各序列数据,单独存文件,文件名为对应变量名""" - ... +class 立体分析器: + def __init__(self, 符号: str, 周期组: List[int], 配置: Optional[缠论配置] = None, 配置组: Optional[Dict[int, 缠论配置]] = None) -> None: ... + @property + def 周期组(self) -> List[int]: ... + def 投喂K线(self, 普K: K线) -> None: ... + def 获取观察者(self, 周期: int) -> Optional[观察者]: ... + def 测试_保存数据(self, root: Optional[str] = None) -> None: ... -def 转化为时间戳(ts: Any) -> int: ... -def 转化为时间戳_数字(ts: Any) -> int: ... +# ========== 缠论配置 (fields via __getattr__/__setattr__) ========== + +class 缠论配置: + def __init__(self, **kwargs: Any) -> None: ... + def to_dict(self) -> Dict[str, Any]: ... + def to_json(self) -> str: ... + def 保存配置(self, path: str = "缠论配置.json") -> None: ... + def 对比(self, other: 缠论配置) -> Dict[str, Tuple[Any, Any]]: ... + @classmethod + def 加载配置(cls, path: str = "缠论配置.json") -> 缠论配置: ... + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 缠论配置: ... + @classmethod + def from_json(cls, json_str: str) -> 缠论配置: ... + @classmethod + def 不推送(cls) -> 缠论配置: ... + @classmethod + def 按序号重组字典(cls, 默认配置: Any, 原始字典: Dict[str, Any]) -> Dict[str, Any]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... diff --git a/chanlun-py/chanlun/__init__.py b/chanlun-py/chanlun/__init__.py index b943aac..4eb1244 100644 --- a/chanlun-py/chanlun/__init__.py +++ b/chanlun-py/chanlun/__init__.py @@ -11,7 +11,10 @@ __all__ = [ "基础买卖点", "平滑异同移动平均线", "指标", - "特征分型", + "指标容器", + "指标计算器", + "均线工具", + "测试_读取数据", "相对强弱指数", "相对方向", "立体分析器", @@ -27,8 +30,31 @@ __all__ = [ "转化为时间戳", "转化为时间戳_数字", "随机指标", + "布林带", + "get_分型模式", + "set_分型模式", + "get_log_level", + "set_log_level", + "get_rs_log_level", + "set_rs_log_level", "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 +from .chan import 测试_读取数据, 转化为时间戳, 转化为时间戳_数字, set_log_level, get_log_level + + +def set_rs_log_level(level: str): + """设置 Rust 侧日志级别 (trace / debug / info / warn / error / off) + + 仅控制 Rust tracing 日志,不影响 Python loguru 日志。 + Python 侧日志通过 set_log_level() 独立控制。 + """ + _rs_set_log_level(level) + + +def get_rs_log_level() -> str: + """获取 Rust 侧日志级别""" + return _rs_get_log_level() diff --git a/chanlun-py/chanlun/__init__.pyi b/chanlun-py/chanlun/__init__.pyi index 2ab0178..15fd7a2 100644 --- a/chanlun-py/chanlun/__init__.pyi +++ b/chanlun-py/chanlun/__init__.pyi @@ -1,438 +1,91 @@ -# Generated from chan.py — type stubs for chanlun Rust bindings -# Auto-generated, do not edit manually +# Type stubs for chanlun Rust bindings (_chanlun.so) +# Auto-generated — do not edit manually +from typing import Any, ClassVar, Optional, List, Dict, Tuple, Union from datetime import datetime -from functools import lru_cache -from typing import Any, Callable, ClassVar, Dict, Iterator, List, Optional, Self, Sequence, Tuple, Union -__all__ = [ - "K线", - "K线合成器", - "中枢", - "买卖点", - "买卖点类型", - "分型", - "分型结构", - "基础买卖点", - "平滑异同移动平均线", - "指标", - "特征分型", - "相对强弱指数", - "相对方向", - "立体分析器", - "笔", - "线段", - "线段特征", - "缠论K线", - "缠论配置", - "缺口", - "背驰分析", - "虚线", - "观察者", - "转化为时间戳", - "转化为时间戳_数字", - "随机指标", -] +# ========== Module-level functions ========== -class 买卖点类型(str, Enum): - """ - 买卖点类型 — 缠论的三类买卖点及扩展类型。 +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: ... - :ivar 是买点: 是否为买入类型 - :ivar 是卖点: 是否为卖出类型 - """ - def __str__(self) -> str: - """返回买卖点类型名称""" - ... - def __repr__(self) -> str: - """返回买卖点类型名称""" - ... - def 是买点(self) -> bool: - """ - 判断是否为买入类型(名称中含"买"字) +# ========== Enum-like types ========== - :return: 是否为买入类型 - """ - ... - def 是卖点(self) -> bool: - """ - 判断是否为卖出类型(名称中含"卖"字) - - :return: 是否为卖出类型 - """ - ... - -class 基础买卖点: - """ - 基础买卖点 — 描述偏离买入/卖出位置的程度。 - - :ivar 备注: 描述文本 - :ivar 偏移: 当前K线相对于买卖点K线的偏移量 - :ivar 失效偏移: 失效K线相对于买卖点K线的偏移量(-1表示未失效) - :ivar 有效性: 是否已失效(存在失效K线) - :ivar 破位值: 中枢破位价格 - :ivar 与MACD柱子匹配: MACD柱子是否匹配买卖点方向 - :ivar 与MACD柱子分型匹配: MACD柱子分型是否匹配 - """ +class 买卖点类型: + 一买: ClassVar[买卖点类型] + 一卖: ClassVar[买卖点类型] + 二买: ClassVar[买卖点类型] + 二卖: ClassVar[买卖点类型] + 三买: ClassVar[买卖点类型] + 三卖: ClassVar[买卖点类型] + T1买: ClassVar[买卖点类型] + T1卖: ClassVar[买卖点类型] + T1P买: ClassVar[买卖点类型] + T1P卖: ClassVar[买卖点类型] + T2买: ClassVar[买卖点类型] + T2卖: ClassVar[买卖点类型] + T2S买: ClassVar[买卖点类型] + T2S卖: ClassVar[买卖点类型] + T3A买: ClassVar[买卖点类型] + T3A卖: ClassVar[买卖点类型] + T3B买: ClassVar[买卖点类型] + T3B卖: ClassVar[买卖点类型] @property - def 类型(self) -> 买卖点类型: ... - @类型.setter - def 类型(self, value: 买卖点类型) -> None: ... + def 是买点(self) -> bool: ... @property - def 买卖点分型(self) -> "分型": ... - @买卖点分型.setter - def 买卖点分型(self, value: "分型") -> None: ... - @property - def 备注(self) -> str: ... - @备注.setter - def 备注(self, value: str) -> None: ... - @property - def 中枢破位值(self) -> float: ... - @中枢破位值.setter - def 中枢破位值(self, value: float) -> None: ... - def __init__(self, 类型: 买卖点类型, 当前K线: "K线", 买卖点分型: "分型", 备注: str, 中枢破位值: float) -> Any: - """ - :param 类型: 买卖点类型 - :param 当前K线: 触发买卖点的K线 - :param 买卖点分型: 买卖点对应的分型 - :param 备注: 描述文本(如"笔_1买") - :param 中枢破位值: 中枢破位价格 - """ - ... + def 是卖点(self) -> bool: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - def 当前K线(self) -> Any: - """当前K线""" - ... - def 破位值(self) -> float: - """ - 破位值 + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... - :return: float - """ - ... - def 偏移(self) -> int: - """ - 偏移 - - :return: int - """ - ... - def 失效偏移(self) -> int: - """ - 失效偏移 - - :return: int - """ - ... - def 有效性(self) -> bool: - """ - 有效性 - - :return: bool - """ - ... - def 与MACD柱子匹配(self) -> bool: - """ - 与MACD柱子匹配 - - :return: bool - """ - ... - def 与MACD柱子分型匹配(self) -> bool: - """ - 与MACD柱子分型匹配 - - :return: bool - """ - ... - -class 买卖点(基础买卖点): - """ - 买卖点 — 一二三类买卖点构造器。 - - 类方法: - 一卖点 / 一买点 / 二卖点 / 二买点 / 三卖点 / 三买点 — 创建对应买卖点实例 - 生成买卖点(特征, 序号, 级别, 分型, 当前缠K) — 根据特征路由到对应构造函数 - """ +class 相对方向: + 向上: ClassVar[相对方向] + 向下: ClassVar[相对方向] + 向上缺口: ClassVar[相对方向] + 向下缺口: ClassVar[相对方向] + 衔接向上: ClassVar[相对方向] + 衔接向下: ClassVar[相对方向] + 顺: ClassVar[相对方向] + 逆: ClassVar[相对方向] + 同: ClassVar[相对方向] + @property + def 是否向上(self) -> bool: ... + @property + def 是否向下(self) -> bool: ... + @property + def 是否包含(self) -> bool: ... + @property + def 是否缺口(self) -> bool: ... + @property + def 是否衔接(self) -> bool: ... + def 翻转(self) -> 相对方向: ... @classmethod - def 一卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 一买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 二卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 二买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 三卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 三买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": - """ - :param 买卖点分型: 买卖点对应的分型 - :param 当前K线: 当前K线 - :param 标识: 标识(未使用,仅保持接口一致) - :param 备注: 描述文本 - :param 中枢破位值: 中枢破位价格 - :return: '买卖点' - """ - ... - @classmethod - def 生成买卖点(cls, 特征: str, 序号: str, 级别: str, 买卖点分型: "分型", 当前缠K: "缠论K线") -> Any: - """ - :param 特征: 特征字符串 - :param 序号: 序号(如"一"、"二"、"三") - :param 级别: 级别字符串 - :param 买卖点分型: 买卖点对应的分型 - :param 当前缠K: 当前缠论K线 - """ - ... - -class 缠论配置: - """ - 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 - - 字段分组: - [基础] 标识 - [缠K] 缠K合并替换 - [笔] 笔内元素数量, 笔弱化, 笔次成笔, 笔内相同终点取舍 等 - [线段] 线段_特征序列忽视老阴老阳, 线段_缺口后紧急修正, 线段内部中枢图显 等 - [分析开关] 分析笔, 分析线段, 分析扩展线段, 分析笔中枢, 分析线段中枢 - [指标] 计算指标, 指标计算方式, MACD/RSI/KDJ 参数 - [推送/显示] 图表展示, 推送K线/笔/线段/中枢, 图表展示_笔 等细分开关 - [买卖点] 买卖点偏移, 买卖点激进识别, 买卖点_背离率, 买卖点_计算方式 等 - [背驰] 线段内部背驰_MACD, 线段内部背驰_斜率 等 - [其他] 手动终止, 加载文件路径 - - 方法: - to_dict() / to_json() / 保存配置(path?) / 加载配置(path?) (classmethod) - from_dict(data) (classmethod) / from_json(json_str) (classmethod) - 不推送() (classmethod) / 对比(other) -> dict - """ - @model_validator(mode="before") - def 兼容旧版本配置(self, values: Dict[str, Any]) -> Dict[str, Any]: - """ - 自动兼容: - 1. 旧版本少字段 → 使用默认值 - 2. 新版本多字段 → 自动忽略多余字段 - 3. 字段改名/删除 → 不报错 - - :param values: 原始配置字典 - :return: 兼容后的字典 - """ - ... - @field_validator("*", mode="wrap") - def bool_parse_fallback_default(self, value: Any, handler: Any, info: Any) -> Any: - """ - :param value: 待验证的值 - :param handler: 默认验证器 - :param info: 字段信息 - :return: 验证后的值 - """ - ... - def to_dict(self) -> dict: - """对象 → 字典""" - ... - def to_json(self) -> str: - """对象 → JSON字符串""" - ... - def 保存配置(self, path: Any = "缠论配置.json") -> Any: - """ - 将配置保存为JSON文件 - :param path: 保存路径,默认"缠论配置.json" - """ - ... - @staticmethod - def 加载配置(path: Any = "缠论配置.json") -> "缠论配置": - """ - 从JSON文件加载配置 - :param path: 配置文件路径 - :return: 缠论配置实例 - """ - ... - @classmethod - def from_dict(cls, data: dict) -> "缠论配置": - """ - :param data: 字典数据 - :return: 缠论配置实例 - """ - ... - @classmethod - def from_json(cls, json_str: str) -> "缠论配置": - """ - :param json_str: JSON字符串 - :return: 缠论配置实例 - """ - ... - @classmethod - def 不推送(cls) -> Any: - """ - 创建不推送任何图表的静默配置(用于纯计算场景) - - :return: 新缠论配置实例 - """ - ... - @classmethod - def 按序号重组字典(cls, 默认配置: Any, 原始字典: dict) -> dict: - """ - 将形如 "1_open", "1_close", "2_open", "name" 的字典重组为嵌套结构 - { - "1_open": 10, - "1_close": 11, - "2_open": 20, - "name": "BTC", # 无法拆分 - "time": 123456 # 无法拆分 - } - 转化为 - { - 1: {"open": 10, "close": 11}, - 2: {"open": 20}, - "无法拆分": { - "name": "BTC", - "time": 123456 - } - } - - :param 默认配置: 默认配置实例 - :param 原始字典: 待重组的原始字典 - :return: 重组后的字典 - """ - ... - def 对比(self, other: "缠论配置") -> dict: - """ - 比较当前配置与另一个配置的差异 - - :param other: 另一个配置实例 - :return: { - "字段名": { - "旧值": 当前配置的值, - "新值": 另一个配置的值 - }, - ... - } - 仅当值不同时才包含该字段 - """ - ... - -class 相对方向(Enum): - """ - 相对方向 — 描述两个K线/分型之间相对位置关系的枚举。 - - 类属性: 向上, 向下, 向上缺口, 向下缺口, 衔接向上, 衔接向下, 顺, 逆, 同 - - :ivar 是否向上: 判断是否为向上方向 - :ivar 是否向下: 判断是否为向下方向 - :ivar 是否包含: 判断是否为包含关系 - :ivar 是否缺口: 判断是否有缺口 - :ivar 是否衔接: 判断是否为首尾衔接 - """ + def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - def 翻转(self) -> "相对方向": - """返回方向的对立面:向上↔向下, 向上缺口↔向下缺口, 顺↔逆, 衔接向上↔衔接向下, 同不变""" - ... - def 是否向上(self) -> bool: - """判断是否为向上方向(向上/向上缺口/衔接向上)""" - ... - def 是否向下(self) -> bool: - """判断是否为向下方向(向下/向下缺口/衔接向下)""" - ... - def 是否包含(self) -> bool: - """判断是否为包含关系(顺/逆/同)""" - ... - def 是否缺口(self) -> bool: - """判断是否有缺口(向下缺口/向上缺口)""" - ... - def 是否衔接(self) -> bool: - """判断是否为首尾衔接(衔接向下/衔接向上)""" - ... + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... + +class 分型结构: + 上: ClassVar[分型结构] + 下: ClassVar[分型结构] + 顶: ClassVar[分型结构] + 底: ClassVar[分型结构] + 散: ClassVar[分型结构] @classmethod - def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> "相对方向": - """ - 分析两个价格区间(前、后)的相对位置关系。 - - :param 前高: 前方价格区间的最高价 - :param 前低: 前方价格区间的最低价 - :param 后高: 后方价格区间的最高价 - :param 后低: 后方价格区间的最低价 - :return: 相对方向枚举值(向上/向下/向上缺口/向下缺口/衔接向上/衔接向下/顺/逆/同) - :raises RuntimeError: 无法识别的方向 - """ - ... - -class 分型结构(Enum): - """ - 分型结构 — 描述三根K线构成的顶底分型形态。 - - 类属性: 上, 下, 顶, 底, 散 - """ + def 分析(cls, 左: Any, 中: Any, 右: Any, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional[分型结构]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - @classmethod - def 分析(cls, 左: Any, 中: Any, 右: Any, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional["分型结构"]: - """ - 分析左中右三个元素构成的分型形态。 - - :param 左: 左侧元素(必须有 高/低 属性) - :param 中: 中间元素 - :param 右: 右侧元素 - :param 可以逆序包含: True 时允许逆序包含关系(如 右>左>中) - :param 忽视顺序包含: True 时跳过顺序包含检查(如 左>中>右) - :return: 分型结构(顶/底/上/下/散),无法判定返回 None - """ - ... + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... class 缺口: - """ - 缺口 — 描述价格区间之间的缺口(未重叠部分)。 - - :ivar 高: 缺口上沿 - :ivar 低: 缺口下沿 - """ + def __init__(self, 高: float, 低: float) -> None: ... @property def 高(self) -> float: ... @高.setter @@ -441,1876 +94,740 @@ class 缺口: def 低(self) -> float: ... @低.setter def 低(self, value: float) -> None: ... - def __init__(self, 高: float, 低: float) -> None: - """ - :param 高: 缺口上沿 - :param 低: 缺口下沿 - """ - ... + @classmethod + def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional[缺口]: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... - @classmethod - def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional["缺口"]: - """ - 以原区间中心为基准,向两侧各取总长度的 `比例` 作为新区间。 - 如果新区间超出原边界则裁剪到边界;若完全越界则返回 None。 + def __hash__(self) -> int: ... + def __reduce__(self) -> tuple: ... - :param 起点: 区间起始值 - :param 终点: 区间结束值 - :param 比例: 向两侧扩展的长度占总长度的比例(0~1 之间) - :return: Optional[缺口] - """ - ... - -class 指标: - """指标 — 静态工具类,提供K线取值的辅助方法。""" - @classmethod - def K线取值(cls, k线: "K线", 指标计算方式: Any) -> Any: - """ - 根据计算方式从K线中取值 - - :param k线: K线对象 - :param 指标计算方式: "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值" - :return: 对应价格 - """ - ... +# ========== Indicators ========== class 平滑异同移动平均线: - """ - 平滑异同移动平均线(MACD)— 基于EMA快慢线差值的趋势指标。 - - 计算字段(输入): - 时间戳: datetime / 收盘价: float - 快线周期: int (默认12) / 慢线周期: int (默认26) / 信号周期: int (默认9) - - 输出字段: - EMA快线: float — 短期EMA值 - EMA慢线: float — 长期EMA值 - DIF: float — 快线减慢线 - DEA: float — DIF的信号线EMA - MACD柱子: float — DIF减DEA - """ - @classmethod - def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> "平滑异同移动平均线": - """ - 首次计算MACD指标(没有历史数据时使用) - - :param 初始收盘价: 第一个数据点的收盘价 - :param 初始时间: 第一个数据点的时间戳 - :param 快线周期: 快线EMA周期 - :param 慢线周期: 慢线EMA周期 - :param 信号周期: 信号线EMA周期 - :return: MACD指标对象 - """ - ... - @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> "平滑异同移动平均线": - """ - :param k线: 原始K线 - :param 计算方式: 指标计算方式(开/高/低/收/均值等) - :param 快线周期: 快线EMA周期 - :param 慢线周期: 慢线EMA周期 - :param 信号周期: 信号线EMA周期 - :return: MACD指标对象 - """ - ... - @classmethod - def 增量计算(cls, 前一个MACD: "平滑异同移动平均线", 当前收盘价: float, 当前时间: datetime) -> "平滑异同移动平均线": - """ - 基于前一个MACD指标增量计算当前MACD指标 - 适用于实时交易系统或流式数据处理 - - :param 前一个MACD: 前一个周期的MACD指标对象 - :param 当前收盘价: 当前K线的收盘价 - :param 当前时间: 当前K线的时间戳 - :return: 当前MACD指标对象 - :raises RuntimeError: 前一个MACD中快线EMA或慢线EMA为None时抛出 - """ - ... - @classmethod - def 增量计算_K线(cls, 前一个MACD: "平滑异同移动平均线", 当前K线: "K线", 计算方式: "str") -> "平滑异同移动平均线": - """ - :param 前一个MACD: 前一个MACD指标对象 - :param 当前K线: 当前K线 - :param 计算方式: 指标计算方式 - :return: 当前MACD指标对象 - """ - ... - -class 相对强弱指数: - """ - 相对强弱指数 (RSI) 指标 - 使用 Wilder 平滑(RMA)进行增量计算,提供完整的中间平滑值, - 并支持对RSI值计算SMA(简单移动平均) - """ - @classmethod - def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> "相对强弱指数": - """ - 首次计算RSI(没有足够历史数据时使用) - 此时无法计算真实RSI,设为 None,但记录初始收盘价作为起点 - - :param 初始收盘价: 第一个数据点的收盘价 - :param 初始时间: 第一个数据点的时间戳 - :param 周期: RSI周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :param RSI_SMA周期: RSI的SMA周期(可选) - :return: RSI指标对象 - """ - ... - @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> "相对强弱指数": - """ - :param k线: 原始K线 - :param 计算方式: 指标计算方式 - :param 周期: RSI周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :param RSI_SMA周期: RSI的SMA周期 - :return: RSI指标对象 - """ - ... - @classmethod - def 增量计算(cls, 前一个RSI: "相对强弱指数", 当前收盘价: float, 当前时间: datetime) -> "相对强弱指数": - """ - 基于前一个RSI指标增量计算当前RSI - 支持可选的RSI_SMA(简单移动平均) - - :param 前一个RSI: 前一个RSI指标对象 - :param 当前收盘价: 当前收盘价 - :param 当前时间: 当前时间戳 - :return: 当前RSI指标对象 - """ - ... - @classmethod - def 增量计算_K线(cls, 前一个RSI: "相对强弱指数", 当前K线: "K线", 计算方式: "str") -> "相对强弱指数": - """ - :param 前一个RSI: 前一个RSI指标对象 - :param 当前K线: 当前K线 - :param 计算方式: 指标计算方式 - :return: 当前RSI指标对象 - """ - ... - -class 随机指标: - """ - KDJ 随机指标 (Stochastic Oscillator) - 使用标准参数:N=9, M1=3, M2=3 - 支持增量计算,需提供当前K线的最高价、最低价、收盘价 - """ - @classmethod - def 首次计算(cls, 初始最高价: float, 初始最低价: float, 初始收盘价: float, 初始时间: datetime, N: int = 9, M1: int = 3, M2: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> "随机指标": - """ - 首次计算KDJ(无历史数据时) - 此时无法计算RSV和K/D/J,仅记录初始三价,初始化队列 - - :param 初始最高价: 第一个数据点的最高价 - :param 初始最低价: 第一个数据点的最低价 - :param 初始收盘价: 第一个数据点的收盘价 - :param 初始时间: 第一个数据点的时间戳 - :param N: RSV周期 - :param M1: K值平滑周期 - :param M2: D值平滑周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :return: KDJ指标对象 - """ - ... - @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, RSV周期: int = 9, K值平滑周期: int = 3, D值平滑周期: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> "随机指标": - """ - :param k线: 原始K线 - :param 计算方式: 指标计算方式(未使用,仅为接口一致) - :param RSV周期: RSV周期 - :param K值平滑周期: K值平滑周期 - :param D值平滑周期: D值平滑周期 - :param 超买阈值: 超买阈值 - :param 超卖阈值: 超卖阈值 - :return: KDJ指标对象 - """ - ... - @classmethod - def 增量计算(cls, 前一个KDJ: "随机指标", 当前最高价: float, 当前最低价: float, 当前收盘价: float, 当前时间: datetime) -> "随机指标": - """ - 基于前一个KDJ对象和当前三价,增量计算当前KDJ值 - - :param 前一个KDJ: 前一个KDJ指标对象 - :param 当前最高价: 当前最高价 - :param 当前最低价: 当前最低价 - :param 当前收盘价: 当前收盘价 - :param 当前时间: 当前时间戳 - :return: 当前KDJ指标对象 - """ - ... - @classmethod - def 增量计算_K线(cls, 前一个KDJ: "随机指标", 当前K线: "K线", 计算方式: "str") -> "随机指标": - """ - :param 前一个KDJ: 前一个KDJ指标对象 - :param 当前K线: 当前K线 - :param 计算方式: 指标计算方式(未使用,仅为接口一致) - :return: 当前KDJ指标对象 - """ - ... - -class 背驰分析: - """ - 背驰分析 — 静态方法容器,提供背驰/背离检测算法。 - - 方法: - MACD背驰(进入段, 离开段, K线序列, 方式?) — MACD柱状线面积背驰 - 斜率背驰(进入段, 离开段, 序列, 配置?) — 线段斜率背驰 - 测度背驰(进入段, 离开段, 序列, 配置?) — 线段测度背驰 - 全量背驰(进入段, 离开段, ...) — 综合所有背驰检测方式 - 配置背驰(进入段, 离开段, ...) — 根据配置选择检测方式 - """ - @staticmethod - def MACD背驰(进入段: "虚线", 离开段: "虚线", K线序列: List["K线"], 方式: str = "总") -> bool: - """ - MACD柱状线面积背驰 - - :param 进入段: 进入中枢的虚线 - :param 离开段: 离开中枢的虚线 - :param K线序列: 完整K线序列 - :param 方式: "总" 或 "阳"/"阴" - :return: 背驰为True - """ - ... - @staticmethod - def 斜率背驰(进入段: "虚线", 离开段: "虚线") -> bool: - """ - 价格斜率背驰 - - :param 进入段: 进入中枢的虚线 - :param 离开段: 离开中枢的虚线 - :return: 背驰为True - """ - ... - @staticmethod - def 测度背驰(进入段: "虚线", 离开段: "虚线") -> bool: - """ - 价格测度背驰(欧氏距离) - - :param 进入段: 进入中枢的虚线 - :param 离开段: 离开中枢的虚线 - :return: 背驰为True - """ - ... - @staticmethod - def 全量背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: - """ - 判断是否满足全部三种背驰条件(MACD + 测度 + 斜率) - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :return: 三者全满足返回True - """ - ... - @staticmethod - def 任意背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: - """ - 判断是否满足任一背驰条件 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :return: 任一满足返回True - """ - ... - @staticmethod - def 配置背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"], 配置: 缠论配置) -> bool: - """ - 根据配置选择对应的背驰检测组合 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :param 配置: 缠论配置(控制MACD/测度/斜率开关) - :return: 背驰结果 - """ - ... - @staticmethod - def 任选背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: - """ - 三个背驰条件中至少两个满足即视为背驰 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :return: 至少两个满足返回True - """ - ... - @staticmethod - def 背驰模式(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"], 配置: 缠论配置, 模式: str) -> bool: - """ - 根据模式字符串选择背驰检测策略 - - :param 进入段: 进入中枢的线段 - :param 离开段: 离开中枢的线段 - :param 普K序列: 完整K线序列 - :param 配置: 缠论配置 - :param 模式: "全量"/"任意"/"配置"/"相对" - :return: 背驰结果 - """ - ... - -class K线(object): - """ - 原始 K 线 — OHLCV 数据,可内嵌 MACD/RSI/KDJ 指标。 - - :ivar 标识: 标识符 - :ivar 序号: 序号 - :ivar 周期: K线周期(秒) - :ivar 时间戳: 时间戳 - :ivar 高: 最高价 - :ivar 低: 最低价 - :ivar 开盘价: 开盘价 - :ivar 收盘价: 收盘价 - :ivar 成交量: 成交量 - :ivar macd: MACD指标对象 - :ivar rsi: RSI指标对象 - :ivar kdj: KDJ指标对象 - :ivar 方向: 涨跌方向(只读) - """ @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... - @property - def 周期(self) -> int: ... - @周期.setter - def 周期(self, value: int) -> None: ... - @property - def 时间戳(self) -> datetime: ... - @时间戳.setter - def 时间戳(self, value: datetime) -> None: ... - @property - def 开盘价(self) -> float: ... - @开盘价.setter - def 开盘价(self, value: float) -> None: ... - @property - def 最高价(self) -> float: ... - @最高价.setter - def 最高价(self, value: float) -> None: ... - @property - def 最低价(self) -> float: ... - @最低价.setter - def 最低价(self, value: float) -> None: ... + def 时间戳(self) -> str: ... @property def 收盘价(self) -> float: ... - @收盘价.setter - def 收盘价(self, value: float) -> None: ... @property - def 成交量(self) -> float: ... - @成交量.setter - def 成交量(self, value: float) -> None: ... + def 快线周期(self) -> int: ... @property - def macd(self) -> 平滑异同移动平均线: ... - @macd.setter - def macd(self, value: 平滑异同移动平均线) -> None: ... + def 慢线周期(self) -> int: ... @property - def rsi(self) -> 相对强弱指数: ... - @rsi.setter - def rsi(self, value: 相对强弱指数) -> None: ... + def 信号周期(self) -> int: ... @property - def kdj(self) -> 随机指标: ... - @kdj.setter - def kdj(self, value: 随机指标) -> None: ... - def __init__(self, 标识: str, 序号: int, 周期: int, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, macd: 平滑异同移动平均线 = None, rsi: 相对强弱指数 = None, kdj: 随机指标 = None) -> Any: - """ - :param 标识: K线标识符 - :param 序号: K线序号 - :param 周期: K线周期(秒) - :param 时间戳: K线时间 - :param 开盘价: 开盘价 - :param 最高价: 最高价 - :param 最低价: 最低价 - :param 收盘价: 收盘价 - :param 成交量: 成交量 - :param macd: MACD指标对象(可选) - :param rsi: RSI指标对象(可选) - :param kdj: KDJ指标对象(可选) - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 方向(self) -> 相对方向: - """:return: 相对方向.向上(开盘<收盘)或 相对方向.向下(开盘>收盘)""" - ... - def __bytes__(self) -> bytes: ... - @classmethod - def 创建普K(cls, 标识: str, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: int, 周期: int) -> "K线": - """ - 快捷构造普通K线 - - :param 标识: K线标识符 - :param 时间戳: K线时间 - :param 开盘价: 开盘价 - :param 最高价: 最高价 - :param 最低价: 最低价 - :param 收盘价: 收盘价 - :param 成交量: 成交量 - :param 序号: K线序号 - :param 周期: K线周期(秒) - :return: K线实例 - """ - ... - @classmethod - def 保存到DAT文件(cls, 路径: str, K线序列: List["K线"]) -> Any: - """ - 将K线序列保存为二进制DAT文件 - - :param 路径: 保存路径 - :param K线序列: K线列表 - """ - ... - @classmethod - def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> "K线": - """ - 从大端字节序二进制数据反序列化K线(兼容.dat/.nb文件格式) - - :param 字节组: 二进制数据(48字节) - :param 周期: 周期(秒) - :param 标识: K线标识 - :return: K线实例 - """ - ... - @classmethod - def 获取MACD(cls, K线序列: List["K线"], 始: "K线", 终: "K线") -> Dict[str, float]: - """ - 计算指定K线区间的MACD柱面积 - - :param K线序列: 完整K线序列 - :param 始: 起始K线 - :param 终: 终点K线 - :return: {"阳": 正值面积和, "阴": 负值面积和, "合": 净面积, "总": 绝对面积和} - """ - ... - @staticmethod - def 截取(序列: List["K线"], 始: "K线", 终: "K线") -> List["K线"]: - """ - 按起止K线截取K线子序列 - - :param 序列: 完整K线序列 - :param 始: 起始K线 - :param 终: 终点K线 - :return: K线子列表 - """ - ... - -class 缠论K线(object): - """ - 缠论K线 — 经包含处理后的标准化K线,有方向和分型结构标记。 - - :ivar 序号: 序号 - :ivar 时间戳: 时间戳 - :ivar 高: 最高价 - :ivar 低: 最低价 - :ivar 方向: 运行方向 - :ivar 分型: 分型结构(顶/底/上/下等) - :ivar 周期: 周期(秒) - :ivar 标识: 标识符 - :ivar 分型特征值: 分型特征值(顶取高,底取低) - :ivar 原始起始序号: 合并前的原始K线起始序号 - :ivar 原始结束序号: 合并前的原始K线结束序号 - :ivar 标的K线: 对应的原始K线 - :ivar 买卖点信息: 买卖点信息(预留) - """ + def DIF(self) -> Optional[float]: ... @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... + def DEA(self) -> Optional[float]: ... @property - def 时间戳(self) -> datetime: ... - @时间戳.setter - def 时间戳(self, value: datetime) -> None: ... + def MACD柱(self) -> float: ... @property - def 最高价(self) -> float: ... - @最高价.setter - def 最高价(self, value: float) -> None: ... + def 快线EMA(self) -> Optional[float]: ... @property - def 最低价(self) -> float: ... - @最低价.setter - def 最低价(self, value: float) -> None: ... + def 慢线EMA(self) -> Optional[float]: ... @property - def 最终方向(self) -> 相对方向: ... - @最终方向.setter - def 最终方向(self, value: 相对方向) -> None: ... - @property - def 普K(self) -> "K线": ... - @普K.setter - def 普K(self, value: "K线") -> None: ... - @property - def 原始起始序号(self) -> int: ... - @原始起始序号.setter - def 原始起始序号(self, value: int) -> None: ... - @property - def 原始结束序号(self) -> int: ... - @原始结束序号.setter - def 原始结束序号(self, value: int) -> None: ... - @property - def 分型(self) -> Optional[分型结构]: ... - @分型.setter - def 分型(self, value: Optional[分型结构]) -> None: ... - def __init__(self, 序号: int, 时间戳: datetime, 最高价: float, 最低价: float, 最终方向: 相对方向, 普K: "K线", 原始起始序号: int, 原始结束序号: int, 分型: Optional[分型结构] = None) -> Any: - """ - :param 序号: 缠论K线序号 - :param 时间戳: 时间戳 - :param 最高价: 最高价 - :param 最低价: 最低价 - :param 最终方向: 方向 - :param 普K: 对应的原始K线 - :param 原始起始序号: 合并起始序号 - :param 原始结束序号: 合并结束序号 - :param 分型: 分型结构(可选) - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 镜像(self) -> Any: - """ - 创建当前缠K的浅拷贝副本 - - :return: 新的缠论K线实例 - """ - ... - def 与MACD柱子匹配(self) -> bool: - """:return: 底分型时MACD柱<0,顶分型时MACD柱>0""" - ... - def 与RSI匹配(self) -> bool: - """:return: 底分型时RSI < RSI_SMA,顶分型时RSI > RSI_SMA""" - ... - def 与KDJ匹配(self) -> bool: - """:return: 底分型时KD""" - ... + def DEA_EMA(self) -> Optional[float]: ... @classmethod - def 时间戳对齐(cls, 基线: List["缠论K线"], k线: "缠论K线") -> Any: - """ - 在基线序列中找到与k线时间戳对齐的时间戳 - - :param 基线: 参照缠K序列 - :param k线: 需要对齐的缠K - :return: 对齐后的时间戳 - """ - ... + def 首次计算(cls, 初始收盘价: float, 初始时间: int, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ... @classmethod - def 创建缠K(cls, 时间戳: datetime, 高: float, 低: float, 方向: 相对方向, 结构: 分型结构, 原始序号: int, 普k: "K线", 之前: Optional["缠论K线"] = None) -> "缠论K线": - """ - 创建新的缠论K线 - - :param 时间戳: K线时间 - :param 高: 最高价 - :param 低: 最低价 - :param 方向: K线运行方向 - :param 结构: 分型结构 - :param 原始序号: 原始K线序号 - :param 普k: 标的原始K线 - :param 之前: 前一缠K(可选,自动校验包含关系) - :return: 缠论K线实例 - :raises ValueError: 包含关系错误 - """ - ... + def 首次计算_K线(cls, k线: Any, 计算方式: str, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ... @classmethod - def 兼并(cls, 之前缠K: Optional["缠论K线"], 当前缠K: "缠论K线", 当前普K: "K线", 配置: 缠论配置) -> Tuple[Optional["缠论K线"], Optional[str]]: - """ - K线包含处理(合并) - - :param 之前缠K: 前一缠K - :param 当前缠K: 当前待处理的缠K - :param 当前普K: 当前原始K线 - :param 配置: 缠论配置 - :return: (新缠K或None, 操作模式:"添加"/"替换"/None) - """ - ... + def 增量计算(cls, 前一个MACD: 平滑异同移动平均线, 当前收盘价: float, 当前时间: int) -> 平滑异同移动平均线: ... @classmethod - def 分析(cls, 当前K线: "K线", 缠K序列: List["缠论K线"], 普K序列: List["K线"], 配置: 缠论配置) -> tuple[str, Optional["分型"]]: - """ - 分析K线,执行指标计算+包含处理+分型判定 - - :param 当前K线: 新到的原始K线 - :param 缠K序列: 现有缠K序列(会被原地修改) - :param 普K序列: 现有普K序列(会被原地修改) - :param 配置: 缠论配置 - :return: (操作状态, 新形成的分型或None) - """ - ... - @staticmethod - def 截取(序列: List["缠论K线"], 始: "缠论K线", 终: "缠论K线") -> List["缠论K线"]: - """ - :param 序列: 缠K序列 - :param 始: 起始缠K - :param 终: 终点缠K - :return: 缠K子列表 - """ - ... - -class 分型(object): - """ - 分型 — 由左中右三根缠论K线构成的顶/底分型结构。 - - :ivar 左: 左侧缠K(可能为None) - :ivar 中: 中间缠K(分型顶点/底点) - :ivar 右: 右侧缠K(可能为None) - :ivar 结构: 分型结构(顶/底/上/下/包含) - :ivar 时间戳: 分型时间戳 - :ivar 分型特征值: 顶分型取最高价,底分型取最低价 - :ivar 关系组: 左中、中右、左右三对相对方向 - :ivar 强度: 分型强度(强/中/弱/未知) - :ivar 与MACD柱子分型匹配: 是否与MACD柱子分型匹配 - """ - @property - def 左(self) -> Optional[缠论K线]: ... - @左.setter - def 左(self, value: Optional[缠论K线]) -> None: ... - @property - def 中(self) -> 缠论K线: ... - @中.setter - def 中(self, value: 缠论K线) -> None: ... - @property - def 右(self) -> Optional[缠论K线]: ... - @右.setter - def 右(self, value: Optional[缠论K线]) -> None: ... - def __init__(self, 左: Optional[缠论K线], 中: 缠论K线, 右: Optional[缠论K线]) -> Any: - """ - :param 左: 左侧缠K - :param 中: 中间缠K - :param 右: 右侧缠K - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 关系组(self) -> Optional[Tuple[相对方向, 相对方向, 相对方向]]: - """ - 左、中、右三对相对方向关系 - - :return: (左中关系, 中右关系, 左右关系) 或 None - """ - ... - def 强度(self) -> Any: - """ - 分型强度(强/中/弱/未知) - - :return: 强度字符串 - """ - ... - def 与MACD柱子分型匹配(self) -> bool: - """:return: 底分型时左右MACD柱 > 中MACD柱,顶分型时左右MACD柱 < 中MACD柱""" - ... - @classmethod - def 判断分型(cls, 左: "分型", 右: "分型", 模式: str = "中") -> bool: - """ - 判断两个分型是否相同(identity比较) - - :param 左: 左分型 - :param 右: 右分型 - :param 模式: 比较模式(默认"中") - :return: 是否为同一对象 - """ - ... - @staticmethod - def 从缠K序列中获取分型(K线序列: List[缠论K线], 中: 缠论K线) -> "分型": - """ - 从缠K序列中提取以指定缠K为中元素的分型 - - :param K线序列: 缠K列表 - :param 中: 中间K线 - :return: 分型实例(右可能为None) - """ - ... - @staticmethod - def 向序列中添加(分型序列: List["分型"], 当前分型: "分型") -> Any: - """ - 向分型序列尾部添加,自动校验顶底交替 - - :param 分型序列: 现有分型列表 - :param 当前分型: 待添加分型 - :raises ValueError: 首元素非顶底、连续同向分型时抛出 - """ - ... - -class 虚线(object): - """ - 虚线 — 笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。 - - :ivar 标识: 类型标识("笔"/"线段"/"扩展线段"等) - :ivar 序号: 序号 - :ivar 级别: 级别 - :ivar 文: 起点分型 - :ivar 武: 终点分型 - :ivar 有效性: 是否有效 - :ivar 基础序列: 内部虚线序列(笔序列/线段序列) - :ivar 特征序列: 特征序列列表(线段专用) - :ivar 实_中枢序列: 实中枢列表 - :ivar 虚_中枢序列: 虚中枢列表 - :ivar 合_中枢序列: 合中枢列表 - :ivar 确认K线: 确认K线 - :ivar 模式: 买卖点模式 - :ivar 前一缺口: 前一缺口 - :ivar 前一结束位置: 前一笔/线段的终点 - :ivar 短路修正: 是否短路修正 - :ivar 方向: 运行方向(只读) - :ivar 高: 最高价(只读) - :ivar 低: 最低价(只读) - :ivar 笔序列: 所有笔(只读) - :ivar 图表标题: 图表显示标题(只读) - """ - @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... - @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 文(self) -> 分型: ... - @文.setter - def 文(self, value: 分型) -> None: ... - @property - def 武(self) -> 分型: ... - @武.setter - def 武(self, value: 分型) -> None: ... - @property - def 级别(self) -> int: ... - @级别.setter - def 级别(self, value: int) -> None: ... - @property - def 有效性(self) -> bool: ... - @有效性.setter - def 有效性(self, value: bool) -> None: ... - def __init__(self, 序号: int, 标识: str, 文: 分型, 武: 分型, 级别: int, 有效性: bool = True) -> Any: - """ - :param 序号: 序号 - :param 标识: 类型标识 - :param 文: 起点分型 - :param 武: 终点分型 - :param 级别: 级别 - :param 有效性: 是否有效 - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 笔序列(self) -> Any: - """笔序列""" - ... - def 图表标题(self) -> str: - """:return: 图表显示标题""" - ... - def 方向(self) -> "相对方向": - """ - :return: 运行方向 - :raises RuntimeError: 无法识别的方向 - """ - ... - def 高(self) -> float: - """虚线区间的最高价(向上线段为终点分型最高价,向下线段为起点分型最高价)""" - ... - def 低(self) -> float: - """虚线区间的最低价(向下线段为终点分型最低价,向上线段为起点分型最低价)""" - ... - def 之前是(self, 之前: "虚线") -> bool: - """ - :param 之前: 前一条虚线 - :return: 当前虚线的起点是否为前一条虚线的终点 - """ - ... - def 之后是(self, 之后: "虚线") -> bool: - """ - :param 之后: 后一条虚线 - :return: 当前虚线的终点是否为后一条虚线的起点 - """ - ... - def 获取普K序列(self, 观察员: "观察者") -> List[K线]: - """ - :param 观察员: 观察者实例 - :return: 区间内的原始K线列表 - """ - ... - def 获取缠K序列(self, 观察员: "观察者") -> List[缠论K线]: - """ - :param 观察员: 观察者实例 - :return: 区间内的缠K列表 - """ - ... - def 获取数据文本(self) -> str: - """获取用于保存的数据文本""" - ... - @classmethod - def 创建笔(cls, 文: 分型, 武: 分型, 有效性: bool = True) -> "虚线": - """ - :param 文: 起点分型 - :param 武: 终点分型 - :param 有效性: 是否有效 - :return: 虚线实例(标识="笔") - """ - ... - @classmethod - def 创建线段(cls, 虚线序列: List["虚线"]) -> "虚线": - """ - :param 虚线序列: 构成线段的虚线列表(笔) - :return: 虚线实例(标识="线段"或"线段<...>") - """ - ... - @classmethod - def 缠K买卖点模式(cls, 模式: str, 缠K: "缠论K线", 配置: 缠论配置) -> Any: - """ - :param 模式: "全量"/"任意"/"配置"/"相对" - :param 缠K: 待检测的缠论K线 - :param 配置: 缠论配置 - :return: 是否满足买卖点条件 - """ - ... - @classmethod - def 买卖点配置匹配(cls, 缠K: "缠论K线", 配置: 缠论配置) -> bool: - """根据配置中的指标开关检测缠K匹配情况(MACD/KDJ/RSI组合)""" - ... - @classmethod - def 买卖点任意匹配(cls, 缠K: "缠论K线") -> bool: - """ - :param 缠K: 缠论K线 - :return: MACD/KDJ/RSI 任一匹配 - """ - ... - @classmethod - def 买卖点全量匹配(cls, csl: Any, 缠K: "缠论K线") -> bool: - """ - :param 缠K: 缠论K线 - :return: MACD/KDJ/RSI 全部匹配 - """ - ... - @classmethod - def 买卖点相对匹配(cls, 缠K: "缠论K线") -> bool: - """ - :param 缠K: 缠论K线 - :return: 三个指标中至少两个匹配 - """ - ... - @classmethod - def 计算MACD柱子均值(cls, 普K序列: List[K线], 实线: "虚线") -> float: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线(笔/线段) - :return: 区间内MACD柱绝对值的平均值 - """ - ... - @classmethod - def 计算MACD柱子均值_阴(cls, 普K序列: List[K线], 实线: "虚线") -> float: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 区间内负数MACD柱绝对值的平均值,无负数时返回False - """ - ... - @classmethod - def 计算MACD柱子均值_阳(cls, 普K序列: List[K线], 实线: "虚线") -> float: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 区间内正数MACD柱绝对值的平均值,无正数时返回False - """ - ... - @classmethod - def 武之全量MACD均值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点K线的MACD柱是否小于区间均值(全量背驰信号) - """ - ... - @classmethod - def 武之MACD均值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 根据方向选择阳/阴均值判断 - """ - ... - @classmethod - def 武之MACD均值_阴(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点MACD柱绝对值小于阴均值时True(背驰) - """ - ... - @classmethod - def 武之MACD均值_阳(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点MACD柱绝对值小于阳均值时True(背驰) - """ - ... - @classmethod - def 武之MACD极值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: - """ - :param 普K序列: 完整K线序列 - :param 实线: 虚线 - :return: 终点MACD柱是否为区间内极值 - """ - ... - @classmethod - def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence["K线"], 方向: 相对方向) -> Any: - """ - 计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) - - :param 普K序列: K线序列 - :param 方向: 运行方向 - :return: [柱子背驰, DIF背驰, DEA背驰] - """ - ... - @classmethod - @lru_cache(maxsize=128) - def 买卖意义(cls, 实线: "虚线", 观察员: "观察者") -> Tuple[bool, str]: - """ - 静止是相对的,而运动是绝对的 - - :param 实线: 虚线(笔/线段) - :param 观察员: 观察者实例 - :return: (是否具有买卖意义, 原因字符串) - """ - ... - @classmethod - def 计算MACD柱子分段(cls, k线序列: List["K线"]) -> Tuple[List[List["K线"]], ...]: - """ - :param k线序列: K线序列 - :return: 按正负分段的MACD柱列表 - """ - ... - @classmethod - def 密集区域按间隔(cls, 交叉标记: List[int], 最大间隔: int = 5, 最少交叉数: int = 3) -> List[Tuple[int, int, int]]: - """ - 交叉标记: 长度为len(macd_list)的列表,0=无交叉, 1=金叉, -1=死叉 - 最大间隔: 相邻交叉索引差 ≤ 此值则归入同一密集区 - 最少交叉数: 一个密集区内至少包含的交叉次数 - - :return: [(起始交叉索引, 结束交叉索引, 区内交叉次数), ...] - """ - ... - @classmethod - def 统计MACD行为(cls, 普K序列: List[K线], 最大间隔: int = 8, 最少交叉数: int = 3) -> dict: - """ - :param 普K序列: K线序列 - :param 最大间隔: 最大间隔 8 - :param 最少交叉数: 最少交叉数 3 - :return: 统计字典 - """ - ... - @classmethod - def 获取_武(cls, 实线: 虚线) -> 分型: - """ - 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武) - :param 实线: 虚线 - :return: 分型 - """ - ... - -class 笔(object): - """ - 笔 — 纯静态方法容器,提供笔划分算法的所有函数。 - - 主要方法: - 获取缠K数量 — 获取笔内缠K数量 - 停顿 — 检测笔的停顿点 - 自检 — 校验笔的有效性 - 获取所有停顿位置 — 获取笔内所有停顿位置 - 获取起始分型 — 获取笔的起始分型 - 生成笔序列 — 从分型序列生成笔序列 - 是否背驰过 — 判断笔是否经过背驰 - """ - @staticmethod - def 获取缠K数量(缠K序列: List[缠论K线], 笔序列: List[虚线], 配置: 缠论配置) -> int: - """ - 获取笔内有效缠K数量(考虑笔弱化等配置) - - :param 缠K序列: 候选笔的基础缠K序列 - :param 笔序列: 已有笔序列 - :param 配置: 缠论配置 - :return: 有效缠K数量 - """ - ... - @staticmethod - def 次高(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 次高 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 次高缠K - """ - ... - @staticmethod - def 次低(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 次低 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 次低缠K - """ - ... - @staticmethod - def 实际高点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 实际高点 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 实际高点缠K - """ - ... - @staticmethod - def 实际低点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """ - 实际低点 - - :param 缠K序列: 缠K序列 - :param 笔内相同终点取舍: 终点取舍方式 - :return: 实际低点缠K - """ - ... - @staticmethod - def 相对关系(筆: 虚线, 配置: 缠论配置) -> bool: - """ - 相对关系 - - :param 筆: 笔虚线 - :param 配置: 缠论配置 - :return: 方向是否匹配 - """ - ... - @classmethod - def 分析(cls, 当前分型: Optional[分型], 分型序列: List[分型], 笔序列: List[虚线], 缠K序列: List[缠论K线], 普K序列: List[K线], 递归层次: int, 配置: 缠论配置) -> Any: - """ - 笔划分核心递归算法 - - :param 当前分型: 新形成的分型(可能为None) - :param 分型序列: 现有分型列表(原地修改) - :param 笔序列: 现有笔列表(原地修改) - :param 缠K序列: 缠K序列 - :param 普K序列: 普K序列 - :param 递归层次: 递归深度计数 - :param 配置: 缠论配置 - :return: 递归层次(int) - """ - ... - @staticmethod - def 以文会友(笔序列: List[虚线], 文: 分型) -> Optional[虚线]: - """ - 以文会友 - - :param 笔序列: 笔列表 - :param 文: 起点分型 - :return: 匹配的笔或None - """ - ... - @staticmethod - def 以武会友(笔序列: List[虚线], 武: 分型) -> Optional[虚线]: - """ - 以武会友 - - :param 笔序列: 笔列表 - :param 武: 终点分型 - :return: 匹配的笔或None - """ - ... - @staticmethod - def 根据缠K找笔(笔序列: List[虚线], 缠K: "缠论K线", 偏移: int = 1) -> Any: - """ - 根据缠K找笔 - - :param 笔序列: 笔列表 - :param 缠K: 缠论K线 - :param 偏移: 序号偏移量 - :return: 包含该缠K的笔或None - """ - ... - @classmethod - def 自检(cls, 筆: 虚线, 观察员: "观察者") -> bool: - """ - 校验笔的有效性:高/低点是否为实际极值 - - :param 筆: 待校验的笔 - :param 观察员: 观察者 - :return: 有效为True - """ - ... - @classmethod - def 获取所有停顿位置(cls, 筆: 虚线, 观察员: "观察者") -> List[虚线]: - """ - 获取笔内所有可能的停顿位置(用于背驰检测) - - :param 筆: 笔 - :param 观察员: 观察者 - :return: 停顿位置的笔列表 - """ - ... - @classmethod - def 是否背驰过(cls, 当前筆: 虚线, 观察员: "观察者") -> List[缠论K线]: - """ - 判断笔内是否发生过MACD趋向背驰 - - :param 当前筆: 笔 - :param 观察员: 观察者 - :return: 发生背驰的停顿位置列表 - """ - ... - -class 线段特征(list): - """ - 线段特征 — list 子类,持有一组同向虚线(笔),是线段划分的中间结构。 - - 继承 list,元素为虚线。特征序列用于线段划分算法中的包含处理。 - - :ivar 序号: 序号 - :ivar 标识: 标识 - :ivar 线段方向: 线段运行方向(特征序列方向为其翻转) - :ivar 文: 第一个元素的起点分型(根据方向选取极值) - :ivar 武: 最后一个元素的终点分型(根据方向选取极值) - :ivar 高: 最高价 - :ivar 低: 最低价 - :ivar 方向: 特征序列方向(线段方向的翻转) - :ivar 图表标题: 图表标题 - """ - @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 基础序列(self) -> List[虚线]: ... - @基础序列.setter - def 基础序列(self, value: List[虚线]) -> None: ... - @property - def 线段方向(self) -> 相对方向: ... - @线段方向.setter - def 线段方向(self, value: 相对方向) -> None: ... - def __init__(self, 标识: str, 基础序列: List[虚线], 线段方向: 相对方向) -> Any: - """ - :param 标识: 标识符 - :param 基础序列: 基础虚线列表 - :param 线段方向: 线段运行方向 - """ - ... - def 图表标题(self) -> str: - """:return: 图表标题""" - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 文(self) -> 分型: - """起点分型(向上线段取高高中的最大者,向下线段取低低中的最小者)""" - ... - def 武(self) -> 分型: - """终点分型(向上线段取高高中的最大者,向下线段取低低中的最小者)""" - ... - def 高(self) -> float: - """:return: 文和武中分型特征值的较大者""" - ... - def 低(self) -> float: - """:return: 文和武中分型特征值的较小者""" - ... - def 方向(self) -> 相对方向: - """:return: 特征序列方向(线段方向的翻转)""" - ... - def 添加(self, 待添加虚线: Union[虚线]) -> Any: - """ - :param 待添加虚线: 待添加的虚线 - :raises ValueError: 方向不匹配时抛出 - """ - ... - def 删除(self, 待删除虚线: Union[虚线]) -> Any: - """ - :param 待删除虚线: 待删除的虚线 - :raises ValueError: 方向不匹配时抛出 - """ - ... - @classmethod - def 新建(cls, 虚线序列: List[虚线], 线段方向: 相对方向) -> "线段特征": - """ - :param 虚线序列: 基础虚线列表 - :param 线段方向: 线段方向 - :return: 线段特征实例 - """ - ... - @classmethod - def 静态分析(cls, 虚线序列: List[虚线], 线段方向: 相对方向, 四象: str, 是否忽视: bool = False) -> List["线段特征"]: - """ - 静态分析虚线序列,生成特征序列 - - :param 虚线序列: 笔/虚线列表 - :param 线段方向: 线段运行方向 - :param 四象: "老阳"/"老阴"/"小阳"/"少阴" - :param 是否忽视: True时不严格处理缺口包含 - :return: 特征序列列表 - """ - ... - @classmethod - def 获取分型序列(cls, 特征序列: List) -> List[Any]: - """ - 从特征序列提取特征分型序列 - - :param 特征序列: 线段特征列表 - :return: 特征分型列表 - """ - ... - -class 特征分型: - """ - 特征分型 — 由左右中三个线段特征构成的顶/底分型,用于线段划分算法。 - - :ivar 左: 左侧特征序列 - :ivar 中: 中间特征序列 - :ivar 右: 右侧特征序列 - :ivar 结构: 分型结构(顶/底) - """ - @property - def 左(self) -> 线段特征: ... - @左.setter - def 左(self, value: 线段特征) -> None: ... - @property - def 中(self) -> 线段特征: ... - @中.setter - def 中(self, value: 线段特征) -> None: ... - @property - def 右(self) -> 线段特征: ... - @右.setter - def 右(self, value: 线段特征) -> None: ... - @property - def 结构(self) -> 分型结构: ... - @结构.setter - def 结构(self, value: 分型结构) -> None: ... - def __init__(self, 左: 线段特征, 中: 线段特征, 右: 线段特征, 结构: 分型结构) -> Any: - """ - :param 左: 左侧特征序列 - :param 中: 中间特征序列 - :param 右: 右侧特征序列 - :param 结构: 分型结构 - """ - ... + def 增量计算_K线(cls, 前一个MACD: 平滑异同移动平均线, 当前K线: Any, 计算方式: str) -> 平滑异同移动平均线: ... def __str__(self) -> str: ... def __repr__(self) -> str: ... -class 线段(object): - """ - 线段 — 纯静态方法容器,提供线段划分算法的所有函数。 - - 主要方法: - 检查线段破坏(前一线段, 当前线段) — 检查线段是否被破坏 - 获取线段特征序列(笔序列, 线段方向, 需要被合并方向序列) — 提取特征序列 - 线段有缺口(特征序列, 线段方向, 当前分型序列, 特征分型序列) — 判断线段是否有缺口 - 判断新线段(笔序列) — 判断是否形成新线段 - 静态分析(观察员) — 静态线段分析 - 生成线段序列(笔序列, 观察员) — 从笔序列生成线段序列 - """ - @classmethod - def 添加虚线(cls, 段: 虚线, 筆: 虚线) -> Any: - """ - 向线段中添加一笔 - - :param 段: 线段 - :param 筆: 待添加的笔 - :raises ValueError: 不连续或标识不符时抛出 - """ - ... - @classmethod - def 武斗(cls, 段: 虚线, 武: 分型, 行号: int) -> Any: - """ - 更新线段的终点分型(武) - - :param 段: 线段 - :param 武: 新的终点分型 - :param 行号: 调用行号(用于调试) - """ - ... - @classmethod - def 特征分型终结(cls, 段: 虚线) -> bool: - """ - 是否符合特征序列正常分型终结 - - :param 段: 线段 - :return: 是否终结 - """ - ... - @classmethod - def 特征序列状态(cls, 段: 虚线) -> Tuple[bool, bool, bool]: - """ - :param 段: 线段 - :return: (左是否存在, 中是否存在, 右是否存在) - """ - ... - @classmethod - def 获取缺口(cls, 段: 虚线) -> Optional[缺口]: - """ - 获取线段特征序列第一二元素间的缺口 - - :param 段: 线段 - :return: 缺口或None - """ - ... - @classmethod - def 四象(cls, 段: 虚线) -> str: - """ - 判断线段的四象属性 - - :param 段: 线段 - :return: "老阳"(向下线段后继缺口向上线段) / "老阴"(向上线段后继缺口向下线段) / "小阳"(普通向上) / "少阴"(普通向下) - """ - ... - @classmethod - def 设置特征序列(cls, 段: 虚线, 序列: Any, 行号: Any) -> Any: - """ - 设置特征序列 - - :param 段: 线段 - :param 序列: 特征序列三元组 (左,中,右) - :param 行号: 调用行号 - """ - ... - @classmethod - def 刷新特征序列(cls, 段: 虚线, 配置: 缠论配置) -> Any: - """ - 刷新特征序列 - - :param 段: 线段 - :param 配置: 缠论配置 - """ - ... - @classmethod - def 分割序列(cls, 段: 虚线, 所属中枢: Optional["中枢"] = None) -> Tuple[List[虚线], List[虚线], List[虚线], Optional[虚线]]: - """ - 将线段基础序列分割为前/后/第三买卖/贯穿伤四部分 - - :param 段: 线段 - :param 所属中枢: 所属中枢(用于第三买卖点检测) - :return: (前序列, 后序列, 第三买卖线, 贯穿伤) - """ - ... - @classmethod - def 刷新(cls, 段: 虚线, 配置: 缠论配置) -> Any: - """ - 刷新线段的特征序列和内部中枢序列 - - :param 段: 线段 - :param 配置: 缠论配置 - """ - ... - @classmethod - def 序列重置(cls, 段: 虚线, 序列: Sequence) -> Any: - """ - 序列重置 - - :param 段: 线段 - :param 序列: 参考序列 - """ - ... - @classmethod - def 查找贯穿伤(cls, 段: 虚线) -> Optional[虚线]: - """ - 查找贯穿伤 - - :param 段: 线段 - :return: 贯穿伤虚线或None - """ - ... - @classmethod - def 获取内部中枢序列(cls, 段: 虚线, 配置: 缠论配置) -> Tuple[List["中枢"], List["中枢"], List["中枢"]]: - """ - 获取内部中枢序列 - - :param 段: 线段 - :param 配置: 缠论配置 - :return: (虚中枢列表, 实中枢列表, 合中枢列表) - """ - ... - @classmethod - def 基础判断(cls, 左: 虚线, 中: 虚线, 右: 虚线, 关系序列: List[相对方向]) -> bool: - """ - 连续三笔且重叠 - - :param 左: 左侧虚线 - :param 中: 中间虚线 - :param 右: 右侧虚线 - :param 关系序列: 允许的方向关系 - :return: 是否满足基础条件 - """ - ... - @classmethod - def 分析(cls, 笔序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置, 层级: int = 0, 关系序列: Any = [相对方向.向上, 相对方向.向下]) -> None: - """ - 线段划分核心递归算法 - - :param 笔序列: 笔列表 - :param 线段序列: 线段列表(原地修改) - :param 配置: 缠论配置 - :param 层级: 递归深度 - :param 关系序列: 允许的方向关系 - """ - ... - @classmethod - def 武终(cls, 段: 虚线, 行号: int) -> Any: - """ - 武终 - - :param 段: 线段 - :param 行号: 调用行号 - """ - ... - @classmethod - def 验证序列(cls, 段: 虚线, 序列: Sequence) -> Any: - """ - 验证序列 - - :param 段: 线段 - :param 序列: 参考序列 - """ - ... - @classmethod - def 扩展分析(cls, 虚线序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置) -> None: - """ - 即同级别分析 - 将笔看成线段 - - :param 虚线序列: 基础虚线列表 - :param 线段序列: 扩展线段列表(原地修改) - :param 配置: 缠论配置 - """ - ... - @classmethod - @lru_cache(maxsize=128) - def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者) -> bool: - """ - 判断线段内部是否发生背驰(基于内部中枢和MACD) - - :param 当前段: 线段 - :param 观察员: 观察者 - :return: bool - """ - ... - @classmethod - def 获取所有停顿位置(cls, 段: 虚线, 观察员: "观察者") -> List[虚线]: - """ - 获取所有停顿位置 - - :param 段: 线段 - :param 观察员: 观察者 - :return: 停顿位置的线段列表 - """ - ... - @classmethod - def 是否背驰过(cls, 当前段: 虚线, 观察员: "观察者") -> List[缠论K线]: - """ - 判断线段内是否发生过背驰(遍历所有停顿位置) - - :param 当前段: 线段 - :param 观察员: 观察者 - :return: 背驰点列表 - """ - ... - -class 中枢(object): - """ - 中枢 — 三段虚线重叠区间构成的价格中枢,支持延伸和扩展。 - - :ivar 序号: 序号 - :ivar 标识: 标识 - :ivar 级别: 级别 - :ivar 基础序列: 构成中枢的基础虚线(至少3条) - :ivar 第三买卖线: 第三类买卖点关联虚线 - :ivar 本级_第三买卖线: 本级第三类买卖点虚线 - :ivar 高: 中枢上沿(虚线低点的最大值) - :ivar 低: 中枢下沿(虚线高点的最小值) - :ivar 高高: 全区间最高价 - :ivar 低低: 全区间最低价 - :ivar 文: 起点分型 - :ivar 武: 终点分型 - :ivar 方向: 中枢方向(首条虚线的方向翻转) - :ivar 离开段: 最后一条虚线 - """ +class 相对强弱指数: @property - def 序号(self) -> int: ... - @序号.setter - def 序号(self, value: int) -> None: ... + def 时间戳(self) -> str: ... @property - def 标识(self) -> str: ... - @标识.setter - def 标识(self, value: str) -> None: ... - @property - def 级别(self) -> int: ... - @级别.setter - def 级别(self, value: int) -> None: ... - @property - def 基础序列(self) -> List[虚线]: ... - @基础序列.setter - def 基础序列(self, value: List[虚线]) -> None: ... - def __init__(self, 序号: int, 标识: str, 级别: int, 基础序列: List[虚线]) -> Any: - """ - :param 序号: 序号 - :param 标识: 标识 - :param 级别: 级别 - :param 基础序列: 基础虚线列表 - """ - ... - def 添加虚线(self, 实线: 虚线) -> Any: - """ - 向中枢添加新虚线(延伸),重置第三买卖线 - - :param 实线: 新虚线 - """ - ... - def __str__(self) -> str: ... - def __repr__(self) -> str: ... - def 图表标题(self) -> str: - """:return: 图表标题""" - ... - def 离开段(self) -> 虚线: - """:return: 最后一条虚线""" - ... - def 方向(self) -> 相对方向: - """:return: 中枢方向(首条虚线的方向翻转)""" - ... - def 高(self) -> float: - """:return: 中枢上沿(前三段中虚线高点的最小值)""" - ... - def 低(self) -> float: - """:return: 中枢下沿(前三段中虚线低点的最大值)""" - ... - def 高高(self) -> float: - """:return: 全区间最高价""" - ... - def 低低(self) -> float: - """:return: 全区间最低价""" - ... - def 文(self) -> 分型: - """:return: 起点分型""" - ... - def 武(self) -> 分型: - """:return: 终点分型""" - ... - def 获取数据文本(self) -> str: - """获取用于保存的数据文本""" - ... - def 完整性(self, 虚实: str) -> Any: - """ - 判断中枢是否完整(是否有第三买卖点或内部中枢离开) - - 详情见 教你炒股票 43:有关背驰的补习课(2007-04-06 15:31:28) - 不完整时 下一个中枢大概率会与当前中枢发生扩展! - - :param 虚实: "实"/"虚"/"合" - :return: 完整为True - """ - ... - def 获取序列(self) -> List[虚线]: - """ - 获取中枢的完整虚线序列(基础序列+第三买卖线) - - :return: 虚线列表 - """ - ... - def 获取扩展中枢(self, 扩展中枢: List, 配置: 缠论配置) -> Any: - """ - 当基础序列>=9时,从中枢中提取扩展线段中枢 - - :param 扩展中枢: 存放扩展中枢的列表 - :param 配置: 缠论配置 - """ - ... - def 校验合法性(self, 序列: Sequence[虚线], 中枢序列: List["中枢"]) -> bool: - """ - 校验当前中枢在给定序列中是否仍然合法 - - :param 序列: 基础虚线序列 - :param 中枢序列: 中枢列表 - :return: 合法为True,不合法会原地裁剪基础序列 - """ - ... - def 设置第三买卖线(self, 线: Union[虚线, None]) -> Any: - """ - 设置第三类买卖点关联虚线 - - :param 线: 第三买卖虚线或None - """ - ... - def 当前状态(self) -> Any: - """ - 获取中枢当前状态:中枢之中/中枢之上/中枢之下 - - 详情见 教你炒股票 49:利润率最大的操作模式(2007-04-26 08:16:56) - 当前中枢最后一段所处的位置关系 - - 一、当下在该中枢之中。 - 因为在中枢里,由于这时候怎么演化都是对的,不操作是最好的操作,等待其演化第二、三类, - 当然,如果你技术好点,可以判断出次级别的第二类买点,这些买点很多情况下都是在中枢中出现的,那当然也是可以参与的。 - 但如果没有这种技术,那就有了再说了。只把握你自己当下技术水平能把握的机会,这才是最重要的。 - 二、当下在该中枢之下。 - 1.当下之前未出现该中枢第三类卖点。 - 2.当下之前已出现该中枢第三类卖点(正出现也包括在这种情况下,按最严格的定义,这最精确的卖点,是瞬间完成的,而具有操作意义的第三类卖点,其实是一个包含该最精确卖点的足够小区间) - 三、当下在该中枢之上。 - 1.当下之前未出现该中枢第三类买点。 - 2.当下之前已出现该中枢第三类买点。 - - :return: "中枢之中" / "中枢之上" / "中枢之下" - """ - ... - @classmethod - def 基础检查(cls, 左: 虚线, 中: 虚线, 右: 虚线) -> bool: - """ - 检查三条虚线是否构成中枢(连续且重叠) - - :param 左: 左侧虚线 - :param 中: 中间虚线 - :param 右: 右侧虚线 - :return: bool - """ - ... - @classmethod - def 创建(cls, 左: 虚线, 中: 虚线, 右: 虚线, 级别: int, 标识: str = "") -> "中枢": - """ - 从三条连续且重叠的虚线创建中枢 - - :param 左: 左侧虚线 - :param 中: 中间虚线 - :param 右: 右侧虚线 - :param 级别: 中枢级别 - :param 标识: 中枢标识前缀 - :return: 中枢实例 - """ - ... - @classmethod - def 从序列中获取中枢(cls, 虚线序列: Sequence[虚线], 起始方向: 相对方向, 标识: str) -> Optional["中枢"]: - """ - 从虚线序列中按起始方向查找第一个中枢 - - :param 虚线序列: 虚线列表 - :param 起始方向: 第一条虚线的方向 - :param 标识: 中枢标识前缀 - :return: 中枢或None - """ - ... - @classmethod - def 向中枢序列尾部添加(cls, 中枢序列: List["中枢"], 待添加中枢: "中枢") -> Any: - """ - :param 中枢序列: 中枢列表 - :param 待添加中枢: 待添加的中枢 - """ - ... - @classmethod - def 从中枢序列尾部弹出(cls, 中枢序列: List["中枢"], 待弹出中枢: "中枢") -> Optional["中枢"]: - """ - :param 中枢序列: 中枢列表 - :param 待弹出中枢: 待弹出的中枢 - :return: 弹出的中枢或None - """ - ... - @classmethod - def 分析(cls, 虚线序列: Sequence[虚线], 中枢序列: List["中枢"], 跳过首部: bool = True, 标识: str = "", 层级: int = 0) -> None: - """ - 中枢识别核心递归算法 - - :param 虚线序列: 基础虚线列表 - :param 中枢序列: 中枢列表(原地修改) - :param 跳过首部: True: 跳过首元素中枢 - :param 标识: 中枢标识前缀 - :param 层级: 递归深度 - """ - ... - -class 观察者: - """ - 观察者 — 单周期缠论分析器,接收K线流式输入并逐层计算所有层级序列。 - - 核心入口为 增加原始K线(普K),每收到一根新K线就增量更新: - 原始K线 → 缠论K线(包含处理+合并)→ 分型 → 笔 → 线段 → 中枢 → 买卖点 - - :ivar 标识: "{符号}:{周期}" - :ivar 当前K线: 最后一根原始K线 - :ivar 当前缠K: 最后一根缠论K线 - :ivar 普通K线序列 / 缠论K线序列 / 分型序列 / 笔序列 / 线段序列 / 中枢序列 等 - """ - @property - def 符号(self) -> str: ... - @符号.setter - def 符号(self, value: str) -> None: ... + def 收盘价(self) -> float: ... @property def 周期(self) -> int: ... - @周期.setter - def 周期(self, value: int) -> None: ... @property - def 配置(self) -> 缠论配置: ... - @配置.setter - def 配置(self, value: 缠论配置) -> None: ... - def __init__(self, 符号: str, 周期: int, 配置: 缠论配置) -> Any: - """ - 初始化观察者 - - :param 符号: 交易对符号 - :param 周期: K线周期(秒) - :param 配置: 缠论配置 - """ - ... - def 观察员(self) -> Any: - """观察员(自引用)""" - ... - def 标识(self) -> str: - ''':return: "{符号}:{周期}"''' - ... - def 当前K线(self) -> Optional["K线"]: - """:return: 最后一根原始K线""" - ... - def 当前缠K(self) -> Optional["缠论K线"]: - """:return: 最后一根缠论K线""" - ... - def 重置基础序列(self) -> Any: - """清空所有分析序列,重置为初始状态""" - ... - @final - def 增加原始K线(self, 普K: K线) -> Any: - """ - 核心入口 — 投喂一根原始K线,增量更新所有层级 - - :param 普K: 新到的原始K线 - - 处理流程: 原始K线→缠论K线(包含处理+合并)→分型→笔→线段→中枢→买卖点 - """ - ... - def 测试_保存数据(self, root: str = None) -> Any: - """ - 拆分各序列数据,单独存文件,文件名为对应变量名 - - :param root: 保存根目录(可选) - """ - ... - def 识别买卖点(self) -> Any: - """识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现)""" - ... - def 静态重新分析(self) -> Any: - """静态重新分析(占位方法)""" - ... - def 加载本地数据(self, 文件路径: str) -> Any: ... + def 超买阈值(self) -> float: ... + @property + def 超卖阈值(self) -> float: ... + @property + def RSI_SMA周期(self) -> Optional[int]: ... + @property + def RSI(self) -> Optional[float]: ... + @property + def 平均上涨(self) -> Optional[float]: ... + @property + def 平均下跌(self) -> Optional[float]: ... + @property + def 上涨幅度(self) -> float: ... + @property + def 下跌幅度(self) -> float: ... + @property + def 平滑系数(self) -> float: ... + @property + def RSI_SMA(self) -> Optional[float]: ... + @property + def RSI历史队列(self) -> List[float]: ... @classmethod - def 读取数据文件(cls, 文件路径: str, 配置: Any = 缠论配置()) -> Self: - """ - :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb - :param 配置: 缠论配置 - :return: 观察者实例 - """ - ... + def 首次计算(cls, 初始收盘价: float, 初始时间: int, 周期: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: ... + @classmethod + def 首次计算_K线(cls, k线: Any, 计算方式: str, 周期: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: ... + @classmethod + def 增量计算(cls, 前一个RSI: 相对强弱指数, 当前收盘价: float, 当前时间: int) -> 相对强弱指数: ... + @classmethod + def 增量计算_K线(cls, 前一个RSI: 相对强弱指数, 当前K线: Any, 计算方式: str) -> 相对强弱指数: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... -class K线合成器: - """ - K线合成器 — 将小周期K线合成为大周期K线,支持多周期级联合成。 +class 随机指标: + @property + def 时间戳(self) -> str: ... + @property + def 最高价(self) -> float: ... + @property + def 最低价(self) -> float: ... + @property + def 收盘价(self) -> float: ... + @property + def N(self) -> int: ... + @property + def M1(self) -> int: ... + @property + def M2(self) -> int: ... + @property + def 超买阈值(self) -> float: ... + @property + def 超卖阈值(self) -> float: ... + @property + def RSV(self) -> Optional[float]: ... + @property + def K(self) -> Optional[float]: ... + @property + def D(self) -> Optional[float]: ... + @property + def J(self) -> Optional[float]: ... + @property + def 历史最高价队列(self) -> List[float]: ... + @property + def 历史最低价队列(self) -> List[float]: ... + @property + def 前一个RSV(self) -> Optional[float]: ... + @property + def 前一个K(self) -> Optional[float]: ... + @property + def 前一个D(self) -> Optional[float]: ... + @classmethod + def 首次计算(cls, 初始最高价: float, 初始最低价: float, 初始收盘价: float, 初始时间: int, N: Optional[int] = None, M1: Optional[int] = None, M2: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None) -> 随机指标: ... + @classmethod + def 首次计算_K线(cls, k线: Any, _计算方式: str, RSV周期: Optional[int] = None, K值平滑周期: Optional[int] = None, D值平滑周期: Optional[int] = None, 超买阈值: Optional[float] = None, 超卖阈值: Optional[float] = None) -> 随机指标: ... + @classmethod + def 增量计算(cls, 前一个KDJ: 随机指标, 当前最高价: float, 当前最低价: float, 当前收盘价: float, 当前时间: int) -> 随机指标: ... + @classmethod + def 增量计算_K线(cls, 前一个KDJ: 随机指标, 当前K线: Any, _计算方式: str) -> 随机指标: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... - :ivar 标识: 合成器标识 - :ivar 周期组: 从小到大排列的周期组 - :ivar 当前K线: 各周期当前K线 - :ivar 合成K线列表: 各周期已合成的K线列表 - """ +class 布林带: + @property + def 时间戳(self) -> int: ... + @property + def 周期(self) -> int: ... + @property + def 标准差倍数(self) -> float: ... + @property + def 上轨(self) -> float: ... + @property + def 中轨(self) -> float: ... + @property + def 下轨(self) -> float: ... + @classmethod + def 首次计算(cls, k线: Any, 计算方式: str, 周期: int = 20, 标准差倍数: float = 2.0) -> 布林带: ... + @classmethod + def 增量计算(cls, 前一个布林带: 布林带, 当前K线: Any, 计算方式: str) -> 布林带: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class 指标容器: + def __init__(self) -> None: ... + def 获取(self, 名称: str) -> Optional[Any]: ... + def 设置(self, 名称: str, 值: Any) -> None: ... + def 包含(self, 名称: str) -> bool: ... + def keys(self) -> List[str]: ... + def __getitem__(self, 名称: str) -> Any: ... + def __getattr__(self, 名称: str) -> Any: ... + def __contains__(self, 名称: str) -> bool: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + +class 指标: + @classmethod + def K线取值(cls, k线: Any, 指标计算方式: str) -> float: ... + +class 均线工具: + @staticmethod + def 增量SMA(普K序列: List[Any], period: int, 计算方式: str) -> float: ... + @staticmethod + def 增量EMA(普K序列: List[Any], period: int, 计算方式: str, 前值: Optional[float] = None) -> float: ... + +class 指标计算器: + @staticmethod + def 计算并挂载(当前K线: Any, 全序列: List[Any], 配置: 缠论配置) -> None: ... + +# ========== K线 ========== + +class K线: + def __init__(self, 标识: str = "bar", 序号: int = 0, 周期: int = 60, 时间戳: int = 0, 高: float = 0.0, 低: float = 0.0, 开盘价: float = 0.0, 收盘价: float = 0.0, 成交量: float = 0.0) -> None: ... + @property + def 标识(self) -> str: ... + @property + def 序号(self) -> int: ... + @property + def 周期(self) -> int: ... + @property + def 时间戳(self) -> int: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def 开盘价(self) -> float: ... + @property + def 收盘价(self) -> float: ... + @property + def 成交量(self) -> float: ... + @property + def 方向(self) -> 相对方向: ... + @property + def macd(self) -> Optional[平滑异同移动平均线]: ... + @property + def rsi(self) -> Optional[相对强弱指数]: ... + @property + def kdj(self) -> Optional[随机指标]: ... + @property + def 指标(self) -> 指标容器: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + @classmethod + def 创建普K(cls, 标识: str, 时间戳: int, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: Optional[int] = None, 周期: Optional[int] = None) -> K线: ... + @classmethod + def 保存到DAT文件(cls, 路径: str, K线序列: List[K线]) -> None: ... + @classmethod + def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> Optional[K线]: ... + @classmethod + def 获取MACD(cls, k线序列: List[K线], 始: K线, 终: K线) -> Dict[str, float]: ... + @staticmethod + def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __bytes__(self) -> bytes: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +class 缠论K线: + @property + def 序号(self) -> int: ... + @property + def 时间戳(self) -> int: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 分型(self) -> Optional[分型结构]: ... + @property + def 周期(self) -> int: ... + @property + def 标识(self) -> str: ... + @property + def 分型特征值(self) -> float: ... + @property + def 原始起始序号(self) -> int: ... + @property + def 原始结束序号(self) -> int: ... + @property + def 标的K线(self) -> K线: ... + @property + def 镜像(self) -> 缠论K线: ... + @property + def 与MACD柱子匹配(self) -> bool: ... + @property + def 与RSI匹配(self) -> bool: ... + @property + def 与KDJ匹配(self) -> bool: ... + @property + def 买卖点信息(self) -> Any: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + @classmethod + def 时间戳对齐(cls, 基线: List[缠论K线], k线: 缠论K线) -> int: ... + @classmethod + def 创建缠K(cls, 时间戳: int, 高: float, 低: float, 方向: 相对方向, 结构: Optional[分型结构], 原始序号: int, 普k: K线, 之前: Optional[缠论K线] = None) -> 缠论K线: ... + @classmethod + def 分析(cls, 当前K线: K线, 缠K序列: List[缠论K线], 普K序列: List[K线], 配置: 缠论配置) -> Tuple[str, Optional[分型]]: ... + @staticmethod + def 截取(序列: List[缠论K线], 始: 缠论K线, 终: 缠论K线) -> List[缠论K线]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +# ========== Structure ========== + +class 分型: + def __init__(self, 左: Optional[缠论K线], 中: 缠论K线, 右: Optional[缠论K线]) -> None: ... + @property + def 左(self) -> Optional[缠论K线]: ... + @property + def 中(self) -> 缠论K线: ... + @property + def 右(self) -> Optional[缠论K线]: ... + @property + def 结构(self) -> 分型结构: ... + @property + def 时间戳(self) -> int: ... + @property + def 分型特征值(self) -> float: ... + @property + def _结构(self) -> 分型结构: ... + @property + def _时间戳(self) -> int: ... + @property + def _分型特征值(self) -> float: ... + @property + def 关系组(self) -> Optional[Tuple[相对方向, 相对方向, 相对方向]]: ... + @property + def 强度(self) -> str: ... + @property + def 与MACD柱子分型匹配(self) -> bool: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + @classmethod + def 判断分型(cls, 左: 分型, 右: 分型, 模式: str = "中") -> bool: ... + @staticmethod + def 从缠K序列中获取分型(K线序列: List[缠论K线], 中: 缠论K线) -> Optional[分型]: ... + @staticmethod + def 向序列中添加(分型序列: Any, 当前分型: 分型) -> None: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +class 虚线: + def __init__(self, 序号: int, 标识: str, 文: 分型, 武: 分型, 级别: int, 有效性: bool = True) -> None: ... + @property + def 标识(self) -> str: ... + @property + def 序号(self) -> int: ... + @property + def 级别(self) -> int: ... + @property + def 文(self) -> 分型: ... + @property + def 武(self) -> 分型: ... + @property + def 有效性(self) -> bool: ... + @property + def 模式(self) -> str: ... + @property + def 特征序列_显示(self) -> bool: ... + @特征序列_显示.setter + def 特征序列_显示(self, value: bool) -> None: ... + @property + def 特征序列(self) -> List[Optional[线段特征]]: ... + @property + def 短路修正(self) -> bool: ... + @property + def 确认K线(self) -> Optional[缠论K线]: ... + @property + def 前一缺口(self) -> Optional[缺口]: ... + @property + def 前一结束位置(self) -> Optional[虚线]: ... + @property + def 基础序列(self) -> List[虚线]: ... + @property + def 实_中枢序列(self) -> List[中枢]: ... + @property + def 虚_中枢序列(self) -> List[中枢]: ... + @property + def 合_中枢序列(self) -> List[中枢]: ... + @property + def 笔序列(self) -> List[虚线]: ... + @property + def 图表标题(self) -> str: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + def 之前是(self, 之前: 虚线) -> bool: ... + def 之后是(self, 之后: 虚线) -> bool: ... + def 获取普K序列(self, 观察员: 观察者) -> List[K线]: ... + def 获取缠K序列(self, 观察员: 观察者) -> List[缠论K线]: ... + def 获取数据文本(self) -> str: ... + @classmethod + def 获取_武(cls, 实线: 虚线) -> 分型: ... + @classmethod + def 创建笔(cls, 文: 分型, 武: 分型, 有效性: bool = True) -> 虚线: ... + @classmethod + def 创建线段(cls, 虚线序列: List[虚线]) -> 虚线: ... + @classmethod + def 缠K买卖点模式(cls, 模式: str, 缠K: 缠论K线, 配置: 缠论配置) -> bool: ... + @classmethod + def 买卖点配置匹配(cls, 缠K: 缠论K线, 配置: 缠论配置) -> bool: ... + @classmethod + def 买卖点任意匹配(cls, 缠K: 缠论K线) -> bool: ... + @classmethod + def 买卖点全量匹配(cls, 缠K: 缠论K线) -> bool: ... + @classmethod + def 买卖点相对匹配(cls, 缠K: 缠论K线) -> bool: ... + @classmethod + def 计算MACD柱子均值(cls, 普K序列: List[K线], 实线: 虚线) -> float: ... + @classmethod + def 武之全量MACD均值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 武之MACD均值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 武之MACD极值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 计算MACD柱子均值_阴(cls, 普K序列: List[K线], 实线: 虚线) -> Optional[float]: ... + @classmethod + def 计算MACD柱子均值_阳(cls, 普K序列: List[K线], 实线: 虚线) -> Optional[float]: ... + @classmethod + def 武之MACD均值_阴(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 武之MACD均值_阳(cls, 普K序列: List[K线], 实线: 虚线) -> bool: ... + @classmethod + def 计算K线序列MACD趋向背驰(cls, 普K序列: List[K线], 方向: 相对方向) -> Tuple[bool, bool, bool]: ... + @classmethod + def 计算MACD柱子分段(cls, k线序列: List[K线]) -> List[List[float]]: ... + @classmethod + def 密集区域按间隔(cls, 交叉标记: List[int], 最大间隔: int = 5, 最少交叉数: int = 3) -> List[Tuple[int, int, int]]: ... + @classmethod + def 统计MACD行为(cls, 普K序列: List[K线], 最大间隔: int = 8, 最少交叉数: int = 3) -> Dict[str, Any]: ... + @classmethod + def 买卖意义(cls, 实线: 虚线, 观察员: 观察者) -> Tuple[bool, str]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... + +class 线段特征: + @property + def 序号(self) -> int: ... @property def 标识(self) -> str: ... @标识.setter def 标识(self, value: str) -> None: ... @property - def 周期组(self) -> List[int]: ... - @周期组.setter - def 周期组(self, value: List[int]) -> None: ... + def 线段方向(self) -> 相对方向: ... @property - def 事件回调(self) -> Optional[Callable]: ... - @事件回调.setter - def 事件回调(self, value: Optional[Callable]) -> None: ... - def __init__(self, 标识: str, 周期组: List[int], 事件回调: Optional[Callable] = None) -> Any: - """ - 初始化K线合成器 + def 基础序列(self) -> List[虚线]: ... + @property + def 图表标题(self) -> str: ... + @property + def 文(self) -> 分型: ... + @property + def 武(self) -> 分型: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @classmethod + def 静态分析(cls, 虚线序列: List[虚线], 线段方向: 相对方向, 四象: str, 是否忽视: bool = False) -> List[线段特征]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... - :param 标识: 合成器标识 - :param 周期组: 从小到大排列的周期列表 - :param 事件回调: 可选,K线合成完成时的回调函数(信号类型, 标识, 周期, 完成K线) - """ - ... - def 设置事件回调(self, 回调函数: Callable) -> Any: - """ - 设置事件回调函数 +# ========== Algorithm ========== - :param 回调函数: 回调函数 - """ - ... - def 投喂(self, 时间戳: datetime, 开: float, 高: float, 低: float, 收: float, 量: float) -> Any: - """ - 投喂原始tick数据 +class 背驰分析: + @classmethod + def MACD背驰(cls, 进入段: 虚线, 离开段: 虚线, K线序列: List[K线], 方式: str = "总") -> bool: ... + @classmethod + def 斜率背驰(cls, 进入段: 虚线, 离开段: 虚线) -> bool: ... + @classmethod + def 测度背驰(cls, 进入段: 虚线, 离开段: 虚线) -> bool: ... + @classmethod + def 全量背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... + @classmethod + def 任意背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... + @classmethod + def 配置背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置) -> bool: ... + @classmethod + def 任选背驰(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: ... + @classmethod + def 背驰模式(cls, 进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: ... - :param 时间戳: 时间戳 - :param 开: 开盘价 - :param 高: 最高价 - :param 低: 最低价 - :param 收: 收盘价 - :param 量: 成交量 - """ - ... - def 投喂K线(self, 普K: K线) -> Any: - """ - 统一入口 — 投喂最小周期K线,自动合成大周期并分发给各周期观察者 +class 笔: + @classmethod + def 以文会友(cls, 笔序列: List[虚线], 文: 分型) -> Optional[虚线]: ... + @classmethod + def 以武会友(cls, 笔序列: List[虚线], 武: 分型) -> Optional[虚线]: ... + @classmethod + def 根据缠K找笔(cls, 笔序列: List[虚线], 缠K: 缠论K线, 偏移: int = 1) -> Optional[虚线]: ... + @classmethod + def 分析(cls, 当前分型: Optional[分型], 分型序列: List[分型], 笔序列: List[虚线], 缠K序列: List[缠论K线], 普K序列: List[K线], 递归层次: int, 配置: 缠论配置) -> int: ... + @classmethod + def 获取所有停顿位置(cls, 筆: 虚线, 观察员: 观察者) -> List[虚线]: ... + @classmethod + def 是否背驰过(cls, 当前筆: 虚线, 观察员: 观察者) -> List[缠论K线]: ... - :param 普K: 最小周期原始K线 - """ - ... - def 获取当前K线(self, 周期: int) -> Optional[K线]: - """ - 获取指定周期当前正在合成的K线 +class 线段: + @classmethod + def 四象(cls, 段: 虚线) -> str: ... + @classmethod + def 获取缺口(cls, 段: 虚线) -> Optional[缺口]: ... + @classmethod + def 特征分型终结(cls, 段: 虚线) -> bool: ... + @classmethod + def 特征序列状态(cls, 段: 虚线) -> Tuple[bool, bool, bool]: ... + @classmethod + def 查找贯穿伤(cls, 段: 虚线) -> Optional[虚线]: ... + @classmethod + def 分割序列(cls, 段: 虚线, 所属中枢: Optional[中枢] = None) -> Tuple[List[虚线], List[虚线], List[虚线], Optional[虚线]]: ... + @classmethod + def 获取内部中枢序列(cls, 段: 虚线, 配置: 缠论配置) -> Tuple[List[中枢], List[中枢], List[中枢]]: ... + @classmethod + def 分析(cls, 笔序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置, 层级: int, 关系序列: Optional[List[相对方向]] = None) -> None: ... + @classmethod + def 扩展分析(cls, 虚线序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置) -> None: ... + @classmethod + def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者) -> bool: ... + @classmethod + def 获取所有停顿位置(cls, 段: 虚线, 观察员: 观察者) -> List[虚线]: ... + @classmethod + def 是否背驰过(cls, 当前段: 虚线, 观察员: 观察者) -> List[缠论K线]: ... - :param 周期: 目标周期 - :return: 当前K线或None - """ - ... +class 中枢: + def __init__(self, 序号: int, 标识: str, 级别: int, 基础序列: List[虚线]) -> None: ... + @property + def 序号(self) -> int: ... + @property + def 标识(self) -> str: ... + @property + def 级别(self) -> int: ... + @property + def 基础序列(self) -> List[虚线]: ... + @property + def 第三买卖线(self) -> Optional[虚线]: ... + @property + def 本级_第三买卖线(self) -> Optional[虚线]: ... + @property + def 图表标题(self) -> str: ... + @property + def 离开段(self) -> 虚线: ... + @property + def 方向(self) -> 相对方向: ... + @property + def 高(self) -> float: ... + @property + def 低(self) -> float: ... + @property + def 高高(self) -> float: ... + @property + def 低低(self) -> float: ... + @property + def 文(self) -> 分型: ... + @property + def 武(self) -> 分型: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + def 设置第三买卖线(self, 线: 虚线) -> None: ... + def 获取序列(self) -> List[虚线]: ... + def 获取数据文本(self) -> str: ... + def 完整性(self, 虚实: str = "合") -> bool: ... + def 获取扩展中枢(self, 扩展中枢: List[中枢], 配置: 缠论配置) -> None: ... + def 当前状态(self) -> str: ... + @classmethod + def 基础检查(cls, 左: 虚线, 中: 虚线, 右: 虚线) -> bool: ... + @classmethod + def 创建(cls, 左: 虚线, 中: 虚线, 右: 虚线, 级别: int, 标识: str = "") -> 中枢: ... + @classmethod + def 分析(cls, 虚线序列: List[虚线], 中枢序列: List[中枢], 跳过首部: bool = True, 标识: str = "", 层级: int = 0) -> None: ... + @classmethod + def 从序列中获取中枢(cls, 虚线序列: List[虚线], 起始方向: 相对方向, 标识: str) -> Optional[中枢]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... + def __eq__(self, other: Any) -> bool: ... + def __hash__(self) -> int: ... -class 立体分析器: - """ - 立体分析器 — 多周期缠论分析器,内部包含K线合成器 + 每周期一个观察者。 +# ========== Business ========== - 通过最小周期合成大周期K线,各周期观察者独立分析,实现多周期联立。 +class 基础买卖点: + def __init__(self, 类型: 买卖点类型, 当前K线: K线, 买卖点分型: 分型, 备注: str, 中枢破位值: float) -> None: ... + @property + def 备注(self) -> str: ... + @备注.setter + def 备注(self, value: str) -> None: ... + @property + def 类型(self) -> 买卖点类型: ... + @property + def 买卖点分型(self) -> 分型: ... + @property + def 买卖点K线(self) -> 缠论K线: ... + @property + def 当前K线(self) -> K线: ... + @property + def 失效K线(self) -> Optional[K线]: ... + @property + def 终结K线(self) -> Optional[K线]: ... + @property + def 破位值(self) -> float: ... + @property + def 结构(self) -> Optional[分型结构]: ... + @property + def 偏移(self) -> int: ... + @property + def 失效偏移(self) -> int: ... + @property + def 有效性(self) -> bool: ... + @property + def 与MACD柱子匹配(self) -> bool: ... + @property + def 与MACD柱子分型匹配(self) -> bool: ... + @property + def __dict__(self) -> Dict[str, Any]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... - :ivar 周期组: 所有分析周期 - :ivar 观察员: 各周期对应的观察者 - :ivar K线合成器: 内部K线合成器 - """ +class 买卖点(基础买卖点): + @classmethod + def 一卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 一买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 二卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 二买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 三卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 三买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1P卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T1P买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2S卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T2S买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3A卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3A买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3B卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def T3B买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: ... + @classmethod + def 生成买卖点(cls, 特征: str, 序号: str, 级别: str, 买卖点分型: 分型, 当前缠K: 缠论K线) -> 买卖点: ... + +class 观察者: + def __init__(self, 符号: str, 周期: int, 配置: 缠论配置 = ...) -> None: ... + @property + def 观察员(self) -> 观察者: ... + @property + def 标识(self) -> str: ... + @property + def 当前K线(self) -> Optional[K线]: ... + @property + def 当前缠K(self) -> Optional[缠论K线]: ... @property def 符号(self) -> str: ... - @符号.setter - def 符号(self, value: str) -> None: ... @property - def 周期组(self) -> List[int]: ... - @周期组.setter - def 周期组(self, value: List[int]) -> None: ... + def 周期(self) -> int: ... @property def 配置(self) -> 缠论配置: ... - @配置.setter - def 配置(self, value: 缠论配置) -> None: ... @property - def 配置组(self) -> Dict[int, 缠论配置]: ... - @配置组.setter - def 配置组(self, value: Dict[int, 缠论配置]) -> None: ... - def __init__(self, 符号: str, 周期组: List[int], 配置: 缠论配置 = 缠论配置(), 配置组: Dict[int, 缠论配置] = dict()) -> Any: - """ - 初始化多周期立体分析器 + def 普通K线序列(self) -> List[K线]: ... + @property + def 基础缠K序列(self) -> List[缠论K线]: ... + @基础缠K序列.setter + def 基础缠K序列(self, value: List[缠论K线]) -> None: ... + @property + def 缠论K线序列(self) -> List[缠论K线]: ... + @property + def 分型序列(self) -> List[分型]: ... + @property + def 笔序列(self) -> List[虚线]: ... + @property + def 笔_中枢序列(self) -> List[中枢]: ... + @property + def 线段序列(self) -> List[虚线]: ... + @property + def 中枢序列(self) -> List[中枢]: ... + @property + def 扩展线段序列(self) -> List[虚线]: ... + @property + def 扩展中枢序列(self) -> List[中枢]: ... + @property + def 扩展线段序列_线段(self) -> List[虚线]: ... + @property + def 扩展中枢序列_线段(self) -> List[中枢]: ... + @property + def 线段_线段序列(self) -> List[虚线]: ... + @property + def 线段_中枢序列(self) -> List[中枢]: ... + @property + def 扩展线段序列_扩展线段(self) -> List[虚线]: ... + @property + def 扩展中枢序列_扩展线段(self) -> List[中枢]: ... + def 重置基础序列(self) -> None: ... + def 增加原始K线(self, 普K: K线) -> None: ... + def 投喂原始数据(self, 时间戳: int, 开: float, 高: float, 低: float, 收: float, 量: float) -> None: ... + def 加载本地数据(self, 文件路径: str) -> None: ... + def 静态重新分析(self) -> None: ... + def 测试_保存数据(self, root: Optional[str] = None) -> str: ... + @classmethod + def 读取数据文件(cls, 观察员: 观察者, 文件路径: str, 配置: Optional[缠论配置] = None) -> 观察者: ... - :param 符号: 交易对符号 - :param 周期组: 所有分析周期列表(第一个为最小输入周期) - :param 配置: 默认配置 - :param 配置组: 各周期独立配置 {周期: 缠论配置} - """ - ... - def 投喂K线(self, 普K: K线) -> Any: - """ - 统一入口 — 投喂最小周期K线,自动合成大周期并分发给各周期观察者 +class K线合成器: + def __init__(self, 标识: str, 周期组: List[int]) -> None: ... + @property + def 标识(self) -> str: ... + @property + def 周期组(self) -> List[int]: ... + def 投喂K线(self, 普K: K线) -> List[Tuple[int, K线]]: ... + def 投喂(self, 时间戳: int, 开: float, 高: float, 低: float, 收: float, 量: float) -> List[Tuple[int, K线]]: ... + def 获取当前K线(self, 周期: int) -> Optional[K线]: ... - :param 普K: 最小周期原始K线 - :raises RuntimeError: 当投喂的K线周期与输入周期不匹配时 - """ - ... - def 测试_保存数据(self) -> Any: - """拆分各序列数据,单独存文件,文件名为对应变量名""" - ... +class 立体分析器: + def __init__(self, 符号: str, 周期组: List[int], 配置: Optional[缠论配置] = None, 配置组: Optional[Dict[int, 缠论配置]] = None) -> None: ... + @property + def 周期组(self) -> List[int]: ... + def 投喂K线(self, 普K: K线) -> None: ... + def 获取观察者(self, 周期: int) -> Optional[观察者]: ... + def 测试_保存数据(self, root: Optional[str] = None) -> None: ... -def 转化为时间戳(ts: Any) -> int: ... -def 转化为时间戳_数字(ts: Any) -> int: ... +# ========== 缠论配置 (fields via __getattr__/__setattr__) ========== + +class 缠论配置: + def __init__(self, **kwargs: Any) -> None: ... + def to_dict(self) -> Dict[str, Any]: ... + def to_json(self) -> str: ... + def 保存配置(self, path: str = "缠论配置.json") -> None: ... + def 对比(self, other: 缠论配置) -> Dict[str, Tuple[Any, Any]]: ... + @classmethod + def 加载配置(cls, path: str = "缠论配置.json") -> 缠论配置: ... + @classmethod + def from_dict(cls, data: Dict[str, Any]) -> 缠论配置: ... + @classmethod + def from_json(cls, json_str: str) -> 缠论配置: ... + @classmethod + def 不推送(cls) -> 缠论配置: ... + @classmethod + def 按序号重组字典(cls, 默认配置: Any, 原始字典: Dict[str, Any]) -> Dict[str, Any]: ... + def __str__(self) -> str: ... + def __repr__(self) -> str: ... diff --git a/chanlun-py/chanlun/chan.py b/chanlun-py/chanlun/chan.py index 2798cc9..fd104ea 100644 --- a/chanlun-py/chanlun/chan.py +++ b/chanlun-py/chanlun/chan.py @@ -21,10 +21,12 @@ 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. """ + # -*- coding: utf-8 -*- # @Time : 2024/10/15 16:45 # @Author : YuYuKunKun # @File : chan.py +from __future__ import annotations import json import math @@ -46,14 +48,14 @@ from typing import ( Dict, Any, Union, - Sequence, Callable, Set, ) +from collections.abc import Sequence -from pydantic import BaseModel, Field, model_validator, ValidationError, field_validator -from termcolor import colored +from chanlun.termcolor import colored +from loguru import logger __all__ = [ "K线", @@ -66,6 +68,7 @@ __all__ = [ "基础买卖点", "平滑异同移动平均线", "指标", + "指标容器", "测试_读取数据", "特征分型", "相对强弱指数", @@ -83,8 +86,52 @@ __all__ = [ "转化为时间戳", "转化为时间戳_数字", "随机指标", + "布林带", + "set_log_level", + "get_log_level", ] +# 日志级别映射: 名称 → loguru 级别名 +_级别映射 = { + "trace": "TRACE", + "debug": "DEBUG", + "info": "INFO", + "warn": "WARNING", + "error": "ERROR", + "off": "OFF", +} +_有效级别 = frozenset(_级别映射.keys()) +_当前日志级别 = "info" + + +def set_log_level(level: str): + """设置 Python 侧日志级别。 + + :param level: 日志级别,不区分大小写 (trace / debug / info / warn / error / off) + """ + global _当前日志级别 + _level = level.lower() + if _level not in _有效级别: + raise ValueError(f"无效日志级别 '{level}',有效值: {', '.join(sorted(_有效级别))}") + + _当前日志级别 = _level + _loguru_level = _级别映射[_level] + + try: + logger.remove(0) + except ValueError: + pass + if _loguru_level != "OFF": + logger.add(sys.stderr, level=_loguru_level) + + +def get_log_level() -> str: + """获取 Python 侧当前日志级别。 + + :return: 日志级别字符串 (trace / debug / info / warn / error / off) + """ + return _当前日志级别 + class 买卖点类型(str, Enum): """买卖点类型 — 缠论的三类买卖点及扩展类型。 @@ -151,7 +198,7 @@ class 基础买卖点: :ivar 与MACD柱子分型匹配: MACD柱子分型是否匹配 """ - def __init__(self, 类型: 买卖点类型, 当前K线: "K线", 买卖点分型: "分型", 备注: str, 中枢破位值: float): + def __init__(self, 类型: 买卖点类型, 当前K线: K线, 买卖点分型: 分型, 备注: str, 中枢破位值: float): """ :param 类型: 买卖点类型 :param 当前K线: 触发买卖点的K线 @@ -163,9 +210,9 @@ class 基础买卖点: self.类型 = 类型 self.买卖点分型 = 买卖点分型 self.买卖点K线 = 买卖点分型.中 # .镜像 - self.__当前K线: "K线" = 当前K线 - self.失效K线: Optional["K线"] = None - self.终结K线: Optional["K线"] = None # 卖出 or 买入 + self.__当前K线: K线 = 当前K线 + self.失效K线: Optional[K线] = None + self.终结K线: Optional[K线] = None # 卖出 or 买入 self.__破位值 = 中枢破位值 self.结构 = None @@ -177,30 +224,30 @@ class 基础买卖点: @property def 当前K线(self): - """当前K线""" + """触发该买卖点的原始K线""" return self.__当前K线 @property def 破位值(self) -> float: - """破位值 + """中枢破位价格。 - :return: float + :return: 买卖点K线突破中枢边界的价位 """ return self.__破位值 @property def 偏移(self) -> int: - """偏移 + """当前K线与买卖点K线的序号差。 - :return: int + :return: 买卖点产生后经过的K线根数 """ return self.__当前K线.序号 - self.买卖点K线.序号 @property def 失效偏移(self) -> int: - """失效偏移 + """失效K线与买卖点K线的序号差。 - :return: int + :return: 序号差值,-1 表示尚未失效 """ if self.失效K线 is None: return -1 @@ -208,40 +255,45 @@ class 基础买卖点: @property def 有效性(self) -> bool: - """有效性 + """是否已失效。 - :return: bool + :return: 存在失效K线时返回 True """ return self.失效K线 is not None @property def 与MACD柱子匹配(self) -> bool: - """与MACD柱子匹配 + """MACD柱方向是否与买卖点方向一致。 - :return: bool + :return: 一致返回 True """ return self.买卖点K线.与MACD柱子匹配 @property def 与MACD柱子分型匹配(self) -> bool: - """与MACD柱子分型匹配 + """买卖点分型的MACD柱面积方向是否与买卖点方向一致。 - :return: bool + :return: 一致返回 True """ return self.买卖点分型.与MACD柱子分型匹配 @final class 买卖点(基础买卖点): - """买卖点 — 一二三类买卖点构造器。 + """一二三类买卖点及扩展类型(T1/T1P/T2/T2S/T3A/T3B)的构造器。 - 类方法: - 一卖点 / 一买点 / 二卖点 / 二买点 / 三卖点 / 三买点 — 创建对应买卖点实例 - 生成买卖点(特征, 序号, 级别, 分型, 当前缠K) — 根据特征路由到对应构造函数 + 提供18种类方法创建对应买卖点实例,以及一个通用路由方法: + + 主要方法: + :meth:`一卖点` / :meth:`一买点` / :meth:`二卖点` / :meth:`二买点` / :meth:`三卖点` / :meth:`三买点` + :meth:`T1卖点` / :meth:`T1买点` / :meth:`T1P卖点` / :meth:`T1P买点` + :meth:`T2卖点` / :meth:`T2买点` / :meth:`T2S卖点` / :meth:`T2S买点` + :meth:`T3A卖点` / :meth:`T3A买点` / :meth:`T3B卖点` / :meth:`T3B买点` + :meth:`生成买卖点` — 根据特征和序号路由到对应构造函数 """ @classmethod - def 一卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": + def 一卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: """ :param 买卖点分型: 买卖点对应的分型 :param 当前K线: 当前K线 @@ -253,7 +305,7 @@ class 买卖点(基础买卖点): return 买卖点(备注=备注, 类型=买卖点类型.一卖, 买卖点分型=买卖点分型, 当前K线=当前K线, 中枢破位值=中枢破位值) @classmethod - def 一买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": + def 一买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: """ :param 买卖点分型: 买卖点对应的分型 :param 当前K线: 当前K线 @@ -265,7 +317,7 @@ class 买卖点(基础买卖点): return 买卖点(备注=备注, 类型=买卖点类型.一买, 买卖点分型=买卖点分型, 当前K线=当前K线, 中枢破位值=中枢破位值) @classmethod - def 二卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": + def 二卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: """ :param 买卖点分型: 买卖点对应的分型 :param 当前K线: 当前K线 @@ -277,7 +329,7 @@ class 买卖点(基础买卖点): return 买卖点(备注=备注, 类型=买卖点类型.二卖, 买卖点分型=买卖点分型, 当前K线=当前K线, 中枢破位值=中枢破位值) @classmethod - def 二买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": + def 二买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: """ :param 买卖点分型: 买卖点对应的分型 :param 当前K线: 当前K线 @@ -289,7 +341,7 @@ class 买卖点(基础买卖点): return 买卖点(备注=备注, 类型=买卖点类型.二买, 买卖点分型=买卖点分型, 当前K线=当前K线, 中枢破位值=中枢破位值) @classmethod - def 三卖点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": + def 三卖点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: """ :param 买卖点分型: 买卖点对应的分型 :param 当前K线: 当前K线 @@ -301,7 +353,7 @@ class 买卖点(基础买卖点): return 买卖点(备注=备注, 类型=买卖点类型.三卖, 买卖点分型=买卖点分型, 当前K线=当前K线, 中枢破位值=中枢破位值) @classmethod - def 三买点(cls, 买卖点分型: "分型", 当前K线: "K线", 标识: str, 备注: str, 中枢破位值: float) -> "买卖点": + def 三买点(cls, 买卖点分型: 分型, 当前K线: K线, 标识: str, 备注: str, 中枢破位值: float) -> 买卖点: """ :param 买卖点分型: 买卖点对应的分型 :param 当前K线: 当前K线 @@ -313,7 +365,7 @@ class 买卖点(基础买卖点): return 买卖点(备注=备注, 类型=买卖点类型.三买, 买卖点分型=买卖点分型, 当前K线=当前K线, 中枢破位值=中枢破位值) @classmethod - def 生成买卖点(cls, 特征: str, 序号: str, 级别: str, 买卖点分型: "分型", 当前缠K: "缠论K线"): + def 生成买卖点(cls, 特征: str, 序号: str, 级别: str, 买卖点分型: 分型, 当前缠K: 缠论K线): """ :param 特征: 特征字符串 :param 序号: 序号(如"一"、"二"、"三") @@ -382,234 +434,436 @@ def 转化为时间戳_数字(ts: Union[str, datetime, int, float]) -> int: raise TypeError(f"不支持的时间戳类型: {type(ts)}") +# 模拟 Pydantic 的 ValidationError,保持原有异常逻辑兼容 +class ValidationError(Exception): + pass + + @final -class 缠论配置(BaseModel): - """缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 +class 缠论配置: + """控制缠论分析各阶段行为的全局参数集。 - 字段分组: - [基础] 标识 - [缠K] 缠K合并替换 - [笔] 笔内元素数量, 笔弱化, 笔次成笔, 笔内相同终点取舍 等 - [线段] 线段_特征序列忽视老阴老阳, 线段_缺口后紧急修正, 线段内部中枢图显 等 - [分析开关] 分析笔, 分析线段, 分析扩展线段, 分析笔中枢, 分析线段中枢 - [指标] 计算指标, 指标计算方式, MACD/RSI/KDJ 参数 - [推送/显示] 图表展示, 推送K线/笔/线段/中枢, 图表展示_笔 等细分开关 - [买卖点] 买卖点偏移, 买卖点激进识别, 买卖点_背离率, 买卖点_计算方式 等 - [背驰] 线段内部背驰_MACD, 线段内部背驰_斜率 等 - [其他] 手动终止, 加载文件路径 + 共 60+ 个字段,均有合理默认值。按功能分组: - 方法: - to_dict() / to_json() / 保存配置(path?) / 加载配置(path?) (classmethod) - from_dict(data) (classmethod) / from_json(json_str) (classmethod) - 不推送() (classmethod) / 对比(other) -> dict + **[基础]** 标识 \\ + **[缠K]** 缠K合并替换 \\ + **[笔]** 笔内元素数量, 笔弱化, 笔次成笔, 笔内相同终点取舍 等 \\ + **[线段]** 线段_特征序列忽视老阴老阳, 线段_缺口后紧急修正, 线段内部中枢图显 等 \\ + **[分析开关]** 分析笔, 分析线段, 分析扩展线段, 分析笔中枢, 分析线段中枢 \\ + **[指标]** 计算指标, 指标计算方式, MACD/RSI/KDJ 参数 \\ + **[推送/显示]** 图表展示, 推送K线/笔/线段/中枢 等 \\ + **[买卖点]** 买卖点偏移, 买卖点激进识别, 买卖点_背离率, 买卖点_计算方式 等 \\ + **[背驰]** 线段内部背驰_MACD, 线段内部背驰_斜率 等 \\ + **[其他]** 手动终止, 加载文件路径 + + 主要方法: + :meth:`to_dict` / :meth:`to_json` — 序列化 \\ + :meth:`from_dict` / :meth:`from_json` — 反序列化 \\ + :meth:`保存配置` / :meth:`加载配置` — 文件读写 \\ + :meth:`不推送` — 创建静默配置 \\ + :meth:`对比` — 比较两个配置 """ - 标识: str = "bar" - 缠K合并替换: bool = False # False: 在原缠K上合并, True: 产出新缠K + def __init__( + self, + 标识: str = "bar", + 缠K合并替换: bool = False, # False: 在原缠K上合并, True: 产出新缠K + 笔内元素数量: int = 5, # 成笔最低长度 + 笔内相同终点取舍: bool = False, # 一笔终点存在多个终点时 True: last, False: first + 笔内起始分型包含整笔: bool = False, # True: 一笔起始分型高低包含整支笔对象则不成笔, False: 只判断分型中间数据是否包含 + 笔内起始分型包含整笔_包括右: bool = False, # True: 将笔之武.右纳入 + 笔内原始K线包含整笔: bool = False, # 在非 [笔内起始分型包含整笔] 时判断原始K线包含整笔的情况 + 笔次级成笔: bool = False, + 笔弱化: bool = False, + 笔弱化_原始数量: int = 3, + # 笔_必须对齐:bool = False # 强迫症设为True, 将获得无与伦比满足。。。 + 线段_非缺口下穿刺: bool = False, # True: 非缺口状态下[小阳, 少阴]时,存在贯穿伤与之后紧邻的三个元素有方向相同的线段时回退, 此举在当下是否有任何意义呢? + 线段_特征序列忽视老阴老阳: bool = False, # True 不用严格的特征序列包含,也就是忽视缺口全以无缺口对待 + 线段_缺口后紧急修正: bool = True, # True: 当 线段_特征序列忽视老阴老阳=False 时生效,同样 线段_特征序列忽视老阴老阳=True时等同于修正武斗不异常,但只产出一个线段 + 线段_修正: bool = False, # 短路修正,不建议使用,但此修正将走势显示的更加清晰 + 线段内部中枢图显: bool = True, + 扩展线段_当下分析: bool = False, # 以当下来看的分析规则,否则以事后来看 + 分析笔: bool = True, + 分析线段: bool = True, + 分析扩展线段: bool = True, + 分析笔中枢: bool = True, + 分析线段中枢: bool = True, + 手动终止: str = "", # 2099-12-31 00:00:00 + 计算指标: bool = True, + 指标计算方式: str = "收", # (开, 高, 低, 收, 高低均值, 高低收均值, 开高低收均值), 默认 收盘价 + 平滑异同移动平均线_快线周期: int = 13, + 平滑异同移动平均线_慢线周期: int = 31, + 平滑异同移动平均线_信号周期: int = 11, + 相对强弱指数_周期: int = 13, + 相对强弱指数_移动平均线周期: int = 13, + 相对强弱指数_超买阈值: float = 75.0, + 相对强弱指数_超卖阈值: float = 25.0, + 随机指标_RSV周期: int = 13, + 随机指标_K值平滑周期: int = 5, + 随机指标_D值平滑周期: int = 5, + 随机指标_超买阈值: float = 80.0, + 随机指标_超卖阈值: float = 20.0, + 计算BOLL: bool = False, + 布林带_周期: int = 20, + 布林带_标准差倍数: float = 2.0, + 均线_类型列表: List[str] = None, + 均线_周期列表: List[int] = None, + # 多参数指标列表(None/空列表 = 使用默认单参数) + MACD_参数列表: List[tuple] = None, # [(key, 快线, 慢线, 信号), ...] + RSI_周期列表: List[tuple] = None, # [(key, 周期), ...] + KDJ_参数列表: List[tuple] = None, # [(key, RSV周期, K平滑, D平滑), ...] + BOLL_参数列表: List[tuple] = None, # [(key, 周期, 标准差倍数), ...] + 图表展示: bool = True, + 推送K线: bool = True, + 推送笔: bool = True, + 推送线段: bool = True, + 推送中枢: bool = True, + 图表展示_笔: bool = True, + 图表展示_线段: bool = True, + 图表展示_扩展线段: bool = True, + 图表展示_扩展线段_线段: bool = True, + 图表展示_线段_线段: bool = True, + 图表展示_中枢_笔: bool = True, + 图表展示_中枢_线段: bool = True, + 图表展示_中枢_扩展线段: bool = True, + 图表展示_中枢_扩展线段_线段: bool = True, + 图表展示_中枢_线段_线段: bool = True, + 图表展示_中枢_线段内部: bool = True, + 买卖点偏移: int = 1, # 最大偏移 + 买卖点激进识别: bool = False, # 激进模式下将不考虑分型的完整性 + 买卖点与MACD柱强相关: bool = False, # True: 卖点需正值 买点需负值 + 买卖点错过误差值: float = 0.01, # 距离买卖点值上下之内 + 买卖点_指标模式: str = "配置", # 【任意,配置,全量, 相对】 对应K线 + 买卖点_指标匹配_MACD: bool = True, # 买在负,卖在正! + 买卖点_指标匹配_KDJ: bool = True, # 买在死叉之后,卖在金叉之后 + 买卖点_指标匹配_RSI: bool = True, # 买在均线之下,卖在均线之上 + # 以下字段酌情废弃 + 买卖点_背离率: float = float("inf"), + 买卖点_T2_回调阈值: float = 1.0, + 买卖点_T2S_最大层级: int = 3, + 买卖点_峰值条件: bool = False, + 买卖点_计算方式: str = "峰", + 买卖点_计算线段BSP1: bool = True, + 买卖点_处理BSP2: bool = True, + 买卖点_计算线段BSP3: bool = True, + 买卖点_依赖T1: bool = True, + 买卖点_中枢来源: str = "合", + 买卖点_调试输出: bool = False, + # 以上字段酌情废弃 + 线段内部背驰_MACD: bool = True, + 线段内部背驰_斜率: bool = True, + 线段内部背驰_测度: bool = True, + 线段内部背驰_模式: str = "相对", # 【任意,配置,全量,相对】 + 加载文件路径: str = "", + ): + # 所有字段赋值 + self.标识 = 标识 + self.缠K合并替换 = 缠K合并替换 + self.笔内元素数量 = 笔内元素数量 + self.笔内相同终点取舍 = 笔内相同终点取舍 + self.笔内起始分型包含整笔 = 笔内起始分型包含整笔 + self.笔内起始分型包含整笔_包括右 = 笔内起始分型包含整笔_包括右 + self.笔内原始K线包含整笔 = 笔内原始K线包含整笔 + self.笔次级成笔 = 笔次级成笔 + self.笔弱化 = 笔弱化 + self.笔弱化_原始数量 = 笔弱化_原始数量 + self.线段_非缺口下穿刺 = 线段_非缺口下穿刺 + self.线段_特征序列忽视老阴老阳 = 线段_特征序列忽视老阴老阳 + self.线段_缺口后紧急修正 = 线段_缺口后紧急修正 + self.线段_修正 = 线段_修正 + self.线段内部中枢图显 = 线段内部中枢图显 + self.扩展线段_当下分析 = 扩展线段_当下分析 + self.分析笔 = 分析笔 + self.分析线段 = 分析线段 + self.分析扩展线段 = 分析扩展线段 + self.分析笔中枢 = 分析笔中枢 + self.分析线段中枢 = 分析线段中枢 + self.手动终止 = 手动终止 + self.计算指标 = 计算指标 + self.指标计算方式 = 指标计算方式 + self.平滑异同移动平均线_快线周期 = 平滑异同移动平均线_快线周期 + self.平滑异同移动平均线_慢线周期 = 平滑异同移动平均线_慢线周期 + self.平滑异同移动平均线_信号周期 = 平滑异同移动平均线_信号周期 + self.相对强弱指数_周期 = 相对强弱指数_周期 + self.相对强弱指数_移动平均线周期 = 相对强弱指数_移动平均线周期 + self.相对强弱指数_超买阈值 = 相对强弱指数_超买阈值 + self.相对强弱指数_超卖阈值 = 相对强弱指数_超卖阈值 + self.随机指标_RSV周期 = 随机指标_RSV周期 + self.随机指标_K值平滑周期 = 随机指标_K值平滑周期 + self.随机指标_D值平滑周期 = 随机指标_D值平滑周期 + self.随机指标_超买阈值 = 随机指标_超买阈值 + self.随机指标_超卖阈值 = 随机指标_超卖阈值 + self.计算BOLL = 计算BOLL + self.布林带_周期 = 布林带_周期 + self.布林带_标准差倍数 = 布林带_标准差倍数 + self.均线_类型列表 = 均线_类型列表 if 均线_类型列表 is not None else [] + self.均线_周期列表 = 均线_周期列表 if 均线_周期列表 is not None else [] + self.MACD_参数列表 = MACD_参数列表 if MACD_参数列表 is not None else [] + self.RSI_周期列表 = RSI_周期列表 if RSI_周期列表 is not None else [] + self.KDJ_参数列表 = KDJ_参数列表 if KDJ_参数列表 is not None else [] + self.BOLL_参数列表 = BOLL_参数列表 if BOLL_参数列表 is not None else [] + self.图表展示 = 图表展示 + self.推送K线 = 推送K线 + self.推送笔 = 推送笔 + self.推送线段 = 推送线段 + self.推送中枢 = 推送中枢 + self.图表展示_笔 = 图表展示_笔 + self.图表展示_线段 = 图表展示_线段 + self.图表展示_扩展线段 = 图表展示_扩展线段 + self.图表展示_扩展线段_线段 = 图表展示_扩展线段_线段 + self.图表展示_线段_线段 = 图表展示_线段_线段 + self.图表展示_中枢_笔 = 图表展示_中枢_笔 + self.图表展示_中枢_线段 = 图表展示_中枢_线段 + self.图表展示_中枢_扩展线段 = 图表展示_中枢_扩展线段 + self.图表展示_中枢_扩展线段_线段 = 图表展示_中枢_扩展线段_线段 + self.图表展示_中枢_线段_线段 = 图表展示_中枢_线段_线段 + self.图表展示_中枢_线段内部 = 图表展示_中枢_线段内部 + self.买卖点偏移 = 买卖点偏移 + self.买卖点激进识别 = 买卖点激进识别 + self.买卖点与MACD柱强相关 = 买卖点与MACD柱强相关 + self.买卖点错过误差值 = 买卖点错过误差值 + self.买卖点_指标模式 = 买卖点_指标模式 + self.买卖点_指标匹配_MACD = 买卖点_指标匹配_MACD + self.买卖点_指标匹配_KDJ = 买卖点_指标匹配_KDJ + self.买卖点_指标匹配_RSI = 买卖点_指标匹配_RSI + self.买卖点_背离率 = 买卖点_背离率 + self.买卖点_T2_回调阈值 = 买卖点_T2_回调阈值 + self.买卖点_T2S_最大层级 = 买卖点_T2S_最大层级 + self.买卖点_峰值条件 = 买卖点_峰值条件 + self.买卖点_计算方式 = 买卖点_计算方式 + self.买卖点_计算线段BSP1 = 买卖点_计算线段BSP1 + self.买卖点_处理BSP2 = 买卖点_处理BSP2 + self.买卖点_计算线段BSP3 = 买卖点_计算线段BSP3 + self.买卖点_依赖T1 = 买卖点_依赖T1 + self.买卖点_中枢来源 = 买卖点_中枢来源 + self.买卖点_调试输出 = 买卖点_调试输出 + self.线段内部背驰_MACD = 线段内部背驰_MACD + self.线段内部背驰_斜率 = 线段内部背驰_斜率 + self.线段内部背驰_测度 = 线段内部背驰_测度 + self.线段内部背驰_模式 = 线段内部背驰_模式 + self.加载文件路径 = 加载文件路径 - 笔内元素数量: int = 5 # 成BI最低长度 + # 执行初始化验证 + self._validate_all_fields() - 笔内相同终点取舍: bool = False # 一笔终点存在多个终点时 True: last, False: first - 笔内起始分型包含整笔: bool = False # True: 一笔起始分型高低包含整支笔对象则不成笔, False: 只判断分型中间数据是否包含 - 笔内起始分型包含整笔_包括右: bool = False # True: 将笔之武.右纳入 - 笔内原始K线包含整笔: bool = False # 在非 [笔内起始分型包含整笔] 时判断原始K线包含整笔的情况 + # 定义所有字段名列表(替代 model_fields) + @classmethod + def model_fields(cls) -> Dict[str, Dict[str, Any]]: + """返回类所有字段的元信息。 - 笔次级成笔: bool = False - 笔弱化: bool = False - 笔弱化_原始数量: int = 3 - # 笔_必须对齐:bool = False # 强迫症设为True, 将获得无与伦比满足。。。 - - 线段_非缺口下穿刺: bool = False # True: 非缺口状态下[小阳, 少阴]时,存在贯穿伤与之后紧邻的三个元素有方向相同的线段时回退, 此举在当下是否有任何意义呢? - 线段_特征序列忽视老阴老阳: bool = False # True 不用严格的特征序列包含,也就是忽视缺口全以无缺口对待 - 线段_缺口后紧急修正: bool = True # True: 当 线段_特征序列忽视老阴老阳=False 时生效,同样 线段_特征序列忽视老阴老阳=True时等同于修正武斗不异常,但只产出一个线段 - 线段_修正: bool = False # 短路修正,不建议使用,但此修正将走势显示的更加清晰 - - 线段内部中枢图显: bool = True - 扩展线段_当下分析: bool = False # 以当下来看的分析规则,否则以事后来看 - - 分析笔: bool = True # 是否计算BI - 分析线段: bool = True # 是否计算XD - 分析扩展线段: bool = True # 是否计算XD - 分析笔中枢: bool = True # 是否计算BI中枢 - 分析线段中枢: bool = True # 是否计算XD中枢 - - 手动终止: str = "" # 2099-12-31 00:00:00 - 计算指标: bool = True - 指标计算方式: str = "收" # (开, 高, 低, 收, 高低均值, 高低收均值, 开高低收均值), 默认 收盘价 - 平滑异同移动平均线_快线周期: int = 13 - 平滑异同移动平均线_慢线周期: int = 31 - 平滑异同移动平均线_信号周期: int = 11 - - 相对强弱指数_周期: int = 13 - 相对强弱指数_移动平均线周期: int = 13 - 相对强弱指数_超买阈值: float = 75.0 - 相对强弱指数_超卖阈值: float = 25.0 - - 随机指标_RSV周期: int = 13 - 随机指标_K值平滑周期: int = 5 - 随机指标_D值平滑周期: int = 5 - 随机指标_超买阈值: float = 80.0 - 随机指标_超卖阈值: float = 20.0 - - 图表展示: bool = True - 推送K线: bool = True - 推送笔: bool = True - 推送线段: bool = True - 推送中枢: bool = True - - 图表展示_笔: bool = True - 图表展示_线段: bool = True - 图表展示_扩展线段: bool = True - 图表展示_扩展线段_线段: bool = True - 图表展示_线段_线段: bool = True - - 图表展示_中枢_笔: bool = True - 图表展示_中枢_线段: bool = True - 图表展示_中枢_扩展线段: bool = True - 图表展示_中枢_扩展线段_线段: bool = True - 图表展示_中枢_线段_线段: bool = True - 图表展示_中枢_线段内部: bool = True - - 买卖点偏移: int = 1 # 最大偏移 - 买卖点激进识别: bool = False # 激进模式下将不考虑分型的完整性 - 买卖点与MACD柱强相关: bool = False # True: 卖点需正值 买点需负值 - 买卖点错过误差值: float = 0.01 # 距离买卖点值上下之内 - - 买卖点_指标模式: str = "配置" # 【任意,配置,全量, 相对】 对应K线 - 买卖点_指标匹配_MACD: bool = True # 买在负,卖在正! - 买卖点_指标匹配_KDJ: bool = True # 买在死叉之后,卖在金叉之后 - 买卖点_指标匹配_RSI: bool = True # 买在均线之下,卖在均线之上 - - # --- 缠论六类买卖点识别参数 --- - 买卖点_背离率: float = float("inf") # T1/T1P背离率,默认无限大(不筛选) - 买卖点_T2_回调阈值: float = 1.0 # T2回调幅度/突破振幅上限 - 买卖点_T2S_最大层级: int = 3 # T2S最大搜索深度 - 买卖点_峰值条件: bool = False # T1是否要求突破中枢内所有元素的极值 - 买卖点_计算方式: str = "峰" # "峰"=最大MACD柱绝对值, "面"=MACD柱面积 - 买卖点_计算线段BSP1: bool = True # 是否启用T1/T1P识别 - 买卖点_处理BSP2: bool = True # 是否启用T2/T2S识别 - 买卖点_计算线段BSP3: bool = True # 是否启用T3A/T3B识别 - 买卖点_依赖T1: bool = True # T2是否必须依赖T1 - 买卖点_中枢来源: str = "合" # "实"/"虚"/"合" - 取线段的哪个内部中枢序列 - 买卖点_调试输出: bool = False # 是否打印调试信息 - - 线段内部背驰_MACD: bool = True - 线段内部背驰_斜率: bool = True - 线段内部背驰_测度: bool = True - 线段内部背驰_模式: str = "相对" # 【任意,配置,全量,相对】 - - 加载文件路径: str = "" - - @model_validator(mode="before") - def 兼容旧版本配置(cls, values: Dict[str, Any]) -> Dict[str, Any]: + :return: 字段名到类型/默认值元信息的映射 """ - 自动兼容: - 1. 旧版本少字段 → 使用默认值 - 2. 新版本多字段 → 自动忽略多余字段 - 3. 字段改名/删除 → 不报错 + return { + "标识": {"annotation": str, "default": "bar"}, + "缠K合并替换": {"annotation": bool, "default": False}, + "笔内元素数量": {"annotation": int, "default": 5}, + "笔内相同终点取舍": {"annotation": bool, "default": False}, + "笔内起始分型包含整笔": {"annotation": bool, "default": False}, + "笔内起始分型包含整笔_包括右": {"annotation": bool, "default": False}, + "笔内原始K线包含整笔": {"annotation": bool, "default": False}, + "笔次级成笔": {"annotation": bool, "default": False}, + "笔弱化": {"annotation": bool, "default": False}, + "笔弱化_原始数量": {"annotation": int, "default": 3}, + "线段_非缺口下穿刺": {"annotation": bool, "default": False}, + "线段_特征序列忽视老阴老阳": {"annotation": bool, "default": False}, + "线段_缺口后紧急修正": {"annotation": bool, "default": True}, + "线段_修正": {"annotation": bool, "default": False}, + "线段内部中枢图显": {"annotation": bool, "default": True}, + "扩展线段_当下分析": {"annotation": bool, "default": False}, + "分析笔": {"annotation": bool, "default": True}, + "分析线段": {"annotation": bool, "default": True}, + "分析扩展线段": {"annotation": bool, "default": True}, + "分析笔中枢": {"annotation": bool, "default": True}, + "分析线段中枢": {"annotation": bool, "default": True}, + "手动终止": {"annotation": str, "default": ""}, + "计算指标": {"annotation": bool, "default": True}, + "指标计算方式": {"annotation": str, "default": "收"}, + "平滑异同移动平均线_快线周期": {"annotation": int, "default": 13}, + "平滑异同移动平均线_慢线周期": {"annotation": int, "default": 31}, + "平滑异同移动平均线_信号周期": {"annotation": int, "default": 11}, + "相对强弱指数_周期": {"annotation": int, "default": 13}, + "相对强弱指数_移动平均线周期": {"annotation": int, "default": 13}, + "相对强弱指数_超买阈值": {"annotation": float, "default": 75.0}, + "相对强弱指数_超卖阈值": {"annotation": float, "default": 25.0}, + "随机指标_RSV周期": {"annotation": int, "default": 13}, + "随机指标_K值平滑周期": {"annotation": int, "default": 5}, + "随机指标_D值平滑周期": {"annotation": int, "default": 5}, + "随机指标_超买阈值": {"annotation": float, "default": 80.0}, + "随机指标_超卖阈值": {"annotation": float, "default": 20.0}, + "计算BOLL": {"annotation": bool, "default": False}, + "布林带_周期": {"annotation": int, "default": 20}, + "布林带_标准差倍数": {"annotation": float, "default": 2.0}, + "均线_类型列表": {"annotation": List[str], "default": []}, + "均线_周期列表": {"annotation": List[int], "default": []}, + "MACD_参数列表": {"annotation": List[tuple], "default": []}, + "RSI_周期列表": {"annotation": List[tuple], "default": []}, + "KDJ_参数列表": {"annotation": List[tuple], "default": []}, + "BOLL_参数列表": {"annotation": List[tuple], "default": []}, + "图表展示": {"annotation": bool, "default": True}, + "推送K线": {"annotation": bool, "default": True}, + "推送笔": {"annotation": bool, "default": True}, + "推送线段": {"annotation": bool, "default": True}, + "推送中枢": {"annotation": bool, "default": True}, + "图表展示_笔": {"annotation": bool, "default": True}, + "图表展示_线段": {"annotation": bool, "default": True}, + "图表展示_扩展线段": {"annotation": bool, "default": True}, + "图表展示_扩展线段_线段": {"annotation": bool, "default": True}, + "图表展示_线段_线段": {"annotation": bool, "default": True}, + "图表展示_中枢_笔": {"annotation": bool, "default": True}, + "图表展示_中枢_线段": {"annotation": bool, "default": True}, + "图表展示_中枢_扩展线段": {"annotation": bool, "default": True}, + "图表展示_中枢_扩展线段_线段": {"annotation": bool, "default": True}, + "图表展示_中枢_线段_线段": {"annotation": bool, "default": True}, + "图表展示_中枢_线段内部": {"annotation": bool, "default": True}, + "买卖点偏移": {"annotation": int, "default": 1}, + "买卖点激进识别": {"annotation": bool, "default": False}, + "买卖点与MACD柱强相关": {"annotation": bool, "default": False}, + "买卖点错过误差值": {"annotation": float, "default": 0.01}, + "买卖点_指标模式": {"annotation": str, "default": "配置"}, + "买卖点_指标匹配_MACD": {"annotation": bool, "default": True}, + "买卖点_指标匹配_KDJ": {"annotation": bool, "default": True}, + "买卖点_指标匹配_RSI": {"annotation": bool, "default": True}, + "买卖点_背离率": {"annotation": float, "default": float("inf")}, + "买卖点_T2_回调阈值": {"annotation": float, "default": 1.0}, + "买卖点_T2S_最大层级": {"annotation": int, "default": 3}, + "买卖点_峰值条件": {"annotation": bool, "default": False}, + "买卖点_计算方式": {"annotation": str, "default": "峰"}, + "买卖点_计算线段BSP1": {"annotation": bool, "default": True}, + "买卖点_处理BSP2": {"annotation": bool, "default": True}, + "买卖点_计算线段BSP3": {"annotation": bool, "default": True}, + "买卖点_依赖T1": {"annotation": bool, "default": True}, + "买卖点_中枢来源": {"annotation": str, "default": "合"}, + "买卖点_调试输出": {"annotation": bool, "default": False}, + "线段内部背驰_MACD": {"annotation": bool, "default": True}, + "线段内部背驰_斜率": {"annotation": bool, "default": True}, + "线段内部背驰_测度": {"annotation": bool, "default": True}, + "线段内部背驰_模式": {"annotation": str, "default": "相对"}, + "加载文件路径": {"annotation": str, "default": ""}, + } - :param values: 原始配置字典 - :return: 兼容后的字典 - """ - return values - - @field_validator("*", mode="wrap") - def bool_parse_fallback_default(cls, value, handler, info): - """ - :param value: 待验证的值 - :param handler: 默认验证器 - :param info: 字段信息 - :return: 验证后的值 - """ - field = cls.model_fields.get(info.field_name) - if not field: - return handler(value) + def _validate_all_fields(self): + """统一验证所有字段,替代原 field_validator""" 允许值 = { "指标计算方式": ["开", "高", "低", "收", "高低均值", "高低收均值", "开高低收均值"], } - # 字段类型 & 默认值 - type_ = field.annotation - default = field.default - fname = info.field_name + fields = self.model_fields() - try: - # --- 1. 处理 bool:使用内置 bool_parsing --- - if type_ is bool: - return handler(value) + for fname, field_info in fields.items(): + value = getattr(self, fname) + type_ = field_info["annotation"] + default = field_info["default"] - # --- 2. 处理 int:使用内置 int_parsing --- - elif type_ is int: - return handler(value) + try: + # 布尔类型验证 + if type_ is bool: + if not isinstance(value, bool): + setattr(self, fname, bool(value)) - # --- 3. 处理指定 str:必须在允许列表内 --- - elif fname in 允许值: - result = handler(value) - if result not in 允许值[fname]: - raise ValueError(f"值不在允许范围内: {result}") - return result + # 整数类型验证 + elif type_ is int: + if not isinstance(value, int): + setattr(self, fname, int(value)) - # 其他类型不处理 - else: - return handler(value) + # 枚举字符串验证 + elif fname in 允许值: + if value not in 允许值[fname]: + logger.warning(f"[{fname}] = {value} 值不在允许范围内,使用默认值:{default}") + setattr(self, fname, default) - # 验证失败 → 统一用默认值 - except ValidationError as e: - if "bool_parsing" in str(e) or "int_parsing" in str(e): - print(f"[{fname}] = {value} 解析失败,使用默认值:{default}") - return default - raise + except (ValueError, TypeError) as e: + logger.warning(f"[{fname}] = {value} 解析失败,使用默认值:{default}") + setattr(self, fname, default) - # 字符串不在允许列表 → 用默认值 - except ValueError as e: - print(f"[{fname}] = {value} {str(e)},使用默认值:{default}") - return default + # ---- 参数列表解析:多参数列表为空时回退到默认单参数 ---- + + def _解析MACD参数列表(self) -> List[tuple]: + if self.MACD_参数列表: + return self.MACD_参数列表 + return [("macd", self.平滑异同移动平均线_快线周期, self.平滑异同移动平均线_慢线周期, self.平滑异同移动平均线_信号周期)] + + def _解析RSI周期列表(self) -> List[tuple]: + if self.RSI_周期列表: + return self.RSI_周期列表 + return [("rsi", self.相对强弱指数_周期)] + + def _解析KDJ参数列表(self) -> List[tuple]: + if self.KDJ_参数列表: + return self.KDJ_参数列表 + return [("kdj", self.随机指标_RSV周期, self.随机指标_K值平滑周期, self.随机指标_D值平滑周期)] + + def _解析BOLL参数列表(self) -> List[tuple]: + if self.BOLL_参数列表: + return self.BOLL_参数列表 + return [("boll", self.布林带_周期, self.布林带_标准差倍数)] + + @classmethod + def 兼容旧版本配置(cls, values: Dict[str, Any]) -> Dict[str, Any]: + """过滤字典中不属于当前配置类的字段,实现前后版本兼容。 + + :param values: 包含配置键值对的原始字典 + :return: 仅保留当前类已知字段的字典 + """ + valid_fields = cls.model_fields().keys() + cleaned = {k: v for k, v in values.items() if k in valid_fields} + return cleaned def to_dict(self) -> dict: - """对象 → 字典""" - return self.model_dump() + """将配置序列化为字典。 + + :return: 包含所有配置字段的字典 + """ + return {k: getattr(self, k) for k in self.model_fields().keys()} def to_json(self) -> str: - """对象 → JSON字符串""" - return self.model_dump_json(ensure_ascii=False, indent=2) + """将配置序列化为 JSON 字符串。 + + :return: 格式化的 JSON 字符串 + """ + return json.dumps(self.to_dict(), ensure_ascii=False, indent=2) def 保存配置(self, path="缠论配置.json"): - """将配置保存为JSON文件 - :param path: 保存路径,默认"缠论配置.json" + """将配置保存为 JSON 文件。 + + :param path: 文件保存路径,默认 "缠论配置.json" """ with open(path, "w", encoding="utf-8") as f: f.write(self.to_json()) - @staticmethod - def 加载配置(path="缠论配置.json") -> "缠论配置": - """从JSON文件加载配置 - :param path: 配置文件路径 - :return: 缠论配置实例 + @classmethod + def 加载配置(cls, path="缠论配置.json") -> 缠论配置: + """从 JSON 文件加载配置。 + + :param path: JSON 文件路径,默认 "缠论配置.json" + :return: 从文件反序列化的配置对象 """ - with open(path, "r", encoding="utf-8") as f: - return 缠论配置.from_json(f.read()) + with open(path, encoding="utf-8") as f: + return cls.from_json(f.read()) @classmethod - def from_dict(cls, data: dict) -> "缠论配置": + def from_dict(cls, data: dict) -> 缠论配置: + """从字典构造配置对象。 + + :param data: 包含配置键值对的字典 + :return: 新的配置实例 """ - :param data: 字典数据 - :return: 缠论配置实例 - """ - return cls(**data) + cleaned_data = cls.兼容旧版本配置(data) + return cls(**cleaned_data) @classmethod - def from_json(cls, json_str: str) -> "缠论配置": - """ - :param json_str: JSON字符串 - :return: 缠论配置实例 + def from_json(cls, json_str: str) -> 缠论配置: + """从 JSON 字符串构造配置对象。 + + :param json_str: JSON 格式的配置字符串 + :return: 新的配置实例 """ data = json.loads(json_str) return cls.from_dict(data) @classmethod def 不推送(cls): - """创建不推送任何图表的静默配置(用于纯计算场景) + """创建不推送任何图表的静默配置。 - :return: 新缠论配置实例 + 所有图表推送开关关闭,适用于纯计算场景。 """ return cls( 线段内部中枢图显=False, @@ -631,83 +885,61 @@ class 缠论配置(BaseModel): 图表展示_中枢_线段内部=False, ) + def model_copy(self, update: dict = None, deep: bool = True): + """创建配置副本,可选覆盖部分字段。 + + :param update: 需要覆盖的字段字典 + :param deep: 是否深拷贝(保留参数兼容性,内部始终深拷贝) + :return: 新的配置实例 + """ + data = self.to_dict() + if update: + data.update(update) + return 缠论配置.from_dict(data) + @classmethod def 按序号重组字典(cls, 默认配置, 原始字典: dict) -> dict: - """ - 将形如 "1_open", "1_close", "2_open", "name" 的字典重组为嵌套结构 - { - "1_open": 10, - "1_close": 11, - "2_open": 20, - "name": "BTC", # 无法拆分 - "time": 123456 # 无法拆分 - } - 转化为 - { - 1: {"open": 10, "close": 11}, - 2: {"open": 20}, - "无法拆分": { - "name": "BTC", - "time": 123456 - } - } + """将带数字前缀的扁平字典重组成按序号索引的配置组。 - :param 默认配置: 默认配置实例 - :param 原始字典: 待重组的原始字典 - :return: 重组后的字典 + 键名格式如 "1_笔模式" 会拆分出序号 1,同序号的键归入同一子配置。 + 无法拆分序号前缀的键合并为通用配置。 + + :param 默认配置: 用作模板的默认配置对象 + :param 原始字典: 带数字前缀的扁平键值对字典 + :return: dict[int, 缠论配置] 按序号索引的配置组 """ 结果 = {} 无法拆分项 = {} for 复合键, 值 in 原始字典.items(): - # 尝试拆分 if "_" in 复合键: 序号部分, 键部分 = 复合键.split("_", 1) try: 序号 = int(序号部分) - # 能正常拆分 → 分组 if 序号 not in 结果: 结果[序号] = {} 结果[序号][键部分] = 值 except: - # 格式异常 → 单独处理 无法拆分项[复合键] = 值 else: - # 无下划线 → 无法拆分 → 单独存放 无法拆分项[复合键] = 值 - # 把无法拆分的也放进结果顶层(你要的结构) - """if 无法拆分项: - 结果["无法拆分"] = 无法拆分项""" 配置组 = dict() for k, v in 结果.items(): - 配置组[k] = 默认配置.model_copy( - update=v, - deep=True, - ) + 配置组[k] = 默认配置.model_copy(update=v, deep=True) return 配置组 - def 对比(self, other: "缠论配置") -> dict: - """ - 比较当前配置与另一个配置的差异 + def 对比(self, other: 缠论配置) -> dict: + """比较两个配置的差异。 - :param other: 另一个配置实例 - :return: { - "字段名": { - "旧值": 当前配置的值, - "新值": 另一个配置的值 - }, - ... - } - 仅当值不同时才包含该字段 + :param other: 要比较的另一个配置对象 + :return: 差异字段及其在新配置中的值 """ diff_dict = {} - # 获取所有字段名 - for field_name in self.model_fields.keys(): + for field_name in self.model_fields().keys(): old_value = getattr(self, field_name) new_value = getattr(other, field_name) - # 直接比较值(支持 None) if old_value != new_value: diff_dict[field_name] = new_value return diff_dict @@ -741,8 +973,11 @@ class 相对方向(Enum): def __repr__(self): return f"相对方向.{self.name}" - def 翻转(self) -> "相对方向": - """返回方向的对立面:向上↔向下, 向上缺口↔向下缺口, 顺↔逆, 衔接向上↔衔接向下, 同不变""" + def 翻转(self) -> 相对方向: + """返回方向的对立面。 + + :return: 对立方向 (向上↔向下, 向上缺口↔向下缺口, 顺↔逆, 衔接向上↔衔接向下, 同不变) + """ match self: case 相对方向.向上: return 相对方向.向下 @@ -764,27 +999,42 @@ class 相对方向(Enum): return self def 是否向上(self) -> bool: - """判断是否为向上方向(向上/向上缺口/衔接向上)""" + """判断是否为向上方向。 + + :return: 方向为 向上/向上缺口/衔接向上 时返回 True + """ return self in (相对方向.向上, 相对方向.向上缺口, 相对方向.衔接向上) def 是否向下(self) -> bool: - """判断是否为向下方向(向下/向下缺口/衔接向下)""" + """判断是否为向下方向。 + + :return: 方向为 向下/向下缺口/衔接向下 时返回 True + """ return self in (相对方向.向下, 相对方向.向下缺口, 相对方向.衔接向下) def 是否包含(self) -> bool: - """判断是否为包含关系(顺/逆/同)""" + """判断是否为包含关系。 + + :return: 方向为 顺/逆/同 时返回 True + """ return self in (相对方向.顺, 相对方向.逆, 相对方向.同) def 是否缺口(self) -> bool: - """判断是否有缺口(向下缺口/向上缺口)""" + """判断是否有缺口。 + + :return: 方向为 向下缺口/向上缺口 时返回 True + """ return self in (相对方向.向下缺口, 相对方向.向上缺口) def 是否衔接(self) -> bool: - """判断是否为首尾衔接(衔接向下/衔接向上)""" + """判断是否为首尾衔接。 + + :return: 方向为 衔接向下/衔接向上 时返回 True + """ return self in (相对方向.衔接向下, 相对方向.衔接向上) @classmethod - def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> "相对方向": + def 分析(cls, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: """分析两个价格区间(前、后)的相对位置关系。 :param 前高: 前方价格区间的最高价 @@ -820,9 +1070,13 @@ class 相对方向(Enum): class 分型结构(Enum): - """分型结构 — 描述三根K线构成的顶底分型形态。 + """描述三根K线构成的顶底分型形态。 - 类属性: 上, 下, 顶, 底, 散 + :ivar 上: 三连向上 — 左中右逐步抬高 + :ivar 下: 三连向下 — 左中右逐步走低 + :ivar 顶: 顶分型 — 左右低、中间高 + :ivar 底: 底分型 — 左右高、中间低 + :ivar 散: 向右扩散 — 无法判定 """ 上 = "三连向上" @@ -838,7 +1092,7 @@ class 分型结构(Enum): return self.name @classmethod - def 分析(cls, 左, 中, 右, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional["分型结构"]: + def 分析(cls, 左, 中, 右, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional[分型结构]: """分析左中右三个元素构成的分型形态。 :param 左: 左侧元素(必须有 高/低 属性) @@ -854,12 +1108,12 @@ class 分型结构(Enum): match (左中关系, 中右关系): case (相对方向.顺, _): if 忽视顺序包含: - ... # print("顺序包含 左中相对方向", 左, 中) + ... # logger.warning("顺序包含 左中相对方向", 左, 中) else: raise ValueError("顺序包含 左中相对方向", 左, 中) case (_, 相对方向.顺): if 忽视顺序包含: - ... # print("顺序包含 中右相对方向", 中, 右) + ... # logger.warning("顺序包含 中右相对方向", 中, 右) else: raise ValueError("顺序包含 中右相对方向", 中, 右) @@ -884,7 +1138,7 @@ class 分型结构(Enum): case (相对方向.逆, 相对方向.逆) if 可以逆序包含: return 分型结构.散 case _: - print("未匹配的关系", 可以逆序包含, 左中关系, 中右关系) + logger.warning(f"未匹配的关系 {可以逆序包含}, {左中关系}, {中右关系}") return None @@ -912,7 +1166,7 @@ class 缺口: return f"缺口区间<{self.低:g} <=> {self.高:g}>" @classmethod - def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional["缺口"]: + def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional[缺口]: """ 以原区间中心为基准,向两侧各取总长度的 `比例` 作为新区间。 如果新区间超出原边界则裁剪到边界;若完全越界则返回 None。 @@ -946,10 +1200,14 @@ class 缺口: class 指标: - """指标 — 静态工具类,提供K线取值的辅助方法。""" + """静态工具类,提供K线取值的辅助方法。 + + 主要方法: + :meth:`K线取值` — 根据计算方式从K线中取对应价格(开/高/低/收/均值) + """ @classmethod - def K线取值(cls, k线: "K线", 指标计算方式): + def K线取值(cls, k线: K线, 指标计算方式): """根据计算方式从K线中取值 :param k线: K线对象 @@ -975,51 +1233,59 @@ class 指标: return k线.收盘价 -class 平滑异同移动平均线(BaseModel): +class 平滑异同移动平均线: """平滑异同移动平均线(MACD)— 基于EMA快慢线差值的趋势指标。 - 计算字段(输入): - 时间戳: datetime / 收盘价: float - 快线周期: int (默认12) / 慢线周期: int (默认26) / 信号周期: int (默认9) + :ivar 时间戳: K线时间戳 + :ivar 收盘价: 当前收盘价 + :ivar 快线周期: 短期EMA周期(默认12) + :ivar 慢线周期: 长期EMA周期(默认26) + :ivar 信号周期: 信号线EMA周期(默认9) + :ivar EMA快线: 短期EMA值 + :ivar EMA慢线: 长期EMA值 + :ivar DIF: 快线减慢线 + :ivar DEA: DIF的信号线EMA + :ivar MACD柱子: DIF减DEA - 输出字段: - EMA快线: float — 短期EMA值 - EMA慢线: float — 长期EMA值 - DIF: float — 快线减慢线 - DEA: float — DIF的信号线EMA - MACD柱子: float — DIF减DEA + 主要方法: + :meth:`首次计算` / :meth:`增量计算` / :meth:`增量计算_K线` """ - # 原始数据 - 时间戳: datetime = Field(..., description="数据点对应的时间") - 收盘价: float = Field(..., description="当前K线的收盘价格") + def __init__( + self, + 时间戳: datetime, + 收盘价: float, + 快线周期: int = 12, + 慢线周期: int = 26, + 信号周期: int = 9, + DIF: Optional[float] = None, + DEA: Optional[float] = None, + MACD柱: Optional[float] = None, + 快线EMA: Optional[float] = None, + 慢线EMA: Optional[float] = None, + DEA_EMA: Optional[float] = None, + ): + # 原始数据 + self.时间戳 = 时间戳 + self.收盘价 = 收盘价 - # 计算参数 - 快线周期: int = Field(12, gt=0, description="短期EMA周期,默认为12") - 慢线周期: int = Field(26, gt=0, description="长期EMA周期,默认为26") - 信号周期: int = Field(9, gt=0, description="信号线EMA周期,默认为9") + # 计算参数 + self.快线周期 = 快线周期 + self.慢线周期 = 慢线周期 + self.信号周期 = 信号周期 - # 核心指标值 - DIF: Optional[float] = Field(None, description="差离值 = EMA(快线) - EMA(慢线)") - DEA: Optional[float] = Field(None, description="信号线 = EMA(DIF, 信号周期)") - MACD柱: Optional[float] = Field(None, description="MACD柱状图 = (DIF - DEA) * 2") + # 核心指标值 + self.DIF = DIF + self.DEA = DEA + self.MACD柱 = MACD柱 - # EMA中间值(用于增量计算) - 快线EMA: Optional[float] = Field(None, description="快线EMA的当前值") - 慢线EMA: Optional[float] = Field(None, description="慢线EMA的当前值") - DEA_EMA: Optional[float] = Field(None, description="DEA的EMA当前值") - - # 模型配置 - model_config = { - "arbitrary_types_allowed": True, # 允许特殊类型 - "json_encoders": { - datetime: lambda v: v.isoformat(), # 日期时间序列化 - Enum: lambda v: v.value, # 枚举值序列化 - }, - } + # EMA中间值(用于增量计算) + self.快线EMA = 快线EMA + self.慢线EMA = 慢线EMA + self.DEA_EMA = DEA_EMA @classmethod - def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> "平滑异同移动平均线": + def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> 平滑异同移动平均线: """ 首次计算MACD指标(没有历史数据时使用) @@ -1058,7 +1324,7 @@ class 平滑异同移动平均线(BaseModel): ) @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> "平滑异同移动平均线": + def 首次计算_K线(cls, k线: K线, 计算方式: str, 快线周期: int = 12, 慢线周期: int = 26, 信号周期: int = 9) -> 平滑异同移动平均线: """ :param k线: 原始K线 :param 计算方式: 指标计算方式(开/高/低/收/均值等) @@ -1072,7 +1338,7 @@ class 平滑异同移动平均线(BaseModel): return cls.首次计算(初始收盘价, 初始时间, 快线周期, 慢线周期, 信号周期) @classmethod - def 增量计算(cls, 前一个MACD: "平滑异同移动平均线", 当前收盘价: float, 当前时间: datetime) -> "平滑异同移动平均线": + def 增量计算(cls, 前一个MACD: 平滑异同移动平均线, 当前收盘价: float, 当前时间: datetime) -> 平滑异同移动平均线: """ 基于前一个MACD指标增量计算当前MACD指标 适用于实时交易系统或流式数据处理 @@ -1093,8 +1359,6 @@ class 平滑异同移动平均线(BaseModel): 快线EMA = 当前收盘价 raise RuntimeError else: - # 快线EMA = 当前收盘价 * 平滑系数(前一个MACD.快线周期) + 前一个MACD.快线EMA * (1 - 平滑系数(前一个MACD.快线周期)) - # 快线EMA = (当前收盘价 - 前一个MACD.快线EMA) * 平滑系数(前一个MACD.快线周期) + 前一个MACD.快线EMA 快线EMA = 当前收盘价 * 平滑系数(前一个MACD.快线周期) + 前一个MACD.快线EMA * ((前一个MACD.快线周期 - 1) / (前一个MACD.快线周期 + 1)) # 计算慢线EMA @@ -1102,8 +1366,6 @@ class 平滑异同移动平均线(BaseModel): 慢线EMA = 当前收盘价 raise RuntimeError else: - # 慢线EMA = 当前收盘价 * 平滑系数(前一个MACD.慢线周期) + 前一个MACD.慢线EMA * (1 - 平滑系数(前一个MACD.慢线周期)) - # 慢线EMA = (当前收盘价 - 前一个MACD.慢线EMA) * 平滑系数(前一个MACD.慢线周期) + 前一个MACD.慢线EMA 慢线EMA = 当前收盘价 * 平滑系数(前一个MACD.慢线周期) + 前一个MACD.慢线EMA * ((前一个MACD.慢线周期 - 1) / (前一个MACD.慢线周期 + 1)) # 计算DIF @@ -1113,8 +1375,6 @@ class 平滑异同移动平均线(BaseModel): if 前一个MACD.DEA_EMA is None: DEA_EMA = DIF else: - # DEA_EMA = 当前收盘价 * 平滑系数(前一个MACD.信号周期) + 前一个MACD.DEA_EMA * (1 - 平滑系数(前一个MACD.信号周期)) - # DEA_EMA = (DIF - 前一个MACD.DEA_EMA) * 平滑系数(前一个MACD.信号周期) + 前一个MACD.DEA_EMA DEA_EMA = DIF * 平滑系数(前一个MACD.信号周期) + 前一个MACD.DEA_EMA * ((前一个MACD.信号周期 - 1) / (前一个MACD.信号周期 + 1)) # 计算MACD柱 @@ -1135,7 +1395,7 @@ class 平滑异同移动平均线(BaseModel): ) @classmethod - def 增量计算_K线(cls, 前一个MACD: "平滑异同移动平均线", 当前K线: "K线", 计算方式: "str") -> "平滑异同移动平均线": + def 增量计算_K线(cls, 前一个MACD: 平滑异同移动平均线, 当前K线: K线, 计算方式: str) -> 平滑异同移动平均线: """ :param 前一个MACD: 前一个MACD指标对象 :param 当前K线: 当前K线 @@ -1147,51 +1407,74 @@ class 平滑异同移动平均线(BaseModel): return cls.增量计算(前一个MACD, 当前收盘价, 当前时间) -class 相对强弱指数(BaseModel): - """ - 相对强弱指数 (RSI) 指标 - 使用 Wilder 平滑(RMA)进行增量计算,提供完整的中间平滑值, - 并支持对RSI值计算SMA(简单移动平均) +class 相对强弱指数: + """相对强弱指数(RSI)— 使用 Wilder 平滑(RMA)进行增量计算。 + + :ivar 时间戳: K线时间戳 + :ivar 收盘价: 当前收盘价 + :ivar 周期: RSI计算周期(默认14) + :ivar 超买阈值: 超买线(默认70.0) + :ivar 超卖阈值: 超卖线(默认30.0) + :ivar RSI: 当前RSI值 + :ivar RSI_SMA周期: RSI的SMA平滑周期(可选) + :ivar RSI_SMA: RSI的SMA值 + :ivar 平均上涨: Wilder平均上涨幅度 + :ivar 平均下跌: Wilder平均下跌幅度 + + 主要方法: + :meth:`首次计算` / :meth:`增量计算` / :meth:`增量计算_K线` """ - # 原始数据 - 时间戳: datetime = Field(..., description="数据点对应的时间") - 收盘价: float = Field(..., description="当前K线的收盘价格") + def __init__( + self, + 时间戳: datetime, + 收盘价: float, + 周期: int = 14, + 超买阈值: float = 70.0, + 超卖阈值: float = 30.0, + RSI_SMA周期: Optional[int] = None, + RSI: Optional[float] = None, + 平均上涨: Optional[float] = None, + 平均下跌: Optional[float] = None, + 上涨幅度: float = 0.0, + 下跌幅度: float = 0.0, + 平滑系数: float = 0.0, + RSI_SMA: Optional[float] = None, + RSI历史队列: List[float] = None, + ): + if RSI历史队列 is None: + RSI历史队列 = [] - # 参数 - 周期: int = Field(14, gt=0, description="RSI周期,默认为14") - 超买阈值: float = Field(70.0, description="超买阈值") - 超卖阈值: float = Field(30.0, description="超卖阈值") - RSI_SMA周期: Optional[int] = Field(None, description="RSI的SMA周期(可选),用于生成信号线") + # 原始数据 + self.时间戳 = 时间戳 + self.收盘价 = 收盘价 - # 核心指标值 - RSI: Optional[float] = Field(None, description="当前RSI值") + # 参数 + self.周期 = 周期 + self.超买阈值 = 超买阈值 + self.超卖阈值 = 超卖阈值 + self.RSI_SMA周期 = RSI_SMA周期 - # 中间平滑值(Wilder平滑) - 平均上涨: Optional[float] = Field(None, description="平均上涨幅度的平滑值") - 平均下跌: Optional[float] = Field(None, description="平均下跌幅度的平滑值") + # 核心指标值 + self.RSI = RSI - # 原始变化值(用于调试) - 上涨幅度: float = Field(0.0, description="当前价格变化中的上涨部分") - 下跌幅度: float = Field(0.0, description="当前价格变化中的下跌部分") + # 中间平滑值(Wilder平滑) + self.平均上涨 = 平均上涨 + self.平均下跌 = 平均下跌 - # 平滑系数(α = 1/周期) - 平滑系数: float = Field(0.0, description="Wilder平滑系数") + # 原始变化值(用于调试) + self.上涨幅度 = 上涨幅度 + self.下跌幅度 = 下跌幅度 - # RSI的SMA(信号线)相关字段 - RSI_SMA: Optional[float] = Field(None, description="RSI的简单移动平均值") - RSI历史队列: List[float] = Field(default_factory=list, description="用于计算SMA的RSI历史队列") + # 平滑系数(α = 1/周期) + self.平滑系数 = 平滑系数 - model_config = { - "arbitrary_types_allowed": True, - "json_encoders": { - datetime: lambda v: v.isoformat(), - Enum: lambda v: v.value, - }, - } + # RSI的SMA(信号线)相关字段 + self.RSI_SMA = RSI_SMA + self.RSI历史队列 = RSI历史队列 @classmethod - def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> "相对强弱指数": + def 首次计算(cls, 初始收盘价: float, 初始时间: datetime, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: """ 首次计算RSI(没有足够历史数据时使用) 此时无法计算真实RSI,设为 None,但记录初始收盘价作为起点 @@ -1222,7 +1505,7 @@ class 相对强弱指数(BaseModel): ) @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> "相对强弱指数": + def 首次计算_K线(cls, k线: K线, 计算方式: str, 周期: int = 14, 超买阈值: float = 70.0, 超卖阈值: float = 30.0, RSI_SMA周期: Optional[int] = None) -> 相对强弱指数: """ :param k线: 原始K线 :param 计算方式: 指标计算方式 @@ -1237,7 +1520,7 @@ class 相对强弱指数(BaseModel): return cls.首次计算(初始收盘价, 初始时间, 周期, 超买阈值, 超卖阈值, RSI_SMA周期) @classmethod - def 增量计算(cls, 前一个RSI: "相对强弱指数", 当前收盘价: float, 当前时间: datetime) -> "相对强弱指数": + def 增量计算(cls, 前一个RSI: 相对强弱指数, 当前收盘价: float, 当前时间: datetime) -> 相对强弱指数: """ 基于前一个RSI指标增量计算当前RSI 支持可选的RSI_SMA(简单移动平均) @@ -1308,7 +1591,7 @@ class 相对强弱指数(BaseModel): ) @classmethod - def 增量计算_K线(cls, 前一个RSI: "相对强弱指数", 当前K线: "K线", 计算方式: "str") -> "相对强弱指数": + def 增量计算_K线(cls, 前一个RSI: 相对强弱指数, 当前K线: K线, 计算方式: str) -> 相对强弱指数: """ :param 前一个RSI: 前一个RSI指标对象 :param 当前K线: 当前K线 @@ -1320,49 +1603,81 @@ class 相对强弱指数(BaseModel): return cls.增量计算(前一个RSI, 当前收盘价, 当前时间) -class 随机指标(BaseModel): - """ - KDJ 随机指标 (Stochastic Oscillator) - 使用标准参数:N=9, M1=3, M2=3 - 支持增量计算,需提供当前K线的最高价、最低价、收盘价 +class 随机指标: + """KDJ 随机指标(Stochastic Oscillator)— 基于RSV的三线指标。 + + :ivar 时间戳: K线时间戳 + :ivar 最高价: 当前最高价 + :ivar 最低价: 当前最低价 + :ivar 收盘价: 当前收盘价 + :ivar N: RSV计算周期(默认9) + :ivar M1: K值平滑周期(默认3) + :ivar M2: D值平滑周期(默认3) + :ivar 超买阈值: 超买线(默认80.0) + :ivar 超卖阈值: 超卖线(默认20.0) + :ivar RSV: 未成熟随机值 + :ivar K: K值(RSV的M1周期移动平均) + :ivar D: D值(K的M2周期移动平均) + :ivar J: J值(3K - 2D) + + 主要方法: + :meth:`首次计算` / :meth:`增量计算` / :meth:`增量计算_K线` """ - # 原始数据 - 时间戳: datetime = Field(..., description="数据点对应的时间") - 最高价: float = Field(..., description="当前K线的最高价") - 最低价: float = Field(..., description="当前K线的最低价") - 收盘价: float = Field(..., description="当前K线的收盘价") + def __init__( + self, + 时间戳: datetime, + 最高价: float, + 最低价: float, + 收盘价: float, + N: int = 9, + M1: int = 3, + M2: int = 3, + 超买阈值: float = 80.0, + 超卖阈值: float = 20.0, + RSV: Optional[float] = None, + K: Optional[float] = None, + D: Optional[float] = None, + J: Optional[float] = None, + 历史最高价队列: list[float] = None, + 历史最低价队列: list[float] = None, + 前一个RSV: Optional[float] = None, + 前一个K: Optional[float] = None, + 前一个D: Optional[float] = None, + ): + if 历史最高价队列 is None: + 历史最高价队列 = [] + if 历史最低价队列 is None: + 历史最低价队列 = [] - # 参数 - N: int = Field(9, ge=1, description="RSV的周期(取最近N根K线)") - M1: int = Field(3, ge=1, description="K值的平滑周期") - M2: int = Field(3, ge=1, description="D值的平滑周期") - 超买阈值: float = Field(80.0, description="超买阈值") - 超卖阈值: float = Field(20.0, description="超卖阈值") + # 原始数据 + self.时间戳 = 时间戳 + self.最高价 = 最高价 + self.最低价 = 最低价 + self.收盘价 = 收盘价 - # 核心指标值 - RSV: Optional[float] = Field(None, description="未成熟随机值") - K: Optional[float] = Field(None, description="K值(快速随机指标)") - D: Optional[float] = Field(None, description="D值(慢速随机指标)") - J: Optional[float] = Field(None, description="J值 = 3K - 2D") + # 参数 + self.N = N + self.M1 = M1 + self.M2 = M2 + self.超买阈值 = 超买阈值 + self.超卖阈值 = 超卖阈值 - # 中间状态(用于增量计算) - 历史最高价队列: list[float] = Field(default_factory=list, description="最近N根K线的最高价队列") - 历史最低价队列: list[float] = Field(default_factory=list, description="最近N根K线的最低价队列") - 前一个RSV: Optional[float] = Field(None, description="上一个RSV值(用于平滑K)") - 前一个K: Optional[float] = Field(None, description="上一个K值") - 前一个D: Optional[float] = Field(None, description="上一个D值") + # 核心指标值 + self.RSV = RSV + self.K = K + self.D = D + self.J = J - model_config = { - "arbitrary_types_allowed": True, - "json_encoders": { - datetime: lambda v: v.isoformat(), - Enum: lambda v: v.value, - }, - } + # 中间状态(用于增量计算) + self.历史最高价队列 = 历史最高价队列 + self.历史最低价队列 = 历史最低价队列 + self.前一个RSV = 前一个RSV + self.前一个K = 前一个K + self.前一个D = 前一个D @classmethod - def 首次计算(cls, 初始最高价: float, 初始最低价: float, 初始收盘价: float, 初始时间: datetime, N: int = 9, M1: int = 3, M2: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> "随机指标": + def 首次计算(cls, 初始最高价: float, 初始最低价: float, 初始收盘价: float, 初始时间: datetime, N: int = 9, M1: int = 3, M2: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> 随机指标: """ 首次计算KDJ(无历史数据时) 此时无法计算RSV和K/D/J,仅记录初始三价,初始化队列 @@ -1401,7 +1716,7 @@ class 随机指标(BaseModel): ) @classmethod - def 首次计算_K线(cls, k线: "K线", 计算方式: str, RSV周期: int = 9, K值平滑周期: int = 3, D值平滑周期: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> "随机指标": + def 首次计算_K线(cls, k线: K线, 计算方式: str, RSV周期: int = 9, K值平滑周期: int = 3, D值平滑周期: int = 3, 超买阈值: float = 80.0, 超卖阈值: float = 20.0) -> 随机指标: """ :param k线: 原始K线 :param 计算方式: 指标计算方式(未使用,仅为接口一致) @@ -1419,7 +1734,7 @@ class 随机指标(BaseModel): return cls.首次计算(初始最高价, 初始最低价, 初始收盘价, 初始时间, RSV周期, K值平滑周期, D值平滑周期, 超买阈值, 超卖阈值) @classmethod - def 增量计算(cls, 前一个KDJ: "随机指标", 当前最高价: float, 当前最低价: float, 当前收盘价: float, 当前时间: datetime) -> "随机指标": + def 增量计算(cls, 前一个KDJ: 随机指标, 当前最高价: float, 当前最低价: float, 当前收盘价: float, 当前时间: datetime) -> 随机指标: """ 基于前一个KDJ对象和当前三价,增量计算当前KDJ值 @@ -1509,7 +1824,7 @@ class 随机指标(BaseModel): ) @classmethod - def 增量计算_K线(cls, 前一个KDJ: "随机指标", 当前K线: "K线", 计算方式: "str") -> "随机指标": + def 增量计算_K线(cls, 前一个KDJ: 随机指标, 当前K线: K线, 计算方式: str) -> 随机指标: """ :param 前一个KDJ: 前一个KDJ指标对象 :param 当前K线: 当前K线 @@ -1523,19 +1838,329 @@ class 随机指标(BaseModel): return cls.增量计算(前一个KDJ, 当前最高价, 当前最低价, 当前收盘价, 当前时间) -class 背驰分析: - """背驰分析 — 静态方法容器,提供背驰/背离检测算法。 +class 布林带: + """布林带(BOLL)— 基于移动平均和标准差的波动率通道。 - 方法: - MACD背驰(进入段, 离开段, K线序列, 方式?) — MACD柱状线面积背驰 - 斜率背驰(进入段, 离开段, 序列, 配置?) — 线段斜率背驰 - 测度背驰(进入段, 离开段, 序列, 配置?) — 线段测度背驰 - 全量背驰(进入段, 离开段, ...) — 综合所有背驰检测方式 - 配置背驰(进入段, 离开段, ...) — 根据配置选择检测方式 + :ivar 时间戳: K线时间戳 + :ivar 周期: 移动平均周期(默认20) + :ivar 标准差倍数: 通道宽度倍数(默认2.0) + :ivar 上轨: 中轨 + 标准差倍数 × 标准差 + :ivar 中轨: 简单移动平均 + :ivar 下轨: 中轨 - 标准差倍数 × 标准差 + + 主要方法: + :meth:`首次计算` / :meth:`增量计算` + """ + + __slots__ = ["时间戳", "周期", "标准差倍数", "上轨", "中轨", "下轨", "_历史队列", "_均值", "_方差和"] + + def __init__(self, 时间戳: datetime, 周期: int = 20, 标准差倍数: float = 2.0, 上轨: float = 0.0, 中轨: float = 0.0, 下轨: float = 0.0, 历史队列: Optional[List[float]] = None, _均值: float = 0.0, _方差和: float = 0.0): + self.时间戳 = 时间戳 + self.周期 = 周期 + self.标准差倍数 = 标准差倍数 + self.上轨 = 上轨 + self.中轨 = 中轨 + self.下轨 = 下轨 + self._历史队列 = 历史队列 if 历史队列 is not None else [] + self._均值 = _均值 + self._方差和 = _方差和 + + @classmethod + def 首次计算(cls, k线: K线, 计算方式: str, 周期: int = 20, 标准差倍数: float = 2.0) -> 布林带: + """首次计算布林带,三条轨线初始化为当前价格。 + + :param k线: 当前K线 + :param 计算方式: 取值方式("收"/"高低均值" 等) + :param 周期: 移动平均周期 + :param 标准差倍数: 标准差倍数 + :return: 初始的布林带实例 + """ + 价格 = 指标.K线取值(k线, 计算方式) + return cls(时间戳=k线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 上轨=价格, 中轨=价格, 下轨=价格, 历史队列=[价格]) + + @classmethod + def 增量计算(cls, prev: 布林带, 当前K线: K线, 计算方式: str) -> 布林带: + """基于前一个布林带实例增量计算当前布林带。 + + :param prev: 前一个布林带实例 + :param 当前K线: 当前K线 + :param 计算方式: 取值方式 + :return: 新的布林带实例 + """ + 当前价 = 指标.K线取值(当前K线, 计算方式) + 周期 = prev.周期 + 标准差倍数 = prev.标准差倍数 + + q = prev._历史队列.copy() + q.append(当前价) + if len(q) > 周期: + q.pop(0) + + # 增量均值和方差 + if len(q) < 周期: + _均值 = sum(q) / len(q) + _方差和 = sum((v - _均值) ** 2 for v in q) + else: + n = float(周期) + old_val = prev._历史队列[0] if len(prev._历史队列) >= 周期 else q[0] + new_mean = prev._均值 + (当前价 - old_val) / n + _均值 = new_mean + # Welford 增量方差 + _方差和 = prev._方差和 + (当前价 - old_val) * (当前价 - _均值 + old_val - prev._均值) + + std = (_方差和 / len(q)) ** 0.5 + return cls(时间戳=当前K线.时间戳, 周期=周期, 标准差倍数=标准差倍数, 中轨=_均值, 上轨=_均值 + 标准差倍数 * std, 下轨=_均值 - 标准差倍数 * std, 历史队列=q, _均值=_均值, _方差和=_方差和) + + +class 指标容器: + """挂载在每根K线上,持有该时刻所有指标的快照与增量计算内部状态。 + + 基于注册表模式,所有指标统一存储在内部 dict,通过 ``__getattr__`` / ``__setattr__`` 访问。 + 运行中可随时添加新指标,无需修改类定义。同指标不同参数通过 key 区分 + (如 ``k线.指标.均线["SMA_5"]`` vs ``k线.指标.均线["SMA_20"]``)。 + + :ivar macd: MACD指标快照 + :ivar rsi: RSI指标快照 + :ivar kdj: KDJ指标快照 + :ivar boll: BOLL指标快照 + :ivar 均线: 均线组 dict — key 格式 ``"{类型}_{周期}"``(如 ``"SMA_5"`` / ``"EMA_20"``) + :ivar 单值: 单值指标 dict — key 格式 ``"{名称}_{周期}"``(如 ``"CCI_14"`` / ``"ATR_14"``) + + 主要方法: + :meth:`获取` — 读取指标值,不存在时返回默认值 + :meth:`设置默认值` — 指标不存在时写入默认值 + :meth:`项目` — 返回所有非None指标的迭代器 + """ + + __slots__ = ["_数据"] + + def __init__(self): + object.__setattr__( + self, + "_数据", + { + "macd": None, + "rsi": None, + "kdj": None, + "boll": None, + "均线": {}, + "单值": {}, + }, + ) + + def __getattr__(self, name: str): + _数据 = object.__getattribute__(self, "_数据") + if name not in _数据: + raise AttributeError(f"指标 '{name}' 不存在于 指标容器 中") + return _数据[name] + + def __setattr__(self, name: str, value): + if name == "_数据": + object.__setattr__(self, name, value) + else: + object.__getattribute__(self, "_数据")[name] = value + + def __delattr__(self, name: str): + if name == "_数据": + object.__delattr__(self, name) + else: + object.__getattribute__(self, "_数据").pop(name, None) + + def __getitem__(self, name: str): + return self.__getattr__(name) + + def __setitem__(self, name: str, value): + self.__setattr__(name, value) + + def __contains__(self, name: str) -> bool: + return name in object.__getattribute__(self, "_数据") + + def __repr__(self) -> str: + _数据 = object.__getattribute__(self, "_数据") + keys = [k for k in _数据 if _数据[k] is not None] + return f"指标容器({', '.join(keys)})" + + def 注册(self, 名称: str, 默认值=None): + """预注册指标,让 __getattr__ 能查询到该指标,不存在时返回默认值而不抛出异常。 + + :param 名称: 指标名称 + :param 默认值: 指标不存在时返回的默认值 + """ + _数据 = object.__getattribute__(self, "_数据") + if 名称 not in _数据: + _数据[名称] = 默认值 + + def 项目(self): + """返回所有已注册的非 None 指标的 (名称, 值) 迭代器,供调试/遍历。""" + return ((k, v) for k, v in object.__getattribute__(self, "_数据").items() if v is not None) + + +class 均线工具: + """增量 SMA/EMA 计算的静态方法容器。 + + 主要方法: + :meth:`增量SMA` — 基于前一根K线的SMA值增量计算当前SMA + :meth:`增量EMA` — 用前一根K线的EMA值递推计算当前EMA """ @staticmethod - def MACD背驰(进入段: "虚线", 离开段: "虚线", K线序列: List["K线"], 方式: str = "总") -> bool: + def 增量SMA(普K序列: List[K线], period: int, 计算方式: str) -> float: + """基于前一根K线的 SMA 值,增量计算当前 SMA。 + + :param 普K序列: 原始K线序列(最近 period+1 根至少) + :param period: 移动平均周期 + :param 计算方式: 取值方式("收"/"高低均值" 等) + :return: 当前 SMA 值 + """ + if len(普K序列) <= period: + return sum(指标.K线取值(k, 计算方式) for k in 普K序列[-period:]) / max(len(普K序列), 1) + prev_key = f"SMA_{period}" + prev = 普K序列[-2].指标.均线.get(prev_key) + if prev is None: + 当前价序列 = [指标.K线取值(k, 计算方式) for k in 普K序列[-period:]] + return sum(当前价序列) / period + 当前价 = 指标.K线取值(普K序列[-1], 计算方式) + oldest = 指标.K线取值(普K序列[-period - 1], 计算方式) + return prev + (当前价 - oldest) / period + + @staticmethod + def 增量EMA(普K序列: List[K线], period: int, 计算方式: str, 前值: Optional[float] = None) -> float: + """用前一根K线的 EMA 值递推计算当前 EMA。 + + :param 普K序列: 原始K线序列 + :param period: 平滑周期 + :param 计算方式: 取值方式("收"/"高低均值" 等) + :param 前值: 前一根K线已缓存的 EMA 值,None 则自动查找 + :return: 当前 EMA 值 + """ + 当前价 = 指标.K线取值(普K序列[-1], 计算方式) + if 前值 is None: + return 当前价 + k = 2.0 / (period + 1) + return 当前价 * k + 前值 * (1.0 - k) + + +class 指标计算器: + """在缠K合并之前,增量计算所有开启的指标并挂载到K线上。 + + 遍历 :class:`缠论配置` 中的参数列表,支持同一指标多种参数变体共存。 + 参数列表为空时自动回退到默认单参数(向后兼容)。 + + 主要方法: + :meth:`计算并挂载` — 增量计算所有开启的指标,将结果写入 ``当前K线.指标`` + """ + + @staticmethod + def 计算并挂载(当前K线: K线, 全序列: List[K线], 配置: 缠论配置): + """增量计算所有开启的指标,将结果写入 当前K线.指标 + + :param 当前K线: 当前传入的K线(已在全序列末尾) + :param 全序列: 完整普K序列(包含当前K线) + :param 配置: 缠论配置 + """ + if 当前K线.指标 is None: + 当前K线.指标 = 指标容器() + + # 全序列[-2] 一定是不同时间戳的前一根K线 + # (同时间戳更新时,[-1]已被替换为新值,[-2]即为前一不同时间戳) + prev = 全序列[-2].指标 if len(全序列) > 1 else None + + if 配置.计算指标: + 指标计算器._计算MACD组(当前K线, prev, 配置) + 指标计算器._计算RSI组(当前K线, prev, 配置) + 指标计算器._计算KDJ组(当前K线, prev, 配置) + 指标计算器._计算BOLL组(当前K线, prev, 配置) + + 指标计算器._更新均线(当前K线, 全序列, 配置) + + @staticmethod + def _计算MACD组(当前K线: K线, prev: Optional[指标容器], 配置: 缠论配置): + idx = 当前K线.指标 + 计算方式 = 配置.指标计算方式 + for key, 快, 慢, 信号 in 配置._解析MACD参数列表(): + prev_val = prev[key] if prev is not None and key in prev else None + if prev_val is not None: + idx[key] = 平滑异同移动平均线.增量计算_K线(prev_val, 当前K线, 计算方式) + else: + idx[key] = 平滑异同移动平均线.首次计算_K线(当前K线, 计算方式, 快, 慢, 信号) + + @staticmethod + def _计算RSI组(当前K线: K线, prev: Optional[指标容器], 配置: 缠论配置): + idx = 当前K线.指标 + 计算方式 = 配置.指标计算方式 + for key, 周期 in 配置._解析RSI周期列表(): + prev_val = prev[key] if prev is not None and key in prev else None + if prev_val is not None: + idx[key] = 相对强弱指数.增量计算_K线(prev_val, 当前K线, 计算方式) + else: + idx[key] = 相对强弱指数.首次计算_K线( + 当前K线, + 计算方式, + 周期, + 配置.相对强弱指数_超买阈值, + 配置.相对强弱指数_超卖阈值, + 配置.相对强弱指数_移动平均线周期, + ) + + @staticmethod + def _计算KDJ组(当前K线: K线, prev: Optional[指标容器], 配置: 缠论配置): + idx = 当前K线.指标 + 计算方式 = 配置.指标计算方式 + for key, rsv, k平滑, d平滑 in 配置._解析KDJ参数列表(): + prev_val = prev[key] if prev is not None and key in prev else None + if prev_val is not None: + idx[key] = 随机指标.增量计算_K线(prev_val, 当前K线, 计算方式) + else: + idx[key] = 随机指标.首次计算_K线( + 当前K线, + 计算方式, + rsv, + k平滑, + d平滑, + 配置.随机指标_超买阈值, + 配置.随机指标_超卖阈值, + ) + + @staticmethod + def _计算BOLL组(当前K线: K线, prev: Optional[指标容器], 配置: 缠论配置): + idx = 当前K线.指标 + 计算方式 = 配置.指标计算方式 + for key, 周期, 标准差倍数 in 配置._解析BOLL参数列表(): + prev_val = prev[key] if prev is not None and key in prev else None + if prev_val is not None: + idx[key] = 布林带.增量计算(prev_val, 当前K线, 计算方式) + else: + idx[key] = 布林带.首次计算(当前K线, 计算方式, 周期, 标准差倍数) + + @staticmethod + def _更新均线(当前K线: K线, 普K序列: List[K线], 配置: 缠论配置): + if not 配置.均线_类型列表 or not 配置.均线_周期列表: + return + for ma_type in 配置.均线_类型列表: + for period in 配置.均线_周期列表: + key = f"{ma_type}_{period}" + if ma_type == "SMA": + 当前K线.指标.均线[key] = 均线工具.增量SMA(普K序列, period, 配置.指标计算方式) + elif ma_type == "EMA": + 前值 = None + if len(普K序列) >= 2: + 前值 = 普K序列[-2].指标.均线.get(key) + 当前K线.指标.均线[key] = 均线工具.增量EMA(普K序列, period, 配置.指标计算方式, 前值) + + +class 背驰分析: + """静态方法容器,提供背驰/背离检测算法。 + + 主要方法: + :meth:`MACD背驰` — MACD柱状线面积背驰 + :meth:`斜率背驰` — 线段斜率背驰 + :meth:`测度背驰` — 线段测度背驰 + :meth:`全量背驰` — 综合所有背驰检测方式 + :meth:`配置背驰` — 根据配置选择检测方式 + """ + + @staticmethod + def MACD背驰(进入段: 虚线, 离开段: 虚线, K线序列: List[K线], 方式: str = "总") -> bool: """MACD柱状线面积背驰 :param 进入段: 进入中枢的虚线 @@ -1554,7 +2179,7 @@ class 背驰分析: return 离开面积 < 进入面积 @staticmethod - def 斜率背驰(进入段: "虚线", 离开段: "虚线") -> bool: + def 斜率背驰(进入段: 虚线, 离开段: 虚线) -> bool: """价格斜率背驰 :param 进入段: 进入中枢的虚线 @@ -1579,7 +2204,7 @@ class 背驰分析: return False @staticmethod - def 测度背驰(进入段: "虚线", 离开段: "虚线") -> bool: + def 测度背驰(进入段: 虚线, 离开段: 虚线) -> bool: """价格测度背驰(欧氏距离) :param 进入段: 进入中枢的虚线 @@ -1603,7 +2228,7 @@ class 背驰分析: return False @staticmethod - def 全量背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: + def 全量背驰(进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: """判断是否满足全部三种背驰条件(MACD + 测度 + 斜率) :param 进入段: 进入中枢的线段 @@ -1614,7 +2239,7 @@ class 背驰分析: return all([背驰分析.MACD背驰(进入段, 离开段, 普K序列), 背驰分析.测度背驰(进入段, 离开段), 背驰分析.斜率背驰(进入段, 离开段)]) @staticmethod - def 任意背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: + def 任意背驰(进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: """判断是否满足任一背驰条件 :param 进入段: 进入中枢的线段 @@ -1625,7 +2250,7 @@ class 背驰分析: return any([背驰分析.MACD背驰(进入段, 离开段, 普K序列), 背驰分析.测度背驰(进入段, 离开段), 背驰分析.斜率背驰(进入段, 离开段)]) @staticmethod - def 配置背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"], 配置: 缠论配置) -> bool: + def 配置背驰(进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置) -> bool: """根据配置选择对应的背驰检测组合 :param 进入段: 进入中枢的线段 @@ -1658,7 +2283,7 @@ class 背驰分析: return False @staticmethod - def 任选背驰(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"]) -> bool: + def 任选背驰(进入段: 虚线, 离开段: 虚线, 普K序列: List[K线]) -> bool: """三个背驰条件中至少两个满足即视为背驰 :param 进入段: 进入中枢的线段 @@ -1670,7 +2295,7 @@ class 背驰分析: return len([背驰 for 背驰 in 混沌槽 if 背驰]) >= 2 @staticmethod - def 背驰模式(进入段: "虚线", 离开段: "虚线", 普K序列: List["K线"], 配置: 缠论配置, 模式: str) -> bool: + def 背驰模式(进入段: 虚线, 离开段: 虚线, 普K序列: List[K线], 配置: 缠论配置, 模式: str) -> bool: """根据模式字符串选择背驰检测策略 :param 进入段: 进入中枢的线段 @@ -1692,8 +2317,8 @@ class 背驰分析: return False -class K线(object): - """原始 K 线 — OHLCV 数据,可内嵌 MACD/RSI/KDJ 指标。 +class K线: + """原始 K 线 — OHLCV 数据,通过 :class:`指标容器` 挂载所有技术指标。 :ivar 标识: 标识符 :ivar 序号: 序号 @@ -1704,13 +2329,22 @@ class K线(object): :ivar 开盘价: 开盘价 :ivar 收盘价: 收盘价 :ivar 成交量: 成交量 - :ivar macd: MACD指标对象 - :ivar rsi: RSI指标对象 - :ivar kdj: KDJ指标对象 + :ivar 指标: 指标容器(含 macd/rsi/kdj/boll/均线/单值) :ivar 方向: 涨跌方向(只读) + + 构造: + :meth:`创建普K` — 创建原始K线实例 + + 序列化: + :meth:`保存到DAT文件` — 将K线序列批量保存为DAT文件 + :meth:`读取大端字节数组` — 从大端字节数组解析K线 + + 工具: + :meth:`获取MACD` — 计算指定区间内的MACD柱子面积 + :meth:`截取` — 截取起始K线到终止K线之间的子序列 """ - __slots__ = ["标识", "序号", "周期", "时间戳", "高", "低", "开盘价", "收盘价", "成交量", "macd", "rsi", "kdj"] + __slots__ = ["标识", "序号", "周期", "时间戳", "高", "低", "开盘价", "收盘价", "成交量", "指标"] def __init__( self, @@ -1723,9 +2357,6 @@ class K线(object): 最低价: float, 收盘价: float, 成交量: float, - macd: 平滑异同移动平均线 = None, - rsi: 相对强弱指数 = None, - kdj: 随机指标 = None, ): """ :param 标识: K线标识符 @@ -1737,9 +2368,9 @@ class K线(object): :param 最低价: 最低价 :param 收盘价: 收盘价 :param 成交量: 成交量 - :param macd: MACD指标对象(可选) - :param rsi: RSI指标对象(可选) - :param kdj: KDJ指标对象(可选) + :param macd: MACD指标对象(可选,存入 指标容器) + :param rsi: RSI指标对象(可选,存入 指标容器) + :param kdj: KDJ指标对象(可选,存入 指标容器) """ self.序号: int = 序号 self.标识: str = 标识 @@ -1750,9 +2381,32 @@ class K线(object): self.收盘价: float = 收盘价 self.成交量: float = 成交量 self.周期: int = 周期 - self.macd: 平滑异同移动平均线 = macd - self.rsi: 相对强弱指数 = rsi - self.kdj: 随机指标 = kdj + self.指标: 指标容器 = 指标容器() + + # ---- property 兼容层:k线.macd → k线.指标.macd ---- + @property + def macd(self) -> Optional[平滑异同移动平均线]: + """该K线绑定的 MACD 指标快照。 + + :return: MACD 实例,未计算时返回 None + """ + return self.指标.macd + + @property + def rsi(self) -> Optional[相对强弱指数]: + """该K线绑定的 RSI 指标快照。 + + :return: RSI 实例,未计算时返回 None + """ + return self.指标.rsi + + @property + def kdj(self) -> Optional[随机指标]: + """该K线绑定的 KDJ 指标快照。 + + :return: KDJ 实例,未计算时返回 None + """ + return self.指标.kdj def __str__(self): return f"{self.标识}<{self.序号}, {self.周期}, {self.方向}, {self.时间戳}, {self.开盘价:g}, {self.高:g}, {self.低:g}, {self.收盘价:g}>" @@ -1779,7 +2433,7 @@ class K线(object): ) @classmethod - def 创建普K(cls, 标识: str, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: int, 周期: int) -> "K线": + def 创建普K(cls, 标识: str, 时间戳: datetime, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 序号: int, 周期: int) -> K线: """快捷构造普通K线 :param 标识: K线标识符 @@ -1807,7 +2461,7 @@ class K线(object): return k线 @classmethod - def 保存到DAT文件(cls, 路径: str, K线序列: List["K线"]): + def 保存到DAT文件(cls, 路径: str, K线序列: List[K线]): """将K线序列保存为二进制DAT文件 :param 路径: 保存路径 @@ -1816,10 +2470,10 @@ class K线(object): with open(路径, "wb") as f: for K in K线序列: f.write(bytes(K)) - print(f"保存到DAT文件: {路径}") + logger.info(f"保存到DAT文件: {路径}") @classmethod - def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> "K线": + def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> K线: """从大端字节序二进制数据反序列化K线(兼容.dat/.nb文件格式) :param 字节组: 二进制数据(48字节) @@ -1842,7 +2496,7 @@ class K线(object): ) @classmethod - def 获取MACD(cls, K线序列: List["K线"], 始: "K线", 终: "K线") -> Dict[str, float]: + def 获取MACD(cls, K线序列: List[K线], 始: K线, 终: K线) -> Dict[str, float]: """计算指定K线区间的MACD柱面积 :param K线序列: 完整K线序列 @@ -1865,7 +2519,7 @@ class K线(object): return {"阳": 阳, "阴": 阴, "合": 合, "总": 阳 + abs(阴)} @staticmethod - def 截取(序列: List["K线"], 始: "K线", 终: "K线") -> List["K线"]: + def 截取(序列: List[K线], 始: K线, 终: K线) -> List[K线]: """按起止K线截取K线子序列 :param 序列: 完整K线序列 @@ -1876,8 +2530,8 @@ class K线(object): return 序列[序列.index(始) : 序列.index(终) + 1] -class 缠论K线(object): - """缠论K线 — 经包含处理后的标准化K线,有方向和分型结构标记。 +class 缠论K线: + """经包含处理后的标准化K线,有方向和分型结构标记。 :ivar 序号: 序号 :ivar 时间戳: 时间戳 @@ -1892,6 +2546,20 @@ class 缠论K线(object): :ivar 原始结束序号: 合并前的原始K线结束序号 :ivar 标的K线: 对应的原始K线 :ivar 买卖点信息: 买卖点信息(预留) + :ivar 镜像: 镜像缠K(底↔顶、上↔下,只读) + :ivar 与MACD柱子匹配: 是否与MACD柱子方向匹配(只读) + :ivar 与RSI匹配: 是否与RSI超买超卖匹配(只读) + :ivar 与KDJ匹配: 是否与KDJ超买超卖匹配(只读) + + 构造: + :meth:`创建缠K` — 从原始K线创建缠论K线(含包含继承) + + 算法: + :meth:`分析` — 包含处理 + 合并,增量更新缠K序列(主入口) + :meth:`时间戳对齐` — 将缠K序列时间戳对齐到指定基线 + + 工具: + :meth:`截取` — 截取起始缠K到终止缠K之间的子序列 """ __slots__ = ["序号", "时间戳", "高", "低", "方向", "分型", "周期", "标识", "分型特征值", "原始起始序号", "原始结束序号", "标的K线", "买卖点信息"] @@ -1903,7 +2571,7 @@ class 缠论K线(object): 最高价: float, 最低价: float, 最终方向: 相对方向, - 普K: "K线", + 普K: K线, 原始起始序号: int, 原始结束序号: int, 分型: Optional[分型结构] = None, @@ -1931,7 +2599,7 @@ class 缠论K线(object): self.原始起始序号: int = 原始起始序号 self.原始结束序号: int = 原始结束序号 - self.标的K线: "K线" = 普K + self.标的K线: K线 = 普K self.买卖点信息: Set[str] = set() def __str__(self): @@ -1989,7 +2657,7 @@ class 缠论K线(object): return False @classmethod - def 时间戳对齐(cls, 基线: List["缠论K线"], k线: "缠论K线"): + def 时间戳对齐(cls, 基线: List[缠论K线], k线: 缠论K线): """在基线序列中找到与k线时间戳对齐的时间戳 :param 基线: 参照缠K序列 @@ -2009,7 +2677,7 @@ class 缠论K线(object): return k线.时间戳 @classmethod - def 创建缠K(cls, 时间戳: datetime, 高: float, 低: float, 方向: 相对方向, 结构: 分型结构, 原始序号: int, 普k: "K线", 之前: Optional["缠论K线"] = None) -> "缠论K线": + def 创建缠K(cls, 时间戳: datetime, 高: float, 低: float, 方向: 相对方向, 结构: 分型结构, 原始序号: int, 普k: K线, 之前: Optional[缠论K线] = None) -> 缠论K线: """创建新的缠论K线 :param 时间戳: K线时间 @@ -2045,7 +2713,7 @@ class 缠论K线(object): return 当前 @classmethod - def 兼并(cls, 之前缠K: Optional["缠论K线"], 当前缠K: "缠论K线", 当前普K: "K线", 配置: 缠论配置) -> Tuple[Optional["缠论K线"], Optional[str]]: + def _兼并(cls, 之前缠K: Optional[缠论K线], 当前缠K: 缠论K线, 当前普K: K线, 配置: 缠论配置) -> Tuple[Optional[缠论K线], Optional[str]]: """K线包含处理(合并) :param 之前缠K: 前一缠K @@ -2098,7 +2766,7 @@ class 缠论K线(object): return None, None @classmethod - def 分析(cls, 当前K线: "K线", 缠K序列: List["缠论K线"], 普K序列: List["K线"], 配置: 缠论配置) -> tuple[str, Optional["分型"]]: + def 分析(cls, 当前K线: K线, 缠K序列: List[缠论K线], 普K序列: List[K线], 配置: 缠论配置) -> tuple[str, Optional[分型]]: """分析K线,执行指标计算+包含处理+分型判定 :param 当前K线: 新到的原始K线 @@ -2109,33 +2777,21 @@ class 缠论K线(object): """ 当前K线.标识 = 配置.标识 if not 普K序列: - if 配置.计算指标: - 当前K线.macd = 平滑异同移动平均线.首次计算_K线(当前K线, 配置.指标计算方式, 配置.平滑异同移动平均线_快线周期, 配置.平滑异同移动平均线_慢线周期, 配置.平滑异同移动平均线_信号周期) - 当前K线.rsi = 相对强弱指数.首次计算_K线(当前K线, 配置.指标计算方式, 配置.相对强弱指数_周期, 配置.相对强弱指数_超买阈值, 配置.相对强弱指数_超卖阈值, 配置.相对强弱指数_移动平均线周期) - 当前K线.kdj = 随机指标.首次计算_K线(当前K线, 配置.指标计算方式, 配置.随机指标_RSV周期, 配置.随机指标_K值平滑周期, 配置.随机指标_D值平滑周期, 配置.随机指标_超买阈值, 配置.随机指标_超卖阈值) 普K序列.append(当前K线) else: 之前普K = 普K序列[-1] if 之前普K.时间戳 == 当前K线.时间戳: 当前K线.序号 = 普K序列[-1].序号 普K序列[-1] = 当前K线 - if 配置.计算指标: - try: - 当前K线.macd = 平滑异同移动平均线.增量计算_K线(普K序列[-2].macd, 当前K线, 配置.指标计算方式) - 当前K线.rsi = 相对强弱指数.增量计算_K线(普K序列[-2].rsi, 当前K线, 配置.指标计算方式) - 当前K线.kdj = 随机指标.增量计算_K线(普K序列[-2].kdj, 当前K线, 配置.指标计算方式) - except IndexError: - pass # traceback.print_exc() else: if 之前普K.时间戳 > 当前K线.时间戳: raise RuntimeError("时序错误") 当前K线.序号 = 之前普K.序号 + 1 - if 配置.计算指标: - 当前K线.macd = 平滑异同移动平均线.增量计算_K线(之前普K.macd, 当前K线, 配置.指标计算方式) - 当前K线.rsi = 相对强弱指数.增量计算_K线(之前普K.rsi, 当前K线, 配置.指标计算方式) - 当前K线.kdj = 随机指标.增量计算_K线(之前普K.kdj, 当前K线, 配置.指标计算方式) 普K序列.append(当前K线) + if 配置.计算指标: + 指标计算器.计算并挂载(当前K线, 普K序列, 配置) + 之前缠K: Optional[缠论K线] = None 状态, 形态 = None, None if 缠K序列: @@ -2143,7 +2799,7 @@ class 缠论K线(object): 之前缠K = 缠K序列[-2] except IndexError: pass - 新缠K, 模式 = 缠论K线.兼并(之前缠K, 缠K序列[-1], 当前K线, 配置) + 新缠K, 模式 = 缠论K线._兼并(之前缠K, 缠K序列[-1], 当前K线, 配置) if 新缠K is not None: if 模式 == "添加": 缠K序列.append(新缠K) @@ -2194,7 +2850,7 @@ class 缠论K线(object): return 状态, 形态 @staticmethod - def 截取(序列: List["缠论K线"], 始: "缠论K线", 终: "缠论K线") -> List["缠论K线"]: + def 截取(序列: List[缠论K线], 始: 缠论K线, 终: 缠论K线) -> List[缠论K线]: """ :param 序列: 缠K序列 :param 始: 起始缠K @@ -2207,18 +2863,25 @@ class 缠论K线(object): 分型模式 = True -class 分型(object): - """分型 — 由左中右三根缠论K线构成的顶/底分型结构。 +class 分型: + """由左中右三根缠论K线构成的顶/底分型结构。 :ivar 左: 左侧缠K(可能为None) :ivar 中: 中间缠K(分型顶点/底点) :ivar 右: 右侧缠K(可能为None) - :ivar 结构: 分型结构(顶/底/上/下/包含) - :ivar 时间戳: 分型时间戳 - :ivar 分型特征值: 顶分型取最高价,底分型取最低价 - :ivar 关系组: 左中、中右、左右三对相对方向 - :ivar 强度: 分型强度(强/中/弱/未知) - :ivar 与MACD柱子分型匹配: 是否与MACD柱子分型匹配 + :ivar 结构: 分型结构(顶/底,受 :data:`分型模式` 控制缓存/实时读取) + :ivar 时间戳: 分型时间戳(受 :data:`分型模式` 控制) + :ivar 分型特征值: 顶分型取最高价,底分型取最低价(受 :data:`分型模式` 控制) + :ivar 关系组: 左中、中右、左右三对相对方向(只读) + :ivar 强度: 分型强度 — 强/中/弱/未知(只读) + :ivar 与MACD柱子分型匹配: 是否与MACD柱子分型匹配(只读) + + 构造: + :meth:`从缠K序列中获取分型` — 从缠K序列中按中间缠K定位分型 + + 工具: + :meth:`判断分型` — 判断左分型的武与右分型的文是否相连 + :meth:`向序列中添加` — 将分型添加到序列(自动去重) """ __slots__ = ["左", "中", "右", "_结构", "_时间戳", "_分型特征值"] @@ -2239,25 +2902,37 @@ class 分型(object): self._分型特征值 = 中.分型特征值 def __str__(self): - return f"{self.中.分型}<{self.时间戳}, {self.分型特征值:g}, None: {self.左 is None}, None: {self.右 is None}>" + return f"{self.结构}<{self.时间戳}, {self.分型特征值:g}, None: {self.左 is None}, None: {self.右 is None}>" def __repr__(self): - return f"{self.中.分型}<{self.时间戳}, {self.分型特征值:g}, None: {self.左 is None}, None: {self.右 is None}>" + return f"{self.结构}<{self.时间戳}, {self.分型特征值:g}, None: {self.左 is None}, None: {self.右 is None}>" @property def 时间戳(self): + """分型时间戳 — 分型模式为 True 时返回构造时缓存值,否则从中间缠K实时读取。 + + :return: 分型的时间戳 + """ if 分型模式: return self._时间戳 return self.中.时间戳 @property def 分型特征值(self): + """分型特征值 — 顶分型为最高价,底分型为最低价。 + + :return: 分型的特征价格 + """ if 分型模式: return self._分型特征值 return self.中.分型特征值 @property def 结构(self): + """分型结构 — 分型模式为 True 时返回构造时缓存值,否则从中间缠K实时读取。 + + :return: 分型结构(顶/底/上/下/散) + """ if 分型模式: return self._结构 return self.中.分型 @@ -2330,7 +3005,7 @@ class 分型(object): return False @classmethod - def 判断分型(cls, 左: "分型", 右: "分型", 模式: str = "中") -> bool: + def 判断分型(cls, 左: 分型, 右: 分型, 模式: str = "中") -> bool: """判断两个分型是否相同(identity比较) :param 左: 左分型 @@ -2341,7 +3016,7 @@ class 分型(object): return 左 is 右 @staticmethod - def 从缠K序列中获取分型(K线序列: List[缠论K线], 中: 缠论K线) -> "分型": + def 从缠K序列中获取分型(K线序列: List[缠论K线], 中: 缠论K线) -> 分型: """从缠K序列中提取以指定缠K为中元素的分型 :param K线序列: 缠K列表 @@ -2355,7 +3030,7 @@ class 分型(object): return 分型(左=K线序列[索引 - 1], 中=中, 右=None) @staticmethod - def 向序列中添加(分型序列: List["分型"], 当前分型: "分型"): + def 向序列中添加(分型序列: List[分型], 当前分型: 分型): """向分型序列尾部添加,自动校验顶底交替 :param 分型序列: 现有分型列表 @@ -2368,13 +3043,13 @@ class 分型(object): if 分型序列[-1].结构 is 当前分型.结构: raise ValueError("分型相同无法添加", 分型序列[-1], 当前分型) if 分型序列[-1].右 is None: - print("分型.向序列中添加, 分型异常", 分型序列[-1]) + logger.warning(f"分型.向序列中添加, 分型异常, {分型序列[-1]}") 分型序列.append(当前分型) -class 虚线(object): - """虚线 — 笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。 +class 虚线: + """笔/线段的通用数据结构,持有一组分型端点(文=起点分型, 武=终点分型)。 :ivar 标识: 类型标识("笔"/"线段"/"扩展线段"等) :ivar 序号: 序号 @@ -2397,6 +3072,33 @@ class 虚线(object): :ivar 低: 最低价(只读) :ivar 笔序列: 所有笔(只读) :ivar 图表标题: 图表显示标题(只读) + + 构造: + :meth:`创建笔` — 从两个分型创建笔虚线 + :meth:`创建线段` — 从虚线序列创建线段虚线 + + 实例方法: + :meth:`之前是` / :meth:`之后是` — 判断两个虚线的先后关系 + :meth:`获取普K序列` — 获取虚线范围内的原始K线序列 + :meth:`获取缠K序列` — 获取虚线范围内的缠论K线序列 + :meth:`获取数据文本` — 返回关键数据的文本表示 + + 买卖点匹配: + :meth:`缠K买卖点模式` / :meth:`买卖点配置匹配` — 按配置模式匹配买卖点 + :meth:`买卖点任意匹配` / :meth:`买卖点全量匹配` / :meth:`买卖点相对匹配` — 不同匹配策略 + :meth:`买卖意义` — 判断虚线是否具有买卖意义 + + MACD计算: + :meth:`计算MACD柱子均值` / :meth:`计算MACD柱子均值_阴` / :meth:`计算MACD柱子均值_阳` + :meth:`武之全量MACD均值` / :meth:`武之MACD均值` / :meth:`武之MACD均值_阴` / :meth:`武之MACD均值_阳` + :meth:`武之MACD极值` — 基于MACD极值判断背驰 + :meth:`计算K线序列MACD趋向背驰` — 检测MACD趋向背驰 + :meth:`计算MACD柱子分段` — 将K线序列按MACD柱子符号分段 + :meth:`统计MACD行为` — 统计密集区域的MACD交叉行为 + + 辅助: + :meth:`密集区域按间隔` — 按最大间隔和最少交叉数查找密集区域 + :meth:`获取_武` — 获取虚线的有效武分型 """ __slots__ = ["标识", "序号", "级别", "文", "武", "有效性", "基础序列", "特征序列", "实_中枢序列", "虚_中枢序列", "合_中枢序列", "确认K线", "模式", "_特征序列_显示", "前一缺口", "前一结束位置", "短路修正"] @@ -2419,12 +3121,12 @@ class 虚线(object): self.有效性 = 有效性 - self.基础序列: List["虚线"] = [] - self.特征序列: List[Optional[线段特征]] = [] + self.基础序列: List[虚线] = [] + self.特征序列: List[Optional[线段特征]] = [None, None, None] - self.实_中枢序列: List["中枢"] = [] - self.虚_中枢序列: List["中枢"] = [] - self.合_中枢序列: List["中枢"] = [] + self.实_中枢序列: List[中枢] = [] + self.虚_中枢序列: List[中枢] = [] + self.合_中枢序列: List[中枢] = [] self.确认K线: Optional[缠论K线] = None self.模式: str = "文武" self._特征序列_显示 = False @@ -2454,7 +3156,7 @@ class 虚线(object): return f"{self.文.中.标识}:{self.文.中.周期}:{self.标识}:{self.序号}" @property - def 方向(self) -> "相对方向": + def 方向(self) -> 相对方向: """ :return: 运行方向 :raises RuntimeError: 无法识别的方向 @@ -2475,19 +3177,25 @@ class 虚线(object): @property def 高(self) -> float: - """虚线区间的最高价(向上线段为终点分型最高价,向下线段为起点分型最高价)""" + """虚线区间的最高价。 + + :return: 向上虚线取武.中.高,向下虚线取文.中.高 + """ if self.方向 is 相对方向.向上: return self.武.中.高 return self.文.中.高 @property def 低(self) -> float: - """虚线区间的最低价(向下线段为终点分型最低价,向上线段为起点分型最低价)""" + """虚线区间的最低价。 + + :return: 向下虚线取武.中.低,向上虚线取文.中.低 + """ if self.方向 is 相对方向.向下: return self.武.中.低 return self.文.中.低 - def 之前是(self, 之前: "虚线") -> bool: + def 之前是(self, 之前: 虚线) -> bool: """ :param 之前: 前一条虚线 :return: 当前虚线的起点是否为前一条虚线的终点 @@ -2496,7 +3204,7 @@ class 虚线(object): return 分型.判断分型(之前.武, self.文) return False - def 之后是(self, 之后: "虚线") -> bool: + def 之后是(self, 之后: 虚线) -> bool: """ :param 之后: 后一条虚线 :return: 当前虚线的终点是否为后一条虚线的起点 @@ -2505,14 +3213,14 @@ class 虚线(object): return 分型.判断分型(self.武, 之后.文) return False - def 获取普K序列(self, 观察员: "观察者") -> List[K线]: + def 获取普K序列(self, 观察员: 观察者) -> List[K线]: """ :param 观察员: 观察者实例 :return: 区间内的原始K线列表 """ return K线.截取(观察员.普通K线序列, self.文.中.标的K线, self.武.中.标的K线) - def 获取缠K序列(self, 观察员: "观察者") -> List[缠论K线]: + def 获取缠K序列(self, 观察员: 观察者) -> List[缠论K线]: """ :param 观察员: 观察者实例 :return: 区间内的缠K列表 @@ -2527,7 +3235,7 @@ class 虚线(object): return f"{self.标识}, {self.序号}, {self.级别}, 文:({int(self.文.时间戳.timestamp())},{self.文.分型特征值:g}), 武:({int(self.武.时间戳.timestamp())},{self.武.分型特征值:g}), {self.有效性}, {len(self.基础序列)}, {线段.特征序列状态(self)}, (前: {str(前)}, 后: {str(后)}, 三: {str(三)}, 伤: {str(贯穿伤)}), 实: {str(self.实_中枢序列)}, 虚: {str(self.虚_中枢序列)}, 合: {str(self.合_中枢序列)}, {self.模式}, {str(self.前一缺口)}, {str(self.前一结束位置)}, {self.短路修正}" @classmethod - def 创建笔(cls, 文: 分型, 武: 分型, 有效性: bool = True) -> "虚线": + def 创建笔(cls, 文: 分型, 武: 分型, 有效性: bool = True) -> 虚线: """ :param 文: 起点分型 :param 武: 终点分型 @@ -2537,11 +3245,16 @@ class 虚线(object): return 虚线(0, "笔", 文, 武, 1, 有效性) @classmethod - def 创建线段(cls, 虚线序列: List["虚线"]) -> "虚线": + def 创建线段(cls, 虚线序列: List[虚线]) -> 虚线: """ :param 虚线序列: 构成线段的虚线列表(笔) :return: 虚线实例(标识="线段"或"线段<...>") """ + 序列数量 = len(虚线序列) + if 序列数量 < 3: + raise RuntimeError(f"创建线段 虚线序列 数量 {序列数量} < 3") + if 序列数量 % 2 == 0: + raise RuntimeError(f"创建线段 虚线序列 数量 {序列数量} 不是单数!") 文 = 虚线序列[0].文 武 = 虚线序列[-1].武 assert 文.结构 != 武.结构 @@ -2555,7 +3268,7 @@ class 虚线(object): return 段 @classmethod - def 缠K买卖点模式(cls, 模式: str, 缠K: "缠论K线", 配置: 缠论配置): + def 缠K买卖点模式(cls, 模式: str, 缠K: 缠论K线, 配置: 缠论配置): """ :param 模式: "全量"/"任意"/"配置"/"相对" :param 缠K: 待检测的缠论K线 @@ -2574,8 +3287,13 @@ class 虚线(object): return False @classmethod - def 买卖点配置匹配(cls, 缠K: "缠论K线", 配置: 缠论配置): - """根据配置中的指标开关检测缠K匹配情况(MACD/KDJ/RSI组合)""" + def 买卖点配置匹配(cls, 缠K: 缠论K线, 配置: 缠论配置): + """根据配置中的指标开关检测缠K匹配情况。 + + :param 缠K: 待检测的缠论K线 + :param 配置: 包含指标开关的缠论配置 + :return: 是否满足配置中开启的指标匹配条件 + """ match 配置.买卖点_指标匹配_MACD, 配置.买卖点_指标匹配_KDJ, 配置.买卖点_指标匹配_RSI: case True, True, True: return 缠K.与MACD柱子匹配 and 缠K.与KDJ匹配 and 缠K.与RSI匹配 @@ -2599,7 +3317,7 @@ class 虚线(object): return False @classmethod - def 买卖点任意匹配(cls, 缠K: "缠论K线"): + def 买卖点任意匹配(cls, 缠K: 缠论K线): """ :param 缠K: 缠论K线 :return: MACD/KDJ/RSI 任一匹配 @@ -2607,7 +3325,7 @@ class 虚线(object): return any([缠K.与MACD柱子匹配, 缠K.与KDJ匹配, 缠K.与RSI匹配]) @classmethod - def 买卖点全量匹配(csl, 缠K: "缠论K线"): + def 买卖点全量匹配(csl, 缠K: 缠论K线): """ :param 缠K: 缠论K线 :return: MACD/KDJ/RSI 全部匹配 @@ -2615,7 +3333,7 @@ class 虚线(object): return all([缠K.与MACD柱子匹配, 缠K.与KDJ匹配, 缠K.与RSI匹配]) @classmethod - def 买卖点相对匹配(cls, 缠K: "缠论K线"): + def 买卖点相对匹配(cls, 缠K: 缠论K线): """ :param 缠K: 缠论K线 :return: 三个指标中至少两个匹配 @@ -2624,7 +3342,7 @@ class 虚线(object): return len([匹配 for 匹配 in 混沌槽 if 匹配]) >= 2 @classmethod - def 计算MACD柱子均值(cls, 普K序列: List[K线], 实线: "虚线") -> float: + def 计算MACD柱子均值(cls, 普K序列: List[K线], 实线: 虚线) -> float: """ :param 普K序列: 完整K线序列 :param 实线: 虚线(笔/线段) @@ -2634,7 +3352,7 @@ class 虚线(object): return sum([abs(K.macd.MACD柱) for K in K线序列]) / len(K线序列) @classmethod - def 计算MACD柱子均值_阴(cls, 普K序列: List[K线], 实线: "虚线") -> float: + def 计算MACD柱子均值_阴(cls, 普K序列: List[K线], 实线: 虚线) -> float: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2647,7 +3365,7 @@ class 虚线(object): return False @classmethod - def 计算MACD柱子均值_阳(cls, 普K序列: List[K线], 实线: "虚线") -> float: + def 计算MACD柱子均值_阳(cls, 普K序列: List[K线], 实线: 虚线) -> float: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2660,7 +3378,7 @@ class 虚线(object): return False @classmethod - def 武之全量MACD均值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: + def 武之全量MACD均值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2669,7 +3387,7 @@ class 虚线(object): return abs(实线.武.中.标的K线.macd.MACD柱) < cls.计算MACD柱子均值(普K序列, 实线) @classmethod - def 武之MACD均值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: + def 武之MACD均值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2681,7 +3399,7 @@ class 虚线(object): return cls.武之MACD均值_阴(普K序列, 实线) @classmethod - def 武之MACD均值_阴(cls, 普K序列: List[K线], 实线: "虚线") -> bool: + def 武之MACD均值_阴(cls, 普K序列: List[K线], 实线: 虚线) -> bool: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2690,7 +3408,7 @@ class 虚线(object): return abs(实线.武.中.标的K线.macd.MACD柱) < abs(cls.计算MACD柱子均值_阴(普K序列, 实线)) @classmethod - def 武之MACD均值_阳(cls, 普K序列: List[K线], 实线: "虚线") -> bool: + def 武之MACD均值_阳(cls, 普K序列: List[K线], 实线: 虚线) -> bool: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2699,7 +3417,7 @@ class 虚线(object): return abs(实线.武.中.标的K线.macd.MACD柱) < abs(cls.计算MACD柱子均值_阳(普K序列, 实线)) @classmethod - def 武之MACD极值(cls, 普K序列: List[K线], 实线: "虚线") -> bool: + def 武之MACD极值(cls, 普K序列: List[K线], 实线: 虚线) -> bool: """ :param 普K序列: 完整K线序列 :param 实线: 虚线 @@ -2716,7 +3434,7 @@ class 虚线(object): return False @classmethod - def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence["K线"], 方向: 相对方向): + def 计算K线序列MACD趋向背驰(cls, 普K序列: Sequence[K线], 方向: 相对方向): """计算K线序列的MACD柱/DIF/DEA趋向背驰(三元素判断) :param 普K序列: K线序列 @@ -2796,7 +3514,7 @@ class 虚线(object): @classmethod @lru_cache(maxsize=128) - def 买卖意义(cls, 实线: "虚线", 观察员: "观察者") -> Tuple[bool, str]: + def 买卖意义(cls, 实线: 虚线, 观察员: 观察者) -> Tuple[bool, str]: """ 静止是相对的,而运动是绝对的 @@ -2831,7 +3549,7 @@ class 虚线(object): return 结果, "" @classmethod - def 计算MACD柱子分段(cls, k线序列: List["K线"]) -> Tuple[List[List["K线"]], ...]: + def 计算MACD柱子分段(cls, k线序列: List[K线]) -> Tuple[List[List[K线]], ...]: """ :param k线序列: K线序列 :return: 按正负分段的MACD柱列表 @@ -2861,12 +3579,12 @@ class 虚线(object): @classmethod def 密集区域按间隔(cls, 交叉标记: List[int], 最大间隔: int = 5, 最少交叉数: int = 3) -> List[Tuple[int, int, int]]: - """ - 交叉标记: 长度为len(macd_list)的列表,0=无交叉, 1=金叉, -1=死叉 - 最大间隔: 相邻交叉索引差 ≤ 此值则归入同一密集区 - 最少交叉数: 一个密集区内至少包含的交叉次数 + """将交叉信号按间隔聚类成密集区域。 - :return: [(起始交叉索引, 结束交叉索引, 区内交叉次数), ...] + :param 交叉标记: 长度为N的列表,0=无交叉, 1=金叉, -1=死叉 + :param 最大间隔: 相邻交叉索引差 ≤ 此值则归入同一密集区 + :param 最少交叉数: 一个密集区内至少包含的交叉次数 + :return: 密集区域列表 [(起始交叉索引, 结束交叉索引, 区内交叉次数), ...] """ # 提取所有交叉点的索引 交叉索引 = [i for i, v in enumerate(交叉标记) if v != 0] @@ -2963,23 +3681,22 @@ class 虚线(object): return tmp.武 -class 笔(object): - """笔 — 纯静态方法容器,提供笔划分算法的所有函数。 +class 笔: + """纯静态方法容器,提供笔划分算法的所有函数。 主要方法: - 获取缠K数量 — 获取笔内缠K数量 - 停顿 — 检测笔的停顿点 - 自检 — 校验笔的有效性 - 获取所有停顿位置 — 获取笔内所有停顿位置 - 获取起始分型 — 获取笔的起始分型 - 生成笔序列 — 从分型序列生成笔序列 - 是否背驰过 — 判断笔是否经过背驰 + :meth:`分析` — 流式分析,增量识别笔 + :meth:`以文会友` — 根据起始点找笔 + :meth:`以武会友` — 根据结束点找笔 + :meth:`根据缠K找笔` — 判断缠K是否在文武序号之间 + :meth:`获取所有停顿位置` — 获取笔内所有停顿位置 + :meth:`是否背驰过` — 判断笔是否经过背驰 """ __slots__ = [] @staticmethod - def 获取缠K数量(缠K序列: List[缠论K线], 笔序列: List[虚线], 配置: 缠论配置) -> int: + def _获取缠K数量(缠K序列: List[缠论K线], 笔序列: List[虚线], 配置: 缠论配置) -> int: """获取笔内有效缠K数量(考虑笔弱化等配置) :param 缠K序列: 候选笔的基础缠K序列 @@ -2992,27 +3709,27 @@ class 笔(object): return 实际数量 if 配置.笔弱化 and 实际数量 >= 3: - 实际高点 = 笔.实际高点(缠K序列, 配置.笔内相同终点取舍) - 实际低点 = 笔.实际低点(缠K序列, 配置.笔内相同终点取舍) - 原始数量 = 1 + abs(实际低点.标的K线.序号 - 实际高点.标的K线.序号) + _实际高点 = 笔._实际高点(缠K序列, 配置.笔内相同终点取舍) + _实际低点 = 笔._实际低点(缠K序列, 配置.笔内相同终点取舍) + 原始数量 = 1 + abs(_实际低点.标的K线.序号 - _实际高点.标的K线.序号) if 原始数量 >= 配置.笔内元素数量: return 配置.笔内元素数量 if 笔序列: - 筆 = 笔.根据缠K找笔(笔序列, 实际高点) or 笔.根据缠K找笔(笔序列, 实际低点) + 筆 = 笔.根据缠K找笔(笔序列, _实际高点) or 笔.根据缠K找笔(笔序列, _实际低点) if 筆: - if 筆.方向 is 相对方向.向上 and 实际低点.低 < 筆.低: + if 筆.方向 is 相对方向.向上 and _实际低点.低 < 筆.低: if 原始数量 >= 配置.笔弱化_原始数量: return 配置.笔内元素数量 - if 筆.方向 is 相对方向.向下 and 实际低点.低 > 筆.高: + if 筆.方向 is 相对方向.向下 and _实际低点.低 > 筆.高: if 原始数量 >= 配置.笔弱化_原始数量: return 配置.笔内元素数量 return 实际数量 @staticmethod - def 次高(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """次高 + def _次高(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: + """_次高 :param 缠K序列: 缠K序列 :param 笔内相同终点取舍: 终点取舍方式 @@ -3025,8 +3742,8 @@ class 笔(object): return highs[-1] if 笔内相同终点取舍 else highs[0] @staticmethod - def 次低(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """次低 + def _次低(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: + """_次低 :param 缠K序列: 缠K序列 :param 笔内相同终点取舍: 终点取舍方式 @@ -3039,8 +3756,8 @@ class 笔(object): return lows[-1] if 笔内相同终点取舍 else lows[0] @staticmethod - def 实际高点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """实际高点 + def _实际高点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: + """_实际高点 :param 缠K序列: 缠K序列 :param 笔内相同终点取舍: 终点取舍方式 @@ -3052,8 +3769,8 @@ class 笔(object): return highs[-1] if 笔内相同终点取舍 else highs[0] @staticmethod - def 实际低点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: - """实际低点 + def _实际低点(缠K序列: List[缠论K线], 笔内相同终点取舍: bool) -> 缠论K线: + """_实际低点 :param 缠K序列: 缠K序列 :param 笔内相同终点取舍: 终点取舍方式 @@ -3065,7 +3782,7 @@ class 笔(object): return lows[-1] if 笔内相同终点取舍 else lows[0] @staticmethod - def 相对关系(筆: 虚线, 配置: 缠论配置) -> bool: + def _相对关系(筆: 虚线, 配置: 缠论配置) -> bool: """相对关系 :param 筆: 笔虚线 @@ -3102,7 +3819,7 @@ class 笔(object): 旧笔.有效性 = False @classmethod - def _添加新笔(cls, 分型序列: List[分型], 笔序列: List[虚线], 待添加分型: "分型", 待添加新笔: 虚线, 行号): + def _添加新笔(cls, 分型序列: List[分型], 笔序列: List[虚线], 待添加分型: 分型, 待添加新笔: 虚线, 行号): """内部方法:添加新分型和新笔 :param 分型序列: 分型列表 @@ -3118,7 +3835,7 @@ class 笔(object): if 分型序列[-1].结构 is 待添加分型.结构: raise ValueError("分型相同无法添加", 分型序列[-1], 待添加分型) if 分型序列[-1].右 is None: - print("分型.向序列中添加, 分型异常", 分型序列[-1]) + logger.warning(f"分型.向序列中添加, 分型异常 {分型序列[-1]}") 分型序列.append(待添加分型) if 笔序列 and not 笔序列[-1].之后是(待添加新笔): @@ -3126,10 +3843,10 @@ class 笔(object): if 笔序列: 待添加新笔.序号 = 笔序列[-1].序号 + 1 - if 待添加新笔.武.左 is None and 待添加新笔.武.右 is None: + if 待添加新笔.武.左 is None or 待添加新笔.武.右 is None: 待添加新笔.有效性 = False if 笔序列[-1].武.结构 in (分型结构.上, 分型结构.下): - print(f"_添加新笔[{行号}] 出现无效分型", 笔序列[-1]) + logger.error(f"_添加新笔[{行号}] 出现无效分型 {笔序列[-1]}") 笔序列.append(待添加新笔) @@ -3150,7 +3867,7 @@ class 笔(object): return 递归层次 if 递归层次 > 64: - print(f"笔.分析 递归深度超出 64 < {递归层次}") + logger.warning(f"笔.分析 递归深度超出 64 < {递归层次}") # return 递归层次 if 当前分型.结构 not in (分型结构.顶, 分型结构.底): @@ -3164,7 +3881,7 @@ class 笔(object): 笔递归分析 = 笔.分析 之前分型 = 分型序列[-1] - if (之前分型.中.时间戳 == 当前分型.中.时间戳) or (之前分型.结构 in (分型结构.上, 分型结构.下)): + if (之前分型.时间戳 == 当前分型.时间戳) or (之前分型.结构 in (分型结构.上, 分型结构.下)): 笔._弹出旧笔(分型序列, 笔序列, sys._getframe().f_lineno) if not 分型序列: if 当前分型.右 is not None: @@ -3172,9 +3889,9 @@ class 笔(object): return 递归层次 之前分型 = 分型序列[-1] - if 之前分型.中.时间戳 > 当前分型.中.时间戳 and 之前分型.中.序号 - 当前分型.中.序号 > 1: + if 之前分型.时间戳 > 当前分型.时间戳 and 之前分型.中.序号 - 当前分型.中.序号 > 1: # raise RuntimeError(f"时序错误-{递归层次}, {之前分型}, {当前分型}") - print(colored(f"时序错误-{递归层次}, {之前分型}, {当前分型}", "red")) + logger.error(f"时序错误-{递归层次}, {之前分型}, {当前分型}") return 递归层次 if 配置.笔弱化 and 笔序列: @@ -3187,11 +3904,11 @@ class 笔(object): if 之前分型.结构 is not 当前分型.结构: 基础序列 = 缠论K线.截取(缠K序列, 之前分型.中, 当前分型.中) 当前笔 = 虚线.创建笔(文=之前分型, 武=当前分型, 有效性=True) - if 笔.获取缠K数量(基础序列, 笔序列, 配置) >= 配置.笔内元素数量: + if 笔._获取缠K数量(基础序列, 笔序列, 配置) >= 配置.笔内元素数量: if 之前分型.结构 is 分型结构.顶 and 当前分型.结构 is 分型结构.底: - 文官 = 笔.实际高点(基础序列, False) + 文官 = 笔._实际高点(基础序列, False) else: - 文官 = 笔.实际低点(基础序列, False) + 文官 = 笔._实际低点(基础序列, False) if 文官 is not 之前分型.中: 临时分型 = 分型.从缠K序列中获取分型(缠K序列, 文官) @@ -3204,20 +3921,20 @@ class 笔(object): return 递归层次 if 之前分型.结构 is 分型结构.顶 and 当前分型.结构 is 分型结构.底: - 武将 = 笔.实际低点(基础序列, 配置.笔内相同终点取舍) + 武将 = 笔._实际低点(基础序列, 配置.笔内相同终点取舍) else: - 武将 = 笔.实际高点(基础序列, 配置.笔内相同终点取舍) + 武将 = 笔._实际高点(基础序列, 配置.笔内相同终点取舍) - if 笔.相对关系(当前笔, 配置) and 当前分型.中 is 武将: + if 笔._相对关系(当前笔, 配置) and 当前分型.中 is 武将: 笔._添加新笔(分型序列, 笔序列, 当前分型, 当前笔, sys._getframe().f_lineno) return 递归层次 if 配置.笔次级成笔: if 之前分型.结构 is 分型结构.顶 and 当前分型.结构 is 分型结构.底: - 武将 = 笔.次低(基础序列, 配置.笔内相同终点取舍) + 武将 = 笔._次低(基础序列, 配置.笔内相同终点取舍) else: - 武将 = 笔.次高(基础序列, 配置.笔内相同终点取舍) - if 笔.相对关系(当前笔, 配置) and 当前分型.中 is 武将: + 武将 = 笔._次高(基础序列, 配置.笔内相同终点取舍) + if 笔._相对关系(当前笔, 配置) and 当前分型.中 is 武将: 笔._添加新笔(分型序列, 笔序列, 当前分型, 当前笔, sys._getframe().f_lineno) return 递归层次 @@ -3233,9 +3950,9 @@ class 笔(object): 笔._弹出旧笔(分型序列, 笔序列, sys._getframe().f_lineno) k线序列 = 缠论K线.截取(缠K序列, 之前分型.中, 当前分型.中) if 之前分型.结构 is 分型结构.顶: - 武将 = 笔.实际低点(k线序列, 配置.笔内相同终点取舍) + 武将 = 笔._实际低点(k线序列, 配置.笔内相同终点取舍) else: - 武将 = 笔.实际高点(k线序列, 配置.笔内相同终点取舍) + 武将 = 笔._实际高点(k线序列, 配置.笔内相同终点取舍) 临时分型 = 分型.从缠K序列中获取分型(缠K序列, 武将) if 分型序列: @@ -3248,7 +3965,7 @@ class 笔(object): 递归层次 = 笔递归分析(临时分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) if 分型序列 and 分型序列[-1] is 临时分型: """""" - # print("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型) + # logger.warning("笔.分析 事后修复错过的笔", 临时分型, "当前分型", 当前分型) 递归层次 = 笔递归分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次 + 1, 配置) return 递归层次 @@ -3284,7 +4001,7 @@ class 笔(object): return None @staticmethod - def 根据缠K找笔(笔序列: List[虚线], 缠K: "缠论K线", 偏移: int = 1): + def 根据缠K找笔(笔序列: List[虚线], 缠K: 缠论K线, 偏移: int = 1): """根据缠K找笔 :param 笔序列: 笔列表 @@ -3294,13 +4011,13 @@ class 笔(object): """ for 筆 in 笔序列[::-1]: if 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号: - # if 缠K in 筆.缠K序列[偏移:]: - return 筆 + if 筆.文.中.周期 == 缠K.周期 and 筆.文.中.标识 == 缠K.标识: + return 筆 return None @classmethod - def 自检(cls, 筆: 虚线, 观察员: "观察者") -> bool: + def _自检(cls, 筆: 虚线, 观察员: 观察者) -> bool: """校验笔的有效性:高/低点是否为实际极值 :param 筆: 待校验的笔 @@ -3310,15 +4027,15 @@ class 笔(object): 笔序列: List[虚线] = 观察员.笔序列 配置: 缠论配置 = 观察员.配置 基础序列 = 筆.获取缠K序列(观察员) - if 笔.获取缠K数量(基础序列, 笔序列, 配置) >= 配置.笔内元素数量: - if 筆.方向 is 相对方向.向下 and 筆.文.中 is 笔.实际高点(基础序列, False) and 筆.武.中 is 笔.实际低点(基础序列, 配置.笔内相同终点取舍): + if 笔._获取缠K数量(基础序列, 笔序列, 配置) >= 配置.笔内元素数量: + if 筆.方向 is 相对方向.向下 and 筆.文.中 is 笔._实际高点(基础序列, False) and 筆.武.中 is 笔._实际低点(基础序列, 配置.笔内相同终点取舍): return True - if 筆.方向 is 相对方向.向上 and 筆.文.中 is 笔.实际低点(基础序列, False) and 筆.武.中 is 笔.实际高点(基础序列, 配置.笔内相同终点取舍): + if 筆.方向 is 相对方向.向上 and 筆.文.中 is 笔._实际低点(基础序列, False) and 筆.武.中 is 笔._实际高点(基础序列, 配置.笔内相同终点取舍): return True return False @classmethod - def 获取所有停顿位置(cls, 筆: 虚线, 观察员: "观察者") -> List[虚线]: + def 获取所有停顿位置(cls, 筆: 虚线, 观察员: 观察者) -> List[虚线]: """获取笔内所有可能的停顿位置(用于背驰检测) :param 筆: 笔 @@ -3334,17 +4051,17 @@ class 笔(object): if K.分型 is 分型结构.顶 and 筆.方向 is 相对方向.向上: 当前笔 = 虚线.创建笔(文=文, 武=武) 当前笔.序号 = 筆.序号 - 笔.自检(当前笔, 观察员) and 笔序列.append(当前笔) + 笔._自检(当前笔, 观察员) and 笔序列.append(当前笔) if K.分型 is 分型结构.底 and 筆.方向 is 相对方向.向下: 当前笔 = 虚线.创建笔(文=文, 武=武) 当前笔.序号 = 筆.序号 - 笔.自检(当前笔, 观察员) and 笔序列.append(当前笔) + 笔._自检(当前笔, 观察员) and 笔序列.append(当前笔) return 笔序列 @classmethod - def 是否背驰过(cls, 当前筆: 虚线, 观察员: "观察者") -> List[缠论K线]: + def 是否背驰过(cls, 当前筆: 虚线, 观察员: 观察者) -> List[缠论K线]: """判断笔内是否发生过MACD趋向背驰 :param 当前筆: 笔 @@ -3360,23 +4077,32 @@ class 笔(object): return 结果 -class 线段特征(list): - """线段特征 — list 子类,持有一组同向虚线(笔),是线段划分的中间结构。 +class 线段特征: + """持有一组同向虚线(笔),是线段划分的中间结构。 - 继承 list,元素为虚线。特征序列用于线段划分算法中的包含处理。 + 特征序列用于线段划分算法中的包含处理和缺口判断。 :ivar 序号: 序号 :ivar 标识: 标识 :ivar 线段方向: 线段运行方向(特征序列方向为其翻转) + :ivar 基础序列: 虚线序列 :ivar 文: 第一个元素的起点分型(根据方向选取极值) :ivar 武: 最后一个元素的终点分型(根据方向选取极值) :ivar 高: 最高价 :ivar 低: 最低价 :ivar 方向: 特征序列方向(线段方向的翻转) :ivar 图表标题: 图表标题 + + 主要方法: + :meth:`新建` — 从虚线序列创建单个线段特征 + :meth:`静态分析` — 将虚线序列静态划分为线段特征列表(含包含处理) + :meth:`获取分型序列` — 从特征序列中提取分型序列 + + 注意: + Rust 绑定层仅导出 ``静态分析``,字段除了 ``标识`` 其余仅可读。 """ - __slots__ = ["序号", "标识", "线段方向"] + __slots__ = ["序号", "标识", "线段方向", "基础序列"] def __init__(self, 标识: str, 基础序列: List[虚线], 线段方向: 相对方向): """ @@ -3384,7 +4110,7 @@ class 线段特征(list): :param 基础序列: 基础虚线列表 :param 线段方向: 线段运行方向 """ - super().__init__(基础序列) + self.基础序列 = 基础序列[:] self.序号 = 0 self.标识: str = 标识 self.线段方向: 相对方向 = 线段方向 @@ -3408,30 +4134,36 @@ class 线段特征(list): @property def 文(self) -> 分型: - """起点分型(向上线段取高高中的最大者,向下线段取低低中的最小者)""" + """线段特征的起点分型。 + + :return: 向上取高高中的最大分型,向下取低低中的最小分型 + """ if self.线段方向 is 相对方向.向上: # 取高高 return max( - [线.文 for 线 in self], - key=lambda o: (o.中.分型特征值, o.中.时间戳), + [线.文 for 线 in self.基础序列], + key=lambda o: (o.分型特征值, o.时间戳), ) else: return min( - [线.文 for 线 in self], - key=lambda o: (o.中.分型特征值, -o.中.时间戳.timestamp()), + [线.文 for 线 in self.基础序列], + key=lambda o: (o.分型特征值, -o.时间戳.timestamp()), ) @property def 武(self) -> 分型: - """终点分型(向上线段取高高中的最大者,向下线段取低低中的最小者)""" + """线段特征的终点分型。 + + :return: 向上取高高中的最大分型,向下取低低中的最小分型 + """ if self.线段方向 is 相对方向.向上: return max( - [线.武 for 线 in self], - key=lambda o: (o.中.分型特征值, o.中.时间戳), + [线.武 for 线 in self.基础序列], + key=lambda o: (o.分型特征值, o.时间戳), ) else: return min( - [线.武 for 线 in self], - key=lambda o: (o.中.分型特征值, -o.中.时间戳.timestamp()), + [线.武 for 线 in self.基础序列], + key=lambda o: (o.分型特征值, -o.时间戳.timestamp()), ) @property @@ -3439,14 +4171,14 @@ class 线段特征(list): """ :return: 文和武中分型特征值的较大者 """ - return max([self.文, self.武], key=lambda fx: fx.中.分型特征值).中.分型特征值 + return max([self.文, self.武], key=lambda fx: fx.分型特征值).分型特征值 @property def 低(self) -> float: """ :return: 文和武中分型特征值的较小者 """ - return min([self.文, self.武], key=lambda fx: fx.中.分型特征值).中.分型特征值 + return min([self.文, self.武], key=lambda fx: fx.分型特征值).分型特征值 @property def 方向(self) -> 相对方向: @@ -3455,26 +4187,26 @@ class 线段特征(list): """ return self.线段方向.翻转() - def 添加(self, 待添加虚线: Union[虚线]): + def _添加(self, 待添加虚线: Union[虚线]): """ :param 待添加虚线: 待添加的虚线 :raises ValueError: 方向不匹配时抛出 """ if 待添加虚线.方向 == self.线段方向: raise ValueError("方向不匹配", self.线段方向, 待添加虚线, self) - self.append(待添加虚线) + self.基础序列.append(待添加虚线) - def 删除(self, 待删除虚线: Union[虚线]): + def _删除(self, 待删除虚线: Union[虚线]): """ :param 待删除虚线: 待删除的虚线 :raises ValueError: 方向不匹配时抛出 """ if 待删除虚线.方向 == self.方向: raise ValueError("方向不匹配", self.线段方向, 待删除虚线, self) - self.remove(待删除虚线) + self.基础序列.remove(待删除虚线) @classmethod - def 新建(cls, 虚线序列: List[虚线], 线段方向: 相对方向) -> "线段特征": + def 新建(cls, 虚线序列: List[虚线], 线段方向: 相对方向) -> 线段特征: """ :param 虚线序列: 基础虚线列表 :param 线段方向: 线段方向 @@ -3483,7 +4215,7 @@ class 线段特征(list): return 线段特征(标识=f"特征<{虚线序列[0].__class__.__name__}>", 基础序列=虚线序列, 线段方向=线段方向) @classmethod - def 静态分析(cls, 虚线序列: List[虚线], 线段方向: 相对方向, 四象: str, 是否忽视: bool = False) -> List["线段特征"]: + def 静态分析(cls, 虚线序列: List[虚线], 线段方向: 相对方向, 四象: str, 是否忽视: bool = False) -> List[线段特征]: """静态分析虚线序列,生成特征序列 :param 虚线序列: 笔/虚线列表 @@ -3514,8 +4246,8 @@ class 线段特征(list): # 条件:向上线段形成顶分型且当前虚线高点高于中,或向下线段形成底分型且当前虚线低点低于中 if (线段方向 is 相对方向.向上 and 结构 is 分型结构.顶 and 当前虚线.高 > 中.高) or (线段方向 is 相对方向.向下 and 结构 is 分型结构.底 and 当前虚线.低 < 中.低): - 小号虚线 = min(中, key=lambda o: o.序号) - 大号虚线 = max(右, key=lambda o: o.序号) + 小号虚线 = min(中.基础序列, key=lambda o: o.序号) + 大号虚线 = max(右.基础序列, key=lambda o: o.序号) fake = 虚线.创建笔(文=小号虚线.文, 武=大号虚线.武, 有效性=False) 特征序列.pop() 特征序列[-1] = 线段特征.新建([fake], 线段方向) @@ -3531,7 +4263,7 @@ class 线段特征(list): # 特征序列非空:检查与最后一个特征序列的方向关系 之前线段特征 = 特征序列[-1] if 相对方向.分析(之前线段特征.高, 之前线段特征.低, 当前虚线.高, 当前虚线.低) in 需要被合并方向序列: - 之前线段特征.添加(当前虚线) + 之前线段特征._添加(当前虚线) else: 特征序列.append(线段特征.新建([当前虚线], 线段方向)) @@ -3555,7 +4287,7 @@ class 线段特征(list): class 特征分型: """特征分型 — 由左右中三个线段特征构成的顶/底分型,用于线段划分算法。 - + 注意: Rust 绑定层不导! :ivar 左: 左侧特征序列 :ivar 中: 中间特征序列 :ivar 右: 右侧特征序列 @@ -3583,22 +4315,34 @@ class 特征分型: return f"特征分型<{self.结构}, {self.中}>" -class 线段(object): - """线段 — 纯静态方法容器,提供线段划分算法的所有函数。 +class 线段: + """纯静态方法容器,提供线段划分算法的所有函数。 - 主要方法: - 检查线段破坏(前一线段, 当前线段) — 检查线段是否被破坏 - 获取线段特征序列(笔序列, 线段方向, 需要被合并方向序列) — 提取特征序列 - 线段有缺口(特征序列, 线段方向, 当前分型序列, 特征分型序列) — 判断线段是否有缺口 - 判断新线段(笔序列) — 判断是否形成新线段 - 静态分析(观察员) — 静态线段分析 - 生成线段序列(笔序列, 观察员) — 从笔序列生成线段序列 + 核心方法: + :meth:`分析` — 流式分析,增量识别线段(主入口) + :meth:`扩展分析` — 流式扩展线段分析(基于线段生成更高级别线段) + + 特征序列: + :meth:`特征分型终结` — 判断线段是否被特征分型终结 + :meth:`特征序列状态` — 返回特征序列的缺口/突破/包含状态 + :meth:`获取缺口` — 获取线段特征序列之间的缺口 + :meth:`四象` — 判断线段的四象模式(涨跌盘等) + + 序列操作: + :meth:`分割序列` — 按中枢将线段基础序列分割为进入段/中枢段/离开段 + :meth:`查找贯穿伤` — 查找贯穿中枢的线段 + :meth:`获取内部中枢序列` — 获取线段内部的笔中枢/段中枢/扩展中枢 + + 辅助判断: + :meth:`判断线段内部是否背驰` — 判断线段内部是否存在背驰 + :meth:`获取所有停顿位置` — 获取线段内所有停顿位置(买卖点候选) + :meth:`是否背驰过` — 返回线段内背驰对应的缠K列表 """ __slots__ = [] @classmethod - def 添加虚线(cls, 段: 虚线, 筆: 虚线): + def _添加虚线(cls, 段: 虚线, 筆: 虚线): """向线段中添加一笔 :param 段: 线段 @@ -3613,32 +4357,33 @@ class 线段(object): 段.基础序列.append(筆) @classmethod - def 武斗(cls, 段: 虚线, 武: 分型, 行号: int): + def _武斗(cls, 段: 虚线, 武: 分型, 行号: int): """更新线段的终点分型(武) :param 段: 线段 :param 武: 新的终点分型 :param 行号: 调用行号(用于调试) """ - # print(f"{段.标识}.武斗[{行号}], ", 武) + # logger.warning(f"{段.标识}.武斗[{行号}], ", 武) if 段.武 is 武: - # print(f"{段.标识}.武斗[{行号}], 相同") + # logger.warning(f"{段.标识}.武斗[{行号}], 相同") return if 段.武.分型特征值 == 武.分型特征值 and 段.武.时间戳 != 武.时间戳: - print(f"{段.标识}.武斗[{行号}], 发现特征值相等但时间戳不同", 段.武, 武) + logger.warning(f"{段.标识}.武斗[{行号}], 发现特征值相等但时间戳不同, {段.武}, {武}") assert 段.文.结构 is not 武.结构, (f"文武结构相同 {行号}", 段.文, 武) if 武.右 is not None and 分型结构.分析(武.左, 武.中, 武.右) is not 武.结构: raise RuntimeError(分型结构.分析(武.左, 武.中, 武.右), 武.结构) if 段.方向 is 相对方向.向上: if 武.分型特征值 < 段.文.分型特征值: raise RuntimeError(f"向上{段.标识}, 结束点 小于 起点", 段.标识, 段.文, 武) - # if max([段.武, 武], key=lambda k: k.分型特征值) is not 武 and 段.模式 == "文武": - # pass # print(colored(f"{段.标识}.武斗[{行号}] 出现回退 从 {段.武} ==>>> {武}", "red", "on_green")) # raise RuntimeError(段.武, 武) + + if 段.武.分型特征值 > 武.分型特征值 and 段.模式 == "文武": + logger.warning(f"{段.标识}.武斗[{行号}] 出现回退 从 {段.武} ==>>> {武}") # raise RuntimeError(段.武, 武) else: if 武.分型特征值 > 段.文.分型特征值: raise RuntimeError(f"向下{段.标识}, 结束点 大于 起点", 段.标识, 段.文, 武) - # if min([段.武, 武], key=lambda k: k.分型特征值) is not 武 and 段.模式 == "文武": - # pass # print(colored(f"{段.标识}.武斗[{行号}] 出现回退 从 {段.武} ==>>> {武}", "red", "on_green")) # raise RuntimeError(段.武, 武) + if 段.武.分型特征值 < 武.分型特征值 and 段.模式 == "文武": + logger.warning(f"{段.标识}.武斗[{行号}] 出现回退 从 {段.武} ==>>> {武}") # raise RuntimeError(段.武, 武) 段.武 = 武 @classmethod @@ -3700,14 +4445,14 @@ class 线段(object): return "小阳" if 段.方向 is 相对方向.向上 else "少阴" @classmethod - def 设置特征序列(cls, 段: 虚线, 序列, 行号): + def _设置特征序列(cls, 段: 虚线, 序列, 行号): """设置特征序列 :param 段: 线段 :param 序列: 特征序列三元组 (左,中,右) :param 行号: 调用行号 """ - # print(f"线段.设置特征序列[{行号}]", self) + # logger.warning(f"线段.设置特征序列[{行号}]", self) if 段.模式 != "文武": return @@ -3718,11 +4463,11 @@ class 线段(object): 段.特征序列 = [左, 中, 右] if 右 is not None: 基础序列 = [] - if 右[-1] not in 段.基础序列: + if 右.基础序列[-1] not in 段.基础序列: raise ValueError() for 元素 in 段.基础序列: 基础序列.append(元素) - if 元素 is 右[-1]: + if 元素 is 右.基础序列[-1]: break if (len(基础序列) >= 6) and (len(基础序列) % 2 == 0): @@ -3733,7 +4478,7 @@ class 线段(object): pass @classmethod - def 刷新特征序列(cls, 段: 虚线, 配置: 缠论配置): + def _刷新特征序列(cls, 段: 虚线, 配置: 缠论配置): """刷新特征序列 :param 段: 线段 @@ -3749,16 +4494,16 @@ class 线段(object): if len(特征序列) >= 3: 分型序列 = 线段特征.获取分型序列(特征序列) if (段.方向 is 相对方向.向上 and 分型序列[-1].结构 is 分型结构.顶) or (段.方向 is 相对方向.向下 and 分型序列[-1].结构 is 分型结构.底): - 线段.设置特征序列(段, [分型序列[-1].左, 分型序列[-1].中, 分型序列[-1].右], sys._getframe().f_lineno) + 线段._设置特征序列(段, [分型序列[-1].左, 分型序列[-1].中, 分型序列[-1].右], sys._getframe().f_lineno) else: - 线段.设置特征序列(段, [特征序列[-2], 特征序列[-1], None], sys._getframe().f_lineno) + 线段._设置特征序列(段, [特征序列[-2], 特征序列[-1], None], sys._getframe().f_lineno) else: 特征序列.extend([None] * (3 - len(特征序列))) - 线段.设置特征序列(段, 特征序列, sys._getframe().f_lineno) + 线段._设置特征序列(段, 特征序列, sys._getframe().f_lineno) @classmethod - def 分割序列(cls, 段: 虚线, 所属中枢: Optional["中枢"] = None) -> Tuple[List[虚线], List[虚线], List[虚线], Optional[虚线]]: + def 分割序列(cls, 段: 虚线, 所属中枢: Optional[中枢] = None) -> Tuple[List[虚线], List[虚线], List[虚线], Optional[虚线]]: """将线段基础序列分割为前/后/第三买卖/贯穿伤四部分 :param 段: 线段 @@ -3767,8 +4512,7 @@ class 线段(object): """ if 段.模式 != "文武": return 段.基础序列[:], [], [], None - if len(段.基础序列) == 0: - print(段.标识, 段.序号) + assert 段.基础序列[0].文 is 段.文, (段.基础序列[0].文, 段.文) 前: List[虚线] = [] 后: List[虚线] = [] @@ -3836,7 +4580,7 @@ class 线段(object): return 前, 后, 第三买卖线, 贯穿伤 @classmethod - def 刷新(cls, 段: 虚线, 配置: 缠论配置): + def _刷新(cls, 段: 虚线, 配置: 缠论配置): """刷新线段的特征序列和内部中枢序列 :param 段: 线段 @@ -3845,36 +4589,36 @@ class 线段(object): if 段.模式 != "文武": return if not len(段.基础序列): - print(" 线段.刷新 基础序列为空") + logger.warning(" 线段.刷新 基础序列为空") return - 线段.刷新特征序列(段, 配置) + 线段._刷新特征序列(段, 配置) 有效特征序列 = [特征 for 特征 in 段.特征序列 if 特征 is not None] if len(有效特征序列) == 3: - 线段.武斗(段, 段.特征序列[1].文, sys._getframe().f_lineno) + 线段._武斗(段, 段.特征序列[1].文, sys._getframe().f_lineno) elif len(有效特征序列) >= 1: 最近特征 = 有效特征序列[-1] - if 最近特征[-1] not in 段.基础序列: - 特征后一笔 = 笔.以武会友(段.基础序列, 最近特征[-1].武) + if 最近特征.基础序列[-1] not in 段.基础序列: + 特征后一笔 = 笔.以武会友(段.基础序列, 最近特征.基础序列[-1].武) else: - 特征后一笔 = 最近特征[-1] + 特征后一笔 = 最近特征.基础序列[-1] if 特征后一笔 is not None: 序号 = 段.基础序列.index(特征后一笔) if 序号 < len(段.基础序列) - 1: 下一笔 = 段.基础序列[序号 + 1] if (段.方向 is 相对方向.向上 and 段.高 <= 下一笔.高) or (段.方向 is 相对方向.向下 and 段.低 >= 下一笔.低): - 线段.武斗(段, 下一笔.武, sys._getframe().f_lineno) + 线段._武斗(段, 下一笔.武, sys._getframe().f_lineno) else: - print(" 线段.刷新 特征后一笔 = None, ", 段, 有效特征序列) + logger.warning(f" 线段.刷新 特征后一笔 = None, {段}, {有效特征序列}") else: raise RuntimeError(len(有效特征序列)) 线段.获取内部中枢序列(段, 配置) @classmethod - def 序列重置(cls, 段: 虚线, 序列: Sequence): + def _序列重置(cls, 段: 虚线, 序列: Sequence): """序列重置 :param 段: 线段 @@ -3909,7 +4653,7 @@ class 线段(object): return None @classmethod - def 获取内部中枢序列(cls, 段: 虚线, 配置: 缠论配置) -> Tuple[List["中枢"], List["中枢"], List["中枢"]]: + def 获取内部中枢序列(cls, 段: 虚线, 配置: 缠论配置) -> Tuple[List[中枢], List[中枢], List[中枢]]: """获取内部中枢序列 :param 段: 线段 @@ -3927,7 +4671,7 @@ class 线段(object): return 段.虚_中枢序列, 段.实_中枢序列, 段.合_中枢序列 # 阴 阳 合 @classmethod - def 基础判断(cls, 左: 虚线, 中: 虚线, 右: 虚线, 关系序列: List[相对方向]) -> bool: + def _基础判断(cls, 左: 虚线, 中: 虚线, 右: 虚线, 关系序列: List[相对方向]) -> bool: """ 连续三笔且重叠 @@ -3991,7 +4735,7 @@ class 线段(object): 待添加线段.前一缺口 = None 线段序列.append(待添加线段) - # print(f"线段._向序列中添加[{行号}]", 待添加虚线) + # logger.warning(f"线段._向序列中添加[{行号}]", 待添加虚线) @classmethod def _弹出线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 配置: 缠论配置, 行号: str): @@ -4013,7 +4757,7 @@ class 线段(object): if 右 is not None: 结构 = 分型结构.分析(左, 中, 右, True, True) if 结构 in (分型结构.顶, 分型结构.底) and not 相对方向.分析(左.高, 左.低, 中.高, 中.低).是否缺口(): - print(colored(f"[警告<{行号}>]:", "yellow"), colored("线段._从序列中删除 发现分型完毕, 且特征序列无缺口", "red"), 待弹出线段) + logger.warning(f"{colored(f'[警告<{行号}>]:', 'yellow')} {colored('线段._从序列中删除 发现分型完毕, 且特征序列无缺口', 'red')} {待弹出线段}") 线段序列.pop() 待弹出线段.前一结束位置 = None @@ -4023,7 +4767,7 @@ class 线段(object): @classmethod def _缺口突破(cls, 线段序列: List[虚线], 配置: 缠论配置, 层级: int) -> bool: - """内部方法:处理缺口突破修正 + """内部方法:处理缺口突破修正, 正常修正 :param 线段序列: 线段列表 :param 配置: 缠论配置 @@ -4031,6 +4775,7 @@ class 线段(object): :return: 是否执行了修正 """ 当前线段 = 线段序列[-1] + assert 当前线段.基础序列, "缺口突破: 当前线段.基础序列为空!" 当前虚线: 虚线 = 当前线段.基础序列[-1] 四象 = 线段.四象(当前线段) 同向 = 当前虚线.方向 is 当前线段.方向 @@ -4051,16 +4796,23 @@ class 线段(object): if not ((四象 == "老阳" and 当前虚线.低 < 当前线段.低) or (四象 == "老阴" and 当前虚线.高 > 当前线段.高)): return False + # 已被修正 + if 线段序列[-2].短路修正: + return False + # 执行修正 序列 = 当前线段.基础序列[:] 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + assert 线段序列, "缺口突破: 线段序列为第二次空!" 当前线段 = 线段序列[-1] + assert 当前线段.特征序列[2] is not None 当前线段基础序列 = 线段.分割序列(当前线段)[0] + assert 当前线段基础序列[-1].之后是(序列[0]), "缺口突破: 子序列不连续!" 当前线段基础序列.extend(序列) 当前线段.基础序列[:] = 当前线段基础序列[:] - 线段.刷新(当前线段, 配置) + 线段._刷新(当前线段, 配置) return True @classmethod @@ -4072,6 +4824,7 @@ class 线段(object): :param 层级: 递归深度 :return: 是否执行了修正 """ + assert 线段序列, "非缺口下穿刺: 线段序列为空!" 当前线段 = 线段序列[-1] 四象 = 线段.四象(当前线段) @@ -4084,6 +4837,7 @@ class 线段(object): if not 贯穿伤: return False + assert 贯穿伤 in 当前线段.基础序列, "非缺口下穿刺: 贯穿伤不在基础序列中!" # 切割基础序列 基础序列 = 当前线段.基础序列[当前线段.基础序列.index(贯穿伤) :] @@ -4098,17 +4852,19 @@ class 线段(object): return False # 执行修正 - print(colored(f"[警告<{sys._getframe().f_lineno}, {层级}>]:", "yellow"), colored("线段.修复贯穿伤", "red"), 贯穿伤, 基础序列) # 异常弹出 + logger.warning(f"{colored(f'[警告<{sys._getframe().f_lineno}, {层级}>]:', 'yellow')} {colored('线段.修复贯穿伤', 'red')} {贯穿伤} {基础序列}") # 异常弹出 基础序列 = 当前线段.基础序列[:] 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") + assert 线段序列, "非缺口下穿刺: 第二次线段序列为空!" 当前线段 = 线段序列[-1] 当前线段.特征序列[2] = None + assert 当前线段.基础序列[-1] in 基础序列, "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" for 临时虚线 in 基础序列[基础序列.index(当前线段.基础序列[-1]) + 1 :]: - 线段.添加虚线(当前线段, 临时虚线) - 线段.刷新(当前线段, 配置) + 线段._添加虚线(当前线段, 临时虚线) + 线段._刷新(当前线段, 配置) - if 当前线段.特征序列[2]: + if 当前线段.特征序列[2] is not None: 段 = 虚线.创建线段([左, 中, 右]) 线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}") 段.特征序列[0] = 线段特征.新建([中], 段.方向) @@ -4119,11 +4875,14 @@ class 线段(object): def _缺口后紧急修正(cls, 线段序列: List[虚线], 配置: 缠论配置, 层级: int) -> bool: """内部方法:处理缺口后紧急修正 + 执行逻辑: 在缺口 + :param 线段序列: 线段列表 :param 配置: 缠论配置 :param 层级: 递归深度 :return: 是否执行了修正 """ + assert 线段序列, "缺口后紧急修正: 线段序列为空!" 当前线段 = 线段序列[-1] 四象 = 线段.四象(当前线段) @@ -4159,12 +4918,15 @@ class 线段(object): @classmethod def _修正(cls, 线段序列: List[虚线], 配置: 缠论配置, 层级: int) -> bool: """内部方法:处理线段修正 + 执行逻辑: 当线段第二特征序列超过两次合并后且尾部元素出现与当前线段相同方向时执行修正, + 生成两个线段,生成的线段符合一段被一段破坏的基础逻辑 :param 线段序列: 线段列表 :param 配置: 缠论配置 :param 层级: 递归深度 :return: 是否执行了修正 """ + assert 线段序列, "修正: 线段序列为空!" 当前线段 = 线段序列[-1] # 条件1:配置允许修正且当前线段基础序列长度足够 @@ -4177,6 +4939,8 @@ class 线段(object): # 条件2:之后基础序列长度至少为6 if len(之后基础序列) < 6: return False + if len(之后基础序列) % 2 != 0: + return False # 取倒数第3和第1个元素 前, 后 = 之后基础序列[-3], 之后基础序列[-1] @@ -4205,17 +4969,18 @@ class 线段(object): @classmethod def 分析(cls, 笔序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置, 层级: int = 0, 关系序列=[相对方向.向上, 相对方向.向下]) -> None: - """线段划分核心递归算法 + """线段划分核心递归算法 — 从笔序列递归生成线段。 :param 笔序列: 笔列表 :param 线段序列: 线段列表(原地修改) :param 配置: 缠论配置 :param 层级: 递归深度 :param 关系序列: 允许的方向关系 + :return: None(结果写入线段序列) """ # 递归深度守卫 if 层级 > 256: - print("线段.分析 递归深度超出 256") + logger.warning("线段.分析 递归深度超出 256") return None # raise RuntimeError("线段分析 层级过深") @@ -4225,7 +4990,7 @@ class 线段(object): if not 线段序列: for i in range(1, len(笔序列) - 1): 左, 中, 右 = 笔序列[i - 1], 笔序列[i], 笔序列[i + 1] - if not 线段.基础判断(左, 中, 右, 关系序列): # FIXME 首个线段必须有明确方向 + if not 线段._基础判断(左, 中, 右, 关系序列): # FIXME 首个线段必须有明确方向 continue 段 = 虚线.创建线段([左, 中, 右]) 线段._添加线段(线段序列, 段, 配置, f"{sys._getframe().f_lineno}, {层级}") @@ -4246,7 +5011,7 @@ class 线段(object): # -------------------- 3. 确保当前线段有效 -------------------- 当前线段 = 线段序列[-1] - 线段.序列重置(当前线段, 笔序列) + 线段._序列重置(当前线段, 笔序列) if len(当前线段.基础序列) < 3: 线段._弹出线段(线段序列, 当前线段, 配置, f"{sys._getframe().f_lineno}, {层级}") @@ -4264,7 +5029,7 @@ class 线段(object): 新段.前一缺口 = None 当前线段 = 线段序列[-1] - 线段.刷新(当前线段, 配置) + 线段._刷新(当前线段, 配置) # -------------------- 5. 调用一次全局修正(不循环) -------------------- 线段._缺口突破(线段序列, 配置, 层级) @@ -4282,17 +5047,21 @@ class 线段(object): 当前线段 = 线段序列[-1] 四象 = 线段.四象(当前线段) - 线段.添加虚线(当前线段, 当前虚线) - 线段.刷新(当前线段, 配置) + 线段._添加虚线(当前线段, 当前虚线) + 线段._刷新(当前线段, 配置) # 依次尝试四种修正,任意一个成功则跳过后续处理 + 修正触发 = False if 线段._缺口突破(线段序列, 配置, 层级): - continue - if 线段._非缺口下穿刺(线段序列, 配置, 层级): - continue - if 线段._缺口后紧急修正(线段序列, 配置, 层级): - continue - if 线段._修正(线段序列, 配置, 层级): + 修正触发 = "缺口突破" + elif 线段._非缺口下穿刺(线段序列, 配置, 层级): + 修正触发 = "非缺口下穿刺" + elif 线段._缺口后紧急修正(线段序列, 配置, 层级): + 修正触发 = "缺口后紧急修正" + elif 线段._修正(线段序列, 配置, 层级): + 修正触发 = "修正" + if 修正触发: + logger.warning(f"分析.修正触发={修正触发}, 笔序列长度={len(笔序列)}, 线段序列长度={len(线段序列)}") continue # 无修正触发,且特征序列[2]已存在 → 创建新段 @@ -4309,24 +5078,24 @@ class 线段(object): if 新段.基础序列[-1] is not 当前虚线: if not 新段.基础序列[-1].之后是(当前虚线): return 线段递归分析(笔序列, 线段序列, 配置, 层级 + 1, 关系序列) - 线段.添加虚线(新段, 当前虚线) + 线段._添加虚线(新段, 当前虚线) - 线段.刷新(新段, 配置) + 线段._刷新(新段, 配置) return None @classmethod - def 武终(cls, 段: 虚线, 行号: int): + def _武终(cls, 段: 虚线, 行号: int): """武终 :param 段: 线段 :param 行号: 调用行号 """ if 段.模式 != "文武": - 线段.武斗(段, 段.基础序列[-1].武, 行号) + 线段._武斗(段, 段.基础序列[-1].武, 行号) @classmethod - def 验证序列(cls, 段: 虚线, 序列: Sequence): + def _验证序列(cls, 段: 虚线, 序列: Sequence): """验证序列 :param 段: 线段 @@ -4338,7 +5107,7 @@ class 线段(object): break if 基础序列: if not 基础序列[-1].之后是(元素): - print(" 线段._验证序列 数据不连续") + logger.warning(" 线段._验证序列 数据不连续") break 基础序列.append(元素) 段.基础序列[:] = 基础序列[:] @@ -4362,7 +5131,7 @@ class 线段(object): 待添加线段.序号 = 之前线段.序号 + 1 线段序列.append(待添加线段) - # print(f"线段._向序列中添加[{行号}]", 待添加线段) + # logger.warning(f"线段._向序列中添加[{行号}]", 待添加线段) @classmethod def _弹出扩展线段(cls, 线段序列: List[虚线], 待弹出线段: 虚线, 行号: int): @@ -4379,19 +5148,18 @@ class 线段(object): if 线段序列[-1] is 待弹出线段: drop = 线段序列.pop() 待弹出线段.有效性 = False - # print(f"线段._从序列中删除[{行号}]", 待弹出线段) + # logger.warning(f"线段._从序列中删除[{行号}]", 待弹出线段) return drop raise ValueError("线段._从序列中删除 弹出数据不在列表中", 待弹出线段) @classmethod def 扩展分析(cls, 虚线序列: List[虚线], 线段序列: List[虚线], 配置: 缠论配置) -> None: - """ - 即同级别分析 - 将笔看成线段 + """同级别分析 — 将虚线视为线段进行递归段划分。 :param 虚线序列: 基础虚线列表 :param 线段序列: 扩展线段列表(原地修改) :param 配置: 缠论配置 + :return: None(结果写入线段序列) """ if not 虚线序列: return None @@ -4417,7 +5185,7 @@ class 线段(object): return None 当前线段 = 线段序列[-1] - 线段.验证序列(当前线段, 虚线序列) + 线段._验证序列(当前线段, 虚线序列) if len(当前线段.基础序列) < 3: 线段._弹出扩展线段(线段序列, 当前线段, sys._getframe().f_lineno) return 线段递归扩展分析(虚线序列, 线段序列, 配置) @@ -4426,12 +5194,12 @@ class 线段(object): 左, 中, 右 = 当前线段.基础序列[:3] if not 相对方向.分析(左.高, 左.低, 右.高, 右.低).是否缺口(): 当前线段.基础序列[:] = 当前线段.笔序列[:3] - 线段.武终(当前线段, sys._getframe().f_lineno) + 线段._武终(当前线段, sys._getframe().f_lineno) else: 线段._弹出扩展线段(线段序列, 当前线段, sys._getframe().f_lineno) return 线段递归扩展分析(虚线序列, 线段序列, 配置) - 线段.武终(当前线段, sys._getframe().f_lineno) + 线段._武终(当前线段, sys._getframe().f_lineno) if 当前线段.基础序列[-1].序号 + 3 > 虚线序列[-1].序号: return None @@ -4443,9 +5211,9 @@ class 线段(object): 左, 中, 右 = 虚线序列[i - 1], 虚线序列[i], 虚线序列[i + 1] 相对关系 = 相对方向.分析(左.高, 左.低, 右.高, 右.低) if 相对关系.是否缺口(): - 线段.添加虚线(当前线段, 左) - 线段.添加虚线(当前线段, 中) - 线段.武终(当前线段, sys._getframe().f_lineno) + 线段._添加虚线(当前线段, 左) + 线段._添加虚线(当前线段, 中) + 线段._武终(当前线段, sys._getframe().f_lineno) continue if 左 in 当前线段.基础序列: @@ -4527,7 +5295,7 @@ class 线段(object): return 背驰 or 盘整背驰 @classmethod - def 获取所有停顿位置(cls, 段: 虚线, 观察员: "观察者") -> List[虚线]: + def 获取所有停顿位置(cls, 段: 虚线, 观察员: 观察者) -> List[虚线]: """获取所有停顿位置 :param 段: 线段 @@ -4555,7 +5323,7 @@ class 线段(object): if 线段序列 and 线段序列[-1].武 is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1: 新段 = 虚线.创建线段(线段序列[-1].基础序列[:]) 新段.序号 = self.序号 - 线段.刷新(新段, 观察员.配置) + 线段._刷新(新段, 观察员.配置) if 新段.方向 is self.方向: 结果.append(新段) 当前停顿 = 线段序列[-1].武 @@ -4567,7 +5335,7 @@ class 线段(object): return 结果 @classmethod - def 是否背驰过(cls, 当前段: 虚线, 观察员: "观察者") -> List[缠论K线]: + def 是否背驰过(cls, 当前段: 虚线, 观察员: 观察者) -> List[缠论K线]: """判断线段内是否发生过背驰(遍历所有停顿位置) :param 当前段: 线段 @@ -4584,8 +5352,8 @@ class 线段(object): return 结果 -class 中枢(object): - """中枢 — 三段虚线重叠区间构成的价格中枢,支持延伸和扩展。 +class 中枢: + """三段虚线重叠区间构成的价格中枢,支持延伸和扩展。 :ivar 序号: 序号 :ivar 标识: 标识 @@ -4601,6 +5369,22 @@ class 中枢(object): :ivar 武: 终点分型 :ivar 方向: 中枢方向(首条虚线的方向翻转) :ivar 离开段: 最后一条虚线 + + 构造方法: + :meth:`创建` — 从左右中三条虚线构造中枢 + :meth:`从序列中获取中枢` — 从虚线序列中提取第一个中枢 + + 流式分析: + :meth:`分析` — 流式分析,增量识别中枢(主入口) + :meth:`基础检查` — 检查三条虚线是否满足中枢重叠条件 + + 实例方法: + :meth:`获取序列` — 获取中枢包含的所有虚线 + :meth:`获取扩展中枢` — 获取中枢的扩展中枢序列 + :meth:`完整性` — 检查中枢虚实序列的完整性 + :meth:`获取数据文本` — 返回中枢关键数据的文本表示 + :meth:`当前状态` — 返回中枢当前状态描述 + :meth:`设置第三买卖线` — 设置第三类买卖点关联虚线 """ __slots__ = ["序号", "标识", "级别", "基础序列", "第三买卖线", "本级_第三买卖线"] @@ -4619,7 +5403,7 @@ class 中枢(object): self.第三买卖线: Optional[虚线] = None self.本级_第三买卖线: Optional[虚线] = None - def 添加虚线(self, 实线: 虚线): + def _添加虚线(self, 实线: 虚线): """向中枢添加新虚线(延伸),重置第三买卖线 :param 实线: 新虚线 @@ -4674,8 +5458,6 @@ class 中枢(object): """ :return: 全区间最高价 """ - if len(self.基础序列) > 3: - return max(self.基础序列, key=lambda o: o.高).高 return max(self.基础序列, key=lambda o: o.高).高 @property @@ -4683,8 +5465,6 @@ class 中枢(object): """ :return: 全区间最低价 """ - if len(self.基础序列) > 3: - return min(self.基础序列, key=lambda o: o.低).低 return min(self.基础序列, key=lambda o: o.低).低 @property @@ -4748,7 +5528,7 @@ class 中枢(object): 线段.扩展分析(self.基础序列, 扩展线段, 配置) 中枢.分析(扩展线段, 扩展中枢, False, f"{self.标识}_扩展中枢_") - def 校验合法性(self, 序列: Sequence[虚线], 中枢序列: List["中枢"]) -> bool: + def _校验合法性(self, 序列: Sequence[虚线], 中枢序列: List[中枢]) -> bool: """校验当前中枢在给定序列中是否仍然合法 :param 序列: 基础虚线序列 @@ -4801,7 +5581,7 @@ class 中枢(object): self.设置第三买卖线(None) else: if not 相对方向.分析(self.高, self.低, self.第三买卖线.高, self.第三买卖线.低).是否缺口(): - self.添加虚线(self.第三买卖线) + self._添加虚线(self.第三买卖线) self.设置第三买卖线(None) else: @@ -4866,7 +5646,7 @@ class 中枢(object): return 相对方向.分析(左.高, 左.低, 右.高, 右.低) in (相对方向.向下, 相对方向.向上, 相对方向.顺, 相对方向.逆, 相对方向.同) @classmethod - def 创建(cls, 左: 虚线, 中: 虚线, 右: 虚线, 级别: int, 标识: str = "") -> "中枢": + def 创建(cls, 左: 虚线, 中: 虚线, 右: 虚线, 级别: int, 标识: str = "") -> 中枢: """从三条连续且重叠的虚线创建中枢 :param 左: 左侧虚线 @@ -4885,7 +5665,7 @@ class 中枢(object): ) @classmethod - def 从序列中获取中枢(cls, 虚线序列: Sequence[虚线], 起始方向: 相对方向, 标识: str) -> Optional["中枢"]: + def 从序列中获取中枢(cls, 虚线序列: Sequence[虚线], 起始方向: 相对方向, 标识: str) -> Optional[中枢]: """从虚线序列中按起始方向查找第一个中枢 :param 虚线序列: 虚线列表 @@ -4905,7 +5685,7 @@ class 中枢(object): return None @classmethod - def 向中枢序列尾部添加(cls, 中枢序列: List["中枢"], 待添加中枢: "中枢"): + def _向中枢序列尾部添加(cls, 中枢序列: List[中枢], 待添加中枢: 中枢): """ :param 中枢序列: 中枢列表 :param 待添加中枢: 待添加的中枢 @@ -4917,7 +5697,7 @@ class 中枢(object): 中枢序列.append(待添加中枢) @classmethod - def 从中枢序列尾部弹出(cls, 中枢序列: List["中枢"], 待弹出中枢: "中枢") -> Optional["中枢"]: + def _从中枢序列尾部弹出(cls, 中枢序列: List[中枢], 待弹出中枢: 中枢) -> Optional[中枢]: """ :param 中枢序列: 中枢列表 :param 待弹出中枢: 待弹出的中枢 @@ -4930,14 +5710,15 @@ class 中枢(object): return None @classmethod - def 分析(cls, 虚线序列: Sequence[虚线], 中枢序列: List["中枢"], 跳过首部: bool = True, 标识: str = "", 层级: int = 0) -> None: - """中枢识别核心递归算法 + def 分析(cls, 虚线序列: Sequence[虚线], 中枢序列: List[中枢], 跳过首部: bool = True, 标识: str = "", 层级: int = 0) -> None: + """中枢识别核心递归算法 — 从虚线序列递归生成中枢。 :param 虚线序列: 基础虚线列表 :param 中枢序列: 中枢列表(原地修改) - :param 跳过首部: True: 跳过首元素中枢 + :param 跳过首部: True 时跳过首元素中枢 :param 标识: 中枢标识前缀 :param 层级: 递归深度 + :return: None(结果写入中枢序列) """ if len(虚线序列) < 3: return None @@ -4959,15 +5740,15 @@ class 中枢(object): if 同向相对关系.是否向下() and 左.方向.是否向下(): continue - 中枢.向中枢序列尾部添加(中枢序列, 新中枢) + 中枢._向中枢序列尾部添加(中枢序列, 新中枢) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) return None 当前中枢 = 中枢序列[-1] - if not 当前中枢.校验合法性(虚线序列, 中枢序列): - 中枢.从中枢序列尾部弹出(中枢序列, 当前中枢) + if not 当前中枢._校验合法性(虚线序列, 中枢序列): + 中枢._从中枢序列尾部弹出(中枢序列, 当前中枢) return 中枢递归分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级 + 1) 序号 = 虚线序列.index(当前中枢.基础序列[-1]) + 1 @@ -4983,7 +5764,7 @@ class 中枢(object): else: if not 基础序列: assert 当前中枢.基础序列[-1].之后是(当前虚线), (当前中枢.基础序列[-1], 当前虚线) - 当前中枢.添加虚线(当前虚线) + 当前中枢._添加虚线(当前虚线) else: 基础序列.append(当前虚线) @@ -4992,22 +5773,40 @@ class 中枢(object): if 新中枢 is None: 基础序列.pop(0) else: - 中枢.向中枢序列尾部添加(中枢序列, 新中枢) + 中枢._向中枢序列尾部添加(中枢序列, 新中枢) 当前中枢 = 新中枢 基础序列 = [] return None class 观察者: - """观察者 — 单周期缠论分析器,接收K线流式输入并逐层计算所有层级序列。 + """单周期缠论分析器,接收K线流式输入并逐层计算所有层级序列。 - 核心入口为 增加原始K线(普K),每收到一根新K线就增量更新: + 核心入口为 :meth:`增加原始K线`,每收到一根新K线就增量更新: 原始K线 → 缠论K线(包含处理+合并)→ 分型 → 笔 → 线段 → 中枢 → 买卖点 :ivar 标识: "{符号}:{周期}" - :ivar 当前K线: 最后一根原始K线 - :ivar 当前缠K: 最后一根缠论K线 - :ivar 普通K线序列 / 缠论K线序列 / 分型序列 / 笔序列 / 线段序列 / 中枢序列 等 + :ivar 当前K线: 最后一根原始K线(只读) + :ivar 当前缠K: 最后一根缠论K线(只读) + :ivar 观察员: 自身引用(只读,便于内部回调) + + 序列字段: + :ivar 普通K线序列 / :ivar 缠论K线序列 / :ivar 分型序列 + :ivar 笔序列 / :ivar 线段序列 / :ivar 中枢序列 + :ivar 买卖点序列 / :ivar 扩展线段序列 + + 数据输入: + :meth:`投喂原始数据` — 投喂原始OHLCV数据(自动创建K线) + :meth:`增加原始K线` — 投入一根K线,触发全链路增量分析(主入口) + + 控制: + :meth:`重置基础序列` — 清空并重置所有基础序列 + :meth:`识别买卖点` — 基于中枢和分型扫描买卖点 + :meth:`静态重新分析` — 清空后根据已有中枢和买卖点重新分析 + + 持久化: + :meth:`加载本地数据` — 从文件路径加载数据 + :meth:`读取数据文件` — 类方法,读取数据文件并返回观察者实例 """ def __init__(self, 符号: str, 周期: int, 配置: 缠论配置): @@ -5038,14 +5837,14 @@ class 观察者: return f"{self.符号}:{self.周期}" @property - def 当前K线(self) -> Optional["K线"]: + def 当前K线(self) -> Optional[K线]: """ :return: 最后一根原始K线 """ return self.普通K线序列[-1] if self.普通K线序列 else None @property - def 当前缠K(self) -> Optional["缠论K线"]: + def 当前缠K(self) -> Optional[缠论K线]: """ :return: 最后一根缠论K线 """ @@ -5087,7 +5886,7 @@ class 观察者: :param 收: 收盘价 :param 量: 成交量 """ - self.增加原始K线(K线.创建普K(self.标识, 时间戳, 开, 高, 低, 收, 量, 0, self.周期)) + self.增加原始K线(K线.创建普K(self.符号, 时间戳, 开, 高, 低, 收, 量, 0, self.周期)) @final def 增加原始K线(self, 普K: K线): @@ -5129,10 +5928,11 @@ class 观察者: self.配置.分析扩展线段 and 线段.扩展分析(self.扩展线段序列, self.扩展线段序列_扩展线段, self.配置) self.配置.分析线段中枢 and 中枢.分析(self.扩展线段序列_扩展线段, self.扩展中枢序列_扩展线段) - def 测试_保存数据(self, root: str = None): + def 测试_保存数据(self, root: str = None) -> str: """拆分各序列数据,单独存文件,文件名为对应变量名 :param root: 保存根目录(可选) + :return: 保存目录 """ # 提取各类文本数据 笔序列_文本数据 = [筆.获取数据文本() for 筆 in self.笔序列] @@ -5188,7 +5988,8 @@ class 观察者: with open(文件全路径, "w", encoding="utf-8") as f: f.write("\n".join(数据列表)) f.write("\n") # 向 C99 对齐 - print(f"全部数据拆分保存完成,目录:{保存路径.resolve()}") + logger.warning(f"全部数据拆分保存完成,目录:{保存路径.resolve()}") + return str(保存路径.resolve()) def 识别买卖点(self): """识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现)""" @@ -5247,7 +6048,7 @@ class 观察者: self.投喂原始数据(转化为时间戳(int(时间戳)), 开盘价, 最高价, 最低价, 收盘价, 成交量) @classmethod - def 读取数据文件(cls, 观察员: "观察者", 文件路径: str, 配置=缠论配置()) -> Self: + def 读取数据文件(cls, 观察员: 观察者, 文件路径: str, 配置=缠论配置()) -> Self: """加载数据文件 :param 观察员: 观察者 :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb @@ -5264,12 +6065,22 @@ class 观察者: class K线合成器: - """K线合成器 — 将小周期K线合成为大周期K线,支持多周期级联合成。 + """将小周期K线合成为大周期K线,支持多周期级联合成。 :ivar 标识: 合成器标识 :ivar 周期组: 从小到大排列的周期组 :ivar 当前K线: 各周期当前K线 :ivar 合成K线列表: 各周期已合成的K线列表 + + 数据输入: + :meth:`投喂` — 投喂原始OHLCV数据 + :meth:`投喂K线` — 投喂原始K线,触发多周期合成(主入口) + + 事件: + :meth:`设置事件回调` — 设置K线合成完成时的回调函数 + + 查询: + :meth:`获取当前K线` — 获取指定周期的当前K线 """ def __init__(self, 标识: str, 周期组: List[int], 事件回调: Optional[Callable] = None): @@ -5414,7 +6225,7 @@ class K线合成器: try: self.事件回调(信号类型="K线完成", 标识=self.标识, 周期=周期, 完成K线=完成K线) except Exception as e: - print(f"K线合成器信号回调错误: {e}") + logger.warning(f"K线合成器信号回调错误: {e}") def 获取当前K线(self, 周期: int) -> Optional[K线]: """获取指定周期当前正在合成的K线 @@ -5426,13 +6237,16 @@ class K线合成器: class 立体分析器: - """立体分析器 — 多周期缠论分析器,内部包含K线合成器 + 每周期一个观察者。 + """多周期缠论分析器,内部包含 :class:`K线合成器` + 每周期一个 :class:`观察者`。 通过最小周期合成大周期K线,各周期观察者独立分析,实现多周期联立。 :ivar 周期组: 所有分析周期 :ivar 观察员: 各周期对应的观察者 :ivar K线合成器: 内部K线合成器 + + 数据输入: + :meth:`投喂K线` — 投入最小周期K线,自动合成大周期并驱动各周期分析(主入口) """ def __init__(self, 符号: str, 周期组: List[int], 配置: 缠论配置 = 缠论配置(), 配置组: Dict[int, 缠论配置] = dict()): @@ -5496,7 +6310,11 @@ class 立体分析器: self._单体分析器[周期].增加原始K线(当前K线) def 测试_保存数据(self, root: str = None): - """拆分各序列数据,单独存文件,文件名为对应变量名""" + """拆分各序列数据,单独存文件。 + + :param root: 保存根目录,默认取脚本所在目录 + :return: 数据保存目录路径 + """ # 生成存储根目录 脚本目录 = Path(__file__).parent if not root else root # 取当前脚本所在文件夹 起始时间 = int(self._单体分析器[self.__输入周期].普通K线序列[0].时间戳.timestamp()) @@ -5510,10 +6328,10 @@ class 立体分析器: for 周期 in self.周期组: self._单体分析器[周期].测试_保存数据(保存路径) - print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}") + logger.warning(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}") -def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[()]: +def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[[], 观察者]: """测试_读取数据 :param 观察员: 观察者 :param 配置: 缠论配置 @@ -5524,7 +6342,7 @@ def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[ 启动时间 = datetime.now() 观察者.读取数据文件(观察员, 配置.加载文件路径, 配置) 消耗用时 = datetime.now() - 启动时间 - print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列)) + logger.info(f"测试_读取数据 耗时 {消耗用时} 普K数量 {len(观察员.普通K线序列)}") return 观察员 return 魔法 @@ -5553,7 +6371,7 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置] k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], 周期, 符号) 多级别分析.投喂K线(k线) 消耗用时 = datetime.now() - 启动时间 - print("测试_周期合成", 消耗用时, "普K数量", len(多级别分析._单体分析器[周期].普通K线序列)) + logger.info(f"测试_周期合成 {消耗用时} 普K数量 {len(多级别分析._单体分析器[周期].普通K线序列)}") return 多级别分析 return 魔法 diff --git a/chanlun-py/pyproject.toml b/chanlun-py/pyproject.toml index 09bf3d7..3ce0773 100644 --- a/chanlun-py/pyproject.toml +++ b/chanlun-py/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "maturin" [project] name = "chanlun" -version = "2605.103" +version = "2606.17" description = "缠论技术分析库 — Rust 高性能实现" readme = { file = "README.md", content-type = "text/markdown" } license = { file = "LICENSE", content-type = "text/plain" } @@ -26,6 +26,11 @@ classifiers = [ "Topic :: Office/Business :: Financial :: Investment", ] requires-python = ">=3.9" +dependencies = [ + "termcolor>=3.0", + "loguru>=0.6", + "backtrader==1.9.78.123", +] [project.urls] Homepage = "https://github.com/YuYuKunKun/chanlun.rs" @@ -41,3 +46,11 @@ manifest-path = "Cargo.toml" [tool.pytest.ini_options] testpaths = ["tests"] addopts = ["-v", "--tb=short", "--durations=10"] + +[tool.ruff.lint] +# 启用 FA (flake8-future-annotations) 和 UP (pyupgrade) 规则 +select = ["FA", "UP"] + +# [tool.ruff.lint.flake8-future-annotations] +# 强制在所有文件中注入 from __future__ import annotations +# force-future-annotations = true \ No newline at end of file diff --git a/chanlun-py/src/algorithm_py.rs b/chanlun-py/src/algorithm_py.rs index 47f8cb6..b298a2b 100644 --- a/chanlun-py/src/algorithm_py.rs +++ b/chanlun-py/src/algorithm_py.rs @@ -27,9 +27,9 @@ use crate::structure_py::{dashed_to_py, fractal_to_py}; use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; use std::collections::HashMap; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::RwLock; +use std::sync::atomic::Ordering; // 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性 static HUB_IDENTITY: std::sync::LazyLock>>> = @@ -58,8 +58,8 @@ pub(crate) fn hub_to_py( use crate::business_py::观察者Py; use crate::config_py::缠论配置Py; -use crate::kline_py::{缠论K线Py, K线Py}; -use crate::structure_py::{分型Py, 线段特征Py, 虚线Py}; +use crate::kline_py::{K线Py, 缠论K线Py}; +use crate::structure_py::{分型Py, 虚线Py}; use crate::types_py::相对方向Py; // ========== 背驰分析 ========== @@ -241,122 +241,20 @@ impl 背驰分析Py { /// 笔 — 静态方法容器,提供笔的创建与分析算法。 /// -/// 所有方法均为 staticmethod,直接调用无需实例化。 +/// 所有方法均为 staticmethod/classmethod,直接调用无需实例化。 /// /// 方法: -/// 创建(左分型, 右分型, 序号, 级别, 有效性?, 标识?) -> 虚线 -/// — 在左右分型之间创建一笔 -/// 分析(分型序列, 配置, 序?, 初级序列?, 级别?) -> list[虚线] -/// — 从分型序列中划分出所有笔 -/// 弱化(笔序列) — 根据配置对笔序列执行弱化处理 -/// 分析前检查(分型序列, 配置) -> bool — 检查是否可以启动笔分析 +/// :meth:`以文会友` — 根据起始分型找笔 +/// :meth:`以武会友` — 根据结束分型找笔 +/// :meth:`根据缠K找笔` — 判断缠K是否在笔的文武序号之间 +/// :meth:`分析` — 笔划分核心递归算法 +/// :meth:`获取所有停顿位置` — 获取笔内所有可能的停顿位置(用于背驰检测) +/// :meth:`是否背驰过` — 判断笔内是否发生过MACD趋向背驰 #[pyclass(name = "笔", module = "chanlun._chanlun")] pub struct 笔Py; #[pymethods] impl 笔Py { - #[classmethod] - /// 获取笔内有效缠K数量(考虑笔弱化等配置) - fn 获取缠K数量( - _cls: &Bound<'_, PyType>, - 缠K序列: Vec>, - 笔序列: Vec>, - 配置: &Bound<'_, 缠论配置Py>, - py: Python<'_>, - ) -> PyResult { - let ck_list: Vec> = 缠K序列 - .iter() - .map(|k| Arc::clone(&k.bind(py).borrow().inner)) - .collect(); - let bi_list: Vec> = 笔序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::bi::笔::获取缠K数量( - &ck_list, &bi_list, &config, - )) - } - - #[classmethod] - /// 次高 - fn 次高( - _cls: &Bound<'_, PyType>, - 缠K序列: Vec>, - 取舍: bool, - py: Python<'_>, - ) -> Option> { - let ck_list: Vec> = 缠K序列 - .iter() - .map(|k| Arc::clone(&k.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::bi::笔::次高(&ck_list, 取舍) - .map(|inner| crate::kline_py::chan_kline_to_py(py, inner)) - } - - #[classmethod] - /// 次低 - fn 次低( - _cls: &Bound<'_, PyType>, - 缠K序列: Vec>, - 取舍: bool, - py: Python<'_>, - ) -> Option> { - let ck_list: Vec> = 缠K序列 - .iter() - .map(|k| Arc::clone(&k.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::bi::笔::次低(&ck_list, 取舍) - .map(|inner| crate::kline_py::chan_kline_to_py(py, inner)) - } - - #[classmethod] - /// 实际高点 - fn 实际高点( - _cls: &Bound<'_, PyType>, - 缠K序列: Vec>, - 取舍: bool, - py: Python<'_>, - ) -> Option> { - let ck_list: Vec> = 缠K序列 - .iter() - .map(|k| Arc::clone(&k.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::bi::笔::实际高点(&ck_list, 取舍) - .map(|inner| crate::kline_py::chan_kline_to_py(py, inner)) - } - - #[classmethod] - /// 实际低点 - fn 实际低点( - _cls: &Bound<'_, PyType>, - 缠K序列: Vec>, - 取舍: bool, - py: Python<'_>, - ) -> Option> { - let ck_list: Vec> = 缠K序列 - .iter() - .map(|k| Arc::clone(&k.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::bi::笔::实际低点(&ck_list, 取舍) - .map(|inner| crate::kline_py::chan_kline_to_py(py, inner)) - } - - #[classmethod] - /// 相对关系 - fn 相对关系( - _cls: &Bound<'_, PyType>, - 筆: &Bound<'_, 虚线Py>, - 配置: &Bound<'_, 缠论配置Py>, - py: Python<'_>, - ) -> PyResult { - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::bi::笔::相对关系( - &筆.borrow().inner, - &config, - )) - } - #[classmethod] /// 以文会友 fn 以文会友( @@ -454,58 +352,6 @@ impl 笔Py { } } - #[classmethod] - fn 分析递归( - _cls: &Bound<'_, PyType>, - 当前分型: &Bound<'_, 分型Py>, - 分型序列: Vec>, - 笔序列: Vec>, - 缠K序列: Vec>, - 普K序列: Vec>, - 递归层次: i64, - 配置: &Bound<'_, 缠论配置Py>, - py: Python<'_>, - ) -> PyResult { - let mut fr_seq: Vec> = 分型序列 - .iter() - .map(|f| Arc::clone(&f.bind(py).borrow().inner)) - .collect(); - let mut bi_seq: Vec> = 笔序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let ck_list: Vec> = 缠K序列 - .iter() - .map(|k| Arc::clone(&k.bind(py).borrow().inner)) - .collect(); - let bar_list: Vec> = 普K序列 - .iter() - .map(|k| k.bind(py).borrow().inner.clone()) - .collect(); - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::bi::笔::分析( - Arc::clone(&当前分型.borrow().inner), - &mut fr_seq, - &mut bi_seq, - &ck_list, - &bar_list, - 递归层次, - &config, - )) - } - - #[classmethod] - /// 校验笔的有效性:高/低点是否为实际极值 - fn 自检( - _cls: &Bound<'_, PyType>, - 筆: &Bound<'_, 虚线Py>, - 观察员: &Bound<'_, 观察者Py>, - ) -> bool { - let obs = 观察员.borrow(); - let obs_ref = obs.obs(); - chanlun::algorithm::bi::笔::自检(&筆.borrow().inner, &obs_ref) - } - #[classmethod] /// 获取笔内所有可能的停顿位置(用于背驰检测) fn 获取所有停顿位置( @@ -552,108 +398,13 @@ impl 笔Py { /// /// 辅助方法: /// 扩展分析(基础序列, 扩展结果容器, 配置) — 从基础序列生成扩展分析 -/// 特征序列分析(特征序列, 中枢序列, 配置, 标识?) — 分析特征序列产生中枢 -/// 分析前检查(分型序列, 配置) -> bool /// 四象(虚线段) -> str — 返回"少阳"/"少阴"/"老阳"/"老阴" /// 特征序列状态(虚线段) -> str -/// 虚线段特征序列成立(虚线段, 虚线序列, 配置) -> bool -/// 移除包含后的特征序列(特征序列, 配置) -> list[线段特征] -/// 检查特征序列趋势(特征序列) -> bool #[pyclass(name = "线段", module = "chanlun._chanlun")] pub struct 线段Py; #[pymethods] impl 线段Py { - #[classmethod] - /// 向线段中添加一笔 - fn 添加虚线( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 筆: &Bound<'_, 虚线Py>, - ) -> PyResult<()> { - let bi_rc = Arc::clone(&筆.borrow().inner); - let ref_mut = 段.borrow_mut(); - chanlun::algorithm::segment::线段::添加虚线(&ref_mut.inner, bi_rc); - Ok(()) - } - - #[classmethod] - /// 更新线段的终点分型(武) - fn 武斗( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 武: &Bound<'_, 分型Py>, - 行号: u32, - ) -> PyResult<()> { - let ref_mut = 段.borrow_mut(); - chanlun::algorithm::segment::线段::武斗( - &ref_mut.inner, - &Arc::clone(&武.borrow().inner), - 行号, - ); - Ok(()) - } - - #[classmethod] - /// 武终 - fn 武终(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>, 行号: u32) -> PyResult<()> { - let ref_mut = 段.borrow_mut(); - chanlun::algorithm::segment::线段::武终(&ref_mut.inner, 行号); - Ok(()) - } - - #[classmethod] - /// 验证序列 - fn 验证序列( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 序列: Vec>, - py: Python<'_>, - ) -> PyResult<()> { - let ref_mut = 段.borrow_mut(); - let rc_list: Vec> = 序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::segment::线段::验证序列(&ref_mut.inner, &rc_list); - Ok(()) - } - - #[classmethod] - /// 序列重置 - fn 序列重置( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 序列: Vec>, - py: Python<'_>, - ) -> PyResult<()> { - let ref_mut = 段.borrow_mut(); - let rc_list: Vec> = 序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::segment::线段::序列重置(&ref_mut.inner, &rc_list); - Ok(()) - } - - #[classmethod] - /// 连续三笔且重叠 - fn 基础判断( - _cls: &Bound<'_, PyType>, - 左: &Bound<'_, 虚线Py>, - 中: &Bound<'_, 虚线Py>, - 右: &Bound<'_, 虚线Py>, - 关系序列: Vec<相对方向Py>, - ) -> bool { - let rel_list: Vec = 关系序列.iter().map(|d| d.inner).collect(); - chanlun::algorithm::segment::线段::基础判断( - &左.borrow().inner, - &中.borrow().inner, - &右.borrow().inner, - &rel_list, - ) - } - #[classmethod] /// 判断线段的四象属性 fn 四象(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>) -> String { @@ -684,52 +435,6 @@ impl 线段Py { chanlun::algorithm::segment::线段::特征序列状态(&段.borrow().inner) } - #[classmethod] - /// 设置特征序列 - fn 设置特征序列( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 序列: &Bound<'_, PyAny>, - 行号: u32, - ) -> PyResult<()> { - let seq: Vec>> = if 序列.is_none() - { - vec![] - } else if let Ok(list) = 序列.cast::() { - let mut result = Vec::with_capacity(list.len()); - for item in list.iter() { - if item.is_none() { - result.push(None); - } else { - let feat: PyRef<'_, 线段特征Py> = item.extract()?; - result.push(Some(Arc::clone(&feat.inner))); - } - } - result - } else { - return Err(pyo3::exceptions::PyTypeError::new_err( - "序列 必须是 list 或 None", - )); - }; - let ref_mut = 段.borrow_mut(); - chanlun::algorithm::segment::线段::设置特征序列(&ref_mut.inner, seq, 行号); - Ok(()) - } - - #[classmethod] - /// 刷新特征序列 - fn 刷新特征序列( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 配置: &Bound<'_, 缠论配置Py>, - py: Python<'_>, - ) -> PyResult<()> { - let ref_mut = 段.borrow_mut(); - let config = 配置.borrow().to_rust_config(py)?; - chanlun::algorithm::segment::线段::刷新特征序列(&ref_mut.inner, &config); - Ok(()) - } - #[classmethod] /// 查找贯穿伤 fn 查找贯穿伤( @@ -774,20 +479,6 @@ impl 线段Py { Ok((wrap(a), wrap(b), wrap(c), d.map(|x| dashed_to_py(py, x)))) } - #[classmethod] - /// 刷新线段的特征序列和内部中枢序列 - fn 刷新( - _cls: &Bound<'_, PyType>, - 段: &Bound<'_, 虚线Py>, - 配置: &Bound<'_, 缠论配置Py>, - py: Python<'_>, - ) -> PyResult<()> { - let ref_mut = 段.borrow_mut(); - let config = 配置.borrow().to_rust_config(py)?; - chanlun::algorithm::segment::线段::刷新(&ref_mut.inner, &config); - Ok(()) - } - #[classmethod] /// 获取内部中枢序列 fn 获取内部中枢序列( @@ -811,133 +502,6 @@ impl 线段Py { } #[classmethod] - /// 内部方法:向线段序列添加新线段 - fn _添加线段( - _cls: &Bound<'_, PyType>, - _线段序列: &Bound<'_, PyAny>, - 待添加线段: &Bound<'_, 虚线Py>, - 配置: &Bound<'_, 缠论配置Py>, - 行号: String, - py: Python<'_>, - ) -> PyResult<()> { - let config = 配置.borrow().to_rust_config(py)?; - let mut seg_seq: Vec> = vec![]; - chanlun::algorithm::segment::线段::_添加线段( - &mut seg_seq, - Arc::clone(&待添加线段.borrow().inner), - &config, - 行号, - ); - Ok(()) - } - - #[classmethod] - /// 内部方法:从线段序列弹出最后一个线段 - fn _弹出线段( - _cls: &Bound<'_, PyType>, - _线段序列: &Bound<'_, PyAny>, - 待弹出线段: &Bound<'_, 虚线Py>, - 配置: &Bound<'_, 缠论配置Py>, - 行号: String, - py: Python<'_>, - ) -> PyResult>> { - let config = 配置.borrow().to_rust_config(py)?; - let mut seg_seq: Vec> = vec![]; - let result = chanlun::algorithm::segment::线段::_弹出线段( - &mut seg_seq, - &Arc::clone(&待弹出线段.borrow().inner), - &config, - 行号, - ); - Ok(result.map(|inner| dashed_to_py(py, inner))) - } - - #[classmethod] - /// 内部方法:处理缺口突破修正 - fn _缺口突破( - _cls: &Bound<'_, PyType>, - 线段序列: Vec>, - 配置: &Bound<'_, 缠论配置Py>, - 层级: i64, - py: Python<'_>, - ) -> PyResult { - let mut seg_seq: Vec> = 线段序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::segment::线段::_缺口突破( - &mut seg_seq, - &config, - 层级, - )) - } - - #[classmethod] - /// 内部方法:处理非缺口下穿刺修正 - fn _非缺口下穿刺( - _cls: &Bound<'_, PyType>, - 线段序列: Vec>, - 配置: &Bound<'_, 缠论配置Py>, - 层级: i64, - py: Python<'_>, - ) -> PyResult { - let mut seg_seq: Vec> = 线段序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::segment::线段::_非缺口下穿刺( - &mut seg_seq, - &config, - 层级, - )) - } - - #[classmethod] - /// 内部方法:处理缺口后紧急修正 - fn _缺口后紧急修正( - _cls: &Bound<'_, PyType>, - 线段序列: Vec>, - 配置: &Bound<'_, 缠论配置Py>, - 层级: i64, - py: Python<'_>, - ) -> PyResult { - let mut seg_seq: Vec> = 线段序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::segment::线段::_缺口后紧急修正( - &mut seg_seq, - &config, - 层级, - )) - } - - #[classmethod] - /// 内部方法:处理线段修正 - fn _修正( - _cls: &Bound<'_, PyType>, - 线段序列: Vec>, - 配置: &Bound<'_, 缠论配置Py>, - 层级: i64, - py: Python<'_>, - ) -> PyResult { - let mut seg_seq: Vec> = 线段序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let config = 配置.borrow().to_rust_config(py)?; - Ok(chanlun::algorithm::segment::线段::_修正( - &mut seg_seq, - &config, - 层级, - )) - } - - #[classmethod] - #[pyo3(signature = (笔序列, 线段序列, 配置, 层级 = 0, 关系序列 = None))] /// 线段划分核心递归算法 fn 分析( _cls: &Bound<'_, PyType>, @@ -974,48 +538,6 @@ impl 线段Py { Ok(()) } - #[classmethod] - /// 内部方法:向扩展线段序列添加新线段 - fn _添加扩展线段( - _cls: &Bound<'_, PyType>, - 线段序列: Vec>, - 待添加线段: &Bound<'_, 虚线Py>, - 行号: u32, - py: Python<'_>, - ) -> PyResult<()> { - let mut seg_seq: Vec> = 线段序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::segment::线段::_添加扩展线段( - &mut seg_seq, - Arc::clone(&待添加线段.borrow().inner), - 行号, - ); - Ok(()) - } - - #[classmethod] - /// 内部方法:从扩展线段序列弹出最后一个线段 - fn _弹出扩展线段( - _cls: &Bound<'_, PyType>, - 线段序列: Vec>, - 待弹出线段: &Bound<'_, 虚线Py>, - 行号: u32, - py: Python<'_>, - ) -> PyResult>> { - let mut seg_seq: Vec> = 线段序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let result = chanlun::algorithm::segment::线段::_弹出扩展线段( - &mut seg_seq, - &Arc::clone(&待弹出线段.borrow().inner), - 行号, - ); - Ok(result.map(|inner| dashed_to_py(py, inner))) - } - #[classmethod] /// 即同级别分析 fn 扩展分析( @@ -1111,7 +633,6 @@ impl 线段Py { /// 添加虚线(虚线) — 向中枢追加虚线(延伸) /// 获取序列() -> list[虚线] — 基础序列 + 第三买卖线 /// 获取扩展中枢(扩展中枢列表, 配置) — 基础序列 >= 9 时生成扩展中枢 -/// 校验合法性(虚线序列) -> bool — 根据当前虚线序列校验中枢合法性 /// 设置第三买卖线(虚线|None) — 设置第三类买卖确认线 /// 完整性(虚实="合") -> bool — 中枢是否完整(已有第三买卖线确认) /// 当前状态() -> str — "中枢之上"/"中枢之下"/"中枢之中" @@ -1122,8 +643,6 @@ impl 线段Py { /// 创建(左虚线, 中虚线, 右虚线, 级别, 标识?) -> 中枢 — 创建新中枢 /// 从序列中获取中枢(虚线序列, 允许重叠?, 标识前缀?, 高级中枢容器?...) -> list[中枢] /// — 扫描虚线序列提取所有中枢 -/// 向中枢序列尾部添加(中枢序列, 新中枢) — 维护中枢序列顺序 -/// 从中枢序列尾部弹出(中枢序列, 配置) — 弹出最后一个中枢并更新相邻中枢 /// 分析(虚线序列, 中枢序列容器, 允许重叠?, 标识前缀?, ...) — 中枢分析主流程 #[pyclass(name = "中枢", module = "chanlun._chanlun", from_py_object)] #[derive(Clone)] @@ -1192,11 +711,6 @@ impl 中枢Py { .map(|d| dashed_to_py(py, Arc::clone(d))) } - /// 向中枢添加新虚线(延伸),重置第三买卖线 - fn 添加虚线(&self, 实线: &Bound<'_, 虚线Py>) { - self.inner.添加虚线(Arc::clone(&实线.borrow().inner)); - } - #[getter] /// :return: 图表标题 fn 图表标题(&self) -> String { @@ -1254,7 +768,7 @@ impl 中枢Py { /// 设置第三类买卖点关联虚线 fn 设置第三买卖线(&mut self, 线: &Bound<'_, 虚线Py>) { let inner = Arc::make_mut(&mut self.inner); - inner.设置第三买卖线(Arc::clone(&线.borrow().inner)); + inner.设置第三买卖线(Some(Arc::clone(&线.borrow().inner))); } /// 获取中枢的完整虚线序列(基础序列+第三买卖线) @@ -1271,23 +785,6 @@ impl 中枢Py { self.inner.获取数据文本() } - #[pyo3(signature = (序列, 中枢序列 = None))] - /// 校验当前中枢在给定序列中是否仍然合法 - fn 校验合法性( - &mut self, - 序列: Vec>, - 中枢序列: Option>>, - py: Python<'_>, - ) -> bool { - let _ = 中枢序列; // Python 版声明了此参数但未使用 - let rc_list: Vec> = 序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - let inner = Arc::make_mut(&mut self.inner); - inner.校验合法性(&rc_list) - } - #[pyo3(signature = (虚实 = "合"))] /// 判断中枢是否完整(是否有第三买卖点或内部中枢离开) fn 完整性(&self, 虚实: &str) -> bool { @@ -1389,58 +886,6 @@ impl 中枢Py { hub_to_py(_cls.py(), inner) } - #[classmethod] - /// 从虚线序列中按起始方向查找第一个中枢 - fn 从序列中获取中枢( - _cls: &Bound<'_, PyType>, - 虚线序列: Vec>, - 起始方向: &Bound<'_, 相对方向Py>, - 标识: &str, - py: Python<'_>, - ) -> Option> { - let rc_list: Vec> = 虚线序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - chanlun::algorithm::hub::中枢::从序列中获取中枢( - &rc_list, - 起始方向.borrow().inner, - 标识, - ) - .map(|inner| hub_to_py(py, inner)) - } - - #[classmethod] - /// :param 中枢序列: 中枢列表 - fn 向中枢序列尾部添加( - _cls: &Bound<'_, PyType>, - 中枢序列: &Bound<'_, PyAny>, - 待添加中枢: &Bound<'_, Self>, - ) -> PyResult<()> { - let py = 中枢序列.py(); - let inner = Arc::clone(&待添加中枢.borrow().inner); - let wrapper = hub_to_py(py, inner); - 中枢序列.call_method1("append", (wrapper,))?; - Ok(()) - } - - #[classmethod] - /// :param 中枢序列: 中枢列表 - fn 从中枢序列尾部弹出( - _cls: &Bound<'_, PyType>, - 中枢序列: &Bound<'_, PyAny>, - _待弹出中枢: &Bound<'_, Self>, - ) -> PyResult> { - let result = 中枢序列.call_method1("pop", ())?; - if result.is_none() { - return Ok(None); - } - let bound: Bound<'_, Self> = result.extract()?; - Ok(Some(Self { - inner: Arc::clone(&bound.borrow().inner), - })) - } - #[classmethod] #[pyo3(signature = (虚线序列, 中枢序列, 跳过首部 = true, 标识 = "", 层级 = 0))] /// 中枢识别核心递归算法 @@ -1464,6 +909,27 @@ impl 中枢Py { chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级); Ok(()) } + + #[classmethod] + /// 从虚线序列中按起始方向查找第一个中枢 + fn 从序列中获取中枢( + _cls: &Bound<'_, PyType>, + 虚线序列: Vec>, + 起始方向: &Bound<'_, 相对方向Py>, + 标识: &str, + py: Python<'_>, + ) -> Option> { + let rc_list: Vec> = 虚线序列 + .iter() + .map(|d| Arc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::hub::中枢::_从序列中获取中枢( + &rc_list, + 起始方向.borrow().inner, + 标识, + ) + .map(|inner| hub_to_py(py, inner)) + } } pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/chanlun-py/src/business_py.rs b/chanlun-py/src/business_py.rs index 2081f5e..f50e11d 100644 --- a/chanlun-py/src/business_py.rs +++ b/chanlun-py/src/business_py.rs @@ -33,7 +33,7 @@ use std::collections::HashMap; use std::sync::Arc; use crate::config_py::缠论配置Py; -use crate::kline_py::{缠论K线Py, K线Py}; +use crate::kline_py::{K线Py, 缠论K线Py}; use crate::types_py::买卖点类型Py; // ========== 基础买卖点 ========== @@ -74,7 +74,6 @@ impl 基础买卖点Py { Arc::clone(&买卖点分型.borrow().inner), 备注, 中枢破位值, - 当前K线.borrow().inner.序号, ), } } @@ -201,11 +200,14 @@ impl 基础买卖点Py { // ========== 买卖点 ========== -/// 买卖点 — 继承 基础买卖点,添加工厂类方法。 +/// 买卖点 — 继承 基础买卖点,提供一二三类及扩展类型(T1/T1P/T2/T2S/T3A/T3B)的工厂类方法。 /// /// 类方法(均返回 买卖点 实例): -/// 一卖点(...) / 一买点(...) / 二卖点(...) / 二买点(...) / 三卖点(...) / 三买点(...) -/// 生成买卖点(特征, 序号, 级别, 分型, 当前缠K) -> 买卖点 +/// :meth:`一卖点` / :meth:`一买点` / :meth:`二卖点` / :meth:`二买点` / :meth:`三卖点` / :meth:`三买点` +/// :meth:`T1卖点` / :meth:`T1买点` / :meth:`T1P卖点` / :meth:`T1P买点` +/// :meth:`T2卖点` / :meth:`T2买点` / :meth:`T2S卖点` / :meth:`T2S买点` +/// :meth:`T3A卖点` / :meth:`T3A买点` / :meth:`T3B卖点` / :meth:`T3B买点` +/// :meth:`生成买卖点` — 根据特征和序号路由到对应构造函数 #[pyclass(name = "买卖点", module = "chanlun._chanlun", extends=基础买卖点Py)] pub struct 买卖点Py; @@ -228,7 +230,6 @@ impl 买卖点Py { 标识, 备注, 中枢破位值, - 当前K线.borrow().inner.序号, ), }; let init = PyClassInitializer::from(base).add_subclass(买卖点Py); @@ -252,7 +253,6 @@ impl 买卖点Py { 标识, 备注, 中枢破位值, - 当前K线.borrow().inner.序号, ), }; let init = PyClassInitializer::from(base).add_subclass(买卖点Py); @@ -276,7 +276,6 @@ impl 买卖点Py { 标识, 备注, 中枢破位值, - 当前K线.borrow().inner.序号, ), }; let init = PyClassInitializer::from(base).add_subclass(买卖点Py); @@ -300,7 +299,6 @@ impl 买卖点Py { 标识, 备注, 中枢破位值, - 当前K线.borrow().inner.序号, ), }; let init = PyClassInitializer::from(base).add_subclass(买卖点Py); @@ -324,7 +322,6 @@ impl 买卖点Py { 标识, 备注, 中枢破位值, - 当前K线.borrow().inner.序号, ), }; let init = PyClassInitializer::from(base).add_subclass(买卖点Py); @@ -348,7 +345,282 @@ impl 买卖点Py { 标识, 备注, 中枢破位值, - 当前K线.borrow().inner.序号, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T1卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T1卖点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T1买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T1买点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T1P卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T1P卖点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T1P买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T1P买点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T2卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T2卖点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T2买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T2买点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T2S卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T2S卖点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T2S买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T2S买点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T3A卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T3A卖点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T3A买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T3A买点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T3B卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T3B卖点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, + ), + }; + let init = PyClassInitializer::from(base).add_subclass(买卖点Py); + Ok(Bound::new(py, init)?.unbind()) + } + + #[classmethod] + fn T3B买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + py: Python<'_>, + ) -> PyResult> { + let base = 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::T3B买点( + Arc::clone(&买卖点分型.borrow().inner), + 当前K线.borrow().inner.clone(), + 标识, + 备注, + 中枢破位值, ), }; let init = PyClassInitializer::from(base).add_subclass(买卖点Py); @@ -546,14 +818,30 @@ impl 观察者Py { 缠论配置Py::from_rust_config(&self.obs().配置) } - /// 清空所有分析序列,重置为初始状态 - fn 重置基础序列(&mut self) { + /// 清空所有分析序列,重置为初始状态(内部实现) + #[pyo3(name = "_重置基础序列")] + fn 重置基础序列_impl(&mut self) -> PyResult<()> { self.obs_mut().重置基础序列(); + Ok(()) } - /// 核心入口 — 投喂一根原始K线,增量更新所有层级 - fn 增加原始K线(&mut self, 普K: &Bound<'_, K线Py>) { + /// 清空所有分析序列,重置为初始状态(公开分发器,支持子类重写) + fn 重置基础序列(slf: &Bound<'_, Self>) -> PyResult<()> { + slf.call_method1("_重置基础序列", ())?; + Ok(()) + } + + /// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现) + #[pyo3(name = "_增加原始K线")] + fn 增加原始K线_impl(&mut self, 普K: &Bound<'_, K线Py>) -> PyResult<()> { self.obs_mut().增加原始K线((*普K.borrow().inner).clone()); + Ok(()) + } + + /// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写) + fn 增加原始K线(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> { + slf.call_method1("_增加原始K线", (普K,))?; + Ok(()) } /// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线, @@ -603,15 +891,30 @@ impl 观察者Py { Ok(()) } - /// 静态重新分析(占位方法) - fn 静态重新分析(&mut self) { + /// 静态重新分析(内部实现) + #[pyo3(name = "_静态重新分析")] + fn 静态重新分析_impl(&mut self) -> PyResult<()> { self.obs_mut().静态重新分析(); + Ok(()) } + /// 静态重新分析(公开分发器,支持子类重写) + fn 静态重新分析(slf: &Bound<'_, Self>) -> PyResult<()> { + slf.call_method1("_静态重新分析", ())?; + Ok(()) + } + + /// 拆分各序列数据,单独存文件,文件名为对应变量名(内部实现) + #[pyo3(name = "_测试_保存数据", signature = (root = None))] + fn 测试_保存数据_impl(&self, root: Option<&str>) -> String { + self.obs().测试_保存数据(root) + } + + /// 拆分各序列数据,单独存文件,文件名为对应变量名(公开分发器,支持子类重写) #[pyo3(signature = (root = None))] - /// 拆分各序列数据,单独存文件,文件名为对应变量名 - fn 测试_保存数据(&self, root: Option<&str>) { - self.obs().测试_保存数据(root); + fn 测试_保存数据(slf: &Bound<'_, Self>, root: Option<&str>) -> PyResult { + let result = slf.call_method1("_测试_保存数据", (root,))?; + result.extract::() } #[classmethod] @@ -977,8 +1280,8 @@ impl 立体分析器Py { } /// 拆分各序列数据,单独存文件,文件名为对应变量名 - fn 测试_保存数据(&self) { - self.inner.测试_保存数据(); + fn 测试_保存数据(&self, root: Option<&str>) { + self.inner.测试_保存数据(root); } #[getter] diff --git a/chanlun-py/src/config_py.rs b/chanlun-py/src/config_py.rs index b56344c..a6975f4 100644 --- a/chanlun-py/src/config_py.rs +++ b/chanlun-py/src/config_py.rs @@ -25,6 +25,7 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; use std::collections::HashMap; +use tracing::warn; /// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。 /// @@ -344,17 +345,17 @@ fn dict_to_rust_config( // 用默认值做基准,只合并类型匹配的字段 let mut merged = default_json.clone(); - if let serde_json::Value::Object(ref input_map) = value { - if let serde_json::Value::Object(ref default_map) = default_json { - for (key, input_val) in input_map { - if let Some(default_val) = default_map.get(key) { - match validate_field(key, input_val, default_val) { - Ok(()) => { - merged[key] = input_val.clone(); - } - Err(msg) => { - eprintln!("\x1b[33m[配置警告]\x1b[m {key}: {msg},已使用默认值 {default_val}"); - } + if let serde_json::Value::Object(ref input_map) = value + && let serde_json::Value::Object(ref default_map) = default_json + { + for (key, input_val) in input_map { + if let Some(default_val) = default_map.get(key) { + match validate_field(key, input_val, default_val) { + Ok(()) => { + merged[key] = input_val.clone(); + } + Err(msg) => { + warn!("[配置警告] {key}: {msg},已使用默认值 {default_val}"); } } } @@ -453,10 +454,10 @@ fn coerce_strings_to_numbers(value: &mut serde_json::Value) { if let Ok(n) = cloned.parse::() { *value = serde_json::Value::Number(serde_json::Number::from(n)); } else if let Ok(n) = cloned.parse::() { - if n.is_finite() { - if let Some(num) = serde_json::Number::from_f64(n) { - *value = serde_json::Value::Number(num); - } + if n.is_finite() + && let Some(num) = serde_json::Number::from_f64(n) + { + *value = serde_json::Value::Number(num); } } else if cloned.eq_ignore_ascii_case("true") { *value = serde_json::Value::Bool(true); @@ -495,10 +496,10 @@ fn coerce_py_value(value: &Bound<'_, PyAny>) -> PyResult> { if let Ok(n) = lower.parse::() { return Ok(n.into_pyobject(py)?.into_any().unbind()); } - if let Ok(n) = lower.parse::() { - if n.is_finite() { - return Ok(n.into_pyobject(py)?.into_any().unbind()); - } + if let Ok(n) = lower.parse::() + && n.is_finite() + { + return Ok(n.into_pyobject(py)?.into_any().unbind()); } Ok(value.clone().unbind()) diff --git a/chanlun-py/src/indicators_py.rs b/chanlun-py/src/indicators_py.rs index 072899f..9ac7d52 100644 --- a/chanlun-py/src/indicators_py.rs +++ b/chanlun-py/src/indicators_py.rs @@ -24,6 +24,7 @@ use pyo3::prelude::*; use pyo3::types::PyType; +use std::sync::Arc; // ========== 平滑异同移动平均线 ========== @@ -603,14 +604,257 @@ impl 随机指标Py { } } +// ========== 布林带 ========== + +/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道。 +/// +/// 属性: +/// 时间戳: int / 周期: int / 标准差倍数: float +/// 上轨: float — 中轨 + 标准差倍数 * 标准差 +/// 中轨: float — 移动平均线 +/// 下轨: float — 中轨 - 标准差倍数 * 标准差 +/// +/// 方法(均为 classmethod,直接构造实例): +/// 首次计算(时间戳, 价格, 周期=20, 标准差倍数=2.0) -> 布林带 +/// 增量计算(前一个布林带, 时间戳, 价格) -> 布林带 +#[pyclass(name = "布林带", module = "chanlun._chanlun", from_py_object)] +#[derive(Clone)] +pub struct 布林带Py { + pub(crate) inner: chanlun::indicators::布林带, +} + +#[pymethods] +impl 布林带Py { + #[new] + fn new() -> Self { + unimplemented!("使用 首次计算 或 增量计算 创建") + } + + #[getter] + fn 时间戳(&self) -> i64 { + self.inner.时间戳 + } + + #[getter] + fn 周期(&self) -> usize { + self.inner.周期 + } + + #[getter] + fn 标准差倍数(&self) -> f64 { + self.inner.标准差倍数 + } + + #[getter] + fn 上轨(&self) -> f64 { + self.inner.上轨 + } + + #[getter] + fn 中轨(&self) -> f64 { + self.inner.中轨 + } + + #[getter] + fn 下轨(&self) -> f64 { + self.inner.下轨 + } + + fn __str__(&self) -> String { + format!( + "布林带(上={:.2}, 中={:.2}, 下={:.2})", + self.inner.上轨, self.inner.中轨, self.inner.下轨 + ) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + #[classmethod] + #[pyo3(signature = (k线, 计算方式, 周期 = 20, 标准差倍数 = 2.0))] + fn 首次计算( + _cls: &Bound<'_, PyType>, + k线: &Bound<'_, PyAny>, + 计算方式: &str, + 周期: usize, + 标准差倍数: f64, + ) -> PyResult { + let 价格 = K线取值(k线, 计算方式)?; + let 时间戳 = 获取时间戳(k线)?; + Ok(Self { + inner: chanlun::indicators::布林带::首次计算(时间戳, 价格, 周期, 标准差倍数), + }) + } + + #[classmethod] + fn 增量计算( + _cls: &Bound<'_, PyType>, + 前一个布林带: &Bound<'_, 布林带Py>, + 当前K线: &Bound<'_, PyAny>, + 计算方式: &str, + ) -> PyResult { + let 价格 = K线取值(当前K线, 计算方式)?; + let 时间戳 = 获取时间戳(当前K线)?; + Ok(Self { + inner: chanlun::indicators::布林带::增量计算( + &前一个布林带.borrow().inner, + 时间戳, + 价格, + ), + }) + } +} + +// ========== 指标容器 ========== + +/// 指标容器 — 挂载在每根 K线上,基于注册表模式持有该时刻所有指标快照。 +/// +/// 与 Python `指标容器` 保持一致: +/// - 默认名称:"macd"/"rsi"/"kdj"/"boll" → 对应指标对象 +/// - 多参数变体:key 格式 "MACD_{快}_{慢}_{信号}" / "RSI_{周期}" 等 +/// - 均线组:通过 "均线" 获取 dict[str, float] +/// - 单值指标:通过 "单值" 获取 dict[str, float] +#[pyclass(name = "指标容器", module = "chanlun._chanlun", skip_from_py_object)] +#[derive(Clone)] +pub struct 指标容器Py { + pub(crate) inner: chanlun::indicators::指标容器, +} + +/// 将 Rust 指标值 转换为 Python 对象 +fn 指标值_to_py(value: &chanlun::indicators::指标值, py: Python<'_>) -> PyResult> { + use chanlun::indicators::指标值; + match value { + 指标值::MACD(m) => { + Ok(Py::new(py, 平滑异同移动平均线Py { inner: m.clone() })?.into_any()) + } + 指标值::RSI(r) => Ok(Py::new(py, 相对强弱指数Py { inner: r.clone() })?.into_any()), + 指标值::KDJ(k) => Ok(Py::new(py, 随机指标Py { inner: k.clone() })?.into_any()), + 指标值::BOLL(b) => Ok(Py::new(py, 布林带Py { inner: b.clone() })?.into_any()), + 指标值::均线(map) | 指标值::单值(map) => { + let dict = pyo3::types::PyDict::new(py); + for (k, v) in map { + dict.set_item(k, *v)?; + } + Ok(dict.into()) + } + } +} + +#[pymethods] +impl 指标容器Py { + #[new] + fn new() -> Self { + Self { + inner: chanlun::indicators::指标容器::new(), + } + } + + /// 按名称获取指标值 + fn 获取(&self, 名称: &str, py: Python<'_>) -> PyResult>> { + match self.inner.获取(名称) { + Some(v) => 指标值_to_py(v, py).map(Some), + None => Ok(None), + } + } + + /// 按名称设置指标值(仅支持 MACD/RSI/KDJ/BOLL 四种类型) + #[pyo3(signature = (名称, 值))] + fn 设置(&mut self, 名称: &str, 值: &Bound<'_, PyAny>) -> PyResult<()> { + use chanlun::indicators::指标值; + if let Ok(m) = 值.cast::<平滑异同移动平均线Py>() { + self.inner + .设置(名称, 指标值::MACD(m.borrow().inner.clone())); + return Ok(()); + } + if let Ok(r) = 值.cast::<相对强弱指数Py>() { + self.inner.设置(名称, 指标值::RSI(r.borrow().inner.clone())); + return Ok(()); + } + if let Ok(k) = 值.cast::<随机指标Py>() { + self.inner.设置(名称, 指标值::KDJ(k.borrow().inner.clone())); + return Ok(()); + } + if let Ok(b) = 值.cast::<布林带Py>() { + self.inner + .设置(名称, 指标值::BOLL(b.borrow().inner.clone())); + return Ok(()); + } + Err(pyo3::exceptions::PyTypeError::new_err( + "不支持的类型,仅支持 MACD/RSI/KDJ/BOLL 指标", + )) + } + + /// 检查是否包含指定名称的指标 + fn 包含(&self, 名称: &str) -> bool { + self.inner.包含(名称) + } + + /// 返回所有已注册的指标名称 + fn keys(&self) -> Vec { + self.inner._数据.keys().cloned().collect() + } + + fn __getitem__(&self, 名称: &str, py: Python<'_>) -> PyResult> { + if self.包含(名称) { + match self.inner.获取(名称) { + Some(v) => 指标值_to_py(v, py), + None => Ok(py.None()), + } + } else { + Err(pyo3::exceptions::PyKeyError::new_err(format!( + "指标 '{}' 不存在", + 名称 + ))) + } + } + + fn __getattr__(&self, 名称: &str, py: Python<'_>) -> PyResult> { + if 名称 == "_数据" { + // 返回内部数据字典的 Python 表示 + let dict = pyo3::types::PyDict::new(py); + for key in self.inner._数据.keys() { + if let Some(v) = self.inner.获取(key) { + dict.set_item(key, 指标值_to_py(v, py)?)?; + } else { + dict.set_item(key, py.None())?; + } + } + 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!( + "指标 '{}' 不存在于 指标容器 中", + 名称 + ))) + } + } + + fn __contains__(&self, 名称: &str) -> bool { + self.包含(名称) + } + + fn __str__(&self) -> String { + self.inner.to_string() + } + + fn __repr__(&self) -> String { + self.__str__() + } +} + // ========== 指标 (static namespace) ========== /// 指标 — 静态工具类,提供指标计算的辅助方法。 /// /// 方法: -/// K线取值(k线, 指标计算方式) -> float (classmethod) -/// 根据计算方式从K线提取数值。 -/// 计算方式: "收盘价" / "开盘价" / "高" / "低" / "均值" 等 +/// :meth:`K线取值` — 根据计算方式从K线提取数值 +/// (计算方式: "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值") #[pyclass(name = "指标", module = "chanlun._chanlun")] pub struct 指标Py; @@ -627,6 +871,144 @@ impl 指标Py { } } +// ========== 均线工具 ========== + +/// 均线工具 — 增量 SMA/EMA 计算的静态方法容器。 +/// +/// 方法: +/// :meth:`增量SMA` — 基于前一根K线的 SMA 值,增量计算当前 SMA +/// :meth:`增量EMA` — 用前一根K线的 EMA 值递推计算当前 EMA +#[pyclass(name = "均线工具", module = "chanlun._chanlun")] +pub struct 均线工具Py; + +#[pymethods] +impl 均线工具Py { + /// 基于前一根K线的 SMA 值,增量计算当前 SMA + #[staticmethod] + #[pyo3(signature = (普K序列, period, 计算方式))] + fn 增量SMA( + 普K序列: Vec>, + period: i64, + 计算方式: &str, + py: Python<'_>, + ) -> PyResult { + if 普K序列.is_empty() { + return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空")); + } + let n = 普K序列.len(); + // 提取所有K线值(一次性 borrow) + let values: Vec = 普K序列 + .iter() + .map(|k| { + let inner = &k.bind(py).borrow().inner; + chanlun::indicators::K线取值( + inner.开盘价, + inner.高, + inner.低, + inner.收盘价, + 计算方式, + ) + }) + .collect(); + + if n <= period as usize { + let start = n.saturating_sub(period as usize); + let sum: f64 = values[start..].iter().sum(); + return Ok(sum / (n.max(1)) as f64); + } + + let prev_key = format!("SMA_{}", period); + // 尝试从前一根K线的均线缓存中读取 + let prev_cached = 普K序列[n - 2] + .bind(py) + .borrow() + .inner + .指标 + .read() + .unwrap() + .均线() + .and_then(|m| m.get(&prev_key)) + .copied(); + if let Some(prev) = prev_cached { + let 当前价 = values[n - 1]; + let oldest = values[n - period as usize - 1]; + return Ok(prev + (当前价 - oldest) / period as f64); + } + + // 回退:完整计算最近 period 根K线 + let sum: f64 = values[n - period as usize..].iter().sum(); + Ok(sum / period as f64) + } + + /// 用前一根K线的 EMA 值递推 + #[staticmethod] + #[pyo3(signature = (普K序列, period, 计算方式, 前值 = None))] + fn 增量EMA( + 普K序列: Vec>, + period: i64, + 计算方式: &str, + 前值: Option, + py: Python<'_>, + ) -> PyResult { + if 普K序列.is_empty() { + return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空")); + } + let last = 普K序列.last().unwrap().bind(py).borrow(); + let 当前价 = chanlun::indicators::K线取值( + last.inner.开盘价, + last.inner.高, + last.inner.低, + last.inner.收盘价, + 计算方式, + ); + match 前值 { + None => Ok(当前价), + Some(prev) => { + let k = 2.0 / (period as f64 + 1.0); + Ok(当前价 * k + prev * (1.0 - k)) + } + } + } +} + +// ========== 指标计算器 ========== + +/// 指标计算器 — 在缠K合并之前,增量计算所有开启的指标并挂载到K线上。 +/// +/// 方法: +/// :meth:`计算并挂载` — 增量计算所有开启的指标,将结果写入 ``当前K线.指标`` +#[pyclass(name = "指标计算器", module = "chanlun._chanlun")] +pub struct 指标计算器Py; + +#[pymethods] +impl 指标计算器Py { + /// 增量计算所有开启的指标,将结果写入 当前K线.指标 + #[staticmethod] + fn 计算并挂载( + 当前K线: &Bound<'_, crate::kline_py::K线Py>, + 全序列: Vec>, + 配置: &Bound<'_, crate::config_py::缠论配置Py>, + py: Python<'_>, + ) -> PyResult<()> { + let config = 配置.borrow().to_rust_config(py)?; + // 全序列包含 当前K线 在末尾;Rust 计算并挂载 的 现有序列 不含当前K线 + let 现有序列: Vec> = if 全序列.len() > 1 { + 全序列[..全序列.len() - 1] + .iter() + .map(|k| k.bind(py).borrow().inner.clone()) + .collect() + } else { + Vec::new() + }; + chanlun::indicators::指标计算器::计算并挂载( + &当前K线.borrow().inner, + &现有序列, + &config, + ); + Ok(()) + } +} + // ========== Helper functions ========== pub(crate) fn K线取值(k线: &Bound<'_, PyAny>, 计算方式: &str) -> PyResult { @@ -664,6 +1046,10 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::<平滑异同移动平均线Py>()?; m.add_class::<相对强弱指数Py>()?; m.add_class::<随机指标Py>()?; + m.add_class::<布林带Py>()?; + m.add_class::<指标容器Py>()?; m.add_class::<指标Py>()?; + m.add_class::<均线工具Py>()?; + m.add_class::<指标计算器Py>()?; Ok(()) } diff --git a/chanlun-py/src/kline_py.rs b/chanlun-py/src/kline_py.rs index 146a8be..490bc5e 100644 --- a/chanlun-py/src/kline_py.rs +++ b/chanlun-py/src/kline_py.rs @@ -25,12 +25,15 @@ use pyo3::prelude::*; use pyo3::types::{PyBytes, PyDict, PyType}; use std::collections::HashMap; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::RwLock; +use std::sync::atomic::Ordering; use crate::config_py::缠论配置Py; -use crate::indicators_py::{平滑异同移动平均线Py, 相对强弱指数Py, 随机指标Py}; +use crate::indicators_py::{ + 平滑异同移动平均线Py, 指标容器Py, 相对强弱指数Py, 随机指标Py +}; +use crate::structure_py::fractal_to_py; use crate::types_py::相对方向Py; // ========== K线 ========== @@ -85,9 +88,7 @@ impl K线Py { 开盘价, 收盘价, 成交量, - macd: None, - rsi: None, - kdj: None, + 指标: RwLock::new(chanlun::indicators::指标容器::new()), }), } } @@ -146,25 +147,39 @@ impl K线Py { #[getter] fn macd(&self) -> Option<平滑异同移动平均线Py> { self.inner - .macd - .as_ref() - .map(|m| 平滑异同移动平均线Py { inner: m.clone() }) + .指标 + .read() + .unwrap() + .macd_cloned() + .map(|m| 平滑异同移动平均线Py { inner: m }) } #[getter] fn rsi(&self) -> Option<相对强弱指数Py> { self.inner - .rsi - .as_ref() - .map(|r| 相对强弱指数Py { inner: r.clone() }) + .指标 + .read() + .unwrap() + .rsi_cloned() + .map(|r| 相对强弱指数Py { inner: r }) } #[getter] fn kdj(&self) -> Option<随机指标Py> { self.inner - .kdj - .as_ref() - .map(|k| 随机指标Py { inner: k.clone() }) + .指标 + .read() + .unwrap() + .kdj_cloned() + .map(|k| 随机指标Py { inner: k }) + } + + /// 指标容器 — 包含所有已注册指标(MACD/RSI/KDJ/BOLL/均线/单值) + #[getter] + fn 指标(&self) -> 指标容器Py { + 指标容器Py { + inner: self.inner.指标.read().unwrap().clone(), + } } /// pandas 兼容 — 返回所有字段构成的字典 @@ -190,6 +205,7 @@ impl K线Py { if let Some(v) = self.kdj() { dict.set_item("kdj", v)?; } + dict.set_item("指标", self.指标())?; Ok(dict.into()) } @@ -545,14 +561,14 @@ impl 缠论K线Py { .unwrap() .get(&src_key) .map(|p| p.clone_ref(py)); - if let Some(cached_src) = cached_src { - if let Ok(new_set) = pyo3::types::PySet::empty(py) { - for item in cached_src.bind(py).iter() { - let _ = new_set.add(item); - } - let py_set: Py = new_set.into(); - BSP_CACHE.write().unwrap().insert(dst_key, py_set); + if let Some(cached_src) = cached_src + && let Ok(new_set) = pyo3::types::PySet::empty(py) + { + for item in cached_src.bind(py).iter() { + let _ = new_set.add(item); } + let py_set: Py = new_set.into(); + BSP_CACHE.write().unwrap().insert(dst_key, py_set); } mirror } @@ -583,10 +599,14 @@ impl 缠论K线Py { if let Some(set) = cached { return Ok(set.into_any()); } - // 创建新的 PySet 并存入全局缓存 + // 创建新的 PySet,从 Rust HashSet 同步已有内容 let set = pyo3::types::PySet::empty(py)?; + let bsp_info = self.inner.买卖点信息.read().unwrap(); + for item in bsp_info.iter() { + set.add(item.as_str())?; + } + drop(bsp_info); BSP_CACHE.write().unwrap().insert(key, set.into()); - // 重新读取并返回(无法从 insert 获取 Py 引用,需要重新读) Ok(BSP_CACHE .read() .unwrap() @@ -641,29 +661,6 @@ impl 缠论K线Py { chan_kline_to_py(py, std::sync::Arc::new(inner)) } - #[classmethod] - /// K线包含处理(合并) - fn 兼并( - _cls: &Bound<'_, PyType>, - 之前缠K: Option<&Bound<'_, Self>>, - 当前缠K: &Bound<'_, Self>, - 当前普K: &Bound<'_, K线Py>, - 配置: &Bound<'_, 缠论配置Py>, - py: Python<'_>, - ) -> PyResult<(Option>, Option)> { - let ck_inner = (*当前缠K.borrow().inner).clone(); - let config = 配置.borrow().to_rust_config(py)?; - let prev_ref = 之前缠K.map(|prev| prev.borrow()); - let prev_inner = prev_ref.as_ref().map(|r| r.inner.as_ref()); - let (result, mode) = chanlun::kline::chan_kline::缠论K线::兼并( - prev_inner, - &ck_inner, - &当前普K.borrow().inner, - &config, - ); - Ok((result.map(|rc| chan_kline_to_py(py, rc)), mode)) - } - #[classmethod] /// 分析K线,执行指标计算+包含处理+分型判定 fn 分析( @@ -686,14 +683,14 @@ impl 缠论K线Py { .map(|k| k.bind(py).borrow().inner.clone()) .collect(); - let (status, _fractal) = chanlun::kline::chan_kline::缠论K线::分析( + let (status, fractal) = chanlun::kline::chan_kline::缠论K线::分析( ck_inner, &mut ck_seq, &mut bar_seq, &config, ); - Ok((status, None)) + Ok((status, fractal.map(|f| fractal_to_py(py, f).into_any()))) } #[staticmethod] diff --git a/chanlun-py/src/lib.rs b/chanlun-py/src/lib.rs index bb85a29..c29af8b 100644 --- a/chanlun-py/src/lib.rs +++ b/chanlun-py/src/lib.rs @@ -25,7 +25,80 @@ #![allow(non_snake_case, clippy::too_many_arguments)] use pyo3::prelude::*; -use std::sync::atomic::Ordering; +use std::sync::atomic::{AtomicU8, Ordering}; +use std::sync::{Mutex, Once, OnceLock}; + +/// 日志级别: 0=trace, 1=debug, 2=info, 3=warn, 4=error, 5=off +static LOG_LEVEL: AtomicU8 = AtomicU8::new(2); // 默认 info + +type 过滤器句柄 = + tracing_subscriber::reload::Handle; +static 过滤器句柄锁: OnceLock> = OnceLock::new(); +static TRACING_INIT: Once = Once::new(); + +fn 级别数字转名称(n: u8) -> &'static str { + match n { + 0 => "trace", + 1 => "debug", + 2 => "info", + 3 => "warn", + 4 => "error", + 5 => "off", + _ => "unknown", + } +} + +fn 级别名称转数字(name: &str) -> Option { + match name.to_lowercase().as_str() { + "trace" => Some(0), + "debug" => Some(1), + "info" => Some(2), + "warn" => Some(3), + "error" => Some(4), + "off" => Some(5), + _ => None, + } +} + +fn init_tracing() { + TRACING_INIT.call_once(|| { + use chrono::Local; + use std::fmt; + use tracing_subscriber::fmt::format::Format; + use tracing_subscriber::fmt::format::Writer; + use tracing_subscriber::fmt::time::FormatTime; + use tracing_subscriber::layer::SubscriberExt; + use tracing_subscriber::util::SubscriberInitExt; + + struct 本地时间; + impl FormatTime for 本地时间 { + fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result { + write!(w, "{}", Local::now().format("%Y-%m-%d %H:%M:%S%.3f")) + } + } + + let format = Format::default() + .with_timer(本地时间) + .with_target(false) + .with_file(true) + .with_line_number(true) + .with_ansi(true) + .compact(); + + let 初始级别 = tracing_subscriber::EnvFilter::try_from_default_env() + .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info")); + + let (过滤器层, 句柄) = tracing_subscriber::reload::Layer::new(初始级别); + 过滤器句柄锁 + .set(Mutex::new(句柄)) + .expect("过滤器句柄锁只能设置一次"); + + tracing_subscriber::registry() + .with(过滤器层) + .with(tracing_subscriber::fmt::layer().event_format(format)) + .init(); + }); +} mod algorithm_py; mod business_py; @@ -47,12 +120,47 @@ fn set_分型模式(value: bool) { chanlun::structure::fractal_obj::分型模式.store(value, Ordering::Relaxed); } +/// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off") +#[pyfunction] +fn get_log_level() -> &'static str { + 级别数字转名称(LOG_LEVEL.load(Ordering::Relaxed)) +} + +/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off") +/// +/// 设为 "off" 可完全关闭日志输出。 +#[pyfunction] +fn set_log_level(level: &str) -> PyResult<()> { + let 数字 = 级别名称转数字(level).ok_or_else(|| { + pyo3::exceptions::PyValueError::new_err(format!( + "无效日志级别 '{}',有效值: trace, debug, info, warn, error, off", + 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); + Ok(()) +} + /// 缠论技术分析库 — Rust 高性能实现 #[pymodule] /// 缠论技术分析库 — Rust 高性能实现 fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + init_tracing(); 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)?)?; // 阶段 1: 枚举和基础类型 types_py::register(m)?; // 阶段 2: 配置 diff --git a/chanlun-py/src/structure_py.rs b/chanlun-py/src/structure_py.rs index c1346ef..01d665a 100644 --- a/chanlun-py/src/structure_py.rs +++ b/chanlun-py/src/structure_py.rs @@ -25,13 +25,13 @@ use pyo3::prelude::*; use pyo3::types::{PyDict, PyType}; use std::collections::HashMap; -use std::sync::atomic::Ordering; use std::sync::Arc; use std::sync::RwLock; +use std::sync::atomic::Ordering; use crate::algorithm_py::hub_to_py; use crate::config_py::缠论配置Py; -use crate::kline_py::{bar_to_py, 缠论K线Py, K线Py}; +use crate::kline_py::{K线Py, bar_to_py, 缠论K线Py}; // ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ---- @@ -42,8 +42,6 @@ static DASHED_IDENTITY: std::sync::LazyLock>> std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); static SEGFEAT_IDENTITY: std::sync::LazyLock>>> = std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); -static FEATFRAC_IDENTITY: std::sync::LazyLock>>> = - std::sync::LazyLock::new(|| RwLock::new(HashMap::new())); pub(crate) fn fractal_to_py( py: Python<'_>, @@ -121,30 +119,6 @@ pub(crate) fn segfeat_to_py( obj } -pub(crate) fn featfrac_to_py( - py: Python<'_>, - inner: Arc, -) -> Py<特征分型Py> { - let key = Arc::as_ptr(&inner) as usize; - if let Some(cached) = FEATFRAC_IDENTITY - .read() - .unwrap() - .get(&key) - .map(|p| p.clone_ref(py)) - { - return cached; - } - FEATFRAC_IDENTITY - .write() - .unwrap() - .retain(|_, v| v.get_refcnt(py) > 1); - let obj = Py::new(py, 特征分型Py { inner }).unwrap(); - FEATFRAC_IDENTITY - .write() - .unwrap() - .insert(key, obj.clone_ref(py)); - obj -} use crate::types_py::{分型结构Py, 相对方向Py, 缺口Py}; // ========== 分型 ========== @@ -208,17 +182,7 @@ impl 分型Py { #[getter] fn 结构(&self, py: Python<'_>) -> Py<分型结构Py> { - let inner = if chanlun::structure::fractal_obj::分型模式.load(Ordering::Relaxed) { - self.inner.结构 - } else { - self.inner - .中 - .分型 - .read() - .unwrap() - .unwrap_or(chanlun::types::分型结构::散) - }; - crate::types_py::获取分型结构单例(py, inner) + crate::types_py::获取分型结构单例(py, self.inner.结构()) } #[getter] @@ -228,11 +192,25 @@ impl 分型Py { #[getter] fn 分型特征值(&self) -> f64 { - if chanlun::structure::fractal_obj::分型模式.load(Ordering::Relaxed) { - self.inner.分型特征值 - } else { - self.inner.中.分型特征值.get() - } + self.inner.分型特征值() + } + + #[getter] + /// 构造时缓存的 _结构(不受 分型模式 影响) + fn _结构(&self, py: Python<'_>) -> Py<分型结构Py> { + crate::types_py::获取分型结构单例(py, self.inner.结构) + } + + #[getter] + /// 构造时缓存的 _时间戳(不受 分型模式 影响) + fn _时间戳(&self) -> i64 { + self.inner.时间戳 + } + + #[getter] + /// 构造时缓存的 _分型特征值(不受 分型模式 影响) + fn _分型特征值(&self) -> f64 { + self.inner.分型特征值 } fn __str__(&self) -> String { @@ -448,11 +426,16 @@ impl 虚线Py { self.inner.模式.read().unwrap().clone() } - #[getter] - fn _特征序列_显示(&self) -> bool { + #[getter(_特征序列_显示)] + fn get_特征序列_显示(&self) -> bool { self.inner._特征序列_显示.load(Ordering::Relaxed) } + #[setter(_特征序列_显示)] + fn set_特征序列_显示(&mut self, value: bool) { + self.inner._特征序列_显示.store(value, Ordering::Relaxed); + } + #[getter] fn 特征序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); @@ -997,15 +980,10 @@ impl 虚线Py { /// 文: 分型 — 特征序列的起点分型 /// 武: 分型 — 特征序列的终点分型 /// 方向: 相对方向 / 高: float / 低: float -/// -/// 方法: -/// 添加(虚线) — 向特征序列追加虚线元素 -/// 删除(虚线) — 从特征序列移除虚线元素 +/// 基本序列: list[虚线] (基础序列) /// /// 类方法: -/// 新建(序号, 文, 武, 基础序列?) -> 线段特征 -/// 静态分析(虚线序列, 配置) -> 线段特征|None -/// 获取分型序列(虚线序列, 配置) -> list[线段特征] +/// :meth:`静态分析` — 对虚线序列进行静态特征分析,返回 线段特征 列表 #[pyclass(name = "线段特征", module = "chanlun._chanlun", from_py_object)] #[derive(Clone)] pub struct 线段特征Py { @@ -1014,28 +992,6 @@ pub struct 线段特征Py { #[pymethods] impl 线段特征Py { - #[new] - fn new( - 标识: String, - 基础序列: Vec>, - 线段方向: &Bound<'_, 相对方向Py>, - py: Python<'_>, - ) -> Self { - let rc_list: Vec> = 基础序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - Self { - inner: Arc::new(chanlun::structure::segment_feat::线段特征::new( - 标识, - rc_list, - 线段方向.borrow().inner, - )), - } - } - - // ---- getters ---- - #[getter] fn 序号(&self) -> i64 { self.inner.序号 @@ -1043,7 +999,12 @@ impl 线段特征Py { #[getter] fn 标识(&self) -> String { - self.inner.标识.clone() + self.inner.标识.read().unwrap().clone() + } + + #[setter] + fn set_标识(&self, value: String) { + *self.inner.标识.write().unwrap() = value; } #[getter] @@ -1052,9 +1013,9 @@ impl 线段特征Py { } #[getter] - fn 元素(&self, py: Python<'_>) -> PyResult> { + fn 基础序列(&self, py: Python<'_>) -> PyResult> { let list = pyo3::types::PyList::empty(py); - for d in &self.inner.元素 { + for d in &self.inner.基础序列 { list.append(dashed_to_py(py, Arc::clone(d)))?; } Ok(list.into()) @@ -1068,55 +1029,6 @@ impl 线段特征Py { self.__str__() } - fn __len__(&self) -> usize { - self.inner.元素.len() - } - - fn __getitem__(&self, index: isize, py: Python<'_>) -> PyResult> { - let len = self.inner.元素.len() as isize; - let idx = if index < 0 { index + len } else { index }; - if idx < 0 || idx >= len { - return Err(pyo3::exceptions::PyIndexError::new_err(format!( - "线段特征 index {index} out of range (len={len})" - ))); - } - let dash = &self.inner.元素[idx as usize]; - let obj: Py = dashed_to_py(py, Arc::clone(dash)).into(); - Ok(obj) - } - - fn __iter__(&self, py: Python<'_>) -> PyResult> { - let list = pyo3::types::PyList::empty(py); - for d in &self.inner.元素 { - list.append(dashed_to_py(py, Arc::clone(d)))?; - } - list.call_method0("__iter__").map(|iter| iter.into()) - } - - fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool { - if let Ok(other) = other.extract::>() { - return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner); - } - false - } - - fn __hash__(&self) -> u64 { - Arc::as_ptr(&self.inner) as u64 - } - - /// pandas 兼容 — 返回关键标量字段构成的字典 - #[getter] - fn __dict__(&self, py: Python<'_>) -> PyResult> { - let dict = PyDict::new(py); - dict.set_item("序号", self.序号())?; - dict.set_item("标识", self.标识())?; - dict.set_item("线段方向", self.线段方向(py))?; - dict.set_item("图表标题", self.图表标题())?; - Ok(dict.into()) - } - - // ---- instance methods ---- - #[getter] /// :return: 图表标题 fn 图表标题(&self) -> String { @@ -1153,47 +1065,8 @@ impl 线段特征Py { self.inner.低() } - /// :param 待添加虚线: 待添加的虚线 - fn 添加(&mut self, 待添加虚线: &Bound<'_, 虚线Py>) -> PyResult<()> { - let inner = Arc::make_mut(&mut self.inner); - inner - .添加(Arc::clone(&待添加虚线.borrow().inner)) - .map_err(pyo3::exceptions::PyValueError::new_err) - } - - /// :param 待删除虚线: 待删除的虚线 - fn 删除(&mut self, 待删除虚线: &Bound<'_, 虚线Py>) -> PyResult<()> { - let inner = Arc::make_mut(&mut self.inner); - inner - .删除(&Arc::clone(&待删除虚线.borrow().inner)) - .map_err(pyo3::exceptions::PyValueError::new_err) - } - - // ---- classmethods ---- - - #[classmethod] - /// :param 虚线序列: 基础虚线列表 - fn 新建( - _cls: &Bound<'_, PyType>, - 虚线序列: Vec>, - 线段方向: &Bound<'_, 相对方向Py>, - py: Python<'_>, - ) -> Self { - let rc_list: Vec> = 虚线序列 - .iter() - .map(|d| Arc::clone(&d.bind(py).borrow().inner)) - .collect(); - Self { - inner: Arc::new(chanlun::structure::segment_feat::线段特征::新建( - rc_list, - 线段方向.borrow().inner, - )), - } - } - #[classmethod] #[pyo3(signature = (虚线序列, 线段方向, 四象, 是否忽视 = false))] - /// 静态分析虚线序列,生成特征序列 fn 静态分析( _cls: &Bound<'_, PyType>, 虚线序列: Vec>, @@ -1201,117 +1074,22 @@ impl 线段特征Py { 四象: &str, 是否忽视: bool, py: Python<'_>, - ) -> Vec { + ) -> PyResult> { let rc_list: Vec> = 虚线序列 .iter() .map(|d| Arc::clone(&d.bind(py).borrow().inner)) .collect(); - chanlun::structure::segment_feat::线段特征::静态分析( + let result = chanlun::structure::segment_feat::线段特征::静态分析( &rc_list, 线段方向.borrow().inner, 四象, 是否忽视, - ) - .into_iter() - .map(|inner| Self { inner }) - .collect() - } - - #[classmethod] - /// 从特征序列提取特征分型序列 - fn 获取分型序列( - _cls: &Bound<'_, PyType>, - 特征序列: Vec>, - py: Python<'_>, - ) -> Vec> { - let rc_list: Vec> = 特征序列 - .iter() - .map(|s| Arc::clone(&s.bind(py).borrow().inner)) - .collect(); - chanlun::structure::segment_feat::线段特征::获取分型序列(&rc_list) - .into_iter() - .map(|inner| featfrac_to_py(py, Arc::new(inner))) - .collect() - } -} - -// ========== 特征分型 ========== - -/// 特征分型 — 线段特征序列中的分型节点。 -/// -/// 属性 (只读): -/// 左: 线段特征|None / 中: 线段特征 / 右: 线段特征|None -/// 结构: 分型结构 — 顶/底分型判定结果 -#[pyclass(name = "特征分型", module = "chanlun._chanlun", from_py_object)] -#[derive(Clone)] -pub struct 特征分型Py { - pub(crate) inner: Arc, -} - -#[pymethods] -impl 特征分型Py { - #[new] - fn new( - 左: &Bound<'_, 线段特征Py>, - 中: &Bound<'_, 线段特征Py>, - 右: &Bound<'_, 线段特征Py>, - 结构: &Bound<'_, 分型结构Py>, - ) -> Self { - Self { - inner: Arc::new(chanlun::structure::feat_fractal::特征分型::new( - Arc::clone(&左.borrow().inner), - Arc::clone(&中.borrow().inner), - Arc::clone(&右.borrow().inner), - 结构.borrow().inner, - )), + ); + let list = pyo3::types::PyList::empty(py); + for sf in result { + list.append(segfeat_to_py(py, sf))?; } - } - - #[getter] - fn 左(&self, py: Python<'_>) -> Py<线段特征Py> { - segfeat_to_py(py, Arc::clone(&self.inner.左)) - } - - #[getter] - fn 中(&self, py: Python<'_>) -> Py<线段特征Py> { - segfeat_to_py(py, Arc::clone(&self.inner.中)) - } - - #[getter] - fn 右(&self, py: Python<'_>) -> Py<线段特征Py> { - segfeat_to_py(py, Arc::clone(&self.inner.右)) - } - - #[getter] - fn 结构(&self, py: Python<'_>) -> Py<分型结构Py> { - crate::types_py::获取分型结构单例(py, self.inner.结构) - } - - /// pandas 兼容 — 返回关键标量字段构成的字典 - #[getter] - fn __dict__(&self, py: Python<'_>) -> PyResult> { - let dict = PyDict::new(py); - dict.set_item("结构", self.结构(py))?; - Ok(dict.into()) - } - - fn __str__(&self) -> String { - format!("{}", self.inner) - } - - fn __repr__(&self) -> String { - self.__str__() - } - - fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool { - if let Ok(other) = other.extract::>() { - return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner); - } - false - } - - fn __hash__(&self) -> u64 { - Arc::as_ptr(&self.inner) as u64 + Ok(list.into()) } } @@ -1319,6 +1097,5 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { m.add_class::<分型Py>()?; m.add_class::<虚线Py>()?; m.add_class::<线段特征Py>()?; - m.add_class::<特征分型Py>()?; Ok(()) } diff --git a/chanlun-py/src/types_py.rs b/chanlun-py/src/types_py.rs index ffb2cbc..48ca8b0 100644 --- a/chanlun-py/src/types_py.rs +++ b/chanlun-py/src/types_py.rs @@ -27,7 +27,7 @@ use std::sync::Mutex; use pyo3::basic::CompareOp; use pyo3::prelude::*; -use pyo3::types::{PyDict, PyType}; +use pyo3::types::{PyBool, PyDict, PyType}; // ========== 单例缓存 ========== @@ -123,18 +123,22 @@ impl 买卖点类型Py { self.inner.to_string() } - fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { + fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult> { + let py = other.py(); + // 比较:先尝试字符串(Python Enum(str)),再同类型,再 name 属性 let eq = if let Ok(s) = other.extract::() { self.inner.to_string() == s } else if let Ok(other) = other.extract::>() { self.inner == other.inner + } else if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::()) { + self.inner.to_string() == py_name } else { - return Err(pyo3::exceptions::PyNotImplementedError::new_err("")); + return Ok(py.NotImplemented()); }; match op { - CompareOp::Eq => Ok(eq), - CompareOp::Ne => Ok(!eq), - _ => Err(pyo3::exceptions::PyNotImplementedError::new_err("")), + CompareOp::Eq => Ok(PyBool::new(py, eq).as_any().to_owned().unbind()), + CompareOp::Ne => Ok(PyBool::new(py, !eq).as_any().to_owned().unbind()), + _ => Ok(py.NotImplemented()), } } @@ -203,19 +207,44 @@ impl 相对方向Py { format!("{}", self.inner) } - fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { - let Ok(other) = other.extract::>() else { - return Err(pyo3::exceptions::PyNotImplementedError::new_err("")); - }; - let eq = self.inner == other.inner; - match op { - CompareOp::Eq => Ok(eq), - CompareOp::Ne => Ok(!eq), - CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)), - CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)), - CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)), - CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)), + fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult> { + let py = other.py(); + // 同类型比较 + if let Ok(other) = other.extract::>() { + let eq = self.inner == other.inner; + return Ok(match op { + CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(), + CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(), + CompareOp::Lt => PyBool::new(py, (self.inner as u8) < (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + CompareOp::Le => PyBool::new(py, (self.inner as u8) <= (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + CompareOp::Gt => PyBool::new(py, (self.inner as u8) > (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + CompareOp::Ge => PyBool::new(py, (self.inner as u8) >= (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + }); } + // 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.相对方向) + if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::()) { + let self_name = format!("{:?}", self.inner); + let eq = self_name == py_name; + return Ok(match op { + CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(), + CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(), + _ => py.NotImplemented(), + }); + } + // 回退:返回 Python NotImplemented + Ok(py.NotImplemented()) } fn __hash__(&self) -> u64 { @@ -315,19 +344,43 @@ impl 分型结构Py { self.inner.to_string() } - fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { - let Ok(other) = other.extract::>() else { - return Err(pyo3::exceptions::PyNotImplementedError::new_err("")); - }; - let eq = self.inner == other.inner; - match op { - CompareOp::Eq => Ok(eq), - CompareOp::Ne => Ok(!eq), - CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)), - CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)), - CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)), - CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)), + fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult> { + let py = other.py(); + // 同类型比较 + if let Ok(other) = other.extract::>() { + let eq = self.inner == other.inner; + return Ok(match op { + CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(), + CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(), + CompareOp::Lt => PyBool::new(py, (self.inner as u8) < (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + CompareOp::Le => PyBool::new(py, (self.inner as u8) <= (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + CompareOp::Gt => PyBool::new(py, (self.inner as u8) > (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + CompareOp::Ge => PyBool::new(py, (self.inner as u8) >= (other.inner as u8)) + .as_any() + .to_owned() + .unbind(), + }); } + // 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.分型结构) + if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::()) { + let self_name = self.inner.to_string(); + let eq = self_name == py_name; + return Ok(match op { + CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(), + CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(), + _ => py.NotImplemented(), + }); + } + Ok(py.NotImplemented()) } fn __hash__(&self) -> u64 { @@ -374,41 +427,17 @@ impl 分型结构Py { let (中高, 中低) = get_hl(中)?; let (右高, 右低) = get_hl(右)?; - let 左中关系 = chanlun::types::相对方向::分析(左高, 左低, 中高, 中低); - let 中右关系 = chanlun::types::相对方向::分析(中高, 中低, 右高, 右低); - - let 向上类 = |d: chanlun::types::相对方向| d.是否向上(); - let 向下类 = |d: chanlun::types::相对方向| d.是否向下(); - - let result = match (左中关系, 中右关系) { - (d1, d2) if matches!(d1, chanlun::types::相对方向::顺) && !忽视顺序包含 => { - panic!("顺序包含: {:?} {:?}", d1, d2); - } - (d1, d2) if matches!(d2, chanlun::types::相对方向::顺) && !忽视顺序包含 => { - panic!("顺序包含: {:?} {:?}", d1, d2); - } - (a, b) if 向上类(a) && 向上类(b) => chanlun::types::分型结构::上, - (a, b) if 向上类(a) && 向下类(b) => chanlun::types::分型结构::顶, - (a, chanlun::types::相对方向::逆) if 向上类(a) && 可以逆序包含 => { - chanlun::types::分型结构::上 - } - (a, b) if 向下类(a) && 向上类(b) => chanlun::types::分型结构::底, - (a, b) if 向下类(a) && 向下类(b) => chanlun::types::分型结构::下, - (a, chanlun::types::相对方向::逆) if 向下类(a) && 可以逆序包含 => { - chanlun::types::分型结构::下 - } - (chanlun::types::相对方向::逆, a) if 向上类(a) && 可以逆序包含 => { - chanlun::types::分型结构::底 - } - (chanlun::types::相对方向::逆, a) if 向下类(a) && 可以逆序包含 => { - chanlun::types::分型结构::顶 - } - (chanlun::types::相对方向::逆, chanlun::types::相对方向::逆) if 可以逆序包含 => { - chanlun::types::分型结构::散 - } - _ => return Ok(None), - }; - Ok(Some(Self { inner: result })) + Ok(chanlun::types::分型结构::分析_内部( + 左高, + 左低, + 中高, + 中低, + 右高, + 右低, + 可以逆序包含, + 忽视顺序包含, + ) + .map(|inner| Self { inner })) } } @@ -451,23 +480,35 @@ impl 缺口Py { format!("{}", self.inner) } - fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult { + fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult> { + let py = other.py(); let Ok(other) = other.extract::>() else { - return Err(pyo3::exceptions::PyNotImplementedError::new_err("")); + return Ok(py.NotImplemented()); }; + let to_bool = |b: bool| PyBool::new(py, b).as_any().to_owned().unbind(); match op { - CompareOp::Eq => Ok(self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低), - CompareOp::Ne => { - Ok(!(self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低)) - } - CompareOp::Lt => Ok(self.inner.高 < other.inner.高 - || (self.inner.高 == other.inner.高 && self.inner.低 < other.inner.低)), - CompareOp::Le => Ok(self.inner.高 < other.inner.高 - || (self.inner.高 == other.inner.高 && self.inner.低 <= other.inner.低)), - CompareOp::Gt => Ok(self.inner.高 > other.inner.高 - || (self.inner.高 == other.inner.高 && self.inner.低 > other.inner.低)), - CompareOp::Ge => Ok(self.inner.高 > other.inner.高 - || (self.inner.高 == other.inner.高 && self.inner.低 >= other.inner.低)), + CompareOp::Eq => Ok(to_bool( + self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低, + )), + CompareOp::Ne => Ok(to_bool( + !(self.inner.高 == other.inner.高 && self.inner.低 == other.inner.低), + )), + CompareOp::Lt => Ok(to_bool( + self.inner.高 < other.inner.高 + || (self.inner.高 == other.inner.高 && self.inner.低 < other.inner.低), + )), + CompareOp::Le => Ok(to_bool( + self.inner.高 < other.inner.高 + || (self.inner.高 == other.inner.高 && self.inner.低 <= other.inner.低), + )), + CompareOp::Gt => Ok(to_bool( + self.inner.高 > other.inner.高 + || (self.inner.高 == other.inner.高 && self.inner.低 > other.inner.低), + )), + CompareOp::Ge => Ok(to_bool( + self.inner.高 > other.inner.高 + || (self.inner.高 == other.inner.高 && self.inner.低 >= other.inner.低), + )), } } diff --git a/chanlun/Cargo.toml b/chanlun/Cargo.toml index e657875..6342280 100644 --- a/chanlun/Cargo.toml +++ b/chanlun/Cargo.toml @@ -1,8 +1,7 @@ [package] name = "chanlun" -version = "26.5.6" -edition = "2021" -rust-version = "1.70" +version = "26.6.1" +edition = "2024" license = "MIT" description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。" readme = "README.md" @@ -18,3 +17,6 @@ serde = { version = "1", features = ["derive"] } serde_json = "1" byteorder = "1" chrono = { version = "0.4", features = ["serde"] } +cached = "1" +tracing = "0.1" +tracing-subscriber = "0.3" diff --git a/chanlun/src/algorithm/bi.rs b/chanlun/src/algorithm/bi.rs index 0c21b88..e68e25b 100644 --- a/chanlun/src/algorithm/bi.rs +++ b/chanlun/src/algorithm/bi.rs @@ -29,15 +29,16 @@ use crate::kline::chan_kline::缠论K线; use crate::structure::dash_line::虚线; use crate::structure::fractal_obj::分型; use crate::types::{分型结构, 相对方向}; -use std::sync::atomic::Ordering; use std::sync::Arc; +use std::sync::atomic::Ordering; +use tracing::{error, warn}; /// 笔 — 从分型生成笔的算法集合(静态方法命名空间) pub struct 笔; impl 笔 { /// 获取可成笔的缠K数量(考虑弱化模式) - pub fn 获取缠K数量( + pub fn _获取缠K数量( 缠K序列: &[Arc<缠论K线>], 笔序列: &[Arc<虚线>], 配置: &缠论配置, @@ -48,10 +49,10 @@ impl 笔 { } if 配置.笔弱化 && 实际数量 >= 3 { - let 实际高点 = Self::实际高点(缠K序列, 配置.笔内相同终点取舍); - let 实际低点 = Self::实际低点(缠K序列, 配置.笔内相同终点取舍); + let 实际高点 = Self::_实际高点(缠K序列, 配置.笔内相同终点取舍); + let 实际低点 = Self::_实际低点(缠K序列, 配置.笔内相同终点取舍); - if let (Some(ref 高点), Some(ref 低点)) = (&实际高点, &实际低点) { + if let (Some(高点), Some(低点)) = (&实际高点, &实际低点) { let 原始数量 = 1 + (低点.标的K线.read().unwrap().序号 - 高点.标的K线.read().unwrap().序号) .unsigned_abs() as usize; @@ -71,26 +72,25 @@ impl 笔 { .and_then(|l| Self::根据缠K找笔(笔序列, l, 1)) }); - if let Some(ref 筆) = 筆 { - if let (Some(ref 高_k), Some(ref 低_k)) = (&实际高点, &实际低点) { - let 原始数量 = 1 - + (低_k.标的K线.read().unwrap().序号 - - 高_k.标的K线.read().unwrap().序号) - .unsigned_abs() as usize; - // 向上笔 - if 筆.方向().是否向上() - && 低_k.低.get() < 筆.低() - && 原始数量 >= 配置.笔弱化_原始数量 as usize - { - return 配置.笔内元素数量 as usize; - } - // 向下笔 - if 筆.方向().是否向下() - && 低_k.低.get() > 筆.高() - && 原始数量 >= 配置.笔弱化_原始数量 as usize - { - return 配置.笔内元素数量 as usize; - } + if let Some(ref 筆) = 筆 + && let (Some(高_k), Some(低_k)) = (&实际高点, &实际低点) + { + let 原始数量 = 1 + + (低_k.标的K线.read().unwrap().序号 - 高_k.标的K线.read().unwrap().序号) + .unsigned_abs() as usize; + // 向上笔 + if 筆.方向().是否向上() + && 低_k.低.get() < 筆.低() + && 原始数量 >= 配置.笔弱化_原始数量 as usize + { + return 配置.笔内元素数量 as usize; + } + // 向下笔 + if 筆.方向().是否向下() + && 低_k.低.get() > 筆.高() + && 原始数量 >= 配置.笔弱化_原始数量 as usize + { + return 配置.笔内元素数量 as usize; } } } @@ -99,10 +99,7 @@ impl 笔 { } /// 次高 — 排除最高值后的次高点 - pub fn 次高(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { - if 缠K序列.len() < 2 { - return 缠K序列.first().cloned(); - } + pub fn _次高(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { let max_高 = 缠K序列 .iter() .map(|k| k.高.get()) @@ -110,9 +107,6 @@ impl 笔 { // 排除最高值 let filtered: Vec<&Arc<缠论K线>> = 缠K序列.iter().filter(|k| k.高.get() != max_高).collect(); - if filtered.is_empty() { - return 缠K序列.first().cloned(); - } // 筛选次高值 let second_高 = filtered .iter() @@ -137,10 +131,7 @@ impl 笔 { } /// 次低 — 排除最低值后的次低点 - pub fn 次低(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { - if 缠K序列.len() < 2 { - return 缠K序列.first().cloned(); - } + pub fn _次低(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { let min_低 = 缠K序列 .iter() .map(|k| k.低.get()) @@ -148,9 +139,6 @@ impl 笔 { // 排除最低值 let filtered: Vec<&Arc<缠论K线>> = 缠K序列.iter().filter(|k| k.低.get() != min_低).collect(); - if filtered.is_empty() { - return 缠K序列.first().cloned(); - } // 筛选次低值 let second_低 = filtered .iter() @@ -175,7 +163,7 @@ impl 笔 { } /// 实际高点 - pub fn 实际高点(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { + pub fn _实际高点(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { if 缠K序列.is_empty() { return None; } @@ -202,7 +190,7 @@ impl 笔 { } /// 实际低点 - pub fn 实际低点(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { + pub fn _实际低点(缠K序列: &[Arc<缠论K线>], 取舍: bool) -> Option> { if 缠K序列.is_empty() { return None; } @@ -229,7 +217,7 @@ impl 笔 { } /// 判断笔的相对关系是否合理 - pub fn 相对关系(筆: &虚线, 配置: &缠论配置) -> bool { + pub fn _相对关系(筆: &虚线, 配置: &缠论配置) -> bool { let 文分型 = &筆.文; let 武分型 = 筆.武.read().unwrap(); @@ -315,10 +303,12 @@ impl 笔 { ) -> Option> { // Python iterates in reverse: for 筆 in 笔序列[::-1] for b in 笔序列.iter().rev() { - // Python: 筆.文.中.序号 - 偏移 <= 缠K.序号.load(Ordering::Relaxed) <= 筆.武.read().unwrap().中.序号 + // Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号 if b.文.中.序号.load(Ordering::Relaxed) - 偏移 <= 缠K.序号.load(Ordering::Relaxed) && 缠K.序号.load(Ordering::Relaxed) <= b.武.read().unwrap().中.序号.load(Ordering::Relaxed) + && b.文.中.周期 == 缠K.周期 + && b.文.中.标识 == 缠K.标识 { return Some(Arc::clone(b)); } @@ -327,288 +317,20 @@ impl 笔 { } /// 从分型序列中弹出最后一个分型和对应的笔 - fn 弹出旧笔(分型序列: &mut Vec>, 笔序列: &mut Vec>) { - 分型序列.pop(); - if !笔序列.is_empty() { - // Python sets旧笔.有效性.store(False, Ordering::Relaxed); with Rc we just drop the笔 - 笔序列.pop(); + fn _弹出旧笔( + 分型序列: &mut Vec>, 笔序列: &mut Vec>, 行号: u32 + ) { + let 旧分型 = 分型序列.pop(); + if let (Some(旧笔), Some(旧分型)) = (笔序列.pop(), 旧分型) { + assert!( + Arc::as_ptr(&旧笔.武.read().unwrap()) == Arc::as_ptr(&旧分型), + "最后一笔终点错误{}", + 行号 + ); + 旧笔.有效性.store(false, Ordering::Relaxed); } } - /// 核心笔分析 — 使用显式栈模拟递归 - /// - /// 返回: 递归层次数 - pub fn 分析_显式栈( - 初始分型: Arc<分型>, - 分型序列: &mut Vec>, - 笔序列: &mut Vec>, - 缠K序列: &[Arc<缠论K线>], - _普K序列: &[Arc], - 配置: &缠论配置, - ) -> i64 { - enum 栈项 { - 分型(Arc<分型>, i64), - /// 修复错过笔哨兵: 若临时分型被接受为最后一个元素,则扫描武将之后的所有分型 - 修复错过笔 { - 临时分型: Arc<分型>, - 武将缠K: Arc<缠论K线>, - 层次: i64, - }, - } - - let mut 栈: Vec<栈项> = Vec::new(); - 栈.push(栈项::分型(初始分型, 0)); - - while let Some(项) = 栈.pop() { - let (当前分型, 递归层次) = match 项 { - 栈项::分型(fx, lvl) => (fx, lvl), - 栈项::修复错过笔 { - 临时分型, - 武将缠K, - 层次, - } => { - // Python line 2406: only scan if临时分型 was accepted as last element - if !分型序列.is_empty() { - if let Some(last_fx) = 分型序列.last() { - if Arc::as_ptr(last_fx) == Arc::as_ptr(&临时分型) { - if let Some(武_idx) = 缠K序列 - .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(&武将缠K)) - { - let mut 错过: Vec> = Vec::new(); - for ck in &缠K序列[武_idx..] { - if *ck.分型.read().unwrap() == Some(分型结构::底) - || *ck.分型.read().unwrap() == Some(分型结构::顶) - { - if let Some(fx) = - 分型::从缠K序列中获取分型(缠K序列, ck) - { - 错过.push(Arc::new(fx)); - } - } - } - // Push in reverse so first in slice is processed first - for m in 错过.into_iter().rev() { - 栈.push(栈项::分型(m, 层次 + 1)); - } - } - } - } - } - // 当前分型 is already next on the stack (pushed before this哨兵) - continue; - } - }; - - if 递归层次 > 256 { - continue; - } - - if !matches!(当前分型.结构, 分型结构::顶 | 分型结构::底) { - continue; - } - - // Python line 2322-2325: 第一个分型直接追加 - if 分型序列.is_empty() { - 分型序列.push(当前分型); - continue; - } - - let 之前分型 = Arc::clone(分型序列.last().unwrap()); - - // Python line 2330-2335: 清理无效数据 - if 之前分型.中.时间戳.load(Ordering::Relaxed) - == 当前分型.中.时间戳.load(Ordering::Relaxed) - || matches!(之前分型.结构, 分型结构::上 | 分型结构::下) - { - Self::弹出旧笔(分型序列, 笔序列); - if 分型序列.is_empty() { - if 当前分型.右.is_some() { - 分型序列.push(当前分型); - } - continue; - } - } - - let 之前分型 = Arc::clone(分型序列.last().unwrap()); - - // Python line 2338: 时序检查 — skip out-of-order fractals - if 之前分型.中.时间戳.load(Ordering::Relaxed) - > 当前分型.中.时间戳.load(Ordering::Relaxed) - && 之前分型.中.序号.load(Ordering::Relaxed) - - 当前分型.中.序号.load(Ordering::Relaxed) - > 1 - { - continue; - } - - // Python line 2343-2348: 笔弱化模式 - if 配置.笔弱化 && !笔序列.is_empty() { - let 前一笔 = 笔序列.last().unwrap(); - let 前一笔缠K数 = 前一笔.武.read().unwrap().中.序号.load(Ordering::Relaxed) - - 前一笔.文.中.序号.load(Ordering::Relaxed) - + 1; - if 前一笔缠K数 == 3 { - let 破位 = (前一笔.方向().是否向上() - && 前一笔.低() > 当前分型.分型特征值 - && 当前分型.结构 == 分型结构::底) - || (前一笔.方向().是否向下() - && 前一笔.高() < 当前分型.分型特征值 - && 当前分型.结构 == 分型结构::顶); - if 破位 { - Self::弹出旧笔(分型序列, 笔序列); - 栈.push(栈项::分型(当前分型, 递归层次 + 1)); - continue; - } - } - } - - // Re-read之前分型 again after笔弱化 pop - let 之前分型 = Arc::clone(分型序列.last().unwrap()); - - // Python line 2350: 分型结构相反 → 可能成笔 - if 之前分型.结构 != 当前分型.结构 { - 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(&当前分型.中)); - - if let (Some(文_idx), Some(武_idx)) = (文_idx, 武_idx) { - let 基础序列 = &缠K序列[文_idx..=武_idx]; - let 缠K数量 = Self::获取缠K数量(基础序列, 笔序列, 配置); - - if 缠K数量 >= 配置.笔内元素数量 as usize { - // Python line 2354-2357: 文官 always uses false (not 笔内相同终点取舍) - let 文官 = match 之前分型.结构 { - 分型结构::顶 => Self::实际高点(基础序列, false), - _ => Self::实际低点(基础序列, false), - }; - - // Python line 2359-2367: 文官 != 之前分型.中 → adjust - if let Some(ref 文官_k) = 文官 { - if Arc::as_ptr(文官_k) != Arc::as_ptr(&之前分型.中) { - if let Some(临时分型) = - 分型::从缠K序列中获取分型(缠K序列, 文官_k) - { - 栈.push(栈项::分型(当前分型, 递归层次)); - 栈.push(栈项::分型(Arc::new(临时分型), 递归层次 + 1)); - continue; - } - } - } - - // Python line 2369-2372: 武将 - let 武将 = match 当前分型.结构 { - 分型结构::底 => { - Self::实际低点(基础序列, 配置.笔内相同终点取舍) - } - _ => Self::实际高点(基础序列, 配置.笔内相同终点取舍), - }; - - let 新笔 = Arc::new(虚线::创建笔( - Arc::clone(&之前分型), - Arc::clone(&当前分型), - true, - )); - - // Python line 2374-2376: 相对关系 and武将 matches - if Self::相对关系(&新笔, 配置) { - if let Some(ref 武将_k) = 武将 { - if Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) { - Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 新笔); - continue; - } - } - } - - // Python line 2378-2385: 笔次级成笔 - if 配置.笔次级成笔 { - let 武将 = match 当前分型.结构 { - 分型结构::底 => Self::次低(基础序列, 配置.笔内相同终点取舍), - _ => Self::次高(基础序列, 配置.笔内相同终点取舍), - }; - if let Some(ref 武将_k) = 武将 { - if Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) - && Self::相对关系(&新笔, 配置) - { - Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 新笔); - continue; - } - } - } - } else { - // Python line 2388-2390: 元素不足 → 右元素扩展 - if let Some(ref 右) = 当前分型.右 { - if let Some(临时分型) = - 分型::从缠K序列中获取分型(缠K序列, 右) - { - 栈.push(栈项::分型(Arc::new(临时分型), 递归层次 + 1)); - continue; - } - } - } - } - } else { - // Python line 2392-2419: 分型结构相同 → 更强则替换 + 修复错过笔 - let 分型特征值 = 当前分型.分型特征值; - - let 更强 = match 之前分型.结构 { - 分型结构::顶 => 之前分型.分型特征值 < 分型特征值, - 分型结构::底 => 之前分型.分型特征值 > 分型特征值, - _ => false, - }; - - if 更强 { - let 被替换分型 = Arc::clone(&之前分型); - Self::弹出旧笔(分型序列, 笔序列); - - if let Some(k线序列) = 缠论K线::截取(缠K序列, &被替换分型.中, &当前分型.中) - { - let 武将 = match 被替换分型.结构 { - 分型结构::顶 => { - Self::实际低点(&k线序列, 配置.笔内相同终点取舍) - } - _ => Self::实际高点(&k线序列, 配置.笔内相同终点取舍), - }; - - if let Some(ref 武将_k) = 武将 { - if let Some(临时分型) = - 分型::从缠K序列中获取分型(缠K序列, 武将_k) - { - let 临时分型_rc = Arc::new(临时分型); - - if !分型序列.is_empty() { - // Push in reverse processing order (LIFO): - 栈.push(栈项::分型(Arc::clone(&当前分型), 递归层次 + 2)); - 栈.push(栈项::修复错过笔 { - 临时分型: Arc::clone(&临时分型_rc), - 武将缠K: Arc::clone(武将_k), - 层次: 递归层次 + 1, - }); - 栈.push(栈项::分型(临时分型_rc, 递归层次 + 1)); - continue; - } else { - 分型序列.push(当前分型); - continue; - } - } - } - } - - if 分型序列.is_empty() { - 分型序列.push(当前分型); - } else { - 栈.push(栈项::分型(当前分型, 递归层次 + 1)); - } - } - } - } - - 栈.len() as i64 - } - /// 核心笔分析 — 递归实现,逐句对照 chan.py 笔.分析 / 笔递归分析 /// /// 返回: 递归层次数 @@ -623,7 +345,7 @@ impl 笔 { ) -> i64 { // Python line 2315-2317: 递归深度限制 if 递归层次 > 64 { - println!("笔.分析 递归深度超出 64 < {}", 递归层次); + warn!("笔.分析 递归深度超出 64 < {}", 递归层次); } // Python line 2319-2320: 非顶底分型跳过 @@ -641,10 +363,10 @@ impl 笔 { // Python line 2329-2335: 清理无效数据 let 之前分型 = Arc::clone(分型序列.last().unwrap()); - if 之前分型.中.时间戳.load(Ordering::Relaxed) == 当前分型.中.时间戳.load(Ordering::Relaxed) + if 之前分型.时间戳() == 当前分型.时间戳() || matches!(之前分型.结构, 分型结构::上 | 分型结构::下) { - Self::弹出旧笔(分型序列, 笔序列); + Self::_弹出旧笔(分型序列, 笔序列, line!()); if 分型序列.is_empty() { if 当前分型.右.is_some() { 分型::向序列中添加(分型序列, 当前分型); @@ -655,11 +377,11 @@ impl 笔 { // Python line 2337-2341: 时序检查 let 之前分型 = Arc::clone(分型序列.last().unwrap()); - if 之前分型.中.时间戳.load(Ordering::Relaxed) > 当前分型.中.时间戳.load(Ordering::Relaxed) + if 之前分型.时间戳() > 当前分型.时间戳() && 之前分型.中.序号.load(Ordering::Relaxed) - 当前分型.中.序号.load(Ordering::Relaxed) > 1 { - println!("时序错误-{}, {}, {}", 递归层次, 之前分型, 当前分型); + error!("时序错误-{}, {}, {}", 递归层次, 之前分型, 当前分型); return 递归层次; } @@ -677,7 +399,7 @@ impl 笔 { && 前一笔.高() < 当前分型.分型特征值 && 当前分型.结构 == 分型结构::顶); if 破位 { - Self::弹出旧笔(分型序列, 笔序列); + Self::_弹出旧笔(分型序列, 笔序列, line!()); return Self::分析( 当前分型, 分型序列, @@ -702,88 +424,93 @@ impl 笔 { true, )); - if Self::获取缠K数量(&基础序列, 笔序列, 配置) >= 配置.笔内元素数量 as usize + if Self::_获取缠K数量(&基础序列, 笔序列, 配置) >= 配置.笔内元素数量 as usize { // Python line 2354-2357: 文官 let 文官 = match 之前分型.结构 { - 分型结构::顶 => Self::实际高点(&基础序列, false), - _ => Self::实际低点(&基础序列, false), + 分型结构::顶 => Self::_实际高点(&基础序列, false), + _ => Self::_实际低点(&基础序列, false), }; // Python line 2359-2367: 文官调整 - if let Some(ref 文官_k) = 文官 { - if Arc::as_ptr(文官_k) != Arc::as_ptr(&之前分型.中) { - if let Some(临时分型) = - 分型::从缠K序列中获取分型(缠K序列, 文官_k) + if let Some(ref 文官_k) = 文官 + && Arc::as_ptr(文官_k) != Arc::as_ptr(&之前分型.中) + && let Some(临时分型) = + 分型::从缠K序列中获取分型(缠K序列, 文官_k) + { + assert!( + if 之前分型.结构 == 分型结构::顶 && 当前分型.结构 == 分型结构::底 { - let 递归层次 = Self::分析( - Arc::new(临时分型), - 分型序列, - 笔序列, - 缠K序列, - _普K序列, - 递归层次 + 1, - 配置, - ); - return Self::分析( - 当前分型, - 分型序列, - 笔序列, - 缠K序列, - _普K序列, - 递归层次 + 1, - 配置, - ); - } - } + 临时分型.结构 == 分型结构::顶 + } else { + 临时分型.结构 == 分型结构::底 + }, + "文官分型结构不在预期: {}", + 临时分型 + ); + let 递归层次 = Self::分析( + Arc::new(临时分型), + 分型序列, + 笔序列, + 缠K序列, + _普K序列, + 递归层次 + 1, + 配置, + ); + return Self::分析( + 当前分型, + 分型序列, + 笔序列, + 缠K序列, + _普K序列, + 递归层次 + 1, + 配置, + ); } // Python line 2369-2375: 武将 and笔形成 let 武将 = match 当前分型.结构 { - 分型结构::底 => Self::实际低点(&基础序列, 配置.笔内相同终点取舍), - _ => Self::实际高点(&基础序列, 配置.笔内相同终点取舍), + 分型结构::底 => Self::_实际低点(&基础序列, 配置.笔内相同终点取舍), + _ => Self::_实际高点(&基础序列, 配置.笔内相同终点取舍), }; - if Self::相对关系(&当前笔, 配置) { - if let Some(ref 武将_k) = 武将 { - if Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) { - // 直接添加(对照 Python _添加新笔:直接 append) - Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 当前笔); - return 递归层次; - } - } + if Self::_相对关系(&当前笔, 配置) + && let Some(ref 武将_k) = 武将 + && Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) + { + // 直接添加(对照 Python _添加新笔:直接 append) + Self::_添加新笔(分型序列, 笔序列, 当前分型, 当前笔, line!()); + return 递归层次; } // Python line 2378-2385: 笔次级成笔 if 配置.笔次级成笔 { let 武将 = match 当前分型.结构 { - 分型结构::底 => Self::次低(&基础序列, 配置.笔内相同终点取舍), - _ => Self::次高(&基础序列, 配置.笔内相同终点取舍), + 分型结构::底 => Self::_次低(&基础序列, 配置.笔内相同终点取舍), + _ => Self::_次高(&基础序列, 配置.笔内相同终点取舍), }; - if let Some(ref 武将_k) = 武将 { - if Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) - && Self::相对关系(&当前笔, 配置) - { - Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 当前笔); - return 递归层次; - } + if let Some(ref 武将_k) = 武将 + && Arc::as_ptr(武将_k) == Arc::as_ptr(&当前分型.中) + && Self::_相对关系(&当前笔, 配置) + { + Self::_添加新笔(分型序列, 笔序列, 当前分型, 当前笔, line!()); + return 递归层次; } } } else { // Python line 2388-2390: 元素不足 → 右元素扩展 - if let Some(ref 右) = 当前分型.右 { - if let Some(临时分型) = 分型::从缠K序列中获取分型(缠K序列, 右) - { - return Self::分析( - Arc::new(临时分型), - 分型序列, - 笔序列, - 缠K序列, - _普K序列, - 递归层次 + 1, - 配置, - ); - } + if let Some(ref 右) = 当前分型.右 + && let Some(临时分型) = 分型::从缠K序列中获取分型(缠K序列, 右) + { + return Self::分析( + Arc::new(临时分型), + 分型序列, + 笔序列, + 缠K序列, + _普K序列, + 递归层次 + 1, + 配置, + ); } } } @@ -800,78 +527,71 @@ impl 笔 { if 更强 { // 保存被弹出的之前分型(用于修复错过笔的范围计算) let 被替换分型 = Arc::clone(&之前分型); - Self::弹出旧笔(分型序列, 笔序列); + Self::_弹出旧笔(分型序列, 笔序列, line!()); if let Some(k线序列) = 缠论K线::截取(缠K序列, &被替换分型.中, &当前分型.中) { let 武将 = match 被替换分型.结构 { - 分型结构::顶 => Self::实际低点(&k线序列, 配置.笔内相同终点取舍), - _ => Self::实际高点(&k线序列, 配置.笔内相同终点取舍), + 分型结构::顶 => Self::_实际低点(&k线序列, 配置.笔内相同终点取舍), + _ => Self::_实际高点(&k线序列, 配置.笔内相同终点取舍), }; - if let Some(ref 武将_k) = 武将 { - if let Some(临时分型) = + if let Some(ref 武将_k) = 武将 + && let Some(临时分型) = 分型::从缠K序列中获取分型(缠K序列, 武将_k) - { - let 临时分型_rc = Arc::new(临时分型); + { + let 临时分型_rc = Arc::new(临时分型); - if !分型序列.is_empty() { - let mut 递归层次 = Self::分析( - Arc::clone(&临时分型_rc), - 分型序列, - 笔序列, - 缠K序列, - _普K序列, - 递归层次 + 1, - 配置, - ); + if !分型序列.is_empty() { + let mut 递归层次 = Self::分析( + Arc::clone(&临时分型_rc), + 分型序列, + 笔序列, + 缠K序列, + _普K序列, + 递归层次 + 1, + 配置, + ); - // 修复错过的笔: 扫描武将之后的所有分型 - if !分型序列.is_empty() - && Arc::as_ptr(分型序列.last().unwrap()) - == Arc::as_ptr(&临时分型_rc) - { - if let Some(武_idx) = 缠K序列 - .iter() - .position(|k| Arc::as_ptr(k) == Arc::as_ptr(武将_k)) + // 修复错过的笔: 扫描武将之后的所有分型 + 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)) + { + for ck in &缠K序列[武_idx..] { + if (*ck.分型.read().unwrap() == Some(分型结构::底) + || *ck.分型.read().unwrap() == Some(分型结构::顶)) + && let Some(错过分型) = + 分型::从缠K序列中获取分型(缠K序列, ck) { - for ck in &缠K序列[武_idx..] { - if *ck.分型.read().unwrap() == Some(分型结构::底) - || *ck.分型.read().unwrap() == Some(分型结构::顶) - { - if let Some(错过分型) = - 分型::从缠K序列中获取分型( - 缠K序列, ck, - ) - { - let 错过分型_rc = Arc::new(错过分型); - 递归层次 = Self::分析( - Arc::clone(&错过分型_rc), - 分型序列, - 笔序列, - 缠K序列, - _普K序列, - 递归层次 + 1, - 配置, - ); - } - } - } + let 错过分型_rc = Arc::new(错过分型); + 递归层次 = Self::分析( + Arc::clone(&错过分型_rc), + 分型序列, + 笔序列, + 缠K序列, + _普K序列, + 递归层次 + 1, + 配置, + ); } } - - return Self::分析( - 当前分型, - 分型序列, - 笔序列, - 缠K序列, - _普K序列, - 递归层次 + 1, - 配置, - ); - } else { - 分型::向序列中添加(分型序列, 当前分型); } + + return Self::分析( + 当前分型, + 分型序列, + 笔序列, + 缠K序列, + _普K序列, + 递归层次 + 1, + 配置, + ); + } else { + 分型::向序列中添加(分型序列, 当前分型); } } } else if 分型序列.is_empty() { @@ -894,56 +614,79 @@ impl 笔 { } /// 添加新笔到序列(递归版本 — 直接追加,对应 Python _添加新笔) - fn _添加新笔递归( + fn _添加新笔( 分型序列: &mut Vec>, 笔序列: &mut Vec>, 新分型: Arc<分型>, mut 新笔: Arc<虚线>, + 行号: u32, ) { + // 首次添加分型检查(Python line 3537-3538) + if 分型序列.is_empty() && !matches!(新分型.结构, 分型结构::顶 | 分型结构::底) + { + panic!("首次添加分型不为 顶底 {:?}", 新分型); + } + // 添加前分型检查(Python line 3539-3543) + if let Some(前一) = 分型序列.last() { + if 前一.结构() == 新分型.结构() { + panic!("分型相同无法添加 {} {}", 前一, 新分型); + } + if 前一.右.is_none() { + warn!("分型.向序列中添加, 分型异常 {}", 前一); + } + } + 分型序列.push(新分型); - if !笔序列.is_empty() { + + if let Some(前一笔) = 笔序列.last() + && !前一笔.之后是(&新笔) + { + panic!("笔.向序列中添加 不连续 {} {}", 前一笔, 新笔); + } + + if let Some(前一笔) = 笔序列.last() { let seg = Arc::make_mut(&mut 新笔); - seg.序号.store( - 笔序列.last().unwrap().序号.load(Ordering::Relaxed) + 1, - Ordering::Relaxed, - ); - if seg.武.read().unwrap().左.is_none() && seg.武.read().unwrap().右.is_none() { + seg.序号 + .store(前一笔.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed); + if seg.武.read().unwrap().左.is_none() || seg.武.read().unwrap().右.is_none() { seg.有效性.store(false, Ordering::Relaxed); } + if matches!( + 前一笔.武.read().unwrap().结构(), + 分型结构::上 | 分型结构::下 + ) { + error!("_添加新笔[{}] 出现无效分型 {}", 行号, 前一笔); + } } 笔序列.push(新笔); } /// 自检 — 验证笔的有效性(文为实际高/低点,武为实际低/高点) - pub fn 自检(筆: &虚线, 观察员: &观察者) -> bool { + pub fn _自检(筆: &虚线, 观察员: &观察者) -> bool { let 笔序列 = &观察员.笔序列; let 配置 = &观察员.配置; let 基础序列 = 筆.获取缠K序列(&观察员.缠论K线序列); - if Self::获取缠K数量(&基础序列, 笔序列, 配置) >= 配置.笔内元素数量 as usize + if Self::_获取缠K数量(&基础序列, 笔序列, 配置) >= 配置.笔内元素数量 as usize { - if 筆.方向() == 相对方向::向下 { - if let (Some(实际高), Some(实际低)) = ( - Self::实际高点(&基础序列, false), - Self::实际低点(&基础序列, 配置.笔内相同终点取舍), - ) { - if Arc::ptr_eq(&筆.文.中, &实际高) - && Arc::ptr_eq(&筆.武.read().unwrap().中, &实际低) - { - return true; - } - } + if 筆.方向() == 相对方向::向下 + && let (Some(实际高), Some(实际低)) = ( + Self::_实际高点(&基础序列, false), + Self::_实际低点(&基础序列, 配置.笔内相同终点取舍), + ) + && Arc::ptr_eq(&筆.文.中, &实际高) + && Arc::ptr_eq(&筆.武.read().unwrap().中, &实际低) + { + return true; } - if 筆.方向() == 相对方向::向上 { - if let (Some(实际低), Some(实际高)) = ( - Self::实际低点(&基础序列, false), - Self::实际高点(&基础序列, 配置.笔内相同终点取舍), - ) { - if Arc::ptr_eq(&筆.文.中, &实际低) - && Arc::ptr_eq(&筆.武.read().unwrap().中, &实际高) - { - return true; - } - } + if 筆.方向() == 相对方向::向上 + && let (Some(实际低), Some(实际高)) = ( + Self::_实际低点(&基础序列, false), + Self::_实际高点(&基础序列, 配置.笔内相同终点取舍), + ) + && Arc::ptr_eq(&筆.文.中, &实际低) + && Arc::ptr_eq(&筆.武.read().unwrap().中, &实际高) + { + return true; } } false @@ -975,7 +718,7 @@ impl 笔 { 当前笔 .序号 .store(筆.序号.load(Ordering::Relaxed), Ordering::Relaxed); - if Self::自检(&当前笔, 观察员) { + if Self::_自检(&当前笔, 观察员) { 笔序列.push(当前笔); } } @@ -1004,3 +747,261 @@ impl 笔 { 结果 } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::kline::bar::K线; + use crate::kline::chan_kline::缠论K线; + use crate::structure::fractal_obj::分型; + use crate::types::分型结构; + use std::sync::atomic::Ordering; + + fn 辅助_创建普K(时间戳: i64, 高: f64, 低: f64) -> Arc { + Arc::new(K线 { + 时间戳, + 高, + 低, + 开盘价: 低, + 收盘价: 高, + ..Default::default() + }) + } + + fn 辅助_创建缠K( + 时间戳: i64, + 高: f64, + 低: f64, + 方向: 相对方向, + 分型: Option<分型结构>, + ) -> Arc<缠论K线> { + let 普K = 辅助_创建普K(时间戳, 高, 低); + Arc::new(缠论K线::创建缠K( + 时间戳, 高, 低, 方向, 分型, 0, 普K, None, + )) + } + + fn 辅助_创建分型( + 左: Arc<缠论K线>, 中: Arc<缠论K线>, 右: Arc<缠论K线> + ) -> Arc<分型> { + Arc::new(分型::new(Some(左), 中, Some(右))) + } + + // ========== _次高 测试 ========== + + #[test] + fn test_次高_基本查找() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 95.0, 相对方向::向上, None), + 辅助_创建缠K(200, 110.0, 98.0, 相对方向::向上, None), + 辅助_创建缠K(300, 105.0, 97.0, 相对方向::向上, None), + ]; + // 最高=110(ts=200), 次高=105(ts=300) + let r = 笔::_次高(&ks, false).unwrap(); + assert_eq!(r.高.get(), 105.0); + assert_eq!(r.时间戳.load(Ordering::Relaxed), 300); + } + + #[test] + fn test_次高_取舍取最后() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 95.0, 相对方向::向上, None), + 辅助_创建缠K(200, 110.0, 98.0, 相对方向::向上, None), + 辅助_创建缠K(300, 105.0, 97.0, 相对方向::向上, None), + 辅助_创建缠K(400, 105.0, 96.0, 相对方向::向上, None), + ]; + let r = 笔::_次高(&ks, true).unwrap(); // 取舍=true: 取最晚的 105 + assert_eq!(r.高.get(), 105.0); + assert_eq!(r.时间戳.load(Ordering::Relaxed), 400); + } + + // ========== _次低 测试 ========== + + #[test] + fn test_次低_基本查找() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 95.0, 相对方向::向下, None), + 辅助_创建缠K(200, 100.0, 90.0, 相对方向::向下, None), + 辅助_创建缠K(300, 100.0, 93.0, 相对方向::向下, None), + ]; + // 最低=90(ts=200), 次低=93(ts=300), 取舍=false: 取最早 + let r = 笔::_次低(&ks, false).unwrap(); + assert_eq!(r.低.get(), 93.0); + } + + #[test] + fn test_次低_取舍取最后() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 95.0, 相对方向::向下, None), + 辅助_创建缠K(200, 100.0, 90.0, 相对方向::向下, None), + 辅助_创建缠K(300, 100.0, 93.0, 相对方向::向下, None), + 辅助_创建缠K(400, 100.0, 93.0, 相对方向::向下, None), + ]; + let r = 笔::_次低(&ks, true).unwrap(); + assert_eq!(r.低.get(), 93.0); + assert_eq!(r.时间戳.load(Ordering::Relaxed), 400); + } + + // ========== _实际高点 测试 ========== + + #[test] + fn test_实际高点_单最高() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 95.0, 相对方向::向上, None), + 辅助_创建缠K(200, 110.0, 98.0, 相对方向::向上, None), + 辅助_创建缠K(300, 105.0, 97.0, 相对方向::向上, None), + ]; + let r = 笔::_实际高点(&ks, false).unwrap(); + assert_eq!(r.高.get(), 110.0); + } + + #[test] + fn test_实际高点_多个相同最高_取舍false取最早() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 110.0, 95.0, 相对方向::向上, None), + 辅助_创建缠K(200, 110.0, 98.0, 相对方向::向上, None), + 辅助_创建缠K(300, 105.0, 97.0, 相对方向::向上, None), + ]; + let r = 笔::_实际高点(&ks, false).unwrap(); + assert_eq!(r.高.get(), 110.0); + assert_eq!(r.时间戳.load(Ordering::Relaxed), 100); + } + + #[test] + fn test_实际高点_多个相同最高_取舍true取最晚() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 110.0, 95.0, 相对方向::向上, None), + 辅助_创建缠K(200, 110.0, 98.0, 相对方向::向上, None), + 辅助_创建缠K(300, 105.0, 97.0, 相对方向::向上, None), + ]; + let r = 笔::_实际高点(&ks, true).unwrap(); + assert_eq!(r.时间戳.load(Ordering::Relaxed), 200); + } + + #[test] + fn test_实际高点_空序列() { + let ks: Vec> = vec![]; + assert!(笔::_实际高点(&ks, false).is_none()); + } + + // ========== _实际低点 测试 ========== + + #[test] + fn test_实际低点_基本() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 95.0, 相对方向::向下, None), + 辅助_创建缠K(200, 100.0, 90.0, 相对方向::向下, None), + 辅助_创建缠K(300, 100.0, 93.0, 相对方向::向下, None), + ]; + let r = 笔::_实际低点(&ks, false).unwrap(); + assert_eq!(r.低.get(), 90.0); + } + + #[test] + fn test_实际低点_多个相同最低() { + let ks: Vec> = vec![ + 辅助_创建缠K(100, 100.0, 90.0, 相对方向::向下, None), + 辅助_创建缠K(200, 100.0, 90.0, 相对方向::向下, None), + 辅助_创建缠K(300, 100.0, 95.0, 相对方向::向下, None), + ]; + let r = 笔::_实际低点(&ks, false).unwrap(); + assert_eq!(r.低.get(), 90.0); + assert_eq!(r.时间戳.load(Ordering::Relaxed), 100); + } + + #[test] + fn test_实际低点_空序列() { + let ks: Vec> = vec![]; + assert!(笔::_实际低点(&ks, false).is_none()); + } + + // ========== 以文会友 测试 ========== + + #[test] + fn test_以文会友_找到匹配() { + let 文 = 辅助_创建分型( + 辅助_创建缠K(90, 105.0, 100.0, 相对方向::向上, Some(分型结构::上)), + 辅助_创建缠K(100, 110.0, 100.0, 相对方向::向上, Some(分型结构::顶)), + 辅助_创建缠K(110, 105.0, 100.0, 相对方向::向下, Some(分型结构::下)), + ); + let 武 = 辅助_创建分型( + 辅助_创建缠K(190, 95.0, 85.0, 相对方向::向下, Some(分型结构::下)), + 辅助_创建缠K(200, 95.0, 80.0, 相对方向::向下, Some(分型结构::底)), + 辅助_创建缠K(210, 100.0, 85.0, 相对方向::向上, Some(分型结构::上)), + ); + let 筆 = 虚线::创建笔(文.clone(), 武, true); + let 笔序列 = vec![Arc::new(筆)]; + + let found = 笔::以文会友(&笔序列, &文); + assert!(found.is_some()); + } + + #[test] + fn test_以文会友_找不到匹配() { + let 文 = 辅助_创建分型( + 辅助_创建缠K(90, 105.0, 100.0, 相对方向::向上, Some(分型结构::上)), + 辅助_创建缠K(100, 110.0, 100.0, 相对方向::向上, Some(分型结构::顶)), + 辅助_创建缠K(110, 105.0, 100.0, 相对方向::向下, Some(分型结构::下)), + ); + let other_文 = 辅助_创建分型( + 辅助_创建缠K(190, 90.0, 80.0, 相对方向::向上, Some(分型结构::上)), + 辅助_创建缠K(200, 95.0, 80.0, 相对方向::向上, Some(分型结构::顶)), + 辅助_创建缠K(210, 90.0, 80.0, 相对方向::向下, Some(分型结构::下)), + ); + let 武 = 辅助_创建分型( + 辅助_创建缠K(290, 85.0, 75.0, 相对方向::向下, Some(分型结构::下)), + 辅助_创建缠K(300, 85.0, 70.0, 相对方向::向下, Some(分型结构::底)), + 辅助_创建缠K(310, 90.0, 75.0, 相对方向::向上, Some(分型结构::上)), + ); + let 筆 = 虚线::创建笔(other_文, 武, true); + let 笔序列 = vec![Arc::new(筆)]; + + let found = 笔::以文会友(&笔序列, &文); + assert!(found.is_none()); + } + + // ========== 以武会友 测试 ========== + + #[test] + fn test_以武会友_找到匹配() { + let 文 = 辅助_创建分型( + 辅助_创建缠K(90, 105.0, 100.0, 相对方向::向上, Some(分型结构::上)), + 辅助_创建缠K(100, 110.0, 100.0, 相对方向::向上, Some(分型结构::顶)), + 辅助_创建缠K(110, 105.0, 100.0, 相对方向::向下, Some(分型结构::下)), + ); + let 武 = 辅助_创建分型( + 辅助_创建缠K(190, 95.0, 85.0, 相对方向::向下, Some(分型结构::下)), + 辅助_创建缠K(200, 95.0, 80.0, 相对方向::向下, Some(分型结构::底)), + 辅助_创建缠K(210, 100.0, 85.0, 相对方向::向上, Some(分型结构::上)), + ); + let 筆 = 虚线::创建笔(文, 武.clone(), true); + let 笔序列 = vec![Arc::new(筆)]; + + let found = 笔::以武会友(&笔序列, &武); + assert!(found.is_some()); + } + + #[test] + fn test_以武会友_找不到匹配() { + let 文 = 辅助_创建分型( + 辅助_创建缠K(90, 105.0, 100.0, 相对方向::向上, Some(分型结构::上)), + 辅助_创建缠K(100, 110.0, 100.0, 相对方向::向上, Some(分型结构::顶)), + 辅助_创建缠K(110, 105.0, 100.0, 相对方向::向下, Some(分型结构::下)), + ); + let 武 = 辅助_创建分型( + 辅助_创建缠K(190, 95.0, 85.0, 相对方向::向下, Some(分型结构::下)), + 辅助_创建缠K(200, 95.0, 80.0, 相对方向::向下, Some(分型结构::底)), + 辅助_创建缠K(210, 100.0, 85.0, 相对方向::向上, Some(分型结构::上)), + ); + let other_武 = 辅助_创建分型( + 辅助_创建缠K(290, 85.0, 75.0, 相对方向::向下, Some(分型结构::下)), + 辅助_创建缠K(300, 85.0, 65.0, 相对方向::向下, Some(分型结构::底)), + 辅助_创建缠K(310, 90.0, 75.0, 相对方向::向上, Some(分型结构::上)), + ); + let 筆 = 虚线::创建笔(文, 武, true); + let 笔序列 = vec![Arc::new(筆)]; + + let found = 笔::以武会友(&笔序列, &other_武); + assert!(found.is_none()); + } +} diff --git a/chanlun/src/algorithm/divergence.rs b/chanlun/src/algorithm/divergence.rs index 41c3330..455d3cb 100644 --- a/chanlun/src/algorithm/divergence.rs +++ b/chanlun/src/algorithm/divergence.rs @@ -200,7 +200,7 @@ impl 背驰分析 { if let (Some(始), Some(终)) = (始_idx, 终_idx) { let (始, 终) = if 始 <= 终 { (始, 终) } else { (终, 始) }; for k in &K线序列[始..=终] { - if let Some(ref macd) = k.macd { + if let Some(macd) = k.指标.read().unwrap().macd() { let hist = macd.MACD柱; if hist >= 0.0 { 阳 += hist; diff --git a/chanlun/src/algorithm/hub.rs b/chanlun/src/algorithm/hub.rs index c728d4d..7107967 100644 --- a/chanlun/src/algorithm/hub.rs +++ b/chanlun/src/algorithm/hub.rs @@ -29,14 +29,29 @@ use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{Arc, RwLock}; /// 中枢 — 三段虚线重叠区间构成的价格中枢 -/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致 +/// +/// 可变字段使用 AtomicI64 / RwLock 实现内部可变性,确保多 Arc 共享时可修改。 +/// +/// 字段: +/// - 序号: 中枢序号,同一级别内递增 +/// - 标识: 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>" +/// - 级别: 中枢级别(笔中枢=1,线段中枢=2 等) +/// - 基础序列: 构成中枢的虚线序列(至少 3 根,延伸后可多至 9 根甚至更多) +/// - 第三买卖线: 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线) +/// - 本级_第三买卖线: 本级第三类买卖点对应的虚线 #[derive(Debug)] pub struct 中枢 { + /// 中枢序号,同一级别内递增 pub 序号: AtomicI64, + /// 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>" pub 标识: RwLock, + /// 中枢级别(笔中枢=1,线段中枢=2 等) pub 级别: AtomicI64, + /// 构成中枢的虚线序列(至少 3 根,延伸后可多至 9+ 根) pub 基础序列: RwLock>>, + /// 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线) pub 第三买卖线: RwLock>>, + /// 本级第三类买卖点对应的虚线 pub 本级_第三买卖线: RwLock>>, } @@ -54,6 +69,7 @@ impl Clone for 中枢 { } impl 中枢 { + /// 创建新中枢(最多取前 3 根虚线作为基础序列) pub fn new(序号: i64, 标识: String, 级别: i64, 基础序列: Vec>) -> Self { Self { 序号: AtomicI64::new(序号), @@ -65,12 +81,14 @@ impl 中枢 { } } - pub fn 添加虚线(&self, 实线: Arc<虚线>) { + /// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线 + pub fn _添加虚线(&self, 实线: Arc<虚线>) { self.基础序列.write().unwrap().push(实线); *self.本级_第三买卖线.write().unwrap() = None; *self.第三买卖线.write().unwrap() = None; } + /// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号" pub fn 图表标题(&self) -> String { format!( "{}:{}:{}:{}", @@ -81,14 +99,17 @@ impl 中枢 { ) } + /// 返回基础序列的最后一根虚线(当前离开段) pub fn 离开段(&self) -> Arc<虚线> { Arc::clone(&self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1]) } + /// 返回中枢方向(与基础序列第一段方向相反) pub fn 方向(&self) -> 相对方向 { self.基础序列.read().unwrap()[0].方向().翻转() } + /// 中枢上沿 = min(前三段的高) pub fn 高(&self) -> f64 { self.基础序列.read().unwrap()[..3] .iter() @@ -97,6 +118,7 @@ impl 中枢 { .unwrap_or(0.0) } + /// 中枢下沿 = max(前三段的低) pub fn 低(&self) -> f64 { self.基础序列.read().unwrap()[..3] .iter() @@ -105,6 +127,7 @@ impl 中枢 { .unwrap_or(0.0) } + /// 中枢最高点 = max(所有段的高) pub fn 高高(&self) -> f64 { self.基础序列 .read() @@ -115,6 +138,7 @@ impl 中枢 { .unwrap_or(0.0) } + /// 中枢最低点 = min(所有段的低) pub fn 低低(&self) -> f64 { self.基础序列 .read() @@ -125,10 +149,12 @@ impl 中枢 { .unwrap_or(0.0) } + /// 返回基础序列第一段的起点分型 pub fn 文(&self) -> Arc<分型> { Arc::clone(&self.基础序列.read().unwrap()[0].文) } + /// 返回基础序列最后一段的终点分型 pub fn 武(&self) -> Arc<分型> { Arc::clone( &*self.基础序列.read().unwrap()[self.基础序列.read().unwrap().len() - 1] @@ -138,8 +164,9 @@ impl 中枢 { ) } - pub fn 设置第三买卖线(&self, 线: Arc<虚线>) { - *self.第三买卖线.write().unwrap() = Some(线); + /// 设置第三类买卖点对应的虚线 + pub fn 设置第三买卖线(&self, 线: Option>) { + *self.第三买卖线.write().unwrap() = 线; } /// 获取序列 — 基础序列 + 第三买卖线(若有) @@ -151,6 +178,7 @@ impl 中枢 { 序列 } + /// 返回序列化数据文本,用于调试和存储 pub fn 获取数据文本(&self) -> String { let 第三买卖线_str = match &*self.第三买卖线.read().unwrap() { Some(x) => format!("{}", x), @@ -175,7 +203,7 @@ impl 中枢 { } /// 校验中枢合法性 - pub fn 校验合法性(&self, 序列: &[Arc<虚线>]) -> bool { + pub fn _校验合法性(&self, 序列: &[Arc<虚线>]) -> bool { let mut 有效序列 = self.基础序列.read().unwrap().clone(); let mut 无效序列: Vec> = Vec::new(); for 元素 in self.基础序列.read().unwrap().iter() { @@ -198,7 +226,7 @@ impl 中枢 { } if 有效序列.len() < 3 { - *self.第三买卖线.write().unwrap() = None; + self.设置第三买卖线(None); *self.本级_第三买卖线.write().unwrap() = None; return false; } @@ -248,7 +276,7 @@ impl 中枢 { if let Some(ref 三买线) = 三买线_opt { if 序列.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(三买线)) { if !self.基础序列.read().unwrap().last().unwrap().之后是(三买线) { - *self.第三买卖线.write().unwrap() = None; + self.设置第三买卖线(None); } else if !crate::types::相对方向::分析( self.高(), self.低(), @@ -257,11 +285,11 @@ impl 中枢 { ) .是否缺口() { - self.添加虚线(Arc::clone(三买线)); - *self.第三买卖线.write().unwrap() = None; + self._添加虚线(Arc::clone(三买线)); + self.设置第三买卖线(None); } } else { - *self.第三买卖线.write().unwrap() = None; + self.设置第三买卖线(None); } } true @@ -359,6 +387,7 @@ impl 中枢 { pub fn 创建( 左: Arc<虚线>, 中: Arc<虚线>, 右: Arc<虚线>, 级别: i64, 标识: &str ) -> Self { + debug_assert!(Self::基础检查(&左, &中, &右), "中枢.创建 基础检查失败"); Self::new( 0, format!("{}中枢<{}>", 标识, 中.标识.read().unwrap()), @@ -367,8 +396,8 @@ impl 中枢 { ) } - /// 从序列中获取中枢 - pub fn 从序列中获取中枢( + /// _从序列中获取中枢 + pub fn _从序列中获取中枢( 虚线序列: &[Arc<虚线>], 起始方向: 相对方向, 标识: &str, @@ -385,33 +414,38 @@ impl 中枢 { None } - /// 向中枢序列尾部添加 - pub fn 向中枢序列尾部添加( + /// _向中枢序列尾部添加 + pub fn _向中枢序列尾部添加( 中枢序列: &mut Vec>, 待添加中枢: Arc<中枢> ) { if let Some(前一个) = 中枢序列.last() { 待添加中枢 .序号 .store(前一个.序号.load(Ordering::Relaxed) + 1, Ordering::Relaxed); - // Python: assert seq[-1].获取序列()[-1].序号 <= new.获取序列()[-1].序号 - let 前_seq = 前一个.获取序列(); - let new_seq = 待添加中枢.获取序列(); - if let (Some(前_last), Some(new_last)) = (前_seq.last(), new_seq.last()) { - if 前_last.序号.load(Ordering::Relaxed) > new_last.序号.load(Ordering::Relaxed) - { - panic!( - "向中枢序列尾部添加 序号错误 前last={} > new_last={}", - 前_last.序号.load(Ordering::Relaxed), - new_last.序号.load(Ordering::Relaxed) - ); - } + let 前_last_序号 = 前一个 + .获取序列() + .last() + .unwrap() + .序号 + .load(Ordering::Relaxed); + let new_last_序号 = 待添加中枢 + .获取序列() + .last() + .unwrap() + .序号 + .load(Ordering::Relaxed); + if 前_last_序号 > new_last_序号 { + panic!( + "向中枢序列尾部添加 序号错误 前last={} > new_last={}", + 前_last_序号, new_last_序号 + ); } } 中枢序列.push(待添加中枢); } /// 从中枢序列尾部弹出 - pub fn 从中枢序列尾部弹出( + pub fn _从中枢序列尾部弹出( 中枢序列: &mut Vec>, 待弹出: &Arc<中枢>, ) -> Option> { @@ -448,7 +482,7 @@ impl 中枢 { let 序号 = 虚线序列 .iter() .position(|x| Arc::as_ptr(x) == Arc::as_ptr(左)) - .unwrap_or(i - 1); + .expect("中枢.分析: 左元素不在虚线序列中"); if 跳过首部 && (左.序号.load(Ordering::Relaxed) == 0 || 序号 == 0) { continue; } @@ -470,7 +504,7 @@ impl 中枢 { 中.级别.load(Ordering::Relaxed), 标识, )); - Self::向中枢序列尾部添加(中枢序列, 新中枢); + Self::_向中枢序列尾部添加(中枢序列, 新中枢); // Python: return 中枢递归分析(虚线序列, 中枢序列, ...) Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, _层级); return; @@ -483,10 +517,10 @@ impl 中枢 { let mut 当前中枢_idx = 中枢序列.len() - 1; // Validate via shared reference (中枢 uses RwLock internally) - let needs_pop = !中枢序列[当前中枢_idx].校验合法性(虚线序列); + let needs_pop = !中枢序列[当前中枢_idx]._校验合法性(虚线序列); if needs_pop { let 当前中枢 = Arc::clone(&中枢序列[当前中枢_idx]); - Self::从中枢序列尾部弹出(中枢序列, &当前中枢); + Self::_从中枢序列尾部弹出(中枢序列, &当前中枢); Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, _层级); return; } @@ -527,12 +561,29 @@ impl 中枢 { .之后是(&当前虚线) }; if needs_三买 { - 中枢序列[当前中枢_idx].设置第三买卖线(当前虚线.clone()); + 中枢序列[当前中枢_idx].设置第三买卖线(Some(当前虚线.clone())); } } else { if 候选序列.is_empty() { // 仍在范围内:延伸中枢 - 中枢序列[当前中枢_idx].添加虚线(当前虚线); + debug_assert!( + 中枢序列[当前中枢_idx] + .基础序列 + .read() + .unwrap() + .last() + .unwrap() + .之后是(&当前虚线), + "中枢延伸: 不连续 {}, {}", + 中枢序列[当前中枢_idx] + .基础序列 + .read() + .unwrap() + .last() + .unwrap(), + 当前虚线 + ); + 中枢序列[当前中枢_idx]._添加虚线(当前虚线); } else { 候选序列.push(当前虚线); } @@ -548,9 +599,9 @@ impl 中枢 { .unwrap() .方向() .翻转(); - match Self::从序列中获取中枢(&候选序列, 起始方向, 标识) { + match Self::_从序列中获取中枢(&候选序列, 起始方向, 标识) { Some(新中枢) => { - Self::向中枢序列尾部添加(中枢序列, 新中枢); + Self::_向中枢序列尾部添加(中枢序列, 新中枢); // Python: 当前中枢 = 新中枢 当前中枢_idx = 中枢序列.len() - 1; 中枢高 = 中枢序列[当前中枢_idx].高(); @@ -732,7 +783,7 @@ mod tests { assert_eq!(中枢.序号.load(Ordering::Relaxed), 99); // RefCell 第三买卖线读写 - 中枢.设置第三买卖线(Arc::clone(&笔1)); + 中枢.设置第三买卖线(Some(Arc::clone(&笔1))); assert!(中枢.第三买卖线.read().unwrap().is_some()); assert_eq!( Arc::as_ptr(中枢.第三买卖线.read().unwrap().as_ref().unwrap()), @@ -764,7 +815,7 @@ mod tests { ); assert_eq!(中枢.基础序列.read().unwrap().len(), 3); - 中枢.添加虚线(Arc::clone(&笔4)); + 中枢._添加虚线(Arc::clone(&笔4)); assert_eq!(中枢.基础序列.read().unwrap().len(), 4); assert_eq!( Arc::as_ptr(&中枢.基础序列.read().unwrap()[3]), @@ -785,12 +836,12 @@ mod tests { 1, vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)], ); - 中枢.设置第三买卖线(Arc::clone(&笔1)); + 中枢.设置第三买卖线(Some(Arc::clone(&笔1))); *中枢.本级_第三买卖线.write().unwrap() = Some(Arc::clone(&笔2)); assert!(中枢.第三买卖线.read().unwrap().is_some()); assert!(中枢.本级_第三买卖线.read().unwrap().is_some()); - 中枢.添加虚线(Arc::clone(&笔4)); + 中枢._添加虚线(Arc::clone(&笔4)); // 添加虚线后第三买卖线被清除 assert!(中枢.第三买卖线.read().unwrap().is_none()); assert!(中枢.本级_第三买卖线.read().unwrap().is_none()); @@ -812,7 +863,7 @@ mod tests { 1, vec![Arc::clone(&笔1), Arc::clone(&笔2), Arc::clone(&笔3)], ); - 中枢.设置第三买卖线(Arc::clone(&笔1)); + 中枢.设置第三买卖线(Some(Arc::clone(&笔1))); let 克隆 = 中枢.clone(); @@ -885,7 +936,7 @@ mod tests { assert_eq!(中枢2.序号.load(Ordering::Relaxed), 88); // 通过 rc1 添加虚线 - 中枢1.添加虚线(Arc::clone(&笔4)); + 中枢1._添加虚线(Arc::clone(&笔4)); assert_eq!(中枢2.基础序列.read().unwrap().len(), 4); // 验证共享的 Arc<虚线> 指针一致 diff --git a/chanlun/src/algorithm/segment.rs b/chanlun/src/algorithm/segment.rs index 474407e..041b680 100644 --- a/chanlun/src/algorithm/segment.rs +++ b/chanlun/src/algorithm/segment.rs @@ -32,8 +32,10 @@ use crate::structure::dash_line::虚线; use crate::structure::fractal_obj::分型; use crate::structure::segment_feat::线段特征; use crate::types::{分型结构, 相对方向, 缺口}; +use cached::stores::LruCache; use std::sync::atomic::Ordering; -use std::sync::Arc; +use std::sync::{Arc, LazyLock, Mutex}; +use tracing::warn; /// 线段 — 从笔生成线段的算法集合(静态方法命名空间) pub struct 线段; @@ -46,6 +48,10 @@ type 分割结果 = ( ); type 中枢序列组 = (Vec>, Vec>, Vec>); +/// 线段内部背驰判断缓存 — 仿照 dash_line.rs 的 买卖意义缓存 模式 +static 线段背驰缓存: LazyLock>> = + LazyLock::new(|| Mutex::new(LruCache::with_size(128))); + impl 线段 { // ================================================================ // 基础操作 @@ -57,7 +63,7 @@ impl 线段 { } /// 向线段的基础序列中添加一笔 - pub fn 添加虚线(段_rc: &Arc<虚线>, 筆: Arc<虚线>) { + pub fn _添加虚线(段_rc: &Arc<虚线>, 筆: Arc<虚线>) { let 段 = Self::取段(段_rc); if !段.基础序列.read().unwrap().is_empty() { if !分型::判断分型( @@ -107,7 +113,7 @@ impl 线段 { } /// 更新线段的终点分型 - pub fn 武斗(段_rc: &Arc<虚线>, 武: &Arc<分型>, 行号: u32) { + pub fn _武斗(段_rc: &Arc<虚线>, 武: &Arc<分型>, 行号: u32) { let 段 = Self::取段(段_rc); if Arc::as_ptr(&*段.武.read().unwrap()) == Arc::as_ptr(武) { return; @@ -115,8 +121,9 @@ impl 线段 { if 段.武.read().unwrap().分型特征值 == 武.分型特征值 && 段.武.read().unwrap().时间戳() != 武.时间戳() { - eprintln!( - "线段.武斗[{}], 发现特征值相等但时间戳不同 {} {}", + warn!( + "{}.武斗[{}], 发现特征值相等但时间戳不同, {}, {}", + 段.标识.read().unwrap(), 行号, 段.武.read().unwrap(), 武 @@ -125,14 +132,17 @@ impl 线段 { if 段.文.结构 == 武.结构 { panic!("文武结构相同 {} {} {}", 行号, 段.文, 武); } - if let (Some(ref 左), Some(ref 右)) = (&武.左, &武.右) { - if let Some(分析结构) = - 分型结构::分析(左.as_ref(), 武.中.as_ref(), 右.as_ref(), false, false) - { - if 分析结构 != 武.结构 { - panic!("武斗[{}], 分型结构不一致 {} != {}", 行号, 分析结构, 武.结构); - } - } + if let Some(右) = &武.右 + && let Some(分析结构) = 分型结构::分析( + 武.左.as_ref().unwrap().as_ref(), + 武.中.as_ref(), + 右.as_ref(), + false, + false, + ) + && 分析结构 != 武.结构 + { + panic!("武斗[{}], 分型结构不一致 {} != {}", 行号, 分析结构, 武.结构); } if 段.方向() == 相对方向::向上 { if 武.分型特征值 < 段.文.分型特征值 { @@ -143,6 +153,17 @@ impl 线段 { 武 ); } + if 段.武.read().unwrap().分型特征值 > 武.分型特征值 + && 段.模式.read().unwrap().as_str() == "文武" + { + warn!( + "{}.武斗[{}] 出现回退 从 {} ==>>> {}", + 段.标识.read().unwrap(), + 行号, + 段.武.read().unwrap(), + 武 + ); + } } else { if 武.分型特征值 > 段.文.分型特征值 { panic!( @@ -152,12 +173,23 @@ impl 线段 { 武 ); } + if 段.武.read().unwrap().分型特征值 < 武.分型特征值 + && 段.模式.read().unwrap().as_str() == "文武" + { + warn!( + "{}.武斗[{}] 出现回退 从 {} ==>>> {}", + 段.标识.read().unwrap(), + 行号, + 段.武.read().unwrap(), + 武 + ); + } } *段.武.write().unwrap() = Arc::clone(武); } /// 武终 — 线段终结时设置终点 - pub fn 武终(段_rc: &Arc<虚线>, 行号: u32) { + pub fn _武终(段_rc: &Arc<虚线>, 行号: u32) { let 武 = { let 段 = Self::取段(段_rc); if 段.模式.read().unwrap().as_str() != "文武" { @@ -177,12 +209,12 @@ impl 线段 { } }; if let Some(武) = 武 { - Self::武斗(段_rc, &武, 行号); + Self::_武斗(段_rc, &武, 行号); } } /// 验证序列 — 截断无效尾部 - pub fn 验证序列(段_rc: &Arc<虚线>, 序列: &[Arc<虚线>]) { + pub fn _验证序列(段_rc: &Arc<虚线>, 序列: &[Arc<虚线>]) { let 段 = Self::取段(段_rc); let mut 基础序列: Vec> = Vec::new(); for 元素 in 段.基础序列.read().unwrap().iter() { @@ -190,12 +222,12 @@ impl 线段 { break; } if !基础序列.is_empty() && !基础序列.last().unwrap().之后是(元素) { - eprintln!("线段._验证序列 数据不连续"); + warn!("线段._验证序列 数据不连续"); break; } 基础序列.push(Arc::clone(元素)); } - if 基础序列.len() % 2 == 0 { + if 基础序列.len().is_multiple_of(2) { 基础序列.pop(); } *段.基础序列.write().unwrap() = 基础序列; @@ -215,9 +247,7 @@ impl 线段 { 基础序列.push(Arc::clone(元素)); } *段.基础序列.write().unwrap() = 基础序列; - if 段.特征序列.read().unwrap().len() > 2 { - 段.特征序列.write().unwrap()[2] = None; - } + 段.特征序列.write().unwrap()[2] = None; } // ================================================================ @@ -225,7 +255,7 @@ impl 线段 { // ================================================================ /// 基础判断 — 连续三笔且重叠才能构成线段 - pub fn 基础判断( + pub fn _基础判断( 左: &虚线, 中: &虚线, 右: &虚线, 关系序列: &[相对方向] ) -> bool { if !左.之后是(中) { @@ -285,9 +315,6 @@ impl 线段 { if 段.模式.read().unwrap().as_str() != "文武" { return None; } - if 段.特征序列.read().unwrap().len() < 2 { - return None; - } let 特序 = 段.特征序列.read().unwrap(); let 左 = 特序[0].as_ref()?; let 中 = 特序[1].as_ref()?; @@ -330,19 +357,12 @@ impl 线段 { /// 特征序列状态 — 返回三个特征序列元素是否为 Some pub fn 特征序列状态(段: &虚线) -> (bool, bool, bool) { - let get = |i: usize| { - 段.特征序列 - .read() - .unwrap() - .get(i) - .map(|x| x.is_some()) - .unwrap_or(false) - }; - (get(0), get(1), get(2)) + let 特序 = 段.特征序列.read().unwrap(); + (特序[0].is_some(), 特序[1].is_some(), 特序[2].is_some()) } /// 设置特征序列 - pub fn 设置特征序列( + pub fn _设置特征序列( 段_rc: &Arc<虚线>, 序列: Vec>>, 行号: u32 ) { let 段 = Self::取段(段_rc); @@ -363,7 +383,7 @@ impl 线段 { if let Some(ref 右特征) = 段.特征序列.read().unwrap()[2] { let mut 基础序列: Vec> = Vec::new(); - let 右尾 = 右特征.元素.last().expect("特征序列元素不应为空"); + let 右尾 = 右特征.基础序列.last().expect("特征序列元素不应为空"); if !段 .基础序列 .read() @@ -380,7 +400,7 @@ impl 线段 { } } - if 基础序列.len() >= 6 && 基础序列.len() % 2 == 0 { + if 基础序列.len() >= 6 && 基础序列.len().is_multiple_of(2) { *段.基础序列.write().unwrap() = 基础序列; } else { panic!("设置特征序列: 基础序列长度不足或非偶数"); @@ -389,7 +409,7 @@ impl 线段 { } /// 刷新特征序列 - pub fn 刷新特征序列(段_rc: &Arc<虚线>, 配置: &缠论配置) { + pub fn _刷新特征序列(段_rc: &Arc<虚线>, 配置: &缠论配置) { // Compute new feature sequence, then delegate to 设置特征序列 for truncation let 序列: Vec>> = { let 段 = &**段_rc; @@ -397,15 +417,13 @@ impl 线段 { return; } let mut 基础序列 = 段.基础序列.read().unwrap().clone(); - if let Some(ref 前结束) = *段.前一结束位置.read().unwrap() { - if let Some(idx) = 基础序列 + if let Some(ref 前结束) = *段.前一结束位置.read().unwrap() + && let Some(idx) = 基础序列 .iter() .position(|x| Arc::as_ptr(x) == Arc::as_ptr(前结束)) - { - if idx > 0 { - 基础序列 = 基础序列[idx - 1..].to_vec(); - } - } + && idx > 0 + { + 基础序列 = 基础序列[idx - 1..].to_vec(); } let 四象 = Self::四象(段); @@ -418,23 +436,15 @@ impl 线段 { if 特征序列.len() >= 3 { let 分型序列 = 线段特征::获取分型序列(&特征序列); - if !分型序列.is_empty() { - let 最后分型 = &分型序列[分型序列.len() - 1]; - if (段.方向() == 相对方向::向上 && 最后分型.结构 == 分型结构::顶) - || (段.方向() == 相对方向::向下 && 最后分型.结构 == 分型结构::底) - { - vec![ - Some(Arc::clone(&最后分型.左)), - Some(Arc::clone(&最后分型.中)), - Some(Arc::clone(&最后分型.右)), - ] - } else { - vec![ - Some(Arc::clone(&特征序列[特征序列.len() - 2])), - Some(Arc::clone(&特征序列[特征序列.len() - 1])), - None, - ] - } + let 最后分型 = &分型序列[分型序列.len() - 1]; + if (段.方向() == 相对方向::向上 && 最后分型.结构 == 分型结构::顶) + || (段.方向() == 相对方向::向下 && 最后分型.结构 == 分型结构::底) + { + vec![ + Some(Arc::clone(&最后分型.左)), + Some(Arc::clone(&最后分型.中)), + Some(Arc::clone(&最后分型.右)), + ] } else { vec![ Some(Arc::clone(&特征序列[特征序列.len() - 2])), @@ -449,7 +459,7 @@ impl 线段 { } }; // 段 borrow ends here - Self::设置特征序列(段_rc, 序列, 0); + Self::_设置特征序列(段_rc, 序列, line!()); } /// 查找贯穿伤 — 基础序列中穿透文分型特征值的笔 @@ -528,14 +538,11 @@ impl 线段 { } if 状态 == Some("中枢之上") { + let 中枢高 = 所属中枢.as_ref().unwrap().高(); + let 中枢低 = 所属中枢.as_ref().unwrap().低(); for 筆 in 段.基础序列.read().unwrap().iter().rev() { if 筆.方向() == 相对方向::向下 { - let 关系 = 相对方向::分析( - 所属中枢.as_ref().unwrap().高(), - 所属中枢.as_ref().unwrap().低(), - 筆.高(), - 筆.低(), - ); + let 关系 = 相对方向::分析(中枢高, 中枢低, 筆.高(), 筆.低()); if 关系 == 相对方向::向上缺口 { 第三买卖线.push(Arc::clone(筆)); } else { @@ -586,17 +593,17 @@ impl 线段 { } /// 刷新 — 完整刷新线段的特征序列和内部中枢 - pub fn 刷新(段_rc: &Arc<虚线>, 配置: &缠论配置) { + pub fn _刷新(段_rc: &Arc<虚线>, 配置: &缠论配置) { let 段 = Self::取段(段_rc); if 段.模式.read().unwrap().as_str() != "文武" { return; } if 段.基础序列.read().unwrap().is_empty() { - eprintln!(" 线段.刷新 基础序列为空"); + warn!(" 线段.刷新 基础序列为空"); return; } - Self::刷新特征序列(段_rc, 配置); + Self::_刷新特征序列(段_rc, 配置); // After 刷新特征序列, work with the updated segment let (武斗_武文, 特征后一笔_opt) = { @@ -610,7 +617,7 @@ impl 线段 { } else if !有效特征序列.is_empty() { let 最近特征 = 有效特征序列[有效特征序列.len() - 1]; - let 特征后一笔 = if 最近特征.元素.last().map(|x| { + let 特征后一笔 = if 最近特征.基础序列.last().map(|x| { 段2.基础序列 .read() .unwrap() @@ -618,16 +625,16 @@ impl 线段 { .any(|b| Arc::as_ptr(b) == Arc::as_ptr(x)) }) == Some(true) { - Some(Arc::clone(最近特征.元素.last().unwrap())) + Some(Arc::clone(最近特征.基础序列.last().unwrap())) } else { 笔::以武会友( &段2.基础序列.read().unwrap(), - &最近特征.元素.last().unwrap().武.read().unwrap(), + &最近特征.基础序列.last().unwrap().武.read().unwrap(), ) }; if 特征后一笔.is_none() { - eprintln!( + warn!( " 线段.刷新 特征后一笔 = None, {}, 有效特征: {}", 段2, 有效特征序列.len() @@ -635,12 +642,12 @@ impl 线段 { } (None, 特征后一笔) } else { - (None, None) + panic!("线段.刷新 有效特征序列为空!"); } }; if let Some(武文) = 武斗_武文 { - Self::武斗(段_rc, &武文, 0); + Self::_武斗(段_rc, &武文, line!()); } else if let Some(特征后一笔) = 特征后一笔_opt { let 武斗候选 = { let 段2 = Self::取段(段_rc); @@ -668,7 +675,7 @@ impl 线段 { } }; if let Some(武) = 武斗候选 { - Self::武斗(段_rc, &武, 0); + Self::_武斗(段_rc, &武, line!()); } } @@ -765,30 +772,26 @@ impl 线段 { *seg.模式.write().unwrap() = "文武".into(); if !线段序列.is_empty() { - if let Some(前一个) = 线段序列.last() { - if !前一个.之后是(seg) { - panic!( - "线段.向序列中添加 不连续[{}] {} {}", - 行号, - 前一个.武.read().unwrap(), - seg.文 - ); - } + if let Some(前一个) = 线段序列.last() + && !前一个.之后是(seg) + { + panic!( + "线段.向序列中添加 不连续[{}] {} {}", + 行号, + 前一个.武.read().unwrap(), + seg.文 + ); } let 之前线段 = 线段序列.last().unwrap(); - // Check 之前线段.特征序列.read().unwrap()[2] is valid - if 之前线段.特征序列.read().unwrap().len() >= 3 - && 之前线段.特征序列.read().unwrap()[2].is_none() - && !之前线段.短路修正.load(Ordering::Relaxed) - { - // The Python code asserts here; we warn - eprintln!( - "线段._向序列中添加[{}], 之前线段.右 = None {}", - 行号, 之前线段 - ); - } + debug_assert!( + 之前线段.特征序列.read().unwrap()[2].is_some() + || 之前线段.短路修正.load(Ordering::Relaxed), + "线段._向序列中添加[{}], 之前线段.右 = None {}", + 行号, + 之前线段 + ); if !seg.基础序列.read().unwrap().iter().any(|x| { Arc::as_ptr(x) == Arc::as_ptr(之前线段.基础序列.read().unwrap().last().unwrap()) @@ -830,21 +833,19 @@ impl 线段 { panic!("线段._从序列中删除 弹出数据不在列表中 {}", 待弹出线段); } - if 待弹出线段.特征序列.read().unwrap().len() >= 3 { + { let 左 = &待弹出线段.特征序列.read().unwrap()[0]; let 中 = &待弹出线段.特征序列.read().unwrap()[1]; let 右 = &待弹出线段.特征序列.read().unwrap()[2]; - if let (Some(l), Some(m), Some(r)) = (左, 中, 右) { - if let Some(结构) = 分型结构::分析(&**l, &**m, &**r, true, true) { - if matches!(结构, 分型结构::顶 | 分型结构::底) - && !相对方向::分析(l.高(), l.低(), m.高(), m.低()).是否缺口() - { - eprintln!( - "[警告<{}>]: 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {}", - 行号, 待弹出线段 - ); - } - } + if let (Some(l), Some(m), Some(r)) = (左, 中, 右) + && let Some(结构) = 分型结构::分析(&**l, &**m, &**r, true, true) + && matches!(结构, 分型结构::顶 | 分型结构::底) + && !相对方向::分析(l.高(), l.低(), m.高(), m.低()).是否缺口() + { + warn!( + "[警告<{}>]: 线段._从序列中删除 发现分型完毕, 且特征序列无缺口 {}", + 行号, 待弹出线段 + ); } } @@ -864,14 +865,11 @@ impl 线段 { pub fn _缺口突破( 线段序列: &mut Vec>, 配置: &缠论配置, 层级: i64 ) -> bool { - if 线段序列.is_empty() { - return false; - } - let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段.基础序列.read().unwrap().is_empty() { - return false; - } + assert!( + !当前线段.基础序列.read().unwrap().is_empty(), + "缺口突破: 当前线段.基础序列为空!" + ); let 当前虚线 = Arc::clone( &当前线段.基础序列.read().unwrap()[当前线段.基础序列.read().unwrap().len() - 1], ); @@ -889,9 +887,7 @@ impl 线段 { } // 条件3:当前线段特征序列[2]必须为None - if 当前线段.特征序列.read().unwrap().len() >= 3 - && 当前线段.特征序列.read().unwrap()[2].is_some() - { + if 当前线段.特征序列.read().unwrap()[2].is_some() { return false; } @@ -902,6 +898,14 @@ impl 线段 { return false; } + // 已被修正 + if 线段序列[线段序列.len() - 2] + .短路修正 + .load(Ordering::Relaxed) + { + return false; + } + // 执行修正 let 序列 = 当前线段.基础序列.read().unwrap().clone(); Self::_弹出线段( @@ -911,25 +915,22 @@ impl 线段 { format!("{}, {}", line!(), 层级), ); - if 线段序列.is_empty() { - return true; - } + assert!(!线段序列.is_empty(), "缺口突破: 线段序列为第二次空!"); + let 当前线段 = Arc::clone(线段序列.last().unwrap()); + + assert!(当前线段.特征序列.read().unwrap()[2].is_some()); + let (前, _, _, _) = Self::分割序列(&当前线段, None); + let mut 当前线段基础序列 = 前; + let 序列首 = Arc::clone(&序列[0]); + assert!( + 当前线段基础序列.last().unwrap().之后是(&序列首), + "缺口突破: 子序列不连续!" + ); + 当前线段基础序列.extend(序列); - { - let idx = 线段序列.len() - 1; - let cur = Arc::make_mut(&mut 线段序列[idx]); - assert!( - cur.特征序列.read().unwrap().len() >= 3 - && cur.特征序列.read().unwrap()[2].is_some(), - "_缺口突破: 特征序列[2] 不可为 None" - ); - let (前, _, _, _) = Self::分割序列(cur, None); - let mut 当前线段基础序列 = 前; - 当前线段基础序列.extend(序列); - *cur.基础序列.write().unwrap() = 当前线段基础序列; - } let idx = 线段序列.len() - 1; - Self::刷新(&线段序列[idx], 配置); + *线段序列[idx].基础序列.write().unwrap() = 当前线段基础序列.clone(); + Self::_刷新(&线段序列[idx], 配置); true } @@ -937,9 +938,7 @@ impl 线段 { pub fn _非缺口下穿刺( 线段序列: &mut Vec>, 配置: &缠论配置, 层级: i64 ) -> bool { - if 线段序列.is_empty() { - return false; - } + assert!(!线段序列.is_empty(), "非缺口下穿刺: 线段序列为空!"); let 当前线段 = Arc::clone(线段序列.last().unwrap()); let 四象 = Self::四象(&当前线段); @@ -947,7 +946,6 @@ impl 线段 { // 外层条件 if !(配置.线段_非缺口下穿刺 && (四象 == "小阳" || 四象 == "少阴") - && 当前线段.特征序列.read().unwrap().len() >= 3 && 当前线段.特征序列.read().unwrap()[2].is_none()) { return false; @@ -968,9 +966,7 @@ impl 线段 { .iter() .position(|x| Arc::as_ptr(x) == Arc::as_ptr(&贯穿伤)); - if 贯穿伤_idx.is_none() { - return false; - } + assert!(贯穿伤_idx.is_some(), "非缺口下穿刺: 贯穿伤不在基础序列中!"); let 基础序列: Vec> = 当前线段.基础序列.read().unwrap()[贯穿伤_idx.unwrap()..].to_vec(); @@ -989,7 +985,13 @@ impl 线段 { } // 执行修正 - eprintln!("[警告<{}, {}>]: 线段.修复贯穿伤 {}", line!(), 层级, 贯穿伤); + warn!( + "[警告<{}, {}>]: 线段.修复贯穿伤 {} {:?}", + line!(), + 层级, + 贯穿伤, + 基础序列 + ); let 原始基础序列 = 当前线段.基础序列.read().unwrap().clone(); Self::_弹出线段( @@ -999,18 +1001,30 @@ impl 线段 { format!("{}, {}", line!(), 层级), ); - if 线段序列.is_empty() { - return true; - } + assert!(!线段序列.is_empty(), "非缺口下穿刺: 第二次线段序列为空!"); + assert!( + 基础序列.iter().any(|x| { + Arc::as_ptr(x) + == Arc::as_ptr( + 线段序列 + .last() + .unwrap() + .基础序列 + .read() + .unwrap() + .last() + .unwrap(), + ) + }), + "非缺口下穿刺: 当前线段.基础序列[-1] 不在 基础序列中!" + ); let 开始序号_opt; let 待添加元素: Vec>; { let idx = 线段序列.len() - 1; let cur = Arc::make_mut(&mut 线段序列[idx]); - if cur.特征序列.read().unwrap().len() >= 3 { - cur.特征序列.write().unwrap()[2] = None; - } + cur.特征序列.write().unwrap()[2] = None; let 开始笔 = Arc::clone(cur.基础序列.read().unwrap().last().unwrap()); let 开始序号 = 原始基础序列 @@ -1029,17 +1043,15 @@ impl 线段 { let idx = 线段序列.len() - 1; let seg_rc = Arc::clone(&线段序列[idx]); for 临时虚线 in &待添加元素 { - Self::添加虚线(&seg_rc, Arc::clone(临时虚线)); + Self::_添加虚线(&seg_rc, Arc::clone(临时虚线)); } 线段序列[idx] = seg_rc; } let idx = 线段序列.len() - 1; - Self::刷新(&线段序列[idx], 配置); + Self::_刷新(&线段序列[idx], 配置); let 当前线段 = Arc::clone(&线段序列[idx]); - if 当前线段.特征序列.read().unwrap().len() >= 3 - && 当前线段.特征序列.read().unwrap()[2].is_some() - { + if 当前线段.特征序列.read().unwrap()[2].is_some() { let 段 = 虚线::创建线段(&[ Arc::clone(&基础序列[基础序列.len() - 3]), Arc::clone(&基础序列[基础序列.len() - 2]), @@ -1050,9 +1062,6 @@ impl 线段 { // Set feature sequence [0] let 新段 = Self::取段(线段序列.last_mut().unwrap()); - if 新段.特征序列.read().unwrap().is_empty() { - 新段.特征序列.write().unwrap().push(None); - } let 中笔 = Arc::clone(&基础序列[基础序列.len() - 2]); 新段.特征序列.write().unwrap()[0] = Some(Arc::new(线段特征::新建(vec![中笔], 新段.方向()))); @@ -1067,9 +1076,7 @@ impl 线段 { 配置: &缠论配置, 层级: i64, ) -> bool { - if 线段序列.is_empty() { - return false; - } + assert!(!线段序列.is_empty(), "缺口后紧急修正: 线段序列为空!"); let 当前线段 = Arc::clone(线段序列.last().unwrap()); let 四象 = Self::四象(&当前线段); @@ -1078,7 +1085,6 @@ impl 线段 { if !(配置.线段_缺口后紧急修正 && !配置.线段_特征序列忽视老阴老阳 && (四象 == "小阳" || 四象 == "少阴") - && 当前线段.特征序列.read().unwrap().len() >= 3 && 当前线段.特征序列.read().unwrap()[2].is_none()) { return false; @@ -1139,9 +1145,7 @@ impl 线段 { pub fn _修正( 线段序列: &mut Vec>, 配置: &缠论配置, 层级: i64 ) -> bool { - if 线段序列.is_empty() { - return false; - } + assert!(!线段序列.is_empty(), "修正: 线段序列为空!"); let 当前线段 = Arc::clone(线段序列.last().unwrap()); @@ -1156,6 +1160,9 @@ impl 线段 { if 之后基础序列.len() < 6 { return false; } + if 之后基础序列.len() % 2 != 0 { + return false; + } let 前 = Arc::clone(&之后基础序列[之后基础序列.len() - 3]); let 后 = Arc::clone(&之后基础序列[之后基础序列.len() - 1]); @@ -1211,7 +1218,7 @@ impl 线段 { loop { if 当前层级 > 256 { - eprintln!("线段.分析 递归深度超出 256"); + warn!("线段.分析 递归深度超出 256"); return; } @@ -1226,7 +1233,7 @@ impl 线段 { let 中 = &笔序列[i]; let 右 = &笔序列[i + 1]; - if !Self::基础判断(左, 中, 右, 关系序列) { + if !Self::_基础判断(左, 中, 右, 关系序列) { continue; } let 段 = @@ -1241,9 +1248,6 @@ impl 线段 { // 段.特征序列.read().unwrap()[0] = 线段特征.新建([中], 段.方向) let 段 = Self::取段(线段序列.last_mut().unwrap()); - if 段.特征序列.read().unwrap().is_empty() { - 段.特征序列.write().unwrap().push(None); - } 段.特征序列.write().unwrap()[0] = Some(Arc::new(线段特征::新建( vec![Arc::clone(中)], 段.方向(), @@ -1314,9 +1318,7 @@ impl 线段 { // ---- 4. 特征序列已完整时的处理 ---- { let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段.特征序列.read().unwrap().len() >= 3 - && 当前线段.特征序列.read().unwrap()[2].is_some() - { + if 当前线段.特征序列.read().unwrap()[2].is_some() { let (_, 基础序列, _, _) = Self::分割序列(&当前线段, None); let 新段 = 虚线::创建线段(&基础序列); let 新段_rc = Arc::new(新段); @@ -1334,7 +1336,7 @@ impl 线段 { // Refresh current segment let idx = 线段序列.len() - 1; - Self::刷新(&线段序列[idx], 配置); + Self::_刷新(&线段序列[idx], 配置); // ---- 5. 调用一次全局修正 ---- Self::_缺口突破(线段序列, 配置, 当前层级); @@ -1369,33 +1371,38 @@ impl 线段 { // 向当前线段添加笔 let 线段_rc = Arc::clone(线段序列.last().unwrap()); - Self::添加虚线(&线段_rc, Arc::clone(&当前虚线)); + Self::_添加虚线(&线段_rc, Arc::clone(&当前虚线)); let seg_idx = 线段序列.len() - 1; 线段序列[seg_idx] = 线段_rc; // 刷新 let idx = 线段序列.len() - 1; - Self::刷新(&线段序列[idx], 配置); + Self::_刷新(&线段序列[idx], 配置); - // 依次尝试四种修正 + // 依次尝试四种修正(仅触发第一个匹配) + let mut 修正触发: Option<&str> = None; if Self::_缺口突破(线段序列, 配置, 当前层级) { - continue; + 修正触发 = Some("缺口突破"); + } else if Self::_非缺口下穿刺(线段序列, 配置, 当前层级) { + 修正触发 = Some("非缺口下穿刺"); + } else if Self::_缺口后紧急修正(线段序列, 配置, 当前层级) { + 修正触发 = Some("缺口后紧急修正"); + } else if Self::_修正(线段序列, 配置, 当前层级) { + 修正触发 = Some("修正"); } - if Self::_非缺口下穿刺(线段序列, 配置, 当前层级) { - continue; - } - if Self::_缺口后紧急修正(线段序列, 配置, 当前层级) { - continue; - } - if Self::_修正(线段序列, 配置, 当前层级) { + if let Some(trigger) = 修正触发 { + warn!( + "分析.修正触发={}, 笔序列长度={}, 线段序列长度={}", + trigger, + 笔序列.len(), + 线段序列.len() + ); continue; } // 无修正触发,检查特征序列 let 当前线段 = Arc::clone(线段序列.last().unwrap()); - if 当前线段.特征序列.read().unwrap().len() < 3 - || 当前线段.特征序列.read().unwrap()[2].is_none() - { + if 当前线段.特征序列.read().unwrap()[2].is_none() { continue; } @@ -1432,13 +1439,13 @@ impl 线段 { } // 向新段添加当前虚线 let 新段_rc = Arc::clone(线段序列.last().unwrap()); - Self::添加虚线(&新段_rc, Arc::clone(&当前虚线)); + Self::_添加虚线(&新段_rc, Arc::clone(&当前虚线)); let seg_idx = 线段序列.len() - 1; 线段序列[seg_idx] = 新段_rc; } let idx = 线段序列.len() - 1; - Self::刷新(&线段序列[idx], 配置); + Self::_刷新(&线段序列[idx], 配置); } if 需要递归 { @@ -1524,7 +1531,7 @@ impl 线段 { loop { if 当前层级 > 256 { - eprintln!("线段.扩展分析 递归深度超出 256"); + warn!("线段.扩展分析 递归深度超出 256"); return; } @@ -1560,7 +1567,7 @@ impl 线段 { // 验证当前线段 let 当前线段_rc = Arc::clone(线段序列.last().unwrap()); - Self::验证序列(&当前线段_rc, 虚线序列); + Self::_验证序列(&当前线段_rc, 虚线序列); let seg_idx = 线段序列.len() - 1; 线段序列[seg_idx] = 当前线段_rc; @@ -1584,7 +1591,7 @@ impl 线段 { *cur.基础序列.write().unwrap() = 前三个; } let seg_idx = 线段序列.len() - 1; - Self::武终(&线段序列[seg_idx], 0); + Self::_武终(&线段序列[seg_idx], 0); } else { let 当前 = Arc::clone(线段序列.last().unwrap()); Self::_弹出扩展线段(线段序列, &当前, line!()); @@ -1595,7 +1602,7 @@ impl 线段 { // 武终 let idx = 线段序列.len() - 1; - Self::武终(&线段序列[idx], 0); + Self::_武终(&线段序列[idx], 0); let 当前线段 = Arc::clone(线段序列.last().unwrap()); if 当前线段 @@ -1635,17 +1642,17 @@ impl 线段 { if 相对关系.是否缺口() { let 段_rc = Arc::clone(线段序列.last().unwrap()); - Self::添加虚线(&段_rc, Arc::clone(左)); + Self::_添加虚线(&段_rc, Arc::clone(左)); let seg_idx = 线段序列.len() - 1; 线段序列[seg_idx] = 段_rc; let 段_rc = Arc::clone(线段序列.last().unwrap()); - Self::添加虚线(&段_rc, Arc::clone(中)); + Self::_添加虚线(&段_rc, Arc::clone(中)); let seg_idx = 线段序列.len() - 1; 线段序列[seg_idx] = 段_rc; let seg_idx = 线段序列.len() - 1; - Self::武终(&线段序列[seg_idx], 0); + Self::_武终(&线段序列[seg_idx], 0); continue; } @@ -1685,6 +1692,25 @@ impl 线段 { /// /// 分析线段的内部中枢和MACD柱分段,判断是否发生内部背驰 pub fn 判断线段内部是否背驰(当前段: &虚线, 观察员: &观察者) -> bool { + let key = ( + 当前段 as *const 虚线 as usize, + 观察员 as *const 观察者 as usize, + ); + { + let mut cache = 线段背驰缓存.lock().unwrap(); + if let Some(val) = cached::Cached::cache_get(&mut *cache, &key) { + return *val; + } + } + + let result = Self::判断线段内部是否背驰_impl(当前段, 观察员); + + let mut cache = 线段背驰缓存.lock().unwrap(); + cached::Cached::cache_set(&mut *cache, key, result); + result + } + + fn 判断线段内部是否背驰_impl(当前段: &虚线, 观察员: &观察者) -> bool { let 实 = &当前段.实_中枢序列; let (阳, 阴, _, _) = Self::分割序列(当前段, None); @@ -1698,9 +1724,10 @@ impl 线段 { let 进入段 = &阳[阳.len() - 3]; let 离开段 = &阳[阳.len() - 1]; - if 进入段.序号.load(Ordering::Relaxed) >= 离开段.序号.load(Ordering::Relaxed) { - return false; - } + debug_assert!( + 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed), + "进入段.序号 >= 离开段.序号" + ); let 关系 = 相对方向::分析(进入段.高(), 进入段.低(), 离开段.高(), 离开段.低()); let mut 背驰 = false; let mut 盘整背驰 = false; @@ -1749,44 +1776,13 @@ impl 线段 { .unwrap() .iter() .position(|b| Arc::ptr_eq(b, &最后中枢.基础序列.read().unwrap()[0])) + && 序号 > 0 { - if 序号 > 0 { - let 进入段 = &当前段.基础序列.read().unwrap()[序号 - 1]; - let 离开段 = &阳[阳.len() - 1]; - if 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) - { - if 进入段.方向() != 离开段.方向() { - return crate::algorithm::divergence::背驰分析::测度背驰( - 进入段, 离开段, - ) && 虚线::买卖意义(离开段, 观察员).0; - } - let 关系 = 相对方向::分析( - 进入段.高(), - 进入段.低(), - 离开段.高(), - 离开段.低(), - ); - if ((进入段.方向().是否向上() && 关系.是否向上()) - || (进入段.方向().是否向下() && 关系.是否向下())) - && crate::algorithm::divergence::背驰分析::背驰模式( - 进入段, - 离开段, - &观察员.普通K线序列, - &观察员.配置, - &观察员.配置.线段内部背驰_模式, - ) - { - return true; - } - } - } - } - } else if 最后中枢.第三买卖线.read().unwrap().is_some() { - // 第三买卖点后盘整背驰 - let 进入段 = &阳[阳.len() - 3]; - let 离开段 = &阳[阳.len() - 1]; - if 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) - { + let 进入段 = &当前段.基础序列.read().unwrap()[序号 - 1]; + let 离开段 = &阳[阳.len() - 1]; + debug_assert!( + 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) + ); if 进入段.方向() != 离开段.方向() { return crate::algorithm::divergence::背驰分析::测度背驰( 进入段, 离开段, @@ -1807,6 +1803,31 @@ impl 线段 { return true; } } + } else if 最后中枢.第三买卖线.read().unwrap().is_some() { + // 第三买卖点后盘整背驰 + let 进入段 = &阳[阳.len() - 3]; + let 离开段 = &阳[阳.len() - 1]; + debug_assert!( + 进入段.序号.load(Ordering::Relaxed) < 离开段.序号.load(Ordering::Relaxed) + ); + if 进入段.方向() != 离开段.方向() { + return crate::algorithm::divergence::背驰分析::测度背驰( + 进入段, 离开段, + ) && 虚线::买卖意义(离开段, 观察员).0; + } + let 关系 = 相对方向::分析(进入段.高(), 进入段.低(), 离开段.高(), 离开段.低()); + if ((进入段.方向().是否向上() && 关系.是否向上()) + || (进入段.方向().是否向下() && 关系.是否向下())) + && crate::algorithm::divergence::背驰分析::背驰模式( + 进入段, + 离开段, + &观察员.普通K线序列, + &观察员.配置, + &观察员.配置.线段内部背驰_模式, + ) + { + return true; + } } } else { // 没有中枢 @@ -1843,7 +1864,6 @@ impl 线段 { for 停顿 in &停顿列表 { 笔序列.push(Arc::clone(停顿)); - 线段序列.clear(); let 笔序列_slice: Vec> = 笔序列.iter().map(Arc::clone).collect(); Self::分析( &笔序列_slice, @@ -1860,34 +1880,32 @@ impl 线段 { ); let 重复 = match (&线段序列.last(), &当前停顿) { - (Some(a), Some(ref b)) => Arc::ptr_eq(&*a.武.read().unwrap(), b), + (Some(a), Some(b)) => Arc::ptr_eq(&*a.武.read().unwrap(), b), _ => false, }; - if !重复 { - if let Some(最后线段) = 线段序列.last() { - if 最后线段.基础序列.read().unwrap().len() % 2 == 1 { - let 新段 = 虚线::创建线段(&最后线段.基础序列.read().unwrap()); - 新段 - .序号 - .store(段.序号.load(Ordering::Relaxed), Ordering::Relaxed); - let 新段_rc = Arc::new(新段); - Self::刷新(&新段_rc, &观察员.配置); - let 新段_inner = - Arc::try_unwrap(新段_rc).unwrap_or_else(|rc| (*rc).clone()); - if 新段_inner.方向() == 段.方向() { - 当前停顿 = Some(Arc::clone( - &*线段序列.last().unwrap().武.read().unwrap(), - )); - 结果.push(新段_inner); - } - } + if !重复 + && let Some(最后线段) = 线段序列.last() + && 最后线段.基础序列.read().unwrap().len() % 2 == 1 + { + let 新段 = 虚线::创建线段(&最后线段.基础序列.read().unwrap()); + 新段 + .序号 + .store(段.序号.load(Ordering::Relaxed), Ordering::Relaxed); + let 新段_rc = Arc::new(新段); + Self::_刷新(&新段_rc, &观察员.配置); + let 新段_inner = + Arc::try_unwrap(新段_rc).unwrap_or_else(|rc| (*rc).clone()); + if 新段_inner.方向() == 段.方向() { + 当前停顿 = + Some(Arc::clone(&*线段序列.last().unwrap().武.read().unwrap())); + 结果.push(新段_inner); } } - if Arc::as_ptr(停顿) != Arc::as_ptr(筆) { - if let Some(popped) = 笔序列.pop() { - popped.有效性.store(false, Ordering::Relaxed); - } + if Arc::as_ptr(停顿) != Arc::as_ptr(筆) + && let Some(popped) = 笔序列.pop() + { + popped.有效性.store(false, Ordering::Relaxed); } } } else { @@ -1913,3 +1931,171 @@ impl 线段 { 结果 } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::kline::bar::K线; + use crate::kline::chan_kline::缠论K线; + use crate::structure::fractal_obj::分型; + use crate::types::分型结构; + + fn 辅助_创建普K(时间戳: i64, 高: f64, 低: f64) -> Arc { + Arc::new(K线 { + 时间戳, + 高, + 低, + 开盘价: 低, + 收盘价: 高, + ..Default::default() + }) + } + + fn 辅助_创建缠K( + 时间戳: i64, + 高: f64, + 低: f64, + 方向: 相对方向, + 分型: Option<分型结构>, + ) -> Arc<缠论K线> { + let 普K = 辅助_创建普K(时间戳, 高, 低); + Arc::new(缠论K线::创建缠K( + 时间戳, 高, 低, 方向, 分型, 0, 普K, None, + )) + } + + fn 辅助_创建分型( + 左: Arc<缠论K线>, 中: Arc<缠论K线>, 右: Arc<缠论K线> + ) -> Arc<分型> { + Arc::new(分型::new(Some(左), 中, Some(右))) + } + + fn 辅助_创建顶分型(时间戳: i64, 高: f64) -> Arc<分型> { + 辅助_创建分型( + 辅助_创建缠K( + 时间戳 - 2, + 高 - 2.0, + 高 - 4.0, + 相对方向::向上, + Some(分型结构::上), + ), + 辅助_创建缠K(时间戳, 高, 高 - 3.0, 相对方向::向上, Some(分型结构::顶)), + 辅助_创建缠K( + 时间戳 + 2, + 高 - 2.0, + 高 - 4.0, + 相对方向::向下, + Some(分型结构::下), + ), + ) + } + + fn 辅助_创建底分型(时间戳: i64, 低: f64) -> Arc<分型> { + 辅助_创建分型( + 辅助_创建缠K( + 时间戳 - 2, + 低 + 4.0, + 低 + 2.0, + 相对方向::向下, + Some(分型结构::下), + ), + 辅助_创建缠K(时间戳, 低 + 3.0, 低, 相对方向::向下, Some(分型结构::底)), + 辅助_创建缠K( + 时间戳 + 2, + 低 + 4.0, + 低 + 2.0, + 相对方向::向上, + Some(分型结构::上), + ), + ) + } + + fn 辅助_创建笔(文: Arc<分型>, 武: Arc<分型>) -> Arc<虚线> { + Arc::new(虚线::创建笔(文, 武, true)) + } + + // ========== 四象 测试 ========== + + #[test] + fn test_四象_无缺口向上() { + let 文 = 辅助_创建底分型(100, 90.0); + let 武 = 辅助_创建顶分型(200, 110.0); + let 段 = 辅助_创建笔(文, 武); + assert_eq!(线段::四象(&段), "小阳"); + } + + #[test] + fn test_四象_无缺口向下() { + let 文 = 辅助_创建顶分型(100, 110.0); + let 武 = 辅助_创建底分型(200, 90.0); + let 段 = 辅助_创建笔(文, 武); + assert_eq!(线段::四象(&段), "少阴"); + } + + #[test] + fn test_四象_有缺口向上() { + let 文 = 辅助_创建底分型(100, 90.0); + let 武 = 辅助_创建顶分型(200, 110.0); + let 段 = 辅助_创建笔(文, 武); + *段.前一缺口.write().unwrap() = Some(缺口 { + 高: 105.0, + 低: 95.0, + }); + assert_eq!(线段::四象(&段), "老阳"); + } + + #[test] + fn test_四象_有缺口向下() { + let 文 = 辅助_创建顶分型(100, 110.0); + let 武 = 辅助_创建底分型(200, 90.0); + let 段 = 辅助_创建笔(文, 武); + *段.前一缺口.write().unwrap() = Some(缺口 { + 高: 105.0, + 低: 95.0, + }); + assert_eq!(线段::四象(&段), "老阴"); + } + + // ========== 特征序列状态 测试 ========== + + #[test] + fn test_特征序列状态_全部空() { + let 文 = 辅助_创建底分型(100, 90.0); + let 武 = 辅助_创建顶分型(200, 110.0); + let 段 = 辅助_创建笔(文, 武); + assert_eq!(线段::特征序列状态(&段), (false, false, false)); + } + + // ========== 获取缺口 测试 ========== + + #[test] + fn test_获取缺口_模式非文武返回空() { + let 文 = 辅助_创建底分型(100, 90.0); + let 武 = 辅助_创建顶分型(200, 110.0); + let 段 = 辅助_创建笔(文, 武); + *段.模式.write().unwrap() = "其他".into(); + assert!(线段::获取缺口(&段).is_none()); + } + + #[test] + fn test_获取缺口_特征序列不足返回空() { + let 文 = 辅助_创建底分型(100, 90.0); + let 武 = 辅助_创建顶分型(200, 110.0); + let 段 = 辅助_创建笔(文, 武); + assert!(线段::获取缺口(&段).is_none()); + } + + // ========== 分割序列 测试 ========== + + #[test] + fn test_分割序列_基本笔_无中枢() { + let 文 = 辅助_创建底分型(100, 90.0); + let 武 = 辅助_创建顶分型(200, 110.0); + let 段 = 辅助_创建笔(文, 武); + let (前, 后, 第三买卖线, 贯穿伤) = 线段::分割序列(&段, None); + // 独立笔无基础序列时,前也为空 + assert!(后.is_empty()); + assert!(第三买卖线.is_empty()); + assert!(贯穿伤.is_none()); + } +} diff --git a/chanlun/src/business/bsp.rs b/chanlun/src/business/bsp.rs index 6d2fe60..ff5966f 100644 --- a/chanlun/src/business/bsp.rs +++ b/chanlun/src/business/bsp.rs @@ -27,33 +27,45 @@ use crate::kline::chan_kline::缠论K线; use crate::structure::fractal_obj::分型; use crate::types::bsp_type::买卖点类型; use crate::types::分型结构; -use std::sync::atomic::Ordering; use std::sync::Arc; +use std::sync::atomic::Ordering; /// 基础买卖点 — 买卖点的基础数据结构 +/// +/// 包含买卖点的完整信息:类型、关联分型/K线、失效与终结状态等。 #[derive(Debug, Clone)] pub struct 基础买卖点 { + /// 买卖点备注文本 pub 备注: String, + /// 买卖点类型(一买/一卖/二买/二卖/三买/三卖/T1/T2/T3 等) pub 类型: 买卖点类型, + /// 买卖点对应的分型 pub 买卖点分型: Arc<分型>, + /// 买卖点对应的缠论K线(即分型的中缠K) pub 买卖点K线: Arc<缠论K线>, + /// 当前K线(买卖点生成时的K线) pub 当前K线: Arc, + /// 失效K线(买卖点失效时设置) pub 失效K线: Option>, + /// 终结K线(买卖点终结时设置) pub 终结K线: Option>, + /// 中枢破位值 pub 破位值: f64, + /// 分型结构(可选,用于补充确认) pub 结构: Option<分型结构>, - /// 当前缠K的序号 — 与 Python 一致,偏移计算使用缠论K线序号而非标的K线序号 - pub 当前序号: i64, + /// 创建时的缠K序号,用于偏移计算(与买卖点K线.序号同尺度) + /// None 时退化为使用当前K线.序号(bar序号,旧行为) + pub 当前缠K序号: Option, } impl 基础买卖点 { + /// 创建基础买卖点,买卖点K线自动取自买卖点分型的中缠K pub fn new( 类型: 买卖点类型, 当前K线: Arc, 买卖点分型: Arc<分型>, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> Self { let 买卖点K线 = Arc::clone(&买卖点分型.中); Self { @@ -66,13 +78,17 @@ impl 基础买卖点 { 终结K线: None, 破位值: 中枢破位值, 结构: None, - 当前序号, + 当前缠K序号: None, } } - /// 偏移 — 当前缠K序号与买卖点K线序号的差(对齐 Python,使用缠论K线序号) + /// 偏移 — 当前缠K序号与买卖点K线序号的差 + /// 如果设置了当前缠K序号(来自生成买卖点),使用缠K序号;否则退化为使用 bar序号 pub fn 偏移(&self) -> i64 { - self.当前序号 - self.买卖点K线.序号.load(Ordering::Relaxed) + match self.当前缠K序号 { + Some(ck_idx) => ck_idx - self.买卖点K线.序号.load(Ordering::Relaxed), + None => self.当前K线.序号 - self.买卖点K线.序号.load(Ordering::Relaxed), + } } /// 失效偏移 @@ -112,116 +128,206 @@ impl std::fmt::Display for 基础买卖点 { } } -/// 买卖点 — 包含一二三类买卖点的工厂方法 +/// 买卖点 — 包含全部 18 种买卖点类型的工厂方法 pub struct 买卖点; impl 买卖点 { + /// 创建 一卖 类型的基础买卖点 pub fn 一卖点( 买卖点分型: Arc<分型>, 当前K线: Arc, _标识: &str, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> 基础买卖点 { - 基础买卖点::new( - 买卖点类型::一卖, - 当前K线, - 买卖点分型, - 备注, - 中枢破位值, - 当前序号, - ) + 基础买卖点::new(买卖点类型::一卖, 当前K线, 买卖点分型, 备注, 中枢破位值) } + /// 创建 一买 类型的基础买卖点 pub fn 一买点( 买卖点分型: Arc<分型>, 当前K线: Arc, _标识: &str, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> 基础买卖点 { - 基础买卖点::new( - 买卖点类型::一买, - 当前K线, - 买卖点分型, - 备注, - 中枢破位值, - 当前序号, - ) + 基础买卖点::new(买卖点类型::一买, 当前K线, 买卖点分型, 备注, 中枢破位值) } + /// 创建 二卖 类型的基础买卖点 pub fn 二卖点( 买卖点分型: Arc<分型>, 当前K线: Arc, _标识: &str, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> 基础买卖点 { - 基础买卖点::new( - 买卖点类型::二卖, - 当前K线, - 买卖点分型, - 备注, - 中枢破位值, - 当前序号, - ) + 基础买卖点::new(买卖点类型::二卖, 当前K线, 买卖点分型, 备注, 中枢破位值) } + /// 创建 二买 类型的基础买卖点 pub fn 二买点( 买卖点分型: Arc<分型>, 当前K线: Arc, _标识: &str, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> 基础买卖点 { - 基础买卖点::new( - 买卖点类型::二买, - 当前K线, - 买卖点分型, - 备注, - 中枢破位值, - 当前序号, - ) + 基础买卖点::new(买卖点类型::二买, 当前K线, 买卖点分型, 备注, 中枢破位值) } + /// 创建 三卖 类型的基础买卖点 pub fn 三卖点( 买卖点分型: Arc<分型>, 当前K线: Arc, _标识: &str, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> 基础买卖点 { - 基础买卖点::new( - 买卖点类型::三卖, - 当前K线, - 买卖点分型, - 备注, - 中枢破位值, - 当前序号, - ) + 基础买卖点::new(买卖点类型::三卖, 当前K线, 买卖点分型, 备注, 中枢破位值) } + /// 创建 三买 类型的基础买卖点 pub fn 三买点( 买卖点分型: Arc<分型>, 当前K线: Arc, _标识: &str, 备注: String, 中枢破位值: f64, - 当前序号: i64, ) -> 基础买卖点 { - 基础买卖点::new( - 买卖点类型::三买, - 当前K线, - 买卖点分型, - 备注, - 中枢破位值, - 当前序号, - ) + 基础买卖点::new(买卖点类型::三买, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T1卖 类型的基础买卖点 + pub fn T1卖点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T1卖, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T1买 类型的基础买卖点 + pub fn T1买点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T1买, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T1P卖 类型的基础买卖点 + pub fn T1P卖点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T1P卖, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T1P买 类型的基础买卖点 + pub fn T1P买点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T1P买, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T2卖 类型的基础买卖点 + pub fn T2卖点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T2卖, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T2买 类型的基础买卖点 + pub fn T2买点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T2买, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T2S卖 类型的基础买卖点 + pub fn T2S卖点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T2S卖, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T2S买 类型的基础买卖点 + pub fn T2S买点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T2S买, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T3A卖 类型的基础买卖点 + pub fn T3A卖点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T3A卖, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T3A买 类型的基础买卖点 + pub fn T3A买点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T3A买, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T3B卖 类型的基础买卖点 + pub fn T3B卖点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T3B卖, 当前K线, 买卖点分型, 备注, 中枢破位值) + } + + /// 创建 T3B买 类型的基础买卖点 + pub fn T3B买点( + 买卖点分型: Arc<分型>, + 当前K线: Arc, + _标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点 { + 基础买卖点::new(买卖点类型::T3B买, 当前K线, 买卖点分型, 备注, 中枢破位值) } /// 生成买卖点 — 根据参数自动选择类型 @@ -238,12 +344,12 @@ impl 买卖点 { "卖" }; let 备注 = format!("{}_{}{}{}", 特征, 级别, 序号, 买卖); - let 破位值 = 买卖点分型.分型特征值; + let 破位值 = 买卖点分型.分型特征值(); // 当前K线 — 从缠K获取其标的K线 let 当前K线 = Arc::clone(&*当前缠K.标的K线.read().unwrap()); - // 当前序号 — 使用缠论K线序号(对齐 Python 偏移计算) - let 当前序号 = 当前缠K.序号.load(Ordering::Relaxed); + // 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算 + let 当前缠K序号 = 当前缠K.序号.load(Ordering::Relaxed); let 类型 = match (序号, 买卖) { ("一", "买") => 买卖点类型::一买, @@ -252,9 +358,238 @@ impl 买卖点 { ("二", "卖") => 买卖点类型::二卖, ("三", "买") => 买卖点类型::三买, ("三", "卖") => 买卖点类型::三卖, + ("T1", "买") => 买卖点类型::T1买, + ("T1", "卖") => 买卖点类型::T1卖, + ("T1P", "买") => 买卖点类型::T1P买, + ("T1P", "卖") => 买卖点类型::T1P卖, + ("T2", "买") => 买卖点类型::T2买, + ("T2", "卖") => 买卖点类型::T2卖, + ("T2S", "买") => 买卖点类型::T2S买, + ("T2S", "卖") => 买卖点类型::T2S卖, + ("T3A", "买") => 买卖点类型::T3A买, + ("T3A", "卖") => 买卖点类型::T3A卖, + ("T3B", "买") => 买卖点类型::T3B买, + ("T3B", "卖") => 买卖点类型::T3B卖, _ => 买卖点类型::一买, // fallback }; - 基础买卖点::new(类型, 当前K线, 买卖点分型, 备注, 破位值, 当前序号) + let mut bsp = 基础买卖点::new(类型, 当前K线, 买卖点分型, 备注, 破位值); + bsp.当前缠K序号 = Some(当前缠K序号); + bsp + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::kline::bar::K线; + use crate::kline::chan_kline::缠论K线; + use crate::structure::fractal_obj::分型; + use crate::types::分型结构; + use crate::types::相对方向; + use std::sync::Arc; + use std::sync::atomic::Ordering; + + fn 辅助_创建普K(时间戳: i64, 序号: i64) -> Arc { + Arc::new(K线 { + 时间戳, + 序号, + 高: 100.0, + 低: 90.0, + 开盘价: 95.0, + 收盘价: 95.0, + ..Default::default() + }) + } + + fn 辅助_创建缠K( + 序号: i64, + 时间戳: i64, + 高: f64, + 低: f64, + 分型: Option<分型结构>, + ) -> Arc<缠论K线> { + let 普K = 辅助_创建普K(时间戳, 0); + Arc::new(缠论K线::创建缠K( + 时间戳, + 高, + 低, + 相对方向::向上, + 分型, + 序号, + 普K, + None, + )) + } + + fn 辅助_创建底分型_中(序号: i64, 时间戳: i64) -> Arc<分型> { + let 中 = 辅助_创建缠K(序号, 时间戳, 100.0, 90.0, Some(分型结构::底)); + 中.序号.store(序号, Ordering::Relaxed); + 中.分型特征值.set(90.0); + let 左 = 辅助_创建缠K(序号 - 1, 时间戳 - 100, 100.0, 92.0, Some(分型结构::下)); + 左.序号.store(序号 - 1, Ordering::Relaxed); + let 右 = 辅助_创建缠K(序号 + 1, 时间戳 + 100, 100.0, 92.0, Some(分型结构::上)); + 右.序号.store(序号 + 1, Ordering::Relaxed); + Arc::new(分型::new(Some(左), 中, Some(右))) + } + + fn 辅助_创建顶分型_中(序号: i64, 时间戳: i64) -> Arc<分型> { + let 中 = 辅助_创建缠K(序号, 时间戳, 100.0, 90.0, Some(分型结构::顶)); + 中.序号.store(序号, Ordering::Relaxed); + 中.分型特征值.set(100.0); + let 左 = 辅助_创建缠K(序号 - 1, 时间戳 - 100, 98.0, 88.0, Some(分型结构::上)); + 左.序号.store(序号 - 1, Ordering::Relaxed); + let 右 = 辅助_创建缠K(序号 + 1, 时间戳 + 100, 98.0, 88.0, Some(分型结构::下)); + 右.序号.store(序号 + 1, Ordering::Relaxed); + Arc::new(分型::new(Some(左), 中, Some(右))) + } + + // ========== 基础买卖点 构造测试 ========== + + #[test] + fn test_基础买卖点_new() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 0); + let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "测试".into(), 90.0); + assert_eq!(bsp.备注, "测试"); + assert_eq!(bsp.类型, 买卖点类型::一买); + assert_eq!(bsp.破位值, 90.0); + assert!(bsp.失效K线.is_none()); + assert!(!bsp.有效性()); + } + + // ========== 偏移 测试 ========== + + #[test] + fn test_偏移_无缠K序号_退化为bar序号差值() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 15); // bar 序号=15 + let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0); + // 无当前缠K序号: 偏移 = 15 - 10 = 5 + assert_eq!(bsp.偏移(), 5); + } + + #[test] + fn test_偏移_有缠K序号_使用缠K序号差值() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 100); // bar 序号(不使用) + let mut bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0); + bsp.当前缠K序号 = Some(15); + // 有当前缠K序号: 偏移 = 15 - 10 = 5 + assert_eq!(bsp.偏移(), 5); + } + + // ========== 失效偏移 测试 ========== + + #[test] + fn test_失效偏移_无失效K线返回负一() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 0); + let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0); + assert_eq!(bsp.失效偏移(), -1); + } + + #[test] + fn test_失效偏移_有失效K线() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 0); + let mut bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0); + bsp.失效K线 = Some(辅助_创建普K(1200, 20)); // 序号=20 + // 失效偏移 = 20 - 10 = 10 + assert_eq!(bsp.失效偏移(), 10); + } + + // ========== 有效性 测试 ========== + + #[test] + fn test_有效性_无失效K线为false() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 0); + let bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0); + assert!(!bsp.有效性()); + } + + #[test] + fn test_有效性_有失效K线为true() { + let 分型 = 辅助_创建底分型_中(10, 1000); + let 当前K = 辅助_创建普K(1100, 0); + let mut bsp = 基础买卖点::new(买卖点类型::一买, 当前K, 分型.clone(), "".into(), 0.0); + bsp.失效K线 = Some(辅助_创建普K(1200, 0)); + assert!(bsp.有效性()); + } + + // ========== 生成买卖点 测试 ========== + + #[test] + fn test_生成买卖点_一买() { + let 分型 = 辅助_创建底分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征A", "一", "本级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::一买); + assert_eq!(bsp.当前缠K序号, Some(5)); + } + + #[test] + fn test_生成买卖点_一卖() { + let 分型 = 辅助_创建顶分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征A", "一", "本级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::一卖); + } + + #[test] + fn test_生成买卖点_二买() { + let 分型 = 辅助_创建底分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征B", "二", "同级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::二买); + } + + #[test] + fn test_生成买卖点_三卖() { + let 分型 = 辅助_创建顶分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征C", "三", "本级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::三卖); + } + + #[test] + fn test_生成买卖点_T1买() { + let 分型 = 辅助_创建底分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("事后", "T1", "次级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::T1买); + } + + #[test] + fn test_生成买卖点_T2S卖() { + let 分型 = 辅助_创建顶分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征D", "T2S", "同级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::T2S卖); + } + + #[test] + fn test_生成买卖点_T3A买() { + let 分型 = 辅助_创建底分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征E", "T3A", "本级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::T3A买); + } + + #[test] + fn test_生成买卖点_T3B买() { + let 分型 = 辅助_创建底分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征F", "T3B", "本级", 分型.clone(), 当前缠K); + assert_eq!(bsp.类型, 买卖点类型::T3B买); + } + + #[test] + fn test_生成买卖点_破位值来自分型特征值() { + let 分型 = 辅助_创建底分型_中(5, 1000); + let 当前缠K = 分型.中.clone(); + let bsp = 买卖点::生成买卖点("特征G", "一", "本级", 分型.clone(), 当前缠K); + assert_eq!(bsp.破位值, 分型.分型特征值()); } } diff --git a/chanlun/src/business/multi_frame.rs b/chanlun/src/business/multi_frame.rs index c7b5ead..0323179 100644 --- a/chanlun/src/business/multi_frame.rs +++ b/chanlun/src/business/multi_frame.rs @@ -29,6 +29,7 @@ use crate::kline::bar::K线; use std::collections::HashMap; use std::sync::Arc; use std::sync::RwLock; +use tracing::{error, info}; /// 立体分析器 — 多周期协调器 /// @@ -42,6 +43,7 @@ pub struct 立体分析器 { } impl 立体分析器 { + /// 创建立体分析器,自动创建K线合成器 + 每周期一个观察者 pub fn new( 符号: String, 周期组: Vec, @@ -51,6 +53,7 @@ impl 立体分析器 { let mut 周期组 = 周期组; 周期组.sort(); let 输入周期 = 周期组[0]; + let 显示周期 = 周期组[1]; let 默认配置 = 配置.unwrap_or_default(); let 配置组 = 配置组.unwrap_or_default(); @@ -71,6 +74,33 @@ impl 立体分析器 { 单体分析器.insert(周期, 观察员); } + // 显示周期特殊配置 + { + let 显示观察员 = 单体分析器.get(&显示周期).expect("显示周期观察者不存在"); + let mut guard = 显示观察员.write().unwrap(); + guard.配置.推送K线 = true; + guard.配置.推送笔 = true; + guard.配置.推送线段 = true; + guard.配置.图表展示 = true; + guard.重置基础序列(); + } + + // 非显示周期的基础缠K序列对齐至显示周期 + { + let 显示缠K序列 = 单体分析器 + .get(&显示周期) + .map(|o| o.read().unwrap().缠论K线序列.clone()) + .unwrap_or_default(); + + for &周期 in &周期组 { + if 周期 != 显示周期 + && let Some(观察员) = 单体分析器.get(&周期) + { + 观察员.write().unwrap().基础缠K序列 = 显示缠K序列.clone(); + } + } + } + Self { 周期组, 输入周期, @@ -83,11 +113,10 @@ impl 立体分析器 { /// 匹配 Python __K线回调:合成器完成K线时喂给观察者 pub fn 投喂K线(&mut self, 普K: K线) { if 普K.周期 != self.输入周期 { - eprintln!( + panic!( "立体分析器.投喂K线 周期不匹配 {} != {}", 普K.周期, self.输入周期 ); - return; } // Feed to synthesizer, get completion events @@ -97,6 +126,9 @@ impl 立体分析器 { for (周期, 完成K线) in 完成事件 { if let Some(观察员) = self.单体分析器.get(&周期) { 观察员.write().unwrap().增加原始K线(完成K线); + if let Some(当前K线) = self.K线合成器.获取当前K线(周期) { + 观察员.write().unwrap().增加原始K线(当前K线.clone()); + } } } } @@ -108,10 +140,13 @@ impl 立体分析器 { /// 测试_保存数据 — 多级别数据拆分保存 /// 创建父目录 PyM_{标识}_{起始时间}_{结束时间},各周期观察者保存到子目录 - pub fn 测试_保存数据(&self) { - let 根目录 = std::env::var("CHANLUN_DATA_DIR") - .map(std::path::PathBuf::from) - .unwrap_or_else(|_| std::env::temp_dir()); + pub fn 测试_保存数据(&self, root: Option<&str>) { + let 根目录 = match root { + Some(r) => std::path::PathBuf::from(r), + None => std::env::var("CHANLUN_DATA_DIR") + .map(std::path::PathBuf::from) + .unwrap_or_else(|_| std::env::temp_dir()), + }; let 起始时间 = self .单体分析器 @@ -139,7 +174,7 @@ impl 立体分析器 { let 保存路径 = 根目录.join(&目录标识); if let Err(e) = std::fs::create_dir_all(&保存路径) { - eprintln!("创建目录失败: {} -> {}", 保存路径.display(), e); + error!("创建目录失败: {} -> {}", 保存路径.display(), e); return; } @@ -152,6 +187,6 @@ impl 立体分析器 { } } - println!("多级别数据拆分保存完成,目录:{}", 保存路径.display()); + info!("多级别数据拆分保存完成,目录:{}", 保存路径.display()); } } diff --git a/chanlun/src/business/observer.rs b/chanlun/src/business/observer.rs index 5b417b8..3c54f7a 100644 --- a/chanlun/src/business/observer.rs +++ b/chanlun/src/business/observer.rs @@ -34,6 +34,7 @@ use crate::types::相对方向; use crate::utils::datetime; use std::sync::atomic::Ordering; use std::sync::{Arc, RwLock}; +use tracing::{error, info}; /// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算 pub struct 观察者 { @@ -76,9 +77,9 @@ pub struct 观察者 { } impl 观察者 { + /// 创建观察者,初始化所有序列为空,若配置了手动终止则解析时间戳 pub fn new(符号: String, 周期: i64, 配置: 缠论配置) -> Arc> { - let 终止时间戳 = if 配置.手动终止 != "1970-01-01 00:00:00" && !配置.手动终止.is_empty() - { + let 终止时间戳 = if !配置.手动终止.is_empty() { datetime::转化为时间戳(&配置.手动终止) } else { None @@ -147,10 +148,10 @@ impl 观察者 { /// 增加原始K线 — 单根K线投喂入口 pub fn 增加原始K线(&mut self, 普K: K线) { - if let Some(终止) = self.终止时间戳 { - if 普K.时间戳 > 终止 { - return; - } + if let Some(终止) = self.终止时间戳 + && 普K.时间戳 > 终止 + { + return; } self.__处理数据(普K); } @@ -270,6 +271,9 @@ impl 观察者 { } } + /// 识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现) + pub fn 识别买卖点(&self) {} + /// 静态重新分析 — 遍历所有缠K重新生成分型/笔/线段 pub fn 静态重新分析(&mut self) { self.分型序列.clear(); @@ -355,7 +359,7 @@ impl 观察者 { } /// 测试_保存数据 — 输出各序列数据文本到文件 - pub fn 测试_保存数据(&self, root: Option<&str>) { + pub fn 测试_保存数据(&self, root: Option<&str>) -> String { let 笔序列_文本数据: Vec = self.笔序列.iter().map(|b| b.获取数据文本()).collect(); let 线段序列_文本数据: Vec = self.线段序列.iter().map(|s| s.获取数据文本()).collect(); @@ -402,9 +406,7 @@ impl 观察者 { // 确定根目录 let 根目录 = match root { Some(r) => std::path::PathBuf::from(r), - None => std::env::var("CHANLUN_DATA_DIR") - .map(std::path::PathBuf::from) - .unwrap_or_else(|_| std::env::temp_dir()), + None => std::env::temp_dir(), }; // 生成子目录名称 @@ -414,8 +416,8 @@ impl 观察者 { let 保存路径 = 根目录.join(&目录标识); if let Err(e) = std::fs::create_dir_all(&保存路径) { - eprintln!("创建目录失败: {} -> {}", 保存路径.display(), e); - return; + error!("创建目录失败: {} -> {}", 保存路径.display(), e); + return String::new(); } // 缠K data for debugging @@ -480,11 +482,12 @@ impl 观察者 { let 文件路径 = 保存路径.join(format!("{}.txt", 文件名)); let 内容 = 数据列表.join("\n") + "\n"; if let Err(e) = std::fs::write(&文件路径, &内容) { - eprintln!("写入文件失败: {} -> {}", 文件路径.display(), e); + error!("写入文件失败: {} -> {}", 文件路径.display(), e); } } - println!("全部数据拆分保存完成,目录:{}", 保存路径.display()); + info!("全部数据拆分保存完成,目录:{}", 保存路径.display()); + 保存路径.display().to_string() } /// 加载本地数据 — 从 .nb 文件加载数据到当前观察者(先重置再投喂) @@ -552,9 +555,9 @@ mod tests { for (i, bi) in obs_ref.笔序列.iter().enumerate() { let pu_seq = bi.获取普K序列(&obs_ref.普通K线序列); if pu_seq.is_empty() { - println!("笔 {}: 获取普K序列 返回空 (fallback failed)", i); - println!(" 文.中.标的K线 原始起始序号: {}", bi.文.中.原始起始序号); - println!( + info!("笔 {}: 获取普K序列 返回空 (fallback failed)", i); + info!(" 文.中.标的K线 原始起始序号: {}", bi.文.中.原始起始序号); + info!( " 武.中.标的K线 原始结束序号: {}", bi.武 .read() @@ -563,7 +566,7 @@ mod tests { .原始结束序号 .load(Ordering::Relaxed) ); - println!(" 普通K线序列.len: {}", obs_ref.普通K线序列.len()); + info!(" 普通K线序列.len: {}", obs_ref.普通K线序列.len()); } else { let first_ptr = Arc::as_ptr(&pu_seq[0]); let found = obs_ref @@ -571,15 +574,15 @@ mod tests { .iter() .any(|k| Arc::as_ptr(k) == first_ptr); if !found { - println!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i); + info!("笔 {}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i); let wen_ptr = Arc::as_ptr(&*bi.文.中.标的K线.read().unwrap()); let wen_found = obs_ref .普通K线序列 .iter() .any(|k| Arc::as_ptr(k) == wen_ptr); - println!(" 文.中.标的K线 在序列中: {}", wen_found); + info!(" 文.中.标的K线 在序列中: {}", wen_found); } else { - println!("笔 {}: OK, 获取普K序列[0] 在序列中找到", i); + info!("笔 {}: OK, 获取普K序列[0] 在序列中找到", i); } } } @@ -602,21 +605,21 @@ mod tests { } let obs = obs_ref.read().unwrap(); - println!("普通K线序列.len: {}", obs.普通K线序列.len()); - println!("笔序列.len: {}", obs.笔序列.len()); + info!("普通K线序列.len: {}", obs.普通K线序列.len()); + info!("笔序列.len: {}", obs.笔序列.len()); for (i, bi) in obs.笔序列.iter().enumerate() { let pu_seq = bi.获取普K序列(&obs.普通K线序列); if pu_seq.is_empty() { - println!("笔 {}: 获取普K序列 返回空!", i); + info!("笔 {}: 获取普K序列 返回空!", i); } else { let first_ptr = Arc::as_ptr(&pu_seq[0]); let found = obs.普通K线序列.iter().any(|k| Arc::as_ptr(k) == first_ptr); if !found { - println!("笔 {}: 获取普K序列[0] 指针不在序列中!", i); + info!("笔 {}: 获取普K序列[0] 指针不在序列中!", i); } if i < 5 { - println!("笔 {}: OK, len={}", i, pu_seq.len()); + info!("笔 {}: OK, len={}", i, pu_seq.len()); } } } @@ -640,13 +643,13 @@ mod tests { let 文_ptr = Arc::as_ptr(&bi.文); let 文_found = obs_ref.分型序列.iter().any(|f| Arc::as_ptr(f) == 文_ptr); if !文_found { - println!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi.文.时间戳()); + info!("笔 {}: 文(时间戳={}) 不在分型序列中!", i, bi.文.时间戳()); } let 武_ptr = Arc::as_ptr(&*bi.武.read().unwrap()); let 武_found = obs_ref.分型序列.iter().any(|f| Arc::as_ptr(f) == 武_ptr); if !武_found { - println!( + info!( "笔 {}: 武(时间戳={}) 不在分型序列中!", i, bi.武.read().unwrap().时间戳() @@ -674,7 +677,7 @@ mod tests { let bi_ptr = Arc::as_ptr(bi_in_seg); let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr); if !found { - println!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j); + info!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j); } } } @@ -698,7 +701,7 @@ mod tests { let bi_ptr = Arc::as_ptr(bi_in_hub); let found = obs_ref.笔序列.iter().any(|b| Arc::as_ptr(b) == bi_ptr); if !found { - println!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j); + info!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j); } } } @@ -708,7 +711,7 @@ mod tests { let seg_ptr = Arc::as_ptr(seg_in_hub); let found = obs_ref.线段序列.iter().any(|s| Arc::as_ptr(s) == seg_ptr); if !found { - println!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j); + info!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j); } } } @@ -749,7 +752,7 @@ mod tests { assert_eq!(中枢1, 中枢2, "重复计算中枢数不一致"); assert_eq!(笔中枢1, 笔中枢2, "重复计算笔中枢数不一致"); - println!( + info!( "两次计算结果一致: 笔={}, 线段={}, 中枢={}, 笔中枢={}", 笔数1, 段数1, 中枢1, 笔中枢1 ); @@ -795,7 +798,7 @@ mod tests { assert_eq!(第一次笔数, 第二次笔数, "重置后重新投喂笔数不一致"); assert_eq!(第一次段数, 第二次段数, "重置后重新投喂线段数不一致"); - println!("重置后重投一致: 笔={}, 线段={}", 第一次笔数, 第一次段数); + info!("重置后重投一致: 笔={}, 线段={}", 第一次笔数, 第一次段数); } // ============================================================ @@ -882,14 +885,14 @@ mod tests { let 中_ptr = Arc::as_ptr(&f.中); let 中_found = obs_ref.缠论K线序列.iter().any(|k| Arc::as_ptr(k) == 中_ptr); if !中_found { - println!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.时间戳); + info!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.时间戳); } if let Some(ref 左) = f.左 { let 左_ptr = Arc::as_ptr(左); let 左_found = obs_ref.缠论K线序列.iter().any(|k| Arc::as_ptr(k) == 左_ptr); if !左_found { - println!("分型 {} 的 左 不在缠论K线序列中!", i); + info!("分型 {} 的 左 不在缠论K线序列中!", i); } } @@ -897,7 +900,7 @@ mod tests { let 右_ptr = Arc::as_ptr(右); let 右_found = obs_ref.缠论K线序列.iter().any(|k| Arc::as_ptr(k) == 右_ptr); if !右_found { - println!("分型 {} 的 右 不在缠论K线序列中!", i); + info!("分型 {} 的 右 不在缠论K线序列中!", i); } } } diff --git a/chanlun/src/business/synthesizer.rs b/chanlun/src/business/synthesizer.rs index b77bb09..00b31b0 100644 --- a/chanlun/src/business/synthesizer.rs +++ b/chanlun/src/business/synthesizer.rs @@ -34,6 +34,7 @@ pub struct K线合成器 { } impl K线合成器 { + /// 创建K线合成器,按周期升序排列,初始化当前K线和合成K线列表 pub fn new(标识: String, 周期组: Vec) -> Self { let mut 周期组 = 周期组; 周期组.sort(); @@ -53,6 +54,20 @@ impl K线合成器 { } } + /// 投喂 — 便捷入口,直接从 OHLCV 创建 K线 并投喂 + pub fn 投喂( + &mut self, + 时间戳: i64, + 开: f64, + 高: f64, + 低: f64, + 收: f64, + 量: f64, + ) -> Vec<(i64, K线)> { + let 普K = K线::创建普K(&self.标识, 时间戳, 开, 高, 低, 收, 量, 0, 0); + self.投喂K线(普K) + } + /// 投喂K线 — 输入最小周期K线,合成为所有目标周期 /// 返回本次投喂完成了哪些周期的K线(周期 → 完成K线) pub fn 投喂K线(&mut self, 普K: K线) -> Vec<(i64, K线)> { @@ -91,7 +106,7 @@ impl K线合成器 { fn _对齐时间戳(&self, 时间戳: i64, 周期: i64) -> i64 { if 周期 == 0 { - return 时间戳; + panic!("_对齐时间戳: 周期不能为0"); } (时间戳 / 周期) * 周期 } diff --git a/chanlun/src/config.rs b/chanlun/src/config.rs index 2cad0e0..51142a5 100644 --- a/chanlun/src/config.rs +++ b/chanlun/src/config.rs @@ -23,6 +23,7 @@ */ use serde::{Deserialize, Deserializer, Serialize}; +use tracing::warn; fn is_infinite_f64(v: &f64) -> bool { v.is_infinite() @@ -30,118 +31,223 @@ fn is_infinite_f64(v: &f64) -> bool { /// 缠论配置 —— 控制所有分析阶段的行为 /// -/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错 +/// 50+ 参数集中控制缠K合并、笔/线段划分、中枢识别、买卖点生成等所有阶段。 +/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错。 #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct 缠论配置 { // ---- 基础 ---- + /// 品种标识(如 "btcusd") pub 标识: String, // ---- 缠K ---- + /// 包含处理时使用合并替换模式(而非添加模式) pub 缠K合并替换: bool, // ---- 笔 ---- + /// 笔内最少缠K数量(含端点) pub 笔内元素数量: i64, + /// 笔内相同终点取舍开关 pub 笔内相同终点取舍: bool, + /// 笔内起始分型包含整笔 pub 笔内起始分型包含整笔: bool, + /// 笔内起始分型包含整笔(含右端点) pub 笔内起始分型包含整笔_包括右: bool, + /// 笔内原始K线包含整笔 pub 笔内原始K线包含整笔: bool, + /// 笔次级成笔(允许在非分型处成笔) pub 笔次级成笔: bool, + /// 笔弱化开关(允许更少元素成笔) pub 笔弱化: bool, + /// 笔弱化模式下的最小原始K线数 pub 笔弱化_原始数量: i64, // ---- 线段 ---- + /// 线段非缺口下的穿刺处理 pub 线段_非缺口下穿刺: bool, + /// 线段特征序列忽略老阴老阳 pub 线段_特征序列忽视老阴老阳: bool, + /// 线段缺口后紧急修正 pub 线段_缺口后紧急修正: bool, + /// 线段修正开关 pub 线段_修正: bool, + /// 线段内部中枢图显示 pub 线段内部中枢图显: bool, + /// 扩展线段当下分析模式 pub 扩展线段_当下分析: bool, // ---- 分析开关 ---- + /// 是否分析笔 pub 分析笔: bool, + /// 是否分析线段 pub 分析线段: bool, + /// 是否分析扩展线段 pub 分析扩展线段: bool, + /// 是否分析笔中枢 pub 分析笔中枢: bool, + /// 是否分析线段中枢 pub 分析线段中枢: bool, // ---- 终止 ---- + /// 手动终止时间(时间字符串,非空时生效) pub 手动终止: String, // ---- 指标 ---- + /// 是否计算技术指标 pub 计算指标: bool, + /// 是否计算布林带 + pub 计算BOLL: bool, + /// 指标计算方式(开/高/低/收/高低均值/高低收均值/开高低收均值) #[serde(deserialize_with = "deserialize_指标计算方式")] pub 指标计算方式: String, // ---- MACD ---- + /// MACD 快线 EMA 周期 pub 平滑异同移动平均线_快线周期: i64, + /// MACD 慢线 EMA 周期 pub 平滑异同移动平均线_慢线周期: i64, + /// MACD 信号线周期 pub 平滑异同移动平均线_信号周期: i64, + /// MACD 多参数列表: Vec<(key, 快线, 慢线, 信号)> + #[serde(default)] + pub MACD_参数列表: Vec<(String, i64, i64, i64)>, // ---- RSI ---- + /// RSI 计算周期 pub 相对强弱指数_周期: i64, + /// RSI SMA 平滑周期 pub 相对强弱指数_移动平均线周期: i64, + /// RSI 超买阈值 pub 相对强弱指数_超买阈值: f64, + /// RSI 超卖阈值 pub 相对强弱指数_超卖阈值: f64, + /// RSI 多周期列表: Vec<(key, 周期)> + #[serde(default)] + pub RSI_周期列表: Vec<(String, i64)>, // ---- KDJ ---- + /// KDJ RSV 周期 pub 随机指标_RSV周期: i64, + /// KDJ K 值平滑周期 pub 随机指标_K值平滑周期: i64, + /// KDJ D 值平滑周期 pub 随机指标_D值平滑周期: i64, + /// KDJ 超买阈值 pub 随机指标_超买阈值: f64, + /// KDJ 超卖阈值 pub 随机指标_超卖阈值: f64, + /// KDJ 多参数列表: Vec<(key, RSV周期, K平滑, D平滑)> + #[serde(default)] + pub KDJ_参数列表: Vec<(String, i64, i64, i64)>, + + // ---- BOLL ---- + /// 布林带周期 + pub 布林带_周期: i64, + /// 布林带标准差倍数 + pub 布林带_标准差倍数: f64, + /// BOLL 多参数列表: Vec<(key, 周期, 标准差倍数)> + #[serde(default)] + pub BOLL_参数列表: Vec<(String, i64, f64)>, + + // ---- 均线 ---- + /// 均线类型列表: ["SMA", "EMA", ...] + #[serde(default)] + pub 均线_类型列表: Vec, + /// 均线周期列表: [5, 10, 20, ...] + #[serde(default)] + pub 均线_周期列表: Vec, // ---- 推送/显示 ---- + /// 是否启用图表展示 pub 图表展示: bool, + /// 是否推送K线 pub 推送K线: bool, + /// 是否推送笔 pub 推送笔: bool, + /// 是否推送线段 pub 推送线段: bool, + /// 是否推送中枢 pub 推送中枢: bool, // ---- 图表展示细分 ---- + /// 图表展示笔 pub 图表展示_笔: bool, + /// 图表展示线段 pub 图表展示_线段: bool, + /// 图表展示扩展线段 pub 图表展示_扩展线段: bool, + /// 图表展示扩展线段(线段级) pub 图表展示_扩展线段_线段: bool, + /// 图表展示线段之线段 pub 图表展示_线段_线段: bool, + /// 图表展示笔中枢 pub 图表展示_中枢_笔: bool, + /// 图表展示线段中枢 pub 图表展示_中枢_线段: bool, + /// 图表展示扩展中枢 pub 图表展示_中枢_扩展线段: bool, + /// 图表展示扩展中枢(线段级) pub 图表展示_中枢_扩展线段_线段: bool, + /// 图表展示线段之中枢 pub 图表展示_中枢_线段_线段: bool, + /// 图表展示线段内部中枢 pub 图表展示_中枢_线段内部: bool, // ---- 买卖点 ---- + /// 买卖点偏移量 pub 买卖点偏移: i64, + /// 买卖点激进识别模式 pub 买卖点激进识别: bool, + /// 买卖点与MACD柱强相关 pub 买卖点与MACD柱强相关: bool, + /// 买卖点错过误差值 pub 买卖点错过误差值: f64, + /// 买卖点指标模式(任意/配置/全量/相对) #[serde(deserialize_with = "deserialize_买卖点_指标模式")] pub 买卖点_指标模式: String, + /// 买卖点指标匹配 MACD pub 买卖点_指标匹配_MACD: bool, + /// 买卖点指标匹配 KDJ pub 买卖点_指标匹配_KDJ: bool, + /// 买卖点指标匹配 RSI pub 买卖点_指标匹配_RSI: bool, + /// 买卖点背离率阈值(Infinity 表示不使用) #[serde(skip_serializing_if = "is_infinite_f64")] pub 买卖点_背离率: f64, + /// 买卖点 T2 回调阈值 pub 买卖点_T2_回调阈值: f64, + /// 买卖点 T2S 最大层级 pub 买卖点_T2S_最大层级: i64, + /// 买卖点峰值条件 pub 买卖点_峰值条件: bool, + /// 买卖点计算方式(峰/谷等) pub 买卖点_计算方式: String, + /// 是否计算线段BSP1 pub 买卖点_计算线段BSP1: bool, + /// 是否处理BSP2 pub 买卖点_处理BSP2: bool, + /// 是否计算线段BSP3 pub 买卖点_计算线段BSP3: bool, + /// 是否依赖T1买卖点 pub 买卖点_依赖T1: bool, + /// 买卖点中枢来源(实/虚/合) pub 买卖点_中枢来源: String, + /// 买卖点调试输出 pub 买卖点_调试输出: bool, // ---- 背驰 ---- + /// 线段内部背驰使用 MACD pub 线段内部背驰_MACD: bool, + /// 线段内部背驰使用斜率 pub 线段内部背驰_斜率: bool, + /// 线段内部背驰使用测度 pub 线段内部背驰_测度: bool, + /// 线段内部背驰模式(任意/配置/全量/相对) #[serde(deserialize_with = "deserialize_线段内部背驰_模式")] pub 线段内部背驰_模式: String, // ---- 文件 ---- + /// 加载数据文件路径 pub 加载文件路径: String, } @@ -163,7 +269,9 @@ where if VALID.contains(&s.as_str()) { Ok(s) } else { - eprintln!("\x1b[33m[配置警告]\x1b[m 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""); + warn!( + "[配置警告] 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"" + ); Ok(DEFAULT.to_string()) } } @@ -178,7 +286,9 @@ where if VALID.contains(&s.as_str()) { Ok(s) } else { - eprintln!("\x1b[33m[配置警告]\x1b[m 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""); + warn!( + "[配置警告] 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"" + ); Ok(DEFAULT.to_string()) } } @@ -193,7 +303,9 @@ where if VALID.contains(&s.as_str()) { Ok(s) } else { - eprintln!("\x1b[33m[配置警告]\x1b[m 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""); + warn!( + "[配置警告] 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"" + ); Ok(DEFAULT.to_string()) } } @@ -224,6 +336,7 @@ impl Default for 缠论配置 { 分析线段中枢: true, 手动终止: String::new(), 计算指标: true, + 计算BOLL: false, 指标计算方式: "收".into(), 平滑异同移动平均线_快线周期: 13, 平滑异同移动平均线_慢线周期: 31, @@ -237,6 +350,14 @@ impl Default for 缠论配置 { 随机指标_D值平滑周期: 5, 随机指标_超买阈值: 80.0, 随机指标_超卖阈值: 20.0, + MACD_参数列表: Vec::new(), + RSI_周期列表: Vec::new(), + KDJ_参数列表: Vec::new(), + 布林带_周期: 20, + 布林带_标准差倍数: 2.0, + BOLL_参数列表: Vec::new(), + 均线_类型列表: Vec::new(), + 均线_周期列表: Vec::new(), 图表展示: true, 推送K线: true, 推送笔: true, @@ -282,18 +403,64 @@ impl Default for 缠论配置 { } impl 缠论配置 { + /// 解析MACD参数列表 — 如果列表非空则使用列表,否则返回默认单组 + pub fn _解析MACD参数列表(&self) -> Vec<(String, i64, i64, i64)> { + if !self.MACD_参数列表.is_empty() { + return self.MACD_参数列表.clone(); + } + vec![( + "macd".into(), + self.平滑异同移动平均线_快线周期, + self.平滑异同移动平均线_慢线周期, + self.平滑异同移动平均线_信号周期, + )] + } + + /// 解析RSI周期列表 — 如果列表非空则使用列表,否则返回默认单组 + pub fn _解析RSI周期列表(&self) -> Vec<(String, i64)> { + if !self.RSI_周期列表.is_empty() { + return self.RSI_周期列表.clone(); + } + vec![("rsi".into(), self.相对强弱指数_周期)] + } + + /// 解析KDJ参数列表 — 如果列表非空则使用列表,否则返回默认单组 + pub fn _解析KDJ参数列表(&self) -> Vec<(String, i64, i64, i64)> { + if !self.KDJ_参数列表.is_empty() { + return self.KDJ_参数列表.clone(); + } + vec![( + "kdj".into(), + self.随机指标_RSV周期, + self.随机指标_K值平滑周期, + self.随机指标_D值平滑周期, + )] + } + + /// 解析BOLL参数列表 — 如果列表非空则使用列表,否则返回默认单组 + pub fn _解析BOLL参数列表(&self) -> Vec<(String, i64, f64)> { + if !self.BOLL_参数列表.is_empty() { + return self.BOLL_参数列表.clone(); + } + vec![("boll".into(), self.布林带_周期, self.布林带_标准差倍数)] + } + + /// 序列化为 JSON 字符串 pub fn to_json(&self) -> String { serde_json::to_string_pretty(self).unwrap_or_default() } + /// 从 JSON 字符串反序列化 pub fn from_json(json_str: &str) -> Result { serde_json::from_str(json_str) } + /// 保存配置到 JSON 文件 pub fn 保存配置(&self, path: &str) -> std::io::Result<()> { std::fs::write(path, self.to_json()) } + /// 从 JSON 文件加载配置 pub fn 加载配置(path: &str) -> Result> { let content = std::fs::read_to_string(path)?; let config = Self::from_json(&content)?; @@ -336,11 +503,11 @@ impl 缠论配置 { serde_json::Map, > = std::collections::BTreeMap::new(); for (key, value) in map { - if let Some(pos) = key.find('_') { - if let Ok(num) = key[..pos].parse::() { - let field = key[pos + 1..].to_string(); - groups.entry(num).or_default().insert(field, value.clone()); - } + if let Some(pos) = key.find('_') + && let Ok(num) = key[..pos].parse::() + { + let field = key[pos + 1..].to_string(); + groups.entry(num).or_default().insert(field, value.clone()); } } for (num, fields) in groups { @@ -366,10 +533,10 @@ impl 缠论配置 { (&self_json, &other_json) { for (key, self_val) in self_map { - if let Some(other_val) = other_map.get(key) { - if self_val != other_val { - diffs.push(key.clone()); - } + if let Some(other_val) = other_map.get(key) + && self_val != other_val + { + diffs.push(key.clone()); } } } diff --git a/chanlun/src/indicators/boll.rs b/chanlun/src/indicators/boll.rs new file mode 100644 index 0000000..783221e --- /dev/null +++ b/chanlun/src/indicators/boll.rs @@ -0,0 +1,169 @@ +/* + * 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 crate::kline::bar::K线; +use serde::{Deserialize, Serialize}; + +/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道 +#[derive(Debug, Clone, Serialize, Deserialize)] +#[serde(default)] +pub struct 布林带 { + /// 数据时间戳 + pub 时间戳: i64, + /// 计算周期 + pub 周期: usize, + /// 标准差倍数(通常为 2.0) + pub 标准差倍数: f64, + /// 上轨(中轨 + 倍数 × 标准差) + pub 上轨: f64, + /// 中轨(移动平均线) + pub 中轨: f64, + /// 下轨(中轨 - 倍数 × 标准差) + pub 下轨: f64, + /// 内部历史队列(不序列化) + #[serde(skip)] + _历史队列: Vec, + /// 内部均值缓存(不序列化) + #[serde(skip)] + _均值: f64, + /// 内部方差和缓存(不序列化) + #[serde(skip)] + _方差和: f64, +} + +impl Default for 布林带 { + fn default() -> Self { + Self { + 时间戳: 0, + 周期: 20, + 标准差倍数: 2.0, + 上轨: 0.0, + 中轨: 0.0, + 下轨: 0.0, + _历史队列: Vec::new(), + _均值: 0.0, + _方差和: 0.0, + } + } +} + +impl 布林带 { + /// 首次计算 BOLL 指标 — 从 K线 取值后计算 + pub fn 首次计算_K线( + k线: &K线, 计算方式: &str, 周期: usize, 标准差倍数: f64 + ) -> Self { + let 价格 = crate::indicators::K线取值(k线.开盘价, k线.高, k线.低, k线.收盘价, 计算方式); + Self::首次计算(k线.时间戳, 价格, 周期, 标准差倍数) + } + + /// 增量计算 BOLL 指标 — 从 K线 取值后递推 + pub fn 增量计算_K线(prev: &布林带, 当前K线: &K线, 计算方式: &str) -> Self { + let 价格 = crate::indicators::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ); + Self::增量计算(prev, 当前K线.时间戳, 价格) + } + + /// 首次计算 — 初始时上中下轨都等于当前价格 + pub fn 首次计算(时间戳: i64, 价格: f64, 周期: usize, 标准差倍数: f64) -> Self { + Self { + 时间戳, + 周期, + 标准差倍数, + 上轨: 价格, + 中轨: 价格, + 下轨: 价格, + _历史队列: vec![价格], + _均值: 价格, + _方差和: 0.0, + } + } + + /// 增量计算 — 基于前一个布林带状态递推计算新的布林带 + pub fn 增量计算(prev: &布林带, 时间戳: i64, 价格: f64) -> Self { + let 周期 = prev.周期; + let 标准差倍数 = prev.标准差倍数; + + let mut q = prev._历史队列.clone(); + q.push(价格); + if q.len() > 周期 { + q.remove(0); + } + + let (_均值, _方差和) = if q.len() < 周期 { + let mean = q.iter().sum::() / q.len() as f64; + let var_sum = q.iter().map(|v| (v - mean).powi(2)).sum(); + (mean, var_sum) + } else { + let n = 周期 as f64; + let old_val = if prev._历史队列.len() >= 周期 { + prev._历史队列[0] + } else { + q[0] + }; + let new_mean = prev._均值 + (价格 - old_val) / n; + let new_var = + prev._方差和 + (价格 - old_val) * (价格 - new_mean + old_val - prev._均值); + (new_mean, new_var) + }; + + let std = (_方差和 / q.len() as f64).sqrt(); + Self { + 时间戳, + 周期, + 标准差倍数, + 中轨: _均值, + 上轨: _均值 + 标准差倍数 * std, + 下轨: _均值 - 标准差倍数 * std, + _历史队列: q, + _均值, + _方差和, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_boll_first() { + let b = 布林带::首次计算(1000, 100.0, 20, 2.0); + assert!((b.上轨 - 100.0).abs() < 0.01); + assert!((b.中轨 - 100.0).abs() < 0.01); + assert!((b.下轨 - 100.0).abs() < 0.01); + } + + #[test] + fn test_boll_incremental() { + let b1 = 布林带::首次计算(1000, 100.0, 5, 2.0); + let b2 = 布林带::增量计算(&b1, 1001, 102.0); + assert!(b2.上轨 >= b2.中轨); + assert!(b2.下轨 <= b2.中轨); + } +} diff --git a/chanlun/src/indicators/calculator.rs b/chanlun/src/indicators/calculator.rs new file mode 100644 index 0000000..6ea33af --- /dev/null +++ b/chanlun/src/indicators/calculator.rs @@ -0,0 +1,301 @@ +/* + * 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 super::container::{指标值, 指标容器}; +use super::{布林带, 平滑异同移动平均线, 相对强弱指数, 随机指标}; +use crate::config::缠论配置; +use crate::kline::bar::K线; +use std::sync::Arc; + +/// 指标计算器 — 在缠K合并之前,增量计算所有开启的指标并挂载到K线上 +pub struct 指标计算器; + +impl 指标计算器 { + /// 增量计算所有开启的指标,将结果写入 当前K线.指标 + /// + /// `现有序列` 不包含当前K线;prev 取自 现有序列.last() + /// 通过 RwLock 内部可变性,以 `&K线` 共享引用写入指标值 + pub fn 计算并挂载(当前K线: &K线, 现有序列: &[Arc], 配置: &缠论配置) { + let prev_guard = 现有序列.last().map(|k| k.指标.read().unwrap()); + let prev = prev_guard.as_deref(); + if 配置.计算指标 { + Self::_计算MACD组(当前K线, prev, 配置); + Self::_计算RSI组(当前K线, prev, 配置); + Self::_计算KDJ组(当前K线, prev, 配置); + Self::_计算BOLL组(当前K线, prev, 配置); + } + Self::_更新均线(当前K线, 现有序列, 配置); + } + + fn _计算MACD组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) { + let 计算方式 = &配置.指标计算方式; + for (i, (key, 快, 慢, 信号)) in 配置._解析MACD参数列表().into_iter().enumerate() + { + let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) { + if let 指标值::MACD(prev_macd) = prev_val { + 指标值::MACD(平滑异同移动平均线::增量计算( + prev_macd, + super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ), + 当前K线.时间戳, + )) + } else { + continue; + } + } else { + 指标值::MACD(平滑异同移动平均线::首次计算( + super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ), + 当前K线.时间戳, + 快, + 慢, + 信号, + )) + }; + 当前K线.指标.write().unwrap().设置(&key, val.clone()); + if i == 0 { + 当前K线.指标.write().unwrap().设置("macd", val); + } + } + } + + fn _计算RSI组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) { + let 计算方式 = &配置.指标计算方式; + for (i, (key, 周期)) in 配置._解析RSI周期列表().into_iter().enumerate() { + let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) { + if let 指标值::RSI(prev_rsi) = prev_val { + 指标值::RSI(相对强弱指数::增量计算( + prev_rsi, + super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ), + 当前K线.时间戳, + )) + } else { + continue; + } + } else { + 指标值::RSI(相对强弱指数::首次计算( + super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ), + 当前K线.时间戳, + 周期, + 配置.相对强弱指数_超买阈值, + 配置.相对强弱指数_超卖阈值, + Some(配置.相对强弱指数_移动平均线周期), + )) + }; + 当前K线.指标.write().unwrap().设置(&key, val.clone()); + if i == 0 { + 当前K线.指标.write().unwrap().设置("rsi", val); + } + } + } + + fn _计算KDJ组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) { + for (i, (key, rsv, k平滑, d平滑)) in 配置._解析KDJ参数列表().into_iter().enumerate() + { + let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) { + if let 指标值::KDJ(prev_kdj) = prev_val { + 指标值::KDJ(随机指标::增量计算( + prev_kdj, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 当前K线.时间戳, + )) + } else { + continue; + } + } else { + 指标值::KDJ(随机指标::首次计算( + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 当前K线.时间戳, + rsv, + k平滑, + d平滑, + 配置.随机指标_超买阈值, + 配置.随机指标_超卖阈值, + )) + }; + 当前K线.指标.write().unwrap().设置(&key, val.clone()); + if i == 0 { + 当前K线.指标.write().unwrap().设置("kdj", val); + } + } + } + + fn _计算BOLL组(当前K线: &K线, prev: Option<&指标容器>, 配置: &缠论配置) { + let 计算方式 = &配置.指标计算方式; + for (i, (key, 周期, 标准差倍数)) in 配置._解析BOLL参数列表().into_iter().enumerate() + { + let val = if let Some(prev_val) = prev.and_then(|p| p.获取(&key)) { + if let 指标值::BOLL(prev_boll) = prev_val { + 指标值::BOLL(布林带::增量计算( + prev_boll, + 当前K线.时间戳, + super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ), + )) + } else { + continue; + } + } else { + 指标值::BOLL(布林带::首次计算( + 当前K线.时间戳, + super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ), + 周期 as usize, + 标准差倍数, + )) + }; + 当前K线.指标.write().unwrap().设置(&key, val.clone()); + if i == 0 { + 当前K线.指标.write().unwrap().设置("boll", val); + } + } + } + + fn _更新均线(当前K线: &K线, 现有序列: &[Arc], 配置: &缠论配置) { + if 配置.均线_类型列表.is_empty() || 配置.均线_周期列表.is_empty() { + return; + } + let 计算方式 = &配置.指标计算方式; + let 当前价 = super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ); + for ma_type in &配置.均线_类型列表 { + for period in &配置.均线_周期列表 { + let key = format!("{}_{}", ma_type, period); + let 值 = match ma_type.as_str() { + "SMA" => Self::_增量SMA(现有序列, 当前价, *period, 计算方式, &key), + "EMA" => Self::_增量EMA(现有序列, 当前价, *period, 计算方式, &key), + _ => continue, + }; + if let Some(均线_map) = 当前K线.指标.write().unwrap().均线_mut() { + 均线_map.insert(key, 值); + } + } + } + } + + /// 增量 SMA: 现有序列 (不含当前K线) + 当前价 + fn _增量SMA( + 现有序列: &[Arc], + 当前价: f64, + period: i64, + 计算方式: &str, + prev_key: &str, + ) -> f64 { + let existing_len = 现有序列.len(); + let p = period as usize; + // 现有序列 + 当前 = total_len + let total_len = existing_len + 1; + if total_len <= p { + let mut sum: f64 = 现有序列[existing_len.saturating_sub(p.saturating_sub(1))..] + .iter() + .map(|k| super::K线取值(k.开盘价, k.高, k.低, k.收盘价, 计算方式)) + .sum(); + sum += 当前价; + return sum / (total_len as f64).max(1.0); + } + // 尝试从前一根K线获取缓存的SMA + if let Some(prev) = 现有序列.last().and_then(|k| { + let guard = k.指标.read().unwrap(); + guard.均线().and_then(|m| m.get(prev_key)).copied() + }) { + let oldest = super::K线取值( + 现有序列[existing_len - p].开盘价, + 现有序列[existing_len - p].高, + 现有序列[existing_len - p].低, + 现有序列[existing_len - p].收盘价, + 计算方式, + ); + return prev + (当前价 - oldest) / period as f64; + } + // 回退:完整计算 + let mut sum: f64 = 现有序列[existing_len.saturating_sub(p.saturating_sub(1))..] + .iter() + .map(|k| super::K线取值(k.开盘价, k.高, k.低, k.收盘价, 计算方式)) + .sum(); + sum += 当前价; + sum / (total_len as f64).min(p as f64) + } + + /// 增量 EMA: 现有序列 (不含当前K线) + 当前价 + fn _增量EMA( + 现有序列: &[Arc], + 当前价: f64, + period: i64, + _计算方式: &str, + prev_key: &str, + ) -> f64 { + let 前值 = 现有序列.last().and_then(|k| { + let guard = k.指标.read().unwrap(); + guard.均线().and_then(|m| m.get(prev_key)).copied() + }); + match 前值 { + None => 当前价, + Some(prev) => { + let k = 2.0 / (period as f64 + 1.0); + 当前价 * k + prev * (1.0 - k) + } + } + } +} diff --git a/chanlun/src/indicators/container.rs b/chanlun/src/indicators/container.rs new file mode 100644 index 0000000..acfc07d --- /dev/null +++ b/chanlun/src/indicators/container.rs @@ -0,0 +1,202 @@ +/* + * 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 super::{布林带, 平滑异同移动平均线, 相对强弱指数, 随机指标}; +use serde::{Deserialize, Serialize}; +use std::collections::HashMap; + +/// 统一指标值 — 支持所有指标类型的动态注册 +#[derive(Debug, Clone, Serialize, Deserialize)] +pub enum 指标值 { + /// MACD 指标 + MACD(平滑异同移动平均线), + /// RSI 指标 + RSI(相对强弱指数), + /// KDJ 指标 + KDJ(随机指标), + /// 布林带指标 + BOLL(布林带), + /// 均线组 (key → 值) + 均线(HashMap), + /// 单值指标组 (key → 值) + 单值(HashMap), +} + +/// 指标容器 — 挂载在每根 K线上,基于注册表模式持有该时刻所有指标快照 +/// +/// 与 Python `指标容器` 保持一致: +/// - 复杂指标:MACD/RSI/KDJ/BOLL,通过默认 key("macd"/"rsi"/"kdj"/"boll")访问 +/// - 多参数变体:key 格式 "MACD_{快}_{慢}_{信号}" / "RSI_{周期}" 等 +/// - 均线组:通过 `均线` 子映射访问,key 格式 "{类型}_{周期}" +/// - 单值指标:通过 `单值` 子映射访问,key 格式 "{名称}_{周期}" +#[derive(Debug, Clone, Default, Serialize, Deserialize)] +pub struct 指标容器 { + pub _数据: HashMap>, +} + +impl 指标容器 { + /// 创建指标容器,预注册 macd/rsi/kdj/boll/均线/单值 默认槽位 + pub fn new() -> Self { + let mut _数据 = HashMap::new(); + _数据.insert("macd".into(), None); + _数据.insert("rsi".into(), None); + _数据.insert("kdj".into(), None); + _数据.insert("boll".into(), None); + _数据.insert("均线".into(), Some(指标值::均线(HashMap::new()))); + _数据.insert("单值".into(), Some(指标值::单值(HashMap::new()))); + Self { _数据 } + } + + /// 预注册指标(不覆盖已有值) + pub fn 注册(&mut self, 名称: &str, 默认值: Option<指标值>) { + if !self._数据.contains_key(名称) { + self._数据.insert(名称.to_string(), 默认值); + } + } + + /// 按名称获取指标值 + pub fn 获取(&self, 名称: &str) -> Option<&指标值> { + self._数据.get(名称).and_then(|v| v.as_ref()) + } + + /// 按名称设置指标值 + pub fn 设置(&mut self, 名称: &str, 值: 指标值) { + self._数据.insert(名称.to_string(), Some(值)); + } + + /// 检查是否包含指定名称的指标 + pub fn 包含(&self, 名称: &str) -> bool { + self._数据.contains_key(名称) + } + + // ---- 默认指标便捷访问 ---- + + /// 获取默认 MACD 指标 + pub fn macd(&self) -> Option<&平滑异同移动平均线> { + match self._数据.get("macd")?.as_ref()? { + 指标值::MACD(m) => Some(m), + _ => None, + } + } + + /// 克隆获取默认 MACD 指标 + pub fn macd_cloned(&self) -> Option<平滑异同移动平均线> { + self.macd().cloned() + } + + /// 设置默认 MACD 指标 + pub fn set_macd(&mut self, m: 平滑异同移动平均线) { + self._数据.insert("macd".into(), Some(指标值::MACD(m))); + } + + /// 获取默认 RSI 指标 + pub fn rsi(&self) -> Option<&相对强弱指数> { + match self._数据.get("rsi")?.as_ref()? { + 指标值::RSI(r) => Some(r), + _ => None, + } + } + + /// 克隆获取默认 RSI 指标 + pub fn rsi_cloned(&self) -> Option<相对强弱指数> { + self.rsi().cloned() + } + + /// 设置默认 RSI 指标 + pub fn set_rsi(&mut self, r: 相对强弱指数) { + self._数据.insert("rsi".into(), Some(指标值::RSI(r))); + } + + /// 获取默认 KDJ 指标 + pub fn kdj(&self) -> Option<&随机指标> { + match self._数据.get("kdj")?.as_ref()? { + 指标值::KDJ(k) => Some(k), + _ => None, + } + } + + /// 克隆获取默认 KDJ 指标 + pub fn kdj_cloned(&self) -> Option<随机指标> { + self.kdj().cloned() + } + + /// 设置默认 KDJ 指标 + pub fn set_kdj(&mut self, k: 随机指标) { + self._数据.insert("kdj".into(), Some(指标值::KDJ(k))); + } + + /// 获取默认布林带指标 + pub fn boll(&self) -> Option<&布林带> { + match self._数据.get("boll")?.as_ref()? { + 指标值::BOLL(b) => Some(b), + _ => None, + } + } + + /// 克隆获取默认布林带指标 + pub fn boll_cloned(&self) -> Option<布林带> { + self.boll().cloned() + } + + /// 设置默认布林带指标 + pub fn set_boll(&mut self, b: 布林带) { + self._数据.insert("boll".into(), Some(指标值::BOLL(b))); + } + + /// 获取均线组 + pub fn 均线(&self) -> Option<&HashMap> { + match self._数据.get("均线")?.as_ref()? { + 指标值::均线(m) => Some(m), + _ => None, + } + } + + /// 获取均线组可变引用 + pub fn 均线_mut(&mut self) -> Option<&mut HashMap> { + match self._数据.get_mut("均线")?.as_mut()? { + 指标值::均线(m) => Some(m), + _ => None, + } + } + + /// 获取单值指标组 + pub fn 单值(&self) -> Option<&HashMap> { + match self._数据.get("单值")?.as_ref()? { + 指标值::单值(s) => Some(s), + _ => None, + } + } +} + +impl std::fmt::Display for 指标容器 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let keys: Vec<&str> = self + ._数据 + .iter() + .filter(|(_, v)| v.is_some()) + .map(|(k, _)| k.as_str()) + .collect(); + write!(f, "指标容器({})", keys.join(", ")) + } +} diff --git a/chanlun/src/indicators/kdj.rs b/chanlun/src/indicators/kdj.rs index 817064b..512ea42 100644 --- a/chanlun/src/indicators/kdj.rs +++ b/chanlun/src/indicators/kdj.rs @@ -22,6 +22,7 @@ * SOFTWARE. */ +use crate::kline::bar::K线; use serde::{Deserialize, Serialize}; /// 随机指标 (KDJ) @@ -30,23 +31,41 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct 随机指标 { + /// 数据时间戳 pub 时间戳: i64, + /// 最高价 pub 最高价: f64, + /// 最低价 pub 最低价: f64, + /// 收盘价 pub 收盘价: f64, + /// RSV 周期 pub N: i64, + /// K 值平滑周期 pub M1: i64, + /// D 值平滑周期 pub M2: i64, + /// 超买阈值 pub 超买阈值: f64, + /// 超卖阈值 pub 超卖阈值: f64, + /// RSV 值(未成熟随机值) pub RSV: Option, + /// K 值 pub K: Option, + /// D 值 pub D: Option, + /// J 值 (3K - 2D) pub J: Option, + /// 历史最高价队列(滑动窗口) pub 历史最高价队列: Vec, + /// 历史最低价队列(滑动窗口) pub 历史最低价队列: Vec, + /// 前一个 RSV(用于平滑递推) pub 前一个RSV: Option, + /// 前一个 K(用于平滑递推) pub 前一个K: Option, + /// 前一个 D(用于平滑递推) pub 前一个D: Option, } @@ -78,6 +97,8 @@ impl Default for 随机指标 { impl 随机指标 { /// 首次计算 KDJ(无历史数据时) #[allow(clippy::too_many_arguments)] + /// 首次计算 KDJ — 无历史数据时的初始计算 + #[allow(clippy::too_many_arguments)] pub fn 首次计算( 初始最高价: f64, 初始最低价: f64, @@ -111,6 +132,39 @@ impl 随机指标 { } } + /// 首次计算 KDJ 指标 — 从 K线 取值后计算(KDJ 始终使用 高/低/收) + pub fn 首次计算_K线( + k线: &K线, + RSV周期: i64, + K值平滑周期: i64, + D值平滑周期: i64, + 超买阈值: f64, + 超卖阈值: f64, + ) -> Self { + Self::首次计算( + k线.高, + k线.低, + k线.收盘价, + k线.时间戳, + RSV周期, + K值平滑周期, + D值平滑周期, + 超买阈值, + 超卖阈值, + ) + } + + /// 增量计算 KDJ 指标 — 从 K线 取值后递推 + pub fn 增量计算_K线(前一个KDJ: &Self, 当前K线: &K线) -> Self { + Self::增量计算( + 前一个KDJ, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 当前K线.时间戳, + ) + } + /// 基于前一个 KDJ 增量计算当前 KDJ pub fn 增量计算( 前一个KDJ: &Self, diff --git a/chanlun/src/indicators/macd.rs b/chanlun/src/indicators/macd.rs index 26952f9..cea8b6f 100644 --- a/chanlun/src/indicators/macd.rs +++ b/chanlun/src/indicators/macd.rs @@ -22,6 +22,7 @@ * SOFTWARE. */ +use crate::kline::bar::K线; use serde::{Deserialize, Serialize}; /// 平滑异同移动平均线 (MACD) @@ -30,18 +31,29 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct 平滑异同移动平均线 { + /// 数据时间戳 pub 时间戳: i64, + /// 收盘价 pub 收盘价: f64, + /// 快线 EMA 周期 pub 快线周期: i64, + /// 慢线 EMA 周期 pub 慢线周期: i64, + /// 信号线周期 pub 信号周期: i64, + /// DIF 值(快线 - 慢线) pub DIF: Option, + /// DEA 值(DIF 的信号线) pub DEA: Option, + /// MACD 柱(2 * (DIF - DEA)) #[serde(rename = "MACD柱")] #[serde(default)] pub MACD柱: f64, + /// 快线 EMA 值 pub 快线EMA: Option, + /// 慢线 EMA 值 pub 慢线EMA: Option, + /// DEA EMA 值 pub DEA_EMA: Option, } @@ -97,6 +109,30 @@ impl 平滑异同移动平均线 { } } + /// 首次计算 MACD 指标 — 从 K线 取值后计算 + pub fn 首次计算_K线( + k线: &K线, + 计算方式: &str, + 快线周期: i64, + 慢线周期: i64, + 信号周期: i64, + ) -> Self { + let 价格 = super::K线取值(k线.开盘价, k线.高, k线.低, k线.收盘价, 计算方式); + Self::首次计算(价格, k线.时间戳, 快线周期, 慢线周期, 信号周期) + } + + /// 增量计算 MACD 指标 — 从 K线 取值后递推 + pub fn 增量计算_K线(前一个MACD: &Self, 当前K线: &K线, 计算方式: &str) -> Self { + let 价格 = super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ); + Self::增量计算(前一个MACD, 价格, 当前K线.时间戳) + } + /// 基于前一个 MACD 指标增量计算当前 MACD pub fn 增量计算(前一个MACD: &Self, 当前收盘价: f64, 当前时间: i64) -> Self { // 快线 EMA diff --git a/chanlun/src/indicators/mod.rs b/chanlun/src/indicators/mod.rs index 2597eb5..c1f4a58 100644 --- a/chanlun/src/indicators/mod.rs +++ b/chanlun/src/indicators/mod.rs @@ -22,10 +22,16 @@ * SOFTWARE. */ +pub mod boll; +pub mod calculator; +pub mod container; pub mod kdj; pub mod macd; pub mod rsi; +pub use boll::布林带; +pub use calculator::指标计算器; +pub use container::{指标值, 指标容器}; pub use kdj::随机指标; pub use macd::平滑异同移动平均线; pub use rsi::相对强弱指数; diff --git a/chanlun/src/indicators/rsi.rs b/chanlun/src/indicators/rsi.rs index b3a5c23..2f2ead0 100644 --- a/chanlun/src/indicators/rsi.rs +++ b/chanlun/src/indicators/rsi.rs @@ -22,6 +22,7 @@ * SOFTWARE. */ +use crate::kline::bar::K线; use serde::{Deserialize, Serialize}; /// 相对强弱指数 (RSI) @@ -30,19 +31,33 @@ use serde::{Deserialize, Serialize}; #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(default)] pub struct 相对强弱指数 { + /// 数据时间戳 pub 时间戳: i64, + /// 收盘价 pub 收盘价: f64, + /// RSI 计算周期 pub 周期: i64, + /// 超买阈值 pub 超买阈值: f64, + /// 超卖阈值 pub 超卖阈值: f64, + /// RSI SMA 平滑周期 pub RSI_SMA周期: Option, + /// RSI 值 pub RSI: Option, + /// 平均上涨幅度 pub 平均上涨: Option, + /// 平均下跌幅度 pub 平均下跌: Option, + /// 当前上涨幅度 pub 上涨幅度: f64, + /// 当前下跌幅度 pub 下跌幅度: f64, + /// 平滑系数 (1/周期) pub 平滑系数: f64, + /// RSI SMA 值 pub RSI_SMA: Option, + /// RSI 历史队列(用于滚动计算) pub RSI历史队列: Vec, } @@ -95,6 +110,31 @@ impl 相对强弱指数 { } } + /// 首次计算 RSI 指标 — 从 K线 取值后计算 + pub fn 首次计算_K线( + k线: &K线, + 计算方式: &str, + 周期: i64, + 超买阈值: f64, + 超卖阈值: f64, + RSI_SMA周期: Option, + ) -> Self { + let 价格 = super::K线取值(k线.开盘价, k线.高, k线.低, k线.收盘价, 计算方式); + Self::首次计算(价格, k线.时间戳, 周期, 超买阈值, 超卖阈值, RSI_SMA周期) + } + + /// 增量计算 RSI 指标 — 从 K线 取值后递推 + pub fn 增量计算_K线(前一个RSI: &Self, 当前K线: &K线, 计算方式: &str) -> Self { + let 价格 = super::K线取值( + 当前K线.开盘价, + 当前K线.高, + 当前K线.低, + 当前K线.收盘价, + 计算方式, + ); + Self::增量计算(前一个RSI, 价格, 当前K线.时间戳) + } + /// 基于前一个 RSI 增量计算当前 RSI pub fn 增量计算(前一个RSI: &Self, 当前收盘价: f64, 当前时间: i64) -> Self { let 周期 = 前一个RSI.周期; @@ -120,11 +160,7 @@ impl 相对强弱指数 { // RSI let RSI = if 平均下跌 == 0.0 { - if 平均上涨 > 0.0 { - 100.0 - } else { - 50.0 - } + if 平均上涨 > 0.0 { 100.0 } else { 50.0 } } else { let RS = 平均上涨 / 平均下跌; 100.0 - (100.0 / (1.0 + RS)) diff --git a/chanlun/src/kline/bar.rs b/chanlun/src/kline/bar.rs index 4bb12ce..136b8f7 100644 --- a/chanlun/src/kline/bar.rs +++ b/chanlun/src/kline/bar.rs @@ -22,30 +22,68 @@ * SOFTWARE. */ -use crate::indicators::{平滑异同移动平均线, 相对强弱指数, 随机指标}; +use crate::indicators::指标容器; use crate::types::相对方向; use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt}; use serde::{Deserialize, Serialize}; use std::collections::HashMap; use std::io::Write; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; -/// 原始K线 (OHLCV + 指标) -#[derive(Debug, Clone, Serialize, Deserialize)] +mod rwlock_container_serde { + use serde::{Deserialize, Deserializer, Serialize, Serializer}; + use std::sync::RwLock; + + /// Serde 序列化辅助(RwLock<指标容器> → 序列化器) + pub fn serialize( + val: &RwLock, + ser: S, + ) -> Result + where + S: Serializer, + { + val.read().unwrap().serialize(ser) + } + + /// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>) + pub fn deserialize<'de, D>(de: D) -> Result, D::Error> + where + D: Deserializer<'de>, + { + Ok(RwLock::new(crate::indicators::指标容器::deserialize( + de, + )?)) + } +} + +/// 原始K线 (OHLCV + 指标容器) +/// +/// 所有指标统一通过 `指标容器` 访问。指标容器使用 RwLock 实现内部可变性, +/// 使 `计算并挂载` 能以 `&K线` 共享引用写入指标值。 +#[derive(Debug, Serialize, Deserialize)] #[serde(default)] pub struct K线 { + /// 品种标识(如 "btcusd") pub 标识: String, + /// K线序号(在序列中的位置) pub 序号: i64, + /// 周期(秒),如 300=5分钟, 86400=日线 pub 周期: i64, + /// Unix 时间戳(秒) pub 时间戳: i64, + /// 最高价 pub 高: f64, + /// 最低价 pub 低: f64, + /// 开盘价 pub 开盘价: f64, + /// 收盘价 pub 收盘价: f64, + /// 成交量 pub 成交量: f64, - pub macd: Option<平滑异同移动平均线>, - pub rsi: Option<相对强弱指数>, - pub kdj: Option<随机指标>, + /// 指标容器(MACD/RSI/KDJ/BOLL/均线等) + #[serde(with = "rwlock_container_serde")] + pub 指标: RwLock<指标容器>, } impl Default for K线 { @@ -60,9 +98,24 @@ impl Default for K线 { 开盘价: 0.0, 收盘价: 0.0, 成交量: 0.0, - macd: None, - rsi: None, - kdj: None, + 指标: RwLock::new(指标容器::new()), + } + } +} + +impl Clone for K线 { + fn clone(&self) -> Self { + Self { + 标识: self.标识.clone(), + 序号: self.序号, + 周期: self.周期, + 时间戳: self.时间戳, + 高: self.高, + 低: self.低, + 开盘价: self.开盘价, + 收盘价: self.收盘价, + 成交量: self.成交量, + 指标: RwLock::new(self.指标.read().unwrap().clone()), } } } @@ -79,6 +132,7 @@ impl K线 { /// 序列化为大端字节序 48 字节 /// 格式: >6d (时间戳, 开盘价, 高, 低, 收盘价, 成交量) + /// TODO: 对齐 Python round(x, 8) 再序列化 pub fn to_bytes(&self) -> [u8; 48] { let mut buf = [0u8; 48]; { @@ -163,9 +217,7 @@ impl K线 { 开盘价, 收盘价, 成交量, - macd: None, - rsi: None, - kdj: None, + 指标: RwLock::new(指标容器::new()), } } @@ -193,7 +245,7 @@ impl K线 { let mut 阳 = 0.0f64; let mut 阴 = 0.0f64; for k in 基序 { - if let Some(ref macd) = k.macd { + if let Some(macd) = k.指标.read().unwrap().macd() { let hist = macd.MACD柱; if hist >= 0.0 { 阳 += hist; diff --git a/chanlun/src/kline/chan_kline.rs b/chanlun/src/kline/chan_kline.rs index 33d6259..be76d85 100644 --- a/chanlun/src/kline/chan_kline.rs +++ b/chanlun/src/kline/chan_kline.rs @@ -23,36 +23,48 @@ */ use crate::config::缠论配置; -use crate::indicators::{ - 平滑异同移动平均线, 相对强弱指数, 随机指标, K线取值 -}; +use crate::indicators::指标计算器; use crate::kline::bar::K线; use crate::structure::fractal_obj::分型; +use crate::types::SyncF64; use crate::types::分型结构; use crate::types::相对方向; -use crate::types::SyncF64; +use std::collections::HashSet; use std::sync::atomic::{AtomicI64, Ordering}; use std::sync::{Arc, RwLock}; /// 缠论K线 — 经包含处理过后的K线 /// -/// 部分字段使用 Cell/RefCell 实现内部可变性,确保包含处理原地修改时 -/// Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。 +/// 部分字段使用 AtomicI64 / SyncF64 / RwLock 实现内部可变性,确保包含处理 +/// 原地修改时 Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。 #[derive(Debug)] pub struct 缠论K线 { + /// 缠K序号(在缠论K线序列中的位置) pub 序号: AtomicI64, + /// Unix 时间戳(秒) pub 时间戳: AtomicI64, + /// 缠K最高价(经包含处理后可能高于原始K线) pub 高: SyncF64, + /// 缠K最低价(经包含处理后可能低于原始K线) pub 低: SyncF64, + /// 缠K方向(向上/向下) pub 方向: RwLock<相对方向>, + /// 分型结构(顶/底/上/下/散) pub 分型: RwLock>, + /// 周期(秒) pub 周期: i64, + /// 品种标识 pub 标识: String, + /// 分型特征值(历史高低点极值,用于背驰判断) pub 分型特征值: SyncF64, + /// 原始K线起始序号(包含处理前) pub 原始起始序号: i64, + /// 原始K线结束序号(包含处理后更新) pub 原始结束序号: AtomicI64, + /// 标的原始K线(该缠K对应的普K) pub 标的K线: RwLock>, - pub 买卖点信息: RwLock>, + /// 买卖点信息集合 + pub 买卖点信息: RwLock>, } impl Clone for 缠论K线 { @@ -66,7 +78,7 @@ impl Clone for 缠论K线 { 分型: RwLock::new(*self.分型.read().unwrap()), 周期: self.周期, 标识: self.标识.clone(), - 分型特征值: SyncF64::new(self.分型特征值.get()), + 分型特征值: SyncF64::new(self.高.get()), 原始起始序号: self.原始起始序号, 原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)), 标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())), @@ -108,7 +120,7 @@ impl 缠论K线 { 分型: RwLock::new(*self.分型.read().unwrap()), 周期: self.周期, 标识: self.标识.clone(), - 分型特征值: SyncF64::new(self.分型特征值.get()), + 分型特征值: SyncF64::new(self.高.get()), 原始起始序号: self.原始起始序号, 原始结束序号: AtomicI64::new(self.原始结束序号.load(Ordering::Relaxed)), 标的K线: RwLock::new(Arc::clone(&self.标的K线.read().unwrap())), @@ -118,16 +130,18 @@ impl 缠论K线 { /// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0 pub fn 与MACD柱子匹配(&self) -> bool { + let 标 = self.标的K线.read().unwrap(); + let 容器 = 标.指标.read().unwrap(); match *self.分型.read().unwrap() { Some(分型结构::底) | Some(分型结构::下) => { - if let Some(ref macd) = self.标的K线.read().unwrap().macd { + if let Some(macd) = 容器.macd() { macd.MACD柱 < 0.0 } else { false } } Some(分型结构::顶) | Some(分型结构::上) => { - if let Some(ref macd) = self.标的K线.read().unwrap().macd { + if let Some(macd) = 容器.macd() { macd.MACD柱 > 0.0 } else { false @@ -139,9 +153,11 @@ impl 缠论K线 { /// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA pub fn 与RSI匹配(&self) -> bool { + let 标 = self.标的K线.read().unwrap(); + let 容器 = 标.指标.read().unwrap(); match *self.分型.read().unwrap() { Some(分型结构::底) | Some(分型结构::下) => { - if let Some(ref rsi) = self.标的K线.read().unwrap().rsi { + if let Some(rsi) = 容器.rsi() { match (rsi.RSI, rsi.RSI_SMA) { (Some(r), Some(sma)) => r < sma, _ => false, @@ -151,7 +167,7 @@ impl 缠论K线 { } } Some(分型结构::顶) | Some(分型结构::上) => { - if let Some(ref rsi) = self.标的K线.read().unwrap().rsi { + if let Some(rsi) = 容器.rsi() { match (rsi.RSI, rsi.RSI_SMA) { (Some(r), Some(sma)) => r > sma, _ => false, @@ -166,9 +182,11 @@ impl 缠论K线 { /// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后) pub fn 与KDJ匹配(&self) -> bool { + let 标 = self.标的K线.read().unwrap(); + let 容器 = 标.指标.read().unwrap(); match *self.分型.read().unwrap() { Some(分型结构::底) | Some(分型结构::下) => { - if let Some(ref kdj) = self.标的K线.read().unwrap().kdj { + if let Some(kdj) = 容器.kdj() { match (kdj.K, kdj.D) { (Some(k), Some(d)) => k < d, _ => false, @@ -178,7 +196,7 @@ impl 缠论K线 { } } Some(分型结构::顶) | Some(分型结构::上) => { - if let Some(ref kdj) = self.标的K线.read().unwrap().kdj { + if let Some(kdj) = 容器.kdj() { match (kdj.K, kdj.D) { (Some(k), Some(d)) => k > d, _ => false, @@ -248,7 +266,7 @@ impl 缠论K线 { 原始起始序号: 原始序号, 原始结束序号: AtomicI64::new(原始序号), 标的K线: RwLock::new(普k), - 买卖点信息: RwLock::new(Vec::new()), + 买卖点信息: RwLock::new(HashSet::new()), }; if let Some(之前) = 之前 { @@ -270,7 +288,7 @@ impl 缠论K线 { /// 兼并(合并)处理 — 缠论包含处理的核心算法 /// /// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None - pub fn 兼并( + pub fn _兼并( 之前缠K: Option<&缠论K线>, 当前缠K: &缠论K线, 当前普K: &Arc, @@ -303,7 +321,7 @@ impl 缠论K线 { // 重复提交检测 — 当序号相同时认为是重复提交K线 if 当前普K.序号 == 当前缠K.原始结束序号.load(Ordering::Relaxed) { - return (None, None); + // no-op: 对齐 Python ... (Ellipsis) } // 序号连续性检查 @@ -370,91 +388,14 @@ impl 缠论K线 { 当前K线.标识 = 配置.标识.clone(); // ---- 阶段1: 普K序列管理 + 指标增量计算 ---- + // 对齐 Python: 先推入序列,再计算指标 if 普K序列.is_empty() { - if 配置.计算指标 { - 当前K线.macd = Some(平滑异同移动平均线::首次计算( - K线取值( - 当前K线.开盘价, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - &配置.指标计算方式, - ), - 当前K线.时间戳, - 配置.平滑异同移动平均线_快线周期, - 配置.平滑异同移动平均线_慢线周期, - 配置.平滑异同移动平均线_信号周期, - )); - 当前K线.rsi = Some(相对强弱指数::首次计算( - K线取值( - 当前K线.开盘价, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - &配置.指标计算方式, - ), - 当前K线.时间戳, - 配置.相对强弱指数_周期, - 配置.相对强弱指数_超买阈值, - 配置.相对强弱指数_超卖阈值, - Some(配置.相对强弱指数_移动平均线周期), - )); - 当前K线.kdj = Some(随机指标::首次计算( - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - 当前K线.时间戳, - 配置.随机指标_RSV周期, - 配置.随机指标_K值平滑周期, - 配置.随机指标_D值平滑周期, - 配置.随机指标_超买阈值, - 配置.随机指标_超卖阈值, - )); - } - let 当前K线_rc = Arc::new(当前K线); - 普K序列.push(当前K线_rc); + 普K序列.push(Arc::new(当前K线)); } else { let 之前普K = 普K序列.last().unwrap(); if 之前普K.时间戳 == 当前K线.时间戳 { - // 同时间戳更新 + // 同时间戳更新 — 替换 [-1] 当前K线.序号 = 之前普K.序号; - if 配置.计算指标 && 普K序列.len() >= 2 { - if let Some(ref prev_macd) = 普K序列[普K序列.len() - 2].macd { - 当前K线.macd = Some(平滑异同移动平均线::增量计算( - prev_macd, - K线取值( - 当前K线.开盘价, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - &配置.指标计算方式, - ), - 当前K线.时间戳, - )); - } - if let Some(ref prev_rsi) = 普K序列[普K序列.len() - 2].rsi { - 当前K线.rsi = Some(相对强弱指数::增量计算( - prev_rsi, - K线取值( - 当前K线.开盘价, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - &配置.指标计算方式, - ), - 当前K线.时间戳, - )); - } - if let Some(ref prev_kdj) = 普K序列[普K序列.len() - 2].kdj { - 当前K线.kdj = Some(随机指标::增量计算( - prev_kdj, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - 当前K线.时间戳, - )); - } - } 普K序列.pop(); 普K序列.push(Arc::new(当前K线)); } else { @@ -462,46 +403,14 @@ impl 缠论K线 { panic!("时序错误: 之前={}, 当前={}", 之前普K.时间戳, 当前K线.时间戳); } 当前K线.序号 = 之前普K.序号 + 1; - if 配置.计算指标 { - if let Some(ref prev_macd) = 之前普K.macd { - 当前K线.macd = Some(平滑异同移动平均线::增量计算( - prev_macd, - K线取值( - 当前K线.开盘价, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - &配置.指标计算方式, - ), - 当前K线.时间戳, - )); - } - if let Some(ref prev_rsi) = 之前普K.rsi { - 当前K线.rsi = Some(相对强弱指数::增量计算( - prev_rsi, - K线取值( - 当前K线.开盘价, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - &配置.指标计算方式, - ), - 当前K线.时间戳, - )); - } - if let Some(ref prev_kdj) = 之前普K.kdj { - 当前K线.kdj = Some(随机指标::增量计算( - prev_kdj, - 当前K线.高, - 当前K线.低, - 当前K线.收盘价, - 当前K线.时间戳, - )); - } - } 普K序列.push(Arc::new(当前K线)); } } + // 计算指标: 对齐 Python,仅当 计算指标 开启时执行 + if 配置.计算指标 { + let n = 普K序列.len(); + 指标计算器::计算并挂载(&普K序列[n - 1], &普K序列[..n - 1], 配置); + } // ---- 阶段2: 缠K合并 ---- let 状态: String; @@ -512,7 +421,7 @@ impl 缠论K线 { let (左边, 右边) = 缠K序列.split_at_mut(len - 1); let 之前缠K: Option<&缠论K线> = 左边.last().map(Arc::as_ref); let 最后一个缠K = &*右边[0]; - let (新缠K, 模式) = Self::兼并(之前缠K, 最后一个缠K, 当前K线_ref, 配置); + let (新缠K, 模式) = Self::_兼并(之前缠K, 最后一个缠K, 当前K线_ref, 配置); if let Some(k) = 新缠K { match 模式.as_deref() { @@ -521,8 +430,7 @@ impl 缠论K线 { 状态 = "创建".into(); } Some("替换") => { - // Cell::set 已原地更新数据,无需 pop+push 打破 Rc 身份 - 状态 = "兼并".into(); + 状态 = "替换".into(); } _ => { 状态 = "兼并".into(); diff --git a/chanlun/src/main.rs b/chanlun/src/main.rs index 1e208e7..9d5d477 100644 --- a/chanlun/src/main.rs +++ b/chanlun/src/main.rs @@ -174,5 +174,5 @@ fn 测试_周期合成(文件路径: &str) { } println!("\n===== 保存分析数据 =====\n"); - 多级别分析.测试_保存数据(); + 多级别分析.测试_保存数据(None); } diff --git a/chanlun/src/structure/dash_line.rs b/chanlun/src/structure/dash_line.rs index 25b5e5d..689070c 100644 --- a/chanlun/src/structure/dash_line.rs +++ b/chanlun/src/structure/dash_line.rs @@ -29,43 +29,97 @@ use crate::kline::chan_kline::缠论K线; use crate::structure::fractal_obj::分型; use crate::structure::segment_feat::线段特征; use crate::types::{分型结构, 相对方向, 缺口}; +use cached::stores::LruCache; use std::sync::atomic::{AtomicBool, AtomicI64, Ordering}; -use std::sync::{Arc, RwLock}; +use std::sync::{Arc, LazyLock, Mutex, RwLock}; +use tracing::warn; -/// 虚线 — 笔和线段的通用数据结构 +/// 虚线 — 笔和线段的通用数据结构。 /// -/// 笔和线段共享此 struct,通过 `标识` 字段区分 ("笔"/"线段"/"扩展线段"等) -/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致 +/// 笔和线段共享此 struct,通过 `标识` 字段区分("笔"/"线段"/"扩展线段"等)。 +/// 文=起点分型,武=终点分型,方向由文→武的结构关系判定(顶→底=向下,底→顶=向上)。 +/// +/// 字段: +/// 标识: "笔" / "线段" / "扩展线段" / "线段<线段>" 等 +/// 序号: 在同级序列中的编号 +/// 级别: 层级深度(笔=1, 线段=2, 线段<线段>=3 ...) +/// 文: 起点分型(不可变,确保指针身份一致) +/// 武: 终点分型(可变,用于动态更新终点) +/// 有效性: 该虚线是否有效 +/// 基础序列: 构成该虚线的子级虚线序列(笔→空;线段→笔序列;线段<线段>→线段序列 ...) +/// 特征序列: 线段特征序列(笔为空;线段为[None, None, None]初始化) +/// 实_中枢序列: 实中枢列表(根据配置识别的中枢) +/// 虚_中枢序列: 虚中枢列表(忽略配置约束的中枢) +/// 合_中枢序列: 合并中枢列表(实+虚+扩展) +/// 确认K线: 线段被破坏时的确认K线 +/// 模式: 买卖点匹配模式("文武"/"全量"/"任意"/"配置"/"相对") +/// _特征序列_显示: 是否显示特征序列(图表渲染用) +/// 前一缺口: 特征序列第一二元素间的缺口 +/// 前一结束位置: 前一虚线段结束时所在的位置 +/// 短路修正: 是否启用短路修正(线段短路过时的紧急处理标记) #[derive(Debug)] pub struct 虚线 { + /// 标识字符串: "笔" / "线段" / "扩展线段" / "线段<线段>" 等 pub 标识: RwLock, + /// 同级序列中的编号 pub 序号: AtomicI64, + /// 层级深度(笔=1, 线段=2) pub 级别: AtomicI64, + /// 起点分型(不可变) pub 文: Arc<分型>, + /// 终点分型(可变) pub 武: RwLock>, + /// 是否有效 pub 有效性: AtomicBool, + /// 构成该虚线的子级虚线序列 pub 基础序列: RwLock>>, + /// 线段特征序列 pub 特征序列: RwLock>>>, + /// 实中枢列表 pub 实_中枢序列: RwLock>>, + /// 虚中枢列表 pub 虚_中枢序列: RwLock>>, + /// 合并中枢列表(实+虚+扩展) pub 合_中枢序列: RwLock>>, + /// 线段被破坏时的确认K线 pub 确认K线: RwLock>>, + /// 买卖点匹配模式 pub 模式: RwLock, + /// 是否显示特征序列(图表渲染) pub _特征序列_显示: AtomicBool, + /// 特征序列第一二元素间的缺口 pub 前一缺口: RwLock>, + /// 前一虚线段结束位置 pub 前一结束位置: RwLock>>, + /// 短路紧急修正标记 pub 短路修正: AtomicBool, } -/// MACD行为统计 — 统计MACD行为 方法的返回类型 +/// MACD行为统计 — `统计MACD行为` 方法的返回类型,汇总MACD穿轴和交叉信息。 +/// +/// 字段: +/// DIF上穿0: DIF从负穿正的次数 +/// DIF下穿0: DIF从正穿负的次数 +/// DEA上穿0: DEA从负穿正的次数 +/// DEA下穿0: DEA从正穿负的次数 +/// 金叉次数: DIF上穿DEA的次数 +/// 死叉次数: DIF下穿DEA的次数 +/// 密集交叉区域: (起始索引, 结束索引, 交叉次数) 列表 #[derive(Debug, Clone)] pub struct MACD行为统计 { + /// DIF从负穿正的次数 pub DIF上穿0: i64, + /// DIF从正穿负的次数 pub DIF下穿0: i64, + /// DEA从负穿正的次数 pub DEA上穿0: i64, + /// DEA从正穿负的次数 pub DEA下穿0: i64, + /// DIF上穿DEA的次数 pub 金叉次数: i64, + /// DIF下穿DEA的次数 pub 死叉次数: i64, + /// 密集交叉区域 (起始索引, 结束索引, 交叉次数) pub 密集交叉区域: Vec<(usize, usize, usize)>, } @@ -93,6 +147,12 @@ impl Clone for 虚线 { } } +type 买卖意义缓存类型 = LruCache<(usize, usize), (bool, String)>; + +/// 买卖意义 LRU 缓存(max 128 条目,对齐 Python @lru_cache(maxsize=128)) +static 买卖意义缓存: LazyLock> = + LazyLock::new(|| Mutex::new(LruCache::with_size(128))); + impl 虚线 { pub fn new( 序号: i64, @@ -110,7 +170,7 @@ impl 虚线 { 武: RwLock::new(武), 有效性: AtomicBool::new(有效性), 基础序列: RwLock::new(Vec::new()), - 特征序列: RwLock::new(Vec::new()), + 特征序列: RwLock::new(vec![None, None, None]), 实_中枢序列: RwLock::new(Vec::new()), 虚_中枢序列: RwLock::new(Vec::new()), 合_中枢序列: RwLock::new(Vec::new()), @@ -123,6 +183,7 @@ impl 虚线 { } } + /// 图表标题 — 格式为 "符号:周期:标识:序号" pub fn 图表标题(&self) -> String { format!( "{}:{}:{}:{}", @@ -133,14 +194,18 @@ impl 虚线 { ) } - /// 方向 — 文到武的方向 + /// 方向 — 文到武的方向(对齐 Python:无法识别时 panic) pub fn 方向(&self) -> 相对方向 { match (self.文.结构, self.武.read().unwrap().结构) { (分型结构::顶, 分型结构::底) => 相对方向::向下, (分型结构::顶, 分型结构::下) => 相对方向::向下, - (分型结构::上, 分型结构::底) => 相对方向::向下, - (分型结构::上, 分型结构::下) => 相对方向::向下, - _ => 相对方向::向上, + (分型结构::底, 分型结构::顶) => 相对方向::向上, + (分型结构::底, 分型结构::上) => 相对方向::向上, + _ => panic!( + "虚线 方向 无法识别: 文.结构={:?}, 武.结构={:?}", + self.文.结构, + self.武.read().unwrap().结构 + ), } } @@ -179,6 +244,7 @@ impl 虚线 { } /// 获取该虚线范围内的普K序列 + /// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价 pub fn 获取普K序列(&self, 普K序列: &[Arc]) -> Vec> { // 使用指针查找(与 Python list.index 身份匹配行为一致), // 而非序号切片——因为序号可能与实际位置不一致。 @@ -192,7 +258,7 @@ impl 虚线 { (Some(s), Some(e)) if s <= e => 普K序列[s..=e].to_vec(), _ => { // 指针查找失败时回退到序号方式 - println!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>"); + warn!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>"); let 始 = self.文.中.原始起始序号 as usize; let 终 = self .武 @@ -211,11 +277,13 @@ impl 虚线 { } /// 获取该虚线范围内的缠K序列 + /// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价 pub fn 获取缠K序列(&self, 缠K序列: &[Arc<缠论K线>]) -> Vec> { 缠论K线::截取(缠K序列, &self.文.中, &self.武.read().unwrap().中).unwrap_or_default() } /// 获取_武 — 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武) + /// 对齐说明:Python 是 @classmethod(cls, 实线),Rust 是实例方法(&self),逻辑等价 pub fn 获取_武(&self) -> Arc<分型> { if *self.标识.read().unwrap() == "笔" { return self.武.read().unwrap().clone(); @@ -254,13 +322,7 @@ impl 虚线 { // 非笔:线段/扩展线段等,完整输出 let (前, 后, 三, 贯穿伤) = crate::algorithm::segment::线段::分割序列(self, None); let (特征_a, 特征_b, 特征_c) = crate::algorithm::segment::线段::特征序列状态(self); - let 特征_bool = |b: bool| -> &str { - if b { - "True" - } else { - "False" - } - }; + let 特征_bool = |b: bool| -> &str { if b { "True" } else { "False" } }; let 前一缺口_str = match &*self.前一缺口.read().unwrap() { Some(g) => format!("{}", g), @@ -334,7 +396,11 @@ impl 虚线 { format_f64_g(self.文.分型特征值), self.武.read().unwrap().时间戳(), format_f64_g(self.武.read().unwrap().分型特征值), - if self.有效性.load(Ordering::Relaxed) { "True" } else { "False" }, + if self.有效性.load(Ordering::Relaxed) { + "True" + } else { + "False" + }, self.基础序列.read().unwrap().len(), 特征_bool(特征_a), 特征_bool(特征_b), @@ -342,14 +408,21 @@ impl 虚线 { 前_str, 后_str, 三_str, - match &贯穿伤 { Some(d) => format!("{}", d), None => "None".to_string() }, + match &贯穿伤 { + Some(d) => format!("{}", d), + None => "None".to_string(), + }, 实_str, 虚_str, 合_str, self.模式.read().unwrap(), 前一缺口_str, 前一结束位置_str, - if self.短路修正.load(Ordering::Relaxed) { "True" } else { "False" }, + if self.短路修正.load(Ordering::Relaxed) { + "True" + } else { + "False" + }, ) } @@ -362,6 +435,13 @@ impl 虚线 { /// 创建线段 pub fn 创建线段(虚线序列: &[Arc<虚线>]) -> Self { + let 序列数量 = 虚线序列.len(); + if 序列数量 < 3 { + panic!("创建线段 虚线序列 数量 {} < 3", 序列数量); + } + if 序列数量.is_multiple_of(2) { + panic!("创建线段 虚线序列 数量 {} 不是单数!", 序列数量); + } let 文 = Arc::clone(&虚线序列[0].文); let 武 = Arc::clone(&*虚线序列[虚线序列.len() - 1].武.read().unwrap()); assert!( @@ -377,6 +457,10 @@ impl 虚线 { }; let 级别 = 虚线序列[0].级别.load(Ordering::Relaxed) + 1; let 段 = Self::new(0, 标识, 文, 武, 级别, true); + *段.特征序列.write().unwrap() = vec![None, None, None]; + *段.实_中枢序列.write().unwrap() = Vec::new(); + *段.虚_中枢序列.write().unwrap() = Vec::new(); + *段.合_中枢序列.write().unwrap() = Vec::new(); *段.基础序列.write().unwrap() = 虚线序列.to_vec(); *段.模式.write().unwrap() = "文武".into(); 段 @@ -443,13 +527,14 @@ impl 虚线 { } let 总: f64 = K线序列 .iter() - .filter_map(|k| k.macd.as_ref()) + .filter_map(|k| k.指标.read().unwrap().macd_cloned()) .map(|m| m.MACD柱.abs()) .sum(); 总 / K线序列.len() as f64 } /// 计算MACD柱子均值_阴 — 负柱的绝对值均值 + /// 对齐说明:Python 无数据时返回 False(bool),Rust 返回 None,调用方处理等价 pub fn 计算MACD柱子均值_阴(普K序列: &[Arc], 实线: &虚线) -> Option { let K线序列 = K线::截取rc( 普K序列, @@ -458,7 +543,7 @@ impl 虚线 { ); let 总: Vec = K线序列 .iter() - .filter_map(|k| k.macd.as_ref()) + .filter_map(|k| k.指标.read().unwrap().macd_cloned()) .filter(|m| m.MACD柱 < 0.0) .map(|m| m.MACD柱.abs()) .collect(); @@ -470,6 +555,7 @@ impl 虚线 { } /// 计算MACD柱子均值_阳 — 正柱的绝对值均值 + /// 对齐说明:Python 无数据时返回 False(bool),Rust 返回 None,调用方处理等价 pub fn 计算MACD柱子均值_阳(普K序列: &[Arc], 实线: &虚线) -> Option { let K线序列 = K线::截取rc( 普K序列, @@ -478,7 +564,7 @@ impl 虚线 { ); let 总: Vec = K线序列 .iter() - .filter_map(|k| k.macd.as_ref()) + .filter_map(|k| k.指标.read().unwrap().macd_cloned()) .filter(|m| m.MACD柱 > 0.0) .map(|m| m.MACD柱.abs()) .collect(); @@ -495,7 +581,7 @@ impl 虚线 { pub fn 武之全量MACD均值(普K序列: &[Arc], 实线: &虚线) -> bool { let 武_ref = 实线.武.read().unwrap(); let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.macd.as_ref() { + let 武_MACD = match 标.指标.read().unwrap().macd() { Some(m) => m.MACD柱.abs(), None => return false, }; @@ -515,7 +601,7 @@ impl 虚线 { pub fn 武之MACD均值_阴(普K序列: &[Arc], 实线: &虚线) -> bool { let 武_ref = 实线.武.read().unwrap(); let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.macd.as_ref() { + let 武_MACD = match 标.指标.read().unwrap().macd() { Some(m) => m.MACD柱.abs(), None => return false, }; @@ -529,7 +615,7 @@ impl 虚线 { pub fn 武之MACD均值_阳(普K序列: &[Arc], 实线: &虚线) -> bool { let 武_ref = 实线.武.read().unwrap(); let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.macd.as_ref() { + let 武_MACD = match 标.指标.read().unwrap().macd() { Some(m) => m.MACD柱.abs(), None => return false, }; @@ -543,7 +629,7 @@ impl 虚线 { pub fn 武之MACD极值(普K序列: &[Arc], 实线: &虚线) -> bool { let 武_ref = 实线.武.read().unwrap(); let 标 = 武_ref.中.标的K线.read().unwrap(); - let 武_MACD = match 标.macd.as_ref() { + let 武_MACD = match 标.指标.read().unwrap().macd() { Some(m) => m.MACD柱, None => return false, }; @@ -554,7 +640,7 @@ impl 虚线 { ); let 所有柱子: Vec = K线序列 .iter() - .filter_map(|k| k.macd.as_ref()) + .filter_map(|k| k.指标.read().unwrap().macd_cloned()) .map(|m| m.MACD柱) .collect(); if 所有柱子.is_empty() { @@ -583,7 +669,13 @@ impl 虚线 { if 方向 == 相对方向::向上 { let 柱子序列: Vec<&Arc> = 普K序列 .iter() - .filter(|k| k.macd.as_ref().is_some_and(|m| m.MACD柱 > 0.0)) + .filter(|k| { + k.指标 + .read() + .unwrap() + .macd() + .is_some_and(|m| m.MACD柱 > 0.0) + }) .collect(); if 柱子序列.is_empty() { return [false, false, false]; @@ -595,32 +687,51 @@ impl 虚线 { let 最高柱子 = 柱子序列 .iter() .max_by(|a, b| { - a.macd - .as_ref() + a.指标 + .read() + .unwrap() + .macd() .unwrap() .MACD柱 - .partial_cmp(&b.macd.as_ref().unwrap().MACD柱) + .partial_cmp(&b.指标.read().unwrap().macd().unwrap().MACD柱) .unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); let mut 柱对 = [Arc::clone(*最高柱子), Arc::clone(最后)]; 柱对.sort_by_key(|k| k.时间戳); - if let (Some(m0), Some(m1)) = (柱对[0].macd.as_ref(), 柱对[1].macd.as_ref()) { - if m0.MACD柱 > m1.MACD柱 && 柱对[0].高 < 柱对[1].高 { - 结果[0] = true; - } + let m0_g = 柱对[0].指标.read().unwrap(); + let m1_g = 柱对[1].指标.read().unwrap(); + if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) + && m0.MACD柱 > m1.MACD柱 + && 柱对[0].高 < 柱对[1].高 + { + 结果[0] = true; } // DIF背驰 (no sort — Python compares peak vs last directly) let 最高离差值 = 柱子序列 .iter() .max_by(|a, b| { - let da = a.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0); - let db = b.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0); + let da = a + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0); + let db = b + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0); da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - if let (Some(m0), Some(m1)) = (最高离差值.macd.as_ref(), 最后.macd.as_ref()) { + let m0_g = 最高离差值.指标.read().unwrap(); + let m1_g = 最后.指标.read().unwrap(); + if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dif0 = m0.DIF.unwrap_or(0.0); let dif1 = m1.DIF.unwrap_or(0.0); if dif0 > dif1 && 最高离差值.高 < 最后.高 { @@ -632,12 +743,26 @@ impl 虚线 { let 最高信号线 = 柱子序列 .iter() .max_by(|a, b| { - let da = a.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0); - let db = b.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0); + let da = a + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0); + let db = b + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0); da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - if let (Some(m0), Some(m1)) = (最高信号线.macd.as_ref(), 最后.macd.as_ref()) { + let m0_g = 最高信号线.指标.read().unwrap(); + let m1_g = 最后.指标.read().unwrap(); + if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dea0 = m0.DEA.unwrap_or(0.0); let dea1 = m1.DEA.unwrap_or(0.0); if dea0 > dea1 && 最高信号线.高 < 最后.高 { @@ -649,7 +774,13 @@ impl 虚线 { } else { let 柱子序列: Vec<&Arc> = 普K序列 .iter() - .filter(|k| k.macd.as_ref().is_some_and(|m| m.MACD柱 < 0.0)) + .filter(|k| { + k.指标 + .read() + .unwrap() + .macd() + .is_some_and(|m| m.MACD柱 < 0.0) + }) .collect(); if 柱子序列.is_empty() { return [false, false, false]; @@ -661,33 +792,54 @@ impl 虚线 { let 最高柱子 = 柱子序列 .iter() .max_by(|a, b| { - a.macd - .as_ref() + a.指标 + .read() + .unwrap() + .macd() .unwrap() .MACD柱 .abs() - .partial_cmp(&b.macd.as_ref().unwrap().MACD柱.abs()) + .partial_cmp(&b.指标.read().unwrap().macd().unwrap().MACD柱.abs()) .unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); let mut 柱对 = [Arc::clone(*最高柱子), Arc::clone(最后)]; 柱对.sort_by_key(|k| k.时间戳); - if let (Some(m0), Some(m1)) = (柱对[0].macd.as_ref(), 柱对[1].macd.as_ref()) { - if m0.MACD柱 < m1.MACD柱 && 柱对[0].低 > 柱对[1].低 { - 结果[0] = true; - } + let m0_g = 柱对[0].指标.read().unwrap(); + let m1_g = 柱对[1].指标.read().unwrap(); + if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) + && m0.MACD柱 < m1.MACD柱 + && 柱对[0].低 > 柱对[1].低 + { + 结果[0] = true; } // DIF背驰 (no sort — Python compares peak vs last directly) let 最高离差值 = 柱子序列 .iter() .max_by(|a, b| { - let da = a.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0).abs(); - let db = b.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0).abs(); + let da = a + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0) + .abs(); + let db = b + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DIF) + .unwrap_or(0.0) + .abs(); da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - if let (Some(m0), Some(m1)) = (最高离差值.macd.as_ref(), 最后.macd.as_ref()) { + let m0_g = 最高离差值.指标.read().unwrap(); + let m1_g = 最后.指标.read().unwrap(); + if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dif0 = m0.DIF.unwrap_or(0.0); let dif1 = m1.DIF.unwrap_or(0.0); if dif0 < dif1 && 最高离差值.低 > 最后.低 { @@ -699,12 +851,28 @@ impl 虚线 { let 最高信号线 = 柱子序列 .iter() .max_by(|a, b| { - let da = a.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0).abs(); - let db = b.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0).abs(); + let da = a + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0) + .abs(); + let db = b + .指标 + .read() + .unwrap() + .macd() + .and_then(|m| m.DEA) + .unwrap_or(0.0) + .abs(); da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal) }) .unwrap(); - if let (Some(m0), Some(m1)) = (最高信号线.macd.as_ref(), 最后.macd.as_ref()) { + let m0_g = 最高信号线.指标.read().unwrap(); + let m1_g = 最后.指标.read().unwrap(); + if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) { let dea0 = m0.DEA.unwrap_or(0.0); let dea1 = m1.DEA.unwrap_or(0.0); if dea0 < dea1 && 最高信号线.低 > 最后.低 { @@ -719,20 +887,15 @@ impl 虚线 { // ---- MACD柱子分段 ---- /// 计算MACD柱子分段 — 按正负号将MACD柱子分段 + /// 对齐说明:Python 丢弃末尾分段的末段残留(疑似 bug),Rust 通过 if !当前段.is_empty() 正确追加 pub fn 计算MACD柱子分段(k线序列: &[Arc]) -> Vec> { if k线序列.is_empty() { return Vec::new(); } - let 符号 = |x: f64| -> &str { - if x > 0.0 { - "正" - } else { - "负" - } - }; + let 符号 = |x: f64| -> &str { if x > 0.0 { "正" } else { "负" } }; - let 首_MACD = match &k线序列[0].macd { + let 首_MACD = match k线序列[0].指标.read().unwrap().macd() { Some(m) => m.MACD柱, None => return Vec::new(), }; @@ -741,7 +904,7 @@ impl 虚线 { let mut 结果 = Vec::new(); for k线 in &k线序列[1..] { - let macd = match &k线.macd { + let macd = match k线.指标.read().unwrap().macd() { Some(m) => m.MACD柱, None => continue, }; @@ -812,13 +975,15 @@ impl 虚线 { let mut dea_down = 0; for i in 1..普K序列.len() { - let pre = &普K序列[i - 1].macd; - let cur = &普K序列[i].macd; + let pre_guard = 普K序列[i - 1].指标.read().unwrap(); + let cur_guard = 普K序列[i].指标.read().unwrap(); + let pre = pre_guard.macd(); + let cur = cur_guard.macd(); if pre.is_none() || cur.is_none() { continue; } - let (pre_dif, cur_dif) = (pre.as_ref().unwrap().DIF, cur.as_ref().unwrap().DIF); - let (pre_dea, cur_dea) = (pre.as_ref().unwrap().DEA, cur.as_ref().unwrap().DEA); + let (pre_dif, cur_dif) = (pre.unwrap().DIF, cur.unwrap().DIF); + let (pre_dea, cur_dea) = (pre.unwrap().DEA, cur.unwrap().DEA); if let (Some(pd), Some(cd)) = (pre_dif, cur_dif) { if pd < 0.0 && cd >= 0.0 { @@ -843,16 +1008,18 @@ impl 虚线 { let mut 交叉标记 = vec![0i32]; for i in 1..普K序列.len() { - let pre = &普K序列[i - 1].macd; - let cur = &普K序列[i].macd; + let pre_guard = 普K序列[i - 1].指标.read().unwrap(); + let cur_guard = 普K序列[i].指标.read().unwrap(); + let pre = pre_guard.macd(); + let cur = cur_guard.macd(); if pre.is_none() || cur.is_none() { 交叉标记.push(0); continue; } - let pre_dif = pre.as_ref().unwrap().DIF; - let pre_dea = pre.as_ref().unwrap().DEA; - let cur_dif = cur.as_ref().unwrap().DIF; - let cur_dea = cur.as_ref().unwrap().DEA; + let pre_dif = pre.unwrap().DIF; + let pre_dea = pre.unwrap().DEA; + let cur_dif = cur.unwrap().DIF; + let cur_dea = cur.unwrap().DEA; if let (Some(pd), Some(cd), Some(pe), Some(ce)) = (pre_dif, cur_dif, pre_dea, cur_dea) { if pd <= pe && cd > ce { @@ -889,13 +1056,38 @@ impl 虚线 { /// 返回 (是否有意义, 原因字符串) pub fn 买卖意义( 实线: &虚线, 观察员: &crate::business::observer::观察者 + ) -> (bool, String) { + // LRU 缓存查找 + let key = ( + std::ptr::from_ref(实线) as usize, + std::ptr::from_ref(观察员) as usize, + ); + { + let mut cache = 买卖意义缓存.lock().unwrap(); + if let Some(val) = cached::Cached::cache_get(&mut *cache, &key) { + return val.clone(); + } + } + + let result = Self::_买卖意义_计算(实线, 观察员); + { + let mut cache = 买卖意义缓存.lock().unwrap(); + cached::Cached::cache_set(&mut *cache, key, result.clone()); + } + result + } + + /// 买卖意义 实际计算(无缓存) + fn _买卖意义_计算( + 实线: &虚线, + 观察员: &crate::business::observer::观察者, ) -> (bool, String) { let 普K序列 = &观察员.普通K线序列; let 配置 = &观察员.配置; if *实线.标识.read().unwrap() != "笔" && *实线.标识.read().unwrap() != "线段" - && !实线.标识.read().unwrap().starts_with("线段<") + && *实线.标识.read().unwrap() != "线段<线段>" { return (false, "标识不在范围内".into()); } @@ -903,7 +1095,7 @@ impl 虚线 { // KDJ指标完整性检查 let 武_ref = 实线.武.read().unwrap(); let 标 = 武_ref.中.标的K线.read().unwrap(); - match 标.kdj.as_ref() { + match 标.指标.read().unwrap().kdj() { Some(kdj) if kdj.K.is_some() && kdj.D.is_some() && kdj.J.is_some() => {} _ => return (false, "KDJ指标不完整".into()), } diff --git a/chanlun/src/structure/feat_fractal.rs b/chanlun/src/structure/feat_fractal.rs index f25949f..3a8419c 100644 --- a/chanlun/src/structure/feat_fractal.rs +++ b/chanlun/src/structure/feat_fractal.rs @@ -26,12 +26,22 @@ use crate::structure::segment_feat::线段特征; use crate::types::分型结构; use std::sync::Arc; -/// 特征分型 — 由三个线段特征元素构成的分型 +/// 特征分型 — 由三个线段特征元素构成的分型,用于线段特征序列的分型分析。 +/// +/// 字段: +/// 左: 左侧线段特征元素 +/// 中: 中间线段特征元素 +/// 右: 右侧线段特征元素 +/// 结构: 由左中右三元素分析得出的分型结构(顶/底/散) #[derive(Debug, Clone)] pub struct 特征分型 { + /// 左侧线段特征元素 pub 左: Arc<线段特征>, + /// 中间线段特征元素 pub 中: Arc<线段特征>, + /// 右侧线段特征元素 pub 右: Arc<线段特征>, + /// 由左中右分析得出的分型结构 pub 结构: 分型结构, } diff --git a/chanlun/src/structure/fractal_obj.rs b/chanlun/src/structure/fractal_obj.rs index b5ecdd1..c97c954 100644 --- a/chanlun/src/structure/fractal_obj.rs +++ b/chanlun/src/structure/fractal_obj.rs @@ -25,21 +25,39 @@ use crate::kline::chan_kline::缠论K线; use crate::types::分型结构; use crate::types::相对方向; +use std::sync::Arc; use std::sync::atomic::AtomicBool; use std::sync::atomic::Ordering; -use std::sync::Arc; +use tracing::warn; /// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取 pub static 分型模式: AtomicBool = AtomicBool::new(true); -/// 分型 — 由三根缠K构成(可能缺左或右) +/// 分型 — 由左中右三根缠论K线构成的顶/底分型。 +/// +/// 字段: +/// 左: 左侧缠K(可能为 None,表示序列起始处分型左翼缺失) +/// 中: 中间缠K(必须存在),分型的核心K线,其 `分型` 字段记录了该位置的分型结构 +/// 右: 右侧缠K(可能为 None,表示序列末尾处分型右翼缺失) +/// 结构: 构造时从 `中.分型` 缓存的分型结构(上/下/顶/底/散) +/// 时间戳: 构造时从 `中.时间戳` 缓存的时刻 +/// 分型特征值: 构造时从 `中.分型特征值` 缓存的值 +/// +/// "分型模式" 全局开关控制 时间戳/结构/分型特征值 的读取行为: +/// True(默认)— 返回构造时缓存值;False — 从 中 缠K 实时读取 #[derive(Debug, Clone)] pub struct 分型 { + /// 左侧缠K(序列起始处分型可能为 None) pub 左: Option>, + /// 中间缠K(必须存在) pub 中: Arc<缠论K线>, + /// 右侧缠K(序列末尾处分型可能为 None) pub 右: Option>, + /// 构造时缓存的 分型结构(上/下/顶/底/散) pub 结构: 分型结构, + /// 构造时缓存的 时间戳 pub 时间戳: i64, + /// 构造时缓存的 分型特征值(历史高低点极值) pub 分型特征值: f64, } @@ -47,6 +65,16 @@ impl 分型 { pub fn new( 左: Option>, 中: Arc<缠论K线>, 右: Option> ) -> Self { + if let (Some(左), Some(右)) = (&左, &右) { + debug_assert!( + 左.时间戳.load(Ordering::Relaxed) < 中.时间戳.load(Ordering::Relaxed) + && 中.时间戳.load(Ordering::Relaxed) < 右.时间戳.load(Ordering::Relaxed), + "分型时间戳断言失败: 左={}, 中={}, 右={}", + 左.时间戳.load(Ordering::Relaxed), + 中.时间戳.load(Ordering::Relaxed), + 右.时间戳.load(Ordering::Relaxed), + ); + } let 结构 = 中.分型.read().unwrap().unwrap_or(分型结构::散); let 时间戳 = 中.时间戳.load(Ordering::Relaxed); let 分型特征值 = 中.分型特征值.get(); @@ -69,6 +97,24 @@ impl 分型 { } } + /// 结构 — 根据 分型模式 决定返回缓存值(True)或实时值(False) + pub fn 结构(&self) -> 分型结构 { + if 分型模式.load(Ordering::Relaxed) { + self.结构 + } else { + self.中.分型.read().unwrap().unwrap_or(分型结构::散) // FIXME 错误 + } + } + + /// 分型特征值 — 根据 分型模式 决定返回缓存值(True)或实时值(False) + pub fn 分型特征值(&self) -> f64 { + if 分型模式.load(Ordering::Relaxed) { + self.分型特征值 + } else { + self.中.分型特征值.get() + } + } + /// 左中右三组关系 pub fn 关系组(&self) -> Option<(相对方向, 相对方向, 相对方向)> { let 左 = self.左.as_ref()?; @@ -82,7 +128,7 @@ impl 分型 { /// 分型强度 — 返回 "强"/"中"/"弱"/"未知" pub fn 强度(&self) -> &str { - if self.结构 != 分型结构::底 && self.结构 != 分型结构::顶 { + if self.结构() != 分型结构::底 && self.结构() != 分型结构::顶 { return "未知"; } if self.右.is_none() || self.左.is_none() { @@ -90,7 +136,7 @@ impl 分型 { } if let Some(ref 关系组) = self.关系组() { - if self.结构 == 分型结构::底 { + if self.结构() == 分型结构::底 { if 关系组.2.是否向下() { return "弱"; } else if 关系组.2.是否向上() { @@ -98,7 +144,7 @@ impl 分型 { } else { return "中"; } - } else if self.结构 == 分型结构::顶 { + } else if self.结构() == 分型结构::顶 { if 关系组.2.是否向上() { return "弱"; } else if 关系组.2.是否向下() { @@ -109,8 +155,8 @@ impl 分型 { } } - if let (Some(ref 左), Some(ref 右)) = (&self.左, &self.右) { - if self.结构 == 分型结构::底 { + if let (Some(左), Some(右)) = (&self.左, &self.右) { + if self.结构() == 分型结构::底 { if 右.标的K线.read().unwrap().收盘价 > 左.标的K线.read().unwrap().高 { return "强"; } else if 右.标的K线.read().unwrap().收盘价 > self.中.标的K线.read().unwrap().高 @@ -119,7 +165,7 @@ impl 分型 { } else { return "弱"; } - } else if self.结构 == 分型结构::顶 { + } else if self.结构() == 分型结构::顶 { if 右.标的K线.read().unwrap().收盘价 < 左.标的K线.read().unwrap().低 { return "强"; } else if 右.标的K线.read().unwrap().收盘价 < self.中.标的K线.read().unwrap().低 @@ -135,23 +181,29 @@ impl 分型 { /// MACD柱子分型匹配 — 检查左中右MACD柱形成顶/底形态 pub fn 与MACD柱子分型匹配(&self) -> bool { - if let (Some(ref 左), Some(ref 右)) = (&self.左, &self.右) { - if self.结构 == 分型结构::底 { + if let (Some(左), Some(右)) = (&self.左, &self.右) { + if self.结构() == 分型结构::底 { let 左_k = 左.标的K线.read().unwrap(); let 中_k = self.中.标的K线.read().unwrap(); let 右_k = 右.标的K线.read().unwrap(); - if let (Some(ref 左macd), Some(ref 中macd), Some(ref 右macd)) = - (&左_k.macd, &中_k.macd, &右_k.macd) + let 左_m = 左_k.指标.read().unwrap(); + let 中_m = 中_k.指标.read().unwrap(); + let 右_m = 右_k.指标.read().unwrap(); + if let (Some(左macd), Some(中macd), Some(右macd)) = + (左_m.macd(), 中_m.macd(), 右_m.macd()) { return 左macd.MACD柱 > 中macd.MACD柱 && 中macd.MACD柱 < 右macd.MACD柱; } } - if self.结构 == 分型结构::顶 { + if self.结构() == 分型结构::顶 { let 左_k = 左.标的K线.read().unwrap(); let 中_k = self.中.标的K线.read().unwrap(); let 右_k = 右.标的K线.read().unwrap(); - if let (Some(ref 左macd), Some(ref 中macd), Some(ref 右macd)) = - (&左_k.macd, &中_k.macd, &右_k.macd) + let 左_m = 左_k.指标.read().unwrap(); + let 中_m = 中_k.指标.read().unwrap(); + let 右_m = 右_k.指标.read().unwrap(); + if let (Some(左macd), Some(中macd), Some(右macd)) = + (左_m.macd(), 中_m.macd(), 右_m.macd()) { return 左macd.MACD柱 < 中macd.MACD柱 && 中macd.MACD柱 > 右macd.MACD柱; } @@ -161,11 +213,8 @@ impl 分型 { } /// 判断两个分型是否匹配 - pub fn 判断分型(左: &Arc<分型>, 右: &Arc<分型>, 模式: &str) -> bool { - match 模式 { - "中" => Arc::as_ptr(左) == Arc::as_ptr(右), - _ => false, - } + pub fn 判断分型(左: &Arc<分型>, 右: &Arc<分型>, _模式: &str) -> bool { + Arc::as_ptr(左) == Arc::as_ptr(右) } /// 从缠K序列中获取以指定缠K为中元素的分型 @@ -192,17 +241,17 @@ impl 分型 { /// 向分型序列中添加新分型 pub fn 向序列中添加(分型序列: &mut Vec>, 当前分型: Arc<分型>) { if 分型序列.is_empty() { - if 当前分型.结构 != 分型结构::顶 && 当前分型.结构 != 分型结构::底 + if 当前分型.结构() != 分型结构::顶 && 当前分型.结构() != 分型结构::底 { panic!("首次添加分型不为 顶底 {:?}", 当前分型); } } else { let 前一个 = &分型序列[分型序列.len() - 1]; - if 前一个.结构 == 当前分型.结构 { + if 前一个.结构() == 当前分型.结构() { panic!("分型相同无法添加 {:?} {:?}", 前一个, 当前分型); } if 前一个.右.is_none() { - eprintln!("分型.向序列中添加, 分型异常 {:?}", 前一个); + warn!("分型.向序列中添加, 分型异常 {:?}", 前一个); } } 分型序列.push(当前分型); @@ -223,13 +272,9 @@ impl std::fmt::Display for 分型 { write!( f, "{}<{}, {}, None: {}, None: {}>", - self.中 - .分型 - .read() - .unwrap() - .unwrap_or(crate::types::分型结构::散), + self.结构(), self.时间戳(), - crate::utils::format_f64_g(self.分型特征值), + crate::utils::format_f64_g(self.分型特征值()), if self.左.is_none() { "True" } else { "False" }, if self.右.is_none() { "True" } else { "False" }, ) diff --git a/chanlun/src/structure/segment_feat.rs b/chanlun/src/structure/segment_feat.rs index 3f53f71..14f4896 100644 --- a/chanlun/src/structure/segment_feat.rs +++ b/chanlun/src/structure/segment_feat.rs @@ -27,36 +27,68 @@ use crate::structure::feat_fractal::特征分型; use crate::structure::fractal_obj::分型; use crate::types::{分型结构, 相对方向}; use std::sync::atomic::Ordering; -use std::sync::Arc; +use std::sync::{Arc, RwLock}; -/// 线段特征 — 特征序列元素(内部是虚线的集合) -#[derive(Debug, Clone)] +/// 线段特征 — 特征序列元素,内部是虚线的集合。 +/// +/// 在线段划分算法中,将同方向的笔归入一个特征序列元素, +/// 然后通过特征序列元素之间的分型关系判定线段终结。 +/// +/// 字段: +/// 序号: 特征序列元素编号 +/// 标识: 标识字符串(如 "特征<虚线>") +/// 线段方向: 所属线段的方向(与元素包含的虚线方向相反) +/// 基础序列: 构成该特征序列元素的虚线序列 +/// +/// 计算属性: +/// 文 — 起点分型(取特征序列中分型特征值极值) +/// 武 — 终点分型(取特征序列中分型特征值极值) +/// 方向 — 线段方向的翻转 +/// 高 / 低 — 文/武 中分型特征值的较大/较小者 +#[derive(Debug)] pub struct 线段特征 { + /// 特征序列元素编号 pub 序号: i64, - pub 标识: String, + /// 标识字符串(如 "特征<虚线>") + pub 标识: RwLock, + /// 所属线段的方向 pub 线段方向: 相对方向, - pub 元素: Vec>, + /// 构成该元素的虚线序列 + pub 基础序列: Vec>, +} + +impl Clone for 线段特征 { + fn clone(&self) -> Self { + Self { + 序号: self.序号, + 标识: RwLock::new(self.标识.read().unwrap().clone()), + 线段方向: self.线段方向, + 基础序列: self.基础序列.clone(), + } + } } impl 线段特征 { + /// 新建线段特征(给定标识、基础序列和线段方向) pub fn new(标识: String, 基础序列: Vec>, 线段方向: 相对方向) -> Self { Self { 序号: 0, - 标识, + 标识: RwLock::new(标识), 线段方向, - 元素: 基础序列, + 基础序列, } } + /// 图表标题 — 返回标识字符串 pub fn 图表标题(&self) -> String { - self.标识.clone() + self.标识.read().unwrap().clone() } /// 文 — 取特征序列元素中分型特征值最大/最小的文分型 /// tiebreaker: later时间戳 wins when特征值 equal (matches Python) pub fn 文(&self) -> Arc<分型> { if self.线段方向.是否向上() { - self.元素 + self.基础序列 .iter() .max_by(|a, b| { a.文 @@ -66,19 +98,19 @@ impl 线段特征 { .then_with(|| a.文.时间戳().cmp(&b.文.时间戳())) }) .map(|x| Arc::clone(&x.文)) - .unwrap_or_else(|| Arc::clone(&self.元素[0].文)) + .unwrap_or_else(|| Arc::clone(&self.基础序列[0].文)) } else { - self.元素 + self.基础序列 .iter() - .min_by(|a, b| { - a.文 + .max_by(|a, b| { + b.文 .分型特征值 - .partial_cmp(&b.文.分型特征值) + .partial_cmp(&a.文.分型特征值) .unwrap_or(std::cmp::Ordering::Equal) - .then_with(|| b.文.时间戳().cmp(&a.文.时间戳())) + .then_with(|| a.文.时间戳().cmp(&b.文.时间戳())) }) .map(|x| Arc::clone(&x.文)) - .unwrap_or_else(|| Arc::clone(&self.元素[0].文)) + .unwrap_or_else(|| Arc::clone(&self.基础序列[0].文)) } } @@ -86,7 +118,7 @@ impl 线段特征 { /// tiebreaker: later时间戳 wins when特征值 equal (matches Python) pub fn 武(&self) -> Arc<分型> { if self.线段方向.是否向上() { - self.元素 + self.基础序列 .iter() .max_by(|a, b| { a.武 @@ -104,36 +136,38 @@ impl 线段特征 { }) }) .map(|x| x.武.read().unwrap().clone()) - .unwrap_or_else(|| self.元素[0].武.read().unwrap().clone()) + .unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone()) } else { - self.元素 + self.基础序列 .iter() - .min_by(|a, b| { - a.武 + .max_by(|a, b| { + b.武 .read() .unwrap() .分型特征值 - .partial_cmp(&b.武.read().unwrap().分型特征值) + .partial_cmp(&a.武.read().unwrap().分型特征值) .unwrap_or(std::cmp::Ordering::Equal) .then_with(|| { - b.武 + a.武 .read() .unwrap() .时间戳() - .cmp(&a.武.read().unwrap().时间戳()) + .cmp(&b.武.read().unwrap().时间戳()) }) }) .map(|x| x.武.read().unwrap().clone()) - .unwrap_or_else(|| self.元素[0].武.read().unwrap().clone()) + .unwrap_or_else(|| self.基础序列[0].武.read().unwrap().clone()) } } + /// 高 — 文和武中分型特征值的较大者 pub fn 高(&self) -> f64 { let 文 = self.文(); let 武 = self.武(); 文.分型特征值.max(武.分型特征值) } + /// 低 — 文和武中分型特征值的较小者 pub fn 低(&self) -> f64 { let 文 = self.文(); let 武 = self.武(); @@ -146,25 +180,25 @@ impl 线段特征 { } /// 向特征序列元素中添加虚线 - pub fn 添加(&mut self, 待添加虚线: Arc<虚线>) -> Result<(), String> { + pub fn _添加(&mut self, 待添加虚线: Arc<虚线>) -> Result<(), String> { if 待添加虚线.方向() == self.线段方向 { return Err("添加方向与线段方向相同".into()); } - self.元素.push(待添加虚线); + self.基础序列.push(待添加虚线); Ok(()) } /// 从特征序列元素中删除虚线 - pub fn 删除(&mut self, 待删除虚线: &Arc<虚线>) -> Result<(), String> { + pub fn _删除(&mut self, 待删除虚线: &Arc<虚线>) -> Result<(), String> { if 待删除虚线.方向() == self.方向() { return Err("删除方向与特征序列方向相同".into()); } if let Some(pos) = self - .元素 + .基础序列 .iter() .position(|x| Arc::as_ptr(x) == Arc::as_ptr(待删除虚线)) { - self.元素.remove(pos); + self.基础序列.remove(pos); Ok(()) } else { Err("待删除虚线不在特征序列中".into()) @@ -210,12 +244,12 @@ impl 线段特征 { if 应替换 { let 小号虚线 = 中 - .元素 + .基础序列 .iter() .min_by_key(|o| o.序号.load(Ordering::Relaxed)) .unwrap(); let 大号虚线 = 右 - .元素 + .基础序列 .iter() .max_by_key(|o| o.序号.load(Ordering::Relaxed)) .unwrap(); @@ -250,7 +284,7 @@ impl 线段特征 { )) { // Clone-modify-replace let mut 新特征 = (*结果[最后_idx]).clone(); - let _ = 新特征.添加(Arc::clone(虚线)); + let _ = 新特征._添加(Arc::clone(虚线)); 结果[最后_idx] = Arc::new(新特征); } else { 结果.push(Arc::new(Self::新建(vec![Arc::clone(虚线)], 线段方向))); @@ -260,7 +294,7 @@ impl 线段特征 { 结果 } - /// 获取分型序列 + /// 获取分型序列 — 从连续的特征序列元素中提取特征分型 pub fn 获取分型序列(特征序列: &[Arc<线段特征>]) -> Vec<特征分型> { let mut 结果 = Vec::new(); if 特征序列.len() < 3 { @@ -295,17 +329,17 @@ 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.标识, self.线段方向) + if self.基础序列.is_empty() { + write!(f, "{}<{}, 空>", self.标识.read().unwrap(), self.线段方向) } else { write!( f, "{}<{}, {}, {}, {}>", - self.标识, + self.标识.read().unwrap(), self.线段方向, self.文(), self.武(), - self.元素.len() + self.基础序列.len() ) } } @@ -558,7 +592,7 @@ mod tests { let 笔1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0); let mut feat = 线段特征::new("测试".into(), vec![], 相对方向::向上); - let result = feat.添加(Arc::clone(&笔1)); + let result = feat._添加(Arc::clone(&笔1)); assert!(result.is_ok()); } @@ -568,7 +602,7 @@ mod tests { let 笔1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0); let mut feat = 线段特征::new("测试".into(), vec![], 相对方向::向下); - let result = feat.添加(Arc::clone(&笔1)); + let result = feat._添加(Arc::clone(&笔1)); assert!(result.is_err()); } diff --git a/chanlun/src/types/bsp_type.rs b/chanlun/src/types/bsp_type.rs index c1d3392..6a8f2f9 100644 --- a/chanlun/src/types/bsp_type.rs +++ b/chanlun/src/types/bsp_type.rs @@ -91,6 +91,7 @@ impl std::fmt::Display for 买卖点类型 { } impl 买卖点类型 { + /// 判断是否为买点类型 pub fn 是买点(&self) -> bool { matches!( self, @@ -106,6 +107,7 @@ impl 买卖点类型 { ) } + /// 判断是否为卖点类型 pub fn 是卖点(&self) -> bool { matches!( self, diff --git a/chanlun/src/types/direction.rs b/chanlun/src/types/direction.rs index 8fc0bff..5b119ec 100644 --- a/chanlun/src/types/direction.rs +++ b/chanlun/src/types/direction.rs @@ -48,6 +48,7 @@ pub enum 相对方向 { } impl 相对方向 { + /// 翻转方向(向上→向下,缺口向上→缺口向下,顺→逆 等) pub fn 翻转(&self) -> Self { match self { Self::向上 => Self::向下, @@ -62,32 +63,33 @@ impl 相对方向 { } } + /// 是否向上方向(向上/向上缺口/衔接向上) pub fn 是否向上(&self) -> bool { matches!(self, Self::向上 | Self::向上缺口 | Self::衔接向上) } + /// 是否向下方向(向下/向下缺口/衔接向下) pub fn 是否向下(&self) -> bool { matches!(self, Self::向下 | Self::向下缺口 | Self::衔接向下) } + /// 是否包含关系(顺/逆/同) pub fn 是否包含(&self) -> bool { matches!(self, Self::顺 | Self::逆 | Self::同) } + /// 是否缺口(向上缺口/向下缺口) pub fn 是否缺口(&self) -> bool { matches!(self, Self::向下缺口 | Self::向上缺口) } + /// 是否衔接(衔接向上/衔接向下) pub fn 是否衔接(&self) -> bool { matches!(self, Self::衔接向下 | Self::衔接向上) } /// 分析两个K线之间的相对方向 pub fn 分析(前高: f64, 前低: f64, 后高: f64, 后低: f64) -> Self { - // NaN 值无法判断方向,视为"同"避免 panic - if 前高.is_nan() || 前低.is_nan() || 后高.is_nan() || 后低.is_nan() { - return Self::同; - } if 前高 == 后高 && 前低 == 后低 { return Self::同; } diff --git a/chanlun/src/types/fractal.rs b/chanlun/src/types/fractal.rs index fdd846e..aad4e75 100644 --- a/chanlun/src/types/fractal.rs +++ b/chanlun/src/types/fractal.rs @@ -23,6 +23,7 @@ */ use serde::{Deserialize, Serialize}; +use tracing::warn; /// 分型结构 —— 三根K线构成的结构形态 #[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] @@ -93,7 +94,9 @@ impl 分型结构 { } #[allow(clippy::too_many_arguments)] - fn 分析_内部( + /// 分析三K线的分型结构(内部实现,接收裸f64值) + #[allow(clippy::too_many_arguments)] + pub fn 分析_内部( 左高: f64, 左低: f64, 中高: f64, @@ -112,36 +115,23 @@ impl 分型结构 { let 向下类 = |d: 相对方向| d.是否向下(); match (左中关系, 中右关系) { - // 顺序包含 — 忽视时可以绕过 (相对方向::顺, _) if !忽视顺序包含 => { - panic!("顺序包含: {:?} {:?}", 左中关系, 中右关系); + panic!("顺序包含 左中相对方向"); } (_, 相对方向::顺) if !忽视顺序包含 => { - panic!("顺序包含: {:?} {:?}", 左中关系, 中右关系); + panic!("顺序包含 中右相对方向"); } - // 向上 + 向上 = 三连上 (a, b) if 向上类(a) && 向上类(b) => Some(Self::上), - // 向上 + 向下 = 顶分型 (a, b) if 向上类(a) && 向下类(b) => Some(Self::顶), - // 向上 + 逆序包含 = 上 (a, 相对方向::逆) if 向上类(a) && 可以逆序包含 => Some(Self::上), - // 向下 + 向上 = 底分型 (a, b) if 向下类(a) && 向上类(b) => Some(Self::底), - // 向下 + 向下 = 三连下 (a, b) if 向下类(a) && 向下类(b) => Some(Self::下), - // 向下 + 逆序包含 = 下 (a, 相对方向::逆) if 向下类(a) && 可以逆序包含 => Some(Self::下), - // 逆序包含 + 向上 = 底 (相对方向::逆, a) if 向上类(a) && 可以逆序包含 => Some(Self::底), - // 逆序包含 + 向下 = 顶 (相对方向::逆, a) if 向下类(a) && 可以逆序包含 => Some(Self::顶), - // 逆序包含 + 逆序包含 = 散 (相对方向::逆, 相对方向::逆) if 可以逆序包含 => Some(Self::散), _ => { - eprintln!( - "无法识别的分型结构: 左中={:?}, 中右={:?}", - 左中关系, 中右关系 - ); + warn!("未匹配的关系 {}, {}, {}", 可以逆序包含, 左中关系, 中右关系); None } } diff --git a/chanlun/src/types/gap.rs b/chanlun/src/types/gap.rs index 295533f..744112b 100644 --- a/chanlun/src/types/gap.rs +++ b/chanlun/src/types/gap.rs @@ -27,11 +27,14 @@ use serde::{Deserialize, Serialize}; /// 缺口 —— 两个价格区间之间的空隙 #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] pub struct 缺口 { + /// 缺口上沿 pub 高: f64, + /// 缺口下沿 pub 低: f64, } impl 缺口 { + /// 创建缺口,高必须大于低 pub fn new(高: f64, 低: f64) -> Self { assert!(高 > 低, "缺口高必须大于低: 高={高}, 低={低}"); Self { 高, 低 } diff --git a/chanlun/src/types/sync_f64.rs b/chanlun/src/types/sync_f64.rs index 65878fd..b657b7a 100644 --- a/chanlun/src/types/sync_f64.rs +++ b/chanlun/src/types/sync_f64.rs @@ -26,17 +26,21 @@ use std::sync::atomic::{AtomicU64, Ordering}; /// f64 原子类型 — 基于 AtomicU64 + 位转换,API 与 `Cell` 一致。 #[derive(Debug, Default)] +/// 线程安全的 f64 原子容器 pub struct SyncF64(AtomicU64); impl SyncF64 { + /// 创建 SyncF64 pub fn new(v: f64) -> Self { Self(AtomicU64::new(v.to_bits())) } + /// 原子读取 f64 值 pub fn get(&self) -> f64 { f64::from_bits(self.0.load(Ordering::Relaxed)) } + /// 原子写入 f64 值 pub fn set(&self, v: f64) { self.0.store(v.to_bits(), Ordering::Relaxed); }