18 Commits

Author SHA1 Message Date
YuWuKunCheng 823a24d364 第九版 2026-06-05 09:02:54 +08:00
YuWuKunCheng 58c85dc7cd 添加 Read the docs 配置 2026-05-31 12:05:48 +08:00
YuWuKunCheng c3e9ae8d77 Nothing v26.5.103 2026-05-30 22:34:10 +08:00
YuWuKunCheng b7c4e60420 1. thread_local! 导致跨线程缓存不可见(主要问题)
kline_py.rs 中 BSP_CACHE、KLINE_IDENTITY、BAR_IDENTITY 使用了 thread_local!。主线程调用 识别买卖点() →
  买卖点信息.add() 写入的是主线程的 PySet,backtrader 策略线程读取的是自己线程独立的空 PySet。

  修复:将三个缓存从 thread_local! 改为全局 static + std::sync::LazyLock<RwLock<HashMap<...>>>

  影响分析

  这些身份缓存的影响与 KLINE_IDENTITY/BAR_IDENTITY 不同——它们只影响 Python 对象身份(is
  比较),不直接影响数据内容(因为 Rust 数据通过 Arc 共享,读写都是同一份)。

  具体后果:
  - 不同线程访问同一个 Rust Arc 会得到不同的 Python wrapper 对象
  - a is b 跨线程比较返回 False,哪怕它们包装同一个底层 Rust 对象
  - 每个线程维护一份独立缓存,内存浪费(不过 wrapper 很小)
2026-05-30 22:15:09 +08:00
YuWuKunCheng 15dc44e8e0 回测测试 2026-05-30 20:06:59 +08:00
YuWuKunCheng bd4fceab02 全量支持 观察者在python端的重写机制 2026-05-30 15:51:18 +08:00
YuWuKunCheng af3ebf13c8 修复 “cargo clippy -- -D warnings” 产生的错误 2026-05-30 13:25:57 +08:00
YuWuKunCheng 22109d8b30 合并 工作流 2026-05-30 13:03:37 +08:00
YuWuKunCheng c2c09fc8ba 合并 工作流 2026-05-30 12:17:22 +08:00
YuWuKunCheng 0eb52cc06a 添加 自动发包 2026-05-30 11:56:06 +08:00
YuWuKunCheng 1e6025a968 添加 原始chan到模块
修复 相对方向的一致性
添加 观察者.投喂原始数据
2026-05-30 11:26:59 +08:00
YuWuKunCheng 14279f3df6 完善 测试类 2026-05-29 23:28:59 +08:00
YuWuKunCheng fca62f3141 修复 买卖点一致性对齐至chan.py 2026-05-29 20:42:34 +08:00
YuWuKunCheng e50172e923 修复 中枢一致性 2026-05-29 20:19:42 +08:00
YuWuKunCheng c87fb66d34 修复 分型时间戳 2026-05-29 17:30:58 +08:00
YuWuKunCheng 9900266516 修复 买卖点 2026-05-29 15:01:02 +08:00
YuWuKunCheng 5c3179eec8 第八版 2026-05-29 04:05:36 +08:00
YuWuKunCheng ae57090d7f 1. Pickle 支持修复(__reduce__)
- 修复了 相对方向Py.__reduce__ 中 str() 返回 相对方向.向上 导致 getattr 失败的问题(拆分取 . 后变体名)
  - 重新编译后 __module__ 正确返回 chanlun._chanlun
  - 所有 4 个类型(相对方向、买卖点类型、分型结构、缺口)pickle 往返测试通过

  2. from_py_object 消除 163 个弃用警告

  - 给 14 个 #[derive(Clone)] 的 pyclass 添加了 from_py_object
  - #[pyclass] 中的正确写法:#[pyclass(name = "X", module = "chanlun._chanlun", from_py_object)]
  - cargo clean 重新编译后零 from_py_object 警告

  3. #[classattr] + __members__

  - 为 3 个枚举类型(相对方向、买卖点类型、分型结构)添加了 __members__ 类属性
  - __members__ 是 dict[str, 实例],行为与 Python Enum.__members__ 一致
  - 通过 pickle 反序列化的值也在 __members__.values() 中

  4. __richcmp__

  - 为 3 个枚举类型 + 缺口实现了 __richcmp__,替换手写 __eq__
  - 相对方向/分型结构:支持全部 6 种比较(基于判别值排序)
  - 买卖点类型:支持 Eq/Ne + 与字符串比较
  - 缺口:支持全部 6 种比较(按 (高, 低) 元组排序)

  5. 补充文档
2026-05-28 14:03:53 +08:00
69 changed files with 19076 additions and 4983 deletions
+100 -8
View File
@@ -16,9 +16,87 @@ env:
jobs:
# ============================================================
# Linux x86_64 (manylinux)
# 1. 发布 chanlun 核心库至 crates.io
# ============================================================
publish-crates:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
exists: ${{ steps.check.outputs.exists }}
steps:
- uses: actions/checkout@v4
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- name: 缓存依赖
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
target
key: ${{ runner.os }}-cargo-${{ hashFiles('chanlun/Cargo.lock') }}
- name: 提取版本号
id: version
working-directory: chanlun
run: |
VER=$(cargo metadata --format-version 1 --no-deps 2>/dev/null \
| jq -r '.packages[] | select(.name == "chanlun") | .version')
echo "version=$VER" >> $GITHUB_OUTPUT
echo "当前版本: $VER"
- name: 检查版本是否已存在
id: check
run: |
VER="${{ steps.version.outputs.version }}"
EXISTS=$(curl -sS "https://crates.io/api/v1/crates/chanlun" \
| jq -r --arg v "$VER" '.versions[]?.num // empty | select(. == $v)')
if [ -n "$EXISTS" ]; then
echo "版本 $VER 已存在于 crates.io,跳过发布"
echo "exists=true" >> $GITHUB_OUTPUT
else
echo "版本 $VER 未发布,继续"
echo "exists=false" >> $GITHUB_OUTPUT
fi
- name: 格式检查
if: steps.check.outputs.exists == 'false'
working-directory: chanlun
run: cargo fmt --check
- name: Lint 检查
if: steps.check.outputs.exists == 'false'
working-directory: chanlun
run: cargo clippy
- name: 运行测试
if: steps.check.outputs.exists == 'false'
working-directory: chanlun
run: cargo test
- name: 验证打包
if: steps.check.outputs.exists == 'false'
working-directory: chanlun
run: cargo publish --dry-run --allow-dirty
- name: 登录 crates.io
if: steps.check.outputs.exists == 'false'
run: cargo login ${{ secrets.CARGO_TOKEN }}
- name: 发布 chanlun 至 crates.io
if: steps.check.outputs.exists == 'false'
working-directory: chanlun
run: cargo publish --allow-dirty
# ============================================================
# 2. 构建 wheel — Linux x86_64 (manylinux)
# ============================================================
linux-x86_64:
needs: [publish-crates]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -31,6 +109,10 @@ jobs:
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
- name: 更新 cargo 索引(确保新版本可见)
run: cargo update
working-directory: chanlun-py
- name: 构建 wheel (manylinux)
uses: PyO3/maturin-action@v1
with:
@@ -45,11 +127,11 @@ jobs:
name: wheels-linux-x86_64
path: chanlun-py/dist/
# ============================================================
# macOS wheels (x86_64 + arm64)
# 3. 构建 wheel — macOS (x86_64 + arm64)
# ============================================================
macos:
needs: [publish-crates]
runs-on: macos-latest
strategy:
matrix:
@@ -65,6 +147,10 @@ jobs:
with:
python-version: '3.12'
- name: 更新 cargo 索引
run: cargo update
working-directory: chanlun-py
- name: 构建 wheel
uses: PyO3/maturin-action@v1
with:
@@ -79,9 +165,10 @@ jobs:
path: chanlun-py/dist/
# ============================================================
# Windows wheels (x86_64)
# 4. 构建 wheel — Windows x86_64
# ============================================================
windows:
needs: [publish-crates]
runs-on: windows-latest
strategy:
matrix:
@@ -97,6 +184,10 @@ jobs:
with:
python-version: '3.12'
- name: 更新 cargo 索引
run: cargo update
working-directory: chanlun-py
- name: 构建 wheel
uses: PyO3/maturin-action@v1
with:
@@ -111,9 +202,10 @@ jobs:
path: chanlun-py/dist/
# ============================================================
# 源码分发包 (sdist)
# 5. 源码分发包 (sdist)
# ============================================================
sdist:
needs: [publish-crates]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
@@ -137,14 +229,14 @@ jobs:
path: chanlun-py/dist/
# ============================================================
# 发布至 PyPI
# 6. 发布至 PyPI
# ============================================================
publish:
needs: [linux-x86_64, macos, windows, sdist]
needs: [publish-crates, linux-x86_64, macos, windows, sdist]
runs-on: ubuntu-latest
if: startsWith(github.ref, 'refs/tags/v') || github.event.inputs.publish-to-pypi == 'true'
permissions:
id-token: write # PyPI 信任发布(推荐)
id-token: write
steps:
- name: 下载所有产物
+22
View File
@@ -0,0 +1,22 @@
version: 2
build:
os: ubuntu-24.04
tools:
python: "3.12"
jobs:
pre_install:
# 安装 Rust 工具链以编译 PyO3 扩展
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable
- source $HOME/.cargo/env
- pip install maturin
sphinx:
configuration: docs/conf.py
python:
install:
- requirements: docs/requirements.txt
# 从源码安装 chanlun-pymaturin develop
- method: pip
path: chanlun-py
+6 -7
View File
@@ -3,7 +3,7 @@
[![PyPI](https://img.shields.io/pypi/v/chanlun)](https://pypi.org/project/chanlun/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
基于 [chanlun](./chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API `chan.py` 完全兼容。
基于 [chanlun](./chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 参考 `chan.py` 设计,高度兼容。
## 安装
@@ -19,8 +19,8 @@ import chanlun
# 创建配置(全部默认值)
config = chanlun.缠论配置()
# 读取 K 线数据文件,创建观察者
obs = chanlun.观察者.读取数据文件("path/to/data.nb", config)
# 读取 K 线数据文件(文件名需遵循 `符号-周期-起始时间戳-结束时间戳.nb` 格式,如 `btcusd-300-1631772074-1632222374.nb`
obs = chanlun.观察者.读取数据文件("path/to/btcusd-300-1631772074-1632222374.nb", config)
# 查看各层级序列
print(f"K线数量: {len(obs.普通K线序列)}")
@@ -29,7 +29,7 @@ print(f"线段数量: {len(obs.线段序列)}")
print(f"中枢数量: {len(obs.中枢序列)}")
# 或使用立体分析器进行多周期分析
analyzer = chanlun.立体分析器("BTCUSD", ["1min", "5min", "30min"], config)
analyzer = chanlun.立体分析器("BTCUSD", [60, 60*5, 60*5*6], config)
# 逐根投喂 K 线...
```
@@ -53,7 +53,6 @@ pip install target/wheels/chanlun-*.whl
```bash
./build.sh develop # 开发安装
./build.sh wheel # 构建 wheel
./build.sh test # 运行集成测试
```
## 导出类
@@ -70,8 +69,8 @@ pip install target/wheels/chanlun-*.whl
## 兼容性
- Python 3.9+
- 类名 / 方法名 / 字段名 / 签名`chan.py` 一致
- 支持 `.nb` / `.dat` 二进制文件格式(大端字节序)
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致
- 支持 `.nb` 二进制文件格式(大端字节序)
## 许可
+59 -25
View File
@@ -28,8 +28,10 @@ SOFTWARE.
import json
import math
import os
import struct
import sys
import tempfile
from dataclasses import dataclass, field
from datetime import datetime
from enum import Enum
@@ -2202,6 +2204,9 @@ class 缠论K线(object):
return 序列[序列.index() : 序列.index() + 1]
分型模式 = True
class 分型(object):
"""分型 — 由左中右三根缠论K线构成的顶/底分型结构。
@@ -2216,7 +2221,7 @@ class 分型(object):
:ivar 与MACD柱子分型匹配: 是否与MACD柱子分型匹配
"""
__slots__ = ["", "", "", "结构", "时间戳", "分型特征值"]
__slots__ = ["", "", "", "_结构", "_时间戳", "_分型特征值"]
def __init__(self, : Optional[缠论K线], : 缠论K线, : Optional[缠论K线]):
"""
@@ -2229,9 +2234,9 @@ class 分型(object):
self.: Optional[缠论K线] =
self.: 缠论K线 =
self.: Optional[缠论K线] =
self.结构 = .分型
self.时间戳 = .时间戳
self.分型特征值 = .分型特征值
self._结构 = .分型
self._时间戳 = .时间戳
self._分型特征值 = .分型特征值
def __str__(self):
return f"{self..分型}<{self.时间戳}, {self.分型特征值:g}, None: {self. is None}, None: {self. is None}>"
@@ -2239,6 +2244,24 @@ class 分型(object):
def __repr__(self):
return f"{self..分型}<{self.时间戳}, {self.分型特征值:g}, None: {self. is None}, None: {self. is None}>"
@property
def 时间戳(self):
if 分型模式:
return self._时间戳
return self..时间戳
@property
def 分型特征值(self):
if 分型模式:
return self._分型特征值
return self..分型特征值
@property
def 结构(self):
if 分型模式:
return self._结构
return self..分型
@property
def 关系组(self) -> Optional[Tuple[相对方向, 相对方向, 相对方向]]:
"""左、中、右三对相对方向关系
@@ -5055,6 +5078,17 @@ class 观察者:
self.扩展线段序列_扩展线段: List[虚线] = []
self.扩展中枢序列_扩展线段: List[中枢] = []
def 投喂原始数据(self, 时间戳: datetime, : float, : float, : float, : float, : float):
"""便捷入口,直接从 OHLCV 创建 K线 并投喂
:param 时间戳: 时间戳
:param 开: 开盘价
:param 高: 最高价
:param 低: 最低价
:param 收: 收盘价
:param 量: 成交量
"""
self.增加原始K线(K线.创建普K(self.标识, 时间戳, , , , , , 0, self.周期))
@final
def 增加原始K线(self, 普K: K线):
"""核心入口 — 投喂一根原始K线,增量更新所有层级
@@ -5201,33 +5235,32 @@ class 观察者:
self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列)
def 加载本地数据(self, 文件路径: str):
"""重置基础序列后加载数据文件
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
"""
self.重置基础序列()
with open(文件路径, "rb") as f:
buffer = f.read()
size = struct.calcsize(">6d")
for i in range(len(buffer) // size):
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], self.周期, self.标识)
self.增加原始K线(k线)
时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量 = struct.unpack(">6d", buffer[i * size : i * size + size])
self.投喂原始数据(转化为时间戳(int(时间戳)), 开盘价, 最高价, 最低价, 收盘价, 成交量)
@classmethod
def 读取数据文件(cls, 文件路径: str, 配置=缠论配置()) -> Self:
"""
def 读取数据文件(cls, 观察员: "观察者", 文件路径: str, 配置=缠论配置()) -> Self:
"""加载数据文件
:param 观察员: 观察者
:param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
:param 配置: 缠论配置
:return: 观察者实例
"""
name = Path(文件路径).name.split(".")[0]
符号, 周期, 起始时间戳, 结束时间戳 = name.split("-")
实例 = cls(符号=符号, 周期=int(周期), 配置=配置)
with open(文件路径, "rb") as f:
buffer = f.read()
size = struct.calcsize(">6d")
for i in range(len(buffer) // size):
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号)
实例.增加原始K线(k线)
return 实例
观察员.符号 = 符号
观察员.周期 = int(周期)
观察员.配置 = 配置
观察员.加载本地数据(文件路径)
return 观察员
class K线合成器:
@@ -5462,10 +5495,10 @@ class 立体分析器:
if 当前K线 := self._K线合成器.获取当前K线(周期):
self._单体分析器[周期].增加原始K线(当前K线)
def 测试_保存数据(self):
def 测试_保存数据(self, root: str = None):
"""拆分各序列数据,单独存文件,文件名为对应变量名"""
# 生成存储根目录
脚本目录 = Path(__file__).parent # 取当前脚本所在文件夹
脚本目录 = Path(__file__).parent if not root else root # 取当前脚本所在文件夹
起始时间 = int(self._单体分析器[self.__输入周期].普通K线序列[0].时间戳.timestamp())
结束时间 = int(self._单体分析器[self.__输入周期].普通K线序列[-1].时间戳.timestamp())
目录标识 = f"PyM_{self._单体分析器[self.__输入周期].标识}_{起始时间}_{结束时间}"
@@ -5480,16 +5513,16 @@ class 立体分析器:
print(f"多级别数据拆分保存完成,目录:{保存路径.resolve()}")
def 测试_读取数据(配置: 缠论配置):
def 测试_读取数据(观察员: 观察者, 配置: 缠论配置) -> Callable[[], "观察者"]:
"""测试_读取数据
:param 观察员: 观察者
:param 配置: 缠论配置
:return: 测试函数
"""
def 魔法():
启动时间 = datetime.now()
观察 = 观察.读取数据文件(配置.加载文件路径, 配置)
观察者.读取数据文件(观察员, 配置.加载文件路径, 配置)
消耗用时 = datetime.now() - 启动时间
print("测试_读取数据 耗时", 消耗用时, "普K数量", len(观察员.普通K线序列))
return 观察员
@@ -5529,5 +5562,6 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置]
if __name__ == "__main__":
当前配置 = 缠论配置.不推送()
当前配置.加载文件路径 = str(Path(__file__).parent / "btcusd-300-1761327300-1776327900.nb")
测试_读取数据(当前配置)().测试_保存数据()
测试_周期合成(当前配置)().测试_保存数据()
观察员 = 观察者("", 0, 当前配置)
测试_读取数据(观察员, 当前配置)() # .测试_保存数据()
# 测试_周期合成(当前配置)().测试_保存数据()
+2
View File
@@ -3,3 +3,5 @@ __pycache__/
*.egg-info/
dist/
build/
Cargo.lock
+7 -4
View File
@@ -1,7 +1,7 @@
[package]
name = "chanlun-py"
version = "26.5.46"
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.2" # { path = "../chanlun" }
pyo3 = { version = "0.28", features = ["extension-module"] }
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"
-1
View File
@@ -1 +0,0 @@
../LICENSE
+21
View File
@@ -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.
-1
View File
@@ -1 +0,0 @@
../README.md
+79
View File
@@ -0,0 +1,79 @@
# chanlun — 缠论技术分析 Python 绑定
[![PyPI](https://img.shields.io/pypi/v/chanlun)](https://pypi.org/project/chanlun/)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
基于 [chanlun](../chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 参考 `chan.py` 设计,高度兼容。
## 安装
```bash
pip install chanlun
```
## 快速开始
```python
import chanlun
# 创建配置(全部默认值)
config = chanlun.缠论配置()
# 读取 K 线数据文件(文件名需遵循 `符号-周期-起始时间戳-结束时间戳.nb` 格式,如 `btcusd-300-1631772074-1632222374.nb`
obs = chanlun.观察者.读取数据文件("path/to/btcusd-300-1631772074-1632222374.nb", config)
# 查看各层级序列
print(f"K线数量: {len(obs.普通K线序列)}")
print(f"笔数量: {len(obs.笔序列)}")
print(f"线段数量: {len(obs.线段序列)}")
print(f"中枢数量: {len(obs.中枢序列)}")
# 或使用立体分析器进行多周期分析
analyzer = chanlun.立体分析器("BTCUSD", [60, 60*5, 60*5*6], config)
# 逐根投喂 K 线...
```
## 从源码构建
前置依赖: [Rust](https://www.rust-lang.org) + [maturin](https://www.maturin.rs)
```bash
pip install maturin
# 开发模式(直接安装到当前 venv)
maturin develop
# 或构建 wheel
maturin build --release
pip install target/wheels/chanlun-*.whl
```
也可使用项目内的 `build.sh`:
```bash
./build.sh develop # 开发安装
./build.sh wheel # 构建 wheel
```
## 导出类
| 类别 | 类名 | 说明 |
|------|------|------|
| 枚举 | `买卖点类型`, `相对方向`, `分型结构` | 缠论基础枚举 |
| 数据 | `缺口`, `K线`, `缠论K线` | K 线数据结构 |
| 结构 | `分型`, `虚线`, `线段特征`, `特征分型` | 分析层级结构 |
| 指标 | `平滑异同移动平均线`, `相对强弱指数`, `随机指标` | MACD/RSI/KDJ |
| 算法 | `笔`, `线段`, `中枢`, `背驰分析` | 识别算法 |
| 业务 | `缠论配置`, `基础买卖点`, `买卖点`, `观察者`, `K线合成器`, `立体分析器` | 分析框架 |
## 兼容性
- Python 3.9+
- 类名 / 方法名 / 字段名与 `chan.py` 保持一致
- 支持 `.nb` 二进制文件格式(大端字节序)
## 许可
本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parseMIT)、termcolorMIT)。
详见 [NOTICE](../NOTICE) 和 [LICENSES/](../LICENSES/) 目录。
+68
View File
@@ -0,0 +1,68 @@
/// build.rs — 编译时版本一致性检查
///
/// 验证 Cargo.toml 和 pyproject.toml 的版本号是否一致。
///
/// Cargo.toml: version = "YY.MM.patch" (e.g., "26.5.57")
/// pyproject.toml: version = "YYMM.patch" (e.g., "2605.57")
/// 规则: YY = major, MM = minor, patch = patch
fn main() {
let cargo_manifest_dir =
std::env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR not set");
let cargo_path = std::path::PathBuf::from(&cargo_manifest_dir);
let cargo_version = read_version(&cargo_path.join("Cargo.toml"), "[package]");
let cargo_parts: Vec<&str> = cargo_version.split('.').collect();
assert_eq!(
cargo_parts.len(),
3,
"Cargo.toml version '{}' is not in YY.MM.patch format",
cargo_version
);
let pyproject_version = read_version(&cargo_path.join("pyproject.toml"), "[project]");
let py_parts: Vec<&str> = pyproject_version.split('.').collect();
assert_eq!(
py_parts.len(),
2,
"pyproject.toml version '{}' is not in YYMM.patch format",
pyproject_version
);
let expected_yymm = format!("{}{:0>2}", cargo_parts[0], cargo_parts[1]);
assert_eq!(
py_parts[0], expected_yymm,
"pyproject.toml version prefix '{}' != expected '{}' (from Cargo {})",
py_parts[0], expected_yymm, cargo_version
);
assert_eq!(
py_parts[1], cargo_parts[2],
"pyproject.toml patch '{}' != Cargo.toml patch '{}'",
py_parts[1], cargo_parts[2]
);
println!("cargo:rerun-if-changed=pyproject.toml");
println!("cargo:rerun-if-changed=Cargo.toml");
}
fn read_version(path: &std::path::Path, section: &str) -> String {
let content =
std::fs::read_to_string(path).unwrap_or_else(|e| panic!("Cannot read {:?}: {}", path, e));
let mut in_section = section.is_empty();
for line in content.lines() {
let trimmed = line.trim();
if trimmed.starts_with('[') {
in_section = trimmed == section;
continue;
}
if !in_section {
continue;
}
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);
}
Executable → Regular
View File
Executable → Regular
View File
+833
View File
@@ -0,0 +1,833 @@
# 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
# ========== Module-level functions ==========
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: ...
# ========== Enum-like types ==========
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) -> bool: ...
@property
def 是卖点(self) -> bool: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
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, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
class 分型结构:
: ClassVar[分型结构]
: ClassVar[分型结构]
: ClassVar[分型结构]
: ClassVar[分型结构]
: ClassVar[分型结构]
@classmethod
def 分析(cls, : Any, : Any, : Any, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional[分型结构]: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
class 缺口:
def __init__(self, : float, : float) -> None: ...
@property
def (self) -> float: ...
@高.setter
def (self, value: float) -> None: ...
@property
def (self) -> float: ...
@低.setter
def (self, value: float) -> None: ...
@classmethod
def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional[缺口]: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
# ========== Indicators ==========
class 平滑异同移动平均线:
@property
def 时间戳(self) -> str: ...
@property
def 收盘价(self) -> float: ...
@property
def 快线周期(self) -> int: ...
@property
def 慢线周期(self) -> int: ...
@property
def 信号周期(self) -> int: ...
@property
def DIF(self) -> Optional[float]: ...
@property
def DEA(self) -> Optional[float]: ...
@property
def MACD柱(self) -> float: ...
@property
def 快线EMA(self) -> Optional[float]: ...
@property
def 慢线EMA(self) -> Optional[float]: ...
@property
def DEA_EMA(self) -> Optional[float]: ...
@classmethod
def 首次计算(cls, 初始收盘价: float, 初始时间: int, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ...
@classmethod
def 首次计算_K线(cls, k线: Any, 计算方式: str, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ...
@classmethod
def 增量计算(cls, 前一个MACD: 平滑异同移动平均线, 当前收盘价: float, 当前时间: int) -> 平滑异同移动平均线: ...
@classmethod
def 增量计算_K线(cls, 前一个MACD: 平滑异同移动平均线, 当前K线: Any, 计算方式: str) -> 平滑异同移动平均线: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
class 相对强弱指数:
@property
def 时间戳(self) -> str: ...
@property
def 收盘价(self) -> float: ...
@property
def 周期(self) -> int: ...
@property
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, 初始收盘价: 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 随机指标:
@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: ...
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) -> 相对方向: ...
@property
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: ...
# ========== Algorithm ==========
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: ...
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线]: ...
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线]: ...
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: ...
# ========== Business ==========
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: ...
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: ...
@property
def 周期(self) -> int: ...
@property
def 配置(self) -> 缠论配置: ...
@property
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) -> 观察者: ...
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线]: ...
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: ...
# ========== 缠论配置 (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: ...
+57
View File
@@ -1,3 +1,60 @@
"""缠论技术分析库 — Rust 高性能实现"""
__all__ = [
"K线",
"K线合成器",
"中枢",
"买卖点",
"买卖点类型",
"分型",
"分型结构",
"基础买卖点",
"平滑异同移动平均线",
"指标",
"指标容器",
"指标计算器",
"均线工具",
"测试_读取数据",
"相对强弱指数",
"相对方向",
"立体分析器",
"",
"线段",
"线段特征",
"缠论K线",
"缠论配置",
"缺口",
"背驰分析",
"虚线",
"观察者",
"转化为时间戳",
"转化为时间戳_数字",
"随机指标",
"布林带",
"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()
+833
View File
@@ -0,0 +1,833 @@
# 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
# ========== Module-level functions ==========
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: ...
# ========== Enum-like types ==========
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) -> bool: ...
@property
def 是卖点(self) -> bool: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
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, 前高: float, 前低: float, 后高: float, 后低: float) -> 相对方向: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
class 分型结构:
: ClassVar[分型结构]
: ClassVar[分型结构]
: ClassVar[分型结构]
: ClassVar[分型结构]
: ClassVar[分型结构]
@classmethod
def 分析(cls, : Any, : Any, : Any, 可以逆序包含: bool = False, 忽视顺序包含: bool = False) -> Optional[分型结构]: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
class 缺口:
def __init__(self, : float, : float) -> None: ...
@property
def (self) -> float: ...
@高.setter
def (self, value: float) -> None: ...
@property
def (self) -> float: ...
@低.setter
def (self, value: float) -> None: ...
@classmethod
def 居中截取区间(cls, 起点: float, 终点: float, 比例: float = 0.15) -> Optional[缺口]: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
def __hash__(self) -> int: ...
def __reduce__(self) -> tuple: ...
# ========== Indicators ==========
class 平滑异同移动平均线:
@property
def 时间戳(self) -> str: ...
@property
def 收盘价(self) -> float: ...
@property
def 快线周期(self) -> int: ...
@property
def 慢线周期(self) -> int: ...
@property
def 信号周期(self) -> int: ...
@property
def DIF(self) -> Optional[float]: ...
@property
def DEA(self) -> Optional[float]: ...
@property
def MACD柱(self) -> float: ...
@property
def 快线EMA(self) -> Optional[float]: ...
@property
def 慢线EMA(self) -> Optional[float]: ...
@property
def DEA_EMA(self) -> Optional[float]: ...
@classmethod
def 首次计算(cls, 初始收盘价: float, 初始时间: int, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ...
@classmethod
def 首次计算_K线(cls, k线: Any, 计算方式: str, 快线周期: Optional[int] = None, 慢线周期: Optional[int] = None, 信号周期: Optional[int] = None) -> 平滑异同移动平均线: ...
@classmethod
def 增量计算(cls, 前一个MACD: 平滑异同移动平均线, 当前收盘价: float, 当前时间: int) -> 平滑异同移动平均线: ...
@classmethod
def 增量计算_K线(cls, 前一个MACD: 平滑异同移动平均线, 当前K线: Any, 计算方式: str) -> 平滑异同移动平均线: ...
def __str__(self) -> str: ...
def __repr__(self) -> str: ...
class 相对强弱指数:
@property
def 时间戳(self) -> str: ...
@property
def 收盘价(self) -> float: ...
@property
def 周期(self) -> int: ...
@property
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, 初始收盘价: 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 随机指标:
@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: ...
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) -> 相对方向: ...
@property
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: ...
# ========== Algorithm ==========
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: ...
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线]: ...
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线]: ...
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: ...
# ========== Business ==========
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: ...
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: ...
@property
def 周期(self) -> int: ...
@property
def 配置(self) -> 缠论配置: ...
@property
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) -> 观察者: ...
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线]: ...
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: ...
# ========== 缠论配置 (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: ...
File diff suppressed because it is too large Load Diff
+18 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "chanlun"
version = "2605.46"
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"
@@ -37,3 +42,15 @@ features = ["pyo3/extension-module"]
python-source = "."
module-name = "chanlun._chanlun"
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
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+119 -20
View File
@@ -25,6 +25,7 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use std::collections::HashMap;
use tracing::warn;
/// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。
///
@@ -91,7 +92,7 @@ use std::collections::HashMap;
/// 不推送() -> 缠论配置 (classmethod) — 创建关闭所有推送的配置副本
/// 按序号重组字典(默认配置, 原始字典) -> dict (classmethod) — 按默认配置的键序重排字典
/// 对比(other) -> dict — 返回与另一个配置的差异字段
#[pyclass(name = "缠论配置")]
#[pyclass(name = "缠论配置", module = "chanlun._chanlun")]
pub struct Py {
fields: HashMap<String, Py<PyAny>>,
}
@@ -183,26 +184,23 @@ impl 缠论配置Py {
}
/// 保存配置到 JSON 文件(默认路径 "缠论配置.json")。
fn (&self, py: Python<'_>, path: Option<&str>) -> PyResult<()> {
let path = path.unwrap_or("缠论配置.json");
#[pyo3(signature = (path = "缠论配置.json"))]
fn (&self, py: Python<'_>, path: &str) -> PyResult<()> {
let json = self.to_json(py)?;
std::fs::write(path, json).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))
}
/// 从 JSON 文件加载配置(默认路径 "缠论配置.json")。
#[classmethod]
fn (
_cls: &Bound<'_, PyType>,
py: Python<'_>,
path: Option<&str>,
) -> PyResult<Self> {
let path = path.unwrap_or("缠论配置.json");
#[pyo3(signature = (path = "缠论配置.json"))]
fn (_cls: &Bound<'_, PyType>, py: Python<'_>, path: &str) -> PyResult<Self> {
let json_str = std::fs::read_to_string(path)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?;
Self::from_json_str(py, &json_str)
}
#[classmethod]
/// :param data: 字典数据
fn from_dict(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyDict>) -> PyResult<Self> {
let default_config = chanlun::config::::default();
let mut fields = config_to_field_dict(&default_config)?;
@@ -220,11 +218,13 @@ impl 缠论配置Py {
}
#[classmethod]
/// :param json_str: JSON字符串
fn from_json(_cls: &Bound<'_, PyType>, py: Python<'_>, json_str: &str) -> PyResult<Self> {
Self::from_json_str(py, json_str)
}
#[classmethod]
/// 创建不推送任何图表的静默配置(用于纯计算场景)
fn (_cls: &Bound<'_, PyType>) -> PyResult<Self> {
let config = chanlun::config::::default().();
let fields = config_to_field_dict(&config)?;
@@ -232,6 +232,7 @@ impl 缠论配置Py {
}
#[classmethod]
/// 将形如 "1_open", "1_close", "2_open", "name" 的字典重组为嵌套结构
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyAny>,
@@ -239,7 +240,7 @@ impl 缠论配置Py {
) -> PyResult<Py<PyDict>> {
let py = .py();
let result = PyDict::new(py);
if let Ok(default_dict) = .downcast::<PyDict>() {
if let Ok(default_dict) = .cast::<PyDict>() {
for (key, value) in default_dict.iter() {
if .contains(&key)? {
result.set_item(key.clone(), .get_item(&key)?)?;
@@ -251,6 +252,8 @@ impl 缠论配置Py {
Ok(result.into())
}
/// 比较当前配置与另一个配置的差异
#[allow(clippy::type_complexity)]
fn (
&self,
py: Python<'_>,
@@ -289,7 +292,10 @@ impl 缠论配置Py {
Ok(Self { fields })
}
pub(crate) fn to_rust_config(&self, py: Python<'_>) -> PyResult<chanlun::config::> {
pub(crate) fn to_rust_config(
&self,
_py: Python<'_>,
) -> PyResult<chanlun::config::> {
dict_to_rust_config(&self.fields)
}
@@ -331,11 +337,104 @@ fn dict_to_rust_config(
let mut value: serde_json::Value = serde_json::from_str(&json_str)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("配置转换失败: {e}")))?;
coerce_strings_to_numbers(&mut value);
serde_json::from_value(value)
// 获取默认配置的 JSON 表示作为 schema
let default_config = chanlun::config::::default();
let default_json = serde_json::to_value(&default_config)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("配置转换失败: {e}")))?;
// 用默认值做基准,只合并类型匹配的字段
let mut merged = default_json.clone();
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}");
}
}
}
}
}
serde_json::from_value(merged)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("配置转换失败: {e}")))
})
}
/// 字符串字段的有效值集合
fn valid_string_values(field: &str) -> Option<&'static [&'static str]> {
match field {
"指标计算方式" => Some(&[
"",
"",
"",
"",
"高低均值",
"高低收均值",
"开高低收均值",
]),
"买卖点_指标模式" => Some(&["任意", "配置", "全量", "相对"]),
"线段内部背驰_模式" => Some(&["任意", "配置", "全量", "相对"]),
_ => None,
}
}
/// 验证单个字段的值是否与默认值类型兼容
fn validate_field(
key: &str,
input: &serde_json::Value,
default: &serde_json::Value,
) -> Result<(), String> {
use serde_json::Value;
// 输入为 null → 跳过(保留默认)
if input.is_null() {
return Err("值为 null".into());
}
// 字符串字段:检查有效值白名单
if default.is_string() && input.is_string() {
if let Some(valid) = valid_string_values(key) {
let s = input.as_str().unwrap();
if !valid.contains(&s) {
return Err(format!("\"{s}\" 不在有效值 {valid:?}"));
}
}
return Ok(());
}
// 类型匹配:直接通过
match (default, input) {
(Value::Bool(_), Value::Bool(_)) => return Ok(()),
(Value::Number(_), Value::Number(_)) => return Ok(()),
(Value::String(_), Value::String(_)) => return Ok(()),
_ => {}
}
// 类型不匹配
let type_name = match input {
Value::Bool(_) => "布尔",
Value::Number(_) => "数值",
Value::String(_) => "字符串",
Value::Array(_) => "数组",
Value::Object(_) => "字典",
Value::Null => "null",
};
let expected = match default {
Value::Bool(_) => "布尔",
Value::Number(_) => "数值",
Value::String(_) => "字符串",
_ => "其他",
};
Err(format!("类型不匹配(需要 {expected},收到 {type_name}"))
}
/// 递归遍历 JSON Value,将数字/布尔字符串转为对应类型。
fn coerce_strings_to_numbers(value: &mut serde_json::Value) {
match value {
@@ -355,10 +454,10 @@ fn coerce_strings_to_numbers(value: &mut serde_json::Value) {
if let Ok(n) = cloned.parse::<i64>() {
*value = serde_json::Value::Number(serde_json::Number::from(n));
} else if let Ok(n) = cloned.parse::<f64>() {
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);
@@ -397,10 +496,10 @@ fn coerce_py_value(value: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
if let Ok(n) = lower.parse::<i64>() {
return Ok(n.into_pyobject(py)?.into_any().unbind());
}
if let Ok(n) = lower.parse::<f64>() {
if n.is_finite() {
return Ok(n.into_pyobject(py)?.into_any().unbind());
}
if let Ok(n) = lower.parse::<f64>()
&& n.is_finite()
{
return Ok(n.into_pyobject(py)?.into_any().unbind());
}
Ok(value.clone().unbind())
+410 -7
View File
@@ -24,6 +24,7 @@
use pyo3::prelude::*;
use pyo3::types::PyType;
use std::sync::Arc;
// ========== 平滑异同移动平均线 ==========
@@ -43,7 +44,11 @@ use pyo3::types::PyType;
/// 增量计算(前一个MACD, 当前收盘价, 当前时间) -> 平滑异同移动平均线
/// — 基于前一根的 EMA 状态增量更新,用于流式计算
/// 增量计算_K线(前一个MACD, 当前K线, 计算方式) -> 平滑异同移动平均线
#[pyclass(name = "平滑异同移动平均线")]
#[pyclass(
name = "平滑异同移动平均线",
module = "chanlun._chanlun",
from_py_object
)]
#[derive(Clone)]
pub struct 线Py {
pub(crate) inner: chanlun::indicators::线,
@@ -124,6 +129,7 @@ impl 平滑异同移动平均线Py {
#[classmethod]
#[pyo3(signature = (初始收盘价, 初始时间, 快线周期 = None, 慢线周期 = None, 信号周期 = None))]
/// 首次计算MACD指标(没有历史数据时使用)
fn (
_cls: &Bound<'_, PyType>,
: f64,
@@ -144,6 +150,7 @@ impl 平滑异同移动平均线Py {
#[classmethod]
#[pyo3(signature = (k线, 计算方式, 快线周期 = None, 慢线周期 = None, 信号周期 = None))]
/// :param k线: 原始K线
fn _K线(
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
@@ -165,6 +172,7 @@ impl 平滑异同移动平均线Py {
}
#[classmethod]
/// 基于前一个MACD指标增量计算当前MACD指标
fn (
_cls: &Bound<'_, PyType>,
MACD: &Bound<'_, 线Py>,
@@ -180,6 +188,7 @@ impl 平滑异同移动平均线Py {
}
#[classmethod]
/// :param 前一个MACD: 前一个MACD指标对象
fn _K线(
_cls: &Bound<'_, PyType>,
MACD: &Bound<'_, 线Py>,
@@ -214,7 +223,7 @@ impl 平滑异同移动平均线Py {
/// 首次计算_K线(k线, 计算方式, ...)
/// 增量计算(前一个RSI, 当前收盘价, 当前时间)
/// 增量计算_K线(前一个RSI, 当前K线, 计算方式)
#[pyclass(name = "相对强弱指数")]
#[pyclass(name = "相对强弱指数", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub(crate) inner: chanlun::indicators::,
@@ -294,6 +303,7 @@ impl 相对强弱指数Py {
#[classmethod]
#[pyo3(signature = (初始收盘价, 初始时间, 周期 = None, 超买阈值 = None, 超卖阈值 = None, RSI_SMA周期 = None))]
/// 首次计算RSI(没有足够历史数据时使用)
fn (
_cls: &Bound<'_, PyType>,
: f64,
@@ -317,6 +327,7 @@ impl 相对强弱指数Py {
#[classmethod]
#[pyo3(signature = (k线, 计算方式, 周期 = None, 超买阈值 = None, 超卖阈值 = None, RSI_SMA周期 = None))]
/// :param k线: 原始K线
fn _K线(
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
@@ -340,6 +351,7 @@ impl 相对强弱指数Py {
}
#[classmethod]
/// 基于前一个RSI指标增量计算当前RSI
fn (
_cls: &Bound<'_, PyType>,
RSI: &Bound<'_, Py>,
@@ -356,6 +368,7 @@ impl 相对强弱指数Py {
}
#[classmethod]
/// :param 前一个RSI: 前一个RSI指标对象
fn _K线(
_cls: &Bound<'_, PyType>,
RSI: &Bound<'_, Py>,
@@ -390,7 +403,7 @@ impl 相对强弱指数Py {
/// 首次计算_K线(k线, 计算方式, ...)
/// 增量计算(前一个KDJ, 最高价, 最低价, 收盘价, 时间)
/// 增量计算_K线(前一个KDJ, 当前K线, 计算方式)
#[pyclass(name = "随机指标")]
#[pyclass(name = "随机指标", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub(crate) inner: chanlun::indicators::,
@@ -488,6 +501,7 @@ impl 随机指标Py {
#[classmethod]
#[pyo3(signature = (初始最高价, 初始最低价, 初始收盘价, 初始时间, N = None, M1 = None, M2 = None, 超买阈值 = None, 超卖阈值 = None))]
/// 首次计算KDJ(无历史数据时)
fn (
_cls: &Bound<'_, PyType>,
: f64,
@@ -517,6 +531,7 @@ impl 随机指标Py {
#[classmethod]
#[pyo3(signature = (k线, _计算方式, RSV周期 = None, K值平滑周期 = None, D值平滑周期 = None, 超买阈值 = None, 超卖阈值 = None))]
/// :param k线: 原始K线
fn _K线(
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
@@ -546,6 +561,7 @@ impl 随机指标Py {
}
#[classmethod]
/// 基于前一个KDJ对象和当前三价,增量计算当前KDJ值
fn (
_cls: &Bound<'_, PyType>,
KDJ: &Bound<'_, Py>,
@@ -566,6 +582,7 @@ impl 随机指标Py {
}
#[classmethod]
/// :param 前一个KDJ: 前一个KDJ指标对象
fn _K线(
_cls: &Bound<'_, PyType>,
KDJ: &Bound<'_, Py>,
@@ -587,20 +604,264 @@ 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<Self> {
let = K线取值(k线, )?;
let = (k线)?;
Ok(Self {
inner: chanlun::indicators::::(, , , ),
})
}
#[classmethod]
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, PyAny>,
: &str,
) -> PyResult<Self> {
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<Py<PyAny>> {
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<Option<Py<PyAny>>> {
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<String> {
self.inner._数据.keys().cloned().collect()
}
fn __getitem__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
if self.() {
match self.inner.() {
Some(v) => _to_py(v, py),
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyKeyError::new_err(format!(
"指标 '{}' 不存在",
)))
}
}
fn __getattr__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
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线提取数值。
/// 计算方式: "收盘价" / "开盘价" / "高" / "低" / "均值" 等
#[pyclass(name = "指标")]
/// :meth:`K线取值` — 根据计算方式从K线提取数值
/// (计算方式: "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值")
#[pyclass(name = "指标", module = "chanlun._chanlun")]
pub struct Py;
#[pymethods]
impl Py {
#[classmethod]
/// 根据计算方式从K线中取值
fn K线取值(
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
@@ -610,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<Py<crate::kline_py::K线Py>>,
period: i64,
: &str,
py: Python<'_>,
) -> PyResult<f64> {
if K序列.is_empty() {
return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空"));
}
let n = K序列.len();
// 提取所有K线值(一次性 borrow)
let values: Vec<f64> = 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<Py<crate::kline_py::K线Py>>,
period: i64,
: &str,
: Option<f64>,
py: Python<'_>,
) -> PyResult<f64> {
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<Py<crate::kline_py::K线Py>>,
: &Bound<'_, crate::config_py::Py>,
py: Python<'_>,
) -> PyResult<()> {
let config = .borrow().to_rust_config(py)?;
// 全序列包含 当前K线 在末尾;Rust 计算并挂载 的 现有序列 不含当前K线
let : Vec<Arc<chanlun::kline::bar::K线>> = 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<f64> {
@@ -647,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(())
}
+234 -147
View File
@@ -23,12 +23,17 @@
*/
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyType};
use pyo3::types::{PyBytes, PyDict, PyType};
use std::collections::HashMap;
use std::rc::Rc;
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线 ==========
@@ -52,9 +57,9 @@ use crate::types_py::相对方向Py;
/// 获取MACD(K线序列, 计算方式, 快线周期?, 慢线周期?, 信号周期?) -> list[平滑异同移动平均线]
/// — 对整个K线序列批量计算 MACD
/// 截取(序列, 起点K线, 终点K线) -> list — 按时间戳截取K线区间
#[pyclass(name = "K线", unsendable)]
#[pyclass(name = "K线", module = "chanlun._chanlun")]
pub struct K线Py {
pub(crate) inner: Rc<chanlun::kline::bar::K线>,
pub(crate) inner: Arc<chanlun::kline::bar::K线>,
}
#[pymethods]
@@ -73,7 +78,7 @@ impl K线Py {
: f64,
) -> Self {
Self {
inner: Rc::new(chanlun::kline::bar::K线 {
inner: Arc::new(chanlun::kline::bar::K线 {
: .to_string(),
,
,
@@ -83,9 +88,7 @@ impl K线Py {
,
,
,
macd: None,
rsi: None,
kdj: None,
: RwLock::new(chanlun::indicators::::new()),
}),
}
}
@@ -94,112 +97,116 @@ impl K线Py {
fn (&self) -> String {
self.inner..clone()
}
#[setter]
fn set_标识(&mut self, v: String) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[setter]
fn set_序号(&mut self, v: i64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[setter]
fn set_周期(&mut self, v: i64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[setter]
fn set_时间戳(&mut self, v: i64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_高(&mut self, v: f64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_低(&mut self, v: f64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_开盘价(&mut self, v: f64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_收盘价(&mut self, v: f64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_成交量(&mut self, v: f64) {
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
fn (&self) -> Py {
Py {
inner: self.inner.(),
}
/// :return: 相对方向.向上(开盘<收盘)或 相对方向.向下(开盘>收盘)
fn (&self, py: Python<'_>) -> Py<Py> {
crate::types_py::(py, self.inner.())
}
#[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 兼容 — 返回所有字段构成的字典
#[getter]
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py);
dict.set_item("标识", self.())?;
dict.set_item("序号", self.())?;
dict.set_item("周期", self.())?;
dict.set_item("时间戳", self.())?;
dict.set_item("", self.())?;
dict.set_item("", self.())?;
dict.set_item("开盘价", self.())?;
dict.set_item("收盘价", self.())?;
dict.set_item("成交量", self.())?;
dict.set_item("方向", self.(py))?;
if let Some(v) = self.macd() {
dict.set_item("macd", v)?;
}
if let Some(v) = self.rsi() {
dict.set_item("rsi", v)?;
}
if let Some(v) = self.kdj() {
dict.set_item("kdj", v)?;
}
dict.set_item("指标", self.())?;
Ok(dict.into())
}
fn __str__(&self) -> String {
@@ -216,17 +223,18 @@ impl K线Py {
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
Arc::as_ptr(&self.inner) as u64
}
#[classmethod]
#[pyo3(signature = (标识, 时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量, 序号 = None, 周期 = None))]
/// 快捷构造普通K线
fn K(
_cls: &Bound<'_, PyType>,
: &str,
@@ -240,7 +248,7 @@ impl K线Py {
: Option<i64>,
) -> Self {
Self {
inner: Rc::new(chanlun::kline::bar::K线::K(
inner: Arc::new(chanlun::kline::bar::K线::K(
,
,
,
@@ -255,6 +263,7 @@ impl K线Py {
}
#[classmethod]
/// 将K线序列保存为二进制DAT文件
fn DAT文件(
_cls: &Bound<'_, PyType>,
: &str,
@@ -268,6 +277,7 @@ impl K线Py {
}
#[classmethod]
/// 从大端字节序二进制数据反序列化K线(兼容.dat/.nb文件格式)
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyBytes>,
@@ -276,12 +286,13 @@ impl K线Py {
) -> Option<Self> {
chanlun::kline::bar::K线::(.as_bytes(), , ).map(|inner| {
Self {
inner: Rc::new(inner),
inner: Arc::new(inner),
}
})
}
#[classmethod]
/// 计算指定K线区间的MACD柱面积
fn MACD(
_cls: &Bound<'_, PyType>,
k线序列: Vec<Py<Self>>,
@@ -295,27 +306,28 @@ impl K线Py {
}
#[staticmethod]
/// 按起止K线截取K线子序列
fn (
: Vec<Py<Self>>,
: &Bound<'_, Self>,
: &Bound<'_, Self>,
py: Python<'_>,
) -> PyResult<Vec<Py<Self>>> {
let start_ptr = Rc::as_ptr(&.borrow().inner);
let end_ptr = Rc::as_ptr(&.borrow().inner);
let start_ptr = Arc::as_ptr(&.borrow().inner);
let end_ptr = Arc::as_ptr(&.borrow().inner);
let start_ts = .borrow().inner.;
let end_ts = .borrow().inner.;
let start_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == start_ptr
Arc::as_ptr(&k.borrow(py).inner) == start_ptr
|| k.borrow(py).inner. == start_ts
})
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("始 不在序列中"))?;
let end_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == end_ptr || k.borrow(py).inner. == end_ts
Arc::as_ptr(&k.borrow(py).inner) == end_ptr || k.borrow(py).inner. == end_ts
})
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("终 不在序列中"))?;
if start_idx > end_idx {
@@ -348,26 +360,75 @@ impl K线Py {
/// 分析(缠K序列, 配置, 可以逆序包含?, 忽视顺序包含?, 可以逆序包含新?) -> (str, 分型|None)
/// — 分析分型形成结果
/// 截取(序列, 起点分型, 终点分型) -> list — 截取分型间的缠K子序列
#[pyclass(name = "缠论K线", unsendable)]
#[pyclass(name = "缠论K线", module = "chanlun._chanlun", from_py_object)]
pub struct K线Py {
pub(crate) inner: std::rc::Rc<chanlun::kline::chan_kline::K线>,
bsp_set: std::cell::RefCell<Option<Py<pyo3::types::PySet>>>,
pub(crate) inner: std::sync::Arc<chanlun::kline::chan_kline::K线>,
}
impl K线Py {
pub(crate) fn from_rc(inner: std::rc::Rc<chanlun::kline::chan_kline::K线>) -> Self {
Self {
inner,
bsp_set: std::cell::RefCell::new(None),
}
pub(crate) fn from_rc(inner: std::sync::Arc<chanlun::kline::chan_kline::K线>) -> Self {
Self { inner }
}
}
/// 对象标识缓存:Arc 地址 → 规范 Python 对象
/// 确保同一底层 Arc 指针在 Python 侧始终映射到同一 PyObject
/// 使用全局 static 而非 thread_local!,保证跨线程对象标识和买卖点信息一致性
static BAR_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<K线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static KLINE_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<K线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
/// 买卖点信息缓存 — 按 Arc 指针全局共享,确保所有 wrapper 看到同一 PySet
static BSP_CACHE: std::sync::LazyLock<RwLock<HashMap<usize, Py<pyo3::types::PySet>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
/// 将 Rc<K线> 转为 Py<K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
pub(crate) fn bar_to_py(
py: Python<'_>,
inner: std::sync::Arc<chanlun::kline::bar::K线>,
) -> Py<K线Py> {
let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = BAR_IDENTITY
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached;
}
let obj = Py::new(py, K线Py { inner }).unwrap();
BAR_IDENTITY.write().unwrap().insert(key, obj.clone_ref(py));
obj
}
/// 将 Rc<缠论K线> 转为 Py<缠论K线Py>,确保同一 Rc 地址总是返回同一 Python 对象
pub(crate) fn chan_kline_to_py(
py: Python<'_>,
inner: std::sync::Arc<chanlun::kline::chan_kline::K线>,
) -> Py<K线Py> {
let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = KLINE_IDENTITY
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached;
}
let obj = Py::new(py, K线Py::from_rc(inner)).unwrap();
KLINE_IDENTITY
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj
}
impl Clone for K线Py {
fn clone(&self) -> Self {
Self {
inner: std::rc::Rc::clone(&self.inner),
bsp_set: std::cell::RefCell::new(None),
inner: std::sync::Arc::clone(&self.inner),
}
}
}
@@ -381,36 +442,36 @@ impl 缠论K线Py {
#[getter]
fn (&self) -> i64 {
self.inner.
self.inner..load(Ordering::Relaxed)
}
#[getter]
fn (&self) -> i64 {
self.inner.
self.inner..load(Ordering::Relaxed)
}
#[getter]
fn (&self) -> f64 {
self.inner.
self.inner..get()
}
#[getter]
fn (&self) -> f64 {
self.inner.
self.inner..get()
}
#[getter]
fn (&self) -> Py {
Py {
inner: self.inner.,
}
fn (&self, py: Python<'_>) -> Py<Py> {
crate::types_py::(py, *self.inner..read().unwrap())
}
#[getter]
fn (&self) -> Option<crate::types_py::Py> {
fn (&self, py: Python<'_>) -> Option<Py<crate::types_py::Py>> {
self.inner
.
.map(|f| crate::types_py::Py { inner: f })
.read()
.unwrap()
.map(|f| crate::types_py::(py, f))
}
#[getter]
@@ -425,7 +486,7 @@ impl 缠论K线Py {
#[getter]
fn (&self) -> f64 {
self.inner.
self.inner..get()
}
#[getter]
@@ -435,14 +496,36 @@ impl 缠论K线Py {
#[getter]
fn (&self) -> i64 {
self.inner.
self.inner..load(Ordering::Relaxed)
}
#[getter]
fn K线(&self) -> K线Py {
K线Py {
inner: self.inner.K线.clone(),
fn K线(&self, py: Python<'_>) -> Py<K线Py> {
bar_to_py(py, self.inner.K线.read().unwrap().clone())
}
/// pandas 兼容 — 返回所有字段构成的字典
#[getter]
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py);
dict.set_item("序号", self.())?;
dict.set_item("时间戳", self.())?;
dict.set_item("", self.())?;
dict.set_item("", self.())?;
dict.set_item("方向", self.(py))?;
dict.set_item("周期", self.())?;
dict.set_item("标识", self.())?;
dict.set_item("分型特征值", self.())?;
dict.set_item("原始起始序号", self.())?;
dict.set_item("原始结束序号", self.())?;
dict.set_item("与MACD柱子匹配", self.MACD柱子匹配())?;
dict.set_item("与RSI匹配", self.RSI匹配())?;
dict.set_item("与KDJ匹配", self.KDJ匹配())?;
if let Some(v) = self.(py) {
dict.set_item("分型", v)?;
}
Ok(dict.into())
}
fn __str__(&self) -> String {
@@ -455,66 +538,86 @@ impl 缠论K线Py {
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
Arc::as_ptr(&self.inner) as u64
}
#[getter]
/// 创建当前缠K的浅拷贝副本
fn (&self, py: Python<'_>) -> Self {
let mut mirror = Self {
inner: std::rc::Rc::new(self.inner.()),
bsp_set: std::cell::RefCell::new(None),
let mirror = Self {
inner: std::sync::Arc::new(self.inner.()),
};
if let Some(ref src_set) = *self.bsp_set.borrow() {
if let Ok(new_set) = pyo3::types::PySet::empty(py) {
for item in src_set.bind(py).iter() {
let _ = new_set.add(item);
}
mirror.bsp_set = std::cell::RefCell::new(Some(new_set.into()));
// 复制买卖点信息到镜像
let src_key = Arc::as_ptr(&self.inner) as usize;
let dst_key = Arc::as_ptr(&mirror.inner) as usize;
let cached_src = BSP_CACHE
.read()
.unwrap()
.get(&src_key)
.map(|p| p.clone_ref(py));
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<pyo3::types::PySet> = new_set.into();
BSP_CACHE.write().unwrap().insert(dst_key, py_set);
}
mirror
}
#[getter]
/// :return: 底分型时MACD柱<0,顶分型时MACD柱>0
fn MACD柱子匹配(&self) -> bool {
self.inner.MACD柱子匹配()
}
#[getter]
/// :return: 底分型时RSI < RSI_SMA,顶分型时RSI > RSI_SMA
fn RSI匹配(&self) -> bool {
self.inner.RSI匹配()
}
#[getter]
/// :return: 底分型时K<D,顶分型时K>D
fn KDJ匹配(&self) -> bool {
self.inner.KDJ匹配()
}
#[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
if self.bsp_set.borrow().is_none() {
let set = pyo3::types::PySet::empty(py)?;
for s in self.inner..borrow().iter() {
set.add(s.clone())?;
}
*self.bsp_set.borrow_mut() = Some(set.into());
let key = Arc::as_ptr(&self.inner) as usize;
// 检查全局缓存
let cached = BSP_CACHE.read().unwrap().get(&key).map(|p| p.clone_ref(py));
if let Some(set) = cached {
return Ok(set.into_any());
}
Ok(self
.bsp_set
.borrow()
.as_ref()
// 创建新的 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());
Ok(BSP_CACHE
.read()
.unwrap()
.get(&key)
.unwrap()
.clone_ref(py)
.into_any())
}
#[classmethod]
/// 在基线序列中找到与k线时间戳对齐的时间戳
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<Self>>,
@@ -523,12 +626,14 @@ impl 缠论K线Py {
) -> i64 {
let rc_list: Vec<_> = 线
.iter()
.map(|k| std::rc::Rc::clone(&k.bind(py).borrow().inner))
.map(|k| std::sync::Arc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::kline::chan_kline::K线::(&rc_list, &k线.borrow().inner)
}
#[classmethod]
#[pyo3(signature = (时间戳, 高, 低, 方向, 结构, 原始序号, 普k, 之前 = None))]
/// 创建新的缠论K线
fn K(
_cls: &Bound<'_, PyType>,
: i64,
@@ -539,7 +644,8 @@ impl 缠论K线Py {
: i64,
k: &Bound<'_, K线Py>,
: Option<&Bound<'_, Self>>,
) -> Self {
py: Python<'_>,
) -> Py<Self> {
let prev_ref = .map(|prev| prev.borrow());
let prev_inner = prev_ref.as_ref().map(|r| r.inner.as_ref());
let inner = chanlun::kline::chan_kline::K线::K(
@@ -552,32 +658,11 @@ impl 缠论K线Py {
k.borrow().inner.clone(),
prev_inner,
);
Self::from_rc(std::rc::Rc::new(inner))
}
#[classmethod]
fn (
_cls: &Bound<'_, PyType>,
K: Option<&Bound<'_, Self>>,
K: &Bound<'_, Self>,
K: &Bound<'_, K线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<(Option<Self>, Option<String>)> {
let mut 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,
&mut ck_inner,
&K.borrow().inner,
&config,
);
Ok((result.map(Self::from_rc), mode))
chan_kline_to_py(py, std::sync::Arc::new(inner))
}
#[classmethod]
/// 分析K线,执行指标计算+包含处理+分型判定
fn (
_cls: &Bound<'_, PyType>,
K线: &Bound<'_, K线Py>,
@@ -591,45 +676,47 @@ impl 缠论K线Py {
let mut ck_seq: Vec<_> = K序列
.iter()
.map(|k| std::rc::Rc::clone(&k.bind(py).borrow().inner))
.map(|k| std::sync::Arc::clone(&k.bind(py).borrow().inner))
.collect();
let mut bar_seq: Vec<_> = K序列
.iter()
.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]
/// :param 序列: 缠K序列
fn (
: Vec<Py<Self>>,
: &Bound<'_, Self>,
: &Bound<'_, Self>,
py: Python<'_>,
) -> PyResult<Vec<Py<Self>>> {
let start_ptr = Rc::as_ptr(&.borrow().inner);
let end_ptr = Rc::as_ptr(&.borrow().inner);
let start_ts = .borrow().inner.;
let end_ts = .borrow().inner.;
let start_ptr = Arc::as_ptr(&.borrow().inner);
let end_ptr = Arc::as_ptr(&.borrow().inner);
let start_ts = .borrow().inner..load(Ordering::Relaxed);
let end_ts = .borrow().inner..load(Ordering::Relaxed);
let start_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == start_ptr
|| k.borrow(py).inner. == start_ts
Arc::as_ptr(&k.borrow(py).inner) == start_ptr
|| k.borrow(py).inner..load(Ordering::Relaxed) == start_ts
})
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("始 不在序列中"))?;
let end_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == end_ptr || k.borrow(py).inner. == end_ts
Arc::as_ptr(&k.borrow(py).inner) == end_ptr
|| k.borrow(py).inner..load(Ordering::Relaxed) == end_ts
})
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("终 不在序列中"))?;
if start_idx > end_idx {
+156 -29
View File
@@ -22,7 +22,83 @@
* SOFTWARE.
*/
#![allow(non_snake_case, clippy::too_many_arguments)]
use pyo3::prelude::*;
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<tracing_subscriber::EnvFilter, tracing_subscriber::Registry>;
static : OnceLock<Mutex<>> = 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<u8> {
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;
@@ -32,9 +108,59 @@ mod kline_py;
mod structure_py;
mod types_py;
/// 分型模式 — True 时使用构造时缓存值,False 时从 中 缠K 实时读取
#[pyfunction]
fn get_分型模式() -> bool {
chanlun::structure::fractal_obj::.load(Ordering::Relaxed)
}
/// 设置 分型模式
#[pyfunction]
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: 配置
@@ -55,39 +181,40 @@ fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
#[cfg(test)]
mod tests {
use crate::*;
use pyo3::prelude::*;
#[test]
fn test_rc_pointer_across_getters() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|py| {
fn test_分型模式_get_set() {
// 手动初始化 Python 解释器(cargo test 环境下 auto-initialize 不一定生效)
unsafe {
if pyo3::ffi::Py_IsInitialized() == 0 {
pyo3::ffi::Py_Initialize();
}
}
pyo3::Python::try_attach(|py| {
let module = PyModule::new(py, "test_module").unwrap();
module.add_class::<business_py::Py>().unwrap();
module.add_class::<business_py::Py>().unwrap();
module.add_class::<business_py::Py>().unwrap();
module.add_class::<kline_py::K线Py>().unwrap();
module.add_class::<kline_py::K线Py>().unwrap();
module.add_class::<structure_py::Py>().unwrap();
module.add_class::<structure_py::线Py>().unwrap();
module.add_class::<config_py::Py>().unwrap();
module
.add_function(wrap_pyfunction!(get_分型模式, &module).unwrap())
.unwrap();
module
.add_function(wrap_pyfunction!(set_分型模式, &module).unwrap())
.unwrap();
let config = config_py::Py::from_rust_config(&Default::default()).unwrap();
let obs = business_py::Py::new_impl("btcusd".into(), 300, config, py).unwrap();
// 默认 true
let getter = module.getattr("get_分型模式").unwrap();
let result: bool = getter.call0().unwrap().extract().unwrap();
assert!(result, "分型模式 默认应为 True");
// Feed one K line
let kline = kline_py::K线Py::new_impl(
"btcusd".into(),
1000,
100.0,
105.0,
99.0,
103.0,
1000.0,
0,
300,
);
let kline_ref = kline.into_ref(py);
// ... this is too complex
});
// 设置为 false
let setter = module.getattr("set_分型模式").unwrap();
setter.call1((false,)).unwrap();
let result: bool = getter.call0().unwrap().extract().unwrap();
assert!(!result, "分型模式 应为 False");
// 恢复 true
setter.call1((true,)).unwrap();
let result: bool = getter.call0().unwrap().extract().unwrap();
assert!(result, "分型模式 应为 True");
})
.expect("Python 解释器初始化后 attach 仍失败");
}
}
File diff suppressed because it is too large Load Diff
+288 -74
View File
@@ -22,8 +22,78 @@
* SOFTWARE.
*/
use std::collections::HashMap;
use std::sync::Mutex;
use pyo3::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::types::PyType;
use pyo3::types::{PyBool, PyDict, PyType};
// ========== 单例缓存 ==========
static _单例缓存: Mutex<Option<HashMap<u8, Py<Py>>>> = Mutex::new(None);
pub fn (
py: Python<'_>,
inner: chanlun::types::,
) -> Py<Py> {
let mut guard = _单例缓存.lock().unwrap();
if let Some(ref map) = *guard {
return map[&(inner as u8)].clone_ref(py);
}
// 首次访问时从类属性加载单例
let module = py.import("chanlun._chanlun").unwrap();
let class = module.getattr("分型结构").unwrap();
let mut map = HashMap::new();
for (name, variant) in &[
("", chanlun::types::::),
("", chanlun::types::::),
("", chanlun::types::::),
("", chanlun::types::::),
("", chanlun::types::::),
] {
let instance: Py<Py> = class.getattr(*name).unwrap().extract().unwrap();
map.insert(*variant as u8, instance);
}
let result = map[&(inner as u8)].clone_ref(py);
*guard = Some(map);
result
}
static _单例缓存: Mutex<Option<HashMap<u8, Py<Py>>>> = Mutex::new(None);
pub fn (
py: Python<'_>,
inner: chanlun::types::,
) -> Py<Py> {
let mut guard = _单例缓存.lock().unwrap();
if let Some(ref map) = *guard {
return map[&(inner as u8)].clone_ref(py);
}
// 首次访问时从类属性加载单例
let module = py.import("chanlun._chanlun").unwrap();
let class = module.getattr("相对方向").unwrap();
let mut map = HashMap::new();
for (name, variant) in &[
("向上", chanlun::types::::),
("向下", chanlun::types::::),
("向上缺口", chanlun::types::::),
("向下缺口", chanlun::types::::),
("衔接向上", chanlun::types::::),
("衔接向下", chanlun::types::::),
("", chanlun::types::::),
("", chanlun::types::::),
("", chanlun::types::::),
] {
let instance: Py<Py> = class.getattr(*name).unwrap().extract().unwrap();
map.insert(*variant as u8, instance);
}
let result = map[&(inner as u8)].clone_ref(py);
*guard = Some(map);
result
}
// ========== 买卖点类型 ==========
@@ -37,7 +107,7 @@ use pyo3::types::PyType;
/// 属性:
/// 是买点: bool — 是否为买入类型
/// 是卖点: bool — 是否为卖出类型
#[pyclass(name = "买卖点类型")]
#[pyclass(name = "买卖点类型", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub inner: chanlun::types::,
@@ -53,14 +123,23 @@ impl 买卖点类型Py {
self.inner.to_string()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(s) = other.extract::<String>() {
return self.inner.to_string() == s;
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
// 比较:先尝试字符串(Python Enum(str)),再同类型,再 name 属性
let eq = if let Ok(s) = other.extract::<String>() {
self.inner.to_string() == s
} else if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
self.inner == other.inner
} else if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
self.inner.to_string() == py_name
} else {
return Ok(py.NotImplemented());
};
match op {
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()),
}
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return self.inner == other.inner;
}
false
}
fn __hash__(&self) -> u64 {
@@ -71,14 +150,25 @@ impl 买卖点类型Py {
}
#[getter]
/// 判断是否为买入类型(名称中含"买"字)
fn (&self) -> bool {
self.inner.()
}
#[getter]
/// 判断是否为卖出类型(名称中含"卖"字)
fn (&self) -> bool {
self.inner.()
}
/// pickle 支持 — 通过 getattr(类, 名称) 重建实例
fn __reduce__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let getattr = py.import("builtins")?.getattr("getattr")?;
let module = py.import("chanlun._chanlun")?;
let cls = module.getattr("买卖点类型")?;
let name = self.__str__();
Ok((getattr, (cls, name)).into_pyobject(py)?.unbind().into())
}
}
// ========== 相对方向 ==========
@@ -101,7 +191,7 @@ impl 买卖点类型Py {
/// 方法:
/// 翻转() -> 相对方向 — 返回方向的对立面(向上↔向下, 缺口↔反向缺口)
/// 分析(前高, 前低, 后高, 后低) -> 相对方向 (classmethod) — 根据价格区间判断方向
#[pyclass(name = "相对方向")]
#[pyclass(name = "相对方向", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub inner: chanlun::types::,
@@ -117,11 +207,44 @@ impl 相对方向Py {
format!("{}", self.inner)
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
// 同类型比较
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return self.inner == other.inner;
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(),
});
}
false
// 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.相对方向)
if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
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 {
@@ -129,32 +252,48 @@ impl 相对方向Py {
}
/// 返回方向的对立面(向上↔向下, 缺口↔反向缺口, 衔接↔反向衔接)。
fn (&self) -> Self {
Self {
inner: self.inner.(),
}
fn (&self, py: Python<'_>) -> Py<Self> {
(py, self.inner.())
}
/// 判断是否为向上方向(向上/向上缺口/衔接向上)
fn (&self) -> bool {
self.inner.()
}
/// 判断是否为向下方向(向下/向下缺口/衔接向下)
fn (&self) -> bool {
self.inner.()
}
/// 判断是否为包含关系(顺/逆/同)
fn (&self) -> bool {
self.inner.()
}
/// 判断是否有缺口(向下缺口/向上缺口)
fn (&self) -> bool {
self.inner.()
}
/// 判断是否为首尾衔接(衔接向下/衔接向上)
fn (&self) -> bool {
self.inner.()
}
/// pickle 支持 — 通过 getattr(类, 名称) 重建实例
fn __reduce__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let getattr = py.import("builtins")?.getattr("getattr")?;
let module = py.import("chanlun._chanlun")?;
let cls = module.getattr("相对方向")?;
let full_name = self.__str__();
let name = full_name
.rsplit_once('.')
.map(|(_, v)| v)
.unwrap_or(&full_name);
Ok((getattr, (cls, name)).into_pyobject(py)?.unbind().into())
}
/// 根据前后价格区间的OHLC值分析方向关系。
///
/// 参数:
@@ -167,10 +306,11 @@ impl 相对方向Py {
#[classmethod]
fn (
_cls: &Bound<'_, PyType>, : f64, : f64, : f64, : f64
) -> Self {
Self {
inner: chanlun::types::::(, , , ),
}
) -> Py<Self> {
(
_cls.py(),
chanlun::types::::(, , , ),
)
}
}
@@ -188,7 +328,7 @@ impl 相对方向Py {
/// 方法:
/// 分析(左, 中, 右, 可以逆序包含?, 忽视顺序包含?) -> 分型结构|None (classmethod)
/// — 根据三根K线的高低价分析分型结构
#[pyclass(name = "分型结构")]
#[pyclass(name = "分型结构", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub inner: chanlun::types::,
@@ -204,17 +344,58 @@ impl 分型结构Py {
self.inner.to_string()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
// 同类型比较
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return self.inner == other.inner;
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(),
});
}
false
// 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.分型结构)
if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
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 {
self.inner as u64
}
/// pickle 支持 — 通过 getattr(类, 名称) 重建实例
fn __reduce__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let getattr = py.import("builtins")?.getattr("getattr")?;
let module = py.import("chanlun._chanlun")?;
let cls = module.getattr("分型结构")?;
let name = self.__str__();
Ok((getattr, (cls, name)).into_pyobject(py)?.unbind().into())
}
/// 根据左中右三根K线的高/低价分析分型结构。
///
/// 参数:
@@ -226,17 +407,15 @@ impl 分型结构Py {
/// 返回:
/// 分型结构 或 None — 无法判定时返回 None
#[classmethod]
#[pyo3(signature = (左, 中, 右, 可以逆序包含 = false, 忽视顺序包含 = false))]
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyAny>,
: &Bound<'_, PyAny>,
: &Bound<'_, PyAny>,
: Option<bool>,
: Option<bool>,
: bool,
: bool,
) -> PyResult<Option<Self>> {
let = .unwrap_or(false);
let = .unwrap_or(false);
let get_hl = |obj: &Bound<'_, PyAny>| -> PyResult<(f64, f64)> {
Ok((
obj.getattr("")?.extract::<f64>()?,
@@ -248,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 }))
}
}
@@ -297,7 +452,7 @@ impl 分型结构Py {
/// 方法:
/// 居中截取区间(起点, 终点, 比例=0.15) -> 缺口|None (classmethod)
/// — 在两个价格之间截取中间区域作为缺口
#[pyclass(name = "缺口")]
#[pyclass(name = "缺口", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub inner: chanlun::types::,
@@ -325,6 +480,46 @@ impl 缺口Py {
format!("{}", self.inner)
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Ok(py.NotImplemented());
};
let to_bool = |b: bool| PyBool::new(py, b).as_any().to_owned().unbind();
match op {
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.),
)),
}
}
fn __hash__(&self) -> u64 {
use std::hash::{Hash, Hasher};
let mut h = std::collections::hash_map::DefaultHasher::new();
self.inner..to_bits().hash(&mut h);
self.inner..to_bits().hash(&mut h);
h.finish()
}
#[getter]
#[pyo3(name = "")]
fn get_高(&self) -> f64 {
@@ -349,6 +544,16 @@ impl 缺口Py {
self.inner. = value;
}
/// pickle 支持 — 通过 cls(高, 低) 重建实例
fn __reduce__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let module = py.import("chanlun._chanlun")?;
let cls = module.getattr("缺口")?;
Ok((cls, (self.inner., self.inner.))
.into_pyobject(py)?
.unbind()
.into())
}
/// 在两个价格之间截取中间区域作为缺口。
///
/// 参数:
@@ -358,13 +563,13 @@ impl 缺口Py {
/// 返回:
/// 缺口 或 None — 起点==终点时返回 None
#[classmethod]
#[pyo3(signature = (起点, 终点, 比例 = 0.15))]
fn (
_cls: &Bound<'_, PyType>,
: f64,
: f64,
: Option<f64>,
: f64,
) -> Option<Self> {
let = .unwrap_or(0.15);
chanlun::types::::(, , ).map(|inner| Self { inner })
}
}
@@ -381,7 +586,7 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
// 买卖点类型 class attributes (singleton instances)
let py = m.py();
let bsp_class = m.getattr("买卖点类型")?;
let bsp_class = bsp_class.downcast_into::<PyType>()?;
let bsp_class = bsp_class.cast_into::<PyType>()?;
let variants: &[(&str, chanlun::types::)] = &[
("一买", chanlun::types::::),
@@ -404,13 +609,16 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
("T3B卖", chanlun::types::::T3B卖),
];
let bsp_members = PyDict::new(py);
for (name, value) in variants {
let instance = Py::new(py, Py { inner: *value })?;
bsp_class.setattr(*name, instance)?;
bsp_class.setattr(*name, instance.clone_ref(py))?;
bsp_members.set_item(*name, instance)?;
}
bsp_class.setattr("__members__", bsp_members)?;
// 相对方向 class attributes
let dir_class = m.getattr("相对方向")?.downcast_into::<PyType>()?.clone();
let dir_class = m.getattr("相对方向")?.cast_into::<PyType>()?.clone();
let dir_variants: &[(&str, chanlun::types::)] = &[
("向上", chanlun::types::::),
("向下", chanlun::types::::),
@@ -423,13 +631,16 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
("", chanlun::types::::),
];
let dir_members = PyDict::new(py);
for (name, value) in dir_variants {
let instance = Py::new(py, Py { inner: *value })?;
dir_class.setattr(*name, instance)?;
dir_class.setattr(*name, instance.clone_ref(py))?;
dir_members.set_item(*name, instance)?;
}
dir_class.setattr("__members__", dir_members)?;
// 分型结构 class attributes
let frac_class = m.getattr("分型结构")?.downcast_into::<PyType>()?.clone();
let frac_class = m.getattr("分型结构")?.cast_into::<PyType>()?.clone();
let frac_variants: &[(&str, chanlun::types::)] = &[
("", chanlun::types::::),
("", chanlun::types::::),
@@ -438,10 +649,13 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
("", chanlun::types::::),
];
let frac_members = PyDict::new(py);
for (name, value) in frac_variants {
let instance = Py::new(py, Py { inner: *value })?;
frac_class.setattr(*name, instance)?;
frac_class.setattr(*name, instance.clone_ref(py))?;
frac_members.set_item(*name, instance)?;
}
frac_class.setattr("__members__", frac_members)?;
// Register module-level functions
m.add_function(wrap_pyfunction!(, m)?)?;
File diff suppressed because it is too large Load Diff
Binary file not shown.
+25
View File
@@ -0,0 +1,25 @@
"""pyo3_test_helpers — 可复用的 PyO3 测试工具包。
提供四个核心模块:
rc_identity Rc/Arc 指针身份一致性测试 Mixin
subclass PyO3 #[pyclass(subclass)] 子类化兼容性测试 Mixin
type_shape 返回值类型形状验证工具
api_consistency 两个模块间 API 描述符类型一致性测试 Mixin
所有 Mixin 都是纯 Python不依赖 pytest unittest.TestCase 配合使用
下游项目复制此目录即可复用
"""
from .api_consistency import ApiConsistencyMixin
from .rc_identity import RcIdentityMixin
from .subclass import PyO3SubclassMixin
from .type_shape import assert_type_shape, TypeShapeAssertions
__all__ = [
"ApiConsistencyMixin",
"RcIdentityMixin",
"PyO3SubclassMixin",
"assert_type_shape",
"TypeShapeAssertions",
]
+196
View File
@@ -0,0 +1,196 @@
"""API 一致性测试 Mixin。
验证两个模块中同名类的公开成员描述符类型一致
典型用途对比 Python 参考实现 (chan.py) Rust/PyO3 移植 (chanlun) API 兼容性
用法::
class TestApi一致性(ApiConsistencyMixin, unittest.TestCase):
reference_module = mylib.ref # Python 参考实现
target_module = mylib # Rust/PyO3 移植
# 可选: 已知差异(不会报错)
known_missing_in_target = {
"SomeClass": {"old_deprecated_method"},
}
known_descriptor_diffs = {
# (class_name, member, ref_type, target_type)
}
# 可选: 成员名过滤(匹配则跳过,支持前缀用 "prefix_" 表示)
noise_filters = ["model_", "parse_", "from_orm"]
"""
import unittest
def _classify_member(cls, attr_name):
"""返回描述符类型: property / classmethod / staticmethod / regular_method / None(data)."""
# 优先检查元类字典中的描述符
for klass in type(cls).__mro__:
if attr_name in klass.__dict__:
raw = klass.__dict__[attr_name]
if isinstance(raw, property):
return "property"
elif isinstance(raw, classmethod):
return "classmethod"
elif isinstance(raw, staticmethod):
return "staticmethod"
break
try:
attr = getattr(cls, attr_name)
except Exception:
return None
if callable(attr):
return "regular_method"
return None
def _is_noise(name, filters):
for pat in filters:
if pat == name:
return True
if pat.endswith("_") and name.startswith(pat):
return True
return False
def _get_classes(mod):
"""获取模块中所有公开的 type."""
return {n: getattr(mod, n) for n in dir(mod) if not n.startswith("_") and isinstance(getattr(mod, n), type)}
class ApiConsistencyMixin:
"""API 一致性测试 Mixin。
子类必须定义:
reference_module: 参考模块 (Python 实现)
target_module: 目标模块 (Rust/PyO3 移植)
子类可选定义:
known_missing_in_target: dict[str, set[str]] 已知 target 中缺失的成员
known_descriptor_diffs: set[tuple] 已知描述符类型差异
noise_filters: list[str] 噪音成员名过滤
"""
reference_module = None
target_module = None
known_missing_in_target: dict = {}
known_descriptor_diffs: set = set()
noise_filters: list = []
@classmethod
def setUpClass(cls):
if cls.reference_module is None or cls.target_module is None:
raise unittest.SkipTest(f"{cls.__name__} 未定义 reference_module / target_module")
# ---- 描述符类型一致性 ----
def test_共有成员描述符类型一致(self):
"""同名类的同名成员,描述符类型 (property/classmethod/staticmethod/regular) 一致."""
ref_classes = _get_classes(self.reference_module)
tgt_classes = _get_classes(self.target_module)
shared = sorted(set(ref_classes) & set(tgt_classes))
failures = []
for cls_name in shared:
ref_cls = ref_classes[cls_name]
tgt_cls = tgt_classes[cls_name]
ref_members = {}
tgt_members = {}
for attr_name in sorted(dir(ref_cls)):
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
continue
cat = _classify_member(ref_cls, attr_name)
if cat:
ref_members[attr_name] = cat
for attr_name in sorted(dir(tgt_cls)):
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
continue
cat = _classify_member(tgt_cls, attr_name)
if cat:
tgt_members[attr_name] = cat
shared_members = sorted(set(ref_members) & set(tgt_members))
for member in shared_members:
ref_cat = ref_members[member]
tgt_cat = tgt_members[member]
if ref_cat != tgt_cat:
diff_key = (cls_name, member, ref_cat, tgt_cat)
if diff_key not in self.known_descriptor_diffs:
failures.append(f"{cls_name}.{member}: ref={ref_cat}, tgt={tgt_cat}")
if failures:
self.fail("描述符类型不一致:\n " + "\n ".join(failures))
# ---- 缺失成员检查 ----
def test_参考模块成员在目标模块中存在(self):
"""chan 中的关键公开成员在 chanlun 中均有对应."""
ref_classes = _get_classes(self.reference_module)
tgt_classes = _get_classes(self.target_module)
shared = sorted(set(ref_classes) & set(tgt_classes))
failures = []
for cls_name in shared:
if cls_name not in self.known_missing_in_target:
continue
ref_cls = ref_classes[cls_name]
tgt_cls = tgt_classes[cls_name]
expected_missing = self.known_missing_in_target.get(cls_name, set())
ref_members = set()
for attr_name in sorted(dir(ref_cls)):
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
continue
cat = _classify_member(ref_cls, attr_name)
if cat and attr_name not in expected_missing:
ref_members.add(attr_name)
tgt_members = set()
for attr_name in sorted(dir(tgt_cls)):
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
continue
cat = _classify_member(tgt_cls, attr_name)
if cat:
tgt_members.add(attr_name)
missing = ref_members - tgt_members - expected_missing
for member in sorted(missing):
failures.append(f"{cls_name}.{member}: ref={_classify_member(ref_cls, member)}, tgt=未导出")
if failures:
self.fail("参考模块中的成员在目标模块中缺失:\n " + "\n ".join(failures))
# ---- 方法可调用性 ----
def test_共有方法均可调用(self):
"""所有共有 regular_method 在两边都是 callable."""
ref_classes = _get_classes(self.reference_module)
tgt_classes = _get_classes(self.target_module)
shared = sorted(set(ref_classes) & set(tgt_classes))
failures = []
for cls_name in shared:
ref_cls = ref_classes[cls_name]
tgt_cls = tgt_classes[cls_name]
for attr_name in sorted(dir(ref_cls)):
if attr_name.startswith("_") or _is_noise(attr_name, self.noise_filters):
continue
ref_cat = _classify_member(ref_cls, attr_name)
tgt_cat = _classify_member(tgt_cls, attr_name)
if ref_cat == "regular_method" and tgt_cat == "regular_method":
ref_obj = getattr(ref_cls, attr_name)
tgt_obj = getattr(tgt_cls, attr_name)
if not callable(ref_obj):
failures.append(f"{cls_name}.{attr_name}: ref 不是 callable")
if not callable(tgt_obj):
failures.append(f"{cls_name}.{attr_name}: tgt 不是 callable")
if failures:
self.fail("方法不可调用:\n " + "\n ".join(failures))
+126
View File
@@ -0,0 +1,126 @@
"""Rc/Arc 指针身份一致性测试 Mixin。
验证同一个 Rust Rc<T>/Arc<T> 无论通过哪条路径到达 Python
始终返回相同的 PyObject`a is b` True
用法::
class TestMyLib(RcIdentityMixin, unittest.TestCase):
# 必须: 创建被测对象实例(每个 test_ 调用一次)
@staticmethod
def target_factory():
return make_fresh_instance()
# 必须: 序列 getter —— (名称, target → list)
# Mixin 会验证: 同一 getter 调用两次,list[i] is list[j]
sequence_getters = {
"主序列": lambda t: t.items,
"子序列": lambda t: t.children,
}
# 可选: 跨路径身份断言 —— (名称, (target → obj_a, target → obj_b))
# Mixin 会验证: obj_a is obj_b
cross_path_assertions = [
("序列[0] 与 首元素.父", lambda t: t.items[0], lambda t: t.items[0].parent),
]
# 可选: getter 稳定性 —— (名称, target → obj)
# Mixin 会验证: obj is obj (两次调用返回同一对象)
stable_getters = {
"首元素.属性": lambda t: t.items[0].attr,
}
# 可选: 序列长度检查的最小值(默认不检查,设为 >0 开启)
min_sequence_lengths = {
"主序列": 3,
"子序列": 2,
}
"""
import unittest
class RcIdentityMixin:
"""Rc/Arc 指针身份一致性测试 Mixin。
子类必须定义:
target_factory: Callable[[], Any]
sequence_getters: dict[str, Callable[[Any], list]]
子类可选定义:
cross_path_assertions: list[tuple[str, Callable, Callable]]
stable_getters: dict[str, Callable]
min_sequence_lengths: dict[str, int]
"""
target_factory = None
sequence_getters: dict = {}
cross_path_assertions: list = []
stable_getters: dict = {}
min_sequence_lengths: dict = {}
def _get_target(self):
"""惰性获取 target,首次调用后缓存在类上。避免 setUpClass MRO 冲突."""
cls = type(self)
# 每次测试重新创建——但这会太慢。用类级别缓存。
# 子类应在 setUpClass 中调用 self._get_target() 或自己设置 cls._cached_target。
if not hasattr(cls, "_cached_target"):
if cls.target_factory is None:
raise unittest.SkipTest(f"{cls.__name__} 未定义 target_factory")
cls._cached_target = cls.target_factory()
return cls._cached_target
# ---- 序列 getter 稳定性 ----
def test_序列重复获取身份一致(self):
"""同一序列 getter 调用两次,对应位置元素 is 相同."""
t = self._get_target()
for name, getter in self.sequence_getters.items():
seq1 = getter(t)
seq2 = getter(t)
self.assertEqual(len(seq1), len(seq2), f"{name}: 两次获取长度不同")
check_n = min(len(seq1), 10)
for i in range(check_n):
self.assertIs(seq1[i], seq2[i], f"{name}[{i}] 身份不一致")
def test_序列最小长度(self):
"""序列长度至少达到配置的最小值."""
t = self._get_target()
for name, getter in self.sequence_getters.items():
if name in self.min_sequence_lengths:
min_len = self.min_sequence_lengths[name]
actual = len(getter(t))
self.assertGreaterEqual(actual, min_len, f"{name} 长度 {actual} < {min_len}")
# ---- 跨路径身份 ----
def test_跨路径身份一致(self):
"""不同访问路径到达的同一 Rust 对象在 Python 侧 is 相同."""
t = self._get_target()
for i, (label, path_a, path_b) in enumerate(self.cross_path_assertions):
obj_a = path_a(t)
obj_b = path_b(t)
self.assertIsNotNone(obj_a, f"[{i}] {label}: path_a 返回 None")
self.assertIsNotNone(obj_b, f"[{i}] {label}: path_b 返回 None")
self.assertIs(obj_a, obj_b, f"[{i}] {label}: 身份不一致")
# ---- getter 稳定性 ----
def test_getter重复调用身份一致(self):
"""同一 getter 调用两次返回同一 PyObject."""
t = self._get_target()
for name, getter in self.stable_getters.items():
obj1 = getter(t)
obj2 = getter(t)
self.assertIs(obj1, obj2, f"{name}: 两次调用返回不同对象")
# ---- list.index 基于 is ----
def test_list_index_基于身份(self):
"""list.index(elem) 正常工作(依赖 __eq__ 基于 is 比较)."""
t = self._get_target()
for name, getter in self.sequence_getters.items():
seq = getter(t)
if len(seq) >= 2:
self.assertEqual(seq.index(seq[0]), 0, f"{name}: index(seq[0]) != 0")
self.assertEqual(seq.index(seq[-1]), len(seq) - 1, f"{name}: index(seq[-1]) != {len(seq) - 1}")
+247
View File
@@ -0,0 +1,247 @@
"""PyO3 #[pyclass(subclass)] 子类化兼容性测试 Mixin。
验证: Python 端可以正常子类化 PyO3 导出的类__new__/__init__ 协作
super() 委托MRO property/method 重写等全部正确
用法::
class TestMyObserver(PyO3SubclassMixin, unittest.TestCase):
base_class = mylib.Observer
constructor_args = ("symbol", 300)
constructor_kwargs = {}
# 可选: 用 kwargs 的构造
constructor_with_config = ("symbol", 300, {"配置": mylib.Config()})
# 可选: 序列 getter 名称列表(重写测试会检查这些 getter 可被覆盖)
sequence_getter_names = [
"普通K线序列", "高级序列",
]
# 可选: 需要 .nb 数据文件才能运行的测试会检查这个
@staticmethod
def has_data_file():
return os.path.isfile("data.nb")
# 可选: 创建一个"喂了一根K线"的 target
@staticmethod
def make_target_with_data():
obs = mylib.Observer("sym", 300)
k = mylib.KLine(...)
obs.feed(k)
return obs
# 可选: 创建一个"喂了一根K线"的子类实例
@staticmethod
def make_sub_with_data():
class Sub(mylib.Observer):
pass
obs = Sub("sym", 300)
k = mylib.KLine(...)
obs.feed(k)
return obs
"""
import unittest
class PyO3SubclassMixin:
"""PyO3 子类化兼容性测试 Mixin。
子类必须定义:
base_class: type
constructor_args: tuple
constructor_kwargs: dict
子类可选定义:
sequence_getter_names: list[str]
has_data_file: Callable[[], bool]
make_target_with_data: Callable[[], Any]
make_sub_with_data: Callable[[], Any]
make_data_item: Callable[[], Any] # 创建一根可喂入的数据项
feed_method_name: str # 喂数据的方法名,默认 "增加原始K线"
property_getters: list[str] # 需要逐一下覆写的 property 名
method_overrides: list[str] # 需要逐一重写的方法名
"""
base_class: type = None
constructor_args: tuple = ()
constructor_kwargs: dict = {}
sequence_getter_names: list = []
# 可选 hooks
has_data_file = None
make_target_with_data = None
make_sub_with_data = None
make_data_item = None
feed_method_name = "增加原始K线"
property_getters: list = []
method_overrides: list = []
@classmethod
def setUpClass(cls):
if cls.base_class is None:
raise unittest.SkipTest(f"{cls.__name__} 未定义 base_class")
# ---- 基础子类化 ----
def test_子类可实例化(self):
"""子类可创建,isinstance 正确."""
Base = self.base_class
class Sub(Base):
pass
obs = Sub(*self.constructor_args, **self.constructor_kwargs)
self.assertIsInstance(obs, Base)
self.assertEqual(type(obs).__name__, "Sub")
def test_子类_init_可添加自定义属性(self):
"""子类 __init__ 可添加自定义属性,基类字段不受影响."""
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
class Sub(Base):
def __init__(self, *a, **kw):
self.tag = "custom"
self.count = 0
obs = Sub(*args, **kwargs)
self.assertEqual(obs.tag, "custom")
self.assertEqual(obs.count, 0)
def test_子类_new_过滤_kwargs(self):
"""__new__ 过滤子类专属参数,只把父类需要的传给 super().__new__."""
Base = self.base_class
args = self.constructor_args
class Sub(Base):
def __new__(cls, *a, extra=None, **kw):
return super().__new__(cls, *a)
def __init__(self, *a, extra=None, **kw):
self.extra = extra
obs = Sub(*args, extra={"debug": True})
self.assertEqual(obs.extra, {"debug": True})
obs2 = Sub(*args)
self.assertIsNone(obs2.extra)
# ---- 方法重写 ----
def test_方法重写_super调用(self):
"""重写方法,super() 调用父类."""
if self.make_target_with_data is None or self.make_sub_with_data is None:
self.skipTest("未定义 make_target_with_data / make_sub_with_data")
base_obs = self.make_target_with_data()
sub_obs = self.make_sub_with_data()
for attr in self.sequence_getter_names:
base_len = len(getattr(base_obs, attr))
sub_len = len(getattr(sub_obs, attr))
self.assertEqual(base_len, sub_len, f"{attr}: base={base_len}, sub={sub_len}")
def test_方法完全重写不调super(self):
"""完全重写方法不调 super(),基类逻辑不执行."""
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
class Sub(Base):
def __init__(self, *a, **kw):
self.log = []
obs = Sub(*args, **kwargs)
self.assertEqual(obs.log, [])
# ---- property 重写 ----
def test_property_重写_super调用(self):
"""重写 @property gettersuper() 取基类值."""
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
class Sub(Base):
pass
obs = Sub(*args, **kwargs)
# 验证实例创建成功即可,具体 getter 覆盖由子类测试
self.assertIsInstance(obs, Base)
def test_str_repr_重写(self):
"""重写 __str__ / __repr__."""
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
class Sub(Base):
def __str__(self):
return f"Custom({id(self)})"
def __repr__(self):
return self.__str__()
obs = Sub(*args, **kwargs)
self.assertIn("Custom", str(obs))
self.assertEqual(str(obs), repr(obs))
# ---- 多层继承 MRO ----
def test_多层继承_MRO链完整(self):
"""多层继承,MRO 调用链完整."""
if self.make_data_item is None:
self.skipTest("未定义 make_data_item")
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
feed_name = self.feed_method_name
class L1(Base):
def __init__(self, *a, **kw):
self._l1_called = False
class L2(L1):
def __init__(self, *a, **kw):
super().__init__(*a, **kw)
self._l2_called = True
obs = L2(*args, **kwargs)
self.assertTrue(obs._l2_called)
def test_未重写方法直接继承(self):
"""未重写的方法从基类直接继承."""
if self.make_data_item is None:
self.skipTest("未定义 make_data_item")
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
class Sub(Base):
pass
obs = Sub(*args, **kwargs)
self.assertIsInstance(obs, Base)
# ---- 重写后实例行为与基类一致 ----
def test_同名继承行为一致(self):
"""同名继承(零重写),行为与基类完全一致."""
if self.make_target_with_data is None:
self.skipTest("未定义 make_target_with_data")
Base = self.base_class
args = self.constructor_args
kwargs = self.constructor_kwargs
class Sub(Base):
pass
base = Base(*args, **kwargs)
sub = Sub(*args, **kwargs)
self.assertIsInstance(sub, Base)
+88
View File
@@ -0,0 +1,88 @@
"""PyO3 返回值的 Python 类型形状验证工具。
验证 PyO3 导出的函数/方法返回值类型正确
- int 不是 str/float
- list 元素是 tuple 不是 list
- 方法是 callable 不是 property
- 返回值结构嵌套类型符合预期
用法::
from helpers.type_shape import assert_type_shape
result = mylib.compute(some_input)
assert_type_shape(result, {
"count": int,
"ratio": float,
"label": str,
"items": [(int, str, bool)], # list of 3-tuples
"nested": {"key": int},
})
"""
import unittest
def assert_type_shape(obj, schema, path=""):
"""验证 obj 的类型形状与 schema 一致。
schema 支持:
- type: obj 必须是该类型实例
- [inner]: obj 必须是 list每个元素验证 inner
- (t1, t2, ...): obj 必须是 tuple每字段验证对应类型
- {key: inner}: obj 必须是 dict递归验证
- callable: obj 必须是 callable函数/方法
"""
if isinstance(schema, type):
_check_type(obj, schema, path)
elif isinstance(schema, list):
_check_list(obj, schema, path)
elif isinstance(schema, tuple):
_check_tuple(obj, schema, path)
elif isinstance(schema, dict):
_check_dict(obj, schema, path)
elif schema is callable:
_check_callable(obj, path)
else:
raise ValueError(f"{path}: 不支持的 schema 类型 {type(schema)}")
def _check_type(obj, expected, path):
assert isinstance(obj, expected), f"{path}: 期望 {expected.__name__}, 实际 {type(obj).__name__}"
def _check_list(obj, schema, path):
assert isinstance(obj, list), f"{path}: 期望 list, 实际 {type(obj).__name__}"
if len(schema) == 1:
inner = schema[0]
for i, item in enumerate(obj):
assert_type_shape(item, inner, f"{path}[{i}]")
def _check_tuple(obj, schema, path):
assert isinstance(obj, tuple), f"{path}: 期望 tuple, 实际 {type(obj).__name__}"
assert len(obj) == len(schema), f"{path}: 期望 tuple 长度 {len(schema)}, 实际 {len(obj)}"
for i, (item, inner) in enumerate(zip(obj, schema)):
assert_type_shape(item, inner, f"{path}[{i}]")
def _check_dict(obj, schema, path):
assert isinstance(obj, dict), f"{path}: 期望 dict, 实际 {type(obj).__name__}"
for key, inner in schema.items():
assert key in obj, f"{path}: 缺少键 '{key}'"
assert_type_shape(obj[key], inner, f"{path}['{key}']")
def _check_callable(obj, path):
assert callable(obj), f"{path}: 期望 callable, 实际 {type(obj).__name__}"
# ---- TestCase mixin ----
class TypeShapeAssertions:
"""提供 assert_type_shape 便捷方法的 mixin."""
def assertTypeShape(self, obj, schema, path=""):
"""断言 obj 的类型形状与 schema 一致."""
assert_type_shape(obj, schema, path)
File diff suppressed because it is too large Load Diff
+5 -3
View File
@@ -1,8 +1,7 @@
[package]
name = "chanlun"
version = "26.5.2"
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"
+21
View File
@@ -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.
File diff suppressed because it is too large Load Diff
+33 -23
View File
@@ -26,7 +26,7 @@ use crate::config::缠论配置;
use crate::kline::bar::K线;
use crate::structure::dash_line::线;
use crate::types::;
use std::rc::Rc;
use std::sync::Arc;
/// 背驰分析 — 判断进入段和离开段之间是否存在背驰
pub struct ;
@@ -35,12 +35,18 @@ impl 背驰分析 {
/// MACD背驰 — MACD柱状线面积背驰
/// 方式: "总"=阳+|阴|总面积, 其他=按进入段方向选阳或阴
pub fn MACD背驰(
: &线, : &线, K线序列: &[Rc<K线>], : &str
: &线, : &线, K线序列: &[Arc<K线>], : &str
) -> bool {
let MACD =
Self::_获取MACD面积(K线序列, &...K线, &...K线);
let MACD =
Self::_获取MACD面积(K线序列, &...K线, &...K线);
let MACD = Self::_获取MACD面积(
K线序列,
&...K线.read().unwrap(),
&..read().unwrap()..K线.read().unwrap(),
);
let MACD = Self::_获取MACD面积(
K线序列,
&...K线.read().unwrap(),
&..read().unwrap()..K线.read().unwrap(),
);
// 计算面积(绝对值求和)
let = if == "" {
@@ -63,18 +69,18 @@ impl 背驰分析 {
/// 斜率背驰 — 价格斜率背驰
pub fn (: &线, : &线) -> bool {
let dx = (.. - ..) as f64;
let dx = (..read().unwrap().() - ..()) as f64;
if dx == 0.0 {
return false;
}
let dy = .. - ..;
let dy = ..read().unwrap(). - ..;
let = dy / dx;
let dx = (.. - ..) as f64;
let dx = (..read().unwrap().() - ..()) as f64;
if dx == 0.0 {
return false;
}
let dy = .. - ..;
let dy = ..read().unwrap(). - ..;
let = dy / dx;
if .() == :: {
@@ -86,12 +92,12 @@ impl 背驰分析 {
/// 测度背驰 — 价格时间测度背驰
pub fn (: &线, : &线) -> bool {
let dx = (.. - ..) as f64;
let dy = .. - ..;
let dx = (..read().unwrap().() - ..()) as f64;
let dy = ..read().unwrap(). - ..;
let = (dx * dx + dy * dy).sqrt();
let dx = (.. - ..) as f64;
let dy = .. - ..;
let dx = (..read().unwrap().() - ..()) as f64;
let dy = ..read().unwrap(). - ..;
let = (dx * dx + dy * dy).sqrt();
if .() == :: {
@@ -102,14 +108,14 @@ impl 背驰分析 {
}
/// 全量背驰 — MACD + 斜率 + 测度 三者全满足
pub fn (: &线, : &线, K序列: &[Rc<K线>]) -> bool {
pub fn (: &线, : &线, K序列: &[Arc<K线>]) -> bool {
Self::MACD背驰(, , K序列, "")
&& Self::(, )
&& Self::(, )
}
/// 任意背驰 — 任一条件满足即可
pub fn (: &线, : &线, K序列: &[Rc<K线>]) -> bool {
pub fn (: &线, : &线, K序列: &[Arc<K线>]) -> bool {
Self::MACD背驰(, , K序列, "")
|| Self::(, )
|| Self::(, )
@@ -119,7 +125,7 @@ impl 背驰分析 {
pub fn (
: &线,
: &线,
K序列: &[Rc<K线>],
K序列: &[Arc<K线>],
: &,
) -> bool {
match (
@@ -152,7 +158,7 @@ impl 背驰分析 {
}
/// 任选背驰 — 至少两个条件满足(多数投票)
pub fn (: &线, : &线, K序列: &[Rc<K线>]) -> bool {
pub fn (: &线, : &线, K序列: &[Arc<K线>]) -> bool {
let = [
Self::MACD背驰(, , K序列, ""),
Self::(, ),
@@ -165,7 +171,7 @@ impl 背驰分析 {
pub fn (
: &线,
: &线,
K序列: &[Rc<K线>],
K序列: &[Arc<K线>],
: &,
: &str,
) -> bool {
@@ -180,9 +186,13 @@ impl 背驰分析 {
// ---- 内部辅助 ----
fn _获取MACD面积(K线序列: &[Rc<K线>], : &Rc<K线>, : &Rc<K线>) -> MACD面积 {
let _idx = K线序列.iter().position(|k| Rc::as_ptr(k) == Rc::as_ptr());
let _idx = K线序列.iter().position(|k| Rc::as_ptr(k) == Rc::as_ptr());
fn _获取MACD面积(K线序列: &[Arc<K线>], : &Arc<K线>, : &Arc<K线>) -> MACD面积 {
let _idx = K线序列
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr());
let _idx = K线序列
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr());
let mut = 0.0f64;
let mut = 0.0f64;
@@ -190,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;
+550 -147
View File
@@ -25,125 +25,177 @@
use crate::structure::dash_line::线;
use crate::structure::fractal_obj::;
use crate::types::;
use std::rc::Rc;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 中枢 — 三段虚线重叠区间构成的价格中枢
#[derive(Debug, Clone)]
///
/// 可变字段使用 AtomicI64 / RwLock 实现内部可变性,确保多 Arc 共享时可修改。
///
/// 字段:
/// - 序号: 中枢序号,同一级别内递增
/// - 标识: 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>"
/// - 级别: 中枢级别(笔中枢=1,线段中枢=2 等)
/// - 基础序列: 构成中枢的虚线序列(至少 3 根,延伸后可多至 9 根甚至更多)
/// - 第三买卖线: 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线)
/// - 本级_第三买卖线: 本级第三类买卖点对应的虚线
#[derive(Debug)]
pub struct {
pub : i64,
pub : String,
pub : i64,
pub : Vec<Rc<线>>,
pub 线: Option<Rc<线>>,
pub _第三买卖线: Option<Rc<线>>,
/// 中枢序号,同一级别内递增
pub : AtomicI64,
/// 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>"
pub : RwLock<String>,
/// 中枢级别(笔中枢=1,线段中枢=2 等)
pub : AtomicI64,
/// 构成中枢的虚线序列(至少 3 根,延伸后可多至 9+ 根)
pub : RwLock<Vec<Arc<线>>>,
/// 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线)
pub 线: RwLock<Option<Arc<线>>>,
/// 本级第三类买卖点对应的虚线
pub _第三买卖线: RwLock<Option<Arc<线>>>,
}
impl Clone for {
fn clone(&self) -> Self {
Self {
: AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()),
: AtomicI64::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()),
线: RwLock::new(self.线.read().unwrap().clone()),
_第三买卖线: RwLock::new(self._第三买卖线.read().unwrap().clone()),
}
}
}
impl {
pub fn new(: i64, : String, : i64, : Vec<Rc<线>>) -> Self {
/// 创建新中枢(最多取前 3 根虚线作为基础序列)
pub fn new(: i64, : String, : i64, : Vec<Arc<线>>) -> Self {
Self {
,
,
,
: .into_iter().take(3).collect(),
线: None,
_第三买卖线: None,
: AtomicI64::new(),
: RwLock::new(),
: AtomicI64::new(),
: RwLock::new(.into_iter().take(3).collect()),
线: RwLock::new(None),
_第三买卖线: RwLock::new(None),
}
}
pub fn 线(&mut self, 线: Rc<线>) {
self..push(线);
self._第三买卖线 = None;
self.线 = None;
/// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线
pub fn _添加虚线(&self, 线: Arc<线>) {
self..write().unwrap().push(线);
*self._第三买卖线.write().unwrap() = None;
*self.线.write().unwrap() = None;
}
/// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号"
pub fn (&self) -> String {
format!(
"{}:{}:{}:{}",
self.()..,
self.()..,
self.,
self.
self..read().unwrap(),
self..load(Ordering::Relaxed)
)
}
pub fn (&self) -> Rc<线> {
Rc::clone(&self.[self..len() - 1])
/// 返回基础序列的最后一根虚线(当前离开段)
pub fn (&self) -> Arc<线> {
Arc::clone(&self..read().unwrap()[self..read().unwrap().len() - 1])
}
/// 返回中枢方向(与基础序列第一段方向相反)
pub fn (&self) -> {
self.[0].().()
self..read().unwrap()[0].().()
}
/// 中枢上沿 = min(前三段的高)
pub fn (&self) -> f64 {
self.[..3]
self..read().unwrap()[..3]
.iter()
.map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0)
}
/// 中枢下沿 = max(前三段的低)
pub fn (&self) -> f64 {
self.[..3]
self..read().unwrap()[..3]
.iter()
.map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0)
}
/// 中枢最高点 = max(所有段的高)
pub fn (&self) -> f64 {
self.
.read()
.unwrap()
.iter()
.map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0)
}
/// 中枢最低点 = min(所有段的低)
pub fn (&self) -> f64 {
self.
.read()
.unwrap()
.iter()
.map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.unwrap_or(0.0)
}
pub fn (&self) -> Rc<> {
Rc::clone(&self.[0].)
/// 返回基础序列第一段的起点分型
pub fn (&self) -> Arc<> {
Arc::clone(&self..read().unwrap()[0].)
}
pub fn (&self) -> Rc<> {
Rc::clone(&self.[self..len() - 1].)
/// 返回基础序列最后一段的终点分型
pub fn (&self) -> Arc<> {
Arc::clone(
&*self..read().unwrap()[self..read().unwrap().len() - 1]
.
.read()
.unwrap(),
)
}
pub fn 线(&mut self, 线: Rc<线>) {
self.线 = Some(线);
/// 设置第三类买卖点对应的虚线
pub fn 线(&self, 线: Option<Arc<线>>) {
*self.线.write().unwrap() = 线;
}
/// 获取序列 — 基础序列 + 第三买卖线(若有)
pub fn (&self) -> Vec<Rc<线>> {
let mut : Vec<Rc<线>> = self..clone();
if let Some(ref ) = self.线 {
.push(Rc::clone());
pub fn (&self) -> Vec<Arc<线>> {
let mut : Vec<Arc<线>> = self..read().unwrap().clone();
if let Some(ref ) = *self.线.read().unwrap() {
.push(Arc::clone());
}
}
/// 返回序列化数据文本,用于调试和存储
pub fn (&self) -> String {
let 线_str = match &self.线 {
let 线_str = match &*self.线.read().unwrap() {
Some(x) => format!("{}", x),
None => "None".to_string(),
};
let _第三买卖线_str = match &self._第三买卖线 {
let _第三买卖线_str = match &*self._第三买卖线.read().unwrap() {
Some(x) => format!("{}", x),
None => "None".to_string(),
};
format!(
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}",
self.,
self.,
self.,
self.().,
self..read().unwrap(),
self..load(Ordering::Relaxed),
self..load(Ordering::Relaxed),
self.().(),
crate::utils::format_f64_g(self.().),
self.().,
self.().(),
crate::utils::format_f64_g(self.().),
线_str,
_第三买卖线_str,
@@ -151,12 +203,12 @@ impl 中枢 {
}
/// 校验中枢合法性
pub fn (&mut self, : &[Rc<线>]) -> bool {
let mut = self..clone();
let mut : Vec<Rc<线>> = Vec::new();
for in &self. {
if !.iter().any(|x| Rc::as_ptr(x) == Rc::as_ptr()) {
.push(Rc::clone());
pub fn _校验合法性(&self, : &[Arc<线>]) -> bool {
let mut = self..read().unwrap().clone();
let mut : Vec<Arc<线>> = Vec::new();
for in self..read().unwrap().iter() {
if !.iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr()) {
.push(Arc::clone());
}
}
@@ -164,50 +216,52 @@ impl 中枢 {
let = &[0];
if let Some(pos) = self
.
.read()
.unwrap()
.iter()
.position(|x| Rc::as_ptr(x) == Rc::as_ptr())
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
{
= self.[..pos].to_vec();
= self..read().unwrap()[..pos].to_vec();
}
}
if .len() < 3 {
self.线 = None;
self._第三买卖线 = None;
self.线(None);
*self._第三买卖线.write().unwrap() = None;
return false;
}
self. = ;
*self..write().unwrap() = ;
let = self.();
let = self.();
= Vec::new();
for in &self. {
for in self..read().unwrap().iter() {
if crate::types::::(, , .(), .()).()
{
break;
}
.push(Rc::clone());
.push(Arc::clone());
}
self. = ;
*self..write().unwrap() = ;
if self..len() < 3 {
if self..read().unwrap().len() < 3 {
return false;
}
for i in 1..self..len() {
let = &self.[i - 1];
let = &self.[i];
for i in 1..self..read().unwrap().len() {
let = &self..read().unwrap()[i - 1];
let = &self..read().unwrap()[i];
if !.() {
return false;
}
}
if !crate::types::::(
self.[0].(),
self.[0].(),
self.[2].(),
self.[2].(),
self..read().unwrap()[0].(),
self..read().unwrap()[0].(),
self..read().unwrap()[2].(),
self..read().unwrap()[2].(),
)
.()
{
@@ -218,10 +272,11 @@ impl 中枢 {
}
}
if let Some(ref 线) = self.线.clone() {
if .iter().any(|x| Rc::as_ptr(x) == Rc::as_ptr(线)) {
if !self..last().unwrap().(线) {
self.线 = None;
let 线_opt = self.线.read().unwrap().clone();
if let Some(ref 线) = 线_opt {
if .iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(线)) {
if !self..read().unwrap().last().unwrap().(线) {
self.线(None);
} else if !crate::types::::(
self.(),
self.(),
@@ -230,11 +285,11 @@ impl 中枢 {
)
.()
{
self.线(Rc::clone(线));
self.线 = None;
self._添加虚线(Arc::clone(线));
self.线(None);
}
} else {
self.线 = None;
self.线(None);
}
}
true
@@ -243,16 +298,18 @@ impl 中枢 {
/// 完整性 — 详见教你炒股票43:有关背驰的补习课
/// 不完整时下一个中枢大概率会与当前中枢发生扩展
pub fn (&self, : &str) -> bool {
if self.[0]. == "" {
return self.线.is_some();
if *self..read().unwrap()[0]..read().unwrap() == "" {
return self.线.read().unwrap().is_some();
}
let 线 = if == "" {
&self..last().unwrap()._中枢序列
let _ref = self..read().unwrap();
let = _ref.last().unwrap();
let _vec = if == "" {
._中枢序列.read().unwrap()
} else {
&self..last().unwrap()._中枢序列
._中枢序列.read().unwrap()
};
for in 线 {
for in _vec.iter() {
if crate::types::::(
self.(),
self.(),
@@ -269,16 +326,19 @@ impl 中枢 {
/// 获取扩展中枢 — 当基础序列 >= 9 时生成扩展中枢
pub fn (
&self, : &mut Vec<Rc<>>, : &crate::config::
&self,
: &mut Vec<Arc<>>,
: &crate::config::,
) {
if self..len() >= 9 {
let mut 线: Vec<Rc<线>> = Vec::new();
crate::algorithm::segment::线::(&self., &mut 线, );
if self..read().unwrap().len() >= 9 {
let mut 线: Vec<Arc<线>> = Vec::new();
let _ref = self..read().unwrap();
crate::algorithm::segment::线::(&_ref, &mut 线, );
::(
&线,
,
false,
&format!("{}_扩展中枢_", self.),
&format!("{}_扩展中枢_", self..read().unwrap()),
0,
);
}
@@ -287,9 +347,15 @@ impl 中枢 {
/// 当前状态 — 详见教你炒股票49:利润率最大的操作模式
/// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下
pub fn (&self) -> &str {
let = self..last().unwrap();
let _ref = self..read().unwrap();
let = Arc::clone(_ref.last().unwrap());
let = ._武();
let = crate::types::::(self.(), self.(), .., ..);
let = crate::types::::(
self.(),
self.(),
...get(),
...get(),
);
if == crate::types:::: {
"中枢之上"
} else if == crate::types:::: {
@@ -319,62 +385,71 @@ impl 中枢 {
/// 创建中枢
pub fn (
: Rc<线>, : Rc<线>, : Rc<线>, : i64, : &str
: Arc<线>, : Arc<线>, : Arc<线>, : i64, : &str
) -> Self {
debug_assert!(Self::(&, &, &), "中枢.创建 基础检查失败");
Self::new(
0,
format!("{}中枢<{}>", , .),
format!("{}中枢<{}>", , ..read().unwrap()),
,
vec![, , ],
)
}
/// 从序列中获取中枢
pub fn (
线: &[Rc<线>],
/// _从序列中获取中枢
pub fn _从序列中获取中枢(
线: &[Arc<线>],
: ,
: &str,
) -> Option<Rc<>> {
) -> Option<Arc<>> {
for i in 2..线.len() {
let = &线[i - 2];
let = &线[i - 1];
let = &线[i];
if Self::(, , ) && .() == {
let = Self::(Rc::clone(), Rc::clone(), Rc::clone(), 0, );
return Some(Rc::new());
let = Self::(Arc::clone(), Arc::clone(), Arc::clone(), 0, );
return Some(Arc::new());
}
}
None
}
/// 向中枢序列尾部添加
pub fn (
: &mut Vec<Rc<>>, mut : Rc<>
/// _向中枢序列尾部添加
pub fn _向中枢序列尾部添加(
: &mut Vec<Arc<>>, : Arc<>
) {
if let Some() = .last() {
let = Rc::make_mut(&mut );
. = . + 1;
// 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. > new_last. {
panic!(
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
_last., new_last.
);
}
.
.store(..load(Ordering::Relaxed) + 1, 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 (
: &mut Vec<Rc<>>,
: &Rc<>,
) -> Option<Rc<>> {
if .last().map(|x| Rc::as_ptr(x)) == Some(Rc::as_ptr()) {
pub fn _从中枢序列尾部弹出(
: &mut Vec<Arc<>>,
: &Arc<>,
) -> Option<Arc<>> {
if .last().map(Arc::as_ptr) == Some(Arc::as_ptr()) {
.pop()
} else {
None
@@ -385,11 +460,11 @@ impl 中枢 {
///
/// 每收到新的虚线序列数据后调用,更新中枢序列
pub fn (
线: &[Rc<线>],
: &mut Vec<Rc<>>,
线: &[Arc<线>],
: &mut Vec<Arc<>>,
: bool,
: &str,
: i64,
_层级: i64,
) {
if 线.len() < 3 {
return;
@@ -406,9 +481,9 @@ impl 中枢 {
// Python: 序号 = 虚线序列.index(左)
let = 线
.iter()
.position(|x| Rc::as_ptr(x) == Rc::as_ptr())
.unwrap_or(i - 1);
if && (. == 0 || == 0) {
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
.expect("中枢.分析: 左元素不在虚线序列中");
if && (..load(Ordering::Relaxed) == 0 || == 0) {
continue;
}
if >= 2 {
@@ -422,16 +497,16 @@ impl 中枢 {
continue;
}
}
let = Rc::new(Self::(
Rc::clone(),
Rc::clone(),
Rc::clone(),
.,
let = Arc::new(Self::(
Arc::clone(),
Arc::clone(),
Arc::clone(),
..load(Ordering::Relaxed),
,
));
Self::(, );
Self::_向中枢序列尾部添加(, );
// Python: return 中枢递归分析(虚线序列, 中枢序列, ...)
Self::(线, , , , );
Self::(线, , , , _层级);
return;
}
}
@@ -441,25 +516,22 @@ impl 中枢 {
// 增量更新
let mut _idx = .len() - 1;
// Validate in-place via Rc::make_mut — avoids full中枢 struct clone
let needs_pop = {
let cur = Rc::make_mut(&mut [_idx]);
!cur.(线)
};
// Validate via shared reference (中枢 uses RwLock internally)
let needs_pop = ![_idx]._校验合法性(线);
if needs_pop {
let = Rc::clone(&[_idx]);
Self::(, &);
Self::(线, , , , );
let = Arc::clone(&[_idx]);
Self::_从中枢序列尾部弹出(, &);
Self::(线, , , , _层级);
return;
}
// 找到当前中枢最后一个元素在虚线序列中的位置
let = {
let cur = &[_idx];
let = &cur.[cur..len() - 1];
let = &cur..read().unwrap()[cur..read().unwrap().len() - 1];
match 线
.iter()
.position(|x| Rc::as_ptr(x) == Rc::as_ptr())
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
{
Some(idx) => idx + 1,
None => return,
@@ -468,10 +540,10 @@ impl 中枢 {
let mut = [_idx].();
let mut = [_idx].();
let mut : Vec<Rc<线>> = Vec::new();
let mut : Vec<Arc<线>> = Vec::new();
for i in ..线.len() {
let 线 = Rc::clone(&线[i]);
for 线_ref in &线[..] {
let 线 = Arc::clone(线_ref);
// 检查是否超出中枢范围(缺口)
if crate::types::::(, , 线.(), 线.()).()
@@ -481,16 +553,37 @@ impl 中枢 {
// Python: if 当前中枢.基础序列[-1].之后是(当前虚线):
let needs_三买 = {
let cur = &[_idx];
cur..last().unwrap().(&线)
cur.
.read()
.unwrap()
.last()
.unwrap()
.(&线)
};
if needs_三买 {
Rc::make_mut(&mut [_idx])
.线(线.clone());
[_idx].线(Some(线.clone()));
}
} else {
if .is_empty() {
// 仍在范围内:延伸中枢
Rc::make_mut(&mut [_idx]).线(线);
debug_assert!(
[_idx]
.
.read()
.unwrap()
.last()
.unwrap()
.(&线),
"中枢延伸: 不连续 {}, {}",
[_idx]
.
.read()
.unwrap()
.last()
.unwrap(),
线
);
[_idx]._添加虚线(线);
} else {
.push(线);
}
@@ -500,13 +593,15 @@ impl 中枢 {
while .len() >= 3 {
let = [_idx]
.
.read()
.unwrap()
.last()
.unwrap()
.()
.();
match Self::(&, , ) {
match Self::_从序列中获取中枢(&, , ) {
Some() => {
Self::(, );
Self::_向中枢序列尾部添加(, );
// Python: 当前中枢 = 新中枢
_idx = .len() - 1;
= [_idx].();
@@ -526,6 +621,8 @@ impl std::fmt::Display for 中枢 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let _str = self
.
.read()
.unwrap()
.iter()
.map(|d| format!("{}", d))
.collect::<Vec<_>>()
@@ -533,13 +630,319 @@ impl std::fmt::Display for 中枢 {
write!(
f,
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
self.,
self..read().unwrap(),
crate::utils::format_f64_g(self.()),
crate::utils::format_f64_g(self.()),
self..len(),
self..read().unwrap().len(),
_str,
self.(),
self.(),
)
}
}
#[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, : f64, : f64) -> K线 {
K线 {
,
,
,
: ,
: ,
..Default::default()
}
}
fn _创建缠K(
: i64,
: f64,
: f64,
: ,
: Option<>,
: i64,
) -> Arc<K线> {
let K = Arc::new(_创建K线(, , , , ));
Arc::new(K线::K(
, , , , , , K, None,
))
}
fn _创建顶分型(: i64, : f64, : f64, : i64) -> Arc<> {
let = _创建缠K(
- 2,
- 2.0,
- 2.0,
::,
Some(::),
- 2,
);
let = _创建缠K(, , , ::, Some(::), );
let = _创建缠K(
+ 2,
- 1.0,
- 1.0,
::,
Some(::),
+ 2,
);
Arc::new(::new(Some(), , Some()))
}
fn _创建底分型(: i64, : f64, : f64, : i64) -> Arc<> {
let = _创建缠K(
- 2,
+ 2.0,
+ 2.0,
::,
Some(::),
- 2,
);
let = K线::K(
,
,
,
::,
Some(::),
,
Arc::new(_创建K线(, , , , )),
None,
);
..set();
let = Arc::new();
let = _创建缠K(
+ 2,
+ 1.0,
+ 1.0,
::,
Some(::),
+ 2,
);
Arc::new(::new(Some(), , Some()))
}
fn _创建笔(
: i64,
: f64,
: f64,
: i64,
: f64,
: f64,
) -> Arc<线> {
let = _创建顶分型(, , , 1);
let = _创建底分型(, , , 2);
Arc::new(线::(, , true))
}
// ============================================================
// 中枢 Cell/RefCell 字段读写
// ============================================================
#[test]
fn test_中枢创建后字段初始值正确() {
let 1 = _创建笔(100, 50.0, 40.0, 200, 30.0, 20.0);
let 2 = _创建笔(200, 30.0, 20.0, 300, 55.0, 45.0);
let 3 = _创建笔(300, 55.0, 45.0, 400, 25.0, 15.0);
let = ::new(
1,
"测试中枢".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
assert_eq!(..load(Ordering::Relaxed), 1);
assert_eq!(*..read().unwrap(), "测试中枢");
assert_eq!(..load(Ordering::Relaxed), 1);
assert_eq!(..read().unwrap().len(), 3);
assert!(.线.read().unwrap().is_none());
assert!(._第三买卖线.read().unwrap().is_none());
}
#[test]
fn test_中枢CellRefCell字段读写() {
let 1 = _创建笔(100, 50.0, 40.0, 200, 30.0, 20.0);
let 2 = _创建笔(200, 30.0, 20.0, 300, 55.0, 45.0);
let 3 = _创建笔(300, 55.0, 45.0, 400, 25.0, 15.0);
let = ::new(
0,
"测试".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
// Cell 序号读写
..store(99, Ordering::Relaxed);
assert_eq!(..load(Ordering::Relaxed), 99);
// RefCell 第三买卖线读写
.线(Some(Arc::clone(&1)));
assert!(.线.read().unwrap().is_some());
assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()),
Arc::as_ptr(&1)
);
// 本级_第三买卖线
assert!(._第三买卖线.read().unwrap().is_none());
*._第三买卖线.write().unwrap() = Some(Arc::clone(&3));
assert!(._第三买卖线.read().unwrap().is_some());
}
// ============================================================
// 中枢 添加虚线
// ============================================================
#[test]
fn test_中枢添加虚线后基础序列扩展() {
let 1 = _创建笔(100, 50.0, 40.0, 200, 30.0, 20.0);
let 2 = _创建笔(200, 30.0, 20.0, 300, 55.0, 45.0);
let 3 = _创建笔(300, 55.0, 45.0, 400, 25.0, 15.0);
let 4 = _创建笔(400, 25.0, 15.0, 500, 60.0, 50.0);
let = ::new(
0,
"测试".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
assert_eq!(..read().unwrap().len(), 3);
._添加虚线(Arc::clone(&4));
assert_eq!(..read().unwrap().len(), 4);
assert_eq!(
Arc::as_ptr(&..read().unwrap()[3]),
Arc::as_ptr(&4)
);
}
#[test]
fn test_中枢添加虚线后清除第三买卖线() {
let 1 = _创建笔(100, 50.0, 40.0, 200, 30.0, 20.0);
let 2 = _创建笔(200, 30.0, 20.0, 300, 55.0, 45.0);
let 3 = _创建笔(300, 55.0, 45.0, 400, 25.0, 15.0);
let 4 = _创建笔(400, 25.0, 15.0, 500, 60.0, 50.0);
let = ::new(
0,
"测试".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
.线(Some(Arc::clone(&1)));
*._第三买卖线.write().unwrap() = Some(Arc::clone(&2));
assert!(.线.read().unwrap().is_some());
assert!(._第三买卖线.read().unwrap().is_some());
._添加虚线(Arc::clone(&4));
// 添加虚线后第三买卖线被清除
assert!(.线.read().unwrap().is_none());
assert!(._第三买卖线.read().unwrap().is_none());
}
// ============================================================
// Clone 后 Rc 指针身份一致
// ============================================================
#[test]
fn test_中枢Clone后基础序列Rc指针一致() {
let 1 = _创建笔(100, 50.0, 40.0, 200, 30.0, 20.0);
let 2 = _创建笔(200, 30.0, 20.0, 300, 55.0, 45.0);
let 3 = _创建笔(300, 55.0, 45.0, 400, 25.0, 15.0);
let = ::new(
0,
"测试".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
.线(Some(Arc::clone(&1)));
let = .clone();
// 基础序列中的 Rc 指针应一致
for i in 0..3 {
assert_eq!(
Arc::as_ptr(&..read().unwrap()[i]),
Arc::as_ptr(&..read().unwrap()[i])
);
}
// 第三买卖线 Rc 指针应一致
assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()),
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap())
);
}
// ============================================================
// 中枢 高/低/高高/低低计算
// ============================================================
#[test]
fn test_中枢高低计算正确() {
// 笔1: 顶(高=50,低=45) →底(高=40,低=30) = 向下笔, 高=50, 低=30
let 1 = _创建笔(100, 50.0, 45.0, 200, 40.0, 30.0);
// 笔2: 底(高=40,低=30) →顶(高=55,低=50) = 向上笔, 高=55, 低=30
let 2 = _创建底分型(200, 40.0, 30.0, 10);
let 2 = _创建顶分型(300, 55.0, 50.0, 15);
let 2 = Arc::new(线::(2, 2, true));
// 笔3: 顶(高=55,低=50) →底(高=35,低=25) = 向下笔, 高=55, 低=25
let 3 = _创建笔(300, 55.0, 50.0, 400, 35.0, 25.0);
let = ::new(
0,
"测试".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
// 高 = min(笔1高, 笔2高, 笔3高) = min(50, 55, 55) = 50
assert!((.() - 50.0).abs() < 0.01, "中枢高={}", .());
// 低 = max(笔1低, 笔2低, 笔3低) = max(30, 30, 25) = 30
assert!((.() - 30.0).abs() < 0.01, "中枢低={}", .());
}
// ============================================================
// 多 Rc 共享下修改可见性
// ============================================================
#[test]
fn test_多Rc共享中枢修改对所有引用可见() {
let 1 = _创建笔(100, 50.0, 40.0, 200, 30.0, 20.0);
let 2 = _创建笔(200, 30.0, 20.0, 300, 55.0, 45.0);
let 3 = _创建笔(300, 55.0, 45.0, 400, 25.0, 15.0);
let 4 = _创建笔(400, 25.0, 15.0, 500, 60.0, 50.0);
let 1 = Arc::new(::new(
0,
"测试".into(),
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
));
let 2 = Arc::clone(&1);
// 通过 rc1 修改序号
1..store(88, Ordering::Relaxed);
assert_eq!(2..load(Ordering::Relaxed), 88);
// 通过 rc1 添加虚线
1._线(Arc::clone(&4));
assert_eq!(2..read().unwrap().len(), 4);
// 验证共享的 Arc<虚线> 指针一致
assert_eq!(
Arc::as_ptr(&1..read().unwrap()[3]),
Arc::as_ptr(&2..read().unwrap()[3])
);
}
}
File diff suppressed because it is too large Load Diff
+420 -30
View File
@@ -27,31 +27,47 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::fractal_obj::;
use crate::types::bsp_type::;
use crate::types::;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::Ordering;
/// 基础买卖点 — 买卖点的基础数据结构
///
/// 包含买卖点的完整信息:类型、关联分型/K线、失效与终结状态等。
#[derive(Debug, Clone)]
pub struct {
/// 买卖点备注文本
pub : String,
/// 买卖点类型(一买/一卖/二买/二卖/三买/三卖/T1/T2/T3 等)
pub : ,
pub : Rc<>,
pub K线: Rc<K线>,
pub K线: Rc<K线>,
pub K线: Option<Rc<K线>>,
pub K线: Option<Rc<K线>>,
/// 买卖点对应的分型
pub : Arc<>,
/// 买卖点对应的缠论K线(即分型的中缠K)
pub K线: Arc<K线>,
/// 当前K线(买卖点生成时的K线)
pub K线: Arc<K线>,
/// 失效K线(买卖点失效时设置)
pub K线: Option<Arc<K线>>,
/// 终结K线(买卖点终结时设置)
pub K线: Option<Arc<K线>>,
/// 中枢破位值
pub : f64,
/// 分型结构(可选,用于补充确认)
pub : Option<>,
/// 创建时的缠K序号,用于偏移计算(与买卖点K线.序号同尺度)
/// None 时退化为使用当前K线.序号(bar序号,旧行为)
pub K序号: Option<i64>,
}
impl {
/// 创建基础买卖点,买卖点K线自动取自买卖点分型的中缠K
pub fn new(
: ,
K线: Rc<K线>,
: Rc<>,
K线: Arc<K线>,
: Arc<>,
: String,
: f64,
) -> Self {
let K线 = Rc::clone(&.);
let K线 = Arc::clone(&.);
Self {
,
,
@@ -62,18 +78,23 @@ impl 基础买卖点 {
K线: None,
: ,
: None,
K序号: None,
}
}
/// 偏移 — 当前K线与买卖点K线序号差
/// 偏移 — 当前缠K序号与买卖点K线序号
/// 如果设置了当前缠K序号(来自生成买卖点),使用缠K序号;否则退化为使用 bar序号
pub fn (&self) -> i64 {
self.K线. - self.K线.
match self.K序号 {
Some(ck_idx) => ck_idx - self.K线..load(Ordering::Relaxed),
None => self.K线. - self.K线..load(Ordering::Relaxed),
}
}
/// 失效偏移
pub fn (&self) -> i64 {
match &self.K线 {
Some(k) => k. - self.K线.,
Some(k) => k. - self.K线..load(Ordering::Relaxed),
None => -1,
}
}
@@ -107,13 +128,14 @@ impl std::fmt::Display for 基础买卖点 {
}
}
/// 买卖点 — 包含一二三类买卖点的工厂方法
/// 买卖点 — 包含全部 18 种买卖点类型的工厂方法
pub struct ;
impl {
/// 创建 一卖 类型的基础买卖点
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -121,9 +143,10 @@ impl 买卖点 {
::new(::, K线, , , )
}
/// 创建 一买 类型的基础买卖点
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -131,9 +154,10 @@ impl 买卖点 {
::new(::, K线, , , )
}
/// 创建 二卖 类型的基础买卖点
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -141,9 +165,10 @@ impl 买卖点 {
::new(::, K线, , , )
}
/// 创建 二买 类型的基础买卖点
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -151,9 +176,10 @@ impl 买卖点 {
::new(::, K线, , , )
}
/// 创建 三卖 类型的基础买卖点
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -161,9 +187,10 @@ impl 买卖点 {
::new(::, K线, , , )
}
/// 创建 三买 类型的基础买卖点
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -171,13 +198,145 @@ impl 买卖点 {
::new(::, K线, , , )
}
/// 创建 T1卖 类型的基础买卖点
pub fn T1卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1卖, K线, , , )
}
/// 创建 T1买 类型的基础买卖点
pub fn T1买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1买, K线, , , )
}
/// 创建 T1P卖 类型的基础买卖点
pub fn T1P卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1P卖, K线, , , )
}
/// 创建 T1P买 类型的基础买卖点
pub fn T1P买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1P买, K线, , , )
}
/// 创建 T2卖 类型的基础买卖点
pub fn T2卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2卖, K线, , , )
}
/// 创建 T2买 类型的基础买卖点
pub fn T2买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2买, K线, , , )
}
/// 创建 T2S卖 类型的基础买卖点
pub fn T2S卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2S卖, K线, , , )
}
/// 创建 T2S买 类型的基础买卖点
pub fn T2S买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2S买, K线, , , )
}
/// 创建 T3A卖 类型的基础买卖点
pub fn T3A卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3A卖, K线, , , )
}
/// 创建 T3A买 类型的基础买卖点
pub fn T3A买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3A买, K线, , , )
}
/// 创建 T3B卖 类型的基础买卖点
pub fn T3B卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3B卖, K线, , , )
}
/// 创建 T3B买 类型的基础买卖点
pub fn T3B买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3B买, K线, , , )
}
/// 生成买卖点 — 根据参数自动选择类型
pub fn (
: &str,
: &str,
: &str,
: Rc<>,
K: Rc<K线>,
: Arc<>,
K: Arc<K线>,
) -> {
let = if matches!(., :: | ::) {
""
@@ -185,10 +344,12 @@ impl 买卖点 {
""
};
let = format!("{}_{}{}{}", , , , );
let = .;
let = .();
// 当前K线 — 从缠K获取其标的K线
let K线 = Rc::clone(&K.K线);
let K线 = Arc::clone(&*K.K线.read().unwrap());
// 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算
let K序号 = K..load(Ordering::Relaxed);
let = match (, ) {
("", "") => ::,
@@ -197,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<K线> {
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., .());
}
}
+54 -18
View File
@@ -26,9 +26,10 @@ use crate::business::observer::观察者;
use crate::business::synthesizer::K线合成器;
use crate::config::;
use crate::kline::bar::K线;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::RwLock;
use tracing::{error, info};
/// 立体分析器 — 多周期协调器
///
@@ -38,10 +39,11 @@ pub struct 立体分析器 {
pub : Vec<i64>,
: i64,
K线合成器: K线合成器,
: HashMap<i64, Rc<RefCell<>>>,
: HashMap<i64, Arc<RwLock<>>>,
}
impl {
/// 创建立体分析器,自动创建K线合成器 + 每周期一个观察者
pub fn new(
: String,
: Vec<i64>,
@@ -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
@@ -96,61 +125,68 @@ impl 立体分析器 {
// Dispatch on completion events (matching Python's __K线回调)
for (, K线) in {
if let Some() = self..get(&) {
.borrow_mut().K线(K线);
.write().unwrap().K线(K线);
if let Some(K线) = self.K线合成器.K线() {
.write().unwrap().K线(K线.clone());
}
}
}
}
/// 获取指定周期的观察者
pub fn (&self, : i64) -> Option<Rc<RefCell<>>> {
pub fn (&self, : i64) -> Option<Arc<RwLock<>>> {
self..get(&).cloned()
}
/// 测试_保存数据 — 多级别数据拆分保存
/// 创建父目录 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
.
.get(&self.)
.and_then(|o| o.borrow().K线序列.first().map(|k| k.))
.and_then(|o| o.read().unwrap().K线序列.first().map(|k| k.))
.unwrap_or(0);
let = self
.
.get(&self.)
.and_then(|o| o.borrow().K线序列.last().map(|k| k.))
.and_then(|o| o.read().unwrap().K线序列.last().map(|k| k.))
.unwrap_or(0);
let = self
.
.get(&self.)
.map(|o| o.borrow()..clone())
.map(|o| o.read().unwrap()..clone())
.unwrap_or_default();
let = self
.
.get(&self.)
.map(|o| o.borrow().)
.map(|o| o.read().unwrap().)
.unwrap_or_default();
let = format!("RustM_{}:{}_{}_{}", , , , );
let = .join(&);
if let Err(e) = std::fs::create_dir_all(&) {
eprintln!("创建目录失败: {} -> {}", .display(), e);
error!("创建目录失败: {} -> {}", .display(), e);
return;
}
for in &self. {
if let Some() = self..get() {
.borrow()
.read()
.unwrap()
._保存数据(Some(&.to_string_lossy()));
}
}
println!("多级别数据拆分保存完成,目录:{}", .display());
info!("多级别数据拆分保存完成,目录:{}", .display());
}
}
+590 -144
View File
@@ -32,8 +32,9 @@ use crate::structure::dash_line::虚线;
use crate::structure::fractal_obj::;
use crate::types::;
use crate::utils::datetime;
use std::cell::RefCell;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
use tracing::{error, info};
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
pub struct {
@@ -42,42 +43,43 @@ pub struct 观察者 {
pub : ,
// K线序列
pub K线序列: Vec<Rc<K线>>,
pub K线序列: Vec<Rc<K线>>,
pub K线序列: Vec<Arc<K线>>,
pub K序列: Vec<Arc<K线>>,
pub K线序列: Vec<Arc<K线>>,
// 分型与笔
pub : Vec<Rc<>>,
pub : Vec<Rc<线>>,
pub _中枢序列: Vec<Rc<>>,
pub : Vec<Arc<>>,
pub : Vec<Arc<线>>,
pub _中枢序列: Vec<Arc<>>,
// 线段
pub 线: Vec<Rc<线>>,
pub : Vec<Rc<>>,
pub 线: Vec<Arc<线>>,
pub : Vec<Arc<>>,
// 扩展线段(笔级)
pub 线: Vec<Rc<线>>,
pub : Vec<Rc<>>,
pub 线: Vec<Arc<线>>,
pub : Vec<Arc<>>,
// 扩展线段(线段级)
pub 线_线段: Vec<Rc<线>>,
pub _线段: Vec<Rc<>>,
pub 线_线段: Vec<Arc<线>>,
pub _线段: Vec<Arc<>>,
// 线段之线段
pub 线_线段序列: Vec<Rc<线>>,
pub 线_中枢序列: Vec<Rc<>>,
pub 线_线段序列: Vec<Arc<线>>,
pub 线_中枢序列: Vec<Arc<>>,
// 扩展线段之扩展线段
pub 线_扩展线段: Vec<Rc<线>>,
pub _扩展线段: Vec<Rc<>>,
pub 线_扩展线段: Vec<Arc<线>>,
pub _扩展线段: Vec<Arc<>>,
// 终止时间戳
: Option<i64>,
}
impl {
pub fn new(: String, : i64, : ) -> Rc<RefCell<Self>> {
let = if . != "1970-01-01 00:00:00" && !..is_empty()
{
/// 创建观察者,初始化所有序列为空,若配置了手动终止则解析时间戳
pub fn new(: String, : i64, : ) -> Arc<RwLock<Self>> {
let = if !..is_empty() {
datetime::(&.)
} else {
None
@@ -88,6 +90,7 @@ impl 观察者 {
,
,
K线序列: Vec::new(),
K序列: Vec::new(),
K线序列: Vec::new(),
: Vec::new(),
: Vec::new(),
@@ -105,7 +108,7 @@ impl 观察者 {
,
};
instance.. = ;
Rc::new(RefCell::new(instance))
Arc::new(RwLock::new(instance))
}
/// 标识
@@ -114,18 +117,19 @@ impl 观察者 {
}
/// 当前K线
pub fn K线(&self) -> Option<&Rc<K线>> {
pub fn K线(&self) -> Option<&Arc<K线>> {
self.K线序列.last()
}
/// 当前缠K
pub fn K(&self) -> Option<&Rc<K线>> {
pub fn K(&self) -> Option<&Arc<K线>> {
self.K线序列.last()
}
/// 重置基础序列
pub fn (&mut self) {
self.K线序列.clear();
self.K序列.clear();
self.K线序列.clear();
self..clear();
self..clear();
@@ -144,14 +148,22 @@ 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);
}
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并投喂
pub fn (
&mut self, : i64, : f64, : f64, : f64, : f64, : f64
) {
let K = K线::K(&self., , , , , , , 0, self.);
self.K线(K);
}
/// 核心数据处理管道
fn __处理数据(&mut self, K: K线) {
// Step 1: 缠论K线分析 (普K is consumed by 分析 as &mut)
@@ -174,6 +186,7 @@ impl 观察者 {
&mut self.,
&self.K线序列,
&self.K线序列,
0,
&self.,
);
}
@@ -232,13 +245,7 @@ impl 观察者 {
&mut self.线_线段序列,
&self.,
0,
&[
::,
::,
::,
::,
::,
],
&[::, ::],
);
}
if self..线 {
@@ -264,6 +271,9 @@ impl 观察者 {
}
}
/// 识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现)
pub fn (&self) {}
/// 静态重新分析 — 遍历所有缠K重新生成分型/笔/线段
pub fn (&mut self) {
self..clear();
@@ -282,16 +292,17 @@ impl 观察者 {
for i in 1..self.K线序列.len() - 1 {
let = ::new(
Some(Rc::clone(&self.K线序列[i - 1])),
Rc::clone(&self.K线序列[i]),
Some(Rc::clone(&self.K线序列[i + 1])),
Some(Arc::clone(&self.K线序列[i - 1])),
Arc::clone(&self.K线序列[i]),
Some(Arc::clone(&self.K线序列[i + 1])),
);
::(
Rc::new(),
Arc::new(),
&mut self.,
&mut self.,
&self.K线序列,
&self.K线序列,
0,
&self.,
);
}
@@ -339,13 +350,7 @@ impl 观察者 {
&mut self.线_线段序列,
&self.,
0,
&[
::,
::,
::,
::,
::,
],
&[::, ::],
);
}
if self..线 {
@@ -354,7 +359,7 @@ impl 观察者 {
}
/// 测试_保存数据 — 输出各序列数据文本到文件
pub fn _保存数据(&self, root: Option<&str>) {
pub fn _保存数据(&self, root: Option<&str>) -> String {
let _文本数据: Vec<String> = self..iter().map(|b| b.()).collect();
let 线_文本数据: Vec<String> =
self.线.iter().map(|s| s.()).collect();
@@ -401,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(),
};
// 生成子目录名称
@@ -413,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
@@ -424,14 +427,14 @@ impl 观察者 {
.map(|ck| {
format!(
"缠K, {}, {}, {:?}, {}, {}, {}, {}, {}",
ck.,
ck.,
ck..load(Ordering::Relaxed),
ck..load(Ordering::Relaxed),
ck.,
ck.,
ck.,
ck.,
*ck..read().unwrap(),
ck..get(),
ck..get(),
ck.,
ck.
ck..load(Ordering::Relaxed)
)
})
.collect();
@@ -442,7 +445,12 @@ impl 观察者 {
.map(|(i, fx)| {
format!(
"分型, {}, {}, {:?}, {}, {}, {}",
i, fx., fx., fx., fx.., fx..,
i,
fx.(),
fx.,
fx.,
fx...load(Ordering::Relaxed),
fx...get(),
)
})
.collect();
@@ -474,46 +482,34 @@ impl 观察者 {
let = .join(format!("{}.txt", ));
let = .join("\n") + "\n";
if let Err(e) = std::fs::write(&, &) {
eprintln!("写入文件失败: {} -> {}", .display(), e);
error!("写入文件失败: {} -> {}", .display(), e);
}
}
println!("全部数据拆分保存完成,目录:{}", .display());
}
/// 解析本地数据文件 — 从 .nb 文件读取并解析所有 K线
pub fn (&self, : &str) -> Result<Vec<K线>, String> {
let data = std::fs::read().map_err(|e| format!("read file: {}", e))?;
let mut bars = Vec::new();
let size = 48;
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], self., &self.)
{
bars.push(k线);
}
}
Ok(bars)
info!("全部数据拆分保存完成,目录:{}", .display());
.display().to_string()
}
/// 加载本地数据 — 从 .nb 文件加载数据到当前观察者(先重置再投喂)
pub fn (&mut self, : &str) -> Result<(), String> {
self.();
let bars = self.()?;
for k线 in bars {
self.K线(k线);
let data = std::fs::read().map_err(|e| format!("read file: {}", e))?;
let size = 48;
for i in 0..data.len() / size {
let offset = i * size;
if let Some((, , , , , )) =
K线::(&data[offset..offset + size])
{
self.(, , , , , );
}
}
Ok(())
}
/// 读取数据文件 — .nb 文件加载数据
/// 读取数据文件 — 更新当前观察者并加载 .nb 文件
pub fn (
: &str,
: Option<>,
) -> Result<Rc<RefCell<Self>>, String> {
let = .unwrap_or_default();
// Parse filename: btcusd-300-1631772074-1632222374.nb
&mut self, : &str, :
) -> Result<(), String> {
let path = std::path::Path::new();
let name = path
.file_stem()
@@ -523,23 +519,12 @@ impl 观察者 {
if parts.len() < 4 {
return Err(format!("invalid filename format: {}", name));
}
let = parts[0].to_string();
let : i64 = parts[1]
self. = parts[0].to_string();
self. = parts[1]
.parse()
.map_err(|e| format!("parse period: {}", e))?;
let = Self::new(, , );
let data = std::fs::read().map_err(|e| format!("read file: {}", e))?;
let size = 48; // 6 × 8 bytes (big-endian double)
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], , "nb") {
.borrow_mut().K线(k线);
}
}
Ok()
self. = ;
self.()
}
}
@@ -548,40 +533,56 @@ mod tests {
use super::*;
use crate::config::;
fn test_data_path() -> String {
let manifest = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
manifest
.parent()
.unwrap()
.join("btcusd-300-1777649100-1778398800.nb")
.to_string_lossy()
.to_string()
}
#[test]
fn test_普k序列指针一致性() {
let config = ::default();
let obs = ::(
"/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb",
Some(config),
)
.unwrap();
let obs_ref = obs.borrow();
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
// 1. Check that each 笔's 获取普K序列 returns K lines whose Rc pointers
// match entries in 普通K线序列
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!(" 武.中.标的K线 原始结束序号: {}", bi...);
println!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
info!("{}: 获取普K序列 返回空 (fallback failed)", i);
info!(" 文.中.标的K线 原始起始序号: {}", bi...);
info!(
" 武.中.标的K线 原始结束序号: {}",
bi.
.read()
.unwrap()
.
.
.load(Ordering::Relaxed)
);
info!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
} else {
// Check first element's pointer
let first_ptr = Rc::as_ptr(&pu_seq[0]);
let first_ptr = Arc::as_ptr(&pu_seq[0]);
let found = obs_ref
.K线序列
.iter()
.any(|k| Rc::as_ptr(k) == first_ptr);
.any(|k| Arc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
// Check if 文.中.标的K线 pointer is in 普通K线序列
let wen_ptr = Rc::as_ptr(&bi...K线);
let wen_found = obs_ref.K线序列.iter().any(|k| Rc::as_ptr(k) == wen_ptr);
println!(" 文.中.标的K线 在序列中: {}", wen_found);
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);
info!(" 文.中.标的K线 在序列中: {}", wen_found);
} else {
println!("{}: OK, 获取普K序列[0] 在序列中找到", i);
info!("{}: OK, 获取普K序列[0] 在序列中找到", i);
}
}
}
@@ -589,49 +590,494 @@ mod tests {
#[test]
fn test_pyo3_flow_pointer_consistency() {
// Simulate what the PyO3 读取数据文件 classmethod does:
// 1. Parse K lines from file
// 2. Create "K线Py { inner: Rc::new(k线) }" for each
// 3. Call observer.增加原始K线(k线_value)
let config = ::default();
let obs_ref = ::new("btcusd".into(), 300, config);
let file_path = "/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb";
let data = std::fs::read(file_path).unwrap();
let data = std::fs::read(test_data_path()).unwrap();
let size = 48;
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
// Simulate: K线Py { inner: Rc::new(k线) }
let _k线_py_inner = Rc::new(k线.clone());
// In the actual PyO3 path, (*普K.borrow().inner).clone() extracts K线 value
// which then gets Rc::wrapped inside the observer
obs_ref.borrow_mut().K线(k线);
let _k线_py_inner = Arc::new(k线.clone());
obs_ref.write().unwrap().K线(k线);
}
}
let obs = obs_ref.borrow();
println!("普通K线序列.len: {}", obs.K线序列.len());
println!("笔序列.len: {}", obs..len());
let obs = obs_ref.read().unwrap();
info!("普通K线序列.len: {}", obs.K线序列.len());
info!("笔序列.len: {}", obs..len());
// Now check: does 获取普K序列 return K lines whose pointers match 普通K线序列?
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 = Rc::as_ptr(&pu_seq[0]);
let found = obs.K线序列.iter().any(|k| Rc::as_ptr(k) == first_ptr);
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);
}
// Only print first few
if i < 5 {
println!("{}: OK, len={}", i, pu_seq.len());
info!("{}: OK, len={}", i, pu_seq.len());
}
}
}
}
// ============================================================
// 分型到笔的 Rc 指针一致性
// ============================================================
#[test]
fn test_分型到笔的文武Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
// 每个笔的文/武 分型 Rc 指针必须在 分型序列 中
for (i, bi) in obs_ref..iter().enumerate() {
let _ptr = Arc::as_ptr(&bi.);
let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr);
if !_found {
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 {
info!(
"笔 {}: 武(时间戳={}) 不在分型序列中!",
i,
bi..read().unwrap().()
);
}
}
}
// ============================================================
// 笔到线段的 Rc 指针一致性
// ============================================================
#[test]
fn test_笔到线段的基础序列Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
// 每个线段的基础序列中的笔 Rc 指针必须在 笔序列 中
for (i, seg) in obs_ref.线.iter().enumerate() {
for (j, bi_in_seg) in seg..read().unwrap().iter().enumerate() {
let bi_ptr = Arc::as_ptr(bi_in_seg);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found {
info!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j);
}
}
}
}
// ============================================================
// 中枢基础序列与源的 Rc 指针一致性
// ============================================================
#[test]
fn test_中枢基础序列与笔序列Rc指针一致() {
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
for (i, hub) in obs_ref._中枢序列.iter().enumerate() {
for (j, bi_in_hub) in hub..read().unwrap().iter().enumerate() {
let bi_ptr = Arc::as_ptr(bi_in_hub);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found {
info!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j);
}
}
}
for (i, hub) in obs_ref..iter().enumerate() {
for (j, seg_in_hub) in hub..read().unwrap().iter().enumerate() {
let seg_ptr = Arc::as_ptr(seg_in_hub);
let found = obs_ref.线.iter().any(|s| Arc::as_ptr(s) == seg_ptr);
if !found {
info!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j);
}
}
}
}
// ============================================================
// 重复计算一致性
// ============================================================
#[test]
fn test_重复计算后结果一致() {
let data = std::fs::read(test_data_path()).unwrap();
let size = 48;
let = || {
let config = ::default();
let obs_ref = ::new("btcusd".into(), 300, config);
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs_ref.write().unwrap().K线(k线);
}
}
let obs = obs_ref.read().unwrap();
(
obs..len(),
obs.线.len(),
obs..len(),
obs._中枢序列.len(),
)
};
let (1, 1, 1, 1) = ();
let (2, 2, 2, 2) = ();
assert_eq!(1, 2, "重复计算笔数不一致");
assert_eq!(1, 2, "重复计算线段数不一致");
assert_eq!(1, 2, "重复计算中枢数不一致");
assert_eq!(1, 2, "重复计算笔中枢数不一致");
info!(
"两次计算结果一致: 笔={}, 线段={}, 中枢={}, 笔中枢={}",
1, 1, 1, 1
);
}
// ============================================================
// 重置后重新投喂数据一致性
// ============================================================
#[test]
fn test_重置后重新投喂数据一致() {
let config = ::default();
let obs_ref = ::new("btcusd".into(), 300, config);
let data = std::fs::read(test_data_path()).unwrap();
let size = 48;
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs_ref.write().unwrap().K线(k线);
}
}
let = obs_ref.read().unwrap()..len();
let = obs_ref.read().unwrap().线.len();
// 重置
obs_ref.write().unwrap().();
assert_eq!(obs_ref.read().unwrap()..len(), 0);
assert_eq!(obs_ref.read().unwrap().线.len(), 0);
// 重新投喂
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs_ref.write().unwrap().K线(k线);
}
}
let = obs_ref.read().unwrap()..len();
let = obs_ref.read().unwrap().线.len();
assert_eq!(, , "重置后重新投喂笔数不一致");
assert_eq!(, , "重置后重新投喂线段数不一致");
info!("重置后重投一致: 笔={}, 线段={}", , );
}
// ============================================================
// RefCell 借用安全性 — 连续大量操作不应 panic
// ============================================================
#[test]
fn test_RefCell借用安全性_连续读取不panic() {
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
// 连续大量读取所有 RefCell 字段,不应 panic
for _ in 0..100 {
for bi in &obs_ref. {
let _标识 = bi..read().unwrap().clone();
let _wu = bi..read().unwrap().clone();
let _基础序列 = bi..read().unwrap().len();
let _特征序列 = bi..read().unwrap().len();
let _模式 = bi..read().unwrap().clone();
let _实中枢 = bi._中枢序列.read().unwrap().len();
let _虚中枢 = bi._中枢序列.read().unwrap().len();
let _合中枢 = bi._中枢序列.read().unwrap().len();
let _确认K = bi.K线.read().unwrap().is_some();
let _序号 = bi..load(Ordering::Relaxed);
let _有效性 = bi..load(Ordering::Relaxed);
let _短路 = bi..load(Ordering::Relaxed);
let _前一缺口 = *bi..read().unwrap();
}
for seg in &obs_ref.线 {
let _ = seg..read().unwrap().clone();
let _ = seg..read().unwrap().len();
}
}
// 到达这里 = 无 panic
}
#[test]
fn test_RefCell借用安全性_交替读写不panic() {
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
// 交替读写 RefCell 字段 — 先读再写同字段,分离 borrow 作用域
if !obs_ref..is_empty() {
let bi = &obs_ref.[0];
// 读
let old_mode = bi..read().unwrap().clone();
// Ref 已释放,可以写
*bi..write().unwrap() = "测试模式".into();
let new_mode = bi..read().unwrap().clone();
assert_eq!(new_mode, "测试模式");
// 恢复
*bi..write().unwrap() = old_mode;
// 读武
let old_wu = bi..read().unwrap().clone();
// Ref 已释放,可以检查
assert!(Arc::as_ptr(&old_wu) == Arc::as_ptr(&old_wu));
}
}
// ============================================================
// 缠K 到 分型 的 Rc 指针一致性
// ============================================================
#[test]
fn test_缠K到分型的Rc指针一致性() {
let obs = ::new("btcusd".into(), 300, Default::default());
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
let obs_ref = obs.read().unwrap();
// 每个分型的左/中/右 缠K 指针必须在 缠论K线序列 中
for (i, f) in obs_ref..iter().enumerate() {
let _ptr = Arc::as_ptr(&f.);
let _found = obs_ref.K线序列.iter().any(|k| Arc::as_ptr(k) == _ptr);
if !_found {
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 {
info!("分型 {} 的 左 不在缠论K线序列中!", i);
}
}
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 {
info!("分型 {} 的 右 不在缠论K线序列中!", i);
}
}
}
}
// ========== 跨线程安全测试 ==========
// 编译期断言:核心类型必须实现 Send + Sync
#[allow(dead_code)]
fn _Send_Sync_编译期检查() {
fn _需要_Send<T: Send>() {}
fn _需要_Sync<T: Sync>() {}
fn _需要_Send_Sync<T: Send + Sync>() {}
// 核心数据结构
_需要_Send::<crate::kline::chan_kline::K线>();
_需要_Send::<crate::structure::dash_line::线>();
_需要_Send::<crate::algorithm::hub::>();
_需要_Send_Sync::<crate::kline::chan_kline::K线>();
_需要_Send_Sync::<crate::structure::dash_line::线>();
_需要_Send_Sync::<crate::algorithm::hub::>();
// Arc 包装后的 Send 检查
_需要_Send::<Arc<crate::kline::chan_kline::K线>>();
_需要_Send::<Arc<crate::structure::dash_line::线>>();
_需要_Send::<Arc<crate::algorithm::hub::>>();
// 观察者
_需要_Send::<crate::business::observer::>();
}
/// 测试:Arc<缠论K线> 可跨线程传递
#[test]
fn test_跨线程_缠论K线_Send() {
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
let k = Arc::new(K线::K(
"test", 1000, 100.0, 110.0, 90.0, 95.0, 1000.0, 0, 300,
));
let ck = K线::K(
1000,
110.0,
90.0,
crate::types::::,
None,
1,
k,
None,
);
let arc_ck = Arc::new(ck);
let arc_ck2 = Arc::clone(&arc_ck);
let handle = std::thread::spawn(move || {
let _ = arc_ck2;
42
});
assert_eq!(handle.join().unwrap(), 42);
assert!((arc_ck..get() - 110.0).abs() < 0.01);
}
/// 测试:Arc<虚线> 可跨线程传递
#[test]
fn test_跨线程_虚线_Send() {
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
use crate::structure::dash_line::线;
use crate::structure::fractal_obj::;
let k = Arc::new(K线::K(
"test", 1000, 100.0, 110.0, 90.0, 95.0, 1000.0, 0, 300,
));
let ck = Arc::new(K线::K(
1000,
110.0,
90.0,
crate::types::::,
None,
1,
k,
None,
));
let frac = Arc::new(::new(None, Arc::clone(&ck), None));
let frac2 = Arc::new(::new(None, ck, None));
let dash = Arc::new(线::(frac, frac2, true));
let dash2 = Arc::clone(&dash);
let handle = std::thread::spawn(move || {
let _ = Arc::as_ptr(&dash2.);
99
});
assert_eq!(handle.join().unwrap(), 99);
assert_eq!(dash..read().unwrap().as_str(), "");
}
/// 测试:Arc<中枢> 可跨线程传递
#[test]
fn test_跨线程_中枢_Send() {
let hub = crate::algorithm::hub::::new(1, "test".into(), 1, vec![]);
let arc_hub = Arc::new(hub);
let arc_hub2 = Arc::clone(&arc_hub);
let handle = std::thread::spawn(move || {
let _ = arc_hub2..load(Ordering::Relaxed);
77
});
assert_eq!(handle.join().unwrap(), 77);
assert_eq!(arc_hub..load(Ordering::Relaxed), 1);
}
/// 测试:多线程并发读取 观察者
#[test]
fn test_跨线程_观察者_多线程读取() {
let obs = ::new("btcusd".into(), 86400, Default::default());
let obs2 = Arc::clone(&obs);
let obs3 = Arc::clone(&obs);
let h1 = std::thread::spawn(move || {
let guard = obs2.read().unwrap();
guard..clone()
});
let h2 = std::thread::spawn(move || {
let guard = obs3.read().unwrap();
guard.
});
assert_eq!(h1.join().unwrap(), "btcusd");
assert_eq!(h2.join().unwrap(), 86400);
}
/// 测试:Cell 字段跨线程读写不 panic
#[test]
fn test_跨线程_Cell字段_并发读写() {
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
let k = Arc::new(K线::K(
"test", 1000, 100.0, 110.0, 90.0, 95.0, 1000.0, 0, 300,
));
let ck = Arc::new(K线::K(
1000,
110.0,
90.0,
crate::types::::,
None,
1,
k,
None,
));
let ck2 = Arc::clone(&ck);
let handle = std::thread::spawn(move || {
let = ck2..load(Ordering::Relaxed);
let = ck2..get();
(, )
});
let (, ) = handle.join().unwrap();
assert_eq!(, 0); // 序号 初始值为 0
assert!(( - 110.0).abs() < 0.01);
ck..store(2, Ordering::Relaxed);
assert_eq!(ck..load(Ordering::Relaxed), 2);
}
/// 测试:Arc<观察者> 直接跨线程传递
#[test]
fn test_跨线程_观察者_所有权转移() {
let obs = ::new("ethusd".into(), 7200, Default::default());
let handle = std::thread::spawn(move || {
let guard = obs.read().unwrap();
(guard..clone(), guard.)
});
let (, ) = handle.join().unwrap();
assert_eq!(, "ethusd");
assert_eq!(, 7200);
}
}
+16 -1
View File
@@ -34,6 +34,7 @@ pub struct K线合成器 {
}
impl K线合成器 {
/// 创建K线合成器,按周期升序排列,初始化当前K线和合成K线列表
pub fn new(: String, : Vec<i64>) -> 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");
}
( / ) *
}
+267 -11
View File
@@ -22,7 +22,8 @@
* SOFTWARE.
*/
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
use tracing::warn;
fn is_infinite_f64(v: &f64) -> bool {
v.is_infinite()
@@ -30,118 +31,285 @@ 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<String>,
/// 均线周期列表: [5, 10, 20, ...]
#[serde(default)]
pub 线_周期列表: Vec<i64>,
// ---- 推送/显示 ----
/// 是否启用图表展示
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,
}
fn deserialize_指标计算方式<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
const VALID: &[&str] = &[
"",
"",
"",
"",
"高低均值",
"高低收均值",
"开高低收均值",
];
const DEFAULT: &str = "";
if VALID.contains(&s.as_str()) {
Ok(s)
} else {
warn!(
"[配置警告] 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
);
Ok(DEFAULT.to_string())
}
}
fn deserialize_买卖点_指标模式<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
const VALID: &[&str] = &["任意", "配置", "全量", "相对"];
const DEFAULT: &str = "配置";
if VALID.contains(&s.as_str()) {
Ok(s)
} else {
warn!(
"[配置警告] 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
);
Ok(DEFAULT.to_string())
}
}
fn deserialize_线段内部背驰_模式<'de, D>(deserializer: D) -> Result<String, D::Error>
where
D: Deserializer<'de>,
{
let s = String::deserialize(deserializer)?;
const VALID: &[&str] = &["任意", "配置", "全量", "相对"];
const DEFAULT: &str = "相对";
if VALID.contains(&s.as_str()) {
Ok(s)
} else {
warn!(
"[配置警告] 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
);
Ok(DEFAULT.to_string())
}
}
impl Default for {
fn default() -> Self {
Self {
@@ -168,6 +336,7 @@ impl Default for 缠论配置 {
线: true,
: String::new(),
: true,
BOLL: false,
: "".into(),
线_快线周期: 13,
线_慢线周期: 31,
@@ -181,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,
@@ -226,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<Self, serde_json::Error> {
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<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let config = Self::from_json(&content)?;
@@ -280,11 +503,11 @@ impl 缠论配置 {
serde_json::Map<String, serde_json::Value>,
> = std::collections::BTreeMap::new();
for (key, value) in map {
if let Some(pos) = key.find('_') {
if let Ok(num) = key[..pos].parse::<i64>() {
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::<i64>()
{
let field = key[pos + 1..].to_string();
groups.entry(num).or_default().insert(field, value.clone());
}
}
for (num, fields) in groups {
@@ -310,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());
}
}
}
@@ -353,6 +576,39 @@ mod tests {
assert_eq!(config., 1);
}
#[test]
fn test_invalid_enum_field_fallback() {
// 无效的 指标计算方式 → 回退默认值 "收"
let json = r#"{"指标计算方式": "胡写"}"#;
let config: = serde_json::from_str(json).unwrap();
assert_eq!(config., "");
// 有效的 指标计算方式 → 正常通过
let json = r#"{"指标计算方式": "开"}"#;
let config: = serde_json::from_str(json).unwrap();
assert_eq!(config., "");
// 无效的 买卖点_指标模式 → 回退默认值 "配置"
let json = r#"{"买卖点_指标模式": "瞎搞"}"#;
let config: = serde_json::from_str(json).unwrap();
assert_eq!(config._指标模式, "配置");
// 有效的 买卖点_指标模式 → 正常通过
let json = r#"{"买卖点_指标模式": "任意"}"#;
let config: = serde_json::from_str(json).unwrap();
assert_eq!(config._指标模式, "任意");
// 无效的 线段内部背驰_模式 → 回退默认值 "相对"
let json = r#"{"线段内部背驰_模式": "乱来"}"#;
let config: = serde_json::from_str(json).unwrap();
assert_eq!(config.线_模式, "相对");
// 有效的 线段内部背驰_模式 → 正常通过
let json = r#"{"线段内部背驰_模式": "全量"}"#;
let config: = serde_json::from_str(json).unwrap();
assert_eq!(config.线_模式, "全量");
}
#[test]
fn test_不推送() {
let config = ::default();
+169
View File
@@ -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<f64>,
/// 内部均值缓存(不序列化)
#[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::<f64>() / 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.);
}
}
+301
View File
@@ -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<K线>], : &) {
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<K线>], : &) {
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<K线>],
: 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<K线>],
: 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)
}
}
}
}
+202
View File
@@ -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<String, f64>),
/// 单值指标组 (key → 值)
(HashMap<String, f64>),
}
/// 指标容器 — 挂载在每根 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<String, Option<>>,
}
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<String, f64>> {
match self._数据.get("均线")?.as_ref()? {
::线(m) => Some(m),
_ => None,
}
}
/// 获取均线组可变引用
pub fn 线_mut(&mut self) -> Option<&mut HashMap<String, f64>> {
match self._数据.get_mut("均线")?.as_mut()? {
::线(m) => Some(m),
_ => None,
}
}
/// 获取单值指标组
pub fn (&self) -> Option<&HashMap<String, f64>> {
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(", "))
}
}
+55
View File
@@ -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<f64>,
/// K 值
pub K: Option<f64>,
/// D 值
pub D: Option<f64>,
/// J 值 (3K - 2D)
pub J: Option<f64>,
/// 历史最高价队列(滑动窗口)
pub : Vec<f64>,
/// 历史最低价队列(滑动窗口)
pub : Vec<f64>,
/// 前一个 RSV(用于平滑递推)
pub RSV: Option<f64>,
/// 前一个 K(用于平滑递推)
pub K: Option<f64>,
/// 前一个 D(用于平滑递推)
pub D: Option<f64>,
}
@@ -77,6 +96,9 @@ impl Default for 随机指标 {
impl {
/// 首次计算 KDJ(无历史数据时)
#[allow(clippy::too_many_arguments)]
/// 首次计算 KDJ — 无历史数据时的初始计算
#[allow(clippy::too_many_arguments)]
pub fn (
: f64,
: f64,
@@ -110,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,
+36
View File
@@ -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<f64>,
/// DEA 值(DIF 的信号线)
pub DEA: Option<f64>,
/// MACD 柱(2 * (DIF - DEA)
#[serde(rename = "MACD柱")]
#[serde(default)]
pub MACD柱: f64,
/// 快线 EMA 值
pub 线EMA: Option<f64>,
/// 慢线 EMA 值
pub 线EMA: Option<f64>,
/// DEA EMA 值
pub DEA_EMA: Option<f64>,
}
@@ -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
+6
View File
@@ -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::;
+41 -5
View File
@@ -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<i64>,
/// RSI 值
pub RSI: Option<f64>,
/// 平均上涨幅度
pub : Option<f64>,
/// 平均下跌幅度
pub : Option<f64>,
/// 当前上涨幅度
pub : f64,
/// 当前下跌幅度
pub : f64,
/// 平滑系数 (1/周期)
pub : f64,
/// RSI SMA 值
pub RSI_SMA: Option<f64>,
/// RSI 历史队列(用于滚动计算)
pub RSI历史队列: Vec<f64>,
}
@@ -95,6 +110,31 @@ impl 相对强弱指数 {
}
}
/// 首次计算 RSI 指标 — 从 K线 取值后计算
pub fn _K线(
k线: &K线,
: &str,
: i64,
: f64,
: f64,
RSI_SMA周期: Option<i64>,
) -> 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))
+88 -20
View File
@@ -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::rc::Rc;
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<S>(
val: &RwLock<crate::indicators::>,
ser: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
val.read().unwrap().serialize(ser)
}
/// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>
pub fn deserialize<'de, D>(de: D) -> Result<RwLock<crate::indicators::>, 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];
{
@@ -125,7 +179,23 @@ impl K线 {
Self::from_bytes(, , )
}
/// 解析原始数据 — 只提取时间戳+OHLCV,不构造 K线
pub fn (: &[u8]) -> Option<(i64, f64, f64, f64, f64, f64)> {
if .len() < 48 {
return None;
}
let mut reader = &[..48];
let = reader.read_f64::<BigEndian>().ok()? as i64;
let = reader.read_f64::<BigEndian>().ok()?;
let = reader.read_f64::<BigEndian>().ok()?;
let = reader.read_f64::<BigEndian>().ok()?;
let = reader.read_f64::<BigEndian>().ok()?;
let = reader.read_f64::<BigEndian>().ok()?;
Some((, , , , , ))
}
/// 创建普通K线
#[allow(clippy::too_many_arguments)]
pub fn K(
: &str,
: i64,
@@ -147,9 +217,7 @@ impl K线 {
,
,
,
macd: None,
rsi: None,
kdj: None,
: RwLock::new(::new()),
}
}
@@ -177,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;
@@ -202,12 +270,12 @@ impl K线 {
Some(&[_idx..=_idx])
}
/// 截取Rc<K线>序列中从始到终的片段
pub fn rc(: &[Rc<Self>], : &Rc<Self>, : &Rc<Self>) -> Vec<Rc<Self>> {
let _ptr = Rc::as_ptr();
let _ptr = Rc::as_ptr();
let _idx = .iter().position(|k| Rc::as_ptr(k) == _ptr);
let _idx = .iter().position(|k| Rc::as_ptr(k) == _ptr);
/// 截取Arc<K线>序列中从始到终的片段
pub fn rc(: &[Arc<Self>], : &Arc<Self>, : &Arc<Self>) -> Vec<Arc<Self>> {
let _ptr = Arc::as_ptr();
let _ptr = Arc::as_ptr();
let _idx = .iter().position(|k| Arc::as_ptr(k) == _ptr);
let _idx = .iter().position(|k| Arc::as_ptr(k) == _ptr);
match (_idx, _idx) {
(Some(s), Some(e)) => [s..=e].to_vec(),
_ => Vec::new(),
+222 -292
View File
@@ -23,31 +23,68 @@
*/
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 std::rc::Rc;
use std::collections::HashSet;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 缠论K线 — 经包含处理过后的K线
#[derive(Debug, Clone)]
///
/// 部分字段使用 AtomicI64 / SyncF64 / RwLock 实现内部可变性,确保包含处理
/// 原地修改时 Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。
#[derive(Debug)]
pub struct K线 {
pub : i64,
pub : i64,
pub : f64,
pub : f64,
pub : ,
pub : Option<>,
/// 缠K序号(在缠论K线序列中的位置)
pub : AtomicI64,
/// Unix 时间戳(秒)
pub : AtomicI64,
/// 缠K最高价(经包含处理后可能高于原始K线)
pub : SyncF64,
/// 缠K最低价(经包含处理后可能低于原始K线)
pub : SyncF64,
/// 缠K方向(向上/向下)
pub : RwLock<>,
/// 分型结构(顶/底/上/下/散)
pub : RwLock<Option<>>,
/// 周期(秒)
pub : i64,
/// 品种标识
pub : String,
pub : f64,
/// 分型特征值(历史高低点极值,用于背驰判断)
pub : SyncF64,
/// 原始K线起始序号(包含处理前)
pub : i64,
pub : i64,
pub K线: Rc<K线>,
pub : std::cell::RefCell<Vec<String>>,
/// 原始K线结束序号(包含处理后更新)
pub : AtomicI64,
/// 标的原始K线(该缠K对应的普K)
pub K线: RwLock<Arc<K线>>,
/// 买卖点信息集合
pub : RwLock<HashSet<String>>,
}
impl Clone for K线 {
fn clone(&self) -> Self {
Self {
: AtomicI64::new(self..load(Ordering::Relaxed)),
: AtomicI64::new(self..load(Ordering::Relaxed)),
: SyncF64::new(self..get()),
: SyncF64::new(self..get()),
: RwLock::new(*self..read().unwrap()),
: RwLock::new(*self..read().unwrap()),
: self.,
: self..clone(),
: SyncF64::new(self..get()),
: self.,
: AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())),
: RwLock::new(self..read().unwrap().clone()),
}
}
}
impl std::fmt::Display for K线 {
@@ -57,13 +94,16 @@ impl std::fmt::Display for 缠论K线 {
f,
"{}<{}, {}, {}, {}, {}, {}, {}>",
self.,
self.,
self..map_or("None".to_string(), |fx| fx.to_string()),
self..load(Ordering::Relaxed),
self.
.read()
.unwrap()
.map_or("None".to_string(), |fx| fx.to_string()),
self.,
self.,
self.,
format_f64_g(self.),
format_f64_g(self.)
*self..read().unwrap(),
self..load(Ordering::Relaxed),
format_f64_g(self..get()),
format_f64_g(self..get())
)
}
}
@@ -72,34 +112,36 @@ impl 缠论K线 {
/// 创建镜像(浅拷贝 Rc 引用)
pub fn (&self) -> Self {
Self {
: self.,
: self.,
: self.,
: self.,
: self.,
: self.,
: AtomicI64::new(self..load(Ordering::Relaxed)),
: AtomicI64::new(self..load(Ordering::Relaxed)),
: SyncF64::new(self..get()),
: SyncF64::new(self..get()),
: RwLock::new(*self..read().unwrap()),
: RwLock::new(*self..read().unwrap()),
: self.,
: self..clone(),
: self.,
: SyncF64::new(self..get()),
: self.,
: self.,
K线: Rc::clone(&self.K线),
: std::cell::RefCell::new(self..borrow().clone()),
: AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())),
: RwLock::new(self..read().unwrap().clone()),
}
}
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
pub fn MACD柱子匹配(&self) -> bool {
match self. {
let = self.K线.read().unwrap();
let = ..read().unwrap();
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref macd) = self.K线.macd {
if let Some(macd) = .macd() {
macd.MACD柱 < 0.0
} else {
false
}
}
Some(::) | Some(::) => {
if let Some(ref macd) = self.K线.macd {
if let Some(macd) = .macd() {
macd.MACD柱 > 0.0
} else {
false
@@ -111,9 +153,11 @@ impl 缠论K线 {
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
pub fn RSI匹配(&self) -> bool {
match self. {
let = self.K线.read().unwrap();
let = ..read().unwrap();
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref rsi) = self.K线.rsi {
if let Some(rsi) = .rsi() {
match (rsi.RSI, rsi.RSI_SMA) {
(Some(r), Some(sma)) => r < sma,
_ => false,
@@ -123,7 +167,7 @@ impl 缠论K线 {
}
}
Some(::) | Some(::) => {
if let Some(ref rsi) = self.K线.rsi {
if let Some(rsi) = .rsi() {
match (rsi.RSI, rsi.RSI_SMA) {
(Some(r), Some(sma)) => r > sma,
_ => false,
@@ -138,9 +182,11 @@ impl 缠论K线 {
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
pub fn KDJ匹配(&self) -> bool {
match self. {
let = self.K线.read().unwrap();
let = ..read().unwrap();
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref kdj) = self.K线.kdj {
if let Some(kdj) = .kdj() {
match (kdj.K, kdj.D) {
(Some(k), Some(d)) => k < d,
_ => false,
@@ -150,7 +196,7 @@ impl 缠论K线 {
}
}
Some(::) | Some(::) => {
if let Some(ref kdj) = self.K线.kdj {
if let Some(kdj) = .kdj() {
match (kdj.K, kdj.D) {
(Some(k), Some(d)) => k > d,
_ => false,
@@ -164,28 +210,31 @@ impl 缠论K线 {
}
/// 时间戳对齐 — 从基线序列中找匹配的时间戳
pub fn (线: &[Rc<K线>], k线: &K线) -> i64 {
pub fn (线: &[Arc<K线>], k线: &K线) -> i64 {
if let Some() = 线.first() {
for k in 线.iter().rev() {
if . < k线. {
if k线. <= k. && k. <= k线. + k线.
if k线..load(Ordering::Relaxed) <= k..load(Ordering::Relaxed)
&& k..load(Ordering::Relaxed)
<= k线..load(Ordering::Relaxed) + k线.
&& (k线..get() - k..get()).abs() < f64::EPSILON
{
if (k线. - k.).abs() < f64::EPSILON {
return k.;
}
return k..load(Ordering::Relaxed);
}
} else if k. <= k线. && k线. <= k. + k.
} else if k..load(Ordering::Relaxed) <= k线..load(Ordering::Relaxed)
&& k线..load(Ordering::Relaxed)
<= k..load(Ordering::Relaxed) + k.
&& (k线..get() - k..get()).abs() < f64::EPSILON
{
if (k线. - k.).abs() < f64::EPSILON {
return k.;
}
return k..load(Ordering::Relaxed);
}
}
}
k线.
k线..load(Ordering::Relaxed)
}
/// 创建缠K
#[allow(clippy::too_many_arguments)]
pub fn K(
: i64,
: f64,
@@ -193,7 +242,7 @@ impl 缠论K线 {
: ,
: Option<>,
: i64,
k: Rc<K线>,
k: Arc<K线>,
: Option<&K线>,
) -> Self {
if .is_nan() || .is_nan() {
@@ -204,25 +253,28 @@ impl 缠论K线 {
let = k.;
let = k..clone();
let mut = Self {
: 0,
,
,
,
,
: ,
let = Self {
: AtomicI64::new(0),
: AtomicI64::new(),
: SyncF64::new(),
: SyncF64::new(),
: RwLock::new(),
: RwLock::new(),
,
,
: ,
: SyncF64::new(),
: ,
: ,
K线: k,
: std::cell::RefCell::new(Vec::new()),
: AtomicI64::new(),
K线: RwLock::new(k),
: RwLock::new(HashSet::new()),
};
if let Some() = {
. = . + 1;
let = ::(., ., ., .);
.
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
let =
::(..get(), ..get(), ..get(), ..get());
if .() {
panic!(
"创建缠K 包含关系: {:?}\n 之前: {}\n 当前: {}",
@@ -236,13 +288,13 @@ impl 缠论K线 {
/// 兼并(合并)处理 — 缠论包含处理的核心算法
///
/// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None
pub fn (
pub fn _兼并(
K: Option<&K线>,
K: &mut K线,
K: &Rc<K线>,
K: &K线,
K: &Arc<K线>,
: &,
) -> (Option<Rc<K线>>, Option<String>) {
let = ::(K., K., K., K.);
) -> (Option<Arc<K线>>, Option<String>) {
let = ::(K..get(), K..get(), K., K.);
// 无包含关系 — 创建新元素追加
if !.() {
@@ -251,37 +303,47 @@ impl 缠论K线 {
} else {
Some(::)
};
let mut K = Self::K(
let K = Self::K(
K.,
K.,
K.,
K.(),
,
K.,
Rc::clone(K),
Arc::clone(K),
Some(K),
);
K. = K. + 1;
return (Some(Rc::new(K)), Some("添加".into()));
K
.
.store(K..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
return (Some(Arc::new(K)), Some("添加".into()));
}
// 重复提交检测 — 当序号相同时认为是重复提交K线
if K. == K. {
return (None, None);
if K. == K..load(Ordering::Relaxed) {
// no-op: 对齐 Python ... (Ellipsis)
}
// 序号连续性检查
if K. - 1 != K. && K. != K.
if K. - 1 != K..load(Ordering::Relaxed)
&& K. != K..load(Ordering::Relaxed)
{
panic!(
"兼并: 不可追加不连续元素 缠K.原始结束序号: {}, 当前普K.序号: {}",
K., K.
K..load(Ordering::Relaxed),
K.
);
}
// 包含关系 — 原地合并到当前缠K
let : fn(f64, f64) -> f64 = if let Some() = K {
if ::(., ., K., K.).()
if ::(
..get(),
..get(),
K..get(),
K..get(),
)
.()
{
f64::min
} else {
@@ -293,20 +355,22 @@ impl 缠论K线 {
// 逆序包含时更新时间和标的K线
if != :: {
K. = K.;
K.K线 = Rc::clone(K);
K..store(K., Ordering::Relaxed);
*K.K线.write().unwrap() = Arc::clone(K);
}
K. = (K., K.);
K. = (K., K.);
K. = K.;
K. = K.();
K..set((K..get(), K.));
K..set((K..get(), K.));
K..store(K., Ordering::Relaxed);
*K..write().unwrap() = K.();
if let Some() = K {
K. = . + 1;
K
.
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
}
if .K合并替换 {
(Some(Rc::new(K.())), Some("替换".into()))
(Some(Arc::new(K.())), Some("替换".into()))
} else {
(None, None)
}
@@ -317,158 +381,47 @@ impl 缠论K线 {
/// 返回 (状态, 形态)
pub fn (
mut K线: K线,
K序列: &mut Vec<Rc<K线>>,
K序列: &mut Vec<Rc<K线>>,
K序列: &mut Vec<Arc<K线>>,
K序列: &mut Vec<Arc<K线>>,
: &,
) -> (String, Option<Rc<>>) {
) -> (String, Option<Arc<>>) {
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 = Rc::new(K线);
K序列.push(K线_rc);
K序列.push(Arc::new(K线));
} else {
let K = K序列.last().unwrap();
if K. == K线. {
// 同时间戳更新
// 同时间戳更新 — 替换 [-1]
K线. = K.;
if . {
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(Rc::new(K线));
K序列.push(Arc::new(K线));
} else {
if K. > 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(Rc::new(K线));
K序列.push(Arc::new(K线));
}
}
// 计算指标: 对齐 Python,仅当 计算指标 开启时执行
if . {
let n = K序列.len();
::(&K序列[n - 1], &K序列[..n - 1], );
}
// ---- 阶段2: 缠K合并 ----
let : String;
let K线_ref: &Rc<K线> = K序列.last().unwrap();
let K线_ref: &Arc<K线> = K序列.last().unwrap();
if !K序列.is_empty() {
let len = K序列.len();
let (, ) = K序列.split_at_mut(len - 1);
let K: Option<&K线> = .last().map(|rc| Rc::as_ref(rc));
let K_mut = Rc::make_mut(&mut [0]);
let (K, ) = Self::(K, K_mut, K线_ref, );
let K: Option<&K线> = .last().map(Arc::as_ref);
let K = &*[0];
let (K, ) = Self::_兼并(K, K, K线_ref, );
if let Some(k) = K {
match .as_deref() {
@@ -477,8 +430,6 @@ impl 缠论K线 {
= "创建".into();
}
Some("替换") => {
K序列.pop();
K序列.push(k);
= "替换".into();
}
_ => {
@@ -496,10 +447,10 @@ impl 缠论K线 {
K线_ref.(),
None,
K线_ref.,
Rc::clone(K序列.last().unwrap()),
Arc::clone(K序列.last().unwrap()),
None,
);
K序列.push(Rc::new(K));
K序列.push(Arc::new(K));
= "新建".into();
}
@@ -509,106 +460,85 @@ impl 缠论K线 {
}
let idx = K序列.len();
let = Rc::clone(&K序列[idx - 3]);
let = Rc::clone(&K序列[idx - 2]);
let = Rc::clone(&K序列[idx - 1]);
let = Arc::clone(&K序列[idx - 3]);
let = Arc::clone(&K序列[idx - 2]);
let = Arc::clone(&K序列[idx - 1]);
let = ::(&*, &*, &*, false, false);
// 需要通过 Rc::get_mut 或 RefCell 修改 中.分型
// 由于使用 Rc,中是不可变的。这里采用创建新 Rc 替换的方式。
// 但这是在 Vec 内部修改,需要使用 Rc::make_mut 或重新构建
// 对齐 Python:无条件设置 中.分型、中.分型特征值、右.分型特征值、右.分型
*K序列[idx - 2]..write().unwrap() = ;
if let Some() = {
// 只在分型未设置或需要更新时才修改缠K,以保持 Rc 指针不变
let = K序列[idx - 2].;
let = .is_none() || != Some();
if {
let _mut = Rc::make_mut(&mut K序列[idx - 2]);
_mut. = Some();
match {
:: => {
_mut. = _mut.;
let = K序列[idx - 1].;
if .is_none() {
let _mut = Rc::make_mut(&mut K序列[idx - 1]);
_mut. = _mut.;
_mut. = Some(::);
}
}
:: => {
_mut. = _mut.;
let = K序列[idx - 1].;
if .is_none() {
let _mut = Rc::make_mut(&mut K序列[idx - 1]);
_mut. = _mut.;
_mut. = Some(::);
}
}
:: => {
_mut. = _mut.;
let = K序列[idx - 1].;
if .is_none() {
let _mut = Rc::make_mut(&mut K序列[idx - 1]);
_mut. = _mut.;
_mut. = Some(::);
}
}
:: => {
_mut. = _mut.;
let = K序列[idx - 1].;
if .is_none() {
let _mut = Rc::make_mut(&mut K序列[idx - 1]);
_mut. = _mut.;
_mut. = Some(::);
}
}
:: => {}
match {
:: => {
K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::);
}
:: => {
K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::);
}
:: => {
K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::);
}
:: => {
K序列[idx - 2]..set(K序列[idx - 2]..get());
K序列[idx - 1]..set(K序列[idx - 1]..get());
*K序列[idx - 1]..write().unwrap() = Some(::);
}
:: => {}
}
let = if matches!(, :: | ::) {
// Python: 形态 = 分型(中, 右, None) — 左=中K线, 中=右K线, 右=None
Rc::new(::new(
Some(Rc::clone(&K序列[idx - 2])),
Rc::clone(&K序列[idx - 1]),
Arc::new(::new(
Some(Arc::clone(&K序列[idx - 2])),
Arc::clone(&K序列[idx - 1]),
None,
))
} else {
Rc::new(::new(
Some(Rc::clone(&K序列[idx - 3])),
Rc::clone(&K序列[idx - 2]),
Some(Rc::clone(&K序列[idx - 1])),
Arc::new(::new(
Some(Arc::clone(&K序列[idx - 3])),
Arc::clone(&K序列[idx - 2]),
Some(Arc::clone(&K序列[idx - 1])),
))
};
return (, Some());
}
(, None)
// 对齐 Python:结构为 None 时仍创建并返回分型
let = Arc::new(::new(
Some(Arc::clone(&K序列[idx - 3])),
Arc::clone(&K序列[idx - 2]),
Some(Arc::clone(&K序列[idx - 1])),
));
(, Some())
}
/// 截取缠K序列从始到终
pub fn (
: &[Rc<K线>], : &K线, : &K线
) -> Option<Vec<Rc<K线>>> {
let _idx =
.iter()
.position(|k| Rc::as_ptr(k) == ( as *const _))?;
let _idx =
.iter()
.position(|k| Rc::as_ptr(k) == ( as *const _))?;
: &[Arc<K线>],
: &K线,
: &K线,
) -> Option<Vec<Arc<K线>>> {
let _idx = .iter().position(|k| std::ptr::eq(Arc::as_ptr(k), ))?;
let _idx = .iter().position(|k| std::ptr::eq(Arc::as_ptr(k), ))?;
Some([_idx..=_idx].to_vec())
}
}
impl crate::types::fractal:: for K线 {
fn (&self) -> f64 {
self.
self..get()
}
fn (&self) -> f64 {
self.
self..get()
}
}
@@ -623,11 +553,11 @@ mod tests {
#[test]
fn test_创建缠K_basic() {
let pk = Rc::new(make_普K(1000, 100.0, 110.0, 95.0, 105.0, 0));
let pk = Arc::new(make_普K(1000, 100.0, 110.0, 95.0, 105.0, 0));
let ck = K线::K(1000, 110.0, 95.0, ::, None, 0, pk, None);
assert_eq!(ck., 110.0);
assert_eq!(ck., 95.0);
assert_eq!(ck., 0);
assert_eq!(ck..get(), 110.0);
assert_eq!(ck..get(), 95.0);
assert_eq!(ck..load(Ordering::Relaxed), 0);
}
#[test]
+31 -33
View File
@@ -77,38 +77,36 @@ fn 测试_读取数据(文件路径: &str) {
let = Instant::now();
let = ::default().();
match ::(, Some()) {
Ok() => {
let = .borrow();
let = .elapsed();
println!(
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
,
.K线序列.len()
);
println!("符号: {}", .);
println!("周期: {}", .);
println!("缠K数量: {}", .K线序列.len());
println!("分型数量: {}", ..len());
println!("笔数量: {}", ..len());
println!("笔中枢数量: {}", ._中枢序列.len());
println!("线段数量: {}", .线.len());
println!("中枢数量: {}", ..len());
println!("扩展线段数量: {}", .线.len());
println!("线段_线段序列数量: {}", .线_线段序列.len());
println!(
"扩展线段_扩展线段数量: {}",
.线_扩展线段.len()
);
let = ::new("".into(), 0, ::default());
.write()
.unwrap()
.(, )
.expect("读取数据文件失败");
let = .read().unwrap();
let = .elapsed();
println!(
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
,
.K线序列.len()
);
println!("符号: {}", .);
println!("周期: {}", .);
println!("缠K数量: {}", .K线序列.len());
println!("分型数量: {}", ..len());
println!("数量: {}", ..len());
println!("笔中枢数量: {}", ._中枢序列.len());
println!("线段数量: {}", .线.len());
println!("中枢数量: {}", ..len());
println!("扩展线段数量: {}", .线.len());
println!("线段_线段序列数量: {}", .线_线段序列.len());
println!(
"扩展线段_扩展线段数量: {}",
.线_扩展线段.len()
);
println!("\n===== 保存分析数据 =====\n");
._保存数据(None);
}
Err(e) => {
eprintln!("读取失败: {}", e);
std::process::exit(1);
}
}
println!("\n===== 保存分析数据 =====\n");
._保存数据(None);
}
/// 测试_周期合成 — 多周期合成分析
@@ -162,7 +160,7 @@ fn 测试_周期合成(文件路径: &str) {
// Display stats per period
for &p in &[, * 5, * 5 * 6] {
if let Some() = .(p) {
let = .borrow();
let = .read().unwrap();
println!(
"周期<{}>: 缠K={}, 分型={}, 笔={}, 线段={}, 中枢={}",
p,
@@ -176,5 +174,5 @@ fn 测试_周期合成(文件路径: &str) {
}
println!("\n===== 保存分析数据 =====\n");
._保存数据();
._保存数据(None);
}
File diff suppressed because it is too large Load Diff
+16 -6
View File
@@ -24,20 +24,30 @@
use crate::structure::segment_feat::线;
use crate::types::;
use std::rc::Rc;
use std::sync::Arc;
/// 特征分型 — 由三个线段特征元素构成的分型
/// 特征分型 — 由三个线段特征元素构成的分型,用于线段特征序列的分型分析。
///
/// 字段:
/// 左: 左侧线段特征元素
/// 中: 中间线段特征元素
/// 右: 右侧线段特征元素
/// 结构: 由左中右三元素分析得出的分型结构(顶/底/散)
#[derive(Debug, Clone)]
pub struct {
pub : Rc<线>,
pub : Rc<线>,
pub : Rc<线>,
/// 左侧线段特征元素
pub : Arc<线>,
/// 中间线段特征元素
pub : Arc<线>,
/// 右侧线段特征元素
pub : Arc<线>,
/// 由左中右分析得出的分型结构
pub : ,
}
impl {
pub fn new(
: Rc<线>, : Rc<线>, : Rc<线>, :
: Arc<线>, : Arc<线>, : Arc<线>, :
) -> Self {
Self {
, , ,
+120 -48
View File
@@ -25,26 +25,59 @@
use crate::kline::chan_kline::K线;
use crate::types::;
use crate::types::;
use std::rc::Rc;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use tracing::warn;
/// 分型 — 由三根缠K构成(可能缺左或右)
/// 分型模式True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取
pub static : AtomicBool = AtomicBool::new(true);
/// 分型 — 由左中右三根缠论K线构成的顶/底分型。
///
/// 字段:
/// 左: 左侧缠K(可能为 None,表示序列起始处分型左翼缺失)
/// 中: 中间缠K(必须存在),分型的核心K线,其 `分型` 字段记录了该位置的分型结构
/// 右: 右侧缠K(可能为 None,表示序列末尾处分型右翼缺失)
/// 结构: 构造时从 `中.分型` 缓存的分型结构(上/下/顶/底/散)
/// 时间戳: 构造时从 `中.时间戳` 缓存的时刻
/// 分型特征值: 构造时从 `中.分型特征值` 缓存的值
///
/// "分型模式" 全局开关控制 时间戳/结构/分型特征值 的读取行为:
/// True(默认)— 返回构造时缓存值;False — 从 中 缠K 实时读取
#[derive(Debug, Clone)]
pub struct {
pub : Option<Rc<K线>>,
pub : Rc<K线>,
pub : Option<Rc<K线>>,
/// 左侧缠K(序列起始处分型可能为 None)
pub : Option<Arc<K线>>,
/// 中间缠K(必须存在)
pub : Arc<K线>,
/// 右侧缠K(序列末尾处分型可能为 None)
pub : Option<Arc<K线>>,
/// 构造时缓存的 分型结构(上/下/顶/底/散)
pub : ,
/// 构造时缓存的 时间戳
pub : i64,
/// 构造时缓存的 分型特征值(历史高低点极值)
pub : f64,
}
impl {
pub fn new(
: Option<Rc<K线>>, : Rc<K线>, : Option<Rc<K线>>
: Option<Arc<K线>>, : Arc<K线>, : Option<Arc<K线>>
) -> Self {
let = ..unwrap_or(::);
let = .;
let = .;
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();
Self {
,
,
@@ -55,20 +88,47 @@ impl 分型 {
}
}
/// 时间戳 — 根据 分型模式 决定返回缓存值(True)或实时值(False)
pub fn (&self) -> i64 {
if .load(Ordering::Relaxed) {
self.
} else {
self...load(Ordering::Relaxed)
}
}
/// 结构 — 根据 分型模式 决定返回缓存值(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()?;
let = self..as_ref()?;
Some((
::(., ., self.., self..),
::(self.., self.., ., .),
::(., ., ., .),
::(..get(), ..get(), self...get(), self...get()),
::(self...get(), self...get(), ..get(), ..get()),
::(..get(), ..get(), ..get(), ..get()),
))
}
/// 分型强度 — 返回 "强"/"中"/"弱"/"未知"
pub fn (&self) -> &str {
if self. != :: && self. != :: {
if self.() != :: && self.() != :: {
return "未知";
}
if self..is_none() || self..is_none() {
@@ -76,7 +136,7 @@ impl 分型 {
}
if let Some(ref ) = self.() {
if self. == :: {
if self.() == :: {
if .2.() {
return "";
} else if .2.() {
@@ -84,7 +144,7 @@ impl 分型 {
} else {
return "";
}
} else if self. == :: {
} else if self.() == :: {
if .2.() {
return "";
} else if .2.() {
@@ -95,19 +155,21 @@ impl 分型 {
}
}
if let (Some(ref ), Some(ref )) = (&self., &self.) {
if self. == :: {
if .K线. > .K线. {
if let (Some(), Some()) = (&self., &self.) {
if self.() == :: {
if .K线.read().unwrap(). > .K线.read().unwrap(). {
return "";
} else if .K线. > self..K线. {
} else if .K线.read().unwrap(). > self..K线.read().unwrap().
{
return "";
} else {
return "";
}
} else if self. == :: {
if .K线. < .K线. {
} else if self.() == :: {
if .K线.read().unwrap(). < .K线.read().unwrap(). {
return "";
} else if .K线. < self..K线. {
} else if .K线.read().unwrap(). < self..K线.read().unwrap().
{
return "";
} else {
return "";
@@ -119,17 +181,29 @@ impl 分型 {
/// MACD柱子分型匹配 — 检查左中右MACD柱形成顶/底形态
pub fn MACD柱子分型匹配(&self) -> bool {
if let (Some(ref ), Some(ref )) = (&self., &self.) {
if self. == :: {
if let (Some(ref macd), Some(ref macd), Some(ref macd)) =
(&.K线.macd, &self..K线.macd, &.K线.macd)
if let (Some(), Some()) = (&self., &self.) {
if self.() == :: {
let _k = .K线.read().unwrap();
let _k = self..K线.read().unwrap();
let _k = .K线.read().unwrap();
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
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 let (Some(ref macd), Some(ref macd), Some(ref macd)) =
(&.K线.macd, &self..K线.macd, &.K线.macd)
if self.() == :: {
let _k = .K线.read().unwrap();
let _k = self..K线.read().unwrap();
let _k = .K线.read().unwrap();
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd())
{
return macd.MACD柱 < macd.MACD柱 && macd.MACD柱 > macd.MACD柱;
}
@@ -139,47 +213,45 @@ impl 分型 {
}
/// 判断两个分型是否匹配
pub fn (: &Rc<>, : &Rc<>, : &str) -> bool {
match {
"" => Rc::as_ptr() == Rc::as_ptr(),
_ => false,
}
pub fn (: &Arc<>, : &Arc<>, _模式: &str) -> bool {
Arc::as_ptr() == Arc::as_ptr()
}
/// 从缠K序列中获取以指定缠K为中元素的分型
pub fn K序列中获取分型(
K线序列: &[Rc<K线>], : &Rc<K线>
K线序列: &[Arc<K线>],
: &Arc<K线>,
) -> Option<Self> {
let idx = K线序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr())?;
.position(|k| Arc::as_ptr(k) == Arc::as_ptr())?;
let = if idx > 0 {
Some(Rc::clone(&K线序列[idx - 1]))
Some(Arc::clone(&K线序列[idx - 1]))
} else {
None
};
let = if idx + 1 < K线序列.len() {
Some(Rc::clone(&K线序列[idx + 1]))
Some(Arc::clone(&K线序列[idx + 1]))
} else {
None
};
Some(Self::new(, Rc::clone(), ))
Some(Self::new(, Arc::clone(), ))
}
/// 向分型序列中添加新分型
pub fn (: &mut Vec<Rc<>>, : Rc<>) {
pub fn (: &mut Vec<Arc<>>, : Arc<>) {
if .is_empty() {
if . != :: && . != ::
if .() != :: && .() != ::
{
panic!("首次添加分型不为 顶底 {:?}", );
}
} else {
let = &[.len() - 1];
if . == . {
if .() == .() {
panic!("分型相同无法添加 {:?} {:?}", , );
}
if ..is_none() {
eprintln!("分型.向序列中添加, 分型异常 {:?}", );
warn!("分型.向序列中添加, 分型异常 {:?}", );
}
}
.push();
@@ -188,10 +260,10 @@ impl 分型 {
impl crate::types::fractal:: for {
fn (&self) -> f64 {
self..
self...get()
}
fn (&self) -> f64 {
self..
self...get()
}
}
@@ -200,9 +272,9 @@ impl std::fmt::Display for 分型 {
write!(
f,
"{}<{}, {}, None: {}, None: {}>",
self...unwrap_or(crate::types::::),
self.,
crate::utils::format_f64_g(self.),
self.(),
self.(),
crate::utils::format_f64_g(self.()),
if self..is_none() { "True" } else { "False" },
if self..is_none() { "True" } else { "False" },
)
+396 -66
View File
@@ -26,97 +26,148 @@ use crate::structure::dash_line::虚线;
use crate::structure::feat_fractal::;
use crate::structure::fractal_obj::;
use crate::types::{, };
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
/// 线段特征 — 特征序列元素内部是虚线的集合
#[derive(Debug, Clone)]
/// 线段特征 — 特征序列元素内部是虚线的集合
///
/// 在线段划分算法中,将同方向的笔归入一个特征序列元素,
/// 然后通过特征序列元素之间的分型关系判定线段终结。
///
/// 字段:
/// 序号: 特征序列元素编号
/// 标识: 标识字符串(如 "特征<虚线>"
/// 线段方向: 所属线段的方向(与元素包含的虚线方向相反)
/// 基础序列: 构成该特征序列元素的虚线序列
///
/// 计算属性:
/// 文 — 起点分型(取特征序列中分型特征值极值)
/// 武 — 终点分型(取特征序列中分型特征值极值)
/// 方向 — 线段方向的翻转
/// 高 / 低 — 文/武 中分型特征值的较大/较小者
#[derive(Debug)]
pub struct 线 {
/// 特征序列元素编号
pub : i64,
pub : String,
/// 标识字符串(如 "特征<虚线>"
pub : RwLock<String>,
/// 所属线段的方向
pub 线: ,
pub : Vec<Rc<线>>,
/// 构成该元素的虚线序列
pub : Vec<Arc<线>>,
}
impl Clone for 线 {
fn clone(&self) -> Self {
Self {
: self.,
: RwLock::new(self..read().unwrap().clone()),
线: self.线,
: self..clone(),
}
}
}
impl 线 {
pub fn new(: String, : Vec<Rc<线>>, 线: ) -> Self {
/// 新建线段特征(给定标识、基础序列和线段方向)
pub fn new(: String, : Vec<Arc<线>>, 线: ) -> 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) -> Rc<> {
pub fn (&self) -> Arc<> {
if self.线.() {
self.
self.
.iter()
.max_by(|a, b| {
a.
.
.partial_cmp(&b..)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a...cmp(&b..))
.then_with(|| a..().cmp(&b..()))
})
.map(|x| Rc::clone(&x.))
.unwrap_or_else(|| Rc::clone(&self.[0].))
.map(|x| Arc::clone(&x.))
.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| Rc::clone(&x.))
.unwrap_or_else(|| Rc::clone(&self.[0].))
.map(|x| Arc::clone(&x.))
.unwrap_or_else(|| Arc::clone(&self.[0].))
}
}
/// 武 — 取特征序列元素中分型特征值最大/最小的武分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Rc<> {
pub fn (&self) -> Arc<> {
if self.线.() {
self.
self.
.iter()
.max_by(|a, b| {
a.
.read()
.unwrap()
.
.partial_cmp(&b..)
.partial_cmp(&b..read().unwrap().)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a...cmp(&b..))
.then_with(|| {
a.
.read()
.unwrap()
.()
.cmp(&b..read().unwrap().())
})
})
.map(|x| Rc::clone(&x.))
.unwrap_or_else(|| Rc::clone(&self.[0].))
.map(|x| x..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..)
.partial_cmp(&a..read().unwrap().)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| b...cmp(&a..))
.then_with(|| {
a.
.read()
.unwrap()
.()
.cmp(&b..read().unwrap().())
})
})
.map(|x| Rc::clone(&x.))
.unwrap_or_else(|| Rc::clone(&self.[0].))
.map(|x| x..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.();
@@ -129,25 +180,25 @@ impl 线段特征 {
}
/// 向特征序列元素中添加虚线
pub fn (&mut self, 线: Rc<线>) -> Result<(), String> {
pub fn _添加(&mut self, 线: Arc<线>) -> Result<(), String> {
if 线.() == self.线 {
return Err("添加方向与线段方向相同".into());
}
self..push(线);
self..push(线);
Ok(())
}
/// 从特征序列元素中删除虚线
pub fn (&mut self, 线: &Rc<线>) -> Result<(), String> {
pub fn _删除(&mut self, 线: &Arc<线>) -> Result<(), String> {
if 线.() == self.() {
return Err("删除方向与特征序列方向相同".into());
}
if let Some(pos) = self
.
.
.iter()
.position(|x| Rc::as_ptr(x) == Rc::as_ptr(线))
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(线))
{
self..remove(pos);
self..remove(pos);
Ok(())
} else {
Err("待删除虚线不在特征序列中".into())
@@ -155,19 +206,19 @@ impl 线段特征 {
}
/// 新建特征序列元素
pub fn (线: Vec<Rc<线>>, 线: ) -> Self {
let = format!("特征<虚线>");
pub fn (线: Vec<Arc<线>>, 线: ) -> Self {
let = "特征<虚线>".to_string();
Self::new(, 线, 线)
}
/// 静态分析 — 从虚线序列生成特征序列元素列表
pub fn (
线: &[Rc<线>],
线: &[Arc<线>],
线: ,
: &str,
: bool,
) -> Vec<Rc<线>> {
let mut : Vec<Rc<线>> = Vec::new();
) -> Vec<Arc<线>> {
let mut : Vec<Arc<线>> = Vec::new();
// 需要被合并的方向集合
let : Vec<> = match {
@@ -179,9 +230,9 @@ impl 线段特征 {
// 情况1:方向相同(可能触发分型替换)
if 线.() == 线 {
if .len() >= 3 {
let = Rc::clone(&[.len() - 3]);
let = Rc::clone(&[.len() - 2]);
let = Rc::clone(&[.len() - 1]);
let = Arc::clone(&[.len() - 3]);
let = Arc::clone(&[.len() - 2]);
let = Arc::clone(&[.len() - 1]);
if let Some() = ::(&*, &*, &*, true, true) {
let = (线 == ::
@@ -192,16 +243,24 @@ impl 线段特征 {
&& 线.() < .());
if {
let 线 = ..iter().min_by_key(|o| o.).unwrap();
let 线 = ..iter().max_by_key(|o| o.).unwrap();
let 线 =
.
.iter()
.min_by_key(|o| o..load(Ordering::Relaxed))
.unwrap();
let 线 =
.
.iter()
.max_by_key(|o| o..load(Ordering::Relaxed))
.unwrap();
let fake = 线::(
Rc::clone(&线.),
Rc::clone(&线.),
Arc::clone(&线.),
线..read().unwrap().clone(),
false,
);
.pop();
let idx = .len() - 1;
[idx] = Rc::new(Self::(vec![Rc::new(fake)], 线));
[idx] = Arc::new(Self::(vec![Arc::new(fake)], 线));
}
}
}
@@ -210,7 +269,7 @@ impl 线段特征 {
// 情况2:方向不同(执行特征序列的合并/添加)
if .is_empty() {
.push(Rc::new(Self::(vec![Rc::clone(线)], 线)));
.push(Arc::new(Self::(vec![Arc::clone(线)], 线)));
continue;
}
@@ -225,26 +284,26 @@ impl 线段特征 {
)) {
// Clone-modify-replace
let mut = (*[_idx]).clone();
let _ = .(Rc::clone(线));
[_idx] = Rc::new();
let _ = ._添加(Arc::clone(线));
[_idx] = Arc::new();
} else {
.push(Rc::new(Self::(vec![Rc::clone(线)], 线)));
.push(Arc::new(Self::(vec![Arc::clone(线)], 线)));
}
}
}
/// 获取分型序列
pub fn (: &[Rc<线>]) -> Vec<> {
/// 获取分型序列 — 从连续的特征序列元素中提取特征分型
pub fn (: &[Arc<线>]) -> Vec<> {
let mut = Vec::new();
if .len() < 3 {
return ;
}
for i in 2...len() {
let = Rc::clone(&[i - 2]);
let = Rc::clone(&[i - 1]);
let = Rc::clone(&[i]);
let = Arc::clone(&[i - 2]);
let = Arc::clone(&[i - 1]);
let = Arc::clone(&[i]);
let = ::_对象(
&* as &dyn crate::types::fractal::,
@@ -270,18 +329,289 @@ 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()
)
}
}
}
#[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, : f64, : f64) -> K线 {
K线 {
,
,
,
: ,
: ,
..Default::default()
}
}
fn _创建缠K(
: i64,
: f64,
: f64,
: ,
: Option<>,
: i64,
) -> Arc<K线> {
let K = Arc::new(_创建K线(, , , , ));
Arc::new(K线::K(
, , , , , , K, None,
))
}
fn _创建顶分型(: i64, : f64, : f64, : i64) -> Arc<> {
let = _创建缠K(
- 2,
- 2.0,
- 2.0,
::,
Some(::),
- 2,
);
let = _创建缠K(, , , ::, Some(::), );
let = _创建缠K(
+ 2,
- 1.0,
- 1.0,
::,
Some(::),
+ 2,
);
Arc::new(::new(Some(), , Some()))
}
fn _创建底分型(: i64, : f64, : f64, : i64) -> Arc<> {
let = _创建缠K(
- 2,
+ 2.0,
+ 2.0,
::,
Some(::),
- 2,
);
let = K线::K(
,
,
,
::,
Some(::),
,
Arc::new(_创建K线(, , , , )),
None,
);
..set();
let = Arc::new();
let = _创建缠K(
+ 2,
+ 1.0,
+ 1.0,
::,
Some(::),
+ 2,
);
Arc::new(::new(Some(), , Some()))
}
fn _创建笔(
: i64,
: f64,
: f64,
: i64,
: f64,
: f64,
) -> Arc<线> {
let = _创建顶分型(, , , 1);
let = _创建底分型(, , , 2);
Arc::new(线::(, , true))
}
// ============================================================
// 文 — 取特征值最大/最小的分型
// 特征序列元素方向与线段方向相反:
// 向上线段 → 元素为向下笔(顶→底) → 文=顶分型 → 取max
// 向下线段 → 元素为向上笔(底→顶) → 文=底分型 → 取min
// ============================================================
#[test]
fn test_文_向上线段取最大特征值分型() {
// 向上线段,元素用向下笔(顶→底),文=顶分型
// 笔1: 顶(特征值=100)→底(80), 文=顶(100)
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
// 笔2: 顶(特征值=110)→底(90), 文=顶(110)
let 2 = _创建笔(200, 110.0, 100.0, 300, 90.0, 80.0);
let feat = 线::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
::,
);
// 向上取max → 笔2.文=110
let = feat.();
assert!((. - 110.0).abs() < 0.01);
}
#[test]
fn test_文_向下线段取最小特征值分型() {
// 向下线段,元素用向上笔(底→顶),文=底分型
// 笔1: 底(特征值=80)→顶(100), 文=底(80)
let 1 = _创建底分型(100, 90.0, 80.0, 5);
let 1 = _创建顶分型(200, 100.0, 90.0, 10);
let 1 = Arc::new(线::(1, 1, true));
// 笔2: 底(特征值=70)→顶(95), 文=底(70)
let 2 = _创建底分型(200, 80.0, 70.0, 15);
let 2 = _创建顶分型(300, 95.0, 85.0, 20);
let 2 = Arc::new(线::(2, 2, true));
let feat = 线::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
::,
);
// 向下取min → 笔2.文=70
let = feat.();
assert!((. - 70.0).abs() < 0.01);
}
// ============================================================
// 武 — 取特征值最大/最小的分型
// 向上线段 → 元素为向下笔 → 武=底分型 → 取max
// 向下线段 → 元素为向上笔 → 武=顶分型 → 取min
// ============================================================
#[test]
fn test_武_向上线段取最大特征值分型() {
// 向上线段,元素用向下笔(顶→底),武=底分型
// 笔1: 顶(100)→底(特征值=80), 武=底(80)
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
// 笔2: 顶(110)→底(特征值=90), 武=底(90)
let 2 = _创建笔(200, 110.0, 100.0, 300, 100.0, 90.0);
let feat = 线::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
::,
);
// 向上取max → 笔2.武=90
let = feat.();
assert!((. - 90.0).abs() < 0.01);
}
#[test]
fn test_武_向下线段取最小特征值分型() {
// 向下线段,元素用向上笔(底→顶),武=顶分型
// 笔1: 底(80)→顶(特征值=100), 武=顶(100)
let 1 = _创建底分型(100, 90.0, 80.0, 5);
let 1 = _创建顶分型(200, 100.0, 90.0, 10);
let 1 = Arc::new(线::(1, 1, true));
// 笔2: 底(60)→顶(特征值=85), 武=顶(85)
let 2 = _创建底分型(200, 70.0, 60.0, 15);
let 2 = _创建顶分型(300, 85.0, 75.0, 20);
let 2 = Arc::new(线::(2, 2, true));
let feat = 线::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
::,
);
// 向下取min → 笔2.武=85
let = feat.();
assert!((. - 85.0).abs() < 0.01);
}
// ============================================================
// 文/武 tiebreaker — 同特征值取后时间戳
// ============================================================
#[test]
fn test_文_同特征值取后时间戳() {
// 两个笔的文特征值相同=100,但时间戳不同
let 1 = _创建顶分型(100, 100.0, 90.0, 5);
let 1 = _创建底分型(200, 90.0, 80.0, 10);
let 1 = Arc::new(线::(1, 1, true));
let 2 = _创建顶分型(300, 100.0, 90.0, 15); // 同特征值,后时间戳
let 2 = _创建底分型(400, 80.0, 70.0, 20);
let 2 = Arc::new(线::(2, 2, true));
let feat = 线::new("测试".into(), vec![1, 2], ::);
let = feat.();
// 向上取最大特征值:都是100 → tiebreaker取后时间戳 → 笔2.文(300)
assert_eq!(.(), 300);
}
#[test]
fn test_武_同特征值取后时间戳_向上() {
let 1 = _创建顶分型(100, 100.0, 90.0, 5);
let 1 = _创建底分型(200, 80.0, 70.0, 10); // 特征值80
let 1 = Arc::new(线::(1, 1, true));
let 2 = _创建顶分型(300, 80.0, 70.0, 15); // 特征值80
let 2 = _创建底分型(400, 80.0, 70.0, 20); // 特征值80
let 2 = Arc::new(线::(2, 2, true));
let feat = 线::new("测试".into(), vec![1, 2], ::);
let = feat.();
// 向上取最大特征值:都是80 → tiebreaker取后时间戳 → 笔2.武(400)
assert_eq!(.(), 400);
}
// ============================================================
// 添加/删除 操作
// ============================================================
#[test]
fn test_添加方向与线段方向相反的虚线可成功() {
// 向下笔(顶→底,方向=向下) 添加到 向上线段(方向=向上) → 方向不同, 可添加
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let mut feat = 线::new("测试".into(), vec![], ::);
let result = feat._添加(Arc::clone(&1));
assert!(result.is_ok());
}
#[test]
fn test_添加方向与线段方向相同的虚线应报错() {
// 向下笔(顶→底,方向=向下) 添加到 向下线段(方向=向下) → 方向相同, 应报错
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let mut feat = 线::new("测试".into(), vec![], ::);
let result = feat._添加(Arc::clone(&1));
assert!(result.is_err());
}
#[test]
fn test_空线段特征文返回第一个元素的文() {
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let feat = 线::new("测试".into(), vec![Arc::clone(&1)], ::);
let = feat.();
assert_eq!(Arc::as_ptr(&), Arc::as_ptr(&1.));
}
}
+2
View File
@@ -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,
+6 -4
View File
@@ -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::;
}
+8 -17
View File
@@ -23,6 +23,7 @@
*/
use serde::{Deserialize, Serialize};
use tracing::warn;
/// 分型结构 —— 三根K线构成的结构形态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -92,7 +93,10 @@ impl 分型结构 {
)
}
fn _内部(
#[allow(clippy::too_many_arguments)]
/// 分析三K线的分型结构(内部实现,接收裸f64值)
#[allow(clippy::too_many_arguments)]
pub fn _内部(
: f64,
: f64,
: f64,
@@ -111,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
}
}
+3
View File
@@ -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 { , }
+2
View File
@@ -26,8 +26,10 @@ pub mod bsp_type;
pub mod direction;
pub mod fractal;
pub mod gap;
pub mod sync_f64;
pub use bsp_type::;
pub use direction::;
pub use fractal::;
pub use gap::;
pub use sync_f64::SyncF64;
+53
View File
@@ -0,0 +1,53 @@
/*
* MIT License
*
* Copyright (c) 2026 YuYuKunKun
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use std::sync::atomic::{AtomicU64, Ordering};
/// f64 原子类型 — 基于 AtomicU64 + 位转换,API 与 `Cell<f64>` 一致。
#[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);
}
}
impl Clone for SyncF64 {
fn clone(&self) -> Self {
Self(AtomicU64::new(self.0.load(Ordering::Relaxed)))
}
}
+57 -4
View File
@@ -22,6 +22,7 @@
* SOFTWARE.
*/
/// 将 f64 格式化为与 Python `:g` (6 位有效数字) 兼容的字符串。
pub fn format_f64_g(value: f64) -> String {
if value.is_nan() {
return "nan".to_string();
@@ -33,9 +34,61 @@ pub fn format_f64_g(value: f64) -> String {
"-inf".to_string()
};
}
if value == 0.0 {
return "0".to_string();
}
// Use high precision then trim trailing zeros
let s = format!("{:.15}", value);
let s = s.trim_end_matches('0');
s.trim_end_matches('.').to_string()
let abs = value.abs();
let exp = abs.log10().floor() as i32;
// Python :g 科学计数法边界: exp < -4 或 exp >= p (=6)
if !(-4..6).contains(&exp) {
let significand = value / 10_f64.powi(exp);
let s = format!("{:.5}", significand);
let s = s.trim_end_matches('0').trim_end_matches('.');
return format!("{}e{:+03}", s, exp);
}
// 定点表示
if abs >= 1.0 {
let int_digits = exp as usize + 1;
if int_digits >= 6 {
return format!("{:.0}", value);
}
let s = format!("{:.prec$}", value, prec = 6 - int_digits);
let s = s.trim_end_matches('0');
s.trim_end_matches('.').to_string()
} else {
let leading_zeros = (-exp) as usize;
let s = format!("{:.prec$}", value, prec = leading_zeros + 5);
let s = s.trim_end_matches('0');
s.trim_end_matches('.').to_string()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_format_large() {
assert_eq!(format_f64_g(82833.0), "82833");
assert_eq!(format_f64_g(74192.2), "74192.2");
assert_eq!(format_f64_g(100.0), "100");
assert_eq!(format_f64_g(0.0), "0");
assert_eq!(format_f64_g(12345.6789), "12345.7");
}
#[test]
fn test_format_small() {
assert_eq!(format_f64_g(0.001234), "0.001234");
assert_eq!(format_f64_g(0.1), "0.1");
assert_eq!(format_f64_g(0.001), "0.001");
}
#[test]
fn test_format_extreme() {
assert_eq!(format_f64_g(1e7), "1e+07");
assert_eq!(format_f64_g(1e-5), "1e-05");
}
}
+55
View File
@@ -0,0 +1,55 @@
# API 参考
## 枚举
| 类 | 说明 |
|---|------|
| `买卖点类型` | 一卖点、一买点、二卖点、二买点、三卖点、三买点 |
| `相对方向` | 向上、向下、向上缺口、向下缺口、衔接向上、衔接向下 |
| `分型结构` | 顶分型、底分型、散(无分型) |
## 数据
| 类 | 说明 |
|---|------|
| `K线` | 原始 OHLCV 数据,内嵌 MACD/RSI/KDJ 指标 |
| `缠论K线` | 经包含处理后的 K 线,有方向(向上/向下)、分型结构标记 |
| `缺口` | 前后两段区间的相对位置关系 |
## 结构
| 类 | 说明 |
|---|------|
| `分型` | 顶/底分型,由左中右三根缠K构成 |
| `虚线` | 笔/线段的通用数据结构 |
| `线段特征` | 线段特征序列,用于分析线段力度 |
| `特征分型` | 特征分型结构 |
## 指标
| 类 | 说明 |
|---|------|
| `平滑异同移动平均线` | MACD — EMA 快慢线 + 信号线 + 柱状图 |
| `相对强弱指数` | RSI — Wilder SMA 平滑,含超买超卖阈值 |
| `随机指标` | KDJ — RSV → K → D → J,含超买超卖阈值 |
| `指标` | 指标工具类 — `K线取值` 等静态方法 |
## 算法
| 类 | 说明 |
|---|------|
| `笔` | 笔划分算法(创建、分析、弱化、分析前检查) |
| `线段` | 线段划分算法(分析、武斗、武终、分割序列) |
| `中枢` | 中枢识别算法(延伸、扩展、第三买卖线) |
| `背驰分析` | MACD/斜率/测度背离检测 |
## 业务
| 类 | 说明 |
|---|------|
| `缠论配置` | 50+ 参数集中控制所有分析阶段的行为 |
| `观察者` | 单周期分析器,持有所有层级序列 |
| `基础买卖点` | 买卖点数据结构 |
| `买卖点` | 继承基础买卖点,添加工厂类方法 |
| `K线合成器` | 将小周期 K 线合成为大周期 K 线 |
| `立体分析器` | 多周期分析器,含 K线合成器 + 每周期观察者 |
+46
View File
@@ -0,0 +1,46 @@
# Sphinx 配置 — chanlun 缠论技术分析库
#
# 使用 MyST 解析器支持 MarkdownFuro 主题
import os
import sys
# 确保文档构建时能 import chanlun
sys.path.insert(0, os.path.abspath(".."))
# ---- 项目信息 ----
project = "chanlun"
copyright = "2026, YuWuKunCheng"
author = "YuWuKunCheng"
release = "26.5.103"
language = "zh_CN"
# ---- 扩展 ----
extensions = [
"myst_parser", # Markdown 支持
"sphinx.ext.viewcode", # 添加源码链接
"sphinx.ext.intersphinx", # 跨项目文档链接
]
# ---- MyST 配置 ----
myst_enable_extensions = [
"colon_fence", # ::: 围栏代码块
"fieldlist", # 字段列表
]
myst_heading_anchors = 3
# ---- 主题 ----
html_theme = "furo"
html_title = "chanlun — 缠论技术分析库"
html_theme_options = {
"source_repository": "https://github.com/yuwukuncheng/chanlun.rs",
"source_branch": "main",
"source_directory": "docs/",
}
# ---- 跨项目链接 ----
intersphinx_mapping = {
"python": ("https://docs.python.org/3", None),
}
add_module_names = False
+45
View File
@@ -0,0 +1,45 @@
# chanlun — 缠论技术分析库
基于 Rust + PyO3 的高性能缠论技术分析 Python 绑定,API 参考 `chan.py` 设计,高度兼容。
## 特性
- **高性能**:核心算法用 Rust 实现,通过 PyO3 暴露为 Python 原生模块
- **完全兼容**:类名、方法名、字段名与 `chan.py` 保持一致
- **流式计算**:逐 K 线增量更新,无需重复计算
- **多周期分析**:内置 K 线合成器和立体分析器,支持跨周期联动
## 安装
```bash
pip install chanlun
```
## 快速开始
```python
import chanlun
config = chanlun.缠论配置()
obs = chanlun.观察者.读取数据文件("BTCUSD-300.nb", config)
print(f"K线: {len(obs.普通K线序列)}")
print(f"笔: {len(obs.笔序列)}")
print(f"线段: {len(obs.线段序列)}")
print(f"中枢: {len(obs.中枢序列)}")
```
```{toctree}
:hidden:
:caption: 文档导航
api
```
```{toctree}
:hidden:
:caption: 外部链接
GitHub <https://github.com/yuwukuncheng/chanlun.rs>
PyPI <https://pypi.org/project/chanlun/>
```
+3
View File
@@ -0,0 +1,3 @@
sphinx>=8.0
myst-parser>=4.0
furo>=2024.0
+63 -34
View File
@@ -657,35 +657,56 @@ class 随机数据(bt.feeds.DataBase):
class 自定义实时数据源(bt.feed.DataBase):
"""
一个用于模拟实时数据推送的数据源继承自Backtrader的DataBase
在实际应用中你需要将模拟生成数据的部分替换为接收WebSocket等推送数据的代码
支持两种数据输入方式
1. 手动投喂调用 投喂数据(时间戳, , , , , ) WebSocket 回调等外部代码推送
2. 后台线程传入 观察员 魔法 参数start() 时自动启动线程获取历史数据
(注意观察员 需实现 读取任意数据 方法)
时间戳格式Unix 时间戳 int 类型
"""
def __init__(self, 数据队列: queue.Queue, 观察员: "观察者", 魔法, **魔法参数):
# 调用父类初始化方法
def __init__(self, 数据队列: queue.Queue = None, 观察员: "观察者" = None, 魔法=None, **魔法参数):
super(自定义实时数据源, self).__init__()
# 存储外部传入的数据队列,用于接收实时数据
self.数据队列 = 数据队列
# 定义一个标志,用于控制数据加载的停止
self.数据队列 = 数据队列 or queue.Queue()
self.正在运行 = False
self.观察员 = 观察员
self.魔法 = 魔法
self.__魔法参数 = 魔法参数
self.已有数据 = False
def 投喂数据(self, 时间戳: int, 开盘价: float, 最高价: float, 最低价: float, 收盘价: float, 成交量: float, 持仓量: float = 0):
"""线程安全的外部数据推送接口,供 WebSocket 回调等外部代码调用。
:param 时间戳: Unix 时间戳int 类型
:param 开盘价: 开盘价
:param 最高价: 最高价
:param 最低价: 最低价
:param 收盘价: 收盘价
:param 成交量: 成交量
:param 持仓量: 持仓量默认为 0
"""
self.数据队列.put((时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量, 持仓量))
def start(self):
"""
数据源开始工作时的初始化操作
这里可以不执行任何操作或者启动数据接收线程等
"""
print(f"[{datetime.now()}] 自定义数据源已启动...")
self.正在运行 = True
def 运行回测():
self.观察员.读取任意数据(self.魔法, **self.__魔法参数)
self.正在运行 = False
if self.观察员 is not None and self.魔法 is not None:
if not hasattr(self.观察员, "读取任意数据"):
print(f"[{datetime.now()}] 警告: 观察员没有 '读取任意数据' 方法,后台线程不会启动")
return
回测线程 = threading.Thread(target=运行回测, daemon=True)
回测线程.start()
def 运行回测():
try:
self.观察员.读取任意数据(self.魔法, **self.__魔法参数)
except Exception as e:
print(f"[{datetime.now()}] 后台数据线程异常: {e}")
finally:
self.正在运行 = False
回测线程 = threading.Thread(target=运行回测, daemon=True)
回测线程.start()
def stop(self):
"""
@@ -696,32 +717,25 @@ class 自定义实时数据源(bt.feed.DataBase):
print(f"[{datetime.now()}] 自定义数据源已停止。")
def _load(self):
"""
(核心方法) Backtrader会循环调用此方法来获取数据
每次被调用时都应该返回新的数据行一个K线/一个数据点
如果没有新数据返回 FalseBacktrader会等待下次调用
"""
"""Backtrader 核心回调 — 阻塞等待数据,有数据时返回 True,数据源停止时返回 False。"""
if not self.正在运行:
return False
# 当没有新数据且系统仍在运行时,进入等待
# 从外部队列获取新数据
while True:
try:
data_point = self.数据队列.get(timeout=0.5)
break
except queue.Empty:
if not self.已有数据:
continue
else:
if self.正在运行:
continue
if not self.正在运行:
return False
# 解析数据元组
dt, o, h, l, c, v, oi = data_point
# 将数据写入Backtrader的数据线 (lines)
self.lines.datetime[0] = bt.date2num(dt)
try:
dt, o, h, l, c, v, oi = data_point
except (ValueError, TypeError) as e:
print(f"[{datetime.now()}] 自定义数据源: 数据格式错误,跳过: {e}")
return True
self.lines.datetime[0] = bt.date2num(datetime.utcfromtimestamp(int(dt)))
self.lines.open[0] = o
self.lines.high[0] = h
self.lines.low[0] = l
@@ -729,7 +743,6 @@ class 自定义实时数据源(bt.feed.DataBase):
self.lines.volume[0] = v
self.lines.openinterest[0] = oi
self.已有数据 = True
# 返回 True 表示成功加载一行数据
return True
@@ -757,7 +770,7 @@ class 高级策略基类(bt.Strategy):
def 日志(self, 文本, 时间=None):
时间 = 时间 or self.datas[0].datetime.datetime(0)
print(f"{时间} {文本}")
print(f"{时间} {self.p.观察员.__class__.__name__}: {文本}")
def 计算目标数量(self, 价格):
"""根据资金类型和仓位比例计算目标数量"""
@@ -946,6 +959,7 @@ class 回测(高级策略基类):
def __init__(self):
super().__init__()
self.已处理信号 = set()
print(f"{self.p.观察员.__class__.__name__}: 加载完成...")
def 获取开仓限价(self, 是否做多):
# 根据缠论分型计算限价,若无则返回 None 使用基类默认逻辑
@@ -957,6 +971,7 @@ class 回测(高级策略基类):
return None
def next(self):
# self.日志(f"{self.p.观察员.__class__.__name__} called next ")
# 1. 更新移动止损(基类方法)
if self.position:
self.更新止损订单(self.position.size > 0, self.data.close[0])
@@ -986,15 +1001,22 @@ class 回测(高级策略基类):
def 检查买信号(self):
if self.p.观察员.笔序列:
k线 = self.p.观察员.缠论K线序列[-1]
self.日志(f"检查买信号 当前笔 {self.p.观察员.笔序列[-1]}")
if k线.买卖点信息:
print(f"回测-首 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
= True if k线.买卖点信息 and "" in next(iter(k线.买卖点信息)) else False
if :
print(k线)
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
差值 = self.p.观察员.当前缠K.序号 - k线.序号
self.日志(f"首_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
k线 = self.p.观察员.缠论K线序列[-2]
if k线.买卖点信息:
print(f"回测-尾 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
= True if self.p.观察员.缠论K线序列[-2].买卖点信息 and "" in next(iter(self.p.观察员.缠论K线序列[-2].买卖点信息)) else False
if :
print(k线)
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
差值 = self.p.观察员.当前缠K.序号 - k线.序号
self.日志(f"尾_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
@@ -1002,16 +1024,23 @@ class 回测(高级策略基类):
def 检查卖信号(self):
if self.p.观察员.笔序列:
self.日志(f"检查卖信号 当前笔 {self.p.观察员.笔序列[-1]}")
k线 = self.p.观察员.缠论K线序列[-1]
if k线.买卖点信息:
print(f"回测-首 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
= True if k线.买卖点信息 and "" in next(iter(k线.买卖点信息)) else False
if :
print(k线)
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
差值 = self.p.观察员.当前缠K.序号 - k线.序号
self.日志(f"首_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
k线 = self.p.观察员.缠论K线序列[-2]
if k线.买卖点信息:
print(f"回测-尾 {self.p.观察员.__class__.__name__}", k线.买卖点信息)
= True if self.p.观察员.缠论K线序列[-2].买卖点信息 and "" in next(iter(self.p.观察员.缠论K线序列[-2].买卖点信息)) else False
if :
print(k线)
原始差值 = self.p.观察员.当前K线.序号 - k线.标的K线.序号
差值 = self.p.观察员.当前缠K.序号 - k线.序号
self.日志(f"尾_买入信号差值: {原始差值}, {差值}, 观察员.当前K线 时间戳: {self.p.观察员.当前K线.时间戳}")
@@ -1019,7 +1048,7 @@ class 回测(高级策略基类):
def log(self, 文本, dt=None):
dt = dt or bt.num2date(self.data.datetime[0])
print(f"[{dt.strftime('%Y-%m-%d %H:%M')}] {self.p.符号} | {文本}")
print(f"[{dt.strftime('%Y-%m-%d %H:%M')}] {self.p.观察员.__class__.__name__}: {self.p.符号} | {文本}")
# ==================== 回测运行入口 ====================