8 Commits

Author SHA1 Message Date
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
YuWuKunCheng 52823e6c75 修复 rust 引用问题 2026-05-28 01:07:16 +08:00
YuYuKunKun 4908870245 Fix link to chanlun Rust core library in README 2026-05-27 01:26:54 +08:00
YuYuKunKun d2f74d847b Remove linux-aarch64 dependency from publish job 2026-05-27 01:07:07 +08:00
YuYuKunKun 7d99802059 Delete Linux aarch64 build steps from publish.yml
Removed the Linux aarch64 build configuration from the workflow.
2026-05-27 01:05:56 +08:00
YuWuKunCheng 98f07508fa Update workflows 2026-05-27 00:59:49 +08:00
YuYuKunKun 9280041f2a Add python-source parameter to publish workflow 2026-05-26 23:46:31 +08:00
40 changed files with 10943 additions and 1892 deletions
+27 -18
View File
@@ -16,37 +16,36 @@ env:
jobs:
# ============================================================
# Linux wheels (x86_64 + aarch64)
# Linux x86_64 (manylinux)
# ============================================================
linux:
linux-x86_64:
runs-on: ubuntu-latest
strategy:
matrix:
target: [x86_64, aarch64]
steps:
- uses: actions/checkout@v4
- name: 安装 Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
- name: 构建 wheel
- name: 构建 wheel (manylinux)
uses: PyO3/maturin-action@v1
with:
target: ${{ matrix.target }}
target: x86_64
working-directory: chanlun-py
args: --release --out dist
manylinux: auto
before-script-linux: |
if [ "${{ matrix.target }}" = "aarch64" ]; then
sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
fi
- name: 上传 wheel 产物
uses: actions/upload-artifact@v4
with:
name: wheels-linux-${{ matrix.target }}
name: wheels-linux-x86_64
path: chanlun-py/dist/
# ============================================================
# macOS wheels (x86_64 + arm64)
# ============================================================
@@ -61,6 +60,11 @@ jobs:
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
- name: 安装 Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: 构建 wheel
uses: PyO3/maturin-action@v1
with:
@@ -88,6 +92,11 @@ jobs:
- name: 安装 Rust 工具链
uses: dtolnay/rust-toolchain@stable
- name: 安装 Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: 构建 wheel
uses: PyO3/maturin-action@v1
with:
@@ -109,6 +118,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: 安装 Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: 构建 sdist
uses: PyO3/maturin-action@v1
with:
@@ -126,7 +140,7 @@ jobs:
# 发布至 PyPI
# ============================================================
publish:
needs: [linux, macos, windows, sdist]
needs: [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:
@@ -147,8 +161,3 @@ jobs:
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
# 使用 OIDC 信任发布(推荐),无需配置 token
# 若使用 API token,在 GitHub Secrets 中设置:
# PYPI_TOKEN: pypi-xxxxxxxxxxxx
# 然后将下一行改为:
# password: ${{ secrets.PYPI_TOKEN }}
+1 -1
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` 完全兼容。
## 安装
+46 -21
View File
@@ -46,6 +46,7 @@ from typing import (
Union,
Sequence,
Callable,
Set,
)
from pydantic import BaseModel, Field, model_validator, ValidationError, field_validator
@@ -1816,7 +1817,7 @@ class K线(object):
print(f"保存到DAT文件: {路径}")
@classmethod
def 读取大端字节数组(cls, 字节组: bytes, 周期: int = 60, 标识: str = "Bar") -> "K线":
def 读取大端字节数组(cls, 字节组: bytes, 周期: int, 标识: str) -> "K线":
"""从大端字节序二进制数据反序列化K线(兼容.dat/.nb文件格式)
:param 字节组: 二进制数据(48字节)
@@ -1929,6 +1930,7 @@ class 缠论K线(object):
self.原始起始序号: int = 原始起始序号
self.原始结束序号: int = 原始结束序号
self.标的K线: "K线" = 普K
self.买卖点信息: Set[str] = set()
def __str__(self):
return f"{self.标识}<{self.序号}, {self.分型}, {self.周期}, {self.方向}, {self.时间戳}, {self.:g}, {self.:g}>"
@@ -1943,6 +1945,7 @@ class 缠论K线(object):
:return: 新的缠论K线实例
"""
K = 缠论K线(self.序号, self.时间戳, self., self., self.方向, self.标的K线, self.原始起始序号, self.原始结束序号, self.分型)
K.买卖点信息.update(self.买卖点信息)
return K
@property
@@ -2518,6 +2521,7 @@ class 虚线(object):
"""
= 虚线序列[0].
= 虚线序列[-1].
assert .结构 != .结构
标识 = "线段" if 虚线序列[0].标识 == "" else f"线段<{虚线序列[0].标识}>"
= 虚线(0, 标识, , , 虚线序列[0].级别 + 1)
.特征序列 = [None] * 3
@@ -2873,8 +2877,8 @@ class 虚线(object):
def 统计MACD行为(cls, 普K序列: List[K线], 最大间隔: int = 8, 最少交叉数: int = 3) -> dict:
"""
:param 普K序列: K线序列
:param 最大间隔: 最大间隔
:param 最少交叉数: 最少交叉数
:param 最大间隔: 最大间隔 8
:param 最少交叉数: 最少交叉数 3
:return: 统计字典
"""
# 1. 穿越零轴计数
@@ -2922,6 +2926,19 @@ class 虚线(object):
"密集交叉区域": 密集区, # (起始交叉索引, 结束交叉索引, 交叉次数)
}
@classmethod
def 获取_武(cls, 实线: 虚线) -> 分型:
"""递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武)
:param 实线: 虚线
:return: 分型
"""
if 实线.标识 == "":
return 实线.
tmp = 实线
while tmp.标识 != "":
tmp = tmp.基础序列[-1]
return tmp.
class (object):
"""笔 — 纯静态方法容器,提供笔划分算法的所有函数。
@@ -3278,7 +3295,7 @@ class 笔(object):
return False
@classmethod
def 获取所有停顿位置(cls, : 虚线, 观察员: "观察者"):
def 获取所有停顿位置(cls, : 虚线, 观察员: "观察者") -> List[虚线]:
"""获取笔内所有可能的停顿位置(用于背驰检测)
:param 筆: 笔
@@ -3304,7 +3321,7 @@ class 笔(object):
return 笔序列
@classmethod
def 是否背驰过(cls, 当前筆: 虚线, 观察员: "观察者"):
def 是否背驰过(cls, 当前筆: 虚线, 观察员: "观察者") -> List[缠论K线]:
"""判断笔内是否发生过MACD趋向背驰
:param 当前筆: 笔
@@ -4417,14 +4434,13 @@ class 线段(object):
@classmethod
@lru_cache(maxsize=128)
def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者):
def 判断线段内部是否背驰(cls, 当前段: 虚线, 观察员: 观察者) -> bool:
"""判断线段内部是否发生背驰(基于内部中枢和MACD)
:param 当前段: 线段
:param 观察员: 观察者
:return: bool
"""
, , = 线段.获取内部中枢序列(当前段, 观察员.配置)
, , _, _ = 线段.分割序列(当前段)
if len() > 0:
"""
@@ -4488,8 +4504,8 @@ class 线段(object):
return 背驰 or 盘整背驰
@classmethod
def 获取所有停顿位置(cls, : 虚线, 观察员: "观察者"):
"""获取所有停顿位置
def 获取所有停顿位置(cls, : 虚线, 观察员: "观察者") -> List[虚线]:
"""获取所有停顿位置
:param 段: 线段
:param 观察员: 观察者
@@ -4507,14 +4523,14 @@ class 线段(object):
当前停顿 = None
for in :
if len(笔序列) >= 2:
if len(笔序列) >= 3:
筆停顿 = .获取所有停顿位置(, 观察员)
筆停顿.append()
for 停顿 in 筆停顿:
笔序列.append(停顿)
线段.分析(笔序列, 线段序列, 观察员.配置, 关系序列=[相对方向.向下, 相对方向.向上, 相对方向., 相对方向., 相对方向.])
if 线段序列 and 线段序列[-1]. is not 当前停顿:
新段 = 虚线.创建线段(线段序列[-1][:])
if 线段序列 and 线段序列[-1]. is not 当前停顿 and len(线段序列[-1].基础序列) % 2 == 1:
新段 = 虚线.创建线段(线段序列[-1].基础序列[:])
新段.序号 = self.序号
线段.刷新(新段, 观察员.配置)
if 新段.方向 is self.方向:
@@ -4528,19 +4544,19 @@ class 线段(object):
return 结果
@classmethod
def 是否背驰过(cls, 当前段: 虚线, 观察员: "观察者"):
def 是否背驰过(cls, 当前段: 虚线, 观察员: "观察者") -> List[缠论K线]:
"""判断线段内是否发生过背驰(遍历所有停顿位置)
:param 当前段: 线段
:param 观察员: 观察者
:return: 背驰点列表
"""
停顿位置 = cls.获取所有停顿位置(当前段, 观察员)
停顿位置 = 线段.获取所有停顿位置(当前段, 观察员)
结果 = []
for in 停顿位置:
.获取内部中枢序列(观察员.配置)
if cls.判断线段内部是否背驰(, 观察员):
线.获取内部中枢序列(, 观察员.配置)
if 线段.判断线段内部是否背驰(, 观察员):
结果.append(..)
return 结果
@@ -4666,7 +4682,7 @@ class 中枢(object):
"""获取用于保存的数据文本"""
return f"{self.标识}, {self.序号}, {self.级别}, 文:({int(self..时间戳.timestamp())},{self..分型特征值:g}), 武:({int(self..时间戳.timestamp())},{self..分型特征值:g}), {self.第三买卖线}, {self.本级_第三买卖线}"
def 完整性(self, 虚实: str = ""):
def 完整性(self, 虚实: str):
"""判断中枢是否完整(是否有第三买卖点或内部中枢离开)
详情见 教你炒股票 43:有关背驰的补习课(2007-04-06 15:31:28)
@@ -4709,7 +4725,7 @@ class 中枢(object):
线段.扩展分析(self.基础序列, 扩展线段, 配置)
中枢.分析(扩展线段, 扩展中枢, False, f"{self.标识}_扩展中枢_")
def 校验合法性(self, 序列: Sequence[虚线], 中枢序列) -> bool:
def 校验合法性(self, 序列: Sequence[虚线], 中枢序列: List["中枢"]) -> bool:
"""校验当前中枢在给定序列中是否仍然合法
:param 序列: 基础虚线序列
@@ -4796,7 +4812,7 @@ class 中枢(object):
:return: "中枢之中" / "中枢之上" / "中枢之下"
"""
状态 = "中枢之中"
尾部 = self.基础序列[-1]. if self.基础序列[-1].标识 == "" else self.基础序列[-1].基础序列[-1].
尾部 = 虚线.获取_武(self.基础序列[-1])
关系 = 相对方向.分析(self., self., 尾部.., 尾部..)
if 关系 is 相对方向.向上缺口:
状态 = "中枢之上"
@@ -5184,6 +5200,15 @@ class 观察者:
self.配置.分析线段 and 线段.分析(self.线段序列, self.线段_线段序列, self.配置)
self.配置.分析线段中枢 and 中枢.分析(self.线段_线段序列, self.线段_中枢序列)
def 加载本地数据(self, 文件路径: str):
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线)
@classmethod
def 读取数据文件(cls, 文件路径: str, 配置=缠论配置()) -> Self:
"""
@@ -5199,7 +5224,7 @@ class 观察者:
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线.读取大端字节数组(buffer[i * size : i * size + size], int(周期), 符号)
实例.增加原始K线(k线)
return 实例
@@ -5492,7 +5517,7 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置]
buffer = f.read()
size = struct.calcsize(">6d")
for i in range(len(buffer) // size):
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], 周期)
k线 = K线.读取大端字节数组(buffer[i * size : i * size + size], 周期, 符号)
多级别分析.投喂K线(k线)
消耗用时 = datetime.now() - 启动时间
print("测试_周期合成", 消耗用时, "普K数量", len(多级别分析._单体分析器[周期].普通K线序列))
+3 -5
View File
@@ -1,6 +1,6 @@
[package]
name = "chanlun-py"
version = "26.5.11"
version = "26.5.86"
edition = "2021"
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
authors = ["YuYuKunKun"]
@@ -12,9 +12,7 @@ crate-type = ["cdylib"]
name = "chanlun"
[dependencies]
# 发布至 PyPI 前,需先将 chanlun 发布至 crates.io,然后替换为版本号依赖:
# chanlun = "0.1"
chanlun = "26.5.1" # { path = "../chanlun" }
pyo3 = { version = "0.28", features = ["extension-module"] }
chanlun = "26.5.3" # { path = "../chanlun" }
pyo3 = { version = "0.28", features = ["extension-module", "experimental-inspect"] }
serde_json = "1"
chrono = "0.4"
+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") {
if let Some(v) = trimmed.split('=').nth(1) {
return v.trim().trim_matches('"').trim().to_string();
}
}
}
panic!("Cannot parse version from {:?}", path);
}
File diff suppressed because it is too large Load Diff
+29
View File
@@ -1,3 +1,32 @@
"""缠论技术分析库 — Rust 高性能实现"""
__all__ = [
"K线",
"K线合成器",
"中枢",
"买卖点",
"买卖点类型",
"分型",
"分型结构",
"基础买卖点",
"平滑异同移动平均线",
"指标",
"特征分型",
"相对强弱指数",
"相对方向",
"立体分析器",
"",
"线段",
"线段特征",
"缠论K线",
"缠论配置",
"缺口",
"背驰分析",
"虚线",
"观察者",
"转化为时间戳",
"转化为时间戳_数字",
"随机指标",
]
from ._chanlun import *
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "chanlun"
version = "2605.11"
version = "2605.86"
description = "缠论技术分析库 — Rust 高性能实现"
readme = { file = "README.md", content-type = "text/markdown" }
license = { file = "LICENSE", content-type = "text/plain" }
File diff suppressed because it is too large Load Diff
+296 -131
View File
@@ -23,10 +23,14 @@
*/
use pyo3::prelude::*;
use pyo3::types::PyType;
use std::cell::RefCell;
use pyo3::types::{PyDict, PyType};
use std::sync::RwLock;
use crate::algorithm_py::hub_to_py;
use crate::kline_py::bar_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py};
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::Arc;
use crate::algorithm_py::Py;
use crate::config_py::Py;
@@ -44,7 +48,7 @@ use crate::types_py::买卖点类型Py;
/// 有效性: bool — 买卖点是否仍有效
/// 与MACD柱子匹配: bool|None — 是否与MACD柱状图方向匹配
/// 与MACD柱子分型匹配: bool|None — 是否与MACD柱分型匹配
#[pyclass(name = "基础买卖点", unsendable)]
#[pyclass(name = "基础买卖点", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub(crate) inner: chanlun::business::bsp::,
@@ -63,8 +67,8 @@ impl 基础买卖点Py {
Self {
inner: chanlun::business::bsp::::new(
.borrow().inner,
Rc::new(K线.borrow().inner.clone()),
Rc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
Arc::clone(&.borrow().inner),
,
,
),
@@ -76,6 +80,12 @@ impl 基础买卖点Py {
self.inner..clone()
}
#[setter]
#[pyo3(name = "备注")]
fn _备注(&mut self, value: String) {
self.inner. = value;
}
#[getter]
fn (&self) -> Py {
Py {
@@ -86,39 +96,39 @@ impl 基础买卖点Py {
#[getter]
fn (&self) -> Py {
Py {
inner: Rc::clone(&self.inner.),
inner: Arc::clone(&self.inner.),
}
}
#[getter]
fn K线(&self) -> K线Py {
K线Py {
inner: Rc::clone(&self.inner.K线),
}
fn K线(&self, py: Python<'_>) -> Py<K线Py> {
crate::kline_py::chan_kline_to_py(py, Arc::clone(&self.inner.K线))
}
#[getter]
fn K线(&self) -> K线Py {
K线Py {
inner: (*self.inner.K线).clone(),
}
/// 当前K线
fn K线(&self, py: Python<'_>) -> Py<K线Py> {
bar_to_py(py, self.inner.K线.clone())
}
#[getter]
fn K线(&self) -> Option<K线Py> {
self.inner.K线.as_ref().map(|k| K线Py {
inner: (**k).clone(),
})
fn K线(&self, py: Python<'_>) -> Option<Py<K线Py>> {
self.inner
.K线
.as_ref()
.map(|k| bar_to_py(py, Arc::clone(k)))
}
#[getter]
fn K线(&self) -> Option<K线Py> {
self.inner.K线.as_ref().map(|k| K线Py {
inner: (**k).clone(),
})
fn K线(&self, py: Python<'_>) -> Option<Py<K线Py>> {
self.inner
.K线
.as_ref()
.map(|k| bar_to_py(py, Arc::clone(k)))
}
#[getter]
/// 破位值
fn (&self) -> f64 {
self.inner.
}
@@ -130,29 +140,54 @@ impl 基础买卖点Py {
.map(|f| crate::types_py::Py { inner: f })
}
#[getter]
/// 偏移
fn (&self) -> i64 {
self.inner.()
}
#[getter]
/// 失效偏移
fn (&self) -> i64 {
self.inner.()
}
#[getter]
/// 有效性
fn (&self) -> bool {
self.inner.()
}
#[getter]
/// 与MACD柱子匹配
fn MACD柱子匹配(&self) -> bool {
self.inner.MACD柱子匹配()
}
#[getter]
/// 与MACD柱子分型匹配
fn MACD柱子分型匹配(&self) -> bool {
self.inner.MACD柱子分型匹配()
}
/// 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("与MACD柱子匹配", self.MACD柱子匹配())?;
dict.set_item("与MACD柱子分型匹配", self.MACD柱子分型匹配())?;
if let Some(v) = self.() {
dict.set_item("结构", v)?;
}
Ok(dict.into())
}
fn __str__(&self) -> String {
format!("{}", self.inner)
}
@@ -170,12 +205,13 @@ impl 基础买卖点Py {
/// 一卖点(...) / 一买点(...) / 二卖点(...) / 二买点(...) / 三卖点(...) / 三买点(...)
/// 生成买卖点(特征, 序号, 级别, 分型, 当前缠K, 备注?) -> 买卖点
/// — 根据特征字符串自动路由到对应的一/二/三类买卖点构造函数
#[pyclass(name = "买卖点")]
#[pyclass(name = "买卖点", module = "chanlun._chanlun")]
pub struct Py;
#[pymethods]
impl Py {
#[classmethod]
/// :param 买卖点分型: 买卖点对应的分型
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
@@ -186,8 +222,8 @@ impl 买卖点Py {
) -> Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
@@ -196,6 +232,7 @@ impl 买卖点Py {
}
#[classmethod]
/// :param 买卖点分型: 买卖点对应的分型
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
@@ -206,8 +243,8 @@ impl 买卖点Py {
) -> Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
@@ -216,6 +253,7 @@ impl 买卖点Py {
}
#[classmethod]
/// :param 买卖点分型: 买卖点对应的分型
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
@@ -226,8 +264,8 @@ impl 买卖点Py {
) -> Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
@@ -236,6 +274,7 @@ impl 买卖点Py {
}
#[classmethod]
/// :param 买卖点分型: 买卖点对应的分型
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
@@ -246,8 +285,8 @@ impl 买卖点Py {
) -> Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
@@ -256,6 +295,7 @@ impl 买卖点Py {
}
#[classmethod]
/// :param 买卖点分型: 买卖点对应的分型
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
@@ -266,8 +306,8 @@ impl 买卖点Py {
) -> Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
@@ -276,6 +316,7 @@ impl 买卖点Py {
}
#[classmethod]
/// :param 买卖点分型: 买卖点对应的分型
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
@@ -286,8 +327,8 @@ impl 买卖点Py {
) -> Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
@@ -296,6 +337,7 @@ impl 买卖点Py {
}
#[classmethod]
/// :param 特征: 特征字符串
fn (
_cls: &Bound<'_, PyType>,
: &str,
@@ -309,8 +351,8 @@ impl 买卖点Py {
,
,
,
Rc::clone(&.borrow().inner),
Rc::clone(&K.borrow().inner),
Arc::clone(&.borrow().inner),
Arc::clone(&K.borrow().inner),
),
}
}
@@ -347,81 +389,124 @@ impl 买卖点Py {
/// 调试方法:
/// 测试_保存数据(root?) — 将各层级序列保存到文件,用于与Python版对比
/// 读取数据文件(文件路径, 配置) -> 观察者 (classmethod) — 从 .nb 文件加载数据
#[pyclass(name = "观察者", subclass, unsendable)]
#[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)]
pub struct Py {
pub(crate) inner: Option<Rc<RefCell<chanlun::business::observer::>>>,
pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::>>>,
}
impl Py {
pub(crate) fn obs(&self) -> std::cell::Ref<'_, chanlun::business::observer::> {
pub(crate) fn obs(
&self,
) -> std::sync::RwLockReadGuard<'_, chanlun::business::observer::> {
self.inner
.as_ref()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
.borrow()
.read()
.unwrap_or_else(|e| e.into_inner())
}
pub(crate) fn obs_mut(&self) -> std::cell::RefMut<'_, chanlun::business::observer::> {
pub(crate) fn obs_mut(
&self,
) -> std::sync::RwLockWriteGuard<'_, chanlun::business::observer::> {
self.inner
.as_ref()
.expect("观察者 尚未初始化,请通过 __init__(符号, 周期, 配置) 构造")
.borrow_mut()
.write()
.unwrap_or_else(|e| e.into_inner())
}
}
#[pymethods]
impl Py {
/// __new__ 只分配空壳,构造逻辑在 __init__
/// __new__ 从 *args/**kwargs 中提取 (符号, 周期, 配置),完整初始化观察者。
/// 由于形参可变(子类参数各异),不依赖固定签名,而是从 args/kwargs 中按位置和名称智能提取。
#[new]
#[pyo3(signature = (*args, **kwargs))]
fn new(
args: &Bound<'_, pyo3::types::PyTuple>,
kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
) -> Self {
let _ = (args, kwargs);
Self { inner: None }
) -> PyResult<Self> {
let py = args.py();
// 提取 符号(位置 0 或关键字)
let : String = if !args.is_empty() {
args.get_item(0)?.extract()?
} else {
match kwargs.and_then(|kw| kw.get_item("符号").ok().flatten()) {
Some(val) => val.extract()?,
None => return Err(pyo3::exceptions::PyTypeError::new_err("缺少参数: 符号")),
}
};
// 提取 周期(位置 1 或关键字)
let : i64 = if args.len() >= 2 {
args.get_item(1)?.extract()?
} else {
match kwargs.and_then(|kw| kw.get_item("周期").ok().flatten()) {
Some(val) => val.extract()?,
None => return Err(pyo3::exceptions::PyTypeError::new_err("缺少参数: 周期")),
}
};
// 提取 配置(关键字优先,然后扫描剩余位置参数)
let mut config = None;
if let Some(val) = kwargs.and_then(|kw| kw.get_item("配置").ok().flatten()) {
let cfg: PyRef<'_, Py> = val.extract()?;
config = Some(cfg.to_rust_config(py)?);
}
if config.is_none() {
for i in 2..args.len() {
if let Ok(cfg) = args.get_item(i)?.extract::<PyRef<'_, Py>>() {
config = Some(cfg.to_rust_config(py)?);
break;
}
}
}
let config = config.unwrap_or_else(chanlun::config::::default);
Ok(Self {
inner: Some(chanlun::business::observer::::new(
, , config,
)),
})
}
/// __init__ 执行真正的构造。子类可重写并调用 super().__init__(符号, 周期, 配置)
#[pyo3(signature = (符号, 周期, 配置 = None))]
/// __init__ 不重复构造 — __new__ 已完成初始化。
/// 接收 *args/**kwargs 是为了兼容 Python 构造函数给 __init__ 传入的任意形参。
#[pyo3(signature = (*args, **kwargs))]
fn __init__(
&mut self,
py: Python<'_>,
: String,
: i64,
: Option<&Bound<'_, Py>>,
args: &Bound<'_, pyo3::types::PyTuple>,
kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
) -> PyResult<()> {
let config = match {
Some(cfg) => cfg.borrow().to_rust_config(py)?,
None => chanlun::config::::default(),
};
self.inner = Some(chanlun::business::observer::::new(
, , config,
));
let _ = (args, kwargs);
Ok(())
}
#[getter]
/// 观察员(自引用)
fn (slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
slf
}
#[getter]
/// :return: "{符号}:{周期}"
fn (&self) -> String {
self.obs().()
}
#[getter]
fn K线(&self) -> Option<K线Py> {
self.obs().K线().map(|k| K线Py {
inner: (**k).clone(),
})
/// :return: 最后一根原始K线
fn K线(&self, py: Python<'_>) -> Option<Py<K线Py>> {
self.obs().K线().map(|k| bar_to_py(py, Arc::clone(k)))
}
#[getter]
fn K(&self) -> Option<K线Py> {
self.obs().K().map(|k| K线Py {
inner: Rc::clone(k),
})
/// :return: 最后一根缠论K线
fn K(&self, py: Python<'_>) -> Option<Py<K线Py>> {
self.obs()
.K()
.map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))
}
#[getter]
@@ -439,36 +524,112 @@ impl 观察者Py {
Py::from_rust_config(&self.obs().)
}
/// 清空所有分析序列,重置为初始状态
fn (&mut self) {
self.obs_mut().();
}
/// 核心入口 — 投喂一根原始K线,增量更新所有层级
fn K线(&mut self, K: &Bound<'_, K线Py>) {
self.obs_mut().K线(K.borrow().inner.clone());
self.obs_mut().K线((*K.borrow().inner).clone());
}
/// 加载本地数据 — 从 .nb 文件加载K线数据(先重置,再通过 Python dispatch 逐根投喂,
/// 确保子类重写的 增加原始K线 被正确调用)。
fn (slf: &Bound<'_, Self>, : &str) -> PyResult<()> {
let py = slf.py();
// 重置基础序列
slf.borrow_mut().obs_mut().();
// 解析文件得到 K线 列表
let bars = slf
.borrow()
.obs()
.()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))?;
// 通过 Python dispatch 逐根投喂,确保子类重写生效
for k线 in bars {
let k线_py = Py::new(
py,
K线Py {
inner: Arc::new(k线),
},
)?;
slf.call_method1("增加原始K线", (k线_py,))?;
}
Ok(())
}
/// 静态重新分析(占位方法)
fn (&mut self) {
self.obs_mut().();
}
#[pyo3(signature = (root = None))]
/// 拆分各序列数据,单独存文件,文件名为对应变量名
fn _保存数据(&self, root: Option<&str>) {
self.obs()._保存数据(root);
}
#[classmethod]
#[pyo3(signature = (文件路径, 配置 = None))]
/// :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
fn (
_cls: &Bound<'_, PyType>,
cls: &Bound<'_, PyType>,
: &str,
: Option<&Bound<'_, Py>>,
py: Python<'_>,
) -> PyResult<Self> {
) -> PyResult<Py<PyAny>> {
let config = match {
Some(cfg) => Some(cfg.borrow().to_rust_config(py)?),
None => None,
Some(cfg) => cfg.borrow().to_rust_config(py)?,
None => chanlun::config::::default(),
};
chanlun::business::observer::::(, config)
.map(|inner| Self { inner: Some(inner) })
.map_err(|e| pyo3::exceptions::PyValueError::new_err(e))
// 从文件名解析 符号/周期: "btcusd-300-1761327300-1776327900.nb"
let path = std::path::Path::new();
let name = path
.file_stem()
.and_then(|n| n.to_str())
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("invalid filename"))?;
let parts: Vec<&str> = name.split('-').collect();
if parts.len() < 4 {
return Err(pyo3::exceptions::PyValueError::new_err(format!(
"invalid filename format: {}",
name
)));
}
let = parts[0].to_string();
let : i64 = parts[1]
.parse()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("parse period: {}", e)))?;
// 通过 cls 构造实例(支持子类化)
let cfg_py = Py::from_rust_config(&config)?;
let cfg_obj = Py::new(py, cfg_py)?;
let obj = cls.call1((.clone(), , cfg_obj))?;
// 读取文件并通过 Python 分发逐根投喂(支持子类重写 增加原始K线)
let data = std::fs::read()
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("read file: {}", e)))?;
let size: usize = 48;
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) =
chanlun::kline::bar::K线::from_bytes(&data[offset..offset + size], , &)
{
let k线_py = Py::new(
py,
K线Py {
inner: Arc::new(k线),
},
)?;
obj.call_method1("增加原始K线", (k线_py,))?;
}
}
Ok(obj.unbind())
}
// ---- 序列 getters ----
@@ -477,20 +638,38 @@ impl 观察者Py {
fn K线序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for k in &self.obs().K线序列 {
list.append(K线Py {
inner: (**k).clone(),
})?;
list.append(bar_to_py(py, Arc::clone(k)))?;
}
Ok(list.into())
}
#[getter]
fn K序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for k in &self.obs().K序列 {
list.append(crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))?;
}
Ok(list.into())
}
#[setter]
#[pyo3(name = "基础缠K序列")]
fn _基础缠K序列(&mut self, value: &Bound<'_, PyAny>) -> PyResult<()> {
let list: &Bound<'_, pyo3::types::PyList> = value.cast()?;
let mut vec = Vec::new();
for item in list {
let k: PyRef<'_, K线Py> = item.extract()?;
vec.push(Arc::clone(&k.inner));
}
self.obs_mut().K序列 = vec;
Ok(())
}
#[getter]
fn K线序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for k in &self.obs().K线序列 {
list.append(K线Py {
inner: Rc::clone(k),
})?;
list.append(crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))?;
}
Ok(list.into())
}
@@ -499,9 +678,7 @@ impl 观察者Py {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for f in &self.obs(). {
list.append(Py {
inner: Rc::clone(f),
})?;
list.append(fractal_to_py(py, Arc::clone(f)))?;
}
Ok(list.into())
}
@@ -510,9 +687,7 @@ impl 观察者Py {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.obs(). {
list.append(线Py {
inner: Rc::clone(d),
})?;
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
}
@@ -521,9 +696,7 @@ impl 观察者Py {
fn _中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for h in &self.obs()._中枢序列 {
list.append(Py {
inner: Rc::clone(h),
})?;
list.append(hub_to_py(py, Arc::clone(h)))?;
}
Ok(list.into())
}
@@ -532,9 +705,7 @@ impl 观察者Py {
fn 线(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.obs().线 {
list.append(线Py {
inner: Rc::clone(d),
})?;
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
}
@@ -543,9 +714,7 @@ impl 观察者Py {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for h in &self.obs(). {
list.append(Py {
inner: Rc::clone(h),
})?;
list.append(hub_to_py(py, Arc::clone(h)))?;
}
Ok(list.into())
}
@@ -554,9 +723,7 @@ impl 观察者Py {
fn 线(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.obs().线 {
list.append(线Py {
inner: Rc::clone(d),
})?;
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
}
@@ -565,9 +732,7 @@ impl 观察者Py {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for h in &self.obs(). {
list.append(Py {
inner: Rc::clone(h),
})?;
list.append(hub_to_py(py, Arc::clone(h)))?;
}
Ok(list.into())
}
@@ -576,9 +741,7 @@ impl 观察者Py {
fn 线_线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.obs().线_线段 {
list.append(线Py {
inner: Rc::clone(d),
})?;
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
}
@@ -587,9 +750,7 @@ impl 观察者Py {
fn _线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for h in &self.obs()._线段 {
list.append(Py {
inner: Rc::clone(h),
})?;
list.append(hub_to_py(py, Arc::clone(h)))?;
}
Ok(list.into())
}
@@ -598,9 +759,7 @@ impl 观察者Py {
fn 线_线段序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.obs().线_线段序列 {
list.append(线Py {
inner: Rc::clone(d),
})?;
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
}
@@ -609,9 +768,7 @@ impl 观察者Py {
fn 线_中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for h in &self.obs().线_中枢序列 {
list.append(Py {
inner: Rc::clone(h),
})?;
list.append(hub_to_py(py, Arc::clone(h)))?;
}
Ok(list.into())
}
@@ -620,9 +777,7 @@ impl 观察者Py {
fn 线_扩展线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.obs().线_扩展线段 {
list.append(线Py {
inner: Rc::clone(d),
})?;
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
}
@@ -631,9 +786,7 @@ impl 观察者Py {
fn _扩展线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for h in &self.obs()._扩展线段 {
list.append(Py {
inner: Rc::clone(h),
})?;
list.append(hub_to_py(py, Arc::clone(h)))?;
}
Ok(list.into())
}
@@ -651,7 +804,7 @@ impl 观察者Py {
/// 投喂(时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量) -> list[(周期, K线)]
/// — 快捷入口,免去构造K线对象
/// 获取当前K线(周期) -> K线|None — 获取指定周期的当前合成结果
#[pyclass(name = "K线合成器", unsendable)]
#[pyclass(name = "K线合成器", module = "chanlun._chanlun")]
pub struct K线合成器Py {
pub(crate) inner: chanlun::business::synthesizer::K线合成器,
}
@@ -665,18 +818,20 @@ impl K线合成器Py {
}
}
/// 统一入口 — 投喂最小周期K线,自动合成大周期并分发给各周期观察者
fn K线(
&mut self,
K: &Bound<'_, K线Py>,
py: Python<'_>,
) -> PyResult<Vec<(i64, K线Py)>> {
let results = self.inner.K线(K.borrow().inner.clone());
let results = self.inner.K线((*K.borrow().inner).clone());
Ok(results
.into_iter()
.map(|(, k)| (, K线Py { inner: k }))
.map(|(, k)| (, K线Py { inner: Arc::new(k) }))
.collect())
}
/// 投喂原始tick数据
fn (
&mut self,
: i64,
@@ -701,14 +856,22 @@ impl K线合成器Py {
let results = self.inner.K线(k);
results
.into_iter()
.map(|(, k2)| (, K线Py { inner: k2 }))
.map(|(, k2)| {
(
,
K线Py {
inner: Arc::new(k2),
},
)
})
.collect()
}
/// 获取指定周期当前正在合成的K线
fn K线(&self, : i64) -> Option<K线Py> {
self.inner
.K线()
.map(|k| K线Py { inner: k.clone() })
self.inner.K线().map(|k| K线Py {
inner: Arc::new(k.clone()),
})
}
#[getter]
@@ -735,7 +898,7 @@ impl K线合成器Py {
/// 方法:
/// 投喂K线(普K) — 喂入最小周期K线,自动合成大周期并分发给各周期观察者
/// 测试_保存数据(root?) — 保存各周期的分析数据到文件
#[pyclass(name = "立体分析器", unsendable)]
#[pyclass(name = "立体分析器", module = "chanlun._chanlun")]
pub struct Py {
pub(crate) inner: chanlun::business::multi_frame::,
}
@@ -775,8 +938,9 @@ impl 立体分析器Py {
})
}
/// 统一入口 — 投喂最小周期K线,自动合成大周期并分发给各周期观察者
fn K线(&mut self, K: &Bound<'_, K线Py>) {
self.inner.K线(K.borrow().inner.clone());
self.inner.K线((*K.borrow().inner).clone());
}
fn (&self, : i64) -> Option<Py> {
@@ -785,6 +949,7 @@ impl 立体分析器Py {
.map(|rc| Py { inner: Some(rc) })
}
/// 拆分各序列数据,单独存文件,文件名为对应变量名
fn _保存数据(&self) {
self.inner._保存数据();
}
+199 -17
View File
@@ -91,7 +91,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>>,
}
@@ -107,16 +107,18 @@ impl 缠论配置Py {
if let Some(kwargs) = kwargs {
for (key, value) in kwargs.iter() {
let key: String = key.extract()?;
if fields.contains_key(&key) {
fields.insert(key, value.clone().unbind());
} else {
if !fields.contains_key(&key) {
return Err(pyo3::exceptions::PyAttributeError::new_err(format!(
"缠论配置 没有字段: {key}"
)));
}
fields.insert(key, value.clone().unbind());
}
}
// 全部通过 serde_json 往返验证类型,统一处理字符串数字/布尔强制转换
let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?;
Ok(Self { fields })
}
@@ -132,7 +134,16 @@ impl 缠论配置Py {
fn __setattr__(&mut self, name: &str, value: &Bound<'_, PyAny>) -> PyResult<()> {
if self.fields.contains_key(name) {
self.fields.insert(name.to_string(), value.clone().unbind());
Ok(())
// 通过 serde 往返验证类型
match dict_to_rust_config(&self.fields) {
Ok(config) => {
self.fields = config_to_field_dict(&config)?;
Ok(())
}
Err(e) => Err(pyo3::exceptions::PyValueError::new_err(format!(
"配置转换失败: {e}"
))),
}
} else {
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
"缠论配置 没有字段: {name}"
@@ -172,45 +183,47 @@ 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)?;
for (key, value) in data.iter() {
let key: String = key.extract()?;
fields.insert(key, value.clone().unbind());
if fields.contains_key(&key) {
fields.insert(key, value.clone().unbind());
}
}
dict_to_rust_config(&fields)?;
let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?;
Ok(Self { fields })
}
#[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)?;
@@ -218,6 +231,7 @@ impl 缠论配置Py {
}
#[classmethod]
/// 将形如 "1_open", "1_close", "2_open", "name" 的字典重组为嵌套结构
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyAny>,
@@ -237,6 +251,7 @@ impl 缠论配置Py {
Ok(result.into())
}
/// 比较当前配置与另一个配置的差异
fn (
&self,
py: Python<'_>,
@@ -270,7 +285,8 @@ impl 缠论配置Py {
fields.insert(key, value.clone().unbind());
}
dict_to_rust_config(&fields)?;
let config = dict_to_rust_config(&fields)?;
let fields = config_to_field_dict(&config)?;
Ok(Self { fields })
}
@@ -313,11 +329,177 @@ fn dict_to_rust_config(
}
let dumps = json_mod.getattr("dumps")?;
let json_str: String = dumps.call1((dict,))?.extract()?;
serde_json::from_str(&json_str)
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);
// 获取默认配置的 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 {
if let serde_json::Value::Object(ref default_map) = default_json {
for (key, input_val) in input_map {
if let Some(default_val) = default_map.get(key) {
match validate_field(key, input_val, default_val) {
Ok(()) => {
merged[key] = input_val.clone();
}
Err(msg) => {
eprintln!("\x1b[33m[配置警告]\x1b[m {key}: {msg},已使用默认值 {default_val}");
}
}
}
}
}
}
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 {
serde_json::Value::Object(map) => {
for (_, v) in map.iter_mut() {
coerce_strings_to_numbers(v);
}
}
serde_json::Value::Array(arr) => {
for v in arr.iter_mut() {
coerce_strings_to_numbers(v);
}
}
serde_json::Value::String(s) => {
// 先 clone 出独立副本,避免借用冲突
let cloned = s.clone();
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);
}
}
} else if cloned.eq_ignore_ascii_case("true") {
*value = serde_json::Value::Bool(true);
} else if cloned.eq_ignore_ascii_case("false") {
*value = serde_json::Value::Bool(false);
}
// 非数字非布尔的原样保留,不做任何修改
}
_ => {}
}
}
/// 将 Python 字符串数字/布尔值强制转为对应类型,非字符串保持不变。
/// 注:主代码路径现通过 serde 往返处理类型转换,此函数作为辅助保留。
#[allow(dead_code)]
fn coerce_py_value(value: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
let py = value.py();
let type_name: String = value.get_type().name()?.extract()?;
if type_name != "str" {
return Ok(value.clone().unbind());
}
let lower_obj = value.call_method0("lower")?;
let lower: String = lower_obj.extract()?;
if lower == "true" || lower == "false" {
let b = lower == "true";
let obj = pyo3::types::PyBool::new(py, b)
.to_owned()
.into_any()
.unbind();
return Ok(obj);
}
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());
}
}
Ok(value.clone().unbind())
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Py>()?;
Ok(())
+21 -4
View File
@@ -43,7 +43,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 +128,7 @@ impl 平滑异同移动平均线Py {
#[classmethod]
#[pyo3(signature = (初始收盘价, 初始时间, 快线周期 = None, 慢线周期 = None, 信号周期 = None))]
/// 首次计算MACD指标(没有历史数据时使用)
fn (
_cls: &Bound<'_, PyType>,
: f64,
@@ -144,6 +149,7 @@ impl 平滑异同移动平均线Py {
#[classmethod]
#[pyo3(signature = (k线, 计算方式, 快线周期 = None, 慢线周期 = None, 信号周期 = None))]
/// :param k线: 原始K线
fn _K线(
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
@@ -165,6 +171,7 @@ impl 平滑异同移动平均线Py {
}
#[classmethod]
/// 基于前一个MACD指标增量计算当前MACD指标
fn (
_cls: &Bound<'_, PyType>,
MACD: &Bound<'_, 线Py>,
@@ -180,6 +187,7 @@ impl 平滑异同移动平均线Py {
}
#[classmethod]
/// :param 前一个MACD: 前一个MACD指标对象
fn _K线(
_cls: &Bound<'_, PyType>,
MACD: &Bound<'_, 线Py>,
@@ -214,7 +222,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 +302,7 @@ impl 相对强弱指数Py {
#[classmethod]
#[pyo3(signature = (初始收盘价, 初始时间, 周期 = None, 超买阈值 = None, 超卖阈值 = None, RSI_SMA周期 = None))]
/// 首次计算RSI(没有足够历史数据时使用)
fn (
_cls: &Bound<'_, PyType>,
: f64,
@@ -317,6 +326,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 +350,7 @@ impl 相对强弱指数Py {
}
#[classmethod]
/// 基于前一个RSI指标增量计算当前RSI
fn (
_cls: &Bound<'_, PyType>,
RSI: &Bound<'_, Py>,
@@ -356,6 +367,7 @@ impl 相对强弱指数Py {
}
#[classmethod]
/// :param 前一个RSI: 前一个RSI指标对象
fn _K线(
_cls: &Bound<'_, PyType>,
RSI: &Bound<'_, Py>,
@@ -390,7 +402,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 +500,7 @@ impl 随机指标Py {
#[classmethod]
#[pyo3(signature = (初始最高价, 初始最低价, 初始收盘价, 初始时间, N = None, M1 = None, M2 = None, 超买阈值 = None, 超卖阈值 = None))]
/// 首次计算KDJ(无历史数据时)
fn (
_cls: &Bound<'_, PyType>,
: f64,
@@ -517,6 +530,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 +560,7 @@ impl 随机指标Py {
}
#[classmethod]
/// 基于前一个KDJ对象和当前三价,增量计算当前KDJ值
fn (
_cls: &Bound<'_, PyType>,
KDJ: &Bound<'_, Py>,
@@ -566,6 +581,7 @@ impl 随机指标Py {
}
#[classmethod]
/// :param 前一个KDJ: 前一个KDJ指标对象
fn _K线(
_cls: &Bound<'_, PyType>,
KDJ: &Bound<'_, Py>,
@@ -595,12 +611,13 @@ impl 随机指标Py {
/// K线取值(k线, 指标计算方式) -> float (classmethod)
/// 根据计算方式从K线提取数值。
/// 计算方式: "收盘价" / "开盘价" / "高" / "低" / "均值" 等
#[pyclass(name = "指标")]
#[pyclass(name = "指标", module = "chanlun._chanlun")]
pub struct Py;
#[pymethods]
impl Py {
#[classmethod]
/// 根据计算方式从K线中取值
fn K线取值(
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
+280 -108
View File
@@ -23,8 +23,11 @@
*/
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyType};
use pyo3::types::{PyBytes, PyDict, PyType};
use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::RwLock;
use crate::config_py::Py;
use crate::indicators_py::{线Py, Py, Py};
@@ -51,10 +54,9 @@ use crate::types_py::相对方向Py;
/// 获取MACD(K线序列, 计算方式, 快线周期?, 慢线周期?, 信号周期?) -> list[平滑异同移动平均线]
/// — 对整个K线序列批量计算 MACD
/// 截取(序列, 起点K线, 终点K线) -> list — 按时间戳截取K线区间
#[pyclass(name = "K线")]
#[derive(Clone)]
#[pyclass(name = "K线", module = "chanlun._chanlun")]
pub struct K线Py {
pub(crate) inner: chanlun::kline::bar::K线,
pub(crate) inner: Arc<chanlun::kline::bar::K线>,
}
#[pymethods]
@@ -73,7 +75,7 @@ impl K线Py {
: f64,
) -> Self {
Self {
inner: chanlun::kline::bar::K线 {
inner: Arc::new(chanlun::kline::bar::K线 {
: .to_string(),
,
,
@@ -86,7 +88,7 @@ impl K线Py {
macd: None,
rsi: None,
kdj: None,
},
}),
}
}
@@ -94,84 +96,49 @@ impl K线Py {
fn (&self) -> String {
self.inner..clone()
}
#[setter]
fn set_标识(&mut self, v: String) {
self.inner. = v;
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[setter]
fn set_序号(&mut self, v: i64) {
self.inner. = v;
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[setter]
fn set_周期(&mut self, v: i64) {
self.inner. = v;
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[setter]
fn set_时间戳(&mut self, v: i64) {
self.inner. = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_高(&mut self, v: f64) {
self.inner. = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_低(&mut self, v: f64) {
self.inner. = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_开盘价(&mut self, v: f64) {
self.inner. = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_收盘价(&mut self, v: f64) {
self.inner. = v;
}
#[getter]
fn (&self) -> f64 {
self.inner.
}
#[setter]
fn set_成交量(&mut self, v: f64) {
self.inner. = v;
}
#[getter]
/// :return: 相对方向.向上(开盘<收盘)或 相对方向.向下(开盘>收盘)
fn (&self) -> Py {
Py {
inner: self.inner.(),
@@ -179,7 +146,7 @@ impl K线Py {
}
#[getter]
fn MACD(&self) -> Option<线Py> {
fn macd(&self) -> Option<线Py> {
self.inner
.macd
.as_ref()
@@ -187,7 +154,7 @@ impl K线Py {
}
#[getter]
fn RSI(&self) -> Option<Py> {
fn rsi(&self) -> Option<Py> {
self.inner
.rsi
.as_ref()
@@ -195,13 +162,39 @@ impl K线Py {
}
#[getter]
fn KDJ(&self) -> Option<Py> {
fn kdj(&self) -> Option<Py> {
self.inner
.kdj
.as_ref()
.map(|k| Py { inner: k.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.())?;
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)?;
}
Ok(dict.into())
}
fn __str__(&self) -> String {
format!("{}", self.inner)
}
@@ -214,8 +207,20 @@ impl K线Py {
PyBytes::new(py, &self.inner.to_bytes()).into()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Arc::as_ptr(&self.inner) as u64
}
#[classmethod]
#[pyo3(signature = (标识, 时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量, 序号 = None, 周期 = None))]
/// 快捷构造普通K线
fn K(
_cls: &Bound<'_, PyType>,
: &str,
@@ -229,7 +234,7 @@ impl K线Py {
: Option<i64>,
) -> Self {
Self {
inner: chanlun::kline::bar::K线::K(
inner: Arc::new(chanlun::kline::bar::K线::K(
,
,
,
@@ -239,11 +244,12 @@ impl K线Py {
,
.unwrap_or(0),
.unwrap_or(60),
),
)),
}
}
#[classmethod]
/// 将K线序列保存为二进制DAT文件
fn DAT文件(
_cls: &Bound<'_, PyType>,
: &str,
@@ -251,23 +257,28 @@ impl K线Py {
py: Python<'_>,
) -> PyResult<()> {
let refs: Vec<_> = K线序列.iter().map(|k| k.bind(py).borrow()).collect();
let bars: Vec<&chanlun::kline::bar::K线> = refs.iter().map(|r| &r.inner).collect();
let bars: Vec<&chanlun::kline::bar::K线> = refs.iter().map(|r| r.inner.as_ref()).collect();
chanlun::kline::bar::K线::DAT文件(, &bars)
.map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))
}
#[classmethod]
/// 从大端字节序二进制数据反序列化K线(兼容.dat/.nb文件格式)
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyBytes>,
: i64,
: &str,
) -> Option<Self> {
chanlun::kline::bar::K线::(.as_bytes(), , )
.map(|inner| Self { inner })
chanlun::kline::bar::K线::(.as_bytes(), , ).map(|inner| {
Self {
inner: Arc::new(inner),
}
})
}
#[classmethod]
/// 计算指定K线区间的MACD柱面积
fn MACD(
_cls: &Bound<'_, PyType>,
k线序列: Vec<Py<Self>>,
@@ -276,31 +287,43 @@ impl K线Py {
py: Python<'_>,
) -> HashMap<String, f64> {
let refs: Vec<_> = k线序列.iter().map(|k| k.bind(py).borrow()).collect();
let bars: Vec<&chanlun::kline::bar::K线> = refs.iter().map(|r| &r.inner).collect();
let bars: Vec<&chanlun::kline::bar::K线> = refs.iter().map(|r| r.inner.as_ref()).collect();
chanlun::kline::bar::K线::MACD(&bars, &.borrow().inner, &.borrow().inner)
}
#[staticmethod]
/// 按起止K线截取K线子序列
fn (
: Vec<Py<Self>>,
: &Bound<'_, Self>,
: &Bound<'_, Self>,
) -> Option<Vec<Py<Self>>> {
let start_ptr = .as_ptr();
let end_ptr = .as_ptr();
let start_idx = .iter().position(|k| k.as_ptr() == start_ptr)?;
let end_idx = .iter().position(|k| k.as_ptr() == end_ptr)?;
if start_idx <= end_idx {
Some(
.into_iter()
.skip(start_idx)
.take(end_idx - start_idx + 1)
.collect(),
)
} else {
None
py: Python<'_>,
) -> PyResult<Vec<Py<Self>>> {
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| {
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| {
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 {
return Err(pyo3::exceptions::PyValueError::new_err("始 排序在 终 之后"));
}
Ok(
.into_iter()
.skip(start_idx)
.take(end_idx - start_idx + 1)
.collect())
}
}
@@ -323,10 +346,73 @@ impl K线Py {
/// 分析(缠K序列, 配置, 可以逆序包含?, 忽视顺序包含?, 可以逆序包含新?) -> (str, 分型|None)
/// — 分析分型形成结果
/// 截取(序列, 起点分型, 终点分型) -> list — 截取分型间的缠K子序列
#[pyclass(name = "缠论K线", unsendable)]
#[derive(Clone)]
#[pyclass(name = "缠论K线", module = "chanlun._chanlun", from_py_object)]
pub struct K线Py {
pub(crate) inner: std::rc::Rc<chanlun::kline::chan_kline::K线>,
pub(crate) inner: std::sync::Arc<chanlun::kline::chan_kline::K线>,
bsp_set: std::sync::RwLock<Option<Py<pyo3::types::PySet>>>,
}
impl K线Py {
pub(crate) fn from_rc(inner: std::sync::Arc<chanlun::kline::chan_kline::K线>) -> Self {
Self {
inner,
bsp_set: std::sync::RwLock::new(None),
}
}
}
thread_local! {
/// 对象标识缓存:Rc 地址 → 规范 Python 对象
/// 确保同一底层 Rc 指针在 Python 侧始终映射到同一 PyObject
static BAR_IDENTITY: RwLock<HashMap<usize, Py<K线Py>>> = RwLock::new(HashMap::new());
static KLINE_IDENTITY: RwLock<HashMap<usize, Py<K线Py>>> = 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.with(|c| c.read().unwrap().get(&key).map(|p| p.clone_ref(py)))
{
return cached;
}
let obj = Py::new(py, K线Py { inner }).unwrap();
BAR_IDENTITY.with(|c| {
c.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.with(|c| c.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.with(|c| {
c.write().unwrap().insert(key, obj.clone_ref(py));
});
obj
}
impl Clone for K线Py {
fn clone(&self) -> Self {
Self {
inner: std::sync::Arc::clone(&self.inner),
bsp_set: std::sync::RwLock::new(None),
}
}
}
#[pymethods]
@@ -338,28 +424,28 @@ 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.,
inner: *self.inner..read().unwrap(),
}
}
@@ -367,6 +453,8 @@ impl 缠论K线Py {
fn (&self) -> Option<crate::types_py::Py> {
self.inner
.
.read()
.unwrap()
.map(|f| crate::types_py::Py { inner: f })
}
@@ -382,7 +470,7 @@ impl 缠论K线Py {
#[getter]
fn (&self) -> f64 {
self.inner.
self.inner..get()
}
#[getter]
@@ -392,14 +480,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.())?;
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.() {
dict.set_item("分型", v)?;
}
Ok(dict.into())
}
fn __str__(&self) -> String {
@@ -410,28 +520,74 @@ impl 缠论K线Py {
self.__str__()
}
fn (&self) -> Self {
Self {
inner: std::rc::Rc::new(self.inner.()),
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Arc::as_ptr(&self.inner) as u64
}
#[getter]
/// 创建当前缠K的浅拷贝副本
fn (&self, py: Python<'_>) -> Self {
let mut mirror = Self {
inner: std::sync::Arc::new(self.inner.()),
bsp_set: std::sync::RwLock::new(None),
};
if let Some(ref src_set) = *self.bsp_set.read().unwrap() {
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::sync::RwLock::new(Some(new_set.into()));
}
}
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.read().unwrap().is_none() {
let set = pyo3::types::PySet::empty(py)?;
for s in self.inner..read().unwrap().iter() {
set.add(s.clone())?;
}
*self.bsp_set.write().unwrap() = Some(set.into());
}
Ok(self
.bsp_set
.read()
.unwrap()
.as_ref()
.unwrap()
.clone_ref(py)
.into_any())
}
#[classmethod]
/// 在基线序列中找到与k线时间戳对齐的时间戳
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<Self>>,
@@ -440,12 +596,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,
@@ -456,7 +614,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(
@@ -466,15 +625,14 @@ impl 缠论K线Py {
.borrow().inner,
.map(|s| s.borrow().inner),
,
std::rc::Rc::new(k.borrow().inner.clone()),
k.borrow().inner.clone(),
prev_inner,
);
Self {
inner: std::rc::Rc::new(inner),
}
chan_kline_to_py(py, std::sync::Arc::new(inner))
}
#[classmethod]
/// K线包含处理(合并)
fn (
_cls: &Bound<'_, PyType>,
K: Option<&Bound<'_, Self>>,
@@ -482,7 +640,7 @@ impl 缠论K线Py {
K: &Bound<'_, K线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<(Option<Self>, Option<String>)> {
) -> PyResult<(Option<Py<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());
@@ -493,10 +651,11 @@ impl 缠论K线Py {
&K.borrow().inner,
&config,
);
Ok((result.map(|rc| Self { inner: rc }), mode))
Ok((result.map(|rc| chan_kline_to_py(py, rc)), mode))
}
#[classmethod]
/// 分析K线,执行指标计算+包含处理+分型判定
fn (
_cls: &Bound<'_, PyType>,
K线: &Bound<'_, K线Py>,
@@ -505,16 +664,16 @@ impl 缠论K线Py {
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<(String, Option<Py<PyAny>>)> {
let ck_inner = K线.borrow().inner.clone();
let ck_inner = (*K线.borrow().inner).clone();
let config = .borrow().to_rust_config(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| std::rc::Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let (status, _fractal) = chanlun::kline::chan_kline::K线::(
@@ -528,26 +687,39 @@ impl 缠论K线Py {
}
#[staticmethod]
/// :param 序列: 缠K序列
fn (
: Vec<Py<Self>>,
: &Bound<'_, Self>,
: &Bound<'_, Self>,
) -> Option<Vec<Py<Self>>> {
let start_ptr = .as_ptr();
let end_ptr = .as_ptr();
let start_idx = .iter().position(|k| k.as_ptr() == start_ptr)?;
let end_idx = .iter().position(|k| k.as_ptr() == end_ptr)?;
if start_idx <= end_idx {
Some(
.into_iter()
.skip(start_idx)
.take(end_idx - start_idx + 1)
.collect(),
)
} else {
None
py: Python<'_>,
) -> PyResult<Vec<Py<Self>>> {
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| {
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| {
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 {
return Err(pyo3::exceptions::PyValueError::new_err("始 排序在 终 之后"));
}
Ok(
.into_iter()
.skip(start_idx)
.take(end_idx - start_idx + 1)
.collect())
}
}
+41
View File
@@ -34,6 +34,7 @@ mod types_py;
/// 缠论技术分析库 — Rust 高性能实现
#[pymodule]
/// 缠论技术分析库 — Rust 高性能实现
fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
// 阶段 1: 枚举和基础类型
types_py::register(m)?;
@@ -51,3 +52,43 @@ fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
business_py::register(m)?;
Ok(())
}
#[cfg(test)]
mod tests {
use crate::*;
use pyo3::prelude::*;
#[test]
fn test_rc_pointer_across_getters() {
pyo3::prepare_freethreaded_python();
Python::with_gil(|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();
let config = config_py::Py::from_rust_config(&Default::default()).unwrap();
let obs = business_py::Py::new_impl("btcusd".into(), 300, config, py).unwrap();
// 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
});
}
}
File diff suppressed because it is too large Load Diff
+135 -35
View File
@@ -22,8 +22,9 @@
* SOFTWARE.
*/
use pyo3::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::types::PyType;
use pyo3::types::{PyDict, PyType};
// ========== 买卖点类型 ==========
@@ -37,7 +38,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 +54,19 @@ 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<bool> {
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 {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
};
match op {
CompareOp::Eq => Ok(eq),
CompareOp::Ne => Ok(!eq),
_ => Err(pyo3::exceptions::PyNotImplementedError::new_err("")),
}
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return self.inner == other.inner;
}
false
}
fn __hash__(&self) -> u64 {
@@ -71,14 +77,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 +118,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 +134,19 @@ impl 相对方向Py {
format!("{}", self.inner)
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return self.inner == other.inner;
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
};
let eq = self.inner == other.inner;
match op {
CompareOp::Eq => Ok(eq),
CompareOp::Ne => Ok(!eq),
CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)),
CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)),
CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)),
CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)),
}
false
}
fn __hash__(&self) -> u64 {
@@ -135,31 +160,44 @@ impl 相对方向Py {
}
}
#[getter]
/// 判断是否为向上方向(向上/向上缺口/衔接向上)
fn (&self) -> bool {
self.inner.()
}
#[getter]
/// 判断是否为向下方向(向下/向下缺口/衔接向下)
fn (&self) -> bool {
self.inner.()
}
#[getter]
/// 判断是否为包含关系(顺/逆/同)
fn (&self) -> bool {
self.inner.()
}
#[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 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值分析方向关系。
///
/// 参数:
@@ -193,7 +231,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::,
@@ -209,17 +247,34 @@ impl 分型结构Py {
self.inner.to_string()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return self.inner == other.inner;
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
};
let eq = self.inner == other.inner;
match op {
CompareOp::Eq => Ok(eq),
CompareOp::Ne => Ok(!eq),
CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)),
CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)),
CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)),
CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)),
}
false
}
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线的高/低价分析分型结构。
///
/// 参数:
@@ -231,17 +286,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>()?,
@@ -302,7 +355,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::,
@@ -330,6 +383,34 @@ impl 缺口Py {
format!("{}", self.inner)
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
};
match op {
CompareOp::Eq => Ok(self.inner. == other.inner. && self.inner. == other.inner.),
CompareOp::Ne => {
Ok(!(self.inner. == other.inner. && self.inner. == other.inner.))
}
CompareOp::Lt => Ok(self.inner. < other.inner.
|| (self.inner. == other.inner. && self.inner. < other.inner.)),
CompareOp::Le => Ok(self.inner. < other.inner.
|| (self.inner. == other.inner. && self.inner. <= other.inner.)),
CompareOp::Gt => Ok(self.inner. > other.inner.
|| (self.inner. == other.inner. && self.inner. > other.inner.)),
CompareOp::Ge => Ok(self.inner. > other.inner.
|| (self.inner. == other.inner. && self.inner. >= other.inner.)),
}
}
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 {
@@ -354,6 +435,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())
}
/// 在两个价格之间截取中间区域作为缺口。
///
/// 参数:
@@ -363,13 +454,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 })
}
}
@@ -409,10 +500,13 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
("T3B卖", chanlun::types::::T3B卖),
];
let mut 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();
@@ -428,10 +522,13 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
("", chanlun::types::::),
];
let mut 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();
@@ -443,10 +540,13 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
("", chanlun::types::::),
];
let mut 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)?)?;
+772 -2
View File
@@ -259,6 +259,417 @@ def test_override_reset():
print(" ✓ test_override_reset")
# --- Property getter 逐一覆盖 ---
def test_override_getter_符号():
"""重写 符号 property."""
class Sub(chanlun.观察者):
@property
def 符号(self):
return f"[WRAPPED] {super().符号}"
obs = Sub("btcusd", 300)
assert obs.符号 == "[WRAPPED] btcusd"
print(" ✓ test_override_getter_符号")
def test_override_getter_周期():
"""重写 周期 property."""
class Sub(chanlun.观察者):
@property
def 周期(self):
return super().周期 * 60
obs = Sub("btcusd", 5)
assert obs.周期 == 300
print(" ✓ test_override_getter_周期")
def test_override_getter_配置():
"""重写 配置 property."""
class Sub(chanlun.观察者):
@property
def 配置(self):
self._cfg_accessed = True
return super().配置
obs = Sub("btcusd", 300)
cfg = obs.配置
assert cfg is not None
assert obs._cfg_accessed
print(" ✓ test_override_getter_配置")
def test_override_getter_当前K线():
"""重写 当前K线 property."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._cur_k_accessed = False
@property
def 当前K线(self):
self._cur_k_accessed = True
return super().当前K线
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
cur = obs.当前K线
assert cur is not None
assert obs._cur_k_accessed
print(" ✓ test_override_getter_当前K线")
def test_override_getter_当前缠K():
"""重写 当前缠K property."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._cur_ck_accessed = False
@property
def 当前缠K(self):
self._cur_ck_accessed = True
return super().当前缠K
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
cur = obs.当前缠K
assert cur is not None
assert obs._cur_ck_accessed
print(" ✓ test_override_getter_当前缠K")
def test_override_getter_观察员():
"""重写 观察员 property."""
class Sub(chanlun.观察者):
@property
def 观察员(self):
self._obs_accessed = True
return self
obs = Sub("btcusd", 300)
assert obs.观察员 is obs
assert obs._obs_accessed
print(" ✓ test_override_getter_观察员")
# --- 序列 getter 覆盖 ---
def test_override_sequence_getter():
"""重写关键序列 gettersuper() 取基类值."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._seq_accessed = set()
@property
def 普通K线序列(self):
self._seq_accessed.add("普通K线序列")
return super().普通K线序列
@property
def 缠论K线序列(self):
self._seq_accessed.add("缠论K线序列")
return super().缠论K线序列
@property
def 笔序列(self):
self._seq_accessed.add("笔序列")
return super().笔序列
@property
def 线段序列(self):
self._seq_accessed.add("线段序列")
return super().线段序列
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
assert len(obs.普通K线序列) == 1
assert "普通K线序列" in obs._seq_accessed
assert len(obs.缠论K线序列) == 1
assert "缠论K线序列" in obs._seq_accessed
assert isinstance(obs.笔序列, list)
assert "笔序列" in obs._seq_accessed
assert isinstance(obs.线段序列, list)
assert "线段序列" in obs._seq_accessed
print(" ✓ test_override_sequence_getter")
def test_override_all_sequence_getters():
"""重写全部 15 个序列 getter,确认每个都可通过 super() 获取."""
all_seqs = [
"普通K线序列",
"缠论K线序列",
"分型序列",
"笔序列",
"笔_中枢序列",
"线段序列",
"中枢序列",
"扩展线段序列",
"扩展中枢序列",
"扩展线段序列_线段",
"扩展中枢序列_线段",
"线段_线段序列",
"线段_中枢序列",
"扩展线段序列_扩展线段",
"扩展中枢序列_扩展线段",
]
# 动态构建子类,重写全部序列 getter
def _make_getter(name):
@property
def getter(self, _name=name):
self._all_seq_accessed.add(_name)
# 通过 MRO 找到父类的 property 并调用
for cls in type(self).__mro__[1:]:
if hasattr(cls, _name) and isinstance(getattr(cls, _name, None), property):
return getattr(cls, _name).fget(self)
return []
return getter
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._all_seq_accessed = set()
for seq_name in all_seqs:
setattr(Sub, seq_name, _make_getter(seq_name))
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
for seq_name in all_seqs:
seq = getattr(obs, seq_name)
assert seq_name in obs._all_seq_accessed, f"{seq_name} 未被拦截"
assert isinstance(seq, list), f"{seq_name} 不是 list,而是 {type(seq)}"
print(" ✓ test_override_all_sequence_getters")
# --- 方法重写 ---
def test_override_加载本地数据():
"""重写 加载本地数据,super() 调基类."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._loaded = False
self._load_count = 0
def 加载本地数据(self, 文件路径):
self._loaded = True
self._load_count += 1
super().加载本地数据(文件路径)
obs = Sub("btcusd", 300)
obs.加载本地数据(NB_PATH)
assert obs._loaded
assert obs._load_count == 1
assert len(obs.普通K线序列) > 0
print(" ✓ test_override_加载本地数据")
def test_override_静态重新分析():
"""重写 静态重新分析,super() 调基类."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._reanalyzed = 0
def 静态重新分析(self):
self._reanalyzed += 1
super().静态重新分析()
obs = Sub("btcusd", 300)
obs.加载本地数据(NB_PATH)
bi_before = len(obs.笔序列)
obs.静态重新分析()
assert obs._reanalyzed == 1
# 重新分析后笔序列仍然存在
assert len(obs.笔序列) >= 0
print(" ✓ test_override_静态重新分析")
def test_override_测试_保存数据():
"""重写 测试_保存数据,super() 调基类."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._saved = False
self._save_root = None
def 测试_保存数据(self, root=None):
self._saved = True
self._save_root = root
super().测试_保存数据(root)
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
with tempfile.TemporaryDirectory() as tmpdir:
obs.测试_保存数据(tmpdir)
assert obs._saved
assert obs._save_root == tmpdir
print(" ✓ test_override_测试_保存数据")
def test_override_读取数据文件():
"""重写 classmethod 读取数据文件."""
class Sub(chanlun.观察者):
@classmethod
def 读取数据文件(cls, 文件路径, 配置=None):
obs = super().读取数据文件(文件路径, 配置)
obs._custom_classmethod_flag = True
return obs
obs = Sub.读取数据文件(NB_PATH)
assert isinstance(obs, Sub)
assert obs._custom_classmethod_flag
assert len(obs.普通K线序列) > 0
print(" ✓ test_override_读取数据文件")
# --- 完全重写(不调 super ---
def test_override_completely_no_super():
"""完全重写 增加原始K线,不调 super(),彻底接管."""
class Sub(chanlun.观察者):
def 增加原始K线(self, 普K):
self._custom = getattr(self, "_custom", [])
self._custom.append(普K.时间戳)
# 不调 super()
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
assert obs._custom == [1761327300]
assert len(obs.普通K线序列) == 0 # 基类逻辑没执行
print(" ✓ test_override_completely_no_super")
# --- 同名继承(零重写) ---
def test_override_identical_subclass():
"""同名继承(零重写),行为与基类完全一致."""
class Sub(chanlun.观察者):
pass
base = chanlun.观察者("btcusd", 300)
sub = Sub("btcusd", 300)
bars = read_nb_bars(NB_PATH, max_bars=200)
for i, (ts, o, h, l, c, v) in enumerate(bars):
bk = chanlun.K线.创建普K(f"b_{i}", ts, o, h, l, c, v, i, 300)
sk = chanlun.K线.创建普K(f"s_{i}", ts, o, h, l, c, v, i, 300)
base.增加原始K线(bk)
sub.增加原始K线(sk)
for attr in ["普通K线序列", "笔序列", "线段序列", "中枢序列"]:
base_len = len(getattr(base, attr))
sub_len = len(getattr(sub, attr))
assert base_len == sub_len, f"{attr}: base={base_len}, sub={sub_len}"
print(" ✓ test_override_identical_subclass")
# --- 多层继承 ---
def test_override_three_level_mixed():
"""三层继承,每层重写不同方法,MRO 链完整."""
class L1(chanlun.观察者):
def __init__(self, 符号, 周期):
self._l1_feed = 0
def 增加原始K线(self, 普K):
self._l1_feed += 1
super().增加原始K线(普K)
class L2(L1):
def __init__(self, 符号, 周期):
super().__init__(符号, 周期)
self._l2_标识 = 0
@property
def 标识(self):
self._l2_标识 += 1
return f"[L2] {super().标识}"
class L3(L2):
def __init__(self, 符号, 周期):
super().__init__(符号, 周期)
self._l3_reset = 0
def 重置基础序列(self):
self._l3_reset += 1
super().重置基础序列()
obs = L3("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.增加原始K线(k)
assert obs._l1_feed == 1, f"L1 feed: {obs._l1_feed}"
assert obs.标识 == "[L2] btcusd:300"
assert obs._l2_标识 == 1
obs.重置基础序列()
assert obs._l3_reset == 1
assert len(obs.普通K线序列) == 0
print(" ✓ test_override_three_level_mixed")
# --- 跨方法调用 ---
def test_override_cross_method_dispatch():
"""子类方法间相互调用,确保 self 始终指向最外层实例."""
class Sub(chanlun.观察者):
def __init__(self, 符号, 周期):
self._feed_called = False
self._reset_called = False
def 增加原始K线(self, 普K):
self._feed_called = True
super().增加原始K线(普K)
def 重置基础序列(self):
self._reset_called = True
super().重置基础序列()
def compound_operation(self, 普K):
self.增加原始K线(普K)
self.重置基础序列()
obs = Sub("btcusd", 300)
k = chanlun.K线.创建普K("t", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
obs.compound_operation(k)
assert obs._feed_called
assert obs._reset_called
assert len(obs.普通K线序列) == 0 # feed 后又 reset
print(" ✓ test_override_cross_method_dispatch")
def run_subclass_tests():
print("=== 观察者 子类化/重写 测试 ===")
tests = [
@@ -272,6 +683,26 @@ def run_subclass_tests():
test_multi_level_inheritance,
test_unoverridden_method_inherited,
test_override_reset,
# 新增:property getter 逐一覆盖
test_override_getter_符号,
test_override_getter_周期,
test_override_getter_配置,
test_override_getter_当前K线,
test_override_getter_当前缠K,
test_override_getter_观察员,
# 新增:序列 getter 覆盖
test_override_sequence_getter,
test_override_all_sequence_getters,
# 新增:方法重写
test_override_加载本地数据,
test_override_静态重新分析,
test_override_测试_保存数据,
test_override_读取数据文件,
# 新增:完全重写 / 同名继承 / 多层 / 跨方法
test_override_completely_no_super,
test_override_identical_subclass,
test_override_three_level_mixed,
test_override_cross_method_dispatch,
]
for test in tests:
try:
@@ -330,7 +761,17 @@ def run_integration_test():
print(f" Output dir: {actual_out_dir}")
print(f" Output files ({len(out_files)}): {out_files}")
# Compare with Python reference
# Compare with Python reference (skip if not available)
if not os.path.isdir(_PY_REF_DIR):
print(f"\nPython reference dir not found: {_PY_REF_DIR}")
print("Skipping Python comparison. Generate reference with chan.py first.")
print(f"Rust output is at: {actual_out_dir}")
print(f"Files: {out_files}")
# Self-consistency check: at least 14 output files expected
assert len(out_files) >= 14, f"Expected >= 14 output files, got {len(out_files)}"
print("Self-consistency check passed.")
return 0
print("\nComparing with Python reference...")
ref_files = sorted(os.listdir(_PY_REF_DIR))
@@ -395,11 +836,332 @@ def run_integration_test():
return 1
# ============================================================
# K线.截取 测试
# ============================================================
def test_kxian_jiequ_basic():
"""Python端创建K线,测试 K线.截取 基本功能."""
k1 = chanlun.K线.创建普K("k1", 1000000000, 100.0, 105.0, 98.0, 102.0, 1000.0, 0, 300)
k2 = chanlun.K线.创建普K("k2", 1000000060, 102.0, 108.0, 101.0, 106.0, 1200.0, 1, 300)
k3 = chanlun.K线.创建普K("k3", 1000000120, 106.0, 110.0, 104.0, 108.0, 900.0, 2, 300)
k4 = chanlun.K线.创建普K("k4", 1000000180, 108.0, 112.0, 107.0, 110.0, 1100.0, 3, 300)
ks = [k1, k2, k3, k4]
# 截取中间 (k2~k4),含头尾
r = chanlun.K线.截取(ks, k2, k4)
assert len(r) == 3
assert r[0].时间戳 == k2.时间戳
assert r[-1].时间戳 == k4.时间戳
# 截取全部
r = chanlun.K线.截取(ks, k1, k4)
assert len(r) == 4
# 截取单根
r = chanlun.K线.截取(ks, k3, k3)
assert len(r) == 1
assert r[0].时间戳 == k3.时间戳
print(" ✓ test_kxian_jiequ_basic")
def test_kxian_jiequ_error():
"""K线.截取 异常边界测试."""
k1 = chanlun.K线.创建普K("k1", 1000000000, 100.0, 105.0, 98.0, 102.0, 1000.0, 0, 300)
k2 = chanlun.K线.创建普K("k2", 1000000060, 102.0, 108.0, 101.0, 106.0, 1200.0, 1, 300)
k3 = chanlun.K线.创建普K("k3", 1000000120, 106.0, 110.0, 104.0, 108.0, 900.0, 2, 300)
ks = [k1, k2, k3]
# 空序列 → ValueError
try:
chanlun.K线.截取([], k1, k1)
raise AssertionError("空序列应抛 ValueError")
except ValueError:
pass
# K线不在序列中 → ValueError
kx = chanlun.K线.创建普K("kx", 9999999999, 1.0, 1.0, 1.0, 1.0, 1.0, 99, 300)
try:
chanlun.K线.截取(ks, kx, k3)
raise AssertionError("不在序列中应抛 ValueError")
except ValueError:
pass
# 始在终之后 → ValueError
try:
chanlun.K线.截取(ks, k3, k1)
raise AssertionError("始在终之后应抛 ValueError")
except ValueError:
pass
print(" ✓ test_kxian_jiequ_error")
def test_kxian_jiequ_from_observer():
"""通过观察者加载数据,从笔中获取普K序列再做截取."""
cfg = chanlun.缠论配置()
obs = chanlun.观察者.读取数据文件(NB_PATH, cfg)
assert len(obs.笔序列) > 0, "笔序列为空,数据可能不够"
bi = obs.笔序列[0]
# 从笔获取普K子序列
pk_seq = bi.获取普K序列(obs)
assert len(pk_seq) > 0
# 用标的K线在全序列上截取,验证与笔首尾匹配
r = chanlun.K线.截取(obs.普通K线序列, bi...标的K线, bi...标的K线)
assert len(r) > 0
assert r[0].时间戳 == bi...标的K线.时间戳
assert r[-1].时间戳 == bi...标的K线.时间戳
# 截取前半段
mid = len(pk_seq) // 2
if mid > 0:
r = chanlun.K线.截取(pk_seq, pk_seq[0], pk_seq[mid])
assert len(r) == mid + 1
assert r[0].时间戳 == pk_seq[0].时间戳
assert r[-1].时间戳 == pk_seq[mid].时间戳
print(f" ✓ test_kxian_jiequ_from_observer (笔0: {len(pk_seq)}根普K)")
def test_kxian_jiequ_multi_bi():
"""遍历多根笔,各自截取笔内嵌K并验证首尾."""
cfg = chanlun.缠论配置()
obs = chanlun.观察者.读取数据文件(NB_PATH, cfg)
n_checked = 0
for i, bi in enumerate(obs.笔序列[:10]):
pk_seq = bi.获取普K序列(obs)
if len(pk_seq) < 2:
continue
r = chanlun.K线.截取(pk_seq, pk_seq[0], pk_seq[-1])
assert len(r) == len(pk_seq), f"笔[{i}] 截取长度不一致: {len(r)} vs {len(pk_seq)}"
n_checked += 1
assert n_checked > 0
print(f" ✓ test_kxian_jiequ_multi_bi (检查 {n_checked} 根笔)")
def run_jiequ_tests():
print("=== K线.截取 测试 ===")
tests = [
test_kxian_jiequ_basic,
test_kxian_jiequ_error,
test_kxian_jiequ_from_observer,
test_kxian_jiequ_multi_bi,
]
for test in tests:
try:
test()
except Exception as e:
print(f"{test.__name__} FAILED: {e}")
import traceback
traceback.print_exc()
return 1
print(" ✓ 全部通过")
return 0
# ============================================================
# Rc 指针身份 / list.index 测试
# ============================================================
def _load_observer(max_bars=None):
"""辅助函数:从 .nb 文件加载数据并创建观察者."""
cfg = chanlun.缠论配置()
obs = chanlun.观察者("btcusd", 300, cfg)
bars = read_nb_bars(NB_PATH, max_bars=max_bars)
for i, (ts, o, h, l, c, v) in enumerate(bars):
k = chanlun.K线.创建普K(f"k{i}", ts, o, h, l, c, v, i, 300)
obs.增加原始K线(k)
return obs
def test_rc_same_seq_index():
"""同一序列内 list.index 基于 Rc 指针身份正常工作."""
obs = _load_observer()
# 分型序列
fx_list = obs.分型序列
if len(fx_list) >= 2:
assert fx_list.index(fx_list[0]) == 0
assert fx_list.index(fx_list[1]) == 1
# 缠论K线序列
ck_list = obs.缠论K线序列
if len(ck_list) >= 2:
assert ck_list.index(ck_list[0]) == 0
assert ck_list.index(ck_list[-1]) == len(ck_list) - 1
# 笔序列
bi_list = obs.笔序列
if len(bi_list) >= 2:
assert bi_list.index(bi_list[0]) == 0
assert bi_list.index(bi_list[-1]) == len(bi_list) - 1
# 线段中的笔序列
if len(obs.线段序列) > 0:
for in obs.线段序列:
笔列表 = .笔序列
if len(笔列表) >= 2:
assert 笔列表.index(笔列表[0]) == 0
assert 笔列表.index(笔列表[-1]) == len(笔列表) - 1
break
# 中枢序列
zs_list = obs.中枢序列
if len(zs_list) >= 2:
assert zs_list.index(zs_list[0]) == 0
assert zs_list.index(zs_list[-1]) == len(zs_list) - 1
print(" ✓ test_rc_same_seq_index")
def test_rc_cross_seq_index():
"""跨序列 list.index:段.笔序列 中查找 中枢.基础序列 元素."""
obs = _load_observer()
n_checked = 0
for in obs.线段序列:
zs_list = .合_中枢序列
if len(zs_list) == 0:
continue
zs = zs_list[-1]
笔列表 = .笔序列
for j, elem in enumerate(zs.基础序列):
idx = 笔列表.index(elem)
assert idx >= 0
assert 笔列表[idx] == elem
n_checked += 1
break # 只测第一个有中枢的段
assert n_checked >= 3, f"应至少检查3个中枢元素,实际 {n_checked}"
print(f" ✓ test_rc_cross_seq_index (检查 {n_checked} 个元素)")
def test_rc_zhaofenxing_cross_ref():
"""分型序列与笔之间的跨序列引用."""
obs = _load_observer()
fx_list = obs.分型序列
if len(fx_list) >= 2 and len(obs.笔序列) > 0:
# 笔.文 (起始分型) 应能在分型序列中找到
bi = obs.笔序列[-1]
try:
idx = fx_list.index(bi.)
assert idx >= 0
except ValueError:
# 可能因为分型过滤导致不在序列中,但不应 panic
pass
print(" ✓ test_rc_zhaofenxing_cross_ref")
def test_rc_user_pattern():
"""复现用户报告的原始代码模式:在中枢基础序列第一个笔之前找同向笔."""
obs = _load_observer()
n_segments_with_zs = 0
for in obs.线段序列:
zs_list = .合_中枢序列
if len(zs_list) == 0:
continue
n_segments_with_zs += 1
zs = zs_list[-1]
笔列表 = .笔序列
# 用户原始代码模式
try:
idx = 笔列表.index(zs.基础序列[0])
except ValueError as e:
raise AssertionError(f"段[{.序号}] 笔列表中找不到中枢基础序列[0]: {e}")
# 向前查找同向笔
found = False
for bi in reversed(笔列表[:idx]):
if bi.方向 == zs.基础序列[0].方向:
found = True
break
# found 可以为 True 或 False(取决于是否有前向笔)
assert n_segments_with_zs > 0, "应至少有一个段包含中枢"
print(f" ✓ test_rc_user_pattern ({n_segments_with_zs} 个有中枢的段)")
def test_tongji_macd_behavior_types():
"""统计MACD行为返回值类型正确: int和list(tuple),非str."""
obs = _load_observer(max_bars=5000)
if len(obs.笔序列) == 0:
print(" - test_tongji_macd_behavior_types SKIP (无笔)")
return
= obs.笔序列[-1]
普K序列 = .获取普K序列(obs)
if len(普K序列) < 2:
print(" - test_tongji_macd_behavior_types SKIP (K线不够)")
return
result = chanlun.虚线.统计MACD行为(普K序列, 8, 3)
for key in ["DIF上穿0", "DIF下穿0", "DEA上穿0", "DEA下穿0", "金叉次数", "死叉次数"]:
assert isinstance(result[key], int), f"{key} 应为 int, 实际 {type(result[key])}"
assert isinstance(result["密集交叉区域"], list), f"密集交叉区域 应为 list"
for item in result["密集交叉区域"]:
assert isinstance(item, tuple), f"密集交叉区域元素 应为 tuple"
assert len(item) == 3
assert all(isinstance(v, int) for v in item)
# 验证比较操作可用
_ = result["DEA上穿0"] > 0 and result["DEA下穿0"] > 0
print(" ✓ test_tongji_macd_behavior_types")
def test_xiangduifangxiang_methods():
"""相对方向.是否向上/是否向下 等必须是方法(需要括号调用),不能是 property."""
fx = chanlun.相对方向.分析(2.0, 0.5, 1.0, 0.8)
# 验证是可调用的方法
assert callable(fx.是否向上), "是否向上 必须是方法"
assert callable(fx.是否向下), "是否向下 必须是方法"
assert callable(fx.是否包含), "是否包含 必须是方法"
assert callable(fx.是否缺口), "是否缺口 必须是方法"
assert callable(fx.是否衔接), "是否衔接 必须是方法"
# 验证返回值类型
assert isinstance(fx.是否向上(), bool)
assert isinstance(fx.是否向下(), bool)
print(" ✓ test_xiangduifangxiang_methods")
def run_rc_identity_tests():
print("=== Rc 身份 / list.index 测试 ===")
tests = [
test_tongji_macd_behavior_types,
test_xiangduifangxiang_methods,
test_rc_same_seq_index,
test_rc_cross_seq_index,
test_rc_zhaofenxing_cross_ref,
test_rc_user_pattern,
]
for test in tests:
try:
test()
except Exception as e:
print(f"{test.__name__} FAILED: {e}")
import traceback
traceback.print_exc()
return 1
print(" ✓ 全部通过")
return 0
def main():
import argparse
parser = argparse.ArgumentParser(description="chanlun PyO3 集成测试")
parser.add_argument("test", nargs="?", default="all", choices=["all", "subclass", "integration"], help="运行哪组测试 (默认: all)")
parser.add_argument("test", nargs="?", default="all", choices=["all", "subclass", "integration", "jiequ", "rc"], help="运行哪组测试 (默认: all)")
args = parser.parse_args()
exit_code = 0
@@ -412,6 +1174,14 @@ def main():
if run_integration_test() != 0:
exit_code = 1
if args.test in ("all", "jiequ"):
if run_jiequ_tests() != 0:
exit_code = 1
if args.test in ("all", "rc"):
if run_rc_identity_tests() != 0:
exit_code = 1
if exit_code == 0:
print("\n✓ 所有测试通过")
+267
View File
@@ -0,0 +1,267 @@
#!/usr/bin/env python3
"""
对象标识测试验证同一 Rc 底层数据通过不同路径访问时
Python 侧始终返回相同的 PyObject`is` 比较为 True
涉及的 Rc 包装类型
- K线 (Rc<K线>) 原始OHLCV数据
- 缠论K线 (Rc<缠论K线>) 包含处理后的K线
- 分型 (Rc<分型>) 顶底分型
- 虚线 (Rc<虚线>) /线段的通用抽象
- 中枢 (Rc<中枢>) 三段虚线重叠区间
- 线段特征 (Rc<线段特征>) 线段特征序列元素
- 特征分型 (Rc<特征分型>) 特征序列的分型
路径示例
- 缠K序列[i] vs 分型序列[j]. (同一根缠K)
- 分型序列[i] vs 笔序列[j]. (同一个分型)
- 笔序列[i] vs 中枢[k].基础序列[m] (同一条虚线)
"""
import chanlun
import math
def create_observer(symbol="btcusd", period=14400, n_bars=500):
"""创建观察者并喂入模拟K线数据。"""
cfg = chanlun.缠论配置()
obs = chanlun.观察者(symbol, period, cfg)
for i in range(n_bars):
trend = i * 3
wave = math.sin(i * 0.05) * 2000
mid = 68000.0 + trend + wave
high = mid + abs(math.cos(i * 0.3)) * 400 + 100
low = mid - abs(math.sin(i * 0.5)) * 400 - 100
k = chanlun.K线(
标识=symbol,
周期=period,
时间戳=1771675200 + i * period,
开盘价=mid - 50,
=high,
=low,
收盘价=mid + 50,
成交量=abs(math.sin(i)) * 1000,
)
obs.增加原始K线(k)
return obs
class Test缠K身份:
"""缠论K线: 从序列、分型、笔端点、中枢等不同路径访问。"""
def test_序列重复获取(self):
"""同一序列获取两次,元素应相同。"""
obs = create_observer()
seq1 = obs.缠论K线序列
seq2 = obs.缠论K线序列
for i in range(min(len(seq1), 10)):
assert seq1[i] is seq2[i], f"缠K序列[{i}] 身份不一致"
def test_分型中K(self):
"""分型.中 与 缠K序列 对应元素应相同。"""
obs = create_observer()
seq = obs.缠论K线序列
分序 = obs.分型序列
for fx in 分序[:10]:
= fx.
for ck in seq:
if ck.时间戳 == .时间戳:
assert ck is , f"分型.中 (ts={.时间戳}) 与序列中元素不匹配"
break
def test_笔端点钟K(self):
"""笔的端点分型的中间K线应与序列元素相同。"""
obs = create_observer()
seq = obs.缠论K线序列
for bi in obs.笔序列:
for nm, getter in [("", lambda b=bi: b.), ("", lambda b=bi: b.)]:
ep = getter()
if ep is None:
continue
= ep.
for ck in seq:
if ck.时间戳 == .时间戳:
assert ck is , f"笔.{nm}.中 (ts={.时间戳}) 与序列中元素不匹配"
break
def test_getter重复调用(self):
"""同一getter调用两次返回同一对象。"""
obs = create_observer()
for fx in obs.分型序列[:5]:
中1 = fx.
中2 = fx.
assert 中1 is 中2, "分型.中 两次调用返回不同对象"
class Test分型身份:
"""分型: 从分型序列、笔/线段端点、买卖点等不同路径访问。"""
def test_序列重复获取(self):
"""同一序列获取两次,元素应相同。"""
obs = create_observer()
seq1 = obs.分型序列
seq2 = obs.分型序列
for i in range(min(len(seq1), 9)):
assert seq1[i] is seq2[i], f"分型序列[{i}] 身份不一致"
def test_笔端点与序列(self):
"""笔.文 / 笔.武 应与分型序列中对应元素相同。"""
obs = create_observer()
分序 = obs.分型序列
for bi in obs.笔序列:
for nm in ["", ""]:
ep = getattr(bi, nm)
if ep is None:
continue
matched = False
for fx in 分序:
if fx.时间戳 == ep.时间戳 and fx.结构 == ep.结构:
assert fx is ep, f"笔.{nm} (ts={ep.时间戳}) 与分型序列中元素不匹配"
matched = True
break
assert matched, f"笔.{nm} (ts={ep.时间戳}) 在分型序列中未找到"
def test_段端点与序列(self):
"""段.文 / 段.武 应与分型序列中对应元素相同。"""
obs = create_observer()
分序 = obs.分型序列
for duan in obs.线段序列:
for nm in ["", ""]:
ep = getattr(duan, nm)
if ep is None:
continue
matched = False
for fx in 分序:
if fx.时间戳 == ep.时间戳 and fx.结构 == ep.结构:
assert fx is ep, f"段.{nm} (ts={ep.时间戳}) 与分型序列中元素不匹配"
matched = True
break
assert matched, f"段.{nm} (ts={ep.时间戳}) 在分型序列中未找到"
def test_getter重复调用(self):
"""同一getter调用两次返回同一对象。"""
obs = create_observer()
for bi in obs.笔序列:
文1 = bi.
文2 = bi.
assert 文1 is 文2, "笔.文 两次调用返回不同对象"
武1 = bi.
武2 = bi.
assert 武1 is 武2, "笔.武 两次调用返回不同对象"
break # 只测第一笔
class Test虚线身份:
"""虚线(笔/线段): 从笔序列、线段序列、中枢内部序列等不同路径访问。"""
def test_笔序列重复获取(self):
obs = create_observer()
seq1 = obs.笔序列
seq2 = obs.笔序列
for i in range(min(len(seq1), 8)):
assert seq1[i] is seq2[i], f"笔序列[{i}] 身份不一致"
def test_线段序列重复获取(self):
obs = create_observer()
seq1 = obs.线段序列
seq2 = obs.线段序列
for i in range(min(len(seq1), 5)):
assert seq1[i] is seq2[i], f"线段序列[{i}] 身份不一致"
def test_多个扩展序列(self):
"""扩展线段的不同序列获取同一虚线应相同。"""
obs = create_observer()
s1 = obs.扩展线段序列
s2 = obs.扩展线段序列_线段
s3 = obs.扩展线段序列_扩展线段
# 这些序列可能包含不同的虚线,但如果同一个 Rc 出现在两个序列中应该相同
for d1 in s1:
for d2 in s2:
if d1.序号 == d2.序号:
assert d1 is d2, f"扩展线段序列[{d1.序号}] 跨序列身份不一致"
break
class TestK线身份:
"""原始K线: 从序列、买卖点、缠K标的等不同路径访问。"""
def test_序列重复获取(self):
obs = create_observer()
seq1 = obs.普通K线序列
seq2 = obs.普通K线序列
for i in range(min(len(seq1), 10)):
assert seq1[i] is seq2[i], f"普K序列[{i}] 身份不一致"
class Test中枢身份:
"""中枢: 从中枢序列、分型关联、笔中枢/线段中枢等不同路径访问。"""
def test_序列重复获取(self):
obs = create_observer(period=3600, n_bars=800)
seq1 = obs.中枢序列
seq2 = obs.中枢序列
for i in range(min(len(seq1), 5)):
assert seq1[i] is seq2[i], f"中枢序列[{i}] 身份不一致"
def test_笔中枢与线段中枢(self):
obs = create_observer(period=3600, n_bars=800)
笔中 = obs.笔_中枢序列
段中 = obs.线段_中枢序列
扩展中 = obs.扩展中枢序列
# 验证同一次获取内的身份
for zs in 笔中:
文1 = zs.
文2 = zs.
assert 文1 is 文2, f"笔中枢.文 两次调用不同"
break
for zs in 段中:
文1 = zs.
文2 = zs.
assert 文1 is 文2, f"段中枢.文 两次调用不同"
break
class Test整体身份:
"""跨类型综合身份测试。"""
def test_买卖点分型(self):
"""验证买卖点的关联分型身份。"""
obs = create_observer(period=3600, n_bars=800)
# 尝试访问可用的结构
分序 = obs.分型序列
笔序 = obs.笔序列
assert len(分序) >= 0 and len(笔序) >= 0 # 至少不崩溃
def test_全链路一致性(self):
"""缠K → 分型 → 笔 → 段 链路中所有对象身份一致。"""
obs = create_observer()
seq = obs.缠论K线序列
for bi in obs.笔序列:
# 笔的端点分型
for nm, getter in [("", lambda b=bi: b.), ("", lambda b=bi: b.)]:
ep = getter()
if ep is None:
continue
# ep 中的 中 是一根缠K,应能在序列中找到相同对象
= ep.
for ck in seq:
if ck.时间戳 == .时间戳:
assert ck is
break
# 左也应该是可访问的
= ep.
if is not None:
for ck in seq:
if ck.时间戳 == .时间戳:
assert ck is
break
# 右也应该是可访问的
= ep.
if is not None:
for ck in seq:
if ck.时间戳 == .时间戳:
assert ck is
break
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "chanlun"
version = "26.5.1"
version = "26.5.3"
edition = "2021"
rust-version = "1.70"
license = "MIT"
+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.
+223 -159
View File
@@ -29,7 +29,8 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::dash_line::线;
use crate::structure::fractal_obj::;
use crate::types::{, };
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
/// 笔 — 从分型生成笔的算法集合(静态方法命名空间)
pub struct ;
@@ -37,8 +38,8 @@ pub struct 笔;
impl {
/// 获取可成笔的缠K数量(考虑弱化模式)
pub fn K数量(
K序列: &[Rc<K线>],
: &[Rc<线>],
K序列: &[Arc<K线>],
: &[Arc<线>],
: &,
) -> usize {
let = K序列.len();
@@ -51,7 +52,9 @@ impl 笔 {
let = Self::(K序列, .);
if let (Some(ref ), Some(ref )) = (&, &) {
let = 1 + (.K线. - .K线.).unsigned_abs() as usize;
let = 1
+ (.K线.read().unwrap(). - .K线.read().unwrap().)
.unsigned_abs() as usize;
if >= . as usize {
return . as usize;
}
@@ -70,16 +73,18 @@ impl 笔 {
if let Some(ref ) = {
if let (Some(ref _k), Some(ref _k)) = (&, &) {
let =
1 + (_k.K线. - _k.K线.).unsigned_abs() as usize;
let = 1
+ (_k.K线.read().unwrap().
- _k.K线.read().unwrap().)
.unsigned_abs() as usize;
// 向上笔
if .().() && _k. < .() {
if .().() && _k..get() < .() {
if >= ._原始数量 as usize {
return . as usize;
}
}
// 向下笔
if .().() && _k. > .() {
if .().() && _k..get() > .() {
if >= ._原始数量 as usize {
return . as usize;
}
@@ -92,147 +97,182 @@ impl 笔 {
}
/// 次高 — 排除最高值后的次高点
pub fn (K序列: &[Rc<K线>], : bool) -> Option<Rc<K线>> {
pub fn (K序列: &[Arc<K线>], : bool) -> Option<Arc<K线>> {
if K序列.len() < 2 {
return K序列.first().cloned();
}
let max_高 = K序列
.iter()
.map(|k| k.)
.map(|k| k..get())
.fold(f64::NEG_INFINITY, f64::max);
// 排除最高值
let filtered: Vec<&Rc<K线>> = K序列.iter().filter(|k| k. != max_高).collect();
let filtered: Vec<&Arc<K线>> =
K序列.iter().filter(|k| k..get() != max_高).collect();
if filtered.is_empty() {
return K序列.first().cloned();
}
// 筛选次高值
let second_高 = filtered
.iter()
.map(|k| k.)
.map(|k| k..get())
.fold(f64::NEG_INFINITY, f64::max);
let mut candidates: Vec<&Rc<K线>> = filtered
let mut candidates: Vec<&Arc<K线>> = filtered
.iter()
.filter(|k| k. == second_高)
.filter(|k| k..get() == second_高)
.copied()
.collect();
// 按时间戳排序
candidates.sort_by(|a, b| a..cmp(&b.));
candidates.sort_by(|a, b| {
a.
.load(Ordering::Relaxed)
.cmp(&b..load(Ordering::Relaxed))
});
if {
Some(Rc::clone(candidates[candidates.len() - 1]))
Some(Arc::clone(candidates[candidates.len() - 1]))
} else {
Some(Rc::clone(candidates[0]))
Some(Arc::clone(candidates[0]))
}
}
/// 次低 — 排除最低值后的次低点
pub fn (K序列: &[Rc<K线>], : bool) -> Option<Rc<K线>> {
pub fn (K序列: &[Arc<K线>], : bool) -> Option<Arc<K线>> {
if K序列.len() < 2 {
return K序列.first().cloned();
}
let min_低 = K序列.iter().map(|k| k.).fold(f64::INFINITY, f64::min);
let min_低 = K序列
.iter()
.map(|k| k..get())
.fold(f64::INFINITY, f64::min);
// 排除最低值
let filtered: Vec<&Rc<K线>> = K序列.iter().filter(|k| k. != min_低).collect();
let filtered: Vec<&Arc<K线>> =
K序列.iter().filter(|k| k..get() != min_低).collect();
if filtered.is_empty() {
return K序列.first().cloned();
}
// 筛选次低值
let second_低 = filtered.iter().map(|k| k.).fold(f64::INFINITY, f64::min);
let mut candidates: Vec<&Rc<K线>> = filtered
let second_低 = filtered
.iter()
.filter(|k| k. == second_低)
.map(|k| k..get())
.fold(f64::INFINITY, f64::min);
let mut candidates: Vec<&Arc<K线>> = filtered
.iter()
.filter(|k| k..get() == second_低)
.copied()
.collect();
// 按时间戳排序
candidates.sort_by(|a, b| a..cmp(&b.));
candidates.sort_by(|a, b| {
a.
.load(Ordering::Relaxed)
.cmp(&b..load(Ordering::Relaxed))
});
if {
Some(Rc::clone(candidates[candidates.len() - 1]))
Some(Arc::clone(candidates[candidates.len() - 1]))
} else {
Some(Rc::clone(candidates[0]))
Some(Arc::clone(candidates[0]))
}
}
/// 实际高点
pub fn (K序列: &[Rc<K线>], : bool) -> Option<Rc<K线>> {
pub fn (K序列: &[Arc<K线>], : bool) -> Option<Arc<K线>> {
if K序列.is_empty() {
return None;
}
let max_高 = K序列
.iter()
.map(|k| k.)
.map(|k| k..get())
.fold(f64::NEG_INFINITY, f64::max);
let mut candidates: Vec<&Rc<K线>> = K序列.iter().filter(|k| k. == max_高).collect();
let mut candidates: Vec<&Arc<K线>> =
K序列.iter().filter(|k| k..get() == max_高).collect();
if candidates.is_empty() {
return Some(Rc::clone(&K序列[0]));
return Some(Arc::clone(&K序列[0]));
}
// 按时间戳排序
candidates.sort_by(|a, b| a..cmp(&b.));
candidates.sort_by(|a, b| {
a.
.load(Ordering::Relaxed)
.cmp(&b..load(Ordering::Relaxed))
});
if {
Some(Rc::clone(candidates[candidates.len() - 1]))
Some(Arc::clone(candidates[candidates.len() - 1]))
} else {
Some(Rc::clone(candidates[0]))
Some(Arc::clone(candidates[0]))
}
}
/// 实际低点
pub fn (K序列: &[Rc<K线>], : bool) -> Option<Rc<K线>> {
pub fn (K序列: &[Arc<K线>], : bool) -> Option<Arc<K线>> {
if K序列.is_empty() {
return None;
}
let min_低 = K序列.iter().map(|k| k.).fold(f64::INFINITY, f64::min);
let mut candidates: Vec<&Rc<K线>> = K序列.iter().filter(|k| k. == min_低).collect();
let min_低 = K序列
.iter()
.map(|k| k..get())
.fold(f64::INFINITY, f64::min);
let mut candidates: Vec<&Arc<K线>> =
K序列.iter().filter(|k| k..get() == min_低).collect();
if candidates.is_empty() {
return Some(Rc::clone(&K序列[0]));
return Some(Arc::clone(&K序列[0]));
}
// 按时间戳排序
candidates.sort_by(|a, b| a..cmp(&b.));
candidates.sort_by(|a, b| {
a.
.load(Ordering::Relaxed)
.cmp(&b..load(Ordering::Relaxed))
});
if {
Some(Rc::clone(candidates[candidates.len() - 1]))
Some(Arc::clone(candidates[candidates.len() - 1]))
} else {
Some(Rc::clone(candidates[0]))
Some(Arc::clone(candidates[0]))
}
}
/// 判断笔的相对关系是否合理
pub fn (: &线, : &) -> bool {
let = &.;
let = &.;
let = ..read().unwrap();
let = if . {
let _rc = Rc::clone(&.);
let _rc = Rc::clone(&.);
let _元素: [Option<&Rc<K线>>; 3] =
let _rc = Arc::clone(&.);
let _rc = Arc::clone(&.);
let _元素: [Option<&Arc<K线>>; 3] =
[..as_ref(), Some(&_rc), ..as_ref()];
let : Vec<&Rc<K线>> = _元素.iter().filter_map(|x| *x).collect();
let : Vec<&Arc<K线>> = _元素.iter().filter_map(|x| *x).collect();
let =
.iter()
.map(|k| k.)
.map(|k| k..get())
.fold(f64::NEG_INFINITY, f64::max);
let = .iter().map(|k| k.).fold(f64::INFINITY, f64::min);
let =
.iter()
.map(|k| k..get())
.fold(f64::INFINITY, f64::min);
let _右: Option<&Rc<K线>> = if ._包括右 {
let _右: Option<&Arc<K线>> = if ._包括右 {
..as_ref()
} else {
None
};
let _元素: [Option<&Rc<K线>>; 3] = [..as_ref(), Some(&_rc), _右];
let : Vec<&Rc<K线>> = _元素.iter().filter_map(|x| *x).collect();
let _元素: [Option<&Arc<K线>>; 3] = [..as_ref(), Some(&_rc), _右];
let : Vec<&Arc<K线>> = _元素.iter().filter_map(|x| *x).collect();
let =
.iter()
.map(|k| k.)
.map(|k| k..get())
.fold(f64::NEG_INFINITY, f64::max);
let = .iter().map(|k| k.).fold(f64::INFINITY, f64::min);
let =
.iter()
.map(|k| k..get())
.fold(f64::INFINITY, f64::min);
crate::types::::(, , , )
} else {
let = crate::types::::(
..,
..,
..,
..,
...get(),
...get(),
...get(),
...get(),
);
if .K线包含整笔 {
let = &..K线;
let = &..K线;
let = ..K线.read().unwrap();
let = ..K线.read().unwrap();
if crate::types::::(., ., ., .)
.()
{
@@ -249,43 +289,46 @@ impl 笔 {
}
/// 以文会友 — 根据起点分型找笔
pub fn (: &[Rc<线>], : &Rc<>) -> Option<Rc<线>> {
pub fn (: &[Arc<线>], : &Arc<>) -> Option<Arc<线>> {
.iter()
.find(|b| Rc::as_ptr(&b.) == Rc::as_ptr())
.find(|b| Arc::as_ptr(&b.) == Arc::as_ptr())
.cloned()
}
/// 以武会友 — 根据终点分型找笔
pub fn (: &[Rc<线>], : &Rc<>) -> Option<Rc<线>> {
pub fn (: &[Arc<线>], : &Arc<>) -> Option<Arc<线>> {
.iter()
.rev()
.find(|b| Rc::as_ptr(&b.) == Rc::as_ptr())
.find(|b| Arc::as_ptr(&*b..read().unwrap()) == Arc::as_ptr())
.cloned()
}
/// 根据缠K找对应的笔
pub fn K找笔(
: &[Rc<线>],
K: &Rc<K线>,
: &[Arc<线>],
K: &Arc<K线>,
: i64,
) -> Option<Rc<线>> {
) -> Option<Arc<线>> {
// Python iterates in reverse: for 筆 in 笔序列[::-1]
for b in .iter().rev() {
// Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号
if b... - <= K. && K. <= b... {
return Some(Rc::clone(b));
// Python: 筆.文.中.序号 - 偏移 <= 缠K.序号.load(Ordering::Relaxed) <= 筆.武.read().unwrap().中.序号
if b....load(Ordering::Relaxed) - <= K..load(Ordering::Relaxed)
&& K..load(Ordering::Relaxed)
<= b..read().unwrap()...load(Ordering::Relaxed)
{
return Some(Arc::clone(b));
}
}
None
}
/// 从分型序列中弹出最后一个分型和对应的笔
fn (: &mut Vec<Rc<>>, : &mut Vec<Rc<线>>) {
fn (: &mut Vec<Arc<>>, : &mut Vec<Arc<线>>) {
.pop();
if !.is_empty() {
// Python sets旧笔.有效性 = False; with Rc we just drop the笔
// Python sets旧笔.有效性.store(False, Ordering::Relaxed); with Rc we just drop the笔
.pop();
}
}
@@ -294,19 +337,19 @@ impl 笔 {
///
/// 返回: 递归层次数
pub fn (
: Rc<>,
: &mut Vec<Rc<>>,
: &mut Vec<Rc<线>>,
K序列: &[Rc<K线>],
_普K序列: &[Rc<K线>],
: Arc<>,
: &mut Vec<Arc<>>,
: &mut Vec<Arc<线>>,
K序列: &[Arc<K线>],
_普K序列: &[Arc<K线>],
: &,
) -> i64 {
enum {
(Rc<>, i64),
(Arc<>, i64),
/// 修复错过笔哨兵: 若临时分型被接受为最后一个元素,则扫描武将之后的所有分型
{
: Rc<>,
K: Rc<K线>,
: Arc<>,
K: Arc<K线>,
: i64,
},
}
@@ -325,20 +368,20 @@ impl 笔 {
// Python line 2406: only scan if临时分型 was accepted as last element
if !.is_empty() {
if let Some(last_fx) = .last() {
if Rc::as_ptr(last_fx) == Rc::as_ptr(&) {
if Arc::as_ptr(last_fx) == Arc::as_ptr(&) {
if let Some(_idx) = K序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(&K))
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(&K))
{
let mut : Vec<Rc<>> = Vec::new();
let mut : Vec<Arc<>> = Vec::new();
for ck in &K序列[_idx..] {
if ck. == Some(::)
|| ck. == Some(::)
if *ck..read().unwrap() == Some(::)
|| *ck..read().unwrap() == Some(::)
{
if let Some(fx) =
::K序列中获取分型(K序列, ck)
{
.push(Rc::new(fx));
.push(Arc::new(fx));
}
}
}
@@ -369,7 +412,7 @@ impl 笔 {
continue;
}
let = Rc::clone(.last().unwrap());
let = Arc::clone(.last().unwrap());
// Python line 2330-2335: 清理无效数据
if . == .
@@ -384,10 +427,13 @@ impl 笔 {
}
}
let = Rc::clone(.last().unwrap());
let = Arc::clone(.last().unwrap());
// Python line 2338: 时序检查 — skip out-of-order fractals
if . > . && .. - .. > 1
if . > .
&& ...load(Ordering::Relaxed)
- ...load(Ordering::Relaxed)
> 1
{
continue;
}
@@ -395,7 +441,9 @@ impl 笔 {
// Python line 2343-2348: 笔弱化模式
if . && !.is_empty() {
let = .last().unwrap();
let K数 = ... - ... + 1;
let K数 = ..read().unwrap()...load(Ordering::Relaxed)
- ....load(Ordering::Relaxed)
+ 1;
if K数 == 3 {
let = (.().()
&& .() > .
@@ -412,16 +460,16 @@ impl 笔 {
}
// Re-read之前分型 again after笔弱化 pop
let = Rc::clone(.last().unwrap());
let = Arc::clone(.last().unwrap());
// Python line 2350: 分型结构相反 → 可能成笔
if . != . {
let _idx = K序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(&.));
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(&.));
let _idx = K序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(&.));
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(&.));
if let (Some(_idx), Some(_idx)) = (_idx, _idx) {
let = &K序列[_idx..=_idx];
@@ -436,12 +484,12 @@ impl 笔 {
// Python line 2359-2367: 文官 != 之前分型.中 → adjust
if let Some(ref _k) = {
if Rc::as_ptr(_k) != Rc::as_ptr(&.) {
if Arc::as_ptr(_k) != Arc::as_ptr(&.) {
if let Some() =
::K序列中获取分型(K序列, _k)
{
.push(::(, ));
.push(::(Rc::new(), + 1));
.push(::(Arc::new(), + 1));
continue;
}
}
@@ -455,16 +503,16 @@ impl 笔 {
_ => Self::(, .),
};
let = Rc::new(线::(
Rc::clone(&),
Rc::clone(&),
let = Arc::new(线::(
Arc::clone(&),
Arc::clone(&),
true,
));
// Python line 2374-2376: 相对关系 and武将 matches
if Self::(&, ) {
if let Some(ref _k) = {
if Rc::as_ptr(_k) == Rc::as_ptr(&.) {
if Arc::as_ptr(_k) == Arc::as_ptr(&.) {
Self::_添加新笔递归(, , , );
continue;
}
@@ -478,7 +526,7 @@ impl 笔 {
_ => Self::(, .),
};
if let Some(ref _k) = {
if Rc::as_ptr(_k) == Rc::as_ptr(&.)
if Arc::as_ptr(_k) == Arc::as_ptr(&.)
&& Self::(&, )
{
Self::_添加新笔递归(, , , );
@@ -492,7 +540,7 @@ impl 笔 {
if let Some() =
::K序列中获取分型(K序列, )
{
.push(::(Rc::new(), + 1));
.push(::(Arc::new(), + 1));
continue;
}
}
@@ -509,7 +557,7 @@ impl 笔 {
};
if {
let = Rc::clone(&);
let = Arc::clone(&);
Self::(, );
if let Some(k线序列) = K线::(K序列, &., &.)
@@ -525,14 +573,14 @@ impl 笔 {
if let Some() =
::K序列中获取分型(K序列, _k)
{
let _rc = Rc::new();
let _rc = Arc::new();
if !.is_empty() {
// Push in reverse processing order (LIFO):
.push(::(Rc::clone(&), + 2));
.push(::(Arc::clone(&), + 2));
.push(:: {
: Rc::clone(&_rc),
K: Rc::clone(_k),
: Arc::clone(&_rc),
K: Arc::clone(_k),
: + 1,
});
.push(::(_rc, + 1));
@@ -561,11 +609,11 @@ impl 笔 {
///
/// 返回: 递归层次数
pub fn (
: Rc<>,
: &mut Vec<Rc<>>,
: &mut Vec<Rc<线>>,
K序列: &[Rc<K线>],
_普K序列: &[Rc<K线>],
: Arc<>,
: &mut Vec<Arc<>>,
: &mut Vec<Arc<线>>,
K序列: &[Arc<K线>],
_普K序列: &[Arc<K线>],
: i64,
: &,
) -> i64 {
@@ -588,7 +636,7 @@ impl 笔 {
}
// Python line 2329-2335: 清理无效数据
let = Rc::clone(.last().unwrap());
let = Arc::clone(.last().unwrap());
if . == .
|| matches!(., :: | ::)
{
@@ -602,8 +650,10 @@ impl 笔 {
}
// Python line 2337-2341: 时序检查
let = Rc::clone(.last().unwrap());
if . > . && .. - .. > 1
let = Arc::clone(.last().unwrap());
if . > .
&& ...load(Ordering::Relaxed) - ...load(Ordering::Relaxed)
> 1
{
println!("时序错误-{}, {}, {}", , , );
return ;
@@ -612,7 +662,9 @@ impl 笔 {
// Python line 2343-2348: 笔弱化模式
if . && !.is_empty() {
let = .last().unwrap();
let K数 = ... - ... + 1;
let K数 = ..read().unwrap()...load(Ordering::Relaxed)
- ....load(Ordering::Relaxed)
+ 1;
if K数 == 3 {
let = (.().()
&& .() > .
@@ -636,13 +688,13 @@ impl 笔 {
}
// Python line 2350: 分型结构相反 → 可能成笔
let = Rc::clone(.last().unwrap());
let = Arc::clone(.last().unwrap());
if . != . {
if let Some() = K线::(K序列, &., &.)
{
let = Rc::new(线::(
Rc::clone(&),
Rc::clone(&),
let = Arc::new(线::(
Arc::clone(&),
Arc::clone(&),
true,
));
@@ -656,12 +708,12 @@ impl 笔 {
// Python line 2359-2367: 文官调整
if let Some(ref _k) = {
if Rc::as_ptr(_k) != Rc::as_ptr(&.) {
if Arc::as_ptr(_k) != Arc::as_ptr(&.) {
if let Some() =
::K序列中获取分型(K序列, _k)
{
let = Self::(
Rc::new(),
Arc::new(),
,
,
K序列,
@@ -690,7 +742,7 @@ impl 笔 {
if Self::(&, ) {
if let Some(ref _k) = {
if Rc::as_ptr(_k) == Rc::as_ptr(&.) {
if Arc::as_ptr(_k) == Arc::as_ptr(&.) {
// 直接添加(对照 Python _添加新笔:直接 append
Self::_添加新笔递归(, , , );
return ;
@@ -705,7 +757,7 @@ impl 笔 {
_ => Self::(&, .),
};
if let Some(ref _k) = {
if Rc::as_ptr(_k) == Rc::as_ptr(&.)
if Arc::as_ptr(_k) == Arc::as_ptr(&.)
&& Self::(&, )
{
Self::_添加新笔递归(, , , );
@@ -719,7 +771,7 @@ impl 笔 {
if let Some() = ::K序列中获取分型(K序列, )
{
return Self::(
Rc::new(),
Arc::new(),
,
,
K序列,
@@ -743,7 +795,7 @@ impl 笔 {
if {
// 保存被弹出的之前分型(用于修复错过笔的范围计算)
let = Rc::clone(&);
let = Arc::clone(&);
Self::(, );
if let Some(k线序列) = K线::(K序列, &., &.)
@@ -757,11 +809,11 @@ impl 笔 {
if let Some() =
::K序列中获取分型(K序列, _k)
{
let _rc = Rc::new();
let _rc = Arc::new();
if !.is_empty() {
let mut = Self::(
Rc::clone(&_rc),
Arc::clone(&_rc),
,
,
K序列,
@@ -772,25 +824,25 @@ impl 笔 {
// 修复错过的笔: 扫描武将之后的所有分型
if !.is_empty()
&& Rc::as_ptr(.last().unwrap())
== Rc::as_ptr(&_rc)
&& Arc::as_ptr(.last().unwrap())
== Arc::as_ptr(&_rc)
{
if let Some(_idx) = K序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(_k))
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(_k))
{
for ck in &K序列[_idx..] {
if ck. == Some(::)
|| ck. == Some(::)
if *ck..read().unwrap() == Some(::)
|| *ck..read().unwrap() == Some(::)
{
if let Some() =
::K序列中获取分型(
K序列, ck,
)
{
let _rc = Rc::new();
let _rc = Arc::new();
= Self::(
Rc::clone(&_rc),
Arc::clone(&_rc),
,
,
K序列,
@@ -839,17 +891,20 @@ impl 笔 {
/// 添加新笔到序列(递归版本 — 直接追加,对应 Python _添加新笔)
fn _添加新笔递归(
: &mut Vec<Rc<>>,
: &mut Vec<Rc<线>>,
: Rc<>,
mut : Rc<线>,
: &mut Vec<Arc<>>,
: &mut Vec<Arc<线>>,
: Arc<>,
mut : Arc<线>,
) {
.push();
if !.is_empty() {
let seg = Rc::make_mut(&mut );
seg. = .last().unwrap(). + 1;
if seg...is_none() && seg...is_none() {
seg. = false;
let seg = Arc::make_mut(&mut );
seg..store(
.last().unwrap()..load(Ordering::Relaxed) + 1,
Ordering::Relaxed,
);
if seg..read().unwrap()..is_none() && seg..read().unwrap()..is_none() {
seg..store(false, Ordering::Relaxed);
}
}
.push();
@@ -867,7 +922,8 @@ impl 笔 {
Self::(&, false),
Self::(&, .),
) {
if Rc::ptr_eq(&.., &) && Rc::ptr_eq(&.., &)
if Arc::ptr_eq(&.., &)
&& Arc::ptr_eq(&..read().unwrap()., &)
{
return true;
}
@@ -878,7 +934,8 @@ impl 笔 {
Self::(&, false),
Self::(&, .),
) {
if Rc::ptr_eq(&.., &) && Rc::ptr_eq(&.., &)
if Arc::ptr_eq(&.., &)
&& Arc::ptr_eq(&..read().unwrap()., &)
{
return true;
}
@@ -891,7 +948,7 @@ impl 笔 {
/// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合
pub fn (: &线, : &) -> Vec<线> {
let mut = Vec::new();
let = Rc::clone(&.);
let = Arc::clone(&.);
let = .K序列(&.K线序列);
if .len() < 5 {
@@ -901,23 +958,30 @@ impl 笔 {
for i in 3...len() - 1 {
let k = &[i];
if k. == Some(::) && .() == :: {
let = Rc::clone(&[i - 1]);
let = Rc::clone(k);
let = Rc::clone(&[i + 1]);
if *k..read().unwrap() == Some(::) && .() == ::
{
let = Arc::clone(&[i - 1]);
let = Arc::clone(k);
let = Arc::clone(&[i + 1]);
let = ::new(Some(), , Some());
let mut = 线::(Rc::clone(&), Rc::new(), true);
. = .;
let = 线::(Arc::clone(&), Arc::new(), true);
.
.store(..load(Ordering::Relaxed), Ordering::Relaxed);
if Self::(&, ) {
.push();
}
} else if k. == Some(::) && .() == :: {
let = Rc::clone(&[i - 1]);
let = Rc::clone(k);
let = Rc::clone(&[i + 1]);
} else if *k..read().unwrap() == Some(::)
&& .() == ::
{
let = Arc::clone(&[i - 1]);
let = Arc::clone(k);
let = Arc::clone(&[i + 1]);
let = ::new(Some(), , Some());
let mut = 线::(Rc::clone(&), Rc::new(), true);
. = .;
let = 线::(Arc::clone(&), Arc::new(), true);
.
.store(..load(Ordering::Relaxed), Ordering::Relaxed);
if Self::(&, ) {
.push();
}
@@ -928,19 +992,19 @@ impl 笔 {
}
/// 是否背驰过 — 判断笔是否在停顿位置出现过MACD趋向背驰
pub fn (: &线, : &) -> Vec<Rc<>> {
pub fn (: &线, : &) -> Vec<Arc<K线>> {
let = Self::(, );
let mut = Vec::new();
for in & {
let k线范围 = K线::rc(
&.K线序列,
&...K线,
&...K线,
&...K线.read().unwrap().clone(),
&..read().unwrap()..K线.read().unwrap().clone(),
);
let = 线::K线序列MACD趋向背驰(&k线范围, .());
if .iter().all(|&x| x) {
.push(Rc::clone(&.));
.push(Arc::clone(&..read().unwrap().));
}
}
+32 -22
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;
+476 -129
View File
@@ -25,35 +25,50 @@
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)]
/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致
#[derive(Debug)]
pub struct {
pub : i64,
pub : String,
pub : i64,
pub : Vec<Rc<线>>,
pub 线: Option<Rc<线>>,
pub _第三买卖线: Option<Rc<线>>,
pub : AtomicI64,
pub : RwLock<String>,
pub : AtomicI64,
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 {
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 {
@@ -61,21 +76,21 @@ impl 中枢 {
"{}:{}:{}:{}",
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].().()
}
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))
@@ -83,7 +98,7 @@ impl 中枢 {
}
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))
@@ -92,6 +107,8 @@ impl 中枢 {
pub fn (&self) -> f64 {
self.
.read()
.unwrap()
.iter()
.map(|x| x.())
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
@@ -100,47 +117,54 @@ impl 中枢 {
pub fn (&self) -> f64 {
self.
.read()
.unwrap()
.iter()
.map(|x| x.())
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
.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, 线: Arc<线>) {
*self.线.write().unwrap() = Some(线);
}
/// 获取序列 — 基础序列 + 第三买卖线(若有)
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..read().unwrap(),
self..load(Ordering::Relaxed),
self..load(Ordering::Relaxed),
self.().,
crate::utils::format_f64_g(self.().),
self.().,
@@ -151,12 +175,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 +188,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.线.write().unwrap() = 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 +244,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.线.write().unwrap() = None;
} else if !crate::types::::(
self.(),
self.(),
@@ -230,11 +257,11 @@ impl 中枢 {
)
.()
{
self.线(Rc::clone(线));
self.线 = None;
self.线(Arc::clone(线));
*self.线.write().unwrap() = None;
}
} else {
self.线 = None;
*self.线.write().unwrap() = None;
}
}
true
@@ -243,16 +270,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 +298,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,13 +319,15 @@ impl 中枢 {
/// 当前状态 — 详见教你炒股票49:利润率最大的操作模式
/// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下
pub fn (&self) -> &str {
let = self..last().unwrap();
let _中 = if . == "" {
&..
} else {
&..last().unwrap()..
};
let = crate::types::::(self.(), self.(), _中., _中.);
let _ref = self..read().unwrap();
let = Arc::clone(_ref.last().unwrap());
let = ._武();
let = crate::types::::(
self.(),
self.(),
...get(),
...get(),
);
if == crate::types:::: {
"中枢之上"
} else if == crate::types:::: {
@@ -323,11 +357,11 @@ impl 中枢 {
/// 创建中枢
pub fn (
: Rc<线>, : Rc<线>, : Rc<线>, : i64, : &str
: Arc<线>, : Arc<线>, : Arc<线>, : i64, : &str
) -> Self {
Self::new(
0,
format!("{}中枢<{}>", , .),
format!("{}中枢<{}>", , ..read().unwrap()),
,
vec![, , ],
)
@@ -335,17 +369,17 @@ impl 中枢 {
/// 从序列中获取中枢
pub fn (
线: &[Rc<线>],
线: &[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
@@ -353,19 +387,22 @@ impl 中枢 {
/// 向中枢序列尾部添加
pub fn (
: &mut Vec<Rc<>>, mut : Rc<>
: &mut Vec<Arc<>>, mut : Arc<>
) {
if let Some() = .last() {
let = Rc::make_mut(&mut );
. = . + 1;
.
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
// Python: assert seq[-1].获取序列()[-1].序号 <= new.获取序列()[-1].序号
let _seq = .();
let new_seq = .();
let new_seq = .();
if let (Some(_last), Some(new_last)) = (_seq.last(), new_seq.last()) {
if _last. > new_last. {
if _last..load(Ordering::Relaxed) > new_last..load(Ordering::Relaxed)
{
panic!(
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
_last., new_last.
_last..load(Ordering::Relaxed),
new_last..load(Ordering::Relaxed)
);
}
}
@@ -375,10 +412,10 @@ impl 中枢 {
/// 从中枢序列尾部弹出
pub fn (
: &mut Vec<Rc<>>,
: &Rc<>,
) -> Option<Rc<>> {
if .last().map(|x| Rc::as_ptr(x)) == Some(Rc::as_ptr()) {
: &mut Vec<Arc<>>,
: &Arc<>,
) -> Option<Arc<>> {
if .last().map(|x| Arc::as_ptr(x)) == Some(Arc::as_ptr()) {
.pop()
} else {
None
@@ -389,8 +426,8 @@ impl 中枢 {
///
/// 每收到新的虚线序列数据后调用,更新中枢序列
pub fn (
线: &[Rc<线>],
: &mut Vec<Rc<>>,
线: &[Arc<线>],
: &mut Vec<Arc<>>,
: bool,
: &str,
: i64,
@@ -410,9 +447,9 @@ impl 中枢 {
// Python: 序号 = 虚线序列.index(左)
let = 线
.iter()
.position(|x| Rc::as_ptr(x) == Rc::as_ptr())
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
.unwrap_or(i - 1);
if && (. == 0 || == 0) {
if && (..load(Ordering::Relaxed) == 0 || == 0) {
continue;
}
if >= 2 {
@@ -426,11 +463,11 @@ 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::(, );
@@ -445,13 +482,10 @@ 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]);
let = Arc::clone(&[_idx]);
Self::(, &);
Self::(线, , , , );
return;
@@ -460,10 +494,10 @@ impl 中枢 {
// 找到当前中枢最后一个元素在虚线序列中的位置
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,
@@ -472,10 +506,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]);
let 线 = Arc::clone(&线[i]);
// 检查是否超出中枢范围(缺口)
if crate::types::::(, , 线.(), 线.()).()
@@ -485,16 +519,20 @@ 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].线(线.clone());
}
} else {
if .is_empty() {
// 仍在范围内:延伸中枢
Rc::make_mut(&mut [_idx]).线(线);
[_idx].线(线);
} else {
.push(线);
}
@@ -504,6 +542,8 @@ impl 中枢 {
while .len() >= 3 {
let = [_idx]
.
.read()
.unwrap()
.last()
.unwrap()
.()
@@ -526,10 +566,317 @@ impl 中枢 {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
use crate::structure::fractal_obj::;
use crate::types::;
fn _创建K线(: i64, : f64, : f64, : f64, : f64) -> K线 {
let mut k = K线::default();
k. = ;
k. = ;
k. = ;
k. = ;
k. = ;
k
}
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 第三买卖线读写
.线(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)],
);
.线(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)],
);
.线(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])
);
}
}
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<_>>()
@@ -537,10 +884,10 @@ 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.(),
File diff suppressed because it is too large Load Diff
+27 -26
View File
@@ -27,18 +27,19 @@ 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::atomic::Ordering;
use std::sync::Arc;
/// 基础买卖点 — 买卖点的基础数据结构
#[derive(Debug, Clone)]
pub struct {
pub : String,
pub : ,
pub : Rc<>,
pub K线: Rc<K线>,
pub K线: Rc<K线>,
pub K线: Option<Rc<K线>>,
pub K线: Option<Rc<K线>>,
pub : Arc<>,
pub K线: Arc<K线>,
pub K线: Arc<K线>,
pub K线: Option<Arc<K线>>,
pub K线: Option<Arc<K线>>,
pub : f64,
pub : Option<>,
}
@@ -46,12 +47,12 @@ pub struct 基础买卖点 {
impl {
pub fn new(
: ,
K线: Rc<K线>,
: Rc<>,
K线: Arc<K线>,
: Arc<>,
: String,
: f64,
) -> Self {
let K线 = Rc::clone(&.);
let K线 = Arc::clone(&.);
Self {
,
,
@@ -67,13 +68,13 @@ impl 基础买卖点 {
/// 偏移 — 当前K线与买卖点K线的序号差
pub fn (&self) -> i64 {
self.K线. - self.K线.
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,
}
}
@@ -112,8 +113,8 @@ pub struct 买卖点;
impl {
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -122,8 +123,8 @@ impl 买卖点 {
}
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -132,8 +133,8 @@ impl 买卖点 {
}
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -142,8 +143,8 @@ impl 买卖点 {
}
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -152,8 +153,8 @@ impl 买卖点 {
}
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -162,8 +163,8 @@ impl 买卖点 {
}
pub fn (
: Rc<>,
K线: Rc<K线>,
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
@@ -176,8 +177,8 @@ impl 买卖点 {
: &str,
: &str,
: &str,
: Rc<>,
K: Rc<K线>,
: Arc<>,
K: Arc<K线>,
) -> {
let = if matches!(., :: | ::) {
""
@@ -188,7 +189,7 @@ impl 买卖点 {
let = .;
// 当前K线 — 从缠K获取其标的K线
let K线 = Rc::clone(&K.K线);
let K线 = Arc::clone(&*K.K线.read().unwrap());
let = match (, ) {
("", "") => ::,
+11 -10
View File
@@ -26,9 +26,9 @@ 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;
/// 立体分析器 — 多周期协调器
///
@@ -38,7 +38,7 @@ pub struct 立体分析器 {
pub : Vec<i64>,
: i64,
K线合成器: K线合成器,
: HashMap<i64, Rc<RefCell<>>>,
: HashMap<i64, Arc<RwLock<>>>,
}
impl {
@@ -96,13 +96,13 @@ 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线);
}
}
}
/// 获取指定周期的观察者
pub fn (&self, : i64) -> Option<Rc<RefCell<>>> {
pub fn (&self, : i64) -> Option<Arc<RwLock<>>> {
self..get(&).cloned()
}
@@ -116,23 +116,23 @@ impl 立体分析器 {
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_{}:{}_{}_{}", , , , );
@@ -146,7 +146,8 @@ impl 立体分析器 {
for in &self. {
if let Some() = self..get() {
.borrow()
.read()
.unwrap()
._保存数据(Some(&.to_string_lossy()));
}
}
+594 -48
View File
@@ -32,8 +32,8 @@ 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};
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
pub struct {
@@ -42,40 +42,41 @@ 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>> {
pub fn new(: String, : i64, : ) -> Arc<RwLock<Self>> {
let = if . != "1970-01-01 00:00:00" && !..is_empty()
{
datetime::(&.)
@@ -88,6 +89,7 @@ impl 观察者 {
,
,
K线序列: Vec::new(),
K序列: Vec::new(),
K线序列: Vec::new(),
: Vec::new(),
: Vec::new(),
@@ -105,7 +107,7 @@ impl 观察者 {
,
};
instance.. = ;
Rc::new(RefCell::new(instance))
Arc::new(RwLock::new(instance))
}
/// 标识
@@ -114,18 +116,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();
@@ -232,13 +235,7 @@ impl 观察者 {
&mut self.线_线段序列,
&self.,
0,
&[
::,
::,
::,
::,
::,
],
&[::, ::],
);
}
if self..线 {
@@ -282,12 +279,12 @@ 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线序列,
@@ -339,13 +336,7 @@ impl 观察者 {
&mut self.线_线段序列,
&self.,
0,
&[
::,
::,
::,
::,
::,
],
&[::, ::],
);
}
if self..线 {
@@ -424,14 +415,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 +433,12 @@ impl 观察者 {
.map(|(i, fx)| {
format!(
"分型, {}, {}, {:?}, {}, {}, {}",
i, fx., fx., fx., fx.., fx..,
i,
fx.,
fx.,
fx.,
fx...load(Ordering::Relaxed),
fx...get(),
)
})
.collect();
@@ -481,11 +477,36 @@ impl 观察者 {
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)
}
/// 加载本地数据 — 从 .nb 文件加载数据到当前观察者(先重置再投喂)
pub fn (&mut self, : &str) -> Result<(), String> {
self.();
let bars = self.()?;
for k线 in bars {
self.K线(k线);
}
Ok(())
}
/// 读取数据文件 — 从 .nb 文件加载数据
pub fn (
: &str,
: Option<>,
) -> Result<Rc<RefCell<Self>>, String> {
) -> Result<Arc<RwLock<Self>>, String> {
let = .unwrap_or_default();
// Parse filename: btcusd-300-1631772074-1632222374.nb
@@ -510,10 +531,535 @@ impl 观察者 {
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线);
.write().unwrap().K线(k线);
}
}
Ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::;
const TEST_DATA_PATH: &str = "/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb";
#[test]
fn test_普k序列指针一致性() {
let config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).unwrap();
let obs_ref = obs.read().unwrap();
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.
.read()
.unwrap()
.
.
.load(Ordering::Relaxed)
);
println!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
} else {
let first_ptr = Arc::as_ptr(&pu_seq[0]);
let found = obs_ref
.K线序列
.iter()
.any(|k| Arc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
let wen_ptr = Arc::as_ptr(&*bi...K线.read().unwrap());
let wen_found = obs_ref
.K线序列
.iter()
.any(|k| Arc::as_ptr(k) == wen_ptr);
println!(" 文.中.标的K线 在序列中: {}", wen_found);
} else {
println!("{}: OK, 获取普K序列[0] 在序列中找到", i);
}
}
}
}
#[test]
fn test_pyo3_flow_pointer_consistency() {
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") {
let _k线_py_inner = Arc::new(k线.clone());
obs_ref.write().unwrap().K线(k线);
}
}
let obs = obs_ref.read().unwrap();
println!("普通K线序列.len: {}", obs.K线序列.len());
println!("笔序列.len: {}", obs..len());
for (i, bi) in obs..iter().enumerate() {
let pu_seq = bi.K序列(&obs.K线序列);
if pu_seq.is_empty() {
println!("{}: 获取普K序列 返回空!", i);
} else {
let first_ptr = Arc::as_ptr(&pu_seq[0]);
let found = obs.K线序列.iter().any(|k| Arc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 指针不在序列中!", i);
}
if i < 5 {
println!("{}: OK, len={}", i, pu_seq.len());
}
}
}
}
// ============================================================
// 分型到笔的 Rc 指针一致性
// ============================================================
#[test]
fn test_分型到笔的文武Rc指针一致性() {
let config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).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 {
println!("{}: 文(时间戳={}) 不在分型序列中!", i, bi..);
}
let _ptr = Arc::as_ptr(&*bi..read().unwrap());
let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr);
if !_found {
println!(
"笔 {}: 武(时间戳={}) 不在分型序列中!",
i,
bi..read().unwrap().
);
}
}
}
// ============================================================
// 笔到线段的 Rc 指针一致性
// ============================================================
#[test]
fn test_笔到线段的基础序列Rc指针一致性() {
let config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).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 {
println!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j);
}
}
}
}
// ============================================================
// 中枢基础序列与源的 Rc 指针一致性
// ============================================================
#[test]
fn test_中枢基础序列与笔序列Rc指针一致() {
let config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).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 {
println!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", 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 {
println!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", 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, "重复计算笔中枢数不一致");
println!(
"两次计算结果一致: 笔={}, 线段={}, 中枢={}, 笔中枢={}",
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!(, , "重置后重新投喂线段数不一致");
println!("重置后重投一致: 笔={}, 线段={}", , );
}
// ============================================================
// RefCell 借用安全性 — 连续大量操作不应 panic
// ============================================================
#[test]
fn test_RefCell借用安全性_连续读取不panic() {
let config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).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 config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).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 config = ::default();
let obs = ::(TEST_DATA_PATH, Some(config)).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 {
println!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.);
}
if let Some(ref ) = f. {
let _ptr = Arc::as_ptr();
let _found = obs_ref.K线序列.iter().any(|k| Arc::as_ptr(k) == _ptr);
if !_found {
println!("分型 {} 的 左 不在缠论K线序列中!", i);
}
}
if let Some(ref ) = f. {
let _ptr = Arc::as_ptr();
let _found = obs_ref.K线序列.iter().any(|k| Arc::as_ptr(k) == _ptr);
if !_found {
println!("分型 {} 的 右 不在缠论K线序列中!", i);
}
}
}
}
// ========== 跨线程安全测试 ==========
// 编译期断言:核心类型必须实现 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);
}
}
+90 -1
View File
@@ -22,7 +22,7 @@
* SOFTWARE.
*/
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
fn is_infinite_f64(v: &f64) -> bool {
v.is_infinite()
@@ -70,6 +70,7 @@ pub struct 缠论配置 {
// ---- 指标 ----
pub : bool,
#[serde(deserialize_with = "deserialize_指标计算方式")]
pub : String,
// ---- MACD ----
@@ -115,6 +116,7 @@ pub struct 缠论配置 {
pub : bool,
pub MACD柱强相关: bool,
pub : f64,
#[serde(deserialize_with = "deserialize_买卖点_指标模式")]
pub _指标模式: String,
pub _指标匹配_MACD: bool,
pub _指标匹配_KDJ: bool,
@@ -136,12 +138,66 @@ pub struct 缠论配置 {
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 {
eprintln!("\x1b[33m[配置警告]\x1b[m 指标计算方式: \"{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 {
eprintln!("\x1b[33m[配置警告]\x1b[m 买卖点_指标模式: \"{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 {
eprintln!("\x1b[33m[配置警告]\x1b[m 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
Ok(DEFAULT.to_string())
}
}
impl Default for {
fn default() -> Self {
Self {
@@ -353,6 +409,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();
+1 -1
View File
@@ -80,7 +80,7 @@ impl 平滑异同移动平均线 {
let 线EMA = ;
let DIF = 线EMA - 线EMA;
let DEA_EMA = DIF;
let MACD柱 = DIF - DEA_EMA;
let MACD柱 = 2.0 * (DIF - DEA_EMA);
Self {
: ,
+7 -7
View File
@@ -28,7 +28,7 @@ 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;
/// 原始K线 (OHLCV + 指标)
#[derive(Debug, Clone, Serialize, Deserialize)]
@@ -202,12 +202,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(),
+194 -164
View File
@@ -30,24 +30,49 @@ use crate::kline::bar::K线;
use crate::structure::fractal_obj::;
use crate::types::;
use crate::types::;
use std::rc::Rc;
use crate::types::SyncF64;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 缠论K线 — 经包含处理过后的K线
#[derive(Debug, Clone)]
///
/// 部分字段使用 Cell/RefCell 实现内部可变性,确保包含处理原地修改时
/// Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。
#[derive(Debug)]
pub struct K线 {
pub : i64,
pub : i64,
pub : f64,
pub : f64,
pub : ,
pub : Option<>,
pub : AtomicI64,
pub : AtomicI64,
pub : SyncF64,
pub : SyncF64,
pub : RwLock<>,
pub : RwLock<Option<>>,
pub : i64,
pub : String,
pub : f64,
pub : SyncF64,
pub : i64,
pub : i64,
pub K线: Rc<K线>,
pub : Option<()>, // 占位,后续替换为实际类型
pub : AtomicI64,
pub K线: RwLock<Arc<K线>>,
pub : RwLock<Vec<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 +82,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 +100,34 @@ 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线),
: None,
: 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. {
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref macd) = self.K线.macd {
if let Some(ref macd) = self.K线.read().unwrap().macd {
macd.MACD柱 < 0.0
} else {
false
}
}
Some(::) | Some(::) => {
if let Some(ref macd) = self.K线.macd {
if let Some(ref macd) = self.K线.read().unwrap().macd {
macd.MACD柱 > 0.0
} else {
false
@@ -111,9 +139,9 @@ impl 缠论K线 {
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
pub fn RSI匹配(&self) -> bool {
match self. {
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref rsi) = self.K线.rsi {
if let Some(ref rsi) = self.K线.read().unwrap().rsi {
match (rsi.RSI, rsi.RSI_SMA) {
(Some(r), Some(sma)) => r < sma,
_ => false,
@@ -123,7 +151,7 @@ impl 缠论K线 {
}
}
Some(::) | Some(::) => {
if let Some(ref rsi) = self.K线.rsi {
if let Some(ref rsi) = self.K线.read().unwrap().rsi {
match (rsi.RSI, rsi.RSI_SMA) {
(Some(r), Some(sma)) => r > sma,
_ => false,
@@ -138,9 +166,9 @@ impl 缠论K线 {
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
pub fn KDJ匹配(&self) -> bool {
match self. {
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref kdj) = self.K线.kdj {
if let Some(ref kdj) = self.K线.read().unwrap().kdj {
match (kdj.K, kdj.D) {
(Some(k), Some(d)) => k < d,
_ => false,
@@ -150,7 +178,7 @@ impl 缠论K线 {
}
}
Some(::) | Some(::) => {
if let Some(ref kdj) = self.K线.kdj {
if let Some(ref kdj) = self.K线.read().unwrap().kdj {
match (kdj.K, kdj.D) {
(Some(k), Some(d)) => k > d,
_ => false,
@@ -164,25 +192,30 @@ 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线.
{
if (k线. - k.).abs() < f64::EPSILON {
return k.;
if (k线..get() - k..get()).abs() < f64::EPSILON
{
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.
{
if (k线. - k.).abs() < f64::EPSILON {
return k.;
if (k线..get() - k..get()).abs() < f64::EPSILON {
return k..load(Ordering::Relaxed);
}
}
}
}
k线.
k线..load(Ordering::Relaxed)
}
/// 创建缠K
@@ -193,7 +226,7 @@ impl 缠论K线 {
: ,
: Option<>,
: i64,
k: Rc<K线>,
k: Arc<K线>,
: Option<&K线>,
) -> Self {
if .is_nan() || .is_nan() {
@@ -204,25 +237,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,
: None,
: AtomicI64::new(),
K线: RwLock::new(k),
: RwLock::new(Vec::new()),
};
if let Some() = {
. = . + 1;
let = ::(., ., ., .);
.
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
let =
::(..get(), ..get(), ..get(), ..get());
if .() {
panic!(
"创建缠K 包含关系: {:?}\n 之前: {}\n 当前: {}",
@@ -238,11 +274,11 @@ impl 缠论K线 {
/// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None
pub fn (
K: Option<&K线>,
K: &mut K线,
K: &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 +287,47 @@ impl 缠论K线 {
} else {
Some(::)
};
let mut K = Self::K(
let K = Self::K(
K.,
K.,
K.,
K.(),
,
K.,
Rc::new(K.clone()),
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. {
if K. == K..load(Ordering::Relaxed) {
return (None, None);
}
// 序号连续性检查
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 +339,22 @@ impl 缠论K线 {
// 逆序包含时更新时间和标的K线
if != :: {
K. = K.;
K.K线 = Rc::new(K.clone());
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,10 +365,10 @@ 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序列管理 + 指标增量计算 ----
@@ -365,7 +413,7 @@ impl 缠论K线 {
._超卖阈值,
));
}
let K线_rc = Rc::new(K线);
let K线_rc = Arc::new(K线);
K序列.push(K线_rc);
} else {
let K = K序列.last().unwrap();
@@ -412,7 +460,7 @@ impl 缠论K线 {
}
}
K序列.pop();
K序列.push(Rc::new(K线));
K序列.push(Arc::new(K线));
} else {
if K. > K线. {
panic!("时序错误: 之前={}, 当前={}", K., K线.);
@@ -455,20 +503,20 @@ impl 缠论K线 {
));
}
}
K序列.push(Rc::new(K线));
K序列.push(Arc::new(K线));
}
}
// ---- 阶段2: 缠K合并 ----
let : String;
let K线_ref: &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(|rc| Arc::as_ref(rc));
let K = &*[0];
let (K, ) = Self::(K, K, K线_ref, );
if let Some(k) = K {
match .as_deref() {
@@ -477,9 +525,8 @@ impl 缠论K线 {
= "创建".into();
}
Some("替换") => {
K序列.pop();
K序列.push(k);
= "替换".into();
// Cell::set 已原地更新数据,无需 pop+push 打破 Rc 身份
= "兼并".into();
}
_ => {
= "兼并".into();
@@ -496,10 +543,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 +556,89 @@ 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])),
));
return (, Some());
}
/// 截取缠K序列从始到终
pub fn (
: &[Rc<K线>], : &K线, : &K线
) -> Option<Vec<Rc<K线>>> {
: &[Arc<K线>],
: &K线,
: &K线,
) -> Option<Vec<Arc<K线>>> {
let _idx =
.iter()
.position(|k| Rc::as_ptr(k) == ( as *const _))?;
.position(|k| Arc::as_ptr(k) == ( as *const _))?;
let _idx =
.iter()
.position(|k| Rc::as_ptr(k) == ( as *const _))?;
.position(|k| Arc::as_ptr(k) == ( as *const _))?;
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 +653,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]
+2 -2
View File
@@ -79,7 +79,7 @@ fn 测试_读取数据(文件路径: &str) {
let = ::default().();
match ::(, Some()) {
Ok() => {
let = .borrow();
let = .read().unwrap();
let = .elapsed();
println!(
"测试_读取数据 耗时 {:.2?} 普K数量 {}",
@@ -162,7 +162,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,
+544 -143
View File
@@ -29,77 +29,113 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::fractal_obj::;
use crate::structure::segment_feat::线;
use crate::types::{, , };
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 虚线 — 笔和线段的通用数据结构
///
/// 笔和线段共享此 struct,通过 `标识` 字段区分 ("笔"/"线段"/"扩展线段"等)
#[derive(Debug, Clone)]
/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致
#[derive(Debug)]
pub struct 线 {
pub : String,
pub : i64,
pub : i64,
pub : Rc<>,
pub : Rc<>,
pub : bool,
pub : Vec<Rc<线>>,
pub : Vec<Option<Rc<线>>>,
pub _中枢序列: Vec<Rc<>>,
pub _中枢序列: Vec<Rc<>>,
pub _中枢序列: Vec<Rc<>>,
pub K线: Option<Rc<K线>>,
pub : String,
pub _特征序列_显示: bool,
pub : Option<>,
pub : Option<Rc<线>>,
pub : bool,
pub : RwLock<String>,
pub : AtomicI64,
pub : AtomicI64,
pub : Arc<>,
pub : RwLock<Arc<>>,
pub : AtomicBool,
pub : RwLock<Vec<Arc<线>>>,
pub : RwLock<Vec<Option<Arc<线>>>>,
pub _中枢序列: RwLock<Vec<Arc<>>>,
pub _中枢序列: RwLock<Vec<Arc<>>>,
pub _中枢序列: RwLock<Vec<Arc<>>>,
pub K线: RwLock<Option<Arc<K线>>>,
pub : RwLock<String>,
pub _特征序列_显示: AtomicBool,
pub : RwLock<Option<>>,
pub : RwLock<Option<Arc<线>>>,
pub : AtomicBool,
}
/// MACD行为统计 — 统计MACD行为 方法的返回类型
#[derive(Debug, Clone)]
pub struct MACD行为统计 {
pub DIF上穿0: i64,
pub DIF下穿0: i64,
pub DEA上穿0: i64,
pub DEA下穿0: i64,
pub : i64,
pub : i64,
pub : Vec<(usize, usize, usize)>,
}
impl Clone for 线 {
fn clone(&self) -> Self {
Self {
: RwLock::new(self..read().unwrap().clone()),
: AtomicI64::new(self..load(Ordering::Relaxed)),
: AtomicI64::new(self..load(Ordering::Relaxed)),
: Arc::clone(&self.),
: RwLock::new(Arc::clone(&self..read().unwrap())),
: AtomicBool::new(self..load(Ordering::Relaxed)),
: RwLock::new(self..read().unwrap().clone()),
: RwLock::new(self..read().unwrap().clone()),
_中枢序列: RwLock::new(self._中枢序列.read().unwrap().clone()),
_中枢序列: RwLock::new(self._中枢序列.read().unwrap().clone()),
_中枢序列: RwLock::new(self._中枢序列.read().unwrap().clone()),
K线: RwLock::new(self.K线.read().unwrap().clone()),
: RwLock::new(self..read().unwrap().clone()),
_特征序列_显示: AtomicBool::new(self._特征序列_显示.load(Ordering::Relaxed)),
: RwLock::new(*self..read().unwrap()),
: RwLock::new(self..read().unwrap().clone()),
: AtomicBool::new(self..load(Ordering::Relaxed)),
}
}
}
impl 线 {
pub fn new(
: i64,
: String,
: Rc<>,
: Rc<>,
: Arc<>,
: Arc<>,
: i64,
: bool,
) -> Self {
Self {
,
,
,
: AtomicI64::new(),
: RwLock::new(),
: AtomicI64::new(),
,
,
,
: Vec::new(),
: Vec::new(),
_中枢序列: Vec::new(),
_中枢序列: Vec::new(),
_中枢序列: Vec::new(),
K线: None,
: "文武".into(),
_特征序列_显示: false,
: None,
: None,
: false,
: RwLock::new(),
: AtomicBool::new(),
: RwLock::new(Vec::new()),
: RwLock::new(Vec::new()),
_中枢序列: RwLock::new(Vec::new()),
_中枢序列: RwLock::new(Vec::new()),
_中枢序列: RwLock::new(Vec::new()),
K线: RwLock::new(None),
: RwLock::new("文武".into()),
_特征序列_显示: AtomicBool::new(false),
: RwLock::new(None),
: RwLock::new(None),
: AtomicBool::new(false),
}
}
/// 笔序列(基础序列的别名)
pub fn (&self) -> &Vec<Rc<线>> {
&self.
}
pub fn (&self) -> String {
format!(
"{}:{}:{}:{}",
self..., self..., self., self.
self...,
self...,
self..read().unwrap(),
self..load(Ordering::Relaxed)
)
}
/// 方向 — 文到武的方向
pub fn (&self) -> {
match (self.., self..) {
match (self.., self..read().unwrap().) {
(::, ::) => ::,
(::, ::) => ::,
(::, ::) => ::,
@@ -111,67 +147,107 @@ impl 虚线 {
/// 虚线高
pub fn (&self) -> f64 {
if self.() == :: {
self...
self....get()
} else {
self...
self..read().unwrap()...get()
}
}
/// 虚线低
pub fn (&self) -> f64 {
if self.() == :: {
self...
self..read().unwrap()...get()
} else {
self...
self....get()
}
}
/// 判断两个虚线是否首尾相连
pub fn (&self, : &线) -> bool {
if self. != . {
if *self..read().unwrap() != *..read().unwrap() {
return false;
}
Rc::as_ptr(&.) == Rc::as_ptr(&self.)
Arc::as_ptr(&*..read().unwrap()) == Arc::as_ptr(&self.)
}
/// 判断两个虚线是否首尾相连
pub fn (&self, : &线) -> bool {
if self. != . {
if *self..read().unwrap() != *..read().unwrap() {
return false;
}
Rc::as_ptr(&self.) == Rc::as_ptr(&.)
Arc::as_ptr(&*self..read().unwrap()) == Arc::as_ptr(&.)
}
/// 获取该虚线范围内的普K序列
pub fn K序列(&self, K序列: &[Rc<K线>]) -> Vec<Rc<K线>> {
let = self... as usize;
let = self... as usize;
if < K序列.len() && < K序列.len() && <= {
K序列[..=].to_vec()
} else {
Vec::new()
pub fn K序列(&self, K序列: &[Arc<K线>]) -> Vec<Arc<K线>> {
// 使用指针查找(与 Python list.index 身份匹配行为一致),
// 而非序号切片——因为序号可能与实际位置不一致。
let = K序列
.iter()
.position(|k| Arc::as_ptr(k) == Arc::as_ptr(&*self...K线.read().unwrap()));
let = K序列.iter().position(|k| {
Arc::as_ptr(k) == Arc::as_ptr(&*self..read().unwrap()..K线.read().unwrap())
});
match (, ) {
(Some(s), Some(e)) if s <= e => K序列[s..=e].to_vec(),
_ => {
// 指针查找失败时回退到序号方式
println!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>");
let = self... as usize;
let = self
.
.read()
.unwrap()
.
.
.load(Ordering::Relaxed) as usize;
if < K序列.len() && < K序列.len() && <= {
K序列[..=].to_vec()
} else {
Vec::new()
}
}
}
}
/// 获取该虚线范围内的缠K序列
pub fn K序列(&self, K序列: &[Rc<K线>]) -> Vec<Rc<K线>> {
K线::(K序列, &self.., &self..).unwrap_or_default()
pub fn K序列(&self, K序列: &[Arc<K线>]) -> Vec<Arc<K线>> {
K线::(K序列, &self.., &self..read().unwrap().).unwrap_or_default()
}
/// 获取_武 — 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武)
pub fn _武(&self) -> Arc<> {
if *self..read().unwrap() == "" {
return self..read().unwrap().clone();
}
let mut current_rc = Arc::clone(self..read().unwrap().last().unwrap());
loop {
if *current_rc..read().unwrap() == "" {
return current_rc..read().unwrap().clone();
}
let next = Arc::clone(current_rc..read().unwrap().last().unwrap());
current_rc = next;
}
}
/// 获取数据文本(用于保存/调试)
pub fn (&self) -> String {
use crate::utils::format_f64_g;
if self. == "" {
if *self..read().unwrap() == "" {
return format!(
"{}, {}, {}, 文:({},{}), 武:({},{}), {}",
self.,
self.,
self.,
self..read().unwrap(),
self..load(Ordering::Relaxed),
self..load(Ordering::Relaxed),
self..,
format_f64_g(self..),
self..,
format_f64_g(self..),
if self. { "True" } else { "False" },
self..read().unwrap().,
format_f64_g(self..read().unwrap().),
if self..load(Ordering::Relaxed) {
"True"
} else {
"False"
},
);
}
@@ -186,11 +262,11 @@ impl 虚线 {
}
};
let _str = match &self. {
let _str = match &*self..read().unwrap() {
Some(g) => format!("{}", g),
None => "None".to_string(),
};
let _str = match &self. {
let _str = match &*self..read().unwrap() {
Some(d) => format!("{}", d),
None => "None".to_string(),
};
@@ -199,6 +275,8 @@ impl 虚线 {
let _str = format!(
"[{}]",
self._中枢序列
.read()
.unwrap()
.iter()
.map(|h| format!("{}", h))
.collect::<Vec<_>>()
@@ -207,6 +285,8 @@ impl 虚线 {
let _str = format!(
"[{}]",
self._中枢序列
.read()
.unwrap()
.iter()
.map(|h| format!("{}", h))
.collect::<Vec<_>>()
@@ -215,6 +295,8 @@ impl 虚线 {
let _str = format!(
"[{}]",
self._中枢序列
.read()
.unwrap()
.iter()
.map(|h| format!("{}", h))
.collect::<Vec<_>>()
@@ -245,15 +327,15 @@ impl 虚线 {
format!(
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}, ({}, {}, {}), (前: {}, 后: {}, 三: {}, 伤: {}), 实: {}, 虚: {}, 合: {}, {}, {}, {}, {}",
self.,
self.,
self.,
self..read().unwrap(),
self..load(Ordering::Relaxed),
self..load(Ordering::Relaxed),
self..,
format_f64_g(self..),
self..,
format_f64_g(self..),
if self. { "True" } else { "False" },
self..len(),
self..read().unwrap().,
format_f64_g(self..read().unwrap().),
if self..load(Ordering::Relaxed) { "True" } else { "False" },
self..read().unwrap().len(),
_bool(_a),
_bool(_b),
_bool(_c),
@@ -264,33 +346,39 @@ impl 虚线 {
_str,
_str,
_str,
self.,
self..read().unwrap(),
_str,
_str,
if self. { "True" } else { "False" },
if self..load(Ordering::Relaxed) { "True" } else { "False" },
)
}
// ---- 关联函数(静态工厂方法) ----
/// 创建笔
pub fn (: Rc<>, : Rc<>, : bool) -> Self {
pub fn (: Arc<>, : Arc<>, : bool) -> Self {
Self::new(0, "".into(), , , 1, )
}
/// 创建线段
pub fn 线(线: &[Rc<线>]) -> Self {
let = Rc::clone(&线[0].);
let = Rc::clone(&线[线.len() - 1].);
let = if 线[0]. == "" {
pub fn 线(线: &[Arc<线>]) -> Self {
let = Arc::clone(&线[0].);
let = Arc::clone(&*线[线.len() - 1]..read().unwrap());
assert!(
. != .,
"创建线段: 文.结构 == 武.结构 文={}, 武={}",
,
);
let : String = if *线[0]..read().unwrap() == "" {
"线段".into()
} else {
format!("线段<{}>", 线[0].)
format!("线段<{}>", 线[0]..read().unwrap())
};
let = 线[0]. + 1;
let mut = Self::new(0, , , , , true);
. = 线.to_vec();
. = "文武".into();
let = 线[0]..load(Ordering::Relaxed) + 1;
let = Self::new(0, , , , , true);
*..write().unwrap() = 线.to_vec();
*..write().unwrap() = "文武".into();
}
@@ -344,8 +432,12 @@ impl 虚线 {
// ---- MACD柱子均值计算 ----
/// 计算MACD柱子均值 — 虚线范围内所有MACD柱的绝对值均值
pub fn MACD柱子均值(K序列: &[Rc<K线>], 线: &线) -> f64 {
let K线序列 = K线::rc(K序列, &线...K线, &线...K线);
pub fn MACD柱子均值(K序列: &[Arc<K线>], 线: &线) -> f64 {
let K线序列 = K线::rc(
K序列,
&*线...K线.read().unwrap(),
&*线..read().unwrap()..K线.read().unwrap(),
);
if K线序列.is_empty() {
return 0.0;
}
@@ -358,8 +450,12 @@ impl 虚线 {
}
/// 计算MACD柱子均值_阴 — 负柱的绝对值均值
pub fn MACD柱子均值_阴(K序列: &[Rc<K线>], 线: &线) -> Option<f64> {
let K线序列 = K线::rc(K序列, &线...K线, &线...K线);
pub fn MACD柱子均值_阴(K序列: &[Arc<K线>], 线: &线) -> Option<f64> {
let K线序列 = K线::rc(
K序列,
&*线...K线.read().unwrap(),
&*线..read().unwrap()..K线.read().unwrap(),
);
let : Vec<f64> = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
@@ -374,8 +470,12 @@ impl 虚线 {
}
/// 计算MACD柱子均值_阳 — 正柱的绝对值均值
pub fn MACD柱子均值_阳(K序列: &[Rc<K线>], 线: &线) -> Option<f64> {
let K线序列 = K线::rc(K序列, &线...K线, &线...K线);
pub fn MACD柱子均值_阳(K序列: &[Arc<K线>], 线: &线) -> Option<f64> {
let K线序列 = K线::rc(
K序列,
&*线...K线.read().unwrap(),
&*线..read().unwrap()..K线.read().unwrap(),
);
let : Vec<f64> = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
@@ -392,8 +492,10 @@ impl 虚线 {
// ---- 武之MACD比较 ----
/// 武之全量MACD均值 — 武端MACD柱是否小于均值(背驰)
pub fn MACD均值(K序列: &[Rc<K线>], 线: &线) -> bool {
let _MACD = match &线...K线.macd {
pub fn MACD均值(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
Some(m) => m.MACD柱.abs(),
None => return false,
};
@@ -401,7 +503,7 @@ impl 虚线 {
}
/// 武之MACD均值 — 按方向选择阴/阳均值比对
pub fn MACD均值(K序列: &[Rc<K线>], 线: &线) -> bool {
pub fn MACD均值(K序列: &[Arc<K线>], 线: &线) -> bool {
if 线.() == :: {
Self::MACD均值_阳(K序列, 线)
} else {
@@ -410,8 +512,10 @@ impl 虚线 {
}
/// 武之MACD均值_阴 — 武端负柱是否小于阴均值
pub fn MACD均值_阴(K序列: &[Rc<K线>], 线: &线) -> bool {
let _MACD = match &线...K线.macd {
pub fn MACD均值_阴(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
Some(m) => m.MACD柱.abs(),
None => return false,
};
@@ -422,8 +526,10 @@ impl 虚线 {
}
/// 武之MACD均值_阳 — 武端正柱是否小于阳均值
pub fn MACD均值_阳(K序列: &[Rc<K线>], 线: &线) -> bool {
let _MACD = match &线...K线.macd {
pub fn MACD均值_阳(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
Some(m) => m.MACD柱.abs(),
None => return false,
};
@@ -434,12 +540,18 @@ impl 虚线 {
}
/// 武之MACD极值 — 武端MACD柱是否为区间极值
pub fn MACD极值(K序列: &[Rc<K线>], 线: &线) -> bool {
let _MACD = match &线...K线.macd {
pub fn MACD极值(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
Some(m) => m.MACD柱,
None => return false,
};
let K线序列 = K线::rc(K序列, &线...K线, &线...K线);
let K线序列 = K线::rc(
K序列,
&*线...K线.read().unwrap(),
&*线..read().unwrap()..K线.read().unwrap(),
);
let : Vec<f64> = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
@@ -461,7 +573,7 @@ impl 虚线 {
/// 计算K线序列MACD趋向背驰 — 分析 MACD柱/DIF/DEA 三项背驰信号
pub fn K线序列MACD趋向背驰(
K序列: &[Rc<K线>], :
K序列: &[Arc<K线>], :
) -> [bool; 3] {
if K序列.is_empty() {
return [false, false, false];
@@ -469,7 +581,7 @@ impl 虚线 {
let = &K序列[K序列.len() - 1];
if == :: {
let : Vec<&Rc<K线>> = K序列
let : Vec<&Arc<K线>> = K序列
.iter()
.filter(|k| k.macd.as_ref().map_or(false, |m| m.MACD柱 > 0.0))
.collect();
@@ -491,7 +603,7 @@ impl 虚线 {
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
let mut = vec![Rc::clone(*), Rc::clone()];
let mut = vec![Arc::clone(*), Arc::clone()];
.sort_by_key(|k| k.);
if let (Some(m0), Some(m1)) = ([0].macd.as_ref(), [1].macd.as_ref()) {
if m0.MACD柱 > m1.MACD柱 && [0]. < [1]. {
@@ -535,7 +647,7 @@ impl 虚线 {
} else {
let : Vec<&Rc<K线>> = K序列
let : Vec<&Arc<K线>> = K序列
.iter()
.filter(|k| k.macd.as_ref().map_or(false, |m| m.MACD柱 < 0.0))
.collect();
@@ -558,7 +670,7 @@ impl 虚线 {
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
let mut = vec![Rc::clone(*), Rc::clone()];
let mut = vec![Arc::clone(*), Arc::clone()];
.sort_by_key(|k| k.);
if let (Some(m0), Some(m1)) = ([0].macd.as_ref(), [1].macd.as_ref()) {
if m0.MACD柱 < m1.MACD柱 && [0]. > [1]. {
@@ -607,7 +719,7 @@ impl 虚线 {
// ---- MACD柱子分段 ----
/// 计算MACD柱子分段 — 按正负号将MACD柱子分段
pub fn MACD柱子分段(k线序列: &[Rc<K线>]) -> Vec<Vec<f64>> {
pub fn MACD柱子分段(k线序列: &[Arc<K线>]) -> Vec<Vec<f64>> {
if k线序列.is_empty() {
return Vec::new();
}
@@ -690,12 +802,10 @@ impl 虚线 {
/// 统计MACD行为 — 分析DIF/DEA穿零轴和金叉死叉
pub fn MACD行为(
K序列: &[Rc<K线>],
K序列: &[Arc<K线>],
: usize,
: usize,
) -> std::collections::HashMap<String, String> {
use std::collections::HashMap;
) -> MACD行为统计 {
let mut dif_up = 0;
let mut dif_down = 0;
let mut dea_up = 0;
@@ -759,17 +869,17 @@ impl 虚线 {
}
}
let = Self::(&, , );
let = Self::(&, , );
let mut map = HashMap::new();
map.insert("DIF上穿0".into(), dif_up.to_string());
map.insert("DIF下穿0".into(), dif_down.to_string());
map.insert("DEA上穿0".into(), dea_up.to_string());
map.insert("DEA下穿0".into(), dea_down.to_string());
map.insert("金叉次数".into(), golden.to_string());
map.insert("死叉次数".into(), death.to_string());
map.insert("密集交叉区域".into(), format!("{:?}", ));
map
MACD行为统计 {
DIF上穿0: dif_up,
DIF下穿0: dif_down,
DEA上穿0: dea_up,
DEA下穿0: dea_down,
: golden,
: death,
,
}
}
// ---- 买卖意义 ----
@@ -783,52 +893,61 @@ impl 虚线 {
let K序列 = &.K线序列;
let = &.;
if 线. != "" && 线. != "线段" && !线..starts_with("线段<")
if *线..read().unwrap() != ""
&& *线..read().unwrap() != "线段"
&& !线..read().unwrap().starts_with("线段<")
{
return (false, "标识不在范围内".into());
}
// KDJ指标完整性检查
match &线...K线.kdj {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
match .kdj.as_ref() {
Some(kdj) if kdj.K.is_some() && kdj.D.is_some() && kdj.J.is_some() => {}
_ => return (false, "KDJ指标不完整".into()),
}
let = Self::K买卖点模式(&._指标模式, &线.., );
let =
Self::K买卖点模式(&._指标模式, &线..read().unwrap()., );
let = false;
let : Vec<Rc<>> = if 线. == "" {
let : Vec<Arc<K线>> = if *线..read().unwrap() == "" {
crate::algorithm::bi::::(线, )
} else {
crate::algorithm::segment::线::(线, )
};
if {
if 线. == "" {
if *线..read().unwrap() == "" {
if Self::MACD均值(K序列, 线) {
return (true, "武之MACD均值".into());
}
if Self::MACD极值(K序列, 线) && !.is_empty() {
return (true, "背驰过且极值".into());
} else if 线..MACD柱子分型匹配() {
} else if 线..read().unwrap().MACD柱子分型匹配() {
return (
true,
format!(
"背驰过:{},极值:{},柱子分型匹配",
.len(),
Self::MACD极值(K序列, 线)
if Self::MACD极值(K序列, 线) {
"True"
} else {
"False"
}
),
);
}
}
if 线. != ""
if *线..read().unwrap() != ""
&& crate::algorithm::segment::线::线(线, )
{
return (true, "线段内部背驰".into());
}
}
if ! && && 线...MACD柱子匹配() {
if ! && && 线..read().unwrap()..MACD柱子匹配() {
if Self::MACD极值(K序列, 线) && .len() > 2 {
return (true, "没结果, 极值, 柱子分型匹配, 背驰过大于2次".into());
}
@@ -838,18 +957,300 @@ impl 虚线 {
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
use crate::types::;
/// 辅助:创建一根最小化的原始K线
fn _创建K线(: i64, : f64, : f64, : f64, : f64) -> K线 {
let mut k = K线::default();
k. = ;
k. = ;
k. = ;
k. = ;
k. = ;
k
}
/// 辅助:创建一根缠论K线
fn _创建缠K(
: i64,
: f64,
: f64,
: ,
: Option<>,
: i64,
) -> Arc<K线> {
let K = Arc::new(_创建K线(, , , , ));
let K = K线::K(, , , , , , K, None);
Arc::new(K)
}
/// 辅助:创建顶分型
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()))
}
// ============================================================
// Cell 字段读写测试
// ============================================================
#[test]
fn test_Cell字段读写一致性() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let = _创建底分型(200, 30.0, 20.0, 10);
let = 线::(, , true);
assert_eq!(..load(Ordering::Relaxed), 0);
assert!(..load(Ordering::Relaxed));
assert!(!..load(Ordering::Relaxed));
assert!(..read().unwrap().is_none());
// 修改 Cell 字段
..store(42, Ordering::Relaxed);
..store(false, Ordering::Relaxed);
..store(true, Ordering::Relaxed);
*..write().unwrap() = Some(::new(200.0, 100.0));
assert_eq!(..load(Ordering::Relaxed), 42);
assert!(!..load(Ordering::Relaxed));
assert!(..load(Ordering::Relaxed));
let qk = ..read().unwrap().unwrap();
assert!((qk. - 200.0).abs() < 0.01);
assert!((qk. - 100.0).abs() < 0.01);
}
// ============================================================
// RefCell 字段读写测试
// ============================================================
#[test]
fn test_RefCell字段读写一致性() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let = _创建底分型(200, 30.0, 20.0, 10);
let = 线::(, , true);
// 标识
assert_eq!(*..read().unwrap(), "");
*..write().unwrap() = "测试标识".into();
assert_eq!(*..read().unwrap(), "测试标识");
// 模式
assert_eq!(*..read().unwrap(), "文武");
*..write().unwrap() = "全量".into();
assert_eq!(*..read().unwrap(), "全量");
// 基础序列
assert!(..read().unwrap().is_empty());
let = _创建底分型(300, 20.0, 10.0, 15);
let 2 = 线::(Arc::clone(&*..read().unwrap()), , true);
..write().unwrap().push(Arc::new(2));
assert_eq!(..read().unwrap().len(), 1);
// 武 - Replace with new 分型
let = _创建底分型(400, 15.0, 5.0, 20);
let _ptr = Arc::as_ptr(&);
*..write().unwrap() = Arc::clone(&);
assert_eq!(Arc::as_ptr(&*..read().unwrap()), _ptr);
}
// ============================================================
// Clone 后 Rc 指针身份一致
// ============================================================
#[test]
fn test_虚线Clone后文Rc指针一致() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let = _创建底分型(200, 30.0, 20.0, 10);
let = 线::(Arc::clone(&), Arc::clone(&), true);
let = .clone();
// 文 Rc 指针应一致
assert_eq!(Arc::as_ptr(&.), Arc::as_ptr(&));
assert_eq!(Arc::as_ptr(&.), Arc::as_ptr(&.));
// 武 Rc 指针应一致
assert_eq!(Arc::as_ptr(&*..read().unwrap()), Arc::as_ptr(&));
assert_eq!(
Arc::as_ptr(&*..read().unwrap()),
Arc::as_ptr(&*..read().unwrap())
);
}
#[test]
fn test_虚线Clone是深拷贝Cell值而非共享() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let = _创建底分型(200, 30.0, 20.0, 10);
let = 线::(, , true);
..store(10, Ordering::Relaxed);
let = .clone();
// Clone 后序号应独立(deep copy for Cell
..store(99, Ordering::Relaxed);
assert_eq!(..load(Ordering::Relaxed), 10);
assert_eq!(..load(Ordering::Relaxed), 99);
// Rc 指针仍应一致(文/武 共享)
assert_eq!(Arc::as_ptr(&.), Arc::as_ptr(&.));
}
// ============================================================
// 多 Rc 共享下 Cell/RefCell 修改可见性
// ============================================================
#[test]
fn test_多Rc共享下Cell修改对所有引用可见() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let = _创建底分型(200, 30.0, 20.0, 10);
let _rc1 = Arc::new(线::(, , true));
let _rc2 = Arc::clone(&_rc1);
// 通过 rc1 修改 Cell
_rc1..store(77, Ordering::Relaxed);
// rc2 应能看到
assert_eq!(_rc2..load(Ordering::Relaxed), 77);
// 通过 rc1 修改 RefCell
*_rc1..write().unwrap() = "配置".into();
assert_eq!(*_rc2..read().unwrap(), "配置");
// 通过 rc1 修改 武
let = _创建底分型(400, 15.0, 5.0, 20);
let _ptr = Arc::as_ptr(&);
*_rc1..write().unwrap() = Arc::clone(&);
assert_eq!(Arc::as_ptr(&*_rc2..read().unwrap()), _ptr);
}
// ============================================================
// 获取_武 递归正确性
// ============================================================
#[test]
fn test_获取武_笔级别直接返回武() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let = _创建底分型(200, 30.0, 20.0, 10);
let = 线::(Arc::clone(&), Arc::clone(&), true);
let wu = ._武();
assert_eq!(Arc::as_ptr(&*..read().unwrap()), Arc::as_ptr(&wu));
assert_eq!(Arc::as_ptr(&wu), Arc::as_ptr(&));
}
#[test]
fn test_获取武_线段级别递归到底层笔() {
let 1 = _创建顶分型(100, 50.0, 40.0, 5);
let 1 = _创建底分型(200, 30.0, 20.0, 10);
let 2 = _创建顶分型(300, 55.0, 45.0, 15);
let 2 = _创建底分型(400, 25.0, 15.0, 20);
let 1 = Arc::new(线::(Arc::clone(&1), Arc::clone(&1), true));
let 2 = Arc::new(线::(Arc::clone(&1), Arc::clone(&2), true));
let 3 = Arc::new(线::(Arc::clone(&2), Arc::clone(&2), true));
let = 线::线(&[Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)]);
// 线段的 获取_武 应返回底层最后一笔的武(底2)
let wu = ._武();
assert_eq!(Arc::as_ptr(&wu), Arc::as_ptr(&2));
// 笔1 的 获取_武 应返回底1
let wu1 = 1._武();
assert_eq!(Arc::as_ptr(&wu1), Arc::as_ptr(&1));
}
// ============================================================
// 虚线 字段原子性 - 修改不影响文(不可变字段)
// ============================================================
#[test]
fn test_修改武不影响文Rc指针() {
let = _创建顶分型(100, 50.0, 40.0, 5);
let 1 = _创建底分型(200, 30.0, 20.0, 10);
let 2 = _创建底分型(300, 25.0, 15.0, 15);
let = 线::(Arc::clone(&), Arc::clone(&1), true);
let _ptr_before = Arc::as_ptr(&.);
// 修改武
*..write().unwrap() = Arc::clone(&2);
// 文指针不变
assert_eq!(Arc::as_ptr(&.), _ptr_before);
// 但方向变了(因为武从底1变成底2)
let = ..read().unwrap().;
assert_eq!(, 300);
}
}
impl std::fmt::Display for 线 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self. == "" {
if *self..read().unwrap() == "" {
write!(
f,
"笔({}, {}, {}, {}, 周期: {}, 数量: {})",
self.,
self..load(Ordering::Relaxed),
self.(),
self.,
self.,
self..read().unwrap(),
self...,
self... - self... + 1
self..read().unwrap()...load(Ordering::Relaxed)
- self....load(Ordering::Relaxed)
+ 1
)
} else {
let = crate::algorithm::segment::线::(self);
@@ -858,20 +1259,20 @@ impl std::fmt::Display for 虚线 {
Some(g) => format!("{}", g),
None => "None".to_string(),
};
let K线_str = match &self.K线 {
let K线_str = match &*self.K线.read().unwrap() {
Some(k) => format!("{}", k),
None => "None".to_string(),
};
write!(
f,
"{}<{}, {}, {}, {}, {}, 数量: {}, 缺口: {}, {}>",
self.,
self.,
self..read().unwrap(),
self..load(Ordering::Relaxed),
,
self.(),
self.,
self.,
self..len(),
self..read().unwrap(),
self..read().unwrap().len(),
_str,
K线_str,
)
+5 -5
View File
@@ -24,20 +24,20 @@
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 {
, , ,
+42 -28
View File
@@ -25,14 +25,15 @@
use crate::kline::chan_kline::K线;
use crate::types::;
use crate::types::;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::Arc;
/// 分型 — 由三根缠K构成(可能缺左或右)
#[derive(Debug, Clone)]
pub struct {
pub : Option<Rc<K线>>,
pub : Rc<K线>,
pub : Option<Rc<K线>>,
pub : Option<Arc<K线>>,
pub : Arc<K线>,
pub : Option<Arc<K线>>,
pub : ,
pub : i64,
pub : f64,
@@ -40,11 +41,11 @@ pub struct 分型 {
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 = .;
let = ..read().unwrap().unwrap_or(::);
let = ..load(Ordering::Relaxed);
let = ..get();
Self {
,
,
@@ -60,9 +61,9 @@ impl 分型 {
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()),
))
}
@@ -97,17 +98,19 @@ impl 分型 {
if let (Some(ref ), Some(ref )) = (&self., &self.) {
if self. == :: {
if .K线. > .K线. {
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线. {
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 "";
@@ -121,15 +124,21 @@ impl 分型 {
pub fn MACD柱子分型匹配(&self) -> bool {
if let (Some(ref ), Some(ref )) = (&self., &self.) {
if self. == :: {
let _k = .K线.read().unwrap();
let _k = self..K线.read().unwrap();
let _k = .K线.read().unwrap();
if let (Some(ref macd), Some(ref macd), Some(ref macd)) =
(&.K线.macd, &self..K线.macd, &.K线.macd)
(&_k.macd, &_k.macd, &_k.macd)
{
return macd.MACD柱 > macd.MACD柱 && macd.MACD柱 < macd.MACD柱;
}
}
if self. == :: {
let _k = .K线.read().unwrap();
let _k = self..K线.read().unwrap();
let _k = .K线.read().unwrap();
if let (Some(ref macd), Some(ref macd), Some(ref macd)) =
(&.K线.macd, &self..K线.macd, &.K线.macd)
(&_k.macd, &_k.macd, &_k.macd)
{
return macd.MACD柱 < macd.MACD柱 && macd.MACD柱 > macd.MACD柱;
}
@@ -139,35 +148,36 @@ impl 分型 {
}
/// 判断两个分型是否匹配
pub fn (: &Rc<>, : &Rc<>, : &str) -> bool {
pub fn (: &Arc<>, : &Arc<>, : &str) -> bool {
match {
"" => Rc::as_ptr() == Rc::as_ptr(),
"" => Arc::as_ptr() == Arc::as_ptr(),
_ => false,
}
}
/// 从缠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 . != :: && . != ::
{
@@ -188,10 +198,10 @@ impl 分型 {
impl crate::types::fractal:: for {
fn (&self) -> f64 {
self..
self...get()
}
fn (&self) -> f64 {
self..
self...get()
}
}
@@ -200,7 +210,11 @@ impl std::fmt::Display for 分型 {
write!(
f,
"{}<{}, {}, None: {}, None: {}>",
self...unwrap_or(crate::types::::),
self.
.
.read()
.unwrap()
.unwrap_or(crate::types::::),
self.,
crate::utils::format_f64_g(self.),
if self..is_none() { "True" } else { "False" },
+335 -40
View File
@@ -26,7 +26,8 @@ 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;
/// 线段特征 — 特征序列元素(内部是虚线的集合)
#[derive(Debug, Clone)]
@@ -34,11 +35,11 @@ pub struct 线段特征 {
pub : i64,
pub : String,
pub 线: ,
pub : Vec<Rc<线>>,
pub : Vec<Arc<线>>,
}
impl 线 {
pub fn new(: String, : Vec<Rc<线>>, 线: ) -> Self {
pub fn new(: String, : Vec<Arc<线>>, 线: ) -> Self {
Self {
: 0,
,
@@ -53,7 +54,7 @@ impl 线段特征 {
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Rc<> {
pub fn (&self) -> Arc<> {
if self.线.() {
self.
.iter()
@@ -64,8 +65,8 @@ impl 线段特征 {
.unwrap_or(std::cmp::Ordering::Equal)
.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.
.iter()
@@ -76,38 +77,54 @@ impl 线段特征 {
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| b...cmp(&a..))
})
.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.
.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.
.iter()
.min_by(|a, b| {
a.
.read()
.unwrap()
.
.partial_cmp(&b..)
.partial_cmp(&b..read().unwrap().)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| b...cmp(&a..))
.then_with(|| {
b.
.read()
.unwrap()
.
.cmp(&a..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())
}
}
@@ -129,7 +146,7 @@ impl 线段特征 {
}
/// 向特征序列元素中添加虚线
pub fn (&mut self, 线: Rc<线>) -> Result<(), String> {
pub fn (&mut self, 线: Arc<线>) -> Result<(), String> {
if 线.() == self.线 {
return Err("添加方向与线段方向相同".into());
}
@@ -138,14 +155,14 @@ impl 线段特征 {
}
/// 从特征序列元素中删除虚线
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);
Ok(())
@@ -155,19 +172,19 @@ impl 线段特征 {
}
/// 新建特征序列元素
pub fn (线: Vec<Rc<线>>, 线: ) -> Self {
pub fn (线: Vec<Arc<线>>, 线: ) -> Self {
let = format!("特征<虚线>");
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 +196,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 +209,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 +235,7 @@ impl 线段特征 {
// 情况2:方向不同(执行特征序列的合并/添加)
if .is_empty() {
.push(Rc::new(Self::(vec![Rc::clone(线)], 线)));
.push(Arc::new(Self::(vec![Arc::clone(线)], 线)));
continue;
}
@@ -225,10 +250,10 @@ 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(线)], 线)));
}
}
@@ -236,15 +261,15 @@ impl 线段特征 {
}
/// 获取分型序列
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::,
@@ -285,3 +310,273 @@ impl std::fmt::Display for 线段特征 {
}
}
}
#[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线 {
let mut k = K线::default();
k. = ;
k. = ;
k. = ;
k. = ;
k. = ;
k
}
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
@@ -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;
+25
View File
@@ -0,0 +1,25 @@
use std::sync::atomic::{AtomicU64, Ordering};
/// f64 原子类型 — 基于 AtomicU64 + 位转换,API 与 `Cell<f64>` 一致。
#[derive(Debug, Default)]
pub struct SyncF64(AtomicU64);
impl SyncF64 {
pub fn new(v: f64) -> Self {
Self(AtomicU64::new(v.to_bits()))
}
pub fn get(&self) -> f64 {
f64::from_bits(self.0.load(Ordering::Relaxed))
}
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 exp < -4 || exp >= 6 {
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');
return 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');
return 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");
}
}