From 7416bfc73ad280cc9a8205930e71e19e4d0511bb Mon Sep 17 00:00:00 2001 From: YuWuKunCheng Date: Mon, 25 May 2026 12:41:14 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AC=AC=E4=B8=89=E7=89=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- chanlun-py/.gitignore | 5 + chanlun-py/Cargo.toml | 20 + chanlun-py/LICENSE | 1 + chanlun-py/README.md | 1 + chanlun-py/build.sh | 163 +++++ chanlun-py/chanlun/__init__.py | 3 + chanlun-py/pyproject.toml | 39 ++ chanlun-py/src/algorithm_py.rs | 1117 +++++++++++++++++++++++++++++++ chanlun-py/src/business_py.rs | 579 ++++++++++++++++ chanlun-py/src/config_py.rs | 222 ++++++ chanlun-py/src/indicators_py.rs | 576 ++++++++++++++++ chanlun-py/src/kline_py.rs | 498 ++++++++++++++ chanlun-py/src/lib.rs | 29 + chanlun-py/src/structure_py.rs | 825 +++++++++++++++++++++++ chanlun-py/src/types_py.rs | 408 +++++++++++ chanlun-py/test_integration.py | 135 ++++ chanlun/Cargo.toml | 2 +- 17 files changed, 4622 insertions(+), 1 deletion(-) create mode 100644 chanlun-py/.gitignore create mode 100644 chanlun-py/Cargo.toml create mode 120000 chanlun-py/LICENSE create mode 120000 chanlun-py/README.md create mode 100755 chanlun-py/build.sh create mode 100644 chanlun-py/chanlun/__init__.py create mode 100644 chanlun-py/pyproject.toml create mode 100644 chanlun-py/src/algorithm_py.rs create mode 100644 chanlun-py/src/business_py.rs create mode 100644 chanlun-py/src/config_py.rs create mode 100644 chanlun-py/src/indicators_py.rs create mode 100644 chanlun-py/src/kline_py.rs create mode 100644 chanlun-py/src/lib.rs create mode 100644 chanlun-py/src/structure_py.rs create mode 100644 chanlun-py/src/types_py.rs create mode 100644 chanlun-py/test_integration.py diff --git a/chanlun-py/.gitignore b/chanlun-py/.gitignore new file mode 100644 index 0000000..2631f06 --- /dev/null +++ b/chanlun-py/.gitignore @@ -0,0 +1,5 @@ +target/ +__pycache__/ +*.egg-info/ +dist/ +build/ diff --git a/chanlun-py/Cargo.toml b/chanlun-py/Cargo.toml new file mode 100644 index 0000000..cace846 --- /dev/null +++ b/chanlun-py/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "chanlun-py" +version = "26.5.1" +edition = "2021" +description = "缠论技术分析库 — Rust 高性能 Python 绑定" +authors = ["YuYuKunKun"] +license = "MIT" +repository = "https://github.com/YuYuKunKun/chanlun.rs" + +[lib] +crate-type = ["cdylib"] +name = "chanlun" + +[dependencies] +# 发布至 PyPI 前,需先将 chanlun 发布至 crates.io,然后替换为版本号依赖: +# chanlun = "0.1" +chanlun = { path = "../chanlun" } +pyo3 = { version = "0.28", features = ["extension-module"] } +serde_json = "1" +chrono = "0.4" diff --git a/chanlun-py/LICENSE b/chanlun-py/LICENSE new file mode 120000 index 0000000..ea5b606 --- /dev/null +++ b/chanlun-py/LICENSE @@ -0,0 +1 @@ +../LICENSE \ No newline at end of file diff --git a/chanlun-py/README.md b/chanlun-py/README.md new file mode 120000 index 0000000..32d46ee --- /dev/null +++ b/chanlun-py/README.md @@ -0,0 +1 @@ +../README.md \ No newline at end of file diff --git a/chanlun-py/build.sh b/chanlun-py/build.sh new file mode 100755 index 0000000..d2c61bd --- /dev/null +++ b/chanlun-py/build.sh @@ -0,0 +1,163 @@ +#!/usr/bin/env bash +# ============================================================ +# chanlun-py — 构建、安装、发布脚本 +# +# 前置依赖: +# pip install maturin +# ============================================================ +set -euo pipefail + +PROJECT_DIR="$(cd "$(dirname "$0")" && pwd)" +cd "$PROJECT_DIR" + +# 颜色输出 +red() { echo -e "\033[31m$*\033[0m"; } +green() { echo -e "\033[32m$*\033[0m"; } +yellow() { echo -e "\033[33m$*\033[0m"; } + +usage() { + cat <<'EOF' +用法: ./build.sh <命令> + +命令: + build 开发模式编译(debug) + release 发布模式编译(release,带优化) + install 构建 wheel 并安装到当前 Python 环境 + install-rel 同 install + develop 开发模式安装到 venv(需要激活虚拟环境) + test 运行集成测试 + sdist 构建源码分发包 + wheel 构建 wheel 包(release) + publish 发布至 PyPI(需先配置 TWINE_ 环境变量或 .pypirc) + clean 清理构建产物 + check 检查 pyproject.toml 配置是否合法 + +PyPI 发布流程: + 1. 确认 Cargo.toml 中 chanlun 依赖已切换为 crates.io 版本(非 path) + 2. 更新版本号: pyproject.toml + Cargo.toml + 3. ./build.sh check — 验证配置 + 4. ./build.sh publish — 构建并推送至 PyPI + + 环境变量: + TWINE_USERNAME — PyPI 用户名(或 __token__ 使用 API token) + TWINE_PASSWORD — PyPI 密码或 API token + 或通过 twine 的 ~/.pypirc 配置 +EOF +} + +cmd_build() { + green "[build] 开发模式编译..." + cargo build + green "编译完成。使用 'maturin develop' 安装到 venv,或 './build.sh wheel' 构建 wheel" +} + +cmd_release() { + green "[release] 发布模式编译..." + cargo build --release +} + +cmd_install() { + green "[install] 构建 wheel 并安装..." + maturin build --release 2>&1 | tail -3 + local wheel=$(ls -t target/wheels/*.whl 2>/dev/null | head -1) + if [ -z "$wheel" ]; then + red "未找到 wheel 文件" + exit 1 + fi + pip install --force-reinstall "$wheel" 2>&1 | tail -3 + green "安装完成!" +} + +cmd_install_rel() { + cmd_install +} + +cmd_develop() { + green "[develop] 开发模式安装(需要 venv)..." + maturin develop 2>&1 | tail -3 +} + +cmd_test() { + green "[test] 运行集成测试..." + # 确保已安装 + python3 -c "import chanlun" 2>/dev/null || { + yellow "chanlun 未安装,正在构建并安装..." + maturin build --release 2>&1 | tail -3 + local wheel=$(ls -t target/wheels/*.whl 2>/dev/null | head -1) + pip install --force-reinstall "$wheel" 2>&1 | tail -3 + } + python3 test_integration.py +} + +cmd_sdist() { + green "[sdist] 构建源码分发包..." + maturin sdist + yellow "构建产物在: target/wheels/" + ls -lh target/wheels/*.tar.gz 2>/dev/null || true +} + +cmd_wheel() { + green "[wheel] 构建 wheel 包..." + maturin build --release + yellow "构建产物在: target/wheels/" + ls -lh target/wheels/*.whl 2>/dev/null || true +} + +cmd_publish() { + yellow "发布前请确认:" + yellow " 1. Cargo.toml 中 chanlun 依赖已切换为 crates.io 版本" + yellow " 2. 版本号已更新 (pyproject.toml + Cargo.toml)" + yellow " 3. git 工作区干净且已打 tag" + echo "" + read -rp "确认发布至 PyPI? [y/N] " confirm + if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then + red "已取消" + exit 0 + fi + + green "[publish] 构建并发布至 PyPI..." + maturin publish + + green "发布完成!" + yellow "安装: pip install chanlun" +} + +cmd_clean() { + green "[clean] 清理构建产物..." + cargo clean + rm -rf target/wheels/ dist/ build/ *.egg-info/ + find . -type d -name "__pycache__" -exec rm -rf {} + 2>/dev/null || true + green "清理完成" +} + +cmd_check() { + green "[check] 验证 pyproject.toml..." + python3 -c " +import tomllib +with open('pyproject.toml', 'rb') as f: + data = tomllib.load(f) +print(' name:', data['project']['name']) +print(' version:', data['project']['version']) +print(' requires-python:', data['project']['requires-python']) +print(' build-backend:', data['build-system']['build-backend']) +print(' OK') +" + green "配置验证通过" +} + +# --- main --- +case "${1:-}" in + build) cmd_build ;; + release) cmd_release ;; + install) cmd_install ;; + install-rel) cmd_install_rel ;; + develop) cmd_develop ;; + test) cmd_test ;; + sdist) cmd_sdist ;; + wheel) cmd_wheel ;; + publish) cmd_publish ;; + clean) cmd_clean ;; + check) cmd_check ;; + -h|--help|help) usage ;; + *) red "未知命令: ${1:-}"; usage; exit 1 ;; +esac diff --git a/chanlun-py/chanlun/__init__.py b/chanlun-py/chanlun/__init__.py new file mode 100644 index 0000000..b05c293 --- /dev/null +++ b/chanlun-py/chanlun/__init__.py @@ -0,0 +1,3 @@ +"""缠论技术分析库 — Rust 高性能实现""" + +from ._chanlun import * diff --git a/chanlun-py/pyproject.toml b/chanlun-py/pyproject.toml new file mode 100644 index 0000000..af92c01 --- /dev/null +++ b/chanlun-py/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["maturin>=1.0,<2.0"] +build-backend = "maturin" + +[project] +name = "chanlun" +version = "2605.1" +description = "缠论技术分析库 — Rust 高性能实现" +readme = { file = "README.md", content-type = "text/markdown" } +license = { file = "LICENSE", content-type = "text/plain" } +authors = [ + { name = "YuYuKunKun" }, +] +keywords = ["chanlun", "technical-analysis", "trading", "stock", "crypto"] +classifiers = [ + "Development Status :: 3 - Alpha", + "Intended Audience :: Financial and Insurance Industry", + "License :: OSI Approved :: MIT License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Rust", + "Topic :: Office/Business :: Financial :: Investment", +] +requires-python = ">=3.9" + +[project.urls] +Homepage = "https://github.com/YuYuKunKun/chanlun.rs" +Repository = "https://github.com/YuYuKunKun/chanlun.rs" +Issues = "https://github.com/YuYuKunKun/chanlun.rs/issues" + +[tool.maturin] +features = ["pyo3/extension-module"] +python-source = "." +module-name = "chanlun._chanlun" +manifest-path = "Cargo.toml" diff --git a/chanlun-py/src/algorithm_py.rs b/chanlun-py/src/algorithm_py.rs new file mode 100644 index 0000000..df2a370 --- /dev/null +++ b/chanlun-py/src/algorithm_py.rs @@ -0,0 +1,1117 @@ +use pyo3::prelude::*; +use pyo3::types::PyType; +use std::rc::Rc; + +use crate::config_py::缠论配置Py; +use crate::kline_py::K线Py; +use crate::structure_py::{分型Py, 虚线Py}; +use crate::types_py::相对方向Py; + +// ========== 背驰分析 ========== + +#[pyclass(name = "背驰分析")] +pub struct 背驰分析Py; + +#[pymethods] +impl 背驰分析Py { + #[classmethod] + #[pyo3(signature = (进入段, 离开段, K线序列, 方式 = "总"))] + fn MACD背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + K线序列: Vec>, + 方式: &str, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = K线序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::algorithm::divergence::背驰分析::MACD背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + &rc_list, + 方式, + ) + } + + #[classmethod] + fn 斜率背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + ) -> bool { + chanlun::algorithm::divergence::背驰分析::斜率背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + ) + } + + #[classmethod] + fn 测度背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + ) -> bool { + chanlun::algorithm::divergence::背驰分析::测度背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + ) + } + + #[classmethod] + fn 全量背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 普K序列: Vec>, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::algorithm::divergence::背驰分析::全量背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + &rc_list, + ) + } + + #[classmethod] + fn 任意背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 普K序列: Vec>, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::algorithm::divergence::背驰分析::任意背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + &rc_list, + ) + } + + #[classmethod] + fn 配置背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 普K序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::divergence::背驰分析::配置背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + &rc_list, + &config, + )) + } + + #[classmethod] + fn 任选背驰( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 普K序列: Vec>, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::algorithm::divergence::背驰分析::任选背驰( + &进入段.borrow().inner, + &离开段.borrow().inner, + &rc_list, + ) + } + + #[classmethod] + fn 背驰模式( + _cls: &Bound<'_, PyType>, + 进入段: &Bound<'_, 虚线Py>, + 离开段: &Bound<'_, 虚线Py>, + 普K序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + 模式: &str, + py: Python<'_>, + ) -> PyResult { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::divergence::背驰分析::背驰模式( + &进入段.borrow().inner, + &离开段.borrow().inner, + &rc_list, + &config, + 模式, + )) + } +} + +// ========== 笔 ========== + +#[pyclass(name = "笔")] +pub struct 笔Py; + +#[pymethods] +impl 笔Py { + #[classmethod] + fn 获取缠K数量( + _cls: &Bound<'_, PyType>, + 缠K序列: Vec>, + 笔序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + let bi_list: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::bi::笔::获取缠K数量( + &ck_list, &bi_list, &config, + )) + } + + #[classmethod] + fn 次高( + _cls: &Bound<'_, PyType>, + 缠K序列: Vec>, + 取舍: bool, + py: Python<'_>, + ) -> Option { + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::次高(&ck_list, 取舍) + .map(|inner| crate::kline_py::缠论K线Py { inner }) + } + + #[classmethod] + fn 次低( + _cls: &Bound<'_, PyType>, + 缠K序列: Vec>, + 取舍: bool, + py: Python<'_>, + ) -> Option { + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::次低(&ck_list, 取舍) + .map(|inner| crate::kline_py::缠论K线Py { inner }) + } + + #[classmethod] + fn 实际高点( + _cls: &Bound<'_, PyType>, + 缠K序列: Vec>, + 取舍: bool, + py: Python<'_>, + ) -> Option { + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::实际高点(&ck_list, 取舍) + .map(|inner| crate::kline_py::缠论K线Py { inner }) + } + + #[classmethod] + fn 实际低点( + _cls: &Bound<'_, PyType>, + 缠K序列: Vec>, + 取舍: bool, + py: Python<'_>, + ) -> Option { + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::实际低点(&ck_list, 取舍) + .map(|inner| crate::kline_py::缠论K线Py { inner }) + } + + #[classmethod] + fn 相对关系( + _cls: &Bound<'_, PyType>, + 筆: &Bound<'_, 虚线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::bi::笔::相对关系( + &筆.borrow().inner, + &config, + )) + } + + #[classmethod] + fn 以文会友( + _cls: &Bound<'_, PyType>, + 笔序列: Vec>, + 文: &Bound<'_, 分型Py>, + py: Python<'_>, + ) -> Option<虚线Py> { + let bi_list: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::以文会友(&bi_list, &文.borrow().inner) + .map(|inner| 虚线Py { inner }) + } + + #[classmethod] + fn 以武会友( + _cls: &Bound<'_, PyType>, + 笔序列: Vec>, + 武: &Bound<'_, 分型Py>, + py: Python<'_>, + ) -> Option<虚线Py> { + let bi_list: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::以武会友(&bi_list, &武.borrow().inner) + .map(|inner| 虚线Py { inner }) + } + + #[classmethod] + #[pyo3(signature = (笔序列, 缠K, 偏移 = 1))] + fn 根据缠K找笔( + _cls: &Bound<'_, PyType>, + 笔序列: Vec>, + 缠K: &Bound<'_, crate::kline_py::缠论K线Py>, + 偏移: i64, + py: Python<'_>, + ) -> Option<虚线Py> { + let bi_list: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::bi::笔::根据缠K找笔(&bi_list, &缠K.borrow().inner, 偏移) + .map(|inner| 虚线Py { inner }) + } + + #[classmethod] + fn 分析( + _cls: &Bound<'_, PyType>, + 当前分型: &Bound<'_, 分型Py>, + 分型序列: Vec>, + 笔序列: Vec>, + 缠K序列: Vec>, + 普K序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let mut fr_seq: Vec> = 分型序列 + .iter() + .map(|f| Rc::clone(&f.bind(py).borrow().inner)) + .collect(); + let mut bi_seq: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + let bar_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(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, + )) + } + + #[classmethod] + fn 分析递归( + _cls: &Bound<'_, PyType>, + 当前分型: &Bound<'_, 分型Py>, + 分型序列: Vec>, + 笔序列: Vec>, + 缠K序列: Vec>, + 普K序列: Vec>, + 递归层次: i64, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let mut fr_seq: Vec> = 分型序列 + .iter() + .map(|f| Rc::clone(&f.bind(py).borrow().inner)) + .collect(); + let mut bi_seq: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let ck_list: Vec> = 缠K序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + let bar_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(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, + )) + } + + // 自检, 获取所有停顿位置, 是否背驰过 — deferred to Phase 7 (need observer) +} + +// ========== 线段 ========== + +#[pyclass(name = "线段")] +pub struct 线段Py; + +#[pymethods] +impl 线段Py { + #[classmethod] + fn 添加虚线( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 筆: &Bound<'_, 虚线Py>, + ) -> PyResult<()> { + let bi_rc = Rc::clone(&筆.borrow().inner); + let mut ref_mut = 段.borrow_mut(); + chanlun::algorithm::segment::线段::添加虚线(&mut ref_mut.inner, bi_rc); + Ok(()) + } + + #[classmethod] + fn 武斗( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 武: &Bound<'_, 分型Py>, + 行号: u32, + ) -> PyResult<()> { + let mut ref_mut = 段.borrow_mut(); + chanlun::algorithm::segment::线段::武斗( + &mut ref_mut.inner, + &Rc::clone(&武.borrow().inner), + 行号, + ); + Ok(()) + } + + #[classmethod] + fn 武终(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>, 行号: u32) -> PyResult<()> { + let mut ref_mut = 段.borrow_mut(); + chanlun::algorithm::segment::线段::武终(&mut ref_mut.inner, 行号); + Ok(()) + } + + #[classmethod] + fn 验证序列( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 序列: Vec>, + py: Python<'_>, + ) -> PyResult<()> { + let mut ref_mut = 段.borrow_mut(); + let rc_list: Vec> = 序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::segment::线段::验证序列(&mut ref_mut.inner, &rc_list); + Ok(()) + } + + #[classmethod] + fn 序列重置( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 序列: Vec>, + py: Python<'_>, + ) -> PyResult<()> { + let mut ref_mut = 段.borrow_mut(); + let rc_list: Vec> = 序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::segment::线段::序列重置(&mut ref_mut.inner, &rc_list); + Ok(()) + } + + #[classmethod] + fn 基础判断( + _cls: &Bound<'_, PyType>, + 左: &Bound<'_, 虚线Py>, + 中: &Bound<'_, 虚线Py>, + 右: &Bound<'_, 虚线Py>, + 关系序列: Vec<相对方向Py>, + ) -> bool { + let rel_list: Vec = 关系序列.iter().map(|d| d.inner).collect(); + chanlun::algorithm::segment::线段::基础判断( + &左.borrow().inner, + &中.borrow().inner, + &右.borrow().inner, + &rel_list, + ) + } + + #[classmethod] + fn 四象(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>) -> String { + chanlun::algorithm::segment::线段::四象(&段.borrow().inner) + } + + #[classmethod] + fn 获取缺口( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + ) -> Option { + chanlun::algorithm::segment::线段::获取缺口(&段.borrow().inner) + .map(|inner| crate::types_py::缺口Py { inner }) + } + + #[classmethod] + fn 特征分型终结(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>) -> bool { + chanlun::algorithm::segment::线段::特征分型终结(&段.borrow().inner) + } + + #[classmethod] + fn 特征序列状态( + _cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py> + ) -> (bool, bool, bool) { + chanlun::algorithm::segment::线段::特征序列状态(&段.borrow().inner) + } + + #[classmethod] + fn 设置特征序列( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 序列: &Bound<'_, PyAny>, + 行号: u32, + ) -> PyResult<()> { + // 特征序列 contains Option>, skip for now + let mut ref_mut = 段.borrow_mut(); + // Just pass empty vec for now — 线段特征Py not fully integrated + chanlun::algorithm::segment::线段::设置特征序列(&mut ref_mut.inner, vec![], 行号); + Ok(()) + } + + #[classmethod] + fn 刷新特征序列( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult<()> { + let mut ref_mut = 段.borrow_mut(); + let config = 配置.borrow().to_rust_config(py)?; + chanlun::algorithm::segment::线段::刷新特征序列(&mut ref_mut.inner, &config); + Ok(()) + } + + #[classmethod] + fn 查找贯穿伤(_cls: &Bound<'_, PyType>, 段: &Bound<'_, 虚线Py>) -> Option<虚线Py> { + chanlun::algorithm::segment::线段::查找贯穿伤(&段.borrow().inner) + .map(|inner| 虚线Py { inner }) + } + + #[classmethod] + fn 分割序列( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 所属中枢: Option<&Bound<'_, PyAny>>, + py: Python<'_>, + ) -> PyResult<( + Vec>, + Vec>, + Vec>, + Option>, + )> { + // 所属中枢 extraction is complex — pass None for now + let (a, b, c, d) = + chanlun::algorithm::segment::线段::分割序列(&段.borrow().inner, None); + let wrap = |v: Vec>| -> PyResult>> { + let mut result = Vec::new(); + for x in v { + result.push(Py::new(py, 虚线Py { inner: x })?); + } + Ok(result) + }; + Ok(( + wrap(a)?, + wrap(b)?, + wrap(c)?, + d.map(|x| Py::new(py, 虚线Py { inner: x })).transpose()?, + )) + } + + #[classmethod] + fn 刷新( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult<()> { + let mut ref_mut = 段.borrow_mut(); + let config = 配置.borrow().to_rust_config(py)?; + chanlun::algorithm::segment::线段::刷新(&mut ref_mut.inner, &config); + Ok(()) + } + + #[classmethod] + fn 获取内部中枢序列( + _cls: &Bound<'_, PyType>, + 段: &Bound<'_, 虚线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult<(Py, Py, Py)> { + let mut ref_mut = 段.borrow_mut(); + let config = 配置.borrow().to_rust_config(py)?; + let (a, b, c) = chanlun::algorithm::segment::线段::获取内部中枢序列( + &mut ref_mut.inner, + &config, + ); + let pk_list = |v: Vec>| -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for h in v { + list.append(Py::new(py, 中枢Py { inner: h })?)?; + } + Ok(list.into()) + }; + Ok((pk_list(a)?, pk_list(b)?, pk_list(c)?)) + } + + #[classmethod] + fn _添加线段( + _cls: &Bound<'_, PyType>, + 线段序列: &Bound<'_, PyAny>, + 待添加线段: &Bound<'_, 虚线Py>, + 配置: &Bound<'_, 缠论配置Py>, + 行号: String, + py: Python<'_>, + ) -> PyResult<()> { + let config = 配置.borrow().to_rust_config(py)?; + let mut seg_seq: Vec> = vec![]; + chanlun::algorithm::segment::线段::_添加线段( + &mut seg_seq, + Rc::clone(&待添加线段.borrow().inner), + &config, + 行号, + ); + Ok(()) + } + + #[classmethod] + fn _弹出线段( + _cls: &Bound<'_, PyType>, + 线段序列: &Bound<'_, PyAny>, + 待弹出线段: &Bound<'_, 虚线Py>, + 配置: &Bound<'_, 缠论配置Py>, + 行号: String, + py: Python<'_>, + ) -> PyResult> { + let config = 配置.borrow().to_rust_config(py)?; + let mut seg_seq: Vec> = vec![]; + let result = chanlun::algorithm::segment::线段::_弹出线段( + &mut seg_seq, + &Rc::clone(&待弹出线段.borrow().inner), + &config, + 行号, + ); + Ok(result.map(|inner| 虚线Py { inner })) + } + + #[classmethod] + fn _缺口突破( + _cls: &Bound<'_, PyType>, + 线段序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + 层级: i64, + py: Python<'_>, + ) -> PyResult { + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::segment::线段::_缺口突破( + &mut seg_seq, + &config, + 层级, + )) + } + + #[classmethod] + fn _非缺口下穿刺( + _cls: &Bound<'_, PyType>, + 线段序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + 层级: i64, + py: Python<'_>, + ) -> PyResult { + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::segment::线段::_非缺口下穿刺( + &mut seg_seq, + &config, + 层级, + )) + } + + #[classmethod] + fn _缺口后紧急修正( + _cls: &Bound<'_, PyType>, + 线段序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + 层级: i64, + py: Python<'_>, + ) -> PyResult { + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::segment::线段::_缺口后紧急修正( + &mut seg_seq, + &config, + 层级, + )) + } + + #[classmethod] + fn _修正( + _cls: &Bound<'_, PyType>, + 线段序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + 层级: i64, + py: Python<'_>, + ) -> PyResult { + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::algorithm::segment::线段::_修正( + &mut seg_seq, + &config, + 层级, + )) + } + + #[classmethod] + #[pyo3(signature = (笔序列, 线段序列, 配置, 层级 = 0, 关系序列 = None))] + fn 分析( + _cls: &Bound<'_, PyType>, + 笔序列: Vec>, + 线段序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + 层级: i64, + 关系序列: Option>, + py: Python<'_>, + ) -> PyResult<()> { + let bi_list: Vec> = 笔序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + let default_rel = vec![ + chanlun::types::相对方向::向上, + chanlun::types::相对方向::向下, + ]; + let rel_list: Vec = 关系序列 + .map(|v| v.into_iter().map(|d| d.inner).collect()) + .unwrap_or(default_rel); + chanlun::algorithm::segment::线段::分析( + &bi_list, + &mut seg_seq, + &config, + 层级, + &rel_list, + ); + Ok(()) + } + + #[classmethod] + fn _添加扩展线段( + _cls: &Bound<'_, PyType>, + 线段序列: Vec>, + 待添加线段: &Bound<'_, 虚线Py>, + 行号: u32, + py: Python<'_>, + ) -> PyResult<()> { + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::segment::线段::_添加扩展线段( + &mut seg_seq, + Rc::clone(&待添加线段.borrow().inner), + 行号, + ); + Ok(()) + } + + #[classmethod] + fn _弹出扩展线段( + _cls: &Bound<'_, PyType>, + 线段序列: Vec>, + 待弹出线段: &Bound<'_, 虚线Py>, + 行号: u32, + py: Python<'_>, + ) -> PyResult> { + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let result = chanlun::algorithm::segment::线段::_弹出扩展线段( + &mut seg_seq, + &Rc::clone(&待弹出线段.borrow().inner), + 行号, + ); + Ok(result.map(|inner| 虚线Py { inner })) + } + + #[classmethod] + fn 扩展分析( + _cls: &Bound<'_, PyType>, + 虚线序列: Vec>, + 线段序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult<()> { + let dash_list: Vec> = 虚线序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let mut seg_seq: Vec> = 线段序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let config = 配置.borrow().to_rust_config(py)?; + chanlun::algorithm::segment::线段::扩展分析(&dash_list, &mut seg_seq, &config); + Ok(()) + } + + // 判断线段内部是否背驰, 段获取所有停顿位置, 是否背驰过 — deferred to Phase 7 (need observer) +} + +// ========== 中枢 ========== + +#[pyclass(name = "中枢", unsendable)] +#[derive(Clone)] +pub struct 中枢Py { + pub(crate) inner: Rc, +} + +#[pymethods] +impl 中枢Py { + #[new] + fn new( + 序号: i64, 标识: String, 级别: i64, 基础序列: Vec>, py: Python<'_> + ) -> Self { + let rc_list: Vec> = 基础序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + Self { + inner: Rc::new(chanlun::algorithm::hub::中枢::new( + 序号, 标识, 级别, rc_list, + )), + } + } + + #[getter] + fn 序号(&self) -> i64 { + self.inner.序号 + } + + #[getter] + fn 标识(&self) -> String { + self.inner.标识.clone() + } + + #[getter] + fn 级别(&self) -> i64 { + self.inner.级别 + } + + #[getter] + fn 第三买卖线(&self) -> Option<虚线Py> { + self.inner.第三买卖线.as_ref().map(|d| 虚线Py { + inner: Rc::clone(d), + }) + } + + #[getter] + fn 本级_第三买卖线(&self) -> Option<虚线Py> { + self.inner.本级_第三买卖线.as_ref().map(|d| 虚线Py { + inner: Rc::clone(d), + }) + } + + fn 添加虚线(&mut self, 实线: &Bound<'_, 虚线Py>) { + let inner = Rc::make_mut(&mut self.inner); + inner.添加虚线(Rc::clone(&实线.borrow().inner)); + } + + #[getter] + fn 图表标题(&self) -> String { + self.inner.图表标题() + } + + #[getter] + fn 离开段(&self) -> 虚线Py { + 虚线Py { + inner: self.inner.离开段(), + } + } + + #[getter] + fn 方向(&self) -> 相对方向Py { + 相对方向Py { + inner: self.inner.方向(), + } + } + + #[getter] + fn 高(&self) -> f64 { + self.inner.高() + } + + #[getter] + fn 低(&self) -> f64 { + self.inner.低() + } + + #[getter] + fn 高高(&self) -> f64 { + self.inner.高高() + } + + #[getter] + fn 低低(&self) -> f64 { + self.inner.低低() + } + + #[getter] + fn 文(&self) -> 分型Py { + 分型Py { + inner: self.inner.文(), + } + } + + #[getter] + fn 武(&self) -> 分型Py { + 分型Py { + inner: self.inner.武(), + } + } + + fn 设置第三买卖线(&mut self, 线: &Bound<'_, 虚线Py>) { + let inner = Rc::make_mut(&mut self.inner); + inner.设置第三买卖线(Rc::clone(&线.borrow().inner)); + } + + fn 获取序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in self.inner.获取序列() { + list.append(虚线Py { inner: d })?; + } + Ok(list.into()) + } + + fn 获取数据文本(&self) -> String { + self.inner.获取数据文本() + } + + fn 校验合法性(&mut self, 序列: Vec>, py: Python<'_>) -> bool { + let rc_list: Vec> = 序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let inner = Rc::make_mut(&mut self.inner); + inner.校验合法性(&rc_list) + } + + #[pyo3(signature = (虚实 = "合"))] + fn 完整性(&self, 虚实: &str) -> bool { + self.inner.完整性(虚实) + } + + fn 获取扩展中枢(&self, 扩展中枢: Vec>, py: Python<'_>) -> PyResult<()> { + let mut hub_seq: Vec> = 扩展中枢 + .iter() + .map(|h| Rc::clone(&h.bind(py).borrow().inner)) + .collect(); + // Need config for this call — use default + let config = chanlun::config::缠论配置::default(); + self.inner.获取扩展中枢(&mut hub_seq, &config); + Ok(()) + } + + #[getter] + fn 当前状态(&self) -> String { + self.inner.当前状态().to_string() + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + // ---- classmethods ---- + + #[classmethod] + fn 基础检查( + _cls: &Bound<'_, PyType>, + 左: &Bound<'_, 虚线Py>, + 中: &Bound<'_, 虚线Py>, + 右: &Bound<'_, 虚线Py>, + ) -> bool { + chanlun::algorithm::hub::中枢::基础检查( + &左.borrow().inner, + &中.borrow().inner, + &右.borrow().inner, + ) + } + + #[classmethod] + #[pyo3(signature = (左, 中, 右, 级别, 标识 = ""))] + fn 创建( + _cls: &Bound<'_, PyType>, + 左: &Bound<'_, 虚线Py>, + 中: &Bound<'_, 虚线Py>, + 右: &Bound<'_, 虚线Py>, + 级别: i64, + 标识: &str, + ) -> Self { + Self { + inner: Rc::new(chanlun::algorithm::hub::中枢::创建( + Rc::clone(&左.borrow().inner), + Rc::clone(&中.borrow().inner), + Rc::clone(&右.borrow().inner), + 级别, + 标识, + )), + } + } + + #[classmethod] + fn 从序列中获取中枢( + _cls: &Bound<'_, PyType>, + 虚线序列: Vec>, + 起始方向: &Bound<'_, 相对方向Py>, + 标识: &str, + py: Python<'_>, + ) -> Option { + let rc_list: Vec> = 虚线序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::hub::中枢::从序列中获取中枢( + &rc_list, + 起始方向.borrow().inner, + 标识, + ) + .map(|inner| Self { inner }) + } + + #[classmethod] + fn 向中枢序列尾部添加( + _cls: &Bound<'_, PyType>, + 中枢序列: &Bound<'_, PyAny>, + 待添加中枢: &Bound<'_, Self>, + ) -> PyResult<()> { + let inner = Rc::clone(&待添加中枢.borrow().inner); + let wrapper = Py::new(中枢序列.py(), Self { inner })?; + 中枢序列.call_method1("append", (wrapper,))?; + Ok(()) + } + + #[classmethod] + fn 从中枢序列尾部弹出( + _cls: &Bound<'_, PyType>, + 中枢序列: &Bound<'_, PyAny>, + 待弹出中枢: &Bound<'_, Self>, + ) -> PyResult> { + let result = 中枢序列.call_method1("pop", ())?; + if result.is_none() { + return Ok(None); + } + let bound: Bound<'_, Self> = result.extract()?; + Ok(Some(Self { + inner: Rc::clone(&bound.borrow().inner), + })) + } + + #[classmethod] + #[pyo3(signature = (虚线序列, 中枢序列, 跳过首部 = true, 标识 = "", 层级 = 0))] + fn 分析( + _cls: &Bound<'_, PyType>, + 虚线序列: Vec>, + 中枢序列: Vec>, + 跳过首部: bool, + 标识: &str, + 层级: i64, + py: Python<'_>, + ) -> PyResult<()> { + let rc_list: Vec> = 虚线序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + let mut hub_seq: Vec> = 中枢序列 + .iter() + .map(|h| Rc::clone(&h.bind(py).borrow().inner)) + .collect(); + chanlun::algorithm::hub::中枢::分析(&rc_list, &mut hub_seq, 跳过首部, 标识, 层级); + Ok(()) + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::<背驰分析Py>()?; + m.add_class::<笔Py>()?; + m.add_class::<线段Py>()?; + m.add_class::<中枢Py>()?; + Ok(()) +} diff --git a/chanlun-py/src/business_py.rs b/chanlun-py/src/business_py.rs new file mode 100644 index 0000000..1c96980 --- /dev/null +++ b/chanlun-py/src/business_py.rs @@ -0,0 +1,579 @@ +use pyo3::prelude::*; +use pyo3::types::PyType; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::algorithm_py::中枢Py; +use crate::config_py::缠论配置Py; +use crate::kline_py::{缠论K线Py, K线Py}; +use crate::structure_py::{分型Py, 虚线Py}; +use crate::types_py::买卖点类型Py; + +// ========== 基础买卖点 ========== + +#[pyclass(name = "基础买卖点", unsendable)] +#[derive(Clone)] +pub struct 基础买卖点Py { + pub(crate) inner: chanlun::business::bsp::基础买卖点, +} + +#[pymethods] +impl 基础买卖点Py { + #[new] + fn new( + 类型: &Bound<'_, 买卖点类型Py>, + 当前K线: &Bound<'_, K线Py>, + 买卖点分型: &Bound<'_, 分型Py>, + 备注: String, + 中枢破位值: f64, + ) -> Self { + Self { + inner: chanlun::business::bsp::基础买卖点::new( + 类型.borrow().inner, + Rc::new(当前K线.borrow().inner.clone()), + Rc::clone(&买卖点分型.borrow().inner), + 备注, + 中枢破位值, + ), + } + } + + #[getter] + fn 备注(&self) -> String { + self.inner.备注.clone() + } + + #[getter] + fn 类型(&self) -> 买卖点类型Py { + 买卖点类型Py { + inner: self.inner.类型, + } + } + + #[getter] + fn 买卖点分型(&self) -> 分型Py { + 分型Py { + inner: Rc::clone(&self.inner.买卖点分型), + } + } + + #[getter] + fn 买卖点K线(&self) -> 缠论K线Py { + 缠论K线Py { + inner: Rc::clone(&self.inner.买卖点K线), + } + } + + #[getter] + fn 当前K线(&self) -> K线Py { + K线Py { + inner: (*self.inner.当前K线).clone(), + } + } + + #[getter] + fn 失效K线(&self) -> Option { + self.inner.失效K线.as_ref().map(|k| K线Py { + inner: (**k).clone(), + }) + } + + #[getter] + fn 终结K线(&self) -> Option { + self.inner.终结K线.as_ref().map(|k| K线Py { + inner: (**k).clone(), + }) + } + + #[getter] + fn 破位值(&self) -> f64 { + self.inner.破位值 + } + + #[getter] + fn 结构(&self) -> Option { + self.inner + .结构 + .map(|f| crate::types_py::分型结构Py { inner: f }) + } + + fn 偏移(&self) -> i64 { + self.inner.偏移() + } + + fn 失效偏移(&self) -> i64 { + self.inner.失效偏移() + } + + #[getter] + fn 有效性(&self) -> bool { + self.inner.有效性() + } + + #[getter] + fn 与MACD柱子匹配(&self) -> bool { + self.inner.与MACD柱子匹配() + } + + #[getter] + fn 与MACD柱子分型匹配(&self) -> bool { + self.inner.与MACD柱子分型匹配() + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } +} + +// ========== 买卖点 ========== + +#[pyclass(name = "买卖点")] +pub struct 买卖点Py; + +#[pymethods] +impl 买卖点Py { + #[classmethod] + fn 一卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::一卖点( + Rc::clone(&买卖点分型.borrow().inner), + Rc::new(当前K线.borrow().inner.clone()), + 标识, + 备注, + 中枢破位值, + ), + } + } + + #[classmethod] + fn 一买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::一买点( + Rc::clone(&买卖点分型.borrow().inner), + Rc::new(当前K线.borrow().inner.clone()), + 标识, + 备注, + 中枢破位值, + ), + } + } + + #[classmethod] + fn 二卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::二卖点( + Rc::clone(&买卖点分型.borrow().inner), + Rc::new(当前K线.borrow().inner.clone()), + 标识, + 备注, + 中枢破位值, + ), + } + } + + #[classmethod] + fn 二买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::二买点( + Rc::clone(&买卖点分型.borrow().inner), + Rc::new(当前K线.borrow().inner.clone()), + 标识, + 备注, + 中枢破位值, + ), + } + } + + #[classmethod] + fn 三卖点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::三卖点( + Rc::clone(&买卖点分型.borrow().inner), + Rc::new(当前K线.borrow().inner.clone()), + 标识, + 备注, + 中枢破位值, + ), + } + } + + #[classmethod] + fn 三买点( + _cls: &Bound<'_, PyType>, + 买卖点分型: &Bound<'_, 分型Py>, + 当前K线: &Bound<'_, K线Py>, + 标识: &str, + 备注: String, + 中枢破位值: f64, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::三买点( + Rc::clone(&买卖点分型.borrow().inner), + Rc::new(当前K线.borrow().inner.clone()), + 标识, + 备注, + 中枢破位值, + ), + } + } + + #[classmethod] + fn 生成买卖点( + _cls: &Bound<'_, PyType>, + 特征: &str, + 序号: &str, + 级别: &str, + 买卖点分型: &Bound<'_, 分型Py>, + 当前缠K: &Bound<'_, 缠论K线Py>, + ) -> 基础买卖点Py { + 基础买卖点Py { + inner: chanlun::business::bsp::买卖点::生成买卖点( + 特征, + 序号, + 级别, + Rc::clone(&买卖点分型.borrow().inner), + Rc::clone(&当前缠K.borrow().inner), + ), + } + } +} + +// ========== 观察者 ========== + +#[pyclass(name = "观察者", unsendable)] +pub struct 观察者Py { + pub(crate) inner: chanlun::business::observer::观察者, +} + +#[pymethods] +impl 观察者Py { + #[new] + #[pyo3(signature = (符号, 周期, 配置 = None))] + fn new( + 符号: String, + 周期: i64, + 配置: Option<&Bound<'_, 缠论配置Py>>, + py: Python<'_>, + ) -> PyResult { + let config = match 配置 { + Some(cfg) => cfg.borrow().to_rust_config(py)?, + None => chanlun::config::缠论配置::default(), + }; + Ok(Self { + inner: chanlun::business::observer::观察者::new(符号, 周期, config), + }) + } + + #[getter] + fn 标识(&self) -> String { + self.inner.标识() + } + + #[getter] + fn 当前K线(&self) -> Option { + self.inner.当前K线().map(|k| K线Py { + inner: (**k).clone(), + }) + } + + #[getter] + fn 当前缠K(&self) -> Option<缠论K线Py> { + self.inner.当前缠K().map(|k| 缠论K线Py { + inner: Rc::clone(k), + }) + } + + #[getter] + fn 符号(&self) -> String { + self.inner.符号.clone() + } + + #[getter] + fn 周期(&self) -> i64 { + self.inner.周期 + } + + fn 重置基础序列(&mut self) { + self.inner.重置基础序列(); + } + + fn 增加原始K线(&mut self, 普K: &Bound<'_, K线Py>) { + self.inner.增加原始K线(普K.borrow().inner.clone()); + } + + fn 静态重新分析(&mut self) { + self.inner.静态重新分析(); + } + + fn 测试_保存数据(&self, root: Option<&str>) { + self.inner.测试_保存数据(root); + } + + #[classmethod] + fn 读取数据文件( + _cls: &Bound<'_, PyType>, + 文件路径: &str, + 配置: Option<&Bound<'_, 缠论配置Py>>, + py: Python<'_>, + ) -> PyResult { + let config = match 配置 { + Some(cfg) => Some(cfg.borrow().to_rust_config(py)?), + None => None, + }; + chanlun::business::observer::观察者::读取数据文件(文件路径, config) + .map(|inner| Self { inner }) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e)) + } + + // ---- 序列 getters ---- + + #[getter] + fn 普通K线序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for k in &self.inner.普通K线序列 { + list.append(K线Py { + inner: (**k).clone(), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 缠论K线序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for k in &self.inner.缠论K线序列 { + list.append(缠论K线Py { + inner: Rc::clone(k), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 分型序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for f in &self.inner.分型序列 { + list.append(分型Py { + inner: Rc::clone(f), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 笔序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in &self.inner.笔序列 { + list.append(虚线Py { + inner: Rc::clone(d), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 笔_中枢序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for h in &self.inner.笔_中枢序列 { + list.append(中枢Py { + inner: Rc::clone(h), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 线段序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in &self.inner.线段序列 { + list.append(虚线Py { + inner: Rc::clone(d), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 中枢序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for h in &self.inner.中枢序列 { + list.append(中枢Py { + inner: Rc::clone(h), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 扩展线段序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in &self.inner.扩展线段序列 { + list.append(虚线Py { + inner: Rc::clone(d), + })?; + } + Ok(list.into()) + } + + #[getter] + fn 扩展中枢序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for h in &self.inner.扩展中枢序列 { + list.append(中枢Py { + inner: Rc::clone(h), + })?; + } + Ok(list.into()) + } +} + +// ========== K线合成器 ========== + +#[pyclass(name = "K线合成器", unsendable)] +pub struct K线合成器Py { + pub(crate) inner: chanlun::business::synthesizer::K线合成器, +} + +#[pymethods] +impl K线合成器Py { + #[new] + fn new(标识: String, 周期组: Vec) -> Self { + Self { + inner: chanlun::business::synthesizer::K线合成器::new(标识, 周期组), + } + } + + fn 投喂K线( + &mut self, + 普K: &Bound<'_, K线Py>, + py: Python<'_>, + ) -> PyResult> { + let results = self.inner.投喂K线(普K.borrow().inner.clone()); + Ok(results + .into_iter() + .map(|(周期, k)| (周期, K线Py { inner: k })) + .collect()) + } + + fn 获取当前K线(&self, 周期: i64) -> Option { + self.inner + .获取当前K线(周期) + .map(|k| K线Py { inner: k.clone() }) + } + + #[getter] + fn 标识(&self) -> String { + self.inner.标识.clone() + } + + #[getter] + fn 周期组(&self) -> Vec { + self.inner.周期组.clone() + } +} + +// ========== 立体分析器 ========== + +#[pyclass(name = "立体分析器", unsendable)] +pub struct 立体分析器Py { + pub(crate) inner: chanlun::business::multi_frame::立体分析器, +} + +#[pymethods] +impl 立体分析器Py { + #[new] + #[pyo3(signature = (符号, 周期组, 配置 = None, 配置组 = None))] + fn new( + 符号: String, + 周期组: Vec, + 配置: Option<&Bound<'_, 缠论配置Py>>, + 配置组: Option<&Bound<'_, PyAny>>, + py: Python<'_>, + ) -> PyResult { + let config = match 配置 { + Some(cfg) => Some(cfg.borrow().to_rust_config(py)?), + None => None, + }; + let cfg_map: Option> = match 配置组 { + Some(dict_any) => { + let dict = dict_any.downcast::()?; + let mut map = HashMap::new(); + for (key, value) in dict.iter() { + let period: i64 = key.extract()?; + let cfg: PyRef<'_, 缠论配置Py> = value.extract()?; + map.insert(period, cfg.to_rust_config(py)?); + } + Some(map) + } + None => None, + }; + Ok(Self { + inner: chanlun::business::multi_frame::立体分析器::new( + 符号, 周期组, config, cfg_map, + ), + }) + } + + fn 投喂K线(&mut self, 普K: &Bound<'_, K线Py>) { + self.inner.投喂K线(普K.borrow().inner.clone()); + } + + // 获取观察者 deferred — 观察者 doesn't implement Clone, needs core change + + fn 测试_保存数据(&self) { + self.inner.测试_保存数据(); + } + + #[getter] + fn 周期组(&self) -> Vec { + self.inner.周期组.clone() + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::<基础买卖点Py>()?; + m.add_class::<买卖点Py>()?; + m.add_class::<观察者Py>()?; + m.add_class::()?; + m.add_class::<立体分析器Py>()?; + Ok(()) +} diff --git a/chanlun-py/src/config_py.rs b/chanlun-py/src/config_py.rs new file mode 100644 index 0000000..e0f67fa --- /dev/null +++ b/chanlun-py/src/config_py.rs @@ -0,0 +1,222 @@ +use pyo3::prelude::*; +use pyo3::types::{PyDict, PyType}; +use std::collections::HashMap; + +/// 缠论配置 — Python binding +#[pyclass(name = "缠论配置")] +pub struct 缠论配置Py { + fields: HashMap>, +} + +#[pymethods] +impl 缠论配置Py { + #[new] + #[pyo3(signature = (**kwargs))] + fn new(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult { + let default_config = chanlun::config::缠论配置::default(); + let mut fields = config_to_field_dict(&default_config)?; + + 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 { + return Err(pyo3::exceptions::PyAttributeError::new_err(format!( + "缠论配置 没有字段: {key}" + ))); + } + } + } + + Ok(Self { fields }) + } + + fn __getattr__(&self, name: &str, py: Python<'_>) -> PyResult> { + match self.fields.get(name) { + Some(v) => Ok(v.clone_ref(py)), + None => Err(pyo3::exceptions::PyAttributeError::new_err(format!( + "缠论配置 没有字段: {name}" + ))), + } + } + + 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(()) + } else { + Err(pyo3::exceptions::PyAttributeError::new_err(format!( + "缠论配置 没有字段: {name}" + ))) + } + } + + fn __str__(&self) -> String { + format!("缠论配置({} fields)", self.fields.len()) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + fn to_dict(&self, py: Python<'_>) -> PyResult> { + let dict = PyDict::new(py); + for (k, v) in &self.fields { + dict.set_item(k, v.clone_ref(py))?; + } + Ok(dict.into()) + } + + fn to_json(&self, py: Python<'_>) -> PyResult { + let dict = self.to_dict(py)?; + let json_mod = py.import("json")?; + let dumps = json_mod.getattr("dumps")?; + dumps.call1((dict,))?.extract() + } + + fn 保存配置(&self, py: Python<'_>, path: Option<&str>) -> PyResult<()> { + let path = path.unwrap_or("缠论配置.json"); + let json = self.to_json(py)?; + std::fs::write(path, json).map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string())) + } + + #[classmethod] + fn 加载配置( + _cls: &Bound<'_, PyType>, + py: Python<'_>, + path: Option<&str>, + ) -> PyResult { + let path = path.unwrap_or("缠论配置.json"); + let json_str = std::fs::read_to_string(path) + .map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string()))?; + Self::from_json_str(py, &json_str) + } + + #[classmethod] + fn from_dict(_cls: &Bound<'_, PyType>, data: &Bound<'_, PyDict>) -> PyResult { + let default_config = chanlun::config::缠论配置::default(); + let mut fields = config_to_field_dict(&default_config)?; + + for (key, value) in data.iter() { + let key: String = key.extract()?; + fields.insert(key, value.clone().unbind()); + } + + dict_to_rust_config(&fields)?; + Ok(Self { fields }) + } + + #[classmethod] + fn from_json(_cls: &Bound<'_, PyType>, py: Python<'_>, json_str: &str) -> PyResult { + Self::from_json_str(py, json_str) + } + + #[classmethod] + fn 不推送(_cls: &Bound<'_, PyType>) -> PyResult { + let config = chanlun::config::缠论配置::default().不推送(); + let fields = config_to_field_dict(&config)?; + Ok(Self { fields }) + } + + #[classmethod] + fn 按序号重组字典( + _cls: &Bound<'_, PyType>, + 默认配置: &Bound<'_, PyAny>, + 原始字典: &Bound<'_, PyDict>, + ) -> PyResult> { + let py = 原始字典.py(); + let result = PyDict::new(py); + if let Ok(default_dict) = 默认配置.downcast::() { + for (key, value) in default_dict.iter() { + if 原始字典.contains(&key)? { + result.set_item(key.clone(), 原始字典.get_item(&key)?)?; + } else { + result.set_item(key, value)?; + } + } + } + Ok(result.into()) + } + + fn 对比( + &self, + py: Python<'_>, + other: &Bound<'_, 缠论配置Py>, + ) -> PyResult, Py)>> { + let other_ref = other.borrow(); + let mut diff = HashMap::new(); + for (key, val) in &self.fields { + if let Some(other_val) = other_ref.fields.get(key) { + let a = val.clone_ref(py); + let b = other_val.clone_ref(py); + let eq = a.bind(py).eq(b.bind(py))?; + if !eq { + diff.insert(key.clone(), (val.clone_ref(py), other_val.clone_ref(py))); + } + } + } + Ok(diff) + } +} + +impl 缠论配置Py { + fn from_json_str(py: Python<'_>, json_str: &str) -> PyResult { + let json_mod = py.import("json")?; + let loads = json_mod.getattr("loads")?; + let data: Bound<'_, PyDict> = loads.call1((json_str,))?.extract()?; + + let mut fields: HashMap> = HashMap::new(); + for (key, value) in data.iter() { + let key: String = key.extract()?; + fields.insert(key, value.clone().unbind()); + } + + dict_to_rust_config(&fields)?; + Ok(Self { fields }) + } + + pub(crate) fn to_rust_config(&self, py: Python<'_>) -> PyResult { + dict_to_rust_config(&self.fields) + } +} + +fn config_to_field_dict( + config: &chanlun::config::缠论配置, +) -> PyResult>> { + Python::attach(|py| { + let json_str = serde_json::to_string(config) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e.to_string()))?; + let json_mod = py.import("json")?; + let loads = json_mod.getattr("loads")?; + let data: Bound<'_, PyDict> = loads.call1((json_str,))?.extract()?; + + let mut fields = HashMap::new(); + for (key, value) in data.iter() { + let key: String = key.extract()?; + fields.insert(key, value.clone().unbind()); + } + Ok(fields) + }) +} + +fn dict_to_rust_config( + fields: &HashMap>, +) -> PyResult { + Python::attach(|py| { + let json_mod = py.import("json")?; + let dict = PyDict::new(py); + for (k, v) in fields { + dict.set_item(k, v.clone_ref(py))?; + } + let dumps = json_mod.getattr("dumps")?; + let json_str: String = dumps.call1((dict,))?.extract()?; + serde_json::from_str(&json_str) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("配置转换失败: {e}"))) + }) +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::<缠论配置Py>()?; + Ok(()) +} diff --git a/chanlun-py/src/indicators_py.rs b/chanlun-py/src/indicators_py.rs new file mode 100644 index 0000000..c76cad3 --- /dev/null +++ b/chanlun-py/src/indicators_py.rs @@ -0,0 +1,576 @@ +use pyo3::prelude::*; +use pyo3::types::PyType; + +// ========== 平滑异同移动平均线 ========== + +#[pyclass(name = "平滑异同移动平均线")] +#[derive(Clone)] +pub struct 平滑异同移动平均线Py { + pub(crate) inner: chanlun::indicators::平滑异同移动平均线, +} + +#[pymethods] +impl 平滑异同移动平均线Py { + #[new] + fn new() -> Self { + unimplemented!("使用 首次计算 或 增量计算 创建") + } + + #[getter] + fn 时间戳(&self) -> String { + self.inner.时间戳.to_string() + } + + #[getter] + fn 收盘价(&self) -> f64 { + self.inner.收盘价 + } + + #[getter] + fn 快线周期(&self) -> i64 { + self.inner.快线周期 + } + + #[getter] + fn 慢线周期(&self) -> i64 { + self.inner.慢线周期 + } + + #[getter] + fn 信号周期(&self) -> i64 { + self.inner.信号周期 + } + + #[getter] + fn DIF(&self) -> Option { + self.inner.DIF + } + + #[getter] + fn DEA(&self) -> Option { + self.inner.DEA + } + + #[getter] + fn MACD柱(&self) -> f64 { + self.inner.MACD柱 + } + + #[getter] + fn 快线EMA(&self) -> Option { + self.inner.快线EMA + } + + #[getter] + fn 慢线EMA(&self) -> Option { + self.inner.慢线EMA + } + + #[getter] + fn DEA_EMA(&self) -> Option { + self.inner.DEA_EMA + } + + fn __str__(&self) -> String { + format!( + "平滑异同移动平均线(DIF={:?}, DEA={:?}, BAR={:?})", + self.inner.DIF, self.inner.DEA, self.inner.MACD柱 + ) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + #[classmethod] + #[pyo3(signature = (初始收盘价, 初始时间, 快线周期 = None, 慢线周期 = None, 信号周期 = None))] + fn 首次计算( + _cls: &Bound<'_, PyType>, + 初始收盘价: f64, + 初始时间: i64, + 快线周期: Option, + 慢线周期: Option, + 信号周期: Option, + ) -> Self { + let inner = chanlun::indicators::平滑异同移动平均线::首次计算( + 初始收盘价, + 初始时间, + 快线周期.unwrap_or(12), + 慢线周期.unwrap_or(26), + 信号周期.unwrap_or(9), + ); + Self { inner } + } + + #[classmethod] + #[pyo3(signature = (k线, 计算方式, 快线周期 = None, 慢线周期 = None, 信号周期 = None))] + fn 首次计算_K线( + _cls: &Bound<'_, PyType>, + k线: &Bound<'_, PyAny>, + 计算方式: &str, + 快线周期: Option, + 慢线周期: Option, + 信号周期: Option, + ) -> PyResult { + let 计算值 = crate::indicators_py::K线取值(k线, 计算方式)?; + Ok(Self { + inner: chanlun::indicators::平滑异同移动平均线::首次计算( + 计算值, + 获取时间戳(k线)?, + 快线周期.unwrap_or(12), + 慢线周期.unwrap_or(26), + 信号周期.unwrap_or(9), + ), + }) + } + + #[classmethod] + fn 增量计算( + _cls: &Bound<'_, PyType>, + 前一个MACD: &Bound<'_, 平滑异同移动平均线Py>, + 当前收盘价: f64, + 当前时间: i64, + ) -> Self { + let inner = chanlun::indicators::平滑异同移动平均线::增量计算( + &前一个MACD.borrow().inner, + 当前收盘价, + 当前时间, + ); + Self { inner } + } + + #[classmethod] + fn 增量计算_K线( + _cls: &Bound<'_, PyType>, + 前一个MACD: &Bound<'_, 平滑异同移动平均线Py>, + 当前K线: &Bound<'_, PyAny>, + 计算方式: &str, + ) -> PyResult { + let 计算值 = K线取值(当前K线, 计算方式)?; + Ok(Self { + inner: chanlun::indicators::平滑异同移动平均线::增量计算( + &前一个MACD.borrow().inner, + 计算值, + 获取时间戳(当前K线)?, + ), + }) + } +} + +// ========== 相对强弱指数 ========== + +#[pyclass(name = "相对强弱指数")] +#[derive(Clone)] +pub struct 相对强弱指数Py { + pub(crate) inner: chanlun::indicators::相对强弱指数, +} + +#[pymethods] +impl 相对强弱指数Py { + #[new] + fn new() -> Self { + unimplemented!("使用 首次计算 或 增量计算 创建") + } + + #[getter] + fn 时间戳(&self) -> String { + self.inner.时间戳.to_string() + } + #[getter] + fn 收盘价(&self) -> f64 { + self.inner.收盘价 + } + #[getter] + fn 周期(&self) -> i64 { + self.inner.周期 + } + #[getter] + fn 超买阈值(&self) -> f64 { + self.inner.超买阈值 + } + #[getter] + fn 超卖阈值(&self) -> f64 { + self.inner.超卖阈值 + } + #[getter] + fn RSI_SMA周期(&self) -> Option { + self.inner.RSI_SMA周期 + } + #[getter] + fn RSI(&self) -> Option { + self.inner.RSI + } + #[getter] + fn 平均上涨(&self) -> Option { + self.inner.平均上涨 + } + #[getter] + fn 平均下跌(&self) -> Option { + self.inner.平均下跌 + } + #[getter] + fn 上涨幅度(&self) -> f64 { + self.inner.上涨幅度 + } + #[getter] + fn 下跌幅度(&self) -> f64 { + self.inner.下跌幅度 + } + #[getter] + fn 平滑系数(&self) -> f64 { + self.inner.平滑系数 + } + #[getter] + fn RSI_SMA(&self) -> Option { + self.inner.RSI_SMA + } + #[getter] + fn RSI历史队列(&self) -> Vec { + self.inner.RSI历史队列.clone() + } + + fn __str__(&self) -> String { + format!("相对强弱指数(RSI={:?})", self.inner.RSI) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + #[classmethod] + #[pyo3(signature = (初始收盘价, 初始时间, 周期 = None, 超买阈值 = None, 超卖阈值 = None, RSI_SMA周期 = None))] + fn 首次计算( + _cls: &Bound<'_, PyType>, + 初始收盘价: f64, + 初始时间: i64, + 周期: Option, + 超买阈值: Option, + 超卖阈值: Option, + RSI_SMA周期: Option, + ) -> Self { + Self { + inner: chanlun::indicators::相对强弱指数::首次计算( + 初始收盘价, + 初始时间, + 周期.unwrap_or(14), + 超买阈值.unwrap_or(70.0), + 超卖阈值.unwrap_or(30.0), + RSI_SMA周期, + ), + } + } + + #[classmethod] + #[pyo3(signature = (k线, 计算方式, 周期 = None, 超买阈值 = None, 超卖阈值 = None, RSI_SMA周期 = None))] + fn 首次计算_K线( + _cls: &Bound<'_, PyType>, + k线: &Bound<'_, PyAny>, + 计算方式: &str, + 周期: Option, + 超买阈值: Option, + 超卖阈值: Option, + RSI_SMA周期: Option, + ) -> PyResult { + let 计算值 = K线取值(k线, 计算方式)?; + Ok(Self { + inner: chanlun::indicators::相对强弱指数::首次计算( + 计算值, + 获取时间戳(k线)?, + 周期.unwrap_or(14), + 超买阈值.unwrap_or(70.0), + 超卖阈值.unwrap_or(30.0), + RSI_SMA周期, + ), + }) + } + + #[classmethod] + fn 增量计算( + _cls: &Bound<'_, PyType>, + 前一个RSI: &Bound<'_, 相对强弱指数Py>, + 当前收盘价: f64, + 当前时间: i64, + ) -> Self { + Self { + inner: chanlun::indicators::相对强弱指数::增量计算( + &前一个RSI.borrow().inner, + 当前收盘价, + 当前时间, + ), + } + } + + #[classmethod] + fn 增量计算_K线( + _cls: &Bound<'_, PyType>, + 前一个RSI: &Bound<'_, 相对强弱指数Py>, + 当前K线: &Bound<'_, PyAny>, + 计算方式: &str, + ) -> PyResult { + let 计算值 = K线取值(当前K线, 计算方式)?; + Ok(Self { + inner: chanlun::indicators::相对强弱指数::增量计算( + &前一个RSI.borrow().inner, + 计算值, + 获取时间戳(当前K线)?, + ), + }) + } +} + +// ========== 随机指标 ========== + +#[pyclass(name = "随机指标")] +#[derive(Clone)] +pub struct 随机指标Py { + pub(crate) inner: chanlun::indicators::随机指标, +} + +#[pymethods] +impl 随机指标Py { + #[new] + fn new() -> Self { + unimplemented!("使用 首次计算 或 增量计算 创建") + } + + #[getter] + fn 时间戳(&self) -> String { + self.inner.时间戳.to_string() + } + #[getter] + fn 最高价(&self) -> f64 { + self.inner.最高价 + } + #[getter] + fn 最低价(&self) -> f64 { + self.inner.最低价 + } + #[getter] + fn 收盘价(&self) -> f64 { + self.inner.收盘价 + } + #[getter] + fn N(&self) -> i64 { + self.inner.N + } + #[getter] + fn M1(&self) -> i64 { + self.inner.M1 + } + #[getter] + fn M2(&self) -> i64 { + self.inner.M2 + } + #[getter] + fn 超买阈值(&self) -> f64 { + self.inner.超买阈值 + } + #[getter] + fn 超卖阈值(&self) -> f64 { + self.inner.超卖阈值 + } + #[getter] + fn RSV(&self) -> Option { + self.inner.RSV + } + #[getter] + fn K(&self) -> Option { + self.inner.K + } + #[getter] + fn D(&self) -> Option { + self.inner.D + } + #[getter] + fn J(&self) -> Option { + self.inner.J + } + #[getter] + fn 历史最高价队列(&self) -> Vec { + self.inner.历史最高价队列.clone() + } + #[getter] + fn 历史最低价队列(&self) -> Vec { + self.inner.历史最低价队列.clone() + } + #[getter] + fn 前一个RSV(&self) -> Option { + self.inner.前一个RSV + } + #[getter] + fn 前一个K(&self) -> Option { + self.inner.前一个K + } + #[getter] + fn 前一个D(&self) -> Option { + self.inner.前一个D + } + + fn __str__(&self) -> String { + format!( + "随机指标(K={:?}, D={:?}, J={:?})", + self.inner.K, self.inner.D, self.inner.J + ) + } + fn __repr__(&self) -> String { + self.__str__() + } + + #[classmethod] + #[pyo3(signature = (初始最高价, 初始最低价, 初始收盘价, 初始时间, N = None, M1 = None, M2 = None, 超买阈值 = None, 超卖阈值 = None))] + fn 首次计算( + _cls: &Bound<'_, PyType>, + 初始最高价: f64, + 初始最低价: f64, + 初始收盘价: f64, + 初始时间: i64, + N: Option, + M1: Option, + M2: Option, + 超买阈值: Option, + 超卖阈值: Option, + ) -> Self { + Self { + inner: chanlun::indicators::随机指标::首次计算( + 初始最高价, + 初始最低价, + 初始收盘价, + 初始时间, + N.unwrap_or(9), + M1.unwrap_or(3), + M2.unwrap_or(3), + 超买阈值.unwrap_or(80.0), + 超卖阈值.unwrap_or(20.0), + ), + } + } + + #[classmethod] + #[pyo3(signature = (k线, _计算方式, RSV周期 = None, K值平滑周期 = None, D值平滑周期 = None, 超买阈值 = None, 超卖阈值 = None))] + fn 首次计算_K线( + _cls: &Bound<'_, PyType>, + k线: &Bound<'_, PyAny>, + _计算方式: &str, + RSV周期: Option, + K值平滑周期: Option, + D值平滑周期: Option, + 超买阈值: Option, + 超卖阈值: Option, + ) -> PyResult { + let 收盘价: f64 = k线.getattr("收盘价")?.extract()?; + let 最高价: f64 = k线.getattr("高")?.extract()?; + let 最低价: f64 = k线.getattr("低")?.extract()?; + Ok(Self { + inner: chanlun::indicators::随机指标::首次计算( + 最高价, + 最低价, + 收盘价, + 获取时间戳(k线)?, + RSV周期.unwrap_or(9), + K值平滑周期.unwrap_or(3), + D值平滑周期.unwrap_or(3), + 超买阈值.unwrap_or(80.0), + 超卖阈值.unwrap_or(20.0), + ), + }) + } + + #[classmethod] + fn 增量计算( + _cls: &Bound<'_, PyType>, + 前一个KDJ: &Bound<'_, 随机指标Py>, + 当前最高价: f64, + 当前最低价: f64, + 当前收盘价: f64, + 当前时间: i64, + ) -> Self { + Self { + inner: chanlun::indicators::随机指标::增量计算( + &前一个KDJ.borrow().inner, + 当前最高价, + 当前最低价, + 当前收盘价, + 当前时间, + ), + } + } + + #[classmethod] + fn 增量计算_K线( + _cls: &Bound<'_, PyType>, + 前一个KDJ: &Bound<'_, 随机指标Py>, + 当前K线: &Bound<'_, PyAny>, + _计算方式: &str, + ) -> PyResult { + let 收盘价: f64 = 当前K线.getattr("收盘价")?.extract()?; + let 最高价: f64 = 当前K线.getattr("高")?.extract()?; + let 最低价: f64 = 当前K线.getattr("低")?.extract()?; + Ok(Self { + inner: chanlun::indicators::随机指标::增量计算( + &前一个KDJ.borrow().inner, + 最高价, + 最低价, + 收盘价, + 获取时间戳(当前K线)?, + ), + }) + } +} + +// ========== 指标 (static namespace) ========== + +#[pyclass(name = "指标")] +pub struct 指标Py; + +#[pymethods] +impl 指标Py { + #[classmethod] + fn K线取值( + _cls: &Bound<'_, PyType>, + k线: &Bound<'_, PyAny>, + 指标计算方式: &str, + ) -> PyResult { + K线取值(k线, 指标计算方式) + } +} + +// ========== Helper functions ========== + +pub(crate) fn K线取值(k线: &Bound<'_, PyAny>, 计算方式: &str) -> PyResult { + let 开盘: f64 = k线.getattr("开盘价")?.extract()?; + let 高: f64 = k线.getattr("高")?.extract()?; + let 低: f64 = k线.getattr("低")?.extract()?; + let 收盘: f64 = k线.getattr("收盘价")?.extract()?; + Ok(chanlun::indicators::K线取值( + 开盘, + 高, + 低, + 收盘, + 计算方式, + )) +} + +fn 获取时间戳(k线: &Bound<'_, PyAny>) -> PyResult { + let ts = k线.getattr("时间戳")?; + if let Ok(i) = ts.extract::() { + return Ok(i); + } + // Try float + if let Ok(f) = ts.extract::() { + return Ok(f as i64); + } + // Try calling .timestamp() if it's a datetime + if let Ok(method) = ts.getattr("timestamp") { + let result: f64 = method.call0()?.extract()?; + return Ok(result as i64); + } + Err(pyo3::exceptions::PyTypeError::new_err("无法获取时间戳")) +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::<平滑异同移动平均线Py>()?; + m.add_class::<相对强弱指数Py>()?; + m.add_class::<随机指标Py>()?; + m.add_class::<指标Py>()?; + Ok(()) +} diff --git a/chanlun-py/src/kline_py.rs b/chanlun-py/src/kline_py.rs new file mode 100644 index 0000000..c1d9212 --- /dev/null +++ b/chanlun-py/src/kline_py.rs @@ -0,0 +1,498 @@ +use pyo3::prelude::*; +use pyo3::types::{PyBytes, PyType}; +use std::collections::HashMap; + +use crate::config_py::缠论配置Py; +use crate::indicators_py::{平滑异同移动平均线Py, 相对强弱指数Py, 随机指标Py}; +use crate::types_py::相对方向Py; + +// ========== K线 ========== + +#[pyclass(name = "K线")] +#[derive(Clone)] +pub struct K线Py { + pub(crate) inner: chanlun::kline::bar::K线, +} + +#[pymethods] +impl K线Py { + #[new] + #[pyo3(signature = (标识 = "bar", 序号 = 0, 周期 = 60, 时间戳 = 0, 高 = 0.0, 低 = 0.0, 开盘价 = 0.0, 收盘价 = 0.0, 成交量 = 0.0))] + fn new( + 标识: &str, + 序号: i64, + 周期: i64, + 时间戳: i64, + 高: f64, + 低: f64, + 开盘价: f64, + 收盘价: f64, + 成交量: f64, + ) -> Self { + Self { + inner: chanlun::kline::bar::K线 { + 标识: 标识.to_string(), + 序号, + 周期, + 时间戳, + 高, + 低, + 开盘价, + 收盘价, + 成交量, + macd: None, + rsi: None, + kdj: None, + }, + } + } + + #[getter] + fn 标识(&self) -> String { + self.inner.标识.clone() + } + #[setter] + fn set_标识(&mut self, v: String) { + self.inner.标识 = v; + } + + #[getter] + fn 序号(&self) -> i64 { + self.inner.序号 + } + #[setter] + fn set_序号(&mut self, v: i64) { + self.inner.序号 = v; + } + + #[getter] + fn 周期(&self) -> i64 { + self.inner.周期 + } + #[setter] + fn set_周期(&mut self, v: i64) { + self.inner.周期 = v; + } + + #[getter] + fn 时间戳(&self) -> i64 { + self.inner.时间戳 + } + #[setter] + fn set_时间戳(&mut self, v: i64) { + self.inner.时间戳 = v; + } + + #[getter] + fn 高(&self) -> f64 { + self.inner.高 + } + #[setter] + fn set_高(&mut self, v: f64) { + self.inner.高 = v; + } + + #[getter] + fn 低(&self) -> f64 { + self.inner.低 + } + #[setter] + fn set_低(&mut self, v: f64) { + self.inner.低 = v; + } + + #[getter] + fn 开盘价(&self) -> f64 { + self.inner.开盘价 + } + #[setter] + fn set_开盘价(&mut self, v: f64) { + self.inner.开盘价 = v; + } + + #[getter] + fn 收盘价(&self) -> f64 { + self.inner.收盘价 + } + #[setter] + fn set_收盘价(&mut self, v: f64) { + self.inner.收盘价 = v; + } + + #[getter] + fn 成交量(&self) -> f64 { + self.inner.成交量 + } + #[setter] + fn set_成交量(&mut self, v: f64) { + self.inner.成交量 = v; + } + + #[getter] + fn 方向(&self) -> 相对方向Py { + 相对方向Py { + inner: self.inner.方向(), + } + } + + #[getter] + fn MACD(&self) -> Option<平滑异同移动平均线Py> { + self.inner + .macd + .as_ref() + .map(|m| 平滑异同移动平均线Py { inner: m.clone() }) + } + + #[getter] + fn RSI(&self) -> Option<相对强弱指数Py> { + self.inner + .rsi + .as_ref() + .map(|r| 相对强弱指数Py { inner: r.clone() }) + } + + #[getter] + fn KDJ(&self) -> Option<随机指标Py> { + self.inner + .kdj + .as_ref() + .map(|k| 随机指标Py { inner: k.clone() }) + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + fn __bytes__(&self, py: Python<'_>) -> Py { + PyBytes::new(py, &self.inner.to_bytes()).into() + } + + #[classmethod] + #[pyo3(signature = (标识, 时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量, 序号 = None, 周期 = None))] + fn 创建普K( + _cls: &Bound<'_, PyType>, + 标识: &str, + 时间戳: i64, + 开盘价: f64, + 最高价: f64, + 最低价: f64, + 收盘价: f64, + 成交量: f64, + 序号: Option, + 周期: Option, + ) -> Self { + Self { + inner: chanlun::kline::bar::K线::创建普K( + 标识, + 时间戳, + 开盘价, + 最高价, + 最低价, + 收盘价, + 成交量, + 序号.unwrap_or(0), + 周期.unwrap_or(60), + ), + } + } + + #[classmethod] + fn 保存到DAT文件( + _cls: &Bound<'_, PyType>, + 路径: &str, + K线序列: Vec>, + 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(); + chanlun::kline::bar::K线::保存到DAT文件(路径, &bars) + .map_err(|e| pyo3::exceptions::PyIOError::new_err(e.to_string())) + } + + #[classmethod] + fn 读取大端字节数组( + _cls: &Bound<'_, PyType>, + 字节组: &Bound<'_, PyBytes>, + 周期: i64, + 标识: &str, + ) -> Option { + chanlun::kline::bar::K线::读取大端字节数组(字节组.as_bytes(), 周期, 标识) + .map(|inner| Self { inner }) + } + + #[classmethod] + fn 获取MACD( + _cls: &Bound<'_, PyType>, + k线序列: Vec>, + 始: &Bound<'_, Self>, + 终: &Bound<'_, Self>, + py: Python<'_>, + ) -> HashMap { + 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(); + chanlun::kline::bar::K线::获取MACD(&bars, &始.borrow().inner, &终.borrow().inner) + } + + #[staticmethod] + fn 截取( + 序列: Vec>, + 始: &Bound<'_, Self>, + 终: &Bound<'_, Self>, + ) -> Option>> { + 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 + } + } +} + +// ========== 缠论K线 ========== + +#[pyclass(name = "缠论K线", unsendable)] +#[derive(Clone)] +pub struct 缠论K线Py { + pub(crate) inner: std::rc::Rc, +} + +#[pymethods] +impl 缠论K线Py { + #[new] + fn new() -> Self { + unimplemented!("使用 创建缠K 创建") + } + + #[getter] + fn 序号(&self) -> i64 { + self.inner.序号 + } + + #[getter] + fn 时间戳(&self) -> i64 { + self.inner.时间戳 + } + + #[getter] + fn 高(&self) -> f64 { + self.inner.高 + } + + #[getter] + fn 低(&self) -> f64 { + self.inner.低 + } + + #[getter] + fn 方向(&self) -> 相对方向Py { + 相对方向Py { + inner: self.inner.方向, + } + } + + #[getter] + fn 分型(&self) -> Option { + self.inner + .分型 + .map(|f| crate::types_py::分型结构Py { inner: f }) + } + + #[getter] + fn 周期(&self) -> i64 { + self.inner.周期 + } + + #[getter] + fn 标识(&self) -> String { + self.inner.标识.clone() + } + + #[getter] + fn 分型特征值(&self) -> f64 { + self.inner.分型特征值 + } + + #[getter] + fn 原始起始序号(&self) -> i64 { + self.inner.原始起始序号 + } + + #[getter] + fn 原始结束序号(&self) -> i64 { + self.inner.原始结束序号 + } + + #[getter] + fn 标的K线(&self) -> K线Py { + K线Py { + inner: (*self.inner.标的K线).clone(), + } + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + fn 镜像(&self) -> Self { + Self { + inner: std::rc::Rc::new(self.inner.镜像()), + } + } + + #[getter] + fn 与MACD柱子匹配(&self) -> bool { + self.inner.与MACD柱子匹配() + } + + #[getter] + fn 与RSI匹配(&self) -> bool { + self.inner.与RSI匹配() + } + + #[getter] + fn 与KDJ匹配(&self) -> bool { + self.inner.与KDJ匹配() + } + + #[classmethod] + fn 时间戳对齐( + _cls: &Bound<'_, PyType>, + 基线: Vec>, + k线: &Bound<'_, Self>, + py: Python<'_>, + ) -> i64 { + let rc_list: Vec<_> = 基线 + .iter() + .map(|k| std::rc::Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + chanlun::kline::chan_kline::缠论K线::时间戳对齐(&rc_list, &k线.borrow().inner) + } + + #[classmethod] + fn 创建缠K( + _cls: &Bound<'_, PyType>, + 时间戳: i64, + 高: f64, + 低: f64, + 方向: &Bound<'_, 相对方向Py>, + 结构: Option<&Bound<'_, crate::types_py::分型结构Py>>, + 原始序号: i64, + 普k: &Bound<'_, K线Py>, + 之前: Option<&Bound<'_, Self>>, + ) -> Self { + let prev_ref = 之前.map(|prev| prev.borrow()); + let prev_inner = prev_ref.as_ref().map(|r| r.inner.as_ref()); + let inner = chanlun::kline::chan_kline::缠论K线::创建缠K( + 时间戳, + 高, + 低, + 方向.borrow().inner, + 结构.map(|s| s.borrow().inner), + 原始序号, + std::rc::Rc::new(普k.borrow().inner.clone()), + prev_inner, + ); + Self { + inner: std::rc::Rc::new(inner), + } + } + + #[classmethod] + fn 兼并( + _cls: &Bound<'_, PyType>, + 之前缠K: Option<&Bound<'_, Self>>, + 当前缠K: &Bound<'_, Self>, + 当前普K: &Bound<'_, K线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult<(Option, Option)> { + let mut ck_inner = (*当前缠K.borrow().inner).clone(); + let config = 配置.borrow().to_rust_config(py)?; + let prev_ref = 之前缠K.map(|prev| prev.borrow()); + let prev_inner = prev_ref.as_ref().map(|r| r.inner.as_ref()); + let (result, mode) = chanlun::kline::chan_kline::缠论K线::兼并( + prev_inner, + &mut ck_inner, + &当前普K.borrow().inner, + &config, + ); + Ok((result.map(|rc| Self { inner: rc }), mode)) + } + + #[classmethod] + fn 分析( + _cls: &Bound<'_, PyType>, + 当前K线: &Bound<'_, K线Py>, + 缠K序列: Vec>, + 普K序列: Vec>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult<(String, Option>)> { + let ck_inner = 当前K线.borrow().inner.clone(); + let config = 配置.borrow().to_rust_config(py)?; + + let mut ck_seq: Vec<_> = 缠K序列 + .iter() + .map(|k| std::rc::Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + let mut bar_seq: Vec<_> = 普K序列 + .iter() + .map(|k| std::rc::Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + + let (status, _fractal) = chanlun::kline::chan_kline::缠论K线::分析( + ck_inner, + &mut ck_seq, + &mut bar_seq, + &config, + ); + + Ok((status, None)) + } + + #[staticmethod] + fn 截取( + 序列: Vec>, + 始: &Bound<'_, Self>, + 终: &Bound<'_, Self>, + ) -> Option>> { + 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 + } + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::()?; + m.add_class::<缠论K线Py>()?; + Ok(()) +} diff --git a/chanlun-py/src/lib.rs b/chanlun-py/src/lib.rs new file mode 100644 index 0000000..924012a --- /dev/null +++ b/chanlun-py/src/lib.rs @@ -0,0 +1,29 @@ +use pyo3::prelude::*; + +mod algorithm_py; +mod business_py; +mod config_py; +mod indicators_py; +mod kline_py; +mod structure_py; +mod types_py; + +/// 缠论技术分析库 — Rust 高性能实现 +#[pymodule] +fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> { + // 阶段 1: 枚举和基础类型 + types_py::register(m)?; + // 阶段 2: 配置 + config_py::register(m)?; + // 阶段 3: 技术指标 + indicators_py::register(m)?; + // 阶段 4: K线 + kline_py::register(m)?; + // 阶段 5: 结构 + structure_py::register(m)?; + // 阶段 6: 算法 + algorithm_py::register(m)?; + // 阶段 7: 业务 + business_py::register(m)?; + Ok(()) +} diff --git a/chanlun-py/src/structure_py.rs b/chanlun-py/src/structure_py.rs new file mode 100644 index 0000000..d12969c --- /dev/null +++ b/chanlun-py/src/structure_py.rs @@ -0,0 +1,825 @@ +use pyo3::prelude::*; +use pyo3::types::PyType; +use std::collections::HashMap; +use std::rc::Rc; + +use crate::config_py::缠论配置Py; +use crate::kline_py::{缠论K线Py, K线Py}; +use crate::types_py::{分型结构Py, 相对方向Py, 缺口Py}; + +// ========== 分型 ========== + +#[pyclass(name = "分型", unsendable)] +#[derive(Clone)] +pub struct 分型Py { + pub(crate) inner: Rc, +} + +#[pymethods] +impl 分型Py { + #[new] + fn new( + 左: Option<&Bound<'_, 缠论K线Py>>, + 中: &Bound<'_, 缠论K线Py>, + 右: Option<&Bound<'_, 缠论K线Py>>, + ) -> Self { + Self { + inner: Rc::new(chanlun::structure::fractal_obj::分型::new( + 左.map(|k| Rc::clone(&k.borrow().inner)), + Rc::clone(&中.borrow().inner), + 右.map(|k| Rc::clone(&k.borrow().inner)), + )), + } + } + + #[getter] + fn 左(&self) -> Option<缠论K线Py> { + self.inner.左.as_ref().map(|k| 缠论K线Py { + inner: Rc::clone(k), + }) + } + + #[getter] + fn 中(&self) -> 缠论K线Py { + 缠论K线Py { + inner: Rc::clone(&self.inner.中), + } + } + + #[getter] + fn 右(&self) -> Option<缠论K线Py> { + self.inner.右.as_ref().map(|k| 缠论K线Py { + inner: Rc::clone(k), + }) + } + + #[getter] + fn 结构(&self) -> 分型结构Py { + 分型结构Py { + inner: self.inner.结构, + } + } + + #[getter] + fn 时间戳(&self) -> i64 { + self.inner.时间戳 + } + + #[getter] + fn 分型特征值(&self) -> f64 { + self.inner.分型特征值 + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + #[getter] + fn 关系组(&self) -> Option<(相对方向Py, 相对方向Py, 相对方向Py)> { + self.inner.关系组().map(|(a, b, c)| { + ( + 相对方向Py { inner: a }, + 相对方向Py { inner: b }, + 相对方向Py { inner: c }, + ) + }) + } + + #[getter] + fn 强度(&self) -> String { + self.inner.强度().to_string() + } + + #[getter] + fn 与MACD柱子分型匹配(&self) -> bool { + self.inner.与MACD柱子分型匹配() + } + + #[classmethod] + #[pyo3(signature = (左, 右, 模式 = "中"))] + fn 判断分型( + _cls: &Bound<'_, PyType>, + 左: &Bound<'_, Self>, + 右: &Bound<'_, Self>, + 模式: &str, + ) -> bool { + chanlun::structure::fractal_obj::分型::判断分型( + &左.borrow().inner, + &右.borrow().inner, + 模式, + ) + } + + #[staticmethod] + fn 从缠K序列中获取分型( + K线序列: Vec>, + 中: &Bound<'_, 缠论K线Py>, + py: Python<'_>, + ) -> Option { + let ck_seq: Vec> = K线序列 + .iter() + .map(|k| Rc::clone(&k.bind(py).borrow().inner)) + .collect(); + chanlun::structure::fractal_obj::分型::从缠K序列中获取分型( + &ck_seq, + &中.borrow().inner, + ) + .map(|inner| Self { + inner: Rc::new(inner), + }) + } + + #[staticmethod] + fn 向序列中添加( + 分型序列: &Bound<'_, PyAny>, 当前分型: &Bound<'_, Self> + ) -> PyResult<()> { + let py = 分型序列.py(); + let inner = Rc::clone(&当前分型.borrow().inner); + let wrapper = Py::new( + py, + Self { + inner: Rc::clone(&inner), + }, + )?; + 分型序列.call_method1("append", (wrapper,))?; + Ok(()) + } +} + +// ========== 虚线 ========== + +#[pyclass(name = "虚线", unsendable)] +#[derive(Clone)] +pub struct 虚线Py { + pub(crate) inner: Rc, +} + +#[pymethods] +impl 虚线Py { + #[new] + #[pyo3(signature = (序号, 标识, 文, 武, 级别, 有效性 = true))] + fn new( + 序号: i64, + 标识: String, + 文: &Bound<'_, 分型Py>, + 武: &Bound<'_, 分型Py>, + 级别: i64, + 有效性: bool, + ) -> Self { + Self { + inner: Rc::new(chanlun::structure::dash_line::虚线::new( + 序号, + 标识, + Rc::clone(&文.borrow().inner), + Rc::clone(&武.borrow().inner), + 级别, + 有效性, + )), + } + } + + // ---- 基础 getters ---- + + #[getter] + fn 标识(&self) -> String { + self.inner.标识.clone() + } + + #[getter] + fn 序号(&self) -> i64 { + self.inner.序号 + } + + #[getter] + fn 级别(&self) -> i64 { + self.inner.级别 + } + + #[getter] + fn 文(&self) -> 分型Py { + 分型Py { + inner: Rc::clone(&self.inner.文), + } + } + + #[getter] + fn 武(&self) -> 分型Py { + 分型Py { + inner: Rc::clone(&self.inner.武), + } + } + + #[getter] + fn 有效性(&self) -> bool { + self.inner.有效性 + } + + #[getter] + fn 模式(&self) -> String { + self.inner.模式.clone() + } + + #[getter] + fn _特征序列_显示(&self) -> bool { + self.inner._特征序列_显示 + } + + #[getter] + fn 短路修正(&self) -> bool { + self.inner.短路修正 + } + + #[getter] + fn 确认K线(&self) -> Option<缠论K线Py> { + self.inner.确认K线.as_ref().map(|k| 缠论K线Py { + inner: Rc::clone(k), + }) + } + + #[getter] + fn 前一缺口(&self) -> Option<缺口Py> { + self.inner.前一缺口.map(|q| 缺口Py { inner: q }) + } + + #[getter] + fn 前一结束位置(&self) -> Option { + self.inner.前一结束位置.as_ref().map(|d| Self { + inner: Rc::clone(d), + }) + } + + // ---- 序列 getters ---- + + #[getter] + fn 基础序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in &self.inner.基础序列 { + list.append(Py::new( + py, + Self { + inner: Rc::clone(d), + }, + )?)?; + } + Ok(list.into()) + } + + // 实_中枢序列 / 虚_中枢序列 / 合_中枢序列 — deferred to Phase 6 (中枢Py not yet available) + + // ---- 计算属性 ---- + + #[getter] + fn 笔序列(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in self.inner.笔序列() { + list.append(Py::new( + py, + Self { + inner: Rc::clone(d), + }, + )?)?; + } + Ok(list.into()) + } + + #[getter] + fn 图表标题(&self) -> String { + self.inner.图表标题() + } + + #[getter] + fn 方向(&self) -> 相对方向Py { + 相对方向Py { + inner: self.inner.方向(), + } + } + + #[getter] + fn 高(&self) -> f64 { + self.inner.高() + } + + #[getter] + fn 低(&self) -> f64 { + self.inner.低() + } + + fn 之前是(&self, 之前: &Bound<'_, Self>) -> bool { + self.inner.之前是(&之前.borrow().inner) + } + + fn 之后是(&self, 之后: &Bound<'_, Self>) -> bool { + self.inner.之后是(&之后.borrow().inner) + } + + fn 获取普K序列(&self, 普K序列: Vec>, py: Python<'_>) -> PyResult> { + let rc_list: Vec> = 普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); + for k in &result { + list.append(K线Py { + inner: (**k).clone(), + })?; + } + Ok(list.into()) + } + + fn 获取缠K序列( + &self, 缠K序列: Vec>, py: Python<'_> + ) -> PyResult> { + let rc_list: Vec> = 缠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 获取数据文本(&self) -> String { + self.inner.获取数据文本() + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + // ---- 静态工厂方法 ---- + + #[classmethod] + fn 创建笔( + _cls: &Bound<'_, PyType>, + 文: &Bound<'_, 分型Py>, + 武: &Bound<'_, 分型Py>, + 有效性: bool, + ) -> Self { + Self { + inner: Rc::new(chanlun::structure::dash_line::虚线::创建笔( + Rc::clone(&文.borrow().inner), + Rc::clone(&武.borrow().inner), + 有效性, + )), + } + } + + #[classmethod] + fn 创建线段(_cls: &Bound<'_, PyType>, 虚线序列: Vec>, py: Python<'_>) -> Self { + let rc_list: Vec> = 虚线序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + Self { + inner: Rc::new(chanlun::structure::dash_line::虚线::创建线段( + &rc_list, + )), + } + } + + // ---- 买卖点模式匹配 ---- + + #[classmethod] + fn 缠K买卖点模式( + _cls: &Bound<'_, PyType>, + 模式: &str, + 缠K: &Bound<'_, 缠论K线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::structure::dash_line::虚线::缠K买卖点模式( + 模式, + &缠K.borrow().inner, + &config, + )) + } + + #[classmethod] + fn 买卖点配置匹配( + _cls: &Bound<'_, PyType>, + 缠K: &Bound<'_, 缠论K线Py>, + 配置: &Bound<'_, 缠论配置Py>, + py: Python<'_>, + ) -> PyResult { + let config = 配置.borrow().to_rust_config(py)?; + Ok(chanlun::structure::dash_line::虚线::买卖点配置匹配( + &缠K.borrow().inner, + &config, + )) + } + + #[classmethod] + fn 买卖点任意匹配(_cls: &Bound<'_, PyType>, 缠K: &Bound<'_, 缠论K线Py>) -> bool { + chanlun::structure::dash_line::虚线::买卖点任意匹配(&缠K.borrow().inner) + } + + #[classmethod] + fn 买卖点全量匹配(_cls: &Bound<'_, PyType>, 缠K: &Bound<'_, 缠论K线Py>) -> bool { + chanlun::structure::dash_line::虚线::买卖点全量匹配(&缠K.borrow().inner) + } + + #[classmethod] + fn 买卖点相对匹配(_cls: &Bound<'_, PyType>, 缠K: &Bound<'_, 缠论K线Py>) -> bool { + chanlun::structure::dash_line::虚线::买卖点相对匹配(&缠K.borrow().inner) + } + + // ---- MACD 相关 classmethods ---- + + #[classmethod] + fn 计算MACD柱子均值( + _cls: &Bound<'_, PyType>, + 普K序列: Vec>, + 实线: &Bound<'_, Self>, + py: Python<'_>, + ) -> f64 { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::计算MACD柱子均值( + &rc_list, + &实线.borrow().inner, + ) + } + + #[classmethod] + fn 武之全量MACD均值( + _cls: &Bound<'_, PyType>, + 普K序列: Vec>, + 实线: &Bound<'_, Self>, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::武之全量MACD均值( + &rc_list, + &实线.borrow().inner, + ) + } + + #[classmethod] + fn 武之MACD均值( + _cls: &Bound<'_, PyType>, + 普K序列: Vec>, + 实线: &Bound<'_, Self>, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::武之MACD均值(&rc_list, &实线.borrow().inner) + } + + #[classmethod] + fn 武之MACD极值( + _cls: &Bound<'_, PyType>, + 普K序列: Vec>, + 实线: &Bound<'_, Self>, + py: Python<'_>, + ) -> bool { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::武之MACD极值(&rc_list, &实线.borrow().inner) + } + + #[classmethod] + fn 计算K线序列MACD趋向背驰( + _cls: &Bound<'_, PyType>, + 普K序列: Vec>, + 方向: &Bound<'_, 相对方向Py>, + py: Python<'_>, + ) -> [bool; 3] { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::计算K线序列MACD趋向背驰( + &rc_list, + 方向.borrow().inner, + ) + } + + #[classmethod] + fn 计算MACD柱子分段( + _cls: &Bound<'_, PyType>, + k线序列: Vec>, + py: Python<'_>, + ) -> Vec> { + let rc_list: Vec> = k线序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::计算MACD柱子分段(&rc_list) + } + + #[classmethod] + fn 密集区域按间隔( + _cls: &Bound<'_, PyType>, + 交叉标记: Vec, + 最大间隔: usize, + 最少交叉数: usize, + ) -> Vec<(usize, usize, usize)> { + chanlun::structure::dash_line::虚线::密集区域按间隔( + &交叉标记, + 最大间隔, + 最少交叉数, + ) + } + + #[classmethod] + fn 统计MACD行为( + _cls: &Bound<'_, PyType>, + 普K序列: Vec>, + 最大间隔: usize, + 最少交叉数: usize, + py: Python<'_>, + ) -> HashMap { + let rc_list: Vec> = 普K序列 + .iter() + .map(|k| Rc::new(k.bind(py).borrow().inner.clone())) + .collect(); + chanlun::structure::dash_line::虚线::统计MACD行为(&rc_list, 最大间隔, 最少交叉数) + } + + // 买卖意义 — deferred to Phase 7 (needs observer) +} + +// ========== 线段特征 ========== + +#[pyclass(name = "线段特征", unsendable)] +#[derive(Clone)] +pub struct 线段特征Py { + pub(crate) inner: Rc, +} + +#[pymethods] +impl 线段特征Py { + #[new] + fn new( + 标识: String, + 基础序列: Vec>, + 线段方向: &Bound<'_, 相对方向Py>, + py: Python<'_>, + ) -> Self { + let rc_list: Vec> = 基础序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + Self { + inner: Rc::new(chanlun::structure::segment_feat::线段特征::new( + 标识, + rc_list, + 线段方向.borrow().inner, + )), + } + } + + // ---- getters ---- + + #[getter] + fn 序号(&self) -> i64 { + self.inner.序号 + } + + #[getter] + fn 标识(&self) -> String { + self.inner.标识.clone() + } + + #[getter] + fn 线段方向(&self) -> 相对方向Py { + 相对方向Py { + inner: self.inner.线段方向, + } + } + + #[getter] + fn 元素(&self, py: Python<'_>) -> PyResult> { + let list = pyo3::types::PyList::empty(py); + for d in &self.inner.元素 { + list.append(Py::new( + py, + 虚线Py { + inner: Rc::clone(d), + }, + )?)?; + } + Ok(list.into()) + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } + + // ---- instance methods ---- + + #[getter] + fn 图表标题(&self) -> String { + self.inner.图表标题() + } + + #[getter] + fn 文(&self) -> 分型Py { + 分型Py { + inner: self.inner.文(), + } + } + + #[getter] + fn 武(&self) -> 分型Py { + 分型Py { + inner: self.inner.武(), + } + } + + #[getter] + fn 方向(&self) -> 相对方向Py { + 相对方向Py { + inner: self.inner.方向(), + } + } + + #[getter] + fn 高(&self) -> f64 { + self.inner.高() + } + + #[getter] + fn 低(&self) -> f64 { + self.inner.低() + } + + fn 添加(&mut self, 待添加虚线: &Bound<'_, 虚线Py>) -> PyResult<()> { + let inner = Rc::make_mut(&mut self.inner); + inner + .添加(Rc::clone(&待添加虚线.borrow().inner)) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e)) + } + + fn 删除(&mut self, 待删除虚线: &Bound<'_, 虚线Py>) -> PyResult<()> { + let inner = Rc::make_mut(&mut self.inner); + inner + .删除(&Rc::clone(&待删除虚线.borrow().inner)) + .map_err(|e| pyo3::exceptions::PyValueError::new_err(e)) + } + + // ---- classmethods ---- + + #[classmethod] + fn 新建( + _cls: &Bound<'_, PyType>, + 虚线序列: Vec>, + 线段方向: &Bound<'_, 相对方向Py>, + py: Python<'_>, + ) -> Self { + let rc_list: Vec> = 虚线序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + Self { + inner: Rc::new(chanlun::structure::segment_feat::线段特征::新建( + rc_list, + 线段方向.borrow().inner, + )), + } + } + + #[classmethod] + fn 静态分析( + _cls: &Bound<'_, PyType>, + 虚线序列: Vec>, + 线段方向: &Bound<'_, 相对方向Py>, + 四象: &str, + 是否忽视: bool, + py: Python<'_>, + ) -> Vec { + let rc_list: Vec> = 虚线序列 + .iter() + .map(|d| Rc::clone(&d.bind(py).borrow().inner)) + .collect(); + chanlun::structure::segment_feat::线段特征::静态分析( + &rc_list, + 线段方向.borrow().inner, + 四象, + 是否忽视, + ) + .into_iter() + .map(|inner| Self { inner }) + .collect() + } + + #[classmethod] + fn 获取分型序列( + _cls: &Bound<'_, PyType>, + 特征序列: Vec>, + py: Python<'_>, + ) -> Vec<特征分型Py> { + let rc_list: Vec> = 特征序列 + .iter() + .map(|s| Rc::clone(&s.bind(py).borrow().inner)) + .collect(); + chanlun::structure::segment_feat::线段特征::获取分型序列(&rc_list) + .into_iter() + .map(|inner| 特征分型Py { + inner: Rc::new(inner), + }) + .collect() + } +} + +// ========== 特征分型 ========== + +#[pyclass(name = "特征分型", unsendable)] +#[derive(Clone)] +pub struct 特征分型Py { + pub(crate) inner: Rc, +} + +#[pymethods] +impl 特征分型Py { + #[new] + fn new( + 左: &Bound<'_, 线段特征Py>, + 中: &Bound<'_, 线段特征Py>, + 右: &Bound<'_, 线段特征Py>, + 结构: &Bound<'_, 分型结构Py>, + ) -> Self { + Self { + inner: Rc::new(chanlun::structure::feat_fractal::特征分型::new( + Rc::clone(&左.borrow().inner), + Rc::clone(&中.borrow().inner), + Rc::clone(&右.borrow().inner), + 结构.borrow().inner, + )), + } + } + + #[getter] + fn 左(&self) -> 线段特征Py { + 线段特征Py { + inner: Rc::clone(&self.inner.左), + } + } + + #[getter] + fn 中(&self) -> 线段特征Py { + 线段特征Py { + inner: Rc::clone(&self.inner.中), + } + } + + #[getter] + fn 右(&self) -> 线段特征Py { + 线段特征Py { + inner: Rc::clone(&self.inner.右), + } + } + + #[getter] + fn 结构(&self) -> 分型结构Py { + 分型结构Py { + inner: self.inner.结构, + } + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + self.__str__() + } +} + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + m.add_class::<分型Py>()?; + m.add_class::<虚线Py>()?; + m.add_class::<线段特征Py>()?; + m.add_class::<特征分型Py>()?; + Ok(()) +} diff --git a/chanlun-py/src/types_py.rs b/chanlun-py/src/types_py.rs new file mode 100644 index 0000000..d874e74 --- /dev/null +++ b/chanlun-py/src/types_py.rs @@ -0,0 +1,408 @@ +use pyo3::prelude::*; +use pyo3::types::PyType; + +// ========== 买卖点类型 ========== + +#[pyclass(name = "买卖点类型")] +#[derive(Clone)] +pub struct 买卖点类型Py { + pub inner: chanlun::types::买卖点类型, +} + +#[pymethods] +impl 买卖点类型Py { + fn __str__(&self) -> String { + self.inner.to_string() + } + + fn __repr__(&self) -> String { + self.inner.to_string() + } + + fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool { + if let Ok(s) = other.extract::() { + return self.inner.to_string() == s; + } + if let Ok(other) = other.extract::>() { + return self.inner == other.inner; + } + false + } + + fn __hash__(&self) -> u64 { + use std::hash::{Hash, Hasher}; + let mut h = std::collections::hash_map::DefaultHasher::new(); + self.inner.to_string().hash(&mut h); + h.finish() + } + + #[getter] + fn 是买点(&self) -> bool { + self.inner.是买点() + } + + #[getter] + fn 是卖点(&self) -> bool { + self.inner.是卖点() + } +} + +// ========== 相对方向 ========== + +#[pyclass(name = "相对方向")] +#[derive(Clone)] +pub struct 相对方向Py { + pub inner: chanlun::types::相对方向, +} + +#[pymethods] +impl 相对方向Py { + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + format!("{}", self.inner) + } + + fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool { + if let Ok(other) = other.extract::>() { + return self.inner == other.inner; + } + false + } + + fn __hash__(&self) -> u64 { + self.inner as u64 + } + + fn 翻转(&self) -> Self { + Self { + inner: 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.是否缺口() + } + + #[getter] + fn 是否衔接(&self) -> bool { + self.inner.是否衔接() + } + + #[classmethod] + fn 分析( + _cls: &Bound<'_, PyType>, 前高: f64, 前低: f64, 后高: f64, 后低: f64 + ) -> Self { + Self { + inner: chanlun::types::相对方向::分析(前高, 前低, 后高, 后低), + } + } +} + +// ========== 分型结构 ========== + +#[pyclass(name = "分型结构")] +#[derive(Clone)] +pub struct 分型结构Py { + pub inner: chanlun::types::分型结构, +} + +#[pymethods] +impl 分型结构Py { + fn __str__(&self) -> String { + self.inner.to_string() + } + + fn __repr__(&self) -> String { + self.inner.to_string() + } + + fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool { + if let Ok(other) = other.extract::>() { + return self.inner == other.inner; + } + false + } + + fn __hash__(&self) -> u64 { + self.inner as u64 + } + + #[classmethod] + fn 分析( + _cls: &Bound<'_, PyType>, + 左: &Bound<'_, PyAny>, + 中: &Bound<'_, PyAny>, + 右: &Bound<'_, PyAny>, + 可以逆序包含: Option, + 忽视顺序包含: Option, + ) -> PyResult> { + let 可以逆序包含 = 可以逆序包含.unwrap_or(false); + let 忽视顺序包含 = 忽视顺序包含.unwrap_or(false); + + let get_hl = |obj: &Bound<'_, PyAny>| -> PyResult<(f64, f64)> { + Ok(( + obj.getattr("高")?.extract::()?, + obj.getattr("低")?.extract::()?, + )) + }; + + let (左高, 左低) = get_hl(左)?; + let (中高, 中低) = get_hl(中)?; + let (右高, 右低) = get_hl(右)?; + + let 左中关系 = chanlun::types::相对方向::分析(左高, 左低, 中高, 中低); + let 中右关系 = chanlun::types::相对方向::分析(中高, 中低, 右高, 右低); + + let 向上类 = |d: chanlun::types::相对方向| d.是否向上(); + let 向下类 = |d: chanlun::types::相对方向| d.是否向下(); + + let result = match (左中关系, 中右关系) { + (d1, d2) if matches!(d1, chanlun::types::相对方向::顺) && !忽视顺序包含 => { + panic!("顺序包含: {:?} {:?}", d1, d2); + } + (d1, d2) if matches!(d2, chanlun::types::相对方向::顺) && !忽视顺序包含 => { + panic!("顺序包含: {:?} {:?}", d1, d2); + } + (a, b) if 向上类(a) && 向上类(b) => chanlun::types::分型结构::上, + (a, b) if 向上类(a) && 向下类(b) => chanlun::types::分型结构::顶, + (a, chanlun::types::相对方向::逆) if 向上类(a) && 可以逆序包含 => { + chanlun::types::分型结构::上 + } + (a, b) if 向下类(a) && 向上类(b) => chanlun::types::分型结构::底, + (a, b) if 向下类(a) && 向下类(b) => chanlun::types::分型结构::下, + (a, chanlun::types::相对方向::逆) if 向下类(a) && 可以逆序包含 => { + chanlun::types::分型结构::下 + } + (chanlun::types::相对方向::逆, a) if 向上类(a) && 可以逆序包含 => { + chanlun::types::分型结构::底 + } + (chanlun::types::相对方向::逆, a) if 向下类(a) && 可以逆序包含 => { + chanlun::types::分型结构::顶 + } + (chanlun::types::相对方向::逆, chanlun::types::相对方向::逆) if 可以逆序包含 => { + chanlun::types::分型结构::散 + } + _ => return Ok(None), + }; + Ok(Some(Self { inner: result })) + } +} + +// ========== 缺口 ========== + +#[pyclass(name = "缺口")] +#[derive(Clone)] +pub struct 缺口Py { + pub inner: chanlun::types::缺口, +} + +#[pymethods] +impl 缺口Py { + #[new] + fn new(高: f64, 低: f64) -> PyResult { + if 高 <= 低 { + return Err(pyo3::exceptions::PyValueError::new_err(format!( + "缺口高必须大于低: 高={高}, 低={低}" + ))); + } + Ok(Self { + inner: chanlun::types::缺口::new(高, 低), + }) + } + + fn __str__(&self) -> String { + format!("{}", self.inner) + } + + fn __repr__(&self) -> String { + format!("{}", self.inner) + } + + #[getter] + #[pyo3(name = "高")] + fn get_高(&self) -> f64 { + self.inner.高 + } + + #[setter] + #[pyo3(name = "高")] + fn set_高(&mut self, value: f64) { + self.inner.高 = value; + } + + #[getter] + #[pyo3(name = "低")] + fn get_低(&self) -> f64 { + self.inner.低 + } + + #[setter] + #[pyo3(name = "低")] + fn set_低(&mut self, value: f64) { + self.inner.低 = value; + } + + #[classmethod] + fn 居中截取区间( + _cls: &Bound<'_, PyType>, + 起点: f64, + 终点: f64, + 比例: Option, + ) -> Option { + let 比例 = 比例.unwrap_or(0.15); + chanlun::types::缺口::居中截取区间(起点, 终点, 比例).map(|inner| Self { inner }) + } +} + +// ========== Registration ========== + +pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> { + // Register classes + m.add_class::<买卖点类型Py>()?; + m.add_class::<相对方向Py>()?; + m.add_class::<分型结构Py>()?; + m.add_class::<缺口Py>()?; + + // 买卖点类型 class attributes (singleton instances) + let py = m.py(); + let bsp_class = m.getattr("买卖点类型")?; + let bsp_class = bsp_class.downcast_into::()?; + + let variants: &[(&str, chanlun::types::买卖点类型)] = &[ + ("一买", chanlun::types::买卖点类型::一买), + ("一卖", chanlun::types::买卖点类型::一卖), + ("二买", chanlun::types::买卖点类型::二买), + ("二卖", chanlun::types::买卖点类型::二卖), + ("三买", chanlun::types::买卖点类型::三买), + ("三卖", chanlun::types::买卖点类型::三卖), + ("T1买", chanlun::types::买卖点类型::T1买), + ("T1卖", chanlun::types::买卖点类型::T1卖), + ("T1P买", chanlun::types::买卖点类型::T1P买), + ("T1P卖", chanlun::types::买卖点类型::T1P卖), + ("T2买", chanlun::types::买卖点类型::T2买), + ("T2卖", chanlun::types::买卖点类型::T2卖), + ("T2S买", chanlun::types::买卖点类型::T2S买), + ("T2S卖", chanlun::types::买卖点类型::T2S卖), + ("T3A买", chanlun::types::买卖点类型::T3A买), + ("T3A卖", chanlun::types::买卖点类型::T3A卖), + ("T3B买", chanlun::types::买卖点类型::T3B买), + ("T3B卖", chanlun::types::买卖点类型::T3B卖), + ]; + + for (name, value) in variants { + let instance = Py::new(py, 买卖点类型Py { inner: *value })?; + bsp_class.setattr(*name, instance)?; + } + + // 相对方向 class attributes + let dir_class = m.getattr("相对方向")?.downcast_into::()?.clone(); + let dir_variants: &[(&str, chanlun::types::相对方向)] = &[ + ("向上", chanlun::types::相对方向::向上), + ("向下", chanlun::types::相对方向::向下), + ("向上缺口", chanlun::types::相对方向::向上缺口), + ("向下缺口", chanlun::types::相对方向::向下缺口), + ("衔接向上", chanlun::types::相对方向::衔接向上), + ("衔接向下", chanlun::types::相对方向::衔接向下), + ("顺", chanlun::types::相对方向::顺), + ("逆", chanlun::types::相对方向::逆), + ("同", chanlun::types::相对方向::同), + ]; + + for (name, value) in dir_variants { + let instance = Py::new(py, 相对方向Py { inner: *value })?; + dir_class.setattr(*name, instance)?; + } + + // 分型结构 class attributes + let frac_class = m.getattr("分型结构")?.downcast_into::()?.clone(); + let frac_variants: &[(&str, chanlun::types::分型结构)] = &[ + ("上", chanlun::types::分型结构::上), + ("下", chanlun::types::分型结构::下), + ("顶", chanlun::types::分型结构::顶), + ("底", chanlun::types::分型结构::底), + ("散", chanlun::types::分型结构::散), + ]; + + for (name, value) in frac_variants { + let instance = Py::new(py, 分型结构Py { inner: *value })?; + frac_class.setattr(*name, instance)?; + } + + // Register module-level functions + m.add_function(wrap_pyfunction!(转化为时间戳, m)?)?; + m.add_function(wrap_pyfunction!(转化为时间戳_数字, m)?)?; + + Ok(()) +} + +// ========== Module-level functions ========== + +#[pyfunction] +fn 转化为时间戳(ts: &Bound<'_, PyAny>) -> PyResult { + // Try int first + if let Ok(v) = ts.extract::() { + return Ok(v); + } + // Try float + if let Ok(v) = ts.extract::() { + return Ok(v as i64); + } + // Try string (ISO 8601 or "YYYY-MM-DD HH:MM:SS") + if let Ok(s) = ts.extract::() { + return parse_datetime_string(&s); + } + // Try datetime object + if let Ok(dt) = ts.getattr("timestamp") { + let ts_float: f64 = dt.call0()?.extract()?; + return Ok(ts_float as i64); + } + Err(pyo3::exceptions::PyValueError::new_err(format!( + "无法转化为时间戳: {:?}", + ts + ))) +} + +#[pyfunction] +fn 转化为时间戳_数字(ts: &Bound<'_, PyAny>) -> PyResult { + 转化为时间戳(ts) +} + +fn parse_datetime_string(s: &str) -> PyResult { + // Try ISO 8601: "2024-10-15T16:45:00" + if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%dT%H:%M:%S") { + return Ok(dt.and_utc().timestamp()); + } + // Try "YYYY-MM-DD HH:MM:SS" + if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(s, "%Y-%m-%d %H:%M:%S") { + return Ok(dt.and_utc().timestamp()); + } + // Try "YYYY-MM-DD" + if let Ok(d) = chrono::NaiveDate::parse_from_str(s, "%Y-%m-%d") { + let dt = d + .and_hms_opt(0, 0, 0) + .ok_or_else(|| pyo3::exceptions::PyValueError::new_err(format!("无法解析日期: {s}")))?; + return Ok(dt.and_utc().timestamp()); + } + Err(pyo3::exceptions::PyValueError::new_err(format!( + "无法解析时间字符串: {s}" + ))) +} diff --git a/chanlun-py/test_integration.py b/chanlun-py/test_integration.py new file mode 100644 index 0000000..f7ef06e --- /dev/null +++ b/chanlun-py/test_integration.py @@ -0,0 +1,135 @@ +#!/usr/bin/env python3 +"""Integration test: feed .nb bars through PyO3 observer, compare with Python reference output.""" + +import sys +import os +import struct + +import chanlun + + +def read_nb_bars(path, max_bars=None): + """Read bars from .nb file (48 bytes each: 6 × f64 big-endian).""" + bars = [] + with open(path, "rb") as f: + i = 0 + while True: + data = f.read(48) + if not data: + break + ts, o, h, l, c, v = struct.unpack(">6d", data) + bars.append((int(ts), o, h, l, c, v)) + i += 1 + if max_bars and i >= max_bars: + break + return bars + + +def main(): + nb_path = "/home/moscow/chanlun.rs/btcusd-300-1761327300-1776327900.nb" + ref_dir = "/home/moscow/chanlun.rs/Py_btcusd:300_1761327300_1776327900" + out_dir = "/tmp/chanlun_py_test_output" + + # Read all bars + print("Reading bars from .nb file...") + bars = read_nb_bars(nb_path) + print(f" Read {len(bars)} bars") + + # Create observer (default config) + print("Creating observer...") + obs = chanlun.观察者("btcusd", 300) + print(f" Observer: {obs.标识}, period={obs.周期}") + + # Feed bars + print("Feeding bars...") + for i, (ts, o, h, l, c, v) in enumerate(bars): + k = chanlun.K线.创建普K(f"btcusd_{i}", ts, o, h, l, c, v, i, 300) + obs.增加原始K线(k) + if i % 10000 == 0: + print(f" Fed {i}/{len(bars)} bars") + + print(f" Done. {len(obs.普通K线序列)} normal K lines, {len(obs.缠论K线序列)} Chan K lines") + + # Save output + print(f"Saving data to {out_dir}...") + os.makedirs(out_dir, exist_ok=True) + obs.测试_保存数据(out_dir) + + # Find the actual output subdirectory created by 测试_保存数据 + subdirs = [d for d in os.listdir(out_dir) if os.path.isdir(os.path.join(out_dir, d))] + if not subdirs: + print("ERROR: No output subdirectory found!") + return 1 + actual_out_dir = os.path.join(out_dir, subdirs[0]) + out_files = sorted(os.listdir(actual_out_dir)) + print(f" Output dir: {actual_out_dir}") + print(f" Output files ({len(out_files)}): {out_files}") + + # Compare with Python reference + print("\nComparing with Python reference...") + ref_files = sorted(os.listdir(ref_dir)) + + match_count = 0 + diff_count = 0 + all_match = True + for fname in ref_files: + ref_path = os.path.join(ref_dir, fname) + out_path = os.path.join(actual_out_dir, fname) + + if not os.path.exists(out_path): + print(f" MISSING: {fname}") + all_match = False + continue + + with open(ref_path) as f: + ref_lines = f.readlines() + with open(out_path) as f: + out_lines = f.readlines() + + if ref_lines == out_lines: + print(f" MATCH: {fname} ({len(ref_lines)} lines)") + match_count += 1 + else: + print(f" DIFF: {fname} (ref={len(ref_lines)} lines, out={len(out_lines)} lines)") + for j, (rl, ol) in enumerate(zip(ref_lines, out_lines)): + if rl != ol: + print(f" Line {j}:") + print(f" REF: {rl.rstrip()}") + print(f" OUT: {ol.rstrip()}") + break + if len(ref_lines) != len(out_lines): + print(f" Line count differs") + diff_count += 1 + all_match = False + + # Also compare extra files against Rust reference + rust_ref_dir = "/home/moscow/chanlun.rs/chanlun/Rust_btcusd:300_1761327300_1776327900" + extra_files = set(out_files) - set(ref_files) + if extra_files: + print("\nComparing extra files with Rust reference...") + for fname in sorted(extra_files): + out_path = os.path.join(actual_out_dir, fname) + rust_ref_path = os.path.join(rust_ref_dir, fname) + if os.path.exists(rust_ref_path): + with open(rust_ref_path) as f: + ref_lines = f.readlines() + with open(out_path) as f: + out_lines = f.readlines() + if ref_lines == out_lines: + print(f" MATCH: {fname} (vs Rust ref, {len(ref_lines)} lines)") + match_count += 1 + else: + print(f" DIFF: {fname} (vs Rust ref)") + diff_count += 1 + + print(f"\nSummary: {match_count} match, {diff_count} differ") + if all_match: + print("All Python reference files match!") + return 0 + else: + print("Some files differ (see above)") + return 1 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/chanlun/Cargo.toml b/chanlun/Cargo.toml index abb7daa..2c4a5bf 100644 --- a/chanlun/Cargo.toml +++ b/chanlun/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "chanlun" -version = "0.1.0" +version = "26.5.1" edition = "2021" [dependencies]