第三版
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
target/
|
||||
__pycache__/
|
||||
*.egg-info/
|
||||
dist/
|
||||
build/
|
||||
@@ -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"
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../LICENSE
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../README.md
|
||||
Executable
+163
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
"""缠论技术分析库 — Rust 高性能实现"""
|
||||
|
||||
from ._chanlun import *
|
||||
@@ -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"
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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<K线Py> {
|
||||
self.inner.失效K线.as_ref().map(|k| K线Py {
|
||||
inner: (**k).clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 终结K线(&self) -> Option<K线Py> {
|
||||
self.inner.终结K线.as_ref().map(|k| K线Py {
|
||||
inner: (**k).clone(),
|
||||
})
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 破位值(&self) -> f64 {
|
||||
self.inner.破位值
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 结构(&self) -> Option<crate::types_py::分型结构Py> {
|
||||
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<Self> {
|
||||
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<K线Py> {
|
||||
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<Self> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<i64>) -> Self {
|
||||
Self {
|
||||
inner: chanlun::business::synthesizer::K线合成器::new(标识, 周期组),
|
||||
}
|
||||
}
|
||||
|
||||
fn 投喂K线(
|
||||
&mut self,
|
||||
普K: &Bound<'_, K线Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Vec<(i64, K线Py)>> {
|
||||
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<K线Py> {
|
||||
self.inner
|
||||
.获取当前K线(周期)
|
||||
.map(|k| K线Py { inner: k.clone() })
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 标识(&self) -> String {
|
||||
self.inner.标识.clone()
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 周期组(&self) -> Vec<i64> {
|
||||
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<i64>,
|
||||
配置: Option<&Bound<'_, 缠论配置Py>>,
|
||||
配置组: Option<&Bound<'_, PyAny>>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<Self> {
|
||||
let config = match 配置 {
|
||||
Some(cfg) => Some(cfg.borrow().to_rust_config(py)?),
|
||||
None => None,
|
||||
};
|
||||
let cfg_map: Option<HashMap<i64, chanlun::config::缠论配置>> = match 配置组 {
|
||||
Some(dict_any) => {
|
||||
let dict = dict_any.downcast::<pyo3::types::PyDict>()?;
|
||||
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<i64> {
|
||||
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::<K线合成器Py>()?;
|
||||
m.add_class::<立体分析器Py>()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -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<String, Py<PyAny>>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 缠论配置Py {
|
||||
#[new]
|
||||
#[pyo3(signature = (**kwargs))]
|
||||
fn new(kwargs: Option<&Bound<'_, PyDict>>) -> PyResult<Self> {
|
||||
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<Py<PyAny>> {
|
||||
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<Py<PyDict>> {
|
||||
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<String> {
|
||||
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<Self> {
|
||||
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<Self> {
|
||||
let default_config = chanlun::config::缠论配置::default();
|
||||
let mut fields = config_to_field_dict(&default_config)?;
|
||||
|
||||
for (key, value) in data.iter() {
|
||||
let key: String = key.extract()?;
|
||||
fields.insert(key, value.clone().unbind());
|
||||
}
|
||||
|
||||
dict_to_rust_config(&fields)?;
|
||||
Ok(Self { fields })
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn from_json(_cls: &Bound<'_, PyType>, py: Python<'_>, json_str: &str) -> PyResult<Self> {
|
||||
Self::from_json_str(py, json_str)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 不推送(_cls: &Bound<'_, PyType>) -> PyResult<Self> {
|
||||
let config = chanlun::config::缠论配置::default().不推送();
|
||||
let fields = config_to_field_dict(&config)?;
|
||||
Ok(Self { fields })
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 按序号重组字典(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
默认配置: &Bound<'_, PyAny>,
|
||||
原始字典: &Bound<'_, PyDict>,
|
||||
) -> PyResult<Py<PyDict>> {
|
||||
let py = 原始字典.py();
|
||||
let result = PyDict::new(py);
|
||||
if let Ok(default_dict) = 默认配置.downcast::<PyDict>() {
|
||||
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<HashMap<String, (Py<PyAny>, Py<PyAny>)>> {
|
||||
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<Self> {
|
||||
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<String, Py<PyAny>> = 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<chanlun::config::缠论配置> {
|
||||
dict_to_rust_config(&self.fields)
|
||||
}
|
||||
}
|
||||
|
||||
fn config_to_field_dict(
|
||||
config: &chanlun::config::缠论配置,
|
||||
) -> PyResult<HashMap<String, Py<PyAny>>> {
|
||||
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<String, Py<PyAny>>,
|
||||
) -> PyResult<chanlun::config::缠论配置> {
|
||||
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(())
|
||||
}
|
||||
@@ -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<f64> {
|
||||
self.inner.DIF
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn DEA(&self) -> Option<f64> {
|
||||
self.inner.DEA
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn MACD柱(&self) -> f64 {
|
||||
self.inner.MACD柱
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 快线EMA(&self) -> Option<f64> {
|
||||
self.inner.快线EMA
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 慢线EMA(&self) -> Option<f64> {
|
||||
self.inner.慢线EMA
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn DEA_EMA(&self) -> Option<f64> {
|
||||
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<i64>,
|
||||
慢线周期: Option<i64>,
|
||||
信号周期: Option<i64>,
|
||||
) -> 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<i64>,
|
||||
慢线周期: Option<i64>,
|
||||
信号周期: Option<i64>,
|
||||
) -> PyResult<Self> {
|
||||
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<Self> {
|
||||
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<i64> {
|
||||
self.inner.RSI_SMA周期
|
||||
}
|
||||
#[getter]
|
||||
fn RSI(&self) -> Option<f64> {
|
||||
self.inner.RSI
|
||||
}
|
||||
#[getter]
|
||||
fn 平均上涨(&self) -> Option<f64> {
|
||||
self.inner.平均上涨
|
||||
}
|
||||
#[getter]
|
||||
fn 平均下跌(&self) -> Option<f64> {
|
||||
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<f64> {
|
||||
self.inner.RSI_SMA
|
||||
}
|
||||
#[getter]
|
||||
fn RSI历史队列(&self) -> Vec<f64> {
|
||||
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<i64>,
|
||||
超买阈值: Option<f64>,
|
||||
超卖阈值: Option<f64>,
|
||||
RSI_SMA周期: Option<i64>,
|
||||
) -> 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<i64>,
|
||||
超买阈值: Option<f64>,
|
||||
超卖阈值: Option<f64>,
|
||||
RSI_SMA周期: Option<i64>,
|
||||
) -> PyResult<Self> {
|
||||
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<Self> {
|
||||
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<f64> {
|
||||
self.inner.RSV
|
||||
}
|
||||
#[getter]
|
||||
fn K(&self) -> Option<f64> {
|
||||
self.inner.K
|
||||
}
|
||||
#[getter]
|
||||
fn D(&self) -> Option<f64> {
|
||||
self.inner.D
|
||||
}
|
||||
#[getter]
|
||||
fn J(&self) -> Option<f64> {
|
||||
self.inner.J
|
||||
}
|
||||
#[getter]
|
||||
fn 历史最高价队列(&self) -> Vec<f64> {
|
||||
self.inner.历史最高价队列.clone()
|
||||
}
|
||||
#[getter]
|
||||
fn 历史最低价队列(&self) -> Vec<f64> {
|
||||
self.inner.历史最低价队列.clone()
|
||||
}
|
||||
#[getter]
|
||||
fn 前一个RSV(&self) -> Option<f64> {
|
||||
self.inner.前一个RSV
|
||||
}
|
||||
#[getter]
|
||||
fn 前一个K(&self) -> Option<f64> {
|
||||
self.inner.前一个K
|
||||
}
|
||||
#[getter]
|
||||
fn 前一个D(&self) -> Option<f64> {
|
||||
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<i64>,
|
||||
M1: Option<i64>,
|
||||
M2: Option<i64>,
|
||||
超买阈值: Option<f64>,
|
||||
超卖阈值: Option<f64>,
|
||||
) -> 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<i64>,
|
||||
K值平滑周期: Option<i64>,
|
||||
D值平滑周期: Option<i64>,
|
||||
超买阈值: Option<f64>,
|
||||
超卖阈值: Option<f64>,
|
||||
) -> PyResult<Self> {
|
||||
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<Self> {
|
||||
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<f64> {
|
||||
K线取值(k线, 指标计算方式)
|
||||
}
|
||||
}
|
||||
|
||||
// ========== Helper functions ==========
|
||||
|
||||
pub(crate) fn K线取值(k线: &Bound<'_, PyAny>, 计算方式: &str) -> PyResult<f64> {
|
||||
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<i64> {
|
||||
let ts = k线.getattr("时间戳")?;
|
||||
if let Ok(i) = ts.extract::<i64>() {
|
||||
return Ok(i);
|
||||
}
|
||||
// Try float
|
||||
if let Ok(f) = ts.extract::<f64>() {
|
||||
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(())
|
||||
}
|
||||
@@ -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> {
|
||||
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<i64>,
|
||||
周期: Option<i64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
inner: chanlun::kline::bar::K线::创建普K(
|
||||
标识,
|
||||
时间戳,
|
||||
开盘价,
|
||||
最高价,
|
||||
最低价,
|
||||
收盘价,
|
||||
成交量,
|
||||
序号.unwrap_or(0),
|
||||
周期.unwrap_or(60),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 保存到DAT文件(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
路径: &str,
|
||||
K线序列: Vec<Py<Self>>,
|
||||
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<Self> {
|
||||
chanlun::kline::bar::K线::读取大端字节数组(字节组.as_bytes(), 周期, 标识)
|
||||
.map(|inner| Self { inner })
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 获取MACD(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
k线序列: Vec<Py<Self>>,
|
||||
始: &Bound<'_, Self>,
|
||||
终: &Bound<'_, Self>,
|
||||
py: Python<'_>,
|
||||
) -> HashMap<String, f64> {
|
||||
let refs: Vec<_> = k线序列.iter().map(|k| k.bind(py).borrow()).collect();
|
||||
let bars: Vec<&chanlun::kline::bar::K线> = refs.iter().map(|r| &r.inner).collect();
|
||||
chanlun::kline::bar::K线::获取MACD(&bars, &始.borrow().inner, &终.borrow().inner)
|
||||
}
|
||||
|
||||
#[staticmethod]
|
||||
fn 截取(
|
||||
序列: Vec<Py<Self>>,
|
||||
始: &Bound<'_, Self>,
|
||||
终: &Bound<'_, Self>,
|
||||
) -> Option<Vec<Py<Self>>> {
|
||||
let start_ptr = 始.as_ptr();
|
||||
let end_ptr = 终.as_ptr();
|
||||
let start_idx = 序列.iter().position(|k| k.as_ptr() == start_ptr)?;
|
||||
let end_idx = 序列.iter().position(|k| k.as_ptr() == end_ptr)?;
|
||||
if start_idx <= end_idx {
|
||||
Some(
|
||||
序列
|
||||
.into_iter()
|
||||
.skip(start_idx)
|
||||
.take(end_idx - start_idx + 1)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ========== 缠论K线 ==========
|
||||
|
||||
#[pyclass(name = "缠论K线", unsendable)]
|
||||
#[derive(Clone)]
|
||||
pub struct 缠论K线Py {
|
||||
pub(crate) inner: std::rc::Rc<chanlun::kline::chan_kline::缠论K线>,
|
||||
}
|
||||
|
||||
#[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<crate::types_py::分型结构Py> {
|
||||
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<Py<Self>>,
|
||||
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<Self>, Option<String>)> {
|
||||
let mut ck_inner = (*当前缠K.borrow().inner).clone();
|
||||
let config = 配置.borrow().to_rust_config(py)?;
|
||||
let prev_ref = 之前缠K.map(|prev| prev.borrow());
|
||||
let prev_inner = prev_ref.as_ref().map(|r| r.inner.as_ref());
|
||||
let (result, mode) = chanlun::kline::chan_kline::缠论K线::兼并(
|
||||
prev_inner,
|
||||
&mut ck_inner,
|
||||
&当前普K.borrow().inner,
|
||||
&config,
|
||||
);
|
||||
Ok((result.map(|rc| Self { inner: rc }), mode))
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 分析(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
当前K线: &Bound<'_, K线Py>,
|
||||
缠K序列: Vec<Py<Self>>,
|
||||
普K序列: Vec<Py<K线Py>>,
|
||||
配置: &Bound<'_, 缠论配置Py>,
|
||||
py: Python<'_>,
|
||||
) -> PyResult<(String, Option<Py<PyAny>>)> {
|
||||
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<Py<Self>>,
|
||||
始: &Bound<'_, Self>,
|
||||
终: &Bound<'_, Self>,
|
||||
) -> Option<Vec<Py<Self>>> {
|
||||
let start_ptr = 始.as_ptr();
|
||||
let end_ptr = 终.as_ptr();
|
||||
let start_idx = 序列.iter().position(|k| k.as_ptr() == start_ptr)?;
|
||||
let end_idx = 序列.iter().position(|k| k.as_ptr() == end_ptr)?;
|
||||
if start_idx <= end_idx {
|
||||
Some(
|
||||
序列
|
||||
.into_iter()
|
||||
.skip(start_idx)
|
||||
.take(end_idx - start_idx + 1)
|
||||
.collect(),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
|
||||
m.add_class::<K线Py>()?;
|
||||
m.add_class::<缠论K线Py>()?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -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(())
|
||||
}
|
||||
@@ -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<chanlun::structure::fractal_obj::分型>,
|
||||
}
|
||||
|
||||
#[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<Py<缠论K线Py>>,
|
||||
中: &Bound<'_, 缠论K线Py>,
|
||||
py: Python<'_>,
|
||||
) -> Option<Self> {
|
||||
let ck_seq: Vec<Rc<chanlun::kline::chan_kline::缠论K线>> = 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<chanlun::structure::dash_line::虚线>,
|
||||
}
|
||||
|
||||
#[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> {
|
||||
self.inner.前一结束位置.as_ref().map(|d| Self {
|
||||
inner: Rc::clone(d),
|
||||
})
|
||||
}
|
||||
|
||||
// ---- 序列 getters ----
|
||||
|
||||
#[getter]
|
||||
fn 基础序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
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<Py<PyAny>> {
|
||||
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<K线Py>>, py: Python<'_>) -> PyResult<Py<PyAny>> {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普K序列
|
||||
.iter()
|
||||
.map(|k| Rc::new(k.bind(py).borrow().inner.clone()))
|
||||
.collect();
|
||||
let result = self.inner.获取普K序列(&rc_list);
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for k in &result {
|
||||
list.append(K线Py {
|
||||
inner: (**k).clone(),
|
||||
})?;
|
||||
}
|
||||
Ok(list.into())
|
||||
}
|
||||
|
||||
fn 获取缠K序列(
|
||||
&self, 缠K序列: Vec<Py<缠论K线Py>>, py: Python<'_>
|
||||
) -> PyResult<Py<PyAny>> {
|
||||
let rc_list: Vec<Rc<chanlun::kline::chan_kline::缠论K线>> = 缠K序列
|
||||
.iter()
|
||||
.map(|k| Rc::clone(&k.bind(py).borrow().inner))
|
||||
.collect();
|
||||
let result = self.inner.获取缠K序列(&rc_list);
|
||||
let list = pyo3::types::PyList::empty(py);
|
||||
for k in &result {
|
||||
list.append(缠论K线Py {
|
||||
inner: Rc::clone(k),
|
||||
})?;
|
||||
}
|
||||
Ok(list.into())
|
||||
}
|
||||
|
||||
#[getter]
|
||||
fn 获取数据文本(&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<Self>>, py: Python<'_>) -> Self {
|
||||
let rc_list: Vec<Rc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.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<bool> {
|
||||
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<bool> {
|
||||
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<Py<K线Py>>,
|
||||
实线: &Bound<'_, Self>,
|
||||
py: Python<'_>,
|
||||
) -> f64 {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普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<Py<K线Py>>,
|
||||
实线: &Bound<'_, Self>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普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<Py<K线Py>>,
|
||||
实线: &Bound<'_, Self>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普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<Py<K线Py>>,
|
||||
实线: &Bound<'_, Self>,
|
||||
py: Python<'_>,
|
||||
) -> bool {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普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<Py<K线Py>>,
|
||||
方向: &Bound<'_, 相对方向Py>,
|
||||
py: Python<'_>,
|
||||
) -> [bool; 3] {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普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<K线Py>>,
|
||||
py: Python<'_>,
|
||||
) -> Vec<Vec<f64>> {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 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<i32>,
|
||||
最大间隔: usize,
|
||||
最少交叉数: usize,
|
||||
) -> Vec<(usize, usize, usize)> {
|
||||
chanlun::structure::dash_line::虚线::密集区域按间隔(
|
||||
&交叉标记,
|
||||
最大间隔,
|
||||
最少交叉数,
|
||||
)
|
||||
}
|
||||
|
||||
#[classmethod]
|
||||
fn 统计MACD行为(
|
||||
_cls: &Bound<'_, PyType>,
|
||||
普K序列: Vec<Py<K线Py>>,
|
||||
最大间隔: usize,
|
||||
最少交叉数: usize,
|
||||
py: Python<'_>,
|
||||
) -> HashMap<String, String> {
|
||||
let rc_list: Vec<Rc<chanlun::kline::bar::K线>> = 普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<chanlun::structure::segment_feat::线段特征>,
|
||||
}
|
||||
|
||||
#[pymethods]
|
||||
impl 线段特征Py {
|
||||
#[new]
|
||||
fn new(
|
||||
标识: String,
|
||||
基础序列: Vec<Py<虚线Py>>,
|
||||
线段方向: &Bound<'_, 相对方向Py>,
|
||||
py: Python<'_>,
|
||||
) -> Self {
|
||||
let rc_list: Vec<Rc<chanlun::structure::dash_line::虚线>> = 基础序列
|
||||
.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<Py<PyAny>> {
|
||||
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<Py<虚线Py>>,
|
||||
线段方向: &Bound<'_, 相对方向Py>,
|
||||
py: Python<'_>,
|
||||
) -> Self {
|
||||
let rc_list: Vec<Rc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.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<Py<虚线Py>>,
|
||||
线段方向: &Bound<'_, 相对方向Py>,
|
||||
四象: &str,
|
||||
是否忽视: bool,
|
||||
py: Python<'_>,
|
||||
) -> Vec<Self> {
|
||||
let rc_list: Vec<Rc<chanlun::structure::dash_line::虚线>> = 虚线序列
|
||||
.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<Self>>,
|
||||
py: Python<'_>,
|
||||
) -> Vec<特征分型Py> {
|
||||
let rc_list: Vec<Rc<chanlun::structure::segment_feat::线段特征>> = 特征序列
|
||||
.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<chanlun::structure::feat_fractal::特征分型>,
|
||||
}
|
||||
|
||||
#[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(())
|
||||
}
|
||||
@@ -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::<String>() {
|
||||
return self.inner.to_string() == s;
|
||||
}
|
||||
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
|
||||
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::<PyRef<'_, Self>>() {
|
||||
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::<PyRef<'_, Self>>() {
|
||||
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<bool>,
|
||||
忽视顺序包含: Option<bool>,
|
||||
) -> PyResult<Option<Self>> {
|
||||
let 可以逆序包含 = 可以逆序包含.unwrap_or(false);
|
||||
let 忽视顺序包含 = 忽视顺序包含.unwrap_or(false);
|
||||
|
||||
let get_hl = |obj: &Bound<'_, PyAny>| -> PyResult<(f64, f64)> {
|
||||
Ok((
|
||||
obj.getattr("高")?.extract::<f64>()?,
|
||||
obj.getattr("低")?.extract::<f64>()?,
|
||||
))
|
||||
};
|
||||
|
||||
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<Self> {
|
||||
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<f64>,
|
||||
) -> Option<Self> {
|
||||
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::<PyType>()?;
|
||||
|
||||
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::<PyType>()?.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::<PyType>()?.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<i64> {
|
||||
// Try int first
|
||||
if let Ok(v) = ts.extract::<i64>() {
|
||||
return Ok(v);
|
||||
}
|
||||
// Try float
|
||||
if let Ok(v) = ts.extract::<f64>() {
|
||||
return Ok(v as i64);
|
||||
}
|
||||
// Try string (ISO 8601 or "YYYY-MM-DD HH:MM:SS")
|
||||
if let Ok(s) = ts.extract::<String>() {
|
||||
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<i64> {
|
||||
转化为时间戳(ts)
|
||||
}
|
||||
|
||||
fn parse_datetime_string(s: &str) -> PyResult<i64> {
|
||||
// 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}"
|
||||
)))
|
||||
}
|
||||
@@ -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())
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "0.1.0"
|
||||
version = "26.5.1"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
|
||||
Reference in New Issue
Block a user