6 Commits

Author SHA1 Message Date
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
21 changed files with 1805 additions and 344 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线序列))
+2 -4
View File
@@ -1,6 +1,6 @@
[package]
name = "chanlun-py"
version = "26.5.11"
version = "26.5.46"
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" }
chanlun = "26.5.2" # { path = "../chanlun" }
pyo3 = { version = "0.28", features = ["extension-module"] }
serde_json = "1"
chrono = "0.4"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "chanlun"
version = "2605.11"
version = "2605.46"
description = "缠论技术分析库 — Rust 高性能实现"
readme = { file = "README.md", content-type = "text/markdown" }
license = { file = "LICENSE", content-type = "text/plain" }
+95 -46
View File
@@ -28,8 +28,8 @@ use std::rc::Rc;
use crate::business_py::Py;
use crate::config_py::Py;
use crate::kline_py::K线Py;
use crate::structure_py::{Py, 线Py};
use crate::kline_py::{K线Py, K线Py};
use crate::structure_py::{Py, 线Py, 线Py};
use crate::types_py::Py;
// ========== 背驰分析 ==========
@@ -61,7 +61,7 @@ impl 背驰分析Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K线序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::algorithm::divergence::::MACD背驰(
&.borrow().inner,
@@ -105,7 +105,7 @@ impl 背驰分析Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::algorithm::divergence::::(
&.borrow().inner,
@@ -124,7 +124,7 @@ impl 背驰分析Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::algorithm::divergence::::(
&.borrow().inner,
@@ -144,7 +144,7 @@ impl 背驰分析Py {
) -> PyResult<bool> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::divergence::::(
@@ -165,7 +165,7 @@ impl 背驰分析Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::algorithm::divergence::::(
&.borrow().inner,
@@ -186,7 +186,7 @@ impl 背驰分析Py {
) -> PyResult<bool> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::divergence::::(
@@ -251,7 +251,7 @@ impl 笔Py {
.map(|k| Rc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::K线Py { inner })
.map(|inner| crate::kline_py::K线Py::from_rc(inner))
}
#[classmethod]
@@ -266,7 +266,7 @@ impl 笔Py {
.map(|k| Rc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::K线Py { inner })
.map(|inner| crate::kline_py::K线Py::from_rc(inner))
}
#[classmethod]
@@ -281,7 +281,7 @@ impl 笔Py {
.map(|k| Rc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::K线Py { inner })
.map(|inner| crate::kline_py::K线Py::from_rc(inner))
}
#[classmethod]
@@ -296,7 +296,7 @@ impl 笔Py {
.map(|k| Rc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::K线Py { inner })
.map(|inner| crate::kline_py::K线Py::from_rc(inner))
}
#[classmethod]
@@ -361,16 +361,20 @@ impl 笔Py {
}
#[classmethod]
#[pyo3(signature = (当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次, 配置))]
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
: Option<&Bound<'_, Py>>,
: Vec<Py<Py>>,
: Vec<Py<线Py>>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
K序列: Vec<Py<K线Py>>,
: i64,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<i64> {
let _ = ; // Python API 兼容参数,核心从0开始计数
let _rc = .map(|f| Rc::clone(&f.borrow().inner));
let mut fr_seq: Vec<Rc<chanlun::structure::fractal_obj::>> =
.iter()
.map(|f| Rc::clone(&f.bind(py).borrow().inner))
@@ -385,17 +389,20 @@ impl 笔Py {
.collect();
let bar_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::bi::::(
Rc::clone(&.borrow().inner),
&mut fr_seq,
&mut bi_seq,
&ck_list,
&bar_list,
&config,
))
match _rc {
Some(fr) => Ok(chanlun::algorithm::bi::::(
fr,
&mut fr_seq,
&mut bi_seq,
&ck_list,
&bar_list,
&config,
)),
None => Ok(),
}
}
#[classmethod]
@@ -424,7 +431,7 @@ impl 笔Py {
.collect();
let bar_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::bi::::(
@@ -468,12 +475,12 @@ impl 笔Py {
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
) -> Vec<Py> {
) -> Vec<K线Py> {
let obs = .borrow();
let obs_ref = obs.obs();
chanlun::algorithm::bi::::(&.borrow().inner, &*obs_ref)
.into_iter()
.map(|f| Py { inner: f })
.map(|ck| K线Py::from_rc(ck))
.collect()
}
}
@@ -620,10 +627,26 @@ impl 线段Py {
: &Bound<'_, PyAny>,
: u32,
) -> PyResult<()> {
// 特征序列 contains Option<Rc<线段特征>>, skip for now
let seq: Vec<Option<Rc<chanlun::structure::segment_feat::线>>> = if .is_none() {
vec![]
} else if let Ok(list) = .downcast::<pyo3::types::PyList>() {
let mut result = Vec::with_capacity(list.len());
for item in list.iter() {
if item.is_none() {
result.push(None);
} else {
let feat: PyRef<'_, 线Py> = item.extract()?;
result.push(Some(Rc::clone(&feat.inner)));
}
}
result
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"序列 必须是 list 或 None",
));
};
let mut ref_mut = .borrow_mut();
// Just pass empty vec for now — 线段特征Py not fully integrated
chanlun::algorithm::segment::线::(&mut ref_mut.inner, vec![], );
chanlun::algorithm::segment::线::(&mut ref_mut.inner, seq, );
Ok(())
}
@@ -658,9 +681,17 @@ impl 线段Py {
Vec<Py<线Py>>,
Option<Py<线Py>>,
)> {
// 所属中枢 extraction is complex — pass None for now
let (a, b, c, d) =
chanlun::algorithm::segment::线::(&.borrow().inner, None);
let borrowed = .borrow();
let (a, b, c, d) = if let Some(hub_bound) = {
if let Ok(mut hub_ref) = hub_bound.extract::<PyRefMut<'_, Py>>() {
let inner_mut = Rc::make_mut(&mut hub_ref.inner);
chanlun::algorithm::segment::线::(&borrowed.inner, Some(inner_mut))
} else {
chanlun::algorithm::segment::线::(&borrowed.inner, None)
}
} else {
chanlun::algorithm::segment::线::(&borrowed.inner, None)
};
let wrap = |v: Vec<Rc<chanlun::structure::dash_line::线>>| -> PyResult<Vec<Py<线Py>>> {
let mut result = Vec::new();
for x in v {
@@ -945,20 +976,17 @@ impl 线段Py {
}
#[classmethod]
fn (
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
) -> Vec<线Py> {
let obs = .borrow();
let obs_ref = obs.obs();
chanlun::algorithm::segment::线::(
&.borrow().inner,
&*obs_ref,
)
.into_iter()
.map(|d| 线Py { inner: Rc::new(d) })
.collect()
chanlun::algorithm::segment::线::(&.borrow().inner, &*obs_ref)
.into_iter()
.map(|d| 线Py { inner: Rc::new(d) })
.collect()
}
#[classmethod]
@@ -966,12 +994,12 @@ impl 线段Py {
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
) -> Vec<Py> {
) -> Vec<K线Py> {
let obs = .borrow();
let obs_ref = obs.obs();
chanlun::algorithm::segment::线::(&.borrow().inner, &*obs_ref)
.into_iter()
.map(|f| Py { inner: f })
.map(|ck| K线Py::from_rc(ck))
.collect()
}
}
@@ -1153,7 +1181,14 @@ impl 中枢Py {
self.inner.()
}
fn (&mut self, : Vec<Py<线Py>>, py: Python<'_>) -> bool {
#[pyo3(signature = (序列, 中枢序列 = None))]
fn (
&mut self,
: Vec<Py<线Py>>,
: Option<Vec<Py<线Py>>>,
py: Python<'_>,
) -> bool {
let _ = ; // Python 版声明了此参数但未使用
let rc_list: Vec<Rc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Rc::clone(&d.bind(py).borrow().inner))
@@ -1167,18 +1202,21 @@ impl 中枢Py {
self.inner.()
}
fn (&self, : Vec<Py<Self>>, py: Python<'_>) -> PyResult<()> {
fn (
&self,
: Vec<Py<Self>>,
: &Bound<'_, crate::config_py::Py>,
py: Python<'_>,
) -> PyResult<()> {
let mut hub_seq: Vec<Rc<chanlun::algorithm::hub::>> =
.iter()
.map(|h| Rc::clone(&h.bind(py).borrow().inner))
.collect();
// Need config for this call — use default
let config = chanlun::config::::default();
let config = .borrow().to_rust_config(.py())?;
self.inner.(&mut hub_seq, &config);
Ok(())
}
#[getter]
fn (&self) -> String {
self.inner.().to_string()
}
@@ -1191,6 +1229,17 @@ impl 中枢Py {
self.__str__()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
// ---- classmethods ----
#[classmethod]
+162 -53
View File
@@ -63,7 +63,7 @@ impl 基础买卖点Py {
Self {
inner: chanlun::business::bsp::::new(
.borrow().inner,
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
Rc::clone(&.borrow().inner),
,
,
@@ -76,6 +76,12 @@ impl 基础买卖点Py {
self.inner..clone()
}
#[setter]
#[pyo3(name = "备注")]
fn _备注(&mut self, value: String) {
self.inner. = value;
}
#[getter]
fn (&self) -> Py {
Py {
@@ -92,29 +98,27 @@ impl 基础买卖点Py {
#[getter]
fn K线(&self) -> K线Py {
K线Py {
inner: Rc::clone(&self.inner.K线),
}
K线Py::from_rc(Rc::clone(&self.inner.K线))
}
#[getter]
fn K线(&self) -> K线Py {
K线Py {
inner: (*self.inner.K线).clone(),
inner: self.inner.K线.clone(),
}
}
#[getter]
fn K线(&self) -> Option<K线Py> {
self.inner.K线.as_ref().map(|k| K线Py {
inner: (**k).clone(),
inner: Rc::clone(k),
})
}
#[getter]
fn K线(&self) -> Option<K线Py> {
self.inner.K线.as_ref().map(|k| K线Py {
inner: (**k).clone(),
inner: Rc::clone(k),
})
}
@@ -130,10 +134,12 @@ impl 基础买卖点Py {
.map(|f| crate::types_py::Py { inner: f })
}
#[getter]
fn (&self) -> i64 {
self.inner.()
}
#[getter]
fn (&self) -> i64 {
self.inner.()
}
@@ -187,7 +193,7 @@ impl 买卖点Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
,
,
,
@@ -207,7 +213,7 @@ impl 买卖点Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
,
,
,
@@ -227,7 +233,7 @@ impl 买卖点Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
,
,
,
@@ -247,7 +253,7 @@ impl 买卖点Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
,
,
,
@@ -267,7 +273,7 @@ impl 买卖点Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
,
,
,
@@ -287,7 +293,7 @@ impl 买卖点Py {
Py {
inner: chanlun::business::bsp::::(
Rc::clone(&.borrow().inner),
Rc::new(K线.borrow().inner.clone()),
K线.borrow().inner.clone(),
,
,
,
@@ -370,33 +376,68 @@ impl 观察者Py {
#[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(())
}
@@ -413,15 +454,15 @@ impl 观察者Py {
#[getter]
fn K线(&self) -> Option<K线Py> {
self.obs().K线().map(|k| K线Py {
inner: (**k).clone(),
inner: Rc::clone(k),
})
}
#[getter]
fn K(&self) -> Option<K线Py> {
self.obs().K().map(|k| K线Py {
inner: Rc::clone(k),
})
self.obs()
.K()
.map(|k| K线Py::from_rc(Rc::clone(k)))
}
#[getter]
@@ -444,7 +485,35 @@ impl 观察者Py {
}
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: Rc::new(k线),
},
)?;
slf.call_method1("增加原始K线", (k线_py,))?;
}
Ok(())
}
fn (&mut self) {
@@ -456,19 +525,61 @@ impl 观察者Py {
}
#[classmethod]
#[pyo3(signature = (文件路径, 配置 = None))]
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: Rc::new(k线),
},
)?;
obj.call_method1("增加原始K线", (k线_py,))?;
}
}
Ok(obj.unbind())
}
// ---- 序列 getters ----
@@ -478,7 +589,7 @@ impl 观察者Py {
let list = pyo3::types::PyList::empty(py);
for k in &self.obs().K线序列 {
list.append(K线Py {
inner: (**k).clone(),
inner: Rc::clone(k),
})?;
}
Ok(list.into())
@@ -488,9 +599,7 @@ 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: Rc::clone(k),
})?;
list.append(K线Py::from_rc(Rc::clone(k)))?;
}
Ok(list.into())
}
@@ -670,10 +779,10 @@ impl K线合成器Py {
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: Rc::new(k) }))
.collect())
}
@@ -701,14 +810,14 @@ impl K线合成器Py {
let results = self.inner.K线(k);
results
.into_iter()
.map(|(, k2)| (, K线Py { inner: k2 }))
.map(|(, k2)| (, K线Py { inner: Rc::new(k2) }))
.collect()
}
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: Rc::new(k.clone()),
})
}
#[getter]
@@ -776,7 +885,7 @@ impl 立体分析器Py {
}
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> {
+96 -8
View File
@@ -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}"
@@ -198,10 +209,13 @@ impl 缠论配置Py {
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 })
}
@@ -270,7 +284,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 +328,84 @@ 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);
serde_json::from_value(value)
.map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("配置转换失败: {e}")))
})
}
/// 递归遍历 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(())
+157 -65
View File
@@ -25,6 +25,7 @@
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyType};
use std::collections::HashMap;
use std::rc::Rc;
use crate::config_py::Py;
use crate::indicators_py::{线Py, Py, Py};
@@ -51,10 +52,9 @@ use crate::types_py::相对方向Py;
/// 获取MACD(K线序列, 计算方式, 快线周期?, 慢线周期?, 信号周期?) -> list[平滑异同移动平均线]
/// — 对整个K线序列批量计算 MACD
/// 截取(序列, 起点K线, 终点K线) -> list — 按时间戳截取K线区间
#[pyclass(name = "K线")]
#[derive(Clone)]
#[pyclass(name = "K线", unsendable)]
pub struct K线Py {
pub(crate) inner: chanlun::kline::bar::K线,
pub(crate) inner: Rc<chanlun::kline::bar::K线>,
}
#[pymethods]
@@ -73,7 +73,7 @@ impl K线Py {
: f64,
) -> Self {
Self {
inner: chanlun::kline::bar::K线 {
inner: Rc::new(chanlun::kline::bar::K线 {
: .to_string(),
,
,
@@ -86,7 +86,7 @@ impl K线Py {
macd: None,
rsi: None,
kdj: None,
},
}),
}
}
@@ -96,7 +96,7 @@ impl K线Py {
}
#[setter]
fn set_标识(&mut self, v: String) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -105,7 +105,7 @@ impl K线Py {
}
#[setter]
fn set_序号(&mut self, v: i64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -114,7 +114,7 @@ impl K线Py {
}
#[setter]
fn set_周期(&mut self, v: i64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -123,7 +123,7 @@ impl K线Py {
}
#[setter]
fn set_时间戳(&mut self, v: i64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -132,7 +132,7 @@ impl K线Py {
}
#[setter]
fn set_高(&mut self, v: f64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -141,7 +141,7 @@ impl K线Py {
}
#[setter]
fn set_低(&mut self, v: f64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -150,7 +150,7 @@ impl K线Py {
}
#[setter]
fn set_开盘价(&mut self, v: f64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -159,7 +159,7 @@ impl K线Py {
}
#[setter]
fn set_收盘价(&mut self, v: f64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -168,7 +168,7 @@ impl K线Py {
}
#[setter]
fn set_成交量(&mut self, v: f64) {
self.inner. = v;
Rc::make_mut(&mut self.inner). = v;
}
#[getter]
@@ -179,7 +179,7 @@ impl K线Py {
}
#[getter]
fn MACD(&self) -> Option<线Py> {
fn macd(&self) -> Option<线Py> {
self.inner
.macd
.as_ref()
@@ -187,7 +187,7 @@ impl K线Py {
}
#[getter]
fn RSI(&self) -> Option<Py> {
fn rsi(&self) -> Option<Py> {
self.inner
.rsi
.as_ref()
@@ -195,7 +195,7 @@ impl K线Py {
}
#[getter]
fn KDJ(&self) -> Option<Py> {
fn kdj(&self) -> Option<Py> {
self.inner
.kdj
.as_ref()
@@ -214,6 +214,17 @@ 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 Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
#[classmethod]
#[pyo3(signature = (标识, 时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量, 序号 = None, 周期 = None))]
fn K(
@@ -229,7 +240,7 @@ impl K线Py {
: Option<i64>,
) -> Self {
Self {
inner: chanlun::kline::bar::K线::K(
inner: Rc::new(chanlun::kline::bar::K线::K(
,
,
,
@@ -239,7 +250,7 @@ impl K线Py {
,
.unwrap_or(0),
.unwrap_or(60),
),
)),
}
}
@@ -251,7 +262,7 @@ 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()))
}
@@ -263,8 +274,11 @@ impl K线Py {
: i64,
: &str,
) -> Option<Self> {
chanlun::kline::bar::K线::(.as_bytes(), , )
.map(|inner| Self { inner })
chanlun::kline::bar::K线::(.as_bytes(), , ).map(|inner| {
Self {
inner: Rc::new(inner),
}
})
}
#[classmethod]
@@ -276,7 +290,7 @@ 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)
}
@@ -285,22 +299,33 @@ impl K线Py {
: 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 = Rc::as_ptr(&.borrow().inner);
let end_ptr = Rc::as_ptr(&.borrow().inner);
let start_ts = .borrow().inner.;
let end_ts = .borrow().inner.;
let start_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == start_ptr
|| k.borrow(py).inner. == start_ts
})
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("始 不在序列中"))?;
let end_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == end_ptr || k.borrow(py).inner. == end_ts
})
.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())
}
}
@@ -324,9 +349,27 @@ impl K线Py {
/// — 分析分型形成结果
/// 截取(序列, 起点分型, 终点分型) -> list — 截取分型间的缠K子序列
#[pyclass(name = "缠论K线", unsendable)]
#[derive(Clone)]
pub struct K线Py {
pub(crate) inner: std::rc::Rc<chanlun::kline::chan_kline::K线>,
bsp_set: std::cell::RefCell<Option<Py<pyo3::types::PySet>>>,
}
impl K线Py {
pub(crate) fn from_rc(inner: std::rc::Rc<chanlun::kline::chan_kline::K线>) -> Self {
Self {
inner,
bsp_set: std::cell::RefCell::new(None),
}
}
}
impl Clone for K线Py {
fn clone(&self) -> Self {
Self {
inner: std::rc::Rc::clone(&self.inner),
bsp_set: std::cell::RefCell::new(None),
}
}
}
#[pymethods]
@@ -398,7 +441,7 @@ impl 缠论K线Py {
#[getter]
fn K线(&self) -> K线Py {
K线Py {
inner: (*self.inner.K线).clone(),
inner: self.inner.K线.clone(),
}
}
@@ -410,10 +453,32 @@ 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 Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
#[getter]
fn (&self, py: Python<'_>) -> Self {
let mut mirror = Self {
inner: std::rc::Rc::new(self.inner.()),
bsp_set: std::cell::RefCell::new(None),
};
if let Some(ref src_set) = *self.bsp_set.borrow() {
if let Ok(new_set) = pyo3::types::PySet::empty(py) {
for item in src_set.bind(py).iter() {
let _ = new_set.add(item);
}
mirror.bsp_set = std::cell::RefCell::new(Some(new_set.into()));
}
}
mirror
}
#[getter]
@@ -431,6 +496,24 @@ impl 缠论K线Py {
self.inner.KDJ匹配()
}
#[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
if self.bsp_set.borrow().is_none() {
let set = pyo3::types::PySet::empty(py)?;
for s in self.inner..borrow().iter() {
set.add(s.clone())?;
}
*self.bsp_set.borrow_mut() = Some(set.into());
}
Ok(self
.bsp_set
.borrow()
.as_ref()
.unwrap()
.clone_ref(py)
.into_any())
}
#[classmethod]
fn (
_cls: &Bound<'_, PyType>,
@@ -466,12 +549,10 @@ 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),
}
Self::from_rc(std::rc::Rc::new(inner))
}
#[classmethod]
@@ -493,7 +574,7 @@ impl 缠论K线Py {
&K.borrow().inner,
&config,
);
Ok((result.map(|rc| Self { inner: rc }), mode))
Ok((result.map(Self::from_rc), mode))
}
#[classmethod]
@@ -505,7 +586,7 @@ 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序列
@@ -514,7 +595,7 @@ impl 缠论K线Py {
.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线::(
@@ -532,22 +613,33 @@ impl 缠论K线Py {
: 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 = Rc::as_ptr(&.borrow().inner);
let end_ptr = Rc::as_ptr(&.borrow().inner);
let start_ts = .borrow().inner.;
let end_ts = .borrow().inner.;
let start_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == start_ptr
|| k.borrow(py).inner. == start_ts
})
.ok_or_else(|| pyo3::exceptions::PyValueError::new_err("始 不在序列中"))?;
let end_idx =
.iter()
.position(|k| {
Rc::as_ptr(&k.borrow(py).inner) == end_ptr || k.borrow(py).inner. == end_ts
})
.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())
}
}
+40
View File
@@ -51,3 +51,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
});
}
}
+181 -52
View File
@@ -72,23 +72,23 @@ impl 分型Py {
#[getter]
fn (&self) -> Option<K线Py> {
self.inner..as_ref().map(|k| K线Py {
inner: Rc::clone(k),
})
self.inner
.
.as_ref()
.map(|k| K线Py::from_rc(Rc::clone(k)))
}
#[getter]
fn (&self) -> K线Py {
K线Py {
inner: Rc::clone(&self.inner.),
}
K线Py::from_rc(Rc::clone(&self.inner.))
}
#[getter]
fn (&self) -> Option<K线Py> {
self.inner..as_ref().map(|k| K线Py {
inner: Rc::clone(k),
})
self.inner
.
.as_ref()
.map(|k| K线Py::from_rc(Rc::clone(k)))
}
#[getter]
@@ -116,6 +116,17 @@ impl 分型Py {
self.__str__()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
#[getter]
fn (&self) -> Option<(Py, Py, Py)> {
self.inner.().map(|(a, b, c)| {
@@ -208,8 +219,8 @@ impl 分型Py {
/// 方法:
/// 之前是(其他虚线) -> bool — 判断当前虚线是否紧接在另一虚线之前
/// 之后是(其他虚线) -> bool — 判断当前虚线是否紧接在另一虚线之后
/// 获取普K序列(普K序列) -> list[K线] — 截取该虚线的原始K线范围
/// 获取缠K序列(缠K序列) -> list[缠论K线] — 截取该虚线的缠K范围
/// 获取普K序列(观察员) -> list[K线] — 截取该虚线的原始K线范围
/// 获取缠K序列(观察员) -> list[缠论K线] — 截取该虚线的缠K范围
/// 获取数据文本() -> str
///
/// 类方法(算法辅助):
@@ -294,6 +305,25 @@ impl 虚线Py {
self.inner._特征序列_显示
}
#[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for item in &self.inner. {
match item {
Some(feat) => list.append(Py::new(
py,
线Py {
inner: Rc::clone(feat),
},
)?)?,
None => {
list.append(py.None())?;
}
}
}
Ok(list.into())
}
#[getter]
fn (&self) -> bool {
self.inner.
@@ -301,9 +331,10 @@ impl 虚线Py {
#[getter]
fn K线(&self) -> Option<K线Py> {
self.inner.K线.as_ref().map(|k| K线Py {
inner: Rc::clone(k),
})
self.inner
.K线
.as_ref()
.map(|k| K线Py::from_rc(Rc::clone(k)))
}
#[getter]
@@ -413,39 +444,43 @@ impl 虚线Py {
self.inner.(&.borrow().inner)
}
fn K序列(&self, K序列: Vec<Py<K线Py>>, py: Python<'_>) -> PyResult<Py<PyAny>> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.collect();
let result = self.inner.K序列(&rc_list);
let list = pyo3::types::PyList::empty(py);
fn K序列(
&self,
: &Bound<'_, crate::business_py::Py>,
) -> PyResult<Py<PyAny>> {
let obs_ref = .borrow();
let observer_inner = obs_ref.obs();
let result = self.inner.K序列(&observer_inner.K线序列);
let list = pyo3::types::PyList::empty(.py());
for k in &result {
list.append(K线Py {
inner: (**k).clone(),
})?;
}
Ok(list.into())
}
fn K序列(
&self, K序列: Vec<Py<K线Py>>, py: Python<'_>
) -> PyResult<Py<PyAny>> {
let rc_list: Vec<Rc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Rc::clone(&k.bind(py).borrow().inner))
.collect();
let result = self.inner.K序列(&rc_list);
let list = pyo3::types::PyList::empty(py);
for k in &result {
list.append(K线Py {
inner: Rc::clone(k),
})?;
}
Ok(list.into())
}
#[getter]
fn K序列(
&self,
: &Bound<'_, crate::business_py::Py>,
) -> PyResult<Py<PyAny>> {
let obs_ref = .borrow();
let observer_inner = obs_ref.obs();
let result = self.inner.K序列(&observer_inner.K线序列);
let list = pyo3::types::PyList::empty(.py());
for k in &result {
list.append(K线Py::from_rc(Rc::clone(k)))?;
}
Ok(list.into())
}
#[classmethod]
fn _武(_cls: &Bound<'_, PyType>, 线: &Bound<'_, Self>) -> Py {
Py {
inner: 线.borrow().inner._武(),
}
}
fn (&self) -> String {
self.inner.()
}
@@ -458,6 +493,17 @@ impl 虚线Py {
self.__str__()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
// ---- 静态工厂方法 ----
#[classmethod]
@@ -547,7 +593,7 @@ impl 虚线Py {
) -> f64 {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD柱子均值(
&rc_list,
@@ -564,7 +610,7 @@ impl 虚线Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD均值(
&rc_list,
@@ -581,7 +627,7 @@ impl 虚线Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD均值(&rc_list, &线.borrow().inner)
}
@@ -595,7 +641,7 @@ impl 虚线Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD极值(&rc_list, &线.borrow().inner)
}
@@ -609,7 +655,7 @@ impl 虚线Py {
) -> Option<f64> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD柱子均值_阴(
&rc_list,
@@ -626,7 +672,7 @@ impl 虚线Py {
) -> Option<f64> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD柱子均值_阳(
&rc_list,
@@ -643,7 +689,7 @@ impl 虚线Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD均值_阴(&rc_list, &线.borrow().inner)
}
@@ -657,7 +703,7 @@ impl 虚线Py {
) -> bool {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD均值_阳(&rc_list, &线.borrow().inner)
}
@@ -671,7 +717,7 @@ impl 虚线Py {
) -> [bool; 3] {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::K线序列MACD趋向背驰(
&rc_list,
@@ -687,7 +733,7 @@ impl 虚线Py {
) -> Vec<Vec<f64>> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = k线序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD柱子分段(&rc_list)
}
@@ -713,12 +759,37 @@ impl 虚线Py {
: usize,
: usize,
py: Python<'_>,
) -> HashMap<String, String> {
) -> PyResult<Py<PyAny>> {
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
chanlun::structure::dash_line::线::MACD行为(&rc_list, , )
let result =
chanlun::structure::dash_line::线::MACD行为(&rc_list, , );
let dict = pyo3::types::PyDict::new(py);
dict.set_item("DIF上穿0", result.DIF上穿0)?;
dict.set_item("DIF下穿0", result.DIF下穿0)?;
dict.set_item("DEA上穿0", result.DEA上穿0)?;
dict.set_item("DEA下穿0", result.DEA下穿0)?;
dict.set_item("金叉次数", result.)?;
dict.set_item("死叉次数", result.)?;
let : Vec<Py<PyAny>> = result
.
.iter()
.map(|(a, b, c)| {
let tup = pyo3::types::PyTuple::new(
py,
[
(*a).into_pyobject(py)?.into_any().unbind(),
(*b).into_pyobject(py)?.into_any().unbind(),
(*c).into_pyobject(py)?.into_any().unbind(),
],
)?;
Ok(tup.into_any().unbind())
})
.collect::<PyResult<Vec<_>>>()?;
dict.set_item("密集交叉区域", )?;
Ok(dict.into())
}
#[classmethod]
@@ -821,6 +892,53 @@ impl 线段特征Py {
self.__str__()
}
fn __len__(&self) -> usize {
self.inner..len()
}
fn __getitem__(&self, index: isize, py: Python<'_>) -> PyResult<Py<PyAny>> {
let len = self.inner..len() as isize;
let idx = if index < 0 { index + len } else { index };
if idx < 0 || idx >= len {
return Err(pyo3::exceptions::PyIndexError::new_err(format!(
"线段特征 index {index} out of range (len={len})"
)));
}
let dash = &self.inner.[idx as usize];
let obj: Py<PyAny> = Py::new(
py,
线Py {
inner: Rc::clone(dash),
},
)?
.into();
Ok(obj)
}
fn __iter__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.inner. {
list.append(Py::new(
py,
线Py {
inner: Rc::clone(d),
},
)?)?;
}
list.call_method0("__iter__").map(|iter| iter.into())
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
// ---- instance methods ----
#[getter]
@@ -1004,6 +1122,17 @@ impl 特征分型Py {
fn __repr__(&self) -> String {
self.__str__()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Rc::as_ptr(&self.inner) == Rc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Rc::as_ptr(&self.inner) as u64
}
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
-5
View File
@@ -135,27 +135,22 @@ 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.()
}
+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✓ 所有测试通过")
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "chanlun"
version = "26.5.1"
version = "26.5.2"
edition = "2021"
rust-version = "1.70"
license = "MIT"
+2 -2
View File
@@ -928,7 +928,7 @@ impl 笔 {
}
/// 是否背驰过 — 判断笔是否在停顿位置出现过MACD趋向背驰
pub fn (: &线, : &) -> Vec<Rc<>> {
pub fn (: &线, : &) -> Vec<Rc<K线>> {
let = Self::(, );
let mut = Vec::new();
@@ -940,7 +940,7 @@ impl 笔 {
);
let = 线::K线序列MACD趋向背驰(&k线范围, .());
if .iter().all(|&x| x) {
.push(Rc::clone(&.));
.push(Rc::clone(&..));
}
}
+2 -6
View File
@@ -288,12 +288,8 @@ impl 中枢 {
/// 返回当前中枢最后一段所处的位置关系:中枢之中/中枢之上/中枢之下
pub fn (&self) -> &str {
let = self..last().unwrap();
let _中 = if . == "" {
&..
} else {
&..last().unwrap()..
};
let = crate::types::::(self.(), self.(), _中., _中.);
let = ._武();
let = crate::types::::(self.(), self.(), .., ..);
if == crate::types:::: {
"中枢之上"
} else if == crate::types:::: {
+32 -30
View File
@@ -27,6 +27,7 @@ use crate::algorithm::hub::中枢;
use crate::business::observer::;
use crate::config::;
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
use crate::structure::dash_line::线;
use crate::structure::fractal_obj::;
use crate::structure::segment_feat::线;
@@ -56,7 +57,7 @@ impl 线段 {
if !..is_empty() {
if !::(&..last().unwrap()., &., "") {
panic!(
"线段.添加虚线 不连续 {:?} {:?}",
"线段.添加虚线 不连续 {} {}",
..last().unwrap(),
);
@@ -85,17 +86,14 @@ impl 线段 {
);
}
if .. == . {
panic!("文武结构相同 {} {:?} {:?}", , ., );
panic!("文武结构相同 {} {} {}", , ., );
}
if let (Some(ref ), Some(ref )) = (&., &.) {
if let Some() =
::(.as_ref(), ..as_ref(), .as_ref(), false, false)
{
if != . {
panic!(
"武斗[{}], 分型结构不一致 {:?} != {:?}",
, , .
);
panic!("武斗[{}], 分型结构不一致 {} != {}", , , .);
}
}
}
@@ -557,7 +555,7 @@ impl 线段 {
if .is_none() {
eprintln!(
" 线段.刷新 特征后一笔 = None, {:?}, 有效特征: {}",
" 线段.刷新 特征后一笔 = None, {}, 有效特征: {}",
2,
.len()
);
@@ -679,7 +677,7 @@ impl 线段 {
if let Some() = 线.last() {
if !.(seg) {
panic!(
"线段.向序列中添加 不连续[{}] {:?} {:?}",
"线段.向序列中添加 不连续[{}] {} {}",
, ., seg.
);
}
@@ -694,7 +692,7 @@ impl 线段 {
{
// The Python code asserts here; we warn
eprintln!(
"线段._向序列中添加[{}], 之前线段.右 = None {:?}",
"线段._向序列中添加[{}], 之前线段.右 = None {}",
, 线
);
}
@@ -706,7 +704,7 @@ impl 线段 {
&& !线.
{
panic!(
"线段._向序列中添加[{}], 之前线段[-1] not in 待添加虚线! {:?}",
"线段._向序列中添加[{}], 之前线段[-1] not in 待添加虚线! {}",
, 线
);
}
@@ -735,7 +733,7 @@ impl 线段 {
}
if Rc::as_ptr(线.last().unwrap()) != Rc::as_ptr(线) {
panic!("线段._从序列中删除 弹出数据不在列表中 {:?}", 线);
panic!("线段._从序列中删除 弹出数据不在列表中 {}", 线);
}
if 线..len() >= 3 {
@@ -889,7 +887,7 @@ impl 线段 {
}
// 执行修正
eprintln!("[警告<{}, {}>]: 线段.修复贯穿伤 {:?}", , , 穿);
eprintln!("[警告<{}, {}>]: 线段.修复贯穿伤 {}", line!(), , 穿);
let = 线..clone();
Self::_弹出线段(
@@ -1320,7 +1318,7 @@ impl 线段 {
if let Some() = 线.last() {
if !.(seg) {
panic!(
"线段.向序列中添加 不连续[{}] {:?} {:?}",
"线段.向序列中添加 不连续[{}] {} {}",
, ., seg.
);
}
@@ -1347,7 +1345,7 @@ impl 线段 {
seg. = false;
Some(drop)
} else {
panic!("线段._从序列中删除 弹出数据不在列表中 {:?}", 线);
panic!("线段._从序列中删除 弹出数据不在列表中 {}", 线);
}
}
@@ -1630,8 +1628,8 @@ impl 线段 {
||
}
/// 获取所有停顿位置 — 在线段范围内找出所有停顿位置
pub fn (: &线, : &) -> Vec<线> {
/// 获取所有停顿位置 — 在线段范围内找出所有停顿位置
pub fn (: &线, : &) -> Vec<线> {
let mut = Vec::new();
if . != "文武" || . != "线段" {
return ;
@@ -1647,7 +1645,7 @@ impl 线段 {
let mut : Option<std::rc::Rc<>> = None;
for in & {
if .len() >= 2 {
if .len() >= 3 {
let = ::(, );
let mut : Vec<Rc<线>> = .into_iter().map(|b| Rc::new(b)).collect();
.push(Rc::clone());
@@ -1676,21 +1674,25 @@ impl 线段 {
};
if ! {
if let Some(线) = 线.last() {
let mut = 线::线(&线.);
. = .;
let mut _rc = Rc::new();
Self::(&mut _rc, &.);
let _inner =
Rc::try_unwrap(_rc).unwrap_or_else(|rc| (*rc).clone());
if _inner.() == .() {
= Some(Rc::clone(&线.last().unwrap().));
.push(_inner);
if 线..len() % 2 == 1 {
let mut = 线::线(&线.);
. = .;
let mut _rc = Rc::new();
Self::(&mut _rc, &.);
let _inner =
Rc::try_unwrap(_rc).unwrap_or_else(|rc| (*rc).clone());
if _inner.() == .() {
= Some(Rc::clone(&线.last().unwrap().));
.push(_inner);
}
}
}
}
if Rc::as_ptr() != Rc::as_ptr() {
.pop();
if let Some(mut popped) = .pop() {
Rc::make_mut(&mut popped). = false;
}
}
}
} else {
@@ -1701,15 +1703,15 @@ impl 线段 {
}
/// 是否背驰过 — 判断线段是否在停顿位置出现过背驰
pub fn (: &线, : &) -> Vec<Rc<>> {
let = Self::(, );
pub fn (: &线, : &) -> Vec<Rc<K线>> {
let = Self::(, );
let mut = Vec::new();
for in {
let mut _rc = Rc::new();
Self::(&mut _rc, &.);
if Self::线(&_rc, ) {
.push(Rc::clone(&_rc.));
.push(Rc::clone(&_rc..));
}
}
+118
View File
@@ -481,6 +481,31 @@ 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,
@@ -517,3 +542,96 @@ impl 观察者 {
Ok()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::;
#[test]
fn test_普k序列指针一致性() {
let config = ::default();
let obs = ::(
"/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb",
Some(config),
)
.unwrap();
let obs_ref = obs.borrow();
// 1. Check that each 笔's 获取普K序列 returns K lines whose Rc pointers
// match entries in 普通K线序列
for (i, bi) in obs_ref..iter().enumerate() {
let pu_seq = bi.K序列(&obs_ref.K线序列);
if pu_seq.is_empty() {
println!("{}: 获取普K序列 返回空 (fallback failed)", i);
println!(" 文.中.标的K线 原始起始序号: {}", bi...);
println!(" 武.中.标的K线 原始结束序号: {}", bi...);
println!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
} else {
// Check first element's pointer
let first_ptr = Rc::as_ptr(&pu_seq[0]);
let found = obs_ref
.K线序列
.iter()
.any(|k| Rc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
// Check if 文.中.标的K线 pointer is in 普通K线序列
let wen_ptr = Rc::as_ptr(&bi...K线);
let wen_found = obs_ref.K线序列.iter().any(|k| Rc::as_ptr(k) == wen_ptr);
println!(" 文.中.标的K线 在序列中: {}", wen_found);
} else {
println!("{}: OK, 获取普K序列[0] 在序列中找到", i);
}
}
}
}
#[test]
fn test_pyo3_flow_pointer_consistency() {
// Simulate what the PyO3 读取数据文件 classmethod does:
// 1. Parse K lines from file
// 2. Create "K线Py { inner: Rc::new(k线) }" for each
// 3. Call observer.增加原始K线(k线_value)
let config = ::default();
let obs_ref = ::new("btcusd".into(), 300, config);
let file_path = "/home/moscow/chanlun.rs/btcusd-300-1777649100-1778398800.nb";
let data = std::fs::read(file_path).unwrap();
let size = 48;
for i in 0..data.len() / size {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
// Simulate: K线Py { inner: Rc::new(k线) }
let _k线_py_inner = Rc::new(k线.clone());
// In the actual PyO3 path, (*普K.borrow().inner).clone() extracts K线 value
// which then gets Rc::wrapped inside the observer
obs_ref.borrow_mut().K线(k线);
}
}
let obs = obs_ref.borrow();
println!("普通K线序列.len: {}", obs.K线序列.len());
println!("笔序列.len: {}", obs..len());
// Now check: does 获取普K序列 return K lines whose pointers match 普通K线序列?
for (i, bi) in obs..iter().enumerate() {
let pu_seq = bi.K序列(&obs.K线序列);
if pu_seq.is_empty() {
println!("{}: 获取普K序列 返回空!", i);
} else {
let first_ptr = Rc::as_ptr(&pu_seq[0]);
let found = obs.K线序列.iter().any(|k| Rc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 指针不在序列中!", i);
}
// Only print first few
if i < 5 {
println!("{}: OK, len={}", i, pu_seq.len());
}
}
}
}
}
+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
@@ -47,7 +47,7 @@ pub struct 缠论K线 {
pub : i64,
pub : i64,
pub K线: Rc<K线>,
pub : Option<()>, // 占位,后续替换为实际类型
pub : std::cell::RefCell<Vec<String>>,
}
impl std::fmt::Display for K线 {
@@ -84,7 +84,7 @@ impl 缠论K线 {
: self.,
: self.,
K线: Rc::clone(&self.K线),
: None,
: std::cell::RefCell::new(self..borrow().clone()),
}
}
@@ -217,7 +217,7 @@ impl 缠论K线 {
: ,
: ,
K线: k,
: None,
: std::cell::RefCell::new(Vec::new()),
};
if let Some() = {
@@ -239,7 +239,7 @@ impl 缠论K线 {
pub fn (
K: Option<&K线>,
K: &mut K线,
K: &K线,
K: &Rc<K线>,
: &,
) -> (Option<Rc<K线>>, Option<String>) {
let = ::(K., K., K., K.);
@@ -258,7 +258,7 @@ impl 缠论K线 {
K.(),
,
K.,
Rc::new(K.clone()),
Rc::clone(K),
Some(K),
);
K. = K. + 1;
@@ -294,7 +294,7 @@ impl 缠论K线 {
// 逆序包含时更新时间和标的K线
if != :: {
K. = K.;
K.K线 = Rc::new(K.clone());
K.K线 = Rc::clone(K);
}
K. = (K., K.);
K. = (K., K.);
@@ -461,7 +461,7 @@ impl 缠论K线 {
// ---- 阶段2: 缠K合并 ----
let : String;
let K线_ref: &K线 = &*K序列.last().unwrap();
let K线_ref: &Rc<K线> = K序列.last().unwrap();
if !K序列.is_empty() {
let len = K序列.len();
+62 -21
View File
@@ -55,6 +55,18 @@ pub struct 虚线 {
pub : bool,
}
/// 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 线 {
pub fn new(
: i64,
@@ -144,12 +156,27 @@ impl 虚线 {
/// 获取该虚线范围内的普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()
// 使用指针查找(与 Python list.index 身份匹配行为一致),
// 而非序号切片——因为序号可能与实际位置不一致。
let = K序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(&self...K线));
let = K序列
.iter()
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(&self...K线));
match (, ) {
(Some(s), Some(e)) if s <= e => K序列[s..=e].to_vec(),
_ => {
// 指针查找失败时回退到序号方式
println!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>");
let = self... as usize;
let = self... as usize;
if < K序列.len() && < K序列.len() && <= {
K序列[..=].to_vec()
} else {
Vec::new()
}
}
}
}
@@ -158,6 +185,18 @@ impl 虚线 {
K线::(K序列, &self.., &self..).unwrap_or_default()
}
/// 获取_武 — 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武)
pub fn _武(&self) -> Rc<> {
if self. == "" {
return Rc::clone(&self.);
}
let mut current: &线 = self;
while current. != "" {
current = &*current..last().unwrap();
}
Rc::clone(&current.)
}
/// 获取数据文本(用于保存/调试)
pub fn (&self) -> String {
use crate::utils::format_f64_g;
@@ -693,9 +732,7 @@ impl 虚线 {
K序列: &[Rc<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 +796,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,
,
}
}
// ---- 买卖意义 ----
@@ -797,7 +834,7 @@ impl 虚线 {
let = Self::K买卖点模式(&._指标模式, &线.., );
let = false;
let : Vec<Rc<>> = if 线. == "" {
let : Vec<Rc<K线>> = if 线. == "" {
crate::algorithm::bi::::(线, )
} else {
crate::algorithm::segment::线::(线, )
@@ -816,7 +853,11 @@ impl 虚线 {
format!(
"背驰过:{},极值:{},柱子分型匹配",
.len(),
Self::MACD极值(K序列, 线)
if Self::MACD极值(K序列, 线) {
"True"
} else {
"False"
}
),
);
}