Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 52823e6c75 | |||
| 4908870245 | |||
| d2f74d847b | |||
| 7d99802059 | |||
| 98f07508fa | |||
| 9280041f2a |
@@ -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 }}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
[](https://pypi.org/project/chanlun/)
|
||||
[](LICENSE)
|
||||
|
||||
基于 [chanlun](../chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 与 `chan.py` 完全兼容。
|
||||
基于 [chanlun](./chanlun/) Rust 核心库的 PyO3 高性能 Python 绑定,API 与 `chan.py` 完全兼容。
|
||||
|
||||
## 安装
|
||||
|
||||
|
||||
@@ -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线序列))
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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" }
|
||||
|
||||
@@ -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
@@ -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> {
|
||||
|
||||
@@ -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
@@ -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())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
@@ -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<()> {
|
||||
|
||||
@@ -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.是否衔接()
|
||||
}
|
||||
|
||||
@@ -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():
|
||||
"""重写关键序列 getter,super() 取基类值."""
|
||||
|
||||
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
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "26.5.1"
|
||||
version = "26.5.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.70"
|
||||
license = "MIT"
|
||||
|
||||
@@ -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(&筆.武.中));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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::相对方向::向下缺口 {
|
||||
|
||||
@@ -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.武.中));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
时间戳: 初始时间,
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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(¤t.武)
|
||||
}
|
||||
|
||||
/// 获取数据文本(用于保存/调试)
|
||||
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"
|
||||
}
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user