第九版

This commit is contained in:
YuWuKunCheng
2026-06-05 01:55:43 +08:00
parent 58c85dc7cd
commit 823a24d364
49 changed files with 7280 additions and 7442 deletions
+2
View File
@@ -3,3 +3,5 @@ __pycache__/
*.egg-info/
dist/
build/
Cargo.lock
+6 -3
View File
@@ -1,7 +1,7 @@
[package]
name = "chanlun-py"
version = "26.5.103"
edition = "2021"
version = "26.6.17"
edition = "2024"
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
authors = ["YuYuKunKun"]
license = "MIT"
@@ -12,7 +12,10 @@ crate-type = ["cdylib"]
name = "chanlun"
[dependencies]
chanlun = "26.5.6" # { path = "../chanlun" }
chanlun = "26.6.1" # { path = "../chanlun" }
pyo3 = { version = "0.28", features = ["experimental-inspect"] }
serde_json = "1"
chrono = "0.4"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["env-filter", "ansi", "std", "registry"] }
tracing-core = "0.1"
-1
View File
@@ -1 +0,0 @@
../LICENSE
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 YuYuKunKun
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+3 -1
View File
@@ -74,4 +74,6 @@ pip install target/wheels/chanlun-*.whl
## 许可
MIT
本项目主体采用 MIT 许可。包含以下第三方开源代码:czsc(Apache 2.0)、parseMIT)、termcolorMIT)。
详见 [NOTICE](../NOTICE) 和 [LICENSES/](../LICENSES/) 目录。
+4 -4
View File
@@ -58,10 +58,10 @@ fn read_version(path: &std::path::Path, section: &str) -> String {
if !in_section {
continue;
}
if trimmed.starts_with("version") {
if let Some(v) = trimmed.split('=').nth(1) {
return v.trim().trim_matches('"').trim().to_string();
}
if trimmed.starts_with("version")
&& let Some(v) = trimmed.split('=').nth(1)
{
return v.trim().trim_matches('"').trim().to_string();
}
}
panic!("Cannot parse version from {:?}", path);
Executable → Regular
View File
Executable → Regular
View File
+751 -2234
View File
File diff suppressed because it is too large Load Diff
+27 -1
View File
@@ -11,7 +11,10 @@ __all__ = [
"基础买卖点",
"平滑异同移动平均线",
"指标",
"特征分型",
"指标容器",
"指标计算器",
"均线工具",
"测试_读取数据",
"相对强弱指数",
"相对方向",
"立体分析器",
@@ -27,8 +30,31 @@ __all__ = [
"转化为时间戳",
"转化为时间戳_数字",
"随机指标",
"布林带",
"get_分型模式",
"set_分型模式",
"get_log_level",
"set_log_level",
"get_rs_log_level",
"set_rs_log_level",
"chan",
]
from ._chanlun import *
from ._chanlun import set_log_level as _rs_set_log_level, get_log_level as _rs_get_log_level
from . import chan
from .chan import 测试_读取数据, 转化为时间戳, 转化为时间戳_数字, set_log_level, get_log_level
def set_rs_log_level(level: str):
"""设置 Rust 侧日志级别 (trace / debug / info / warn / error / off)
仅控制 Rust tracing 日志,不影响 Python loguru 日志。
Python 侧日志通过 set_log_level() 独立控制。
"""
_rs_set_log_level(level)
def get_rs_log_level() -> str:
"""获取 Rust 侧日志级别"""
return _rs_get_log_level()
File diff suppressed because it is too large Load Diff
+1537 -719
View File
File diff suppressed because it is too large Load Diff
+14 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "chanlun"
version = "2605.103"
version = "2606.17"
description = "缠论技术分析库 — Rust 高性能实现"
readme = { file = "README.md", content-type = "text/markdown" }
license = { file = "LICENSE", content-type = "text/plain" }
@@ -26,6 +26,11 @@ classifiers = [
"Topic :: Office/Business :: Financial :: Investment",
]
requires-python = ">=3.9"
dependencies = [
"termcolor>=3.0",
"loguru>=0.6",
"backtrader==1.9.78.123",
]
[project.urls]
Homepage = "https://github.com/YuYuKunKun/chanlun.rs"
@@ -41,3 +46,11 @@ manifest-path = "Cargo.toml"
[tool.pytest.ini_options]
testpaths = ["tests"]
addopts = ["-v", "--tb=short", "--durations=10"]
[tool.ruff.lint]
# 启用 FA (flake8-future-annotations) 和 UP (pyupgrade) 规则
select = ["FA", "UP"]
# [tool.ruff.lint.flake8-future-annotations]
# 强制在所有文件中注入 from __future__ import annotations
# force-future-annotations = true
+32 -566
View File
@@ -27,9 +27,9 @@ use crate::structure_py::{dashed_to_py, fractal_to_py};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering;
// 使用全局 static 而非 thread_local!,保证跨线程对象标识一致性
static HUB_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<Py>>>> =
@@ -58,8 +58,8 @@ pub(crate) fn hub_to_py(
use crate::business_py::Py;
use crate::config_py::Py;
use crate::kline_py::{K线Py, K线Py};
use crate::structure_py::{Py, 线Py, 线Py};
use crate::kline_py::{K线Py, K线Py};
use crate::structure_py::{Py, 线Py};
use crate::types_py::Py;
// ========== 背驰分析 ==========
@@ -241,122 +241,20 @@ impl 背驰分析Py {
/// 笔 — 静态方法容器,提供笔的创建与分析算法。
///
/// 所有方法均为 staticmethod,直接调用无需实例化。
/// 所有方法均为 staticmethod/classmethod,直接调用无需实例化。
///
/// 方法:
/// 创建(左分型, 右分型, 序号, 级别, 有效性?, 标识?) -> 虚线
/// — 在左右分型之间创建一
/// 分析(分型序列, 配置, 序?, 初级序列?, 级别?) -> list[虚线]
/// — 从分型序列中划分出所有笔
/// 弱化(笔序列) — 根据配置对笔序列执行弱化处理
/// 分析前检查(分型序列, 配置) -> bool — 检查是否可以启动笔分析
/// :meth:`以文会友` — 根据起始分型找笔
/// :meth:`以武会友` — 根据结束分型找
/// :meth:`根据缠K找笔` — 判断缠K是否在笔的文武序号之间
/// :meth:`分析` — 笔划分核心递归算法
/// :meth:`获取所有停顿位置` — 获取笔内所有可能的停顿位置(用于背驰检测)
/// :meth:`是否背驰过` — 判断笔内是否发生过MACD趋向背驰
#[pyclass(name = "", module = "chanlun._chanlun")]
pub struct Py;
#[pymethods]
impl Py {
#[classmethod]
/// 获取笔内有效缠K数量(考虑笔弱化等配置)
fn K数量(
_cls: &Bound<'_, PyType>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
: Vec<Py<线Py>>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<usize> {
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
.collect();
let bi_list: Vec<Arc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Arc::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<Py<crate::kline_py::K线Py>>,
: bool,
py: Python<'_>,
) -> Option<Py<crate::kline_py::K线Py>> {
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
}
#[classmethod]
/// 次低
fn (
_cls: &Bound<'_, PyType>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
: bool,
py: Python<'_>,
) -> Option<Py<crate::kline_py::K线Py>> {
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
}
#[classmethod]
/// 实际高点
fn (
_cls: &Bound<'_, PyType>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
: bool,
py: Python<'_>,
) -> Option<Py<crate::kline_py::K线Py>> {
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
}
#[classmethod]
/// 实际低点
fn (
_cls: &Bound<'_, PyType>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
: bool,
py: Python<'_>,
) -> Option<Py<crate::kline_py::K线Py>> {
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
.collect();
chanlun::algorithm::bi::::(&ck_list, )
.map(|inner| crate::kline_py::chan_kline_to_py(py, inner))
}
#[classmethod]
/// 相对关系
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<bool> {
let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::bi::::(
&.borrow().inner,
&config,
))
}
#[classmethod]
/// 以文会友
fn (
@@ -454,58 +352,6 @@ impl 笔Py {
}
}
#[classmethod]
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
: Vec<Py<Py>>,
: Vec<Py<线Py>>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
K序列: Vec<Py<K线Py>>,
: i64,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<i64> {
let mut fr_seq: Vec<Arc<chanlun::structure::fractal_obj::>> =
.iter()
.map(|f| Arc::clone(&f.bind(py).borrow().inner))
.collect();
let mut bi_seq: Vec<Arc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
.collect();
let bar_list: Vec<Arc<chanlun::kline::bar::K线>> = K序列
.iter()
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let config = .borrow().to_rust_config(py)?;
Ok(chanlun::algorithm::bi::::(
Arc::clone(&.borrow().inner),
&mut fr_seq,
&mut bi_seq,
&ck_list,
&bar_list,
,
&config,
))
}
#[classmethod]
/// 校验笔的有效性:高/低点是否为实际极值
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
) -> bool {
let obs = .borrow();
let obs_ref = obs.obs();
chanlun::algorithm::bi::::(&.borrow().inner, &obs_ref)
}
#[classmethod]
/// 获取笔内所有可能的停顿位置(用于背驰检测)
fn (
@@ -552,108 +398,13 @@ impl 笔Py {
///
/// 辅助方法:
/// 扩展分析(基础序列, 扩展结果容器, 配置) — 从基础序列生成扩展分析
/// 特征序列分析(特征序列, 中枢序列, 配置, 标识?) — 分析特征序列产生中枢
/// 分析前检查(分型序列, 配置) -> bool
/// 四象(虚线段) -> str — 返回"少阳"/"少阴"/"老阳"/"老阴"
/// 特征序列状态(虚线段) -> str
/// 虚线段特征序列成立(虚线段, 虚线序列, 配置) -> bool
/// 移除包含后的特征序列(特征序列, 配置) -> list[线段特征]
/// 检查特征序列趋势(特征序列) -> bool
#[pyclass(name = "线段", module = "chanlun._chanlun")]
pub struct 线Py;
#[pymethods]
impl 线Py {
#[classmethod]
/// 向线段中添加一笔
fn 线(
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
) -> PyResult<()> {
let bi_rc = Arc::clone(&.borrow().inner);
let ref_mut = .borrow_mut();
chanlun::algorithm::segment::线::线(&ref_mut.inner, bi_rc);
Ok(())
}
#[classmethod]
/// 更新线段的终点分型(武)
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
: u32,
) -> PyResult<()> {
let ref_mut = .borrow_mut();
chanlun::algorithm::segment::线::(
&ref_mut.inner,
&Arc::clone(&.borrow().inner),
,
);
Ok(())
}
#[classmethod]
/// 武终
fn (_cls: &Bound<'_, PyType>, : &Bound<'_, 线Py>, : u32) -> PyResult<()> {
let ref_mut = .borrow_mut();
chanlun::algorithm::segment::线::(&ref_mut.inner, );
Ok(())
}
#[classmethod]
/// 验证序列
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: Vec<Py<线Py>>,
py: Python<'_>,
) -> PyResult<()> {
let ref_mut = .borrow_mut();
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
chanlun::algorithm::segment::线::(&ref_mut.inner, &rc_list);
Ok(())
}
#[classmethod]
/// 序列重置
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: Vec<Py<线Py>>,
py: Python<'_>,
) -> PyResult<()> {
let ref_mut = .borrow_mut();
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
chanlun::algorithm::segment::线::(&ref_mut.inner, &rc_list);
Ok(())
}
#[classmethod]
/// 连续三笔且重叠
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: Vec<Py>,
) -> bool {
let rel_list: Vec<chanlun::types::> = .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 {
@@ -684,52 +435,6 @@ impl 线段Py {
chanlun::algorithm::segment::线::(&.borrow().inner)
}
#[classmethod]
/// 设置特征序列
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, PyAny>,
: u32,
) -> PyResult<()> {
let seq: Vec<Option<Arc<chanlun::structure::segment_feat::线>>> = if .is_none()
{
vec![]
} else if let Ok(list) = .cast::<pyo3::types::PyList>() {
let mut result = Vec::with_capacity(list.len());
for item in list.iter() {
if item.is_none() {
result.push(None);
} else {
let feat: PyRef<'_, 线Py> = item.extract()?;
result.push(Some(Arc::clone(&feat.inner)));
}
}
result
} else {
return Err(pyo3::exceptions::PyTypeError::new_err(
"序列 必须是 list 或 None",
));
};
let ref_mut = .borrow_mut();
chanlun::algorithm::segment::线::(&ref_mut.inner, seq, );
Ok(())
}
#[classmethod]
/// 刷新特征序列
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<()> {
let ref_mut = .borrow_mut();
let config = .borrow().to_rust_config(py)?;
chanlun::algorithm::segment::线::(&ref_mut.inner, &config);
Ok(())
}
#[classmethod]
/// 查找贯穿伤
fn 穿(
@@ -774,20 +479,6 @@ impl 线段Py {
Ok((wrap(a), wrap(b), wrap(c), d.map(|x| dashed_to_py(py, x))))
}
#[classmethod]
/// 刷新线段的特征序列和内部中枢序列
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<()> {
let ref_mut = .borrow_mut();
let config = .borrow().to_rust_config(py)?;
chanlun::algorithm::segment::线::(&ref_mut.inner, &config);
Ok(())
}
#[classmethod]
/// 获取内部中枢序列
fn (
@@ -811,133 +502,6 @@ impl 线段Py {
}
#[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<Arc<chanlun::structure::dash_line::线>> = vec![];
chanlun::algorithm::segment::线::_添加线段(
&mut seg_seq,
Arc::clone(&线.borrow().inner),
&config,
,
);
Ok(())
}
#[classmethod]
/// 内部方法:从线段序列弹出最后一个线段
fn _弹出线段(
_cls: &Bound<'_, PyType>,
_线段序列: &Bound<'_, PyAny>,
线: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
: String,
py: Python<'_>,
) -> PyResult<Option<Py<线Py>>> {
let config = .borrow().to_rust_config(py)?;
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = vec![];
let result = chanlun::algorithm::segment::线::_弹出线段(
&mut seg_seq,
&Arc::clone(&线.borrow().inner),
&config,
,
);
Ok(result.map(|inner| dashed_to_py(py, inner)))
}
#[classmethod]
/// 内部方法:处理缺口突破修正
fn _缺口突破(
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
: &Bound<'_, Py>,
: i64,
py: Python<'_>,
) -> PyResult<bool> {
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::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<Py<线Py>>,
: &Bound<'_, Py>,
: i64,
py: Python<'_>,
) -> PyResult<bool> {
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::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<Py<线Py>>,
: &Bound<'_, Py>,
: i64,
py: Python<'_>,
) -> PyResult<bool> {
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::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<Py<线Py>>,
: &Bound<'_, Py>,
: i64,
py: Python<'_>,
) -> PyResult<bool> {
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::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>,
@@ -974,48 +538,6 @@ impl 线段Py {
Ok(())
}
#[classmethod]
/// 内部方法:向扩展线段序列添加新线段
fn _添加扩展线段(
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
线: &Bound<'_, 线Py>,
: u32,
py: Python<'_>,
) -> PyResult<()> {
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
chanlun::algorithm::segment::线::_添加扩展线段(
&mut seg_seq,
Arc::clone(&线.borrow().inner),
,
);
Ok(())
}
#[classmethod]
/// 内部方法:从扩展线段序列弹出最后一个线段
fn _弹出扩展线段(
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
线: &Bound<'_, 线Py>,
: u32,
py: Python<'_>,
) -> PyResult<Option<Py<线Py>>> {
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let result = chanlun::algorithm::segment::线::_弹出扩展线段(
&mut seg_seq,
&Arc::clone(&线.borrow().inner),
,
);
Ok(result.map(|inner| dashed_to_py(py, inner)))
}
#[classmethod]
/// 即同级别分析
fn (
@@ -1111,7 +633,6 @@ impl 线段Py {
/// 添加虚线(虚线) — 向中枢追加虚线(延伸)
/// 获取序列() -> list[虚线] — 基础序列 + 第三买卖线
/// 获取扩展中枢(扩展中枢列表, 配置) — 基础序列 >= 9 时生成扩展中枢
/// 校验合法性(虚线序列) -> bool — 根据当前虚线序列校验中枢合法性
/// 设置第三买卖线(虚线|None) — 设置第三类买卖确认线
/// 完整性(虚实="合") -> bool — 中枢是否完整(已有第三买卖线确认)
/// 当前状态() -> str — "中枢之上"/"中枢之下"/"中枢之中"
@@ -1122,8 +643,6 @@ impl 线段Py {
/// 创建(左虚线, 中虚线, 右虚线, 级别, 标识?) -> 中枢 — 创建新中枢
/// 从序列中获取中枢(虚线序列, 允许重叠?, 标识前缀?, 高级中枢容器?...) -> list[中枢]
/// — 扫描虚线序列提取所有中枢
/// 向中枢序列尾部添加(中枢序列, 新中枢) — 维护中枢序列顺序
/// 从中枢序列尾部弹出(中枢序列, 配置) — 弹出最后一个中枢并更新相邻中枢
/// 分析(虚线序列, 中枢序列容器, 允许重叠?, 标识前缀?, ...) — 中枢分析主流程
#[pyclass(name = "中枢", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
@@ -1192,11 +711,6 @@ impl 中枢Py {
.map(|d| dashed_to_py(py, Arc::clone(d)))
}
/// 向中枢添加新虚线(延伸),重置第三买卖线
fn 线(&self, 线: &Bound<'_, 线Py>) {
self.inner.线(Arc::clone(&线.borrow().inner));
}
#[getter]
/// :return: 图表标题
fn (&self) -> String {
@@ -1254,7 +768,7 @@ impl 中枢Py {
/// 设置第三类买卖点关联虚线
fn 线(&mut self, 线: &Bound<'_, 线Py>) {
let inner = Arc::make_mut(&mut self.inner);
inner.线(Arc::clone(&线.borrow().inner));
inner.线(Some(Arc::clone(&线.borrow().inner)));
}
/// 获取中枢的完整虚线序列(基础序列+第三买卖线)
@@ -1271,23 +785,6 @@ impl 中枢Py {
self.inner.()
}
#[pyo3(signature = (序列, 中枢序列 = None))]
/// 校验当前中枢在给定序列中是否仍然合法
fn (
&mut self,
: Vec<Py<线Py>>,
: Option<Vec<Py<线Py>>>,
py: Python<'_>,
) -> bool {
let _ = ; // Python 版声明了此参数但未使用
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let inner = Arc::make_mut(&mut self.inner);
inner.(&rc_list)
}
#[pyo3(signature = (虚实 = ""))]
/// 判断中枢是否完整(是否有第三买卖点或内部中枢离开)
fn (&self, : &str) -> bool {
@@ -1389,58 +886,6 @@ impl 中枢Py {
hub_to_py(_cls.py(), inner)
}
#[classmethod]
/// 从虚线序列中按起始方向查找第一个中枢
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
: &Bound<'_, Py>,
: &str,
py: Python<'_>,
) -> Option<Py<Py>> {
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
chanlun::algorithm::hub::::(
&rc_list,
.borrow().inner,
,
)
.map(|inner| hub_to_py(py, inner))
}
#[classmethod]
/// :param 中枢序列: 中枢列表
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyAny>,
: &Bound<'_, Self>,
) -> PyResult<()> {
let py = .py();
let inner = Arc::clone(&.borrow().inner);
let wrapper = hub_to_py(py, inner);
.call_method1("append", (wrapper,))?;
Ok(())
}
#[classmethod]
/// :param 中枢序列: 中枢列表
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, PyAny>,
_待弹出中枢: &Bound<'_, Self>,
) -> PyResult<Option<Self>> {
let result = .call_method1("pop", ())?;
if result.is_none() {
return Ok(None);
}
let bound: Bound<'_, Self> = result.extract()?;
Ok(Some(Self {
inner: Arc::clone(&bound.borrow().inner),
}))
}
#[classmethod]
#[pyo3(signature = (虚线序列, 中枢序列, 跳过首部 = true, 标识 = "", 层级 = 0))]
/// 中枢识别核心递归算法
@@ -1464,6 +909,27 @@ impl 中枢Py {
chanlun::algorithm::hub::::(&rc_list, &mut hub_seq, , , );
Ok(())
}
#[classmethod]
/// 从虚线序列中按起始方向查找第一个中枢
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
: &Bound<'_, Py>,
: &str,
py: Python<'_>,
) -> Option<Py<Py>> {
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
chanlun::algorithm::hub::::_从序列中获取中枢(
&rc_list,
.borrow().inner,
,
)
.map(|inner| hub_to_py(py, inner))
}
}
pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
+325 -22
View File
@@ -33,7 +33,7 @@ use std::collections::HashMap;
use std::sync::Arc;
use crate::config_py::Py;
use crate::kline_py::{K线Py, K线Py};
use crate::kline_py::{K线Py, K线Py};
use crate::types_py::Py;
// ========== 基础买卖点 ==========
@@ -74,7 +74,6 @@ impl 基础买卖点Py {
Arc::clone(&.borrow().inner),
,
,
K线.borrow().inner.,
),
}
}
@@ -201,11 +200,14 @@ impl 基础买卖点Py {
// ========== 买卖点 ==========
/// 买卖点 — 继承 基础买卖点,添加工厂类方法。
/// 买卖点 — 继承 基础买卖点,提供一二三类及扩展类型(T1/T1P/T2/T2S/T3A/T3B)的工厂类方法。
///
/// 类方法(均返回 买卖点 实例):
/// 一卖点(...) / 一买点(...) / 二卖点(...) / 二买点(...) / 三卖点(...) / 三买点(...)
/// 生成买卖点(特征, 序号, 级别, 分型, 当前缠K) -> 买卖点
/// :meth:`一卖点` / :meth:`一买点` / :meth:`二卖点` / :meth:`二买点` / :meth:`三卖点` / :meth:`三买点`
/// :meth:`T1卖点` / :meth:`T1买点` / :meth:`T1P卖点` / :meth:`T1P买点`
/// :meth:`T2卖点` / :meth:`T2买点` / :meth:`T2S卖点` / :meth:`T2S买点`
/// :meth:`T3A卖点` / :meth:`T3A买点` / :meth:`T3B卖点` / :meth:`T3B买点`
/// :meth:`生成买卖点` — 根据特征和序号路由到对应构造函数
#[pyclass(name = "买卖点", module = "chanlun._chanlun", extends=基础买卖点Py)]
pub struct Py;
@@ -228,7 +230,6 @@ impl 买卖点Py {
,
,
,
K线.borrow().inner.,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
@@ -252,7 +253,6 @@ impl 买卖点Py {
,
,
,
K线.borrow().inner.,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
@@ -276,7 +276,6 @@ impl 买卖点Py {
,
,
,
K线.borrow().inner.,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
@@ -300,7 +299,6 @@ impl 买卖点Py {
,
,
,
K线.borrow().inner.,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
@@ -324,7 +322,6 @@ impl 买卖点Py {
,
,
,
K线.borrow().inner.,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
@@ -348,7 +345,282 @@ impl 买卖点Py {
,
,
,
K线.borrow().inner.,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T1卖点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T1卖点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T1买点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T1买点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T1P卖点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T1P卖点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T1P买点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T1P买点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T2卖点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T2卖点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T2买点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T2买点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T2S卖点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T2S卖点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T2S买点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T2S买点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T3A卖点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T3A卖点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T3A买点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T3A买点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T3B卖点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T3B卖点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
Ok(Bound::new(py, init)?.unbind())
}
#[classmethod]
fn T3B买点(
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, K线Py>,
: &str,
: String,
: f64,
py: Python<'_>,
) -> PyResult<Py<Self>> {
let base = Py {
inner: chanlun::business::bsp::::T3B买点(
Arc::clone(&.borrow().inner),
K线.borrow().inner.clone(),
,
,
,
),
};
let init = PyClassInitializer::from(base).add_subclass(Py);
@@ -546,14 +818,30 @@ impl 观察者Py {
Py::from_rust_config(&self.obs().)
}
/// 清空所有分析序列,重置为初始状态
fn (&mut self) {
/// 清空所有分析序列,重置为初始状态(内部实现)
#[pyo3(name = "_重置基础序列")]
fn _impl(&mut self) -> PyResult<()> {
self.obs_mut().();
Ok(())
}
/// 核心入口 — 投喂一根原始K线,增量更新所有层级
fn K线(&mut self, K: &Bound<'_, K线Py>) {
/// 清空所有分析序列,重置为初始状态(公开分发器,支持子类重写)
fn (slf: &Bound<'_, Self>) -> PyResult<()> {
slf.call_method1("_重置基础序列", ())?;
Ok(())
}
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(内部实现)
#[pyo3(name = "_增加原始K线")]
fn K线_impl(&mut self, K: &Bound<'_, K线Py>) -> PyResult<()> {
self.obs_mut().K线((*K.borrow().inner).clone());
Ok(())
}
/// 核心入口 — 投喂一根原始K线,增量更新所有层级(公开分发器,支持子类重写)
fn K线(slf: &Bound<'_, Self>, K: &Bound<'_, K线Py>) -> PyResult<()> {
slf.call_method1("_增加原始K线", (K,))?;
Ok(())
}
/// 投喂原始数据 — 便捷入口,直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线,
@@ -603,15 +891,30 @@ impl 观察者Py {
Ok(())
}
/// 静态重新分析(占位方法
fn (&mut self) {
/// 静态重新分析(内部实现
#[pyo3(name = "_静态重新分析")]
fn _impl(&mut self) -> PyResult<()> {
self.obs_mut().();
Ok(())
}
/// 静态重新分析(公开分发器,支持子类重写)
fn (slf: &Bound<'_, Self>) -> PyResult<()> {
slf.call_method1("_静态重新分析", ())?;
Ok(())
}
/// 拆分各序列数据,单独存文件,文件名为对应变量名(内部实现)
#[pyo3(name = "_测试_保存数据", signature = (root = None))]
fn _保存数据_impl(&self, root: Option<&str>) -> String {
self.obs()._保存数据(root)
}
/// 拆分各序列数据,单独存文件,文件名为对应变量名(公开分发器,支持子类重写)
#[pyo3(signature = (root = None))]
/// 拆分各序列数据,单独存文件,文件名为对应变量名
fn _保存数据(&self, root: Option<&str>) {
self.obs()._保存数据(root);
fn _保存数据(slf: &Bound<'_, Self>, root: Option<&str>) -> PyResult<String> {
let result = slf.call_method1("_测试_保存数据", (root,))?;
result.extract::<String>()
}
#[classmethod]
@@ -977,8 +1280,8 @@ impl 立体分析器Py {
}
/// 拆分各序列数据,单独存文件,文件名为对应变量名
fn _保存数据(&self) {
self.inner._保存数据();
fn _保存数据(&self, root: Option<&str>) {
self.inner._保存数据(root);
}
#[getter]
+20 -19
View File
@@ -25,6 +25,7 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use std::collections::HashMap;
use tracing::warn;
/// 缠论配置 — 控制所有分析阶段行为的参数集(共 60+ 字段,均有默认值)。
///
@@ -344,17 +345,17 @@ fn dict_to_rust_config(
// 用默认值做基准,只合并类型匹配的字段
let mut merged = default_json.clone();
if let serde_json::Value::Object(ref input_map) = value {
if let serde_json::Value::Object(ref default_map) = default_json {
for (key, input_val) in input_map {
if let Some(default_val) = default_map.get(key) {
match validate_field(key, input_val, default_val) {
Ok(()) => {
merged[key] = input_val.clone();
}
Err(msg) => {
eprintln!("\x1b[33m[配置警告]\x1b[m {key}: {msg},已使用默认值 {default_val}");
}
if let serde_json::Value::Object(ref input_map) = value
&& let serde_json::Value::Object(ref default_map) = default_json
{
for (key, input_val) in input_map {
if let Some(default_val) = default_map.get(key) {
match validate_field(key, input_val, default_val) {
Ok(()) => {
merged[key] = input_val.clone();
}
Err(msg) => {
warn!("[配置警告] {key}: {msg},已使用默认值 {default_val}");
}
}
}
@@ -453,10 +454,10 @@ fn coerce_strings_to_numbers(value: &mut serde_json::Value) {
if let Ok(n) = cloned.parse::<i64>() {
*value = serde_json::Value::Number(serde_json::Number::from(n));
} else if let Ok(n) = cloned.parse::<f64>() {
if n.is_finite() {
if let Some(num) = serde_json::Number::from_f64(n) {
*value = serde_json::Value::Number(num);
}
if n.is_finite()
&& let Some(num) = serde_json::Number::from_f64(n)
{
*value = serde_json::Value::Number(num);
}
} else if cloned.eq_ignore_ascii_case("true") {
*value = serde_json::Value::Bool(true);
@@ -495,10 +496,10 @@ fn coerce_py_value(value: &Bound<'_, PyAny>) -> PyResult<Py<PyAny>> {
if let Ok(n) = lower.parse::<i64>() {
return Ok(n.into_pyobject(py)?.into_any().unbind());
}
if let Ok(n) = lower.parse::<f64>() {
if n.is_finite() {
return Ok(n.into_pyobject(py)?.into_any().unbind());
}
if let Ok(n) = lower.parse::<f64>()
&& n.is_finite()
{
return Ok(n.into_pyobject(py)?.into_any().unbind());
}
Ok(value.clone().unbind())
+389 -3
View File
@@ -24,6 +24,7 @@
use pyo3::prelude::*;
use pyo3::types::PyType;
use std::sync::Arc;
// ========== 平滑异同移动平均线 ==========
@@ -603,14 +604,257 @@ impl 随机指标Py {
}
}
// ========== 布林带 ==========
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道。
///
/// 属性:
/// 时间戳: int / 周期: int / 标准差倍数: float
/// 上轨: float — 中轨 + 标准差倍数 * 标准差
/// 中轨: float — 移动平均线
/// 下轨: float — 中轨 - 标准差倍数 * 标准差
///
/// 方法(均为 classmethod,直接构造实例):
/// 首次计算(时间戳, 价格, 周期=20, 标准差倍数=2.0) -> 布林带
/// 增量计算(前一个布林带, 时间戳, 价格) -> 布林带
#[pyclass(name = "布林带", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub(crate) inner: chanlun::indicators::,
}
#[pymethods]
impl Py {
#[new]
fn new() -> Self {
unimplemented!("使用 首次计算 或 增量计算 创建")
}
#[getter]
fn (&self) -> i64 {
self.inner.
}
#[getter]
fn (&self) -> usize {
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.
}
fn __str__(&self) -> String {
format!(
"布林带(上={:.2}, 中={:.2}, 下={:.2})",
self.inner., self.inner., self.inner.
)
}
fn __repr__(&self) -> String {
self.__str__()
}
#[classmethod]
#[pyo3(signature = (k线, 计算方式, 周期 = 20, 标准差倍数 = 2.0))]
fn (
_cls: &Bound<'_, PyType>,
k线: &Bound<'_, PyAny>,
: &str,
: usize,
: f64,
) -> PyResult<Self> {
let = K线取值(k线, )?;
let = (k线)?;
Ok(Self {
inner: chanlun::indicators::::(, , , ),
})
}
#[classmethod]
fn (
_cls: &Bound<'_, PyType>,
: &Bound<'_, Py>,
K线: &Bound<'_, PyAny>,
: &str,
) -> PyResult<Self> {
let = K线取值(K线, )?;
let = (K线)?;
Ok(Self {
inner: chanlun::indicators::::(
&.borrow().inner,
,
,
),
})
}
}
// ========== 指标容器 ==========
/// 指标容器 — 挂载在每根 K线上,基于注册表模式持有该时刻所有指标快照。
///
/// 与 Python `指标容器` 保持一致:
/// - 默认名称:"macd"/"rsi"/"kdj"/"boll" → 对应指标对象
/// - 多参数变体:key 格式 "MACD_{快}_{慢}_{信号}" / "RSI_{周期}" 等
/// - 均线组:通过 "均线" 获取 dict[str, float]
/// - 单值指标:通过 "单值" 获取 dict[str, float]
#[pyclass(name = "指标容器", module = "chanlun._chanlun", skip_from_py_object)]
#[derive(Clone)]
pub struct Py {
pub(crate) inner: chanlun::indicators::,
}
/// 将 Rust 指标值 转换为 Python 对象
fn _to_py(value: &chanlun::indicators::, py: Python<'_>) -> PyResult<Py<PyAny>> {
use chanlun::indicators::;
match value {
::MACD(m) => {
Ok(Py::new(py, 线Py { inner: m.clone() })?.into_any())
}
::RSI(r) => Ok(Py::new(py, Py { inner: r.clone() })?.into_any()),
::KDJ(k) => Ok(Py::new(py, Py { inner: k.clone() })?.into_any()),
::BOLL(b) => Ok(Py::new(py, Py { inner: b.clone() })?.into_any()),
::线(map) | ::(map) => {
let dict = pyo3::types::PyDict::new(py);
for (k, v) in map {
dict.set_item(k, *v)?;
}
Ok(dict.into())
}
}
}
#[pymethods]
impl Py {
#[new]
fn new() -> Self {
Self {
inner: chanlun::indicators::::new(),
}
}
/// 按名称获取指标值
fn (&self, : &str, py: Python<'_>) -> PyResult<Option<Py<PyAny>>> {
match self.inner.() {
Some(v) => _to_py(v, py).map(Some),
None => Ok(None),
}
}
/// 按名称设置指标值(仅支持 MACD/RSI/KDJ/BOLL 四种类型)
#[pyo3(signature = (名称, 值))]
fn (&mut self, : &str, : &Bound<'_, PyAny>) -> PyResult<()> {
use chanlun::indicators::;
if let Ok(m) = .cast::<线Py>() {
self.inner
.(, ::MACD(m.borrow().inner.clone()));
return Ok(());
}
if let Ok(r) = .cast::<Py>() {
self.inner.(, ::RSI(r.borrow().inner.clone()));
return Ok(());
}
if let Ok(k) = .cast::<Py>() {
self.inner.(, ::KDJ(k.borrow().inner.clone()));
return Ok(());
}
if let Ok(b) = .cast::<Py>() {
self.inner
.(, ::BOLL(b.borrow().inner.clone()));
return Ok(());
}
Err(pyo3::exceptions::PyTypeError::new_err(
"不支持的类型,仅支持 MACD/RSI/KDJ/BOLL 指标",
))
}
/// 检查是否包含指定名称的指标
fn (&self, : &str) -> bool {
self.inner.()
}
/// 返回所有已注册的指标名称
fn keys(&self) -> Vec<String> {
self.inner._数据.keys().cloned().collect()
}
fn __getitem__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
if self.() {
match self.inner.() {
Some(v) => _to_py(v, py),
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyKeyError::new_err(format!(
"指标 '{}' 不存在",
)))
}
}
fn __getattr__(&self, : &str, py: Python<'_>) -> PyResult<Py<PyAny>> {
if == "_数据" {
// 返回内部数据字典的 Python 表示
let dict = pyo3::types::PyDict::new(py);
for key in self.inner._数据.keys() {
if let Some(v) = self.inner.(key) {
dict.set_item(key, _to_py(v, py)?)?;
} else {
dict.set_item(key, py.None())?;
}
}
return Ok(dict.into());
}
if self.() {
match self.inner.() {
Some(v) => _to_py(v, py),
None => Ok(py.None()),
}
} else {
Err(pyo3::exceptions::PyAttributeError::new_err(format!(
"指标 '{}' 不存在于 指标容器 中",
)))
}
}
fn __contains__(&self, : &str) -> bool {
self.()
}
fn __str__(&self) -> String {
self.inner.to_string()
}
fn __repr__(&self) -> String {
self.__str__()
}
}
// ========== 指标 (static namespace) ==========
/// 指标 — 静态工具类,提供指标计算的辅助方法。
///
/// 方法:
/// K线取值(k线, 指标计算方式) -> float (classmethod)
/// 根据计算方式从K线提取数值。
/// 计算方式: "收盘价" / "开盘价" / "高" / "低" / "均值" 等
/// :meth:`K线取值` — 根据计算方式从K线提取数值
/// (计算方式: "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值")
#[pyclass(name = "指标", module = "chanlun._chanlun")]
pub struct Py;
@@ -627,6 +871,144 @@ impl 指标Py {
}
}
// ========== 均线工具 ==========
/// 均线工具 — 增量 SMA/EMA 计算的静态方法容器。
///
/// 方法:
/// :meth:`增量SMA` — 基于前一根K线的 SMA 值,增量计算当前 SMA
/// :meth:`增量EMA` — 用前一根K线的 EMA 值递推计算当前 EMA
#[pyclass(name = "均线工具", module = "chanlun._chanlun")]
pub struct 线Py;
#[pymethods]
impl 线Py {
/// 基于前一根K线的 SMA 值,增量计算当前 SMA
#[staticmethod]
#[pyo3(signature = (普K序列, period, 计算方式))]
fn SMA(
K序列: Vec<Py<crate::kline_py::K线Py>>,
period: i64,
: &str,
py: Python<'_>,
) -> PyResult<f64> {
if K序列.is_empty() {
return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空"));
}
let n = K序列.len();
// 提取所有K线值(一次性 borrow)
let values: Vec<f64> = K序列
.iter()
.map(|k| {
let inner = &k.bind(py).borrow().inner;
chanlun::indicators::K线取值(
inner.,
inner.,
inner.,
inner.,
,
)
})
.collect();
if n <= period as usize {
let start = n.saturating_sub(period as usize);
let sum: f64 = values[start..].iter().sum();
return Ok(sum / (n.max(1)) as f64);
}
let prev_key = format!("SMA_{}", period);
// 尝试从前一根K线的均线缓存中读取
let prev_cached = K序列[n - 2]
.bind(py)
.borrow()
.inner
.
.read()
.unwrap()
.线()
.and_then(|m| m.get(&prev_key))
.copied();
if let Some(prev) = prev_cached {
let = values[n - 1];
let oldest = values[n - period as usize - 1];
return Ok(prev + ( - oldest) / period as f64);
}
// 回退:完整计算最近 period 根K线
let sum: f64 = values[n - period as usize..].iter().sum();
Ok(sum / period as f64)
}
/// 用前一根K线的 EMA 值递推
#[staticmethod]
#[pyo3(signature = (普K序列, period, 计算方式, 前值 = None))]
fn EMA(
K序列: Vec<Py<crate::kline_py::K线Py>>,
period: i64,
: &str,
: Option<f64>,
py: Python<'_>,
) -> PyResult<f64> {
if K序列.is_empty() {
return Err(pyo3::exceptions::PyValueError::new_err("普K序列 不能为空"));
}
let last = K序列.last().unwrap().bind(py).borrow();
let = chanlun::indicators::K线取值(
last.inner.,
last.inner.,
last.inner.,
last.inner.,
,
);
match {
None => Ok(),
Some(prev) => {
let k = 2.0 / (period as f64 + 1.0);
Ok( * k + prev * (1.0 - k))
}
}
}
}
// ========== 指标计算器 ==========
/// 指标计算器 — 在缠K合并之前,增量计算所有开启的指标并挂载到K线上。
///
/// 方法:
/// :meth:`计算并挂载` — 增量计算所有开启的指标,将结果写入 ``当前K线.指标``
#[pyclass(name = "指标计算器", module = "chanlun._chanlun")]
pub struct Py;
#[pymethods]
impl Py {
/// 增量计算所有开启的指标,将结果写入 当前K线.指标
#[staticmethod]
fn (
K线: &Bound<'_, crate::kline_py::K线Py>,
: Vec<Py<crate::kline_py::K线Py>>,
: &Bound<'_, crate::config_py::Py>,
py: Python<'_>,
) -> PyResult<()> {
let config = .borrow().to_rust_config(py)?;
// 全序列包含 当前K线 在末尾;Rust 计算并挂载 的 现有序列 不含当前K线
let : Vec<Arc<chanlun::kline::bar::K线>> = if .len() > 1 {
[...len() - 1]
.iter()
.map(|k| k.bind(py).borrow().inner.clone())
.collect()
} else {
Vec::new()
};
chanlun::indicators::::(
&K线.borrow().inner,
&,
&config,
);
Ok(())
}
}
// ========== Helper functions ==========
pub(crate) fn K线取值(k线: &Bound<'_, PyAny>, : &str) -> PyResult<f64> {
@@ -664,6 +1046,10 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<线Py>()?;
m.add_class::<Py>()?;
m.add_class::<Py>()?;
m.add_class::<Py>()?;
m.add_class::<Py>()?;
m.add_class::<Py>()?;
m.add_class::<线Py>()?;
m.add_class::<Py>()?;
Ok(())
}
+45 -48
View File
@@ -25,12 +25,15 @@
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyType};
use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering;
use crate::config_py::Py;
use crate::indicators_py::{线Py, Py, Py};
use crate::indicators_py::{
线Py, Py, Py, Py
};
use crate::structure_py::fractal_to_py;
use crate::types_py::Py;
// ========== K线 ==========
@@ -85,9 +88,7 @@ impl K线Py {
,
,
,
macd: None,
rsi: None,
kdj: None,
: RwLock::new(chanlun::indicators::::new()),
}),
}
}
@@ -146,25 +147,39 @@ impl K线Py {
#[getter]
fn macd(&self) -> Option<线Py> {
self.inner
.macd
.as_ref()
.map(|m| 线Py { inner: m.clone() })
.
.read()
.unwrap()
.macd_cloned()
.map(|m| 线Py { inner: m })
}
#[getter]
fn rsi(&self) -> Option<Py> {
self.inner
.rsi
.as_ref()
.map(|r| Py { inner: r.clone() })
.
.read()
.unwrap()
.rsi_cloned()
.map(|r| Py { inner: r })
}
#[getter]
fn kdj(&self) -> Option<Py> {
self.inner
.kdj
.as_ref()
.map(|k| Py { inner: k.clone() })
.
.read()
.unwrap()
.kdj_cloned()
.map(|k| Py { inner: k })
}
/// 指标容器 — 包含所有已注册指标(MACD/RSI/KDJ/BOLL/均线/单值)
#[getter]
fn (&self) -> Py {
Py {
inner: self.inner..read().unwrap().clone(),
}
}
/// pandas 兼容 — 返回所有字段构成的字典
@@ -190,6 +205,7 @@ impl K线Py {
if let Some(v) = self.kdj() {
dict.set_item("kdj", v)?;
}
dict.set_item("指标", self.())?;
Ok(dict.into())
}
@@ -545,14 +561,14 @@ impl 缠论K线Py {
.unwrap()
.get(&src_key)
.map(|p| p.clone_ref(py));
if let Some(cached_src) = cached_src {
if let Ok(new_set) = pyo3::types::PySet::empty(py) {
for item in cached_src.bind(py).iter() {
let _ = new_set.add(item);
}
let py_set: Py<pyo3::types::PySet> = new_set.into();
BSP_CACHE.write().unwrap().insert(dst_key, py_set);
if let Some(cached_src) = cached_src
&& let Ok(new_set) = pyo3::types::PySet::empty(py)
{
for item in cached_src.bind(py).iter() {
let _ = new_set.add(item);
}
let py_set: Py<pyo3::types::PySet> = new_set.into();
BSP_CACHE.write().unwrap().insert(dst_key, py_set);
}
mirror
}
@@ -583,10 +599,14 @@ impl 缠论K线Py {
if let Some(set) = cached {
return Ok(set.into_any());
}
// 创建新的 PySet 并存入全局缓存
// 创建新的 PySet,从 Rust HashSet 同步已有内容
let set = pyo3::types::PySet::empty(py)?;
let bsp_info = self.inner..read().unwrap();
for item in bsp_info.iter() {
set.add(item.as_str())?;
}
drop(bsp_info);
BSP_CACHE.write().unwrap().insert(key, set.into());
// 重新读取并返回(无法从 insert 获取 Py 引用,需要重新读)
Ok(BSP_CACHE
.read()
.unwrap()
@@ -641,29 +661,6 @@ impl 缠论K线Py {
chan_kline_to_py(py, std::sync::Arc::new(inner))
}
#[classmethod]
/// K线包含处理(合并)
fn (
_cls: &Bound<'_, PyType>,
K: Option<&Bound<'_, Self>>,
K: &Bound<'_, Self>,
K: &Bound<'_, K线Py>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<(Option<Py<Self>>, Option<String>)> {
let 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,
&ck_inner,
&K.borrow().inner,
&config,
);
Ok((result.map(|rc| chan_kline_to_py(py, rc)), mode))
}
#[classmethod]
/// 分析K线,执行指标计算+包含处理+分型判定
fn (
@@ -686,14 +683,14 @@ impl 缠论K线Py {
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let (status, _fractal) = chanlun::kline::chan_kline::K线::(
let (status, fractal) = chanlun::kline::chan_kline::K线::(
ck_inner,
&mut ck_seq,
&mut bar_seq,
&config,
);
Ok((status, None))
Ok((status, fractal.map(|f| fractal_to_py(py, f).into_any())))
}
#[staticmethod]
+109 -1
View File
@@ -25,7 +25,80 @@
#![allow(non_snake_case, clippy::too_many_arguments)]
use pyo3::prelude::*;
use std::sync::atomic::Ordering;
use std::sync::atomic::{AtomicU8, Ordering};
use std::sync::{Mutex, Once, OnceLock};
/// 日志级别: 0=trace, 1=debug, 2=info, 3=warn, 4=error, 5=off
static LOG_LEVEL: AtomicU8 = AtomicU8::new(2); // 默认 info
type =
tracing_subscriber::reload::Handle<tracing_subscriber::EnvFilter, tracing_subscriber::Registry>;
static : OnceLock<Mutex<>> = OnceLock::new();
static TRACING_INIT: Once = Once::new();
fn (n: u8) -> &'static str {
match n {
0 => "trace",
1 => "debug",
2 => "info",
3 => "warn",
4 => "error",
5 => "off",
_ => "unknown",
}
}
fn (name: &str) -> Option<u8> {
match name.to_lowercase().as_str() {
"trace" => Some(0),
"debug" => Some(1),
"info" => Some(2),
"warn" => Some(3),
"error" => Some(4),
"off" => Some(5),
_ => None,
}
}
fn init_tracing() {
TRACING_INIT.call_once(|| {
use chrono::Local;
use std::fmt;
use tracing_subscriber::fmt::format::Format;
use tracing_subscriber::fmt::format::Writer;
use tracing_subscriber::fmt::time::FormatTime;
use tracing_subscriber::layer::SubscriberExt;
use tracing_subscriber::util::SubscriberInitExt;
struct ;
impl FormatTime for {
fn format_time(&self, w: &mut Writer<'_>) -> fmt::Result {
write!(w, "{}", Local::now().format("%Y-%m-%d %H:%M:%S%.3f"))
}
}
let format = Format::default()
.with_timer()
.with_target(false)
.with_file(true)
.with_line_number(true)
.with_ansi(true)
.compact();
let = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"));
let (, ) = tracing_subscriber::reload::Layer::new();
.set(Mutex::new())
.expect("过滤器句柄锁只能设置一次");
tracing_subscriber::registry()
.with()
.with(tracing_subscriber::fmt::layer().event_format(format))
.init();
});
}
mod algorithm_py;
mod business_py;
@@ -47,12 +120,47 @@ fn set_分型模式(value: bool) {
chanlun::structure::fractal_obj::.store(value, Ordering::Relaxed);
}
/// 获取当前日志级别 ("trace" / "debug" / "info" / "warn" / "error" / "off")
#[pyfunction]
fn get_log_level() -> &'static str {
(LOG_LEVEL.load(Ordering::Relaxed))
}
/// 设置日志级别 (不区分大小写: "trace" / "debug" / "info" / "warn" / "error" / "off")
///
/// 设为 "off" 可完全关闭日志输出。
#[pyfunction]
fn set_log_level(level: &str) -> PyResult<()> {
let = (level).ok_or_else(|| {
pyo3::exceptions::PyValueError::new_err(format!(
"无效日志级别 '{}',有效值: trace, debug, info, warn, error, off",
level
))
})?;
let guard =
.get()
.ok_or_else(|| pyo3::exceptions::PyRuntimeError::new_err("日志系统尚未初始化"))?;
let handle = guard.lock().unwrap();
let = ();
let filter = tracing_subscriber::EnvFilter::new();
handle
.reload(filter)
.map_err(|_| pyo3::exceptions::PyRuntimeError::new_err("切换日志级别失败"))?;
LOG_LEVEL.store(, Ordering::Relaxed);
Ok(())
}
/// 缠论技术分析库 — Rust 高性能实现
#[pymodule]
/// 缠论技术分析库 — Rust 高性能实现
fn _chanlun(_py: Python, m: &Bound<'_, PyModule>) -> PyResult<()> {
init_tracing();
m.add_function(wrap_pyfunction!(get_分型模式, m)?)?;
m.add_function(wrap_pyfunction!(set_分型模式, m)?)?;
m.add_function(wrap_pyfunction!(get_log_level, m)?)?;
m.add_function(wrap_pyfunction!(set_log_level, m)?)?;
// 阶段 1: 枚举和基础类型
types_py::register(m)?;
// 阶段 2: 配置
+46 -269
View File
@@ -25,13 +25,13 @@
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use std::collections::HashMap;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::RwLock;
use std::sync::atomic::Ordering;
use crate::algorithm_py::hub_to_py;
use crate::config_py::Py;
use crate::kline_py::{bar_to_py, K线Py, K线Py};
use crate::kline_py::{K线Py, bar_to_py, K线Py};
// ---- 身份缓存 (弱引用:通过 refcnt 检测存活,仅缓存持有则视为过期) ----
@@ -42,8 +42,6 @@ static DASHED_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<虚线Py>>>
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static SEGFEAT_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<线Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
static FEATFRAC_IDENTITY: std::sync::LazyLock<RwLock<HashMap<usize, Py<Py>>>> =
std::sync::LazyLock::new(|| RwLock::new(HashMap::new()));
pub(crate) fn fractal_to_py(
py: Python<'_>,
@@ -121,30 +119,6 @@ pub(crate) fn segfeat_to_py(
obj
}
pub(crate) fn featfrac_to_py(
py: Python<'_>,
inner: Arc<chanlun::structure::feat_fractal::>,
) -> Py<Py> {
let key = Arc::as_ptr(&inner) as usize;
if let Some(cached) = FEATFRAC_IDENTITY
.read()
.unwrap()
.get(&key)
.map(|p| p.clone_ref(py))
{
return cached;
}
FEATFRAC_IDENTITY
.write()
.unwrap()
.retain(|_, v| v.get_refcnt(py) > 1);
let obj = Py::new(py, Py { inner }).unwrap();
FEATFRAC_IDENTITY
.write()
.unwrap()
.insert(key, obj.clone_ref(py));
obj
}
use crate::types_py::{Py, Py, Py};
// ========== 分型 ==========
@@ -208,17 +182,7 @@ impl 分型Py {
#[getter]
fn (&self, py: Python<'_>) -> Py<Py> {
let inner = if chanlun::structure::fractal_obj::.load(Ordering::Relaxed) {
self.inner.
} else {
self.inner
.
.
.read()
.unwrap()
.unwrap_or(chanlun::types::::)
};
crate::types_py::(py, inner)
crate::types_py::(py, self.inner.())
}
#[getter]
@@ -228,11 +192,25 @@ impl 分型Py {
#[getter]
fn (&self) -> f64 {
if chanlun::structure::fractal_obj::.load(Ordering::Relaxed) {
self.inner.
} else {
self.inner...get()
}
self.inner.()
}
#[getter]
/// 构造时缓存的 _结构(不受 分型模式 影响)
fn _结构(&self, py: Python<'_>) -> Py<Py> {
crate::types_py::(py, self.inner.)
}
#[getter]
/// 构造时缓存的 _时间戳(不受 分型模式 影响)
fn _时间戳(&self) -> i64 {
self.inner.
}
#[getter]
/// 构造时缓存的 _分型特征值(不受 分型模式 影响)
fn _分型特征值(&self) -> f64 {
self.inner.
}
fn __str__(&self) -> String {
@@ -448,11 +426,16 @@ impl 虚线Py {
self.inner..read().unwrap().clone()
}
#[getter]
fn _特征序列_显示(&self) -> bool {
#[getter(_特征序列_显示)]
fn get_特征序列_显示(&self) -> bool {
self.inner._特征序列_显示.load(Ordering::Relaxed)
}
#[setter(_特征序列_显示)]
fn set_特征序列_显示(&mut self, value: bool) {
self.inner._特征序列_显示.store(value, Ordering::Relaxed);
}
#[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
@@ -997,15 +980,10 @@ impl 虚线Py {
/// 文: 分型 — 特征序列的起点分型
/// 武: 分型 — 特征序列的终点分型
/// 方向: 相对方向 / 高: float / 低: float
///
/// 方法:
/// 添加(虚线) — 向特征序列追加虚线元素
/// 删除(虚线) — 从特征序列移除虚线元素
/// 基本序列: list[虚线] (基础序列)
///
/// 类方法:
/// 新建(序号, 文, 武, 基础序列?) -> 线段特征
/// 静态分析(虚线序列, 配置) -> 线段特征|None
/// 获取分型序列(虚线序列, 配置) -> list[线段特征]
/// :meth:`静态分析` — 对虚线序列进行静态特征分析,返回 线段特征 列表
#[pyclass(name = "线段特征", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct 线Py {
@@ -1014,28 +992,6 @@ pub struct 线段特征Py {
#[pymethods]
impl 线Py {
#[new]
fn new(
: String,
: Vec<Py<线Py>>,
线: &Bound<'_, Py>,
py: Python<'_>,
) -> Self {
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> =
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
Self {
inner: Arc::new(chanlun::structure::segment_feat::线::new(
,
rc_list,
线.borrow().inner,
)),
}
}
// ---- getters ----
#[getter]
fn (&self) -> i64 {
self.inner.
@@ -1043,7 +999,12 @@ impl 线段特征Py {
#[getter]
fn (&self) -> String {
self.inner..clone()
self.inner..read().unwrap().clone()
}
#[setter]
fn set_标识(&self, value: String) {
*self.inner..write().unwrap() = value;
}
#[getter]
@@ -1052,9 +1013,9 @@ impl 线段特征Py {
}
#[getter]
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
fn (&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.inner. {
for d in &self.inner. {
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
Ok(list.into())
@@ -1068,55 +1029,6 @@ impl 线段特征Py {
self.__str__()
}
fn __len__(&self) -> usize {
self.inner..len()
}
fn __getitem__(&self, index: isize, py: Python<'_>) -> PyResult<Py<PyAny>> {
let len = self.inner..len() as isize;
let idx = if index < 0 { index + len } else { index };
if idx < 0 || idx >= len {
return Err(pyo3::exceptions::PyIndexError::new_err(format!(
"线段特征 index {index} out of range (len={len})"
)));
}
let dash = &self.inner.[idx as usize];
let obj: Py<PyAny> = dashed_to_py(py, Arc::clone(dash)).into();
Ok(obj)
}
fn __iter__(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
let list = pyo3::types::PyList::empty(py);
for d in &self.inner. {
list.append(dashed_to_py(py, Arc::clone(d)))?;
}
list.call_method0("__iter__").map(|iter| iter.into())
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Arc::as_ptr(&self.inner) as u64
}
/// pandas 兼容 — 返回关键标量字段构成的字典
#[getter]
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py);
dict.set_item("序号", self.())?;
dict.set_item("标识", self.())?;
dict.set_item("线段方向", self.线(py))?;
dict.set_item("图表标题", self.())?;
Ok(dict.into())
}
// ---- instance methods ----
#[getter]
/// :return: 图表标题
fn (&self) -> String {
@@ -1153,47 +1065,8 @@ impl 线段特征Py {
self.inner.()
}
/// :param 待添加虚线: 待添加的虚线
fn (&mut self, 线: &Bound<'_, 线Py>) -> PyResult<()> {
let inner = Arc::make_mut(&mut self.inner);
inner
.(Arc::clone(&线.borrow().inner))
.map_err(pyo3::exceptions::PyValueError::new_err)
}
/// :param 待删除虚线: 待删除的虚线
fn (&mut self, 线: &Bound<'_, 线Py>) -> PyResult<()> {
let inner = Arc::make_mut(&mut self.inner);
inner
.(&Arc::clone(&线.borrow().inner))
.map_err(pyo3::exceptions::PyValueError::new_err)
}
// ---- classmethods ----
#[classmethod]
/// :param 虚线序列: 基础虚线列表
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
线: &Bound<'_, Py>,
py: Python<'_>,
) -> Self {
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
Self {
inner: Arc::new(chanlun::structure::segment_feat::线::(
rc_list,
线.borrow().inner,
)),
}
}
#[classmethod]
#[pyo3(signature = (虚线序列, 线段方向, 四象, 是否忽视 = false))]
/// 静态分析虚线序列,生成特征序列
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
@@ -1201,117 +1074,22 @@ impl 线段特征Py {
: &str,
: bool,
py: Python<'_>,
) -> Vec<Self> {
) -> PyResult<Py<PyAny>> {
let rc_list: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
chanlun::structure::segment_feat::线::(
let result = 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<Py>> {
let rc_list: Vec<Arc<chanlun::structure::segment_feat::线>> =
.iter()
.map(|s| Arc::clone(&s.bind(py).borrow().inner))
.collect();
chanlun::structure::segment_feat::线::(&rc_list)
.into_iter()
.map(|inner| featfrac_to_py(py, Arc::new(inner)))
.collect()
}
}
// ========== 特征分型 ==========
/// 特征分型 — 线段特征序列中的分型节点。
///
/// 属性 (只读):
/// 左: 线段特征|None / 中: 线段特征 / 右: 线段特征|None
/// 结构: 分型结构 — 顶/底分型判定结果
#[pyclass(name = "特征分型", module = "chanlun._chanlun", from_py_object)]
#[derive(Clone)]
pub struct Py {
pub(crate) inner: Arc<chanlun::structure::feat_fractal::>,
}
#[pymethods]
impl Py {
#[new]
fn new(
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, 线Py>,
: &Bound<'_, Py>,
) -> Self {
Self {
inner: Arc::new(chanlun::structure::feat_fractal::::new(
Arc::clone(&.borrow().inner),
Arc::clone(&.borrow().inner),
Arc::clone(&.borrow().inner),
.borrow().inner,
)),
);
let list = pyo3::types::PyList::empty(py);
for sf in result {
list.append(segfeat_to_py(py, sf))?;
}
}
#[getter]
fn (&self, py: Python<'_>) -> Py<线Py> {
segfeat_to_py(py, Arc::clone(&self.inner.))
}
#[getter]
fn (&self, py: Python<'_>) -> Py<线Py> {
segfeat_to_py(py, Arc::clone(&self.inner.))
}
#[getter]
fn (&self, py: Python<'_>) -> Py<线Py> {
segfeat_to_py(py, Arc::clone(&self.inner.))
}
#[getter]
fn (&self, py: Python<'_>) -> Py<Py> {
crate::types_py::(py, self.inner.)
}
/// pandas 兼容 — 返回关键标量字段构成的字典
#[getter]
fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
let dict = PyDict::new(py);
dict.set_item("结构", self.(py))?;
Ok(dict.into())
}
fn __str__(&self) -> String {
format!("{}", self.inner)
}
fn __repr__(&self) -> String {
self.__str__()
}
fn __eq__(&self, other: &Bound<'_, PyAny>) -> bool {
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
return Arc::as_ptr(&self.inner) == Arc::as_ptr(&other.inner);
}
false
}
fn __hash__(&self) -> u64 {
Arc::as_ptr(&self.inner) as u64
Ok(list.into())
}
}
@@ -1319,6 +1097,5 @@ pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
m.add_class::<Py>()?;
m.add_class::<线Py>()?;
m.add_class::<线Py>()?;
m.add_class::<Py>()?;
Ok(())
}
+120 -79
View File
@@ -27,7 +27,7 @@ use std::sync::Mutex;
use pyo3::basic::CompareOp;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use pyo3::types::{PyBool, PyDict, PyType};
// ========== 单例缓存 ==========
@@ -123,18 +123,22 @@ impl 买卖点类型Py {
self.inner.to_string()
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
// 比较:先尝试字符串(Python Enum(str)),再同类型,再 name 属性
let eq = if let Ok(s) = other.extract::<String>() {
self.inner.to_string() == s
} else if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
self.inner == other.inner
} else if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
self.inner.to_string() == py_name
} else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
return Ok(py.NotImplemented());
};
match op {
CompareOp::Eq => Ok(eq),
CompareOp::Ne => Ok(!eq),
_ => Err(pyo3::exceptions::PyNotImplementedError::new_err("")),
CompareOp::Eq => Ok(PyBool::new(py, eq).as_any().to_owned().unbind()),
CompareOp::Ne => Ok(PyBool::new(py, !eq).as_any().to_owned().unbind()),
_ => Ok(py.NotImplemented()),
}
}
@@ -203,19 +207,44 @@ impl 相对方向Py {
format!("{}", self.inner)
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
};
let eq = self.inner == other.inner;
match op {
CompareOp::Eq => Ok(eq),
CompareOp::Ne => Ok(!eq),
CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)),
CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)),
CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)),
CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)),
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
// 同类型比较
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
let eq = self.inner == other.inner;
return Ok(match op {
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
CompareOp::Lt => PyBool::new(py, (self.inner as u8) < (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
CompareOp::Le => PyBool::new(py, (self.inner as u8) <= (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
CompareOp::Gt => PyBool::new(py, (self.inner as u8) > (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
CompareOp::Ge => PyBool::new(py, (self.inner as u8) >= (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
});
}
// 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.相对方向)
if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
let self_name = format!("{:?}", self.inner);
let eq = self_name == py_name;
return Ok(match op {
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
_ => py.NotImplemented(),
});
}
// 回退:返回 Python NotImplemented
Ok(py.NotImplemented())
}
fn __hash__(&self) -> u64 {
@@ -315,19 +344,43 @@ impl 分型结构Py {
self.inner.to_string()
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
};
let eq = self.inner == other.inner;
match op {
CompareOp::Eq => Ok(eq),
CompareOp::Ne => Ok(!eq),
CompareOp::Lt => Ok((self.inner as u8) < (other.inner as u8)),
CompareOp::Le => Ok((self.inner as u8) <= (other.inner as u8)),
CompareOp::Gt => Ok((self.inner as u8) > (other.inner as u8)),
CompareOp::Ge => Ok((self.inner as u8) >= (other.inner as u8)),
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
// 同类型比较
if let Ok(other) = other.extract::<PyRef<'_, Self>>() {
let eq = self.inner == other.inner;
return Ok(match op {
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
CompareOp::Lt => PyBool::new(py, (self.inner as u8) < (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
CompareOp::Le => PyBool::new(py, (self.inner as u8) <= (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
CompareOp::Gt => PyBool::new(py, (self.inner as u8) > (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
CompareOp::Ge => PyBool::new(py, (self.inner as u8) >= (other.inner as u8))
.as_any()
.to_owned()
.unbind(),
});
}
// 跨模块比较:通过 name 属性匹配 Python Enum(如 chan.chan.分型结构)
if let Ok(py_name) = other.getattr("name").and_then(|n| n.extract::<String>()) {
let self_name = self.inner.to_string();
let eq = self_name == py_name;
return Ok(match op {
CompareOp::Eq => PyBool::new(py, eq).as_any().to_owned().unbind(),
CompareOp::Ne => PyBool::new(py, !eq).as_any().to_owned().unbind(),
_ => py.NotImplemented(),
});
}
Ok(py.NotImplemented())
}
fn __hash__(&self) -> u64 {
@@ -374,41 +427,17 @@ impl 分型结构Py {
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 }))
Ok(chanlun::types::::_内部(
,
,
,
,
,
,
,
,
)
.map(|inner| Self { inner }))
}
}
@@ -451,23 +480,35 @@ impl 缺口Py {
format!("{}", self.inner)
}
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<bool> {
fn __richcmp__(&self, other: &Bound<'_, PyAny>, op: CompareOp) -> PyResult<Py<PyAny>> {
let py = other.py();
let Ok(other) = other.extract::<PyRef<'_, Self>>() else {
return Err(pyo3::exceptions::PyNotImplementedError::new_err(""));
return Ok(py.NotImplemented());
};
let to_bool = |b: bool| PyBool::new(py, b).as_any().to_owned().unbind();
match op {
CompareOp::Eq => Ok(self.inner. == other.inner. && self.inner. == other.inner.),
CompareOp::Ne => {
Ok(!(self.inner. == other.inner. && self.inner. == other.inner.))
}
CompareOp::Lt => Ok(self.inner. < other.inner.
|| (self.inner. == other.inner. && self.inner. < other.inner.)),
CompareOp::Le => Ok(self.inner. < other.inner.
|| (self.inner. == other.inner. && self.inner. <= other.inner.)),
CompareOp::Gt => Ok(self.inner. > other.inner.
|| (self.inner. == other.inner. && self.inner. > other.inner.)),
CompareOp::Ge => Ok(self.inner. > other.inner.
|| (self.inner. == other.inner. && self.inner. >= other.inner.)),
CompareOp::Eq => Ok(to_bool(
self.inner. == other.inner. && self.inner. == other.inner.,
)),
CompareOp::Ne => Ok(to_bool(
!(self.inner. == other.inner. && self.inner. == other.inner.),
)),
CompareOp::Lt => Ok(to_bool(
self.inner. < other.inner.
|| (self.inner. == other.inner. && self.inner. < other.inner.),
)),
CompareOp::Le => Ok(to_bool(
self.inner. < other.inner.
|| (self.inner. == other.inner. && self.inner. <= other.inner.),
)),
CompareOp::Gt => Ok(to_bool(
self.inner. > other.inner.
|| (self.inner. == other.inner. && self.inner. > other.inner.),
)),
CompareOp::Ge => Ok(to_bool(
self.inner. > other.inner.
|| (self.inner. == other.inner. && self.inner. >= other.inner.),
)),
}
}
+5 -3
View File
@@ -1,8 +1,7 @@
[package]
name = "chanlun"
version = "26.5.6"
edition = "2021"
rust-version = "1.70"
version = "26.6.1"
edition = "2024"
license = "MIT"
description = "基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。"
readme = "README.md"
@@ -18,3 +17,6 @@ serde = { version = "1", features = ["derive"] }
serde_json = "1"
byteorder = "1"
chrono = { version = "0.4", features = ["serde"] }
cached = "1"
tracing = "0.1"
tracing-subscriber = "0.3"
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -200,7 +200,7 @@ impl 背驰分析 {
if let (Some(), Some()) = (_idx, _idx) {
let (, ) = if <= { (, ) } else { (, ) };
for k in &K线序列[..=] {
if let Some(ref macd) = k.macd {
if let Some(macd) = k..read().unwrap().macd() {
let hist = macd.MACD柱;
if hist >= 0.0 {
+= hist;
+92 -41
View File
@@ -29,14 +29,29 @@ use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 中枢 — 三段虚线重叠区间构成的价格中枢
/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致
///
/// 可变字段使用 AtomicI64 / RwLock 实现内部可变性,确保多 Arc 共享时可修改。
///
/// 字段:
/// - 序号: 中枢序号,同一级别内递增
/// - 标识: 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>"
/// - 级别: 中枢级别(笔中枢=1,线段中枢=2 等)
/// - 基础序列: 构成中枢的虚线序列(至少 3 根,延伸后可多至 9 根甚至更多)
/// - 第三买卖线: 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线)
/// - 本级_第三买卖线: 本级第三类买卖点对应的虚线
#[derive(Debug)]
pub struct {
/// 中枢序号,同一级别内递增
pub : AtomicI64,
/// 中枢标识,格式如 "笔中枢<0>" 或 "线段中枢<1>"
pub : RwLock<String>,
/// 中枢级别(笔中枢=1,线段中枢=2 等)
pub : AtomicI64,
/// 构成中枢的虚线序列(至少 3 根,延伸后可多至 9+ 根)
pub : RwLock<Vec<Arc<线>>>,
/// 第三类买卖点对应的虚线(离开中枢后不回中枢的虚线)
pub 线: RwLock<Option<Arc<线>>>,
/// 本级第三类买卖点对应的虚线
pub _第三买卖线: RwLock<Option<Arc<线>>>,
}
@@ -54,6 +69,7 @@ impl Clone for 中枢 {
}
impl {
/// 创建新中枢(最多取前 3 根虚线作为基础序列)
pub fn new(: i64, : String, : i64, : Vec<Arc<线>>) -> Self {
Self {
: AtomicI64::new(),
@@ -65,12 +81,14 @@ impl 中枢 {
}
}
pub fn 线(&self, 线: Arc<线>) {
/// 向基础序列尾部添加虚线(中枢延伸),并清除第三买卖线
pub fn _添加虚线(&self, 线: Arc<线>) {
self..write().unwrap().push(线);
*self._第三买卖线.write().unwrap() = None;
*self.线.write().unwrap() = None;
}
/// 返回图表标题字符串,格式为 "文.标识:文.周期:中枢标识:序号"
pub fn (&self) -> String {
format!(
"{}:{}:{}:{}",
@@ -81,14 +99,17 @@ impl 中枢 {
)
}
/// 返回基础序列的最后一根虚线(当前离开段)
pub fn (&self) -> Arc<线> {
Arc::clone(&self..read().unwrap()[self..read().unwrap().len() - 1])
}
/// 返回中枢方向(与基础序列第一段方向相反)
pub fn (&self) -> {
self..read().unwrap()[0].().()
}
/// 中枢上沿 = min(前三段的高)
pub fn (&self) -> f64 {
self..read().unwrap()[..3]
.iter()
@@ -97,6 +118,7 @@ impl 中枢 {
.unwrap_or(0.0)
}
/// 中枢下沿 = max(前三段的低)
pub fn (&self) -> f64 {
self..read().unwrap()[..3]
.iter()
@@ -105,6 +127,7 @@ impl 中枢 {
.unwrap_or(0.0)
}
/// 中枢最高点 = max(所有段的高)
pub fn (&self) -> f64 {
self.
.read()
@@ -115,6 +138,7 @@ impl 中枢 {
.unwrap_or(0.0)
}
/// 中枢最低点 = min(所有段的低)
pub fn (&self) -> f64 {
self.
.read()
@@ -125,10 +149,12 @@ impl 中枢 {
.unwrap_or(0.0)
}
/// 返回基础序列第一段的起点分型
pub fn (&self) -> Arc<> {
Arc::clone(&self..read().unwrap()[0].)
}
/// 返回基础序列最后一段的终点分型
pub fn (&self) -> Arc<> {
Arc::clone(
&*self..read().unwrap()[self..read().unwrap().len() - 1]
@@ -138,8 +164,9 @@ impl 中枢 {
)
}
pub fn 线(&self, 线: Arc<线>) {
*self.线.write().unwrap() = Some(线);
/// 设置第三类买卖点对应的虚线
pub fn 线(&self, 线: Option<Arc<线>>) {
*self.线.write().unwrap() = 线;
}
/// 获取序列 — 基础序列 + 第三买卖线(若有)
@@ -151,6 +178,7 @@ impl 中枢 {
}
/// 返回序列化数据文本,用于调试和存储
pub fn (&self) -> String {
let 线_str = match &*self.线.read().unwrap() {
Some(x) => format!("{}", x),
@@ -175,7 +203,7 @@ impl 中枢 {
}
/// 校验中枢合法性
pub fn (&self, : &[Arc<线>]) -> bool {
pub fn _校验合法性(&self, : &[Arc<线>]) -> bool {
let mut = self..read().unwrap().clone();
let mut : Vec<Arc<线>> = Vec::new();
for in self..read().unwrap().iter() {
@@ -198,7 +226,7 @@ impl 中枢 {
}
if .len() < 3 {
*self.线.write().unwrap() = None;
self.线(None);
*self._第三买卖线.write().unwrap() = None;
return false;
}
@@ -248,7 +276,7 @@ impl 中枢 {
if let Some(ref 线) = 线_opt {
if .iter().any(|x| Arc::as_ptr(x) == Arc::as_ptr(线)) {
if !self..read().unwrap().last().unwrap().(线) {
*self.线.write().unwrap() = None;
self.线(None);
} else if !crate::types::::(
self.(),
self.(),
@@ -257,11 +285,11 @@ impl 中枢 {
)
.()
{
self.线(Arc::clone(线));
*self.线.write().unwrap() = None;
self._添加虚线(Arc::clone(线));
self.线(None);
}
} else {
*self.线.write().unwrap() = None;
self.线(None);
}
}
true
@@ -359,6 +387,7 @@ impl 中枢 {
pub fn (
: Arc<线>, : Arc<线>, : Arc<线>, : i64, : &str
) -> Self {
debug_assert!(Self::(&, &, &), "中枢.创建 基础检查失败");
Self::new(
0,
format!("{}中枢<{}>", , ..read().unwrap()),
@@ -367,8 +396,8 @@ impl 中枢 {
)
}
/// 从序列中获取中枢
pub fn (
/// _从序列中获取中枢
pub fn _从序列中获取中枢(
线: &[Arc<线>],
: ,
: &str,
@@ -385,33 +414,38 @@ impl 中枢 {
None
}
/// 向中枢序列尾部添加
pub fn (
/// _向中枢序列尾部添加
pub fn _向中枢序列尾部添加(
: &mut Vec<Arc<>>, : Arc<>
) {
if let Some() = .last() {
.
.store(..load(Ordering::Relaxed) + 1, Ordering::Relaxed);
// Python: assert seq[-1].获取序列()[-1].序号 <= new.获取序列()[-1].序号
let _seq = .();
let new_seq = .();
if let (Some(_last), Some(new_last)) = (_seq.last(), new_seq.last()) {
if _last..load(Ordering::Relaxed) > new_last..load(Ordering::Relaxed)
{
panic!(
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
_last..load(Ordering::Relaxed),
new_last..load(Ordering::Relaxed)
);
}
let _last_序号 =
.()
.last()
.unwrap()
.
.load(Ordering::Relaxed);
let new_last_序号 =
.()
.last()
.unwrap()
.
.load(Ordering::Relaxed);
if _last_序号 > new_last_序号 {
panic!(
"向中枢序列尾部添加 序号错误 前last={} > new_last={}",
_last_序号, new_last_序号
);
}
}
.push();
}
/// 从中枢序列尾部弹出
pub fn (
pub fn _从中枢序列尾部弹出(
: &mut Vec<Arc<>>,
: &Arc<>,
) -> Option<Arc<>> {
@@ -448,7 +482,7 @@ impl 中枢 {
let = 线
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr())
.unwrap_or(i - 1);
.expect("中枢.分析: 左元素不在虚线序列中");
if && (..load(Ordering::Relaxed) == 0 || == 0) {
continue;
}
@@ -470,7 +504,7 @@ impl 中枢 {
..load(Ordering::Relaxed),
,
));
Self::(, );
Self::_向中枢序列尾部添加(, );
// Python: return 中枢递归分析(虚线序列, 中枢序列, ...)
Self::(线, , , , _层级);
return;
@@ -483,10 +517,10 @@ impl 中枢 {
let mut _idx = .len() - 1;
// Validate via shared reference (中枢 uses RwLock internally)
let needs_pop = ![_idx].(线);
let needs_pop = ![_idx]._校验合法性(线);
if needs_pop {
let = Arc::clone(&[_idx]);
Self::(, &);
Self::_从中枢序列尾部弹出(, &);
Self::(线, , , , _层级);
return;
}
@@ -527,12 +561,29 @@ impl 中枢 {
.(&线)
};
if needs_三买 {
[_idx].线(线.clone());
[_idx].线(Some(线.clone()));
}
} else {
if .is_empty() {
// 仍在范围内:延伸中枢
[_idx].线(线);
debug_assert!(
[_idx]
.
.read()
.unwrap()
.last()
.unwrap()
.(&线),
"中枢延伸: 不连续 {}, {}",
[_idx]
.
.read()
.unwrap()
.last()
.unwrap(),
线
);
[_idx]._添加虚线(线);
} else {
.push(线);
}
@@ -548,9 +599,9 @@ impl 中枢 {
.unwrap()
.()
.();
match Self::(&, , ) {
match Self::_从序列中获取中枢(&, , ) {
Some() => {
Self::(, );
Self::_向中枢序列尾部添加(, );
// Python: 当前中枢 = 新中枢
_idx = .len() - 1;
= [_idx].();
@@ -732,7 +783,7 @@ mod tests {
assert_eq!(..load(Ordering::Relaxed), 99);
// RefCell 第三买卖线读写
.线(Arc::clone(&1));
.线(Some(Arc::clone(&1)));
assert!(.线.read().unwrap().is_some());
assert_eq!(
Arc::as_ptr(.线.read().unwrap().as_ref().unwrap()),
@@ -764,7 +815,7 @@ mod tests {
);
assert_eq!(..read().unwrap().len(), 3);
.线(Arc::clone(&4));
._添加虚线(Arc::clone(&4));
assert_eq!(..read().unwrap().len(), 4);
assert_eq!(
Arc::as_ptr(&..read().unwrap()[3]),
@@ -785,12 +836,12 @@ mod tests {
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
.线(Arc::clone(&1));
.线(Some(Arc::clone(&1)));
*._第三买卖线.write().unwrap() = Some(Arc::clone(&2));
assert!(.线.read().unwrap().is_some());
assert!(._第三买卖线.read().unwrap().is_some());
.线(Arc::clone(&4));
._添加虚线(Arc::clone(&4));
// 添加虚线后第三买卖线被清除
assert!(.线.read().unwrap().is_none());
assert!(._第三买卖线.read().unwrap().is_none());
@@ -812,7 +863,7 @@ mod tests {
1,
vec![Arc::clone(&1), Arc::clone(&2), Arc::clone(&3)],
);
.线(Arc::clone(&1));
.线(Some(Arc::clone(&1)));
let = .clone();
@@ -885,7 +936,7 @@ mod tests {
assert_eq!(2..load(Ordering::Relaxed), 88);
// 通过 rc1 添加虚线
1.线(Arc::clone(&4));
1._线(Arc::clone(&4));
assert_eq!(2..read().unwrap().len(), 4);
// 验证共享的 Arc<虚线> 指针一致
File diff suppressed because it is too large Load Diff
+401 -66
View File
@@ -27,33 +27,45 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::fractal_obj::;
use crate::types::bsp_type::;
use crate::types::;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::atomic::Ordering;
/// 基础买卖点 — 买卖点的基础数据结构
///
/// 包含买卖点的完整信息:类型、关联分型/K线、失效与终结状态等。
#[derive(Debug, Clone)]
pub struct {
/// 买卖点备注文本
pub : String,
/// 买卖点类型(一买/一卖/二买/二卖/三买/三卖/T1/T2/T3 等)
pub : ,
/// 买卖点对应的分型
pub : Arc<>,
/// 买卖点对应的缠论K线(即分型的中缠K)
pub K线: Arc<K线>,
/// 当前K线(买卖点生成时的K线)
pub K线: Arc<K线>,
/// 失效K线(买卖点失效时设置)
pub K线: Option<Arc<K线>>,
/// 终结K线(买卖点终结时设置)
pub K线: Option<Arc<K线>>,
/// 中枢破位值
pub : f64,
/// 分型结构(可选,用于补充确认)
pub : Option<>,
/// 当前缠K序号 — 与 Python 一致,偏移计算使用缠论K线序号而非标的K线序号
pub : i64,
/// 创建时的缠K序号,用于偏移计算(与买卖点K线.序号同尺度)
/// None 时退化为使用当前K线.序号(bar序号,旧行为)
pub K序号: Option<i64>,
}
impl {
/// 创建基础买卖点,买卖点K线自动取自买卖点分型的中缠K
pub fn new(
: ,
K线: Arc<K线>,
: Arc<>,
: String,
: f64,
: i64,
) -> Self {
let K线 = Arc::clone(&.);
Self {
@@ -66,13 +78,17 @@ impl 基础买卖点 {
K线: None,
: ,
: None,
,
K序号: None,
}
}
/// 偏移 — 当前缠K序号与买卖点K线序号的差(对齐 Python,使用缠论K线序号)
/// 偏移 — 当前缠K序号与买卖点K线序号的差
/// 如果设置了当前缠K序号(来自生成买卖点),使用缠K序号;否则退化为使用 bar序号
pub fn (&self) -> i64 {
self. - self.K线..load(Ordering::Relaxed)
match self.K序号 {
Some(ck_idx) => ck_idx - self.K线..load(Ordering::Relaxed),
None => self.K线. - self.K线..load(Ordering::Relaxed),
}
}
/// 失效偏移
@@ -112,116 +128,206 @@ impl std::fmt::Display for 基础买卖点 {
}
}
/// 买卖点 — 包含一二三类买卖点的工厂方法
/// 买卖点 — 包含全部 18 种买卖点类型的工厂方法
pub struct ;
impl {
/// 创建 一卖 类型的基础买卖点
pub fn (
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
: i64,
) -> {
::new(
::,
K线,
,
,
,
,
)
::new(::, K线, , , )
}
/// 创建 一买 类型的基础买卖点
pub fn (
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
: i64,
) -> {
::new(
::,
K线,
,
,
,
,
)
::new(::, K线, , , )
}
/// 创建 二卖 类型的基础买卖点
pub fn (
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
: i64,
) -> {
::new(
::,
K线,
,
,
,
,
)
::new(::, K线, , , )
}
/// 创建 二买 类型的基础买卖点
pub fn (
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
: i64,
) -> {
::new(
::,
K线,
,
,
,
,
)
::new(::, K线, , , )
}
/// 创建 三卖 类型的基础买卖点
pub fn (
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
: i64,
) -> {
::new(
::,
K线,
,
,
,
,
)
::new(::, K线, , , )
}
/// 创建 三买 类型的基础买卖点
pub fn (
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
: i64,
) -> {
::new(
::,
K线,
,
,
,
,
)
::new(::, K线, , , )
}
/// 创建 T1卖 类型的基础买卖点
pub fn T1卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1卖, K线, , , )
}
/// 创建 T1买 类型的基础买卖点
pub fn T1买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1买, K线, , , )
}
/// 创建 T1P卖 类型的基础买卖点
pub fn T1P卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1P卖, K线, , , )
}
/// 创建 T1P买 类型的基础买卖点
pub fn T1P买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T1P买, K线, , , )
}
/// 创建 T2卖 类型的基础买卖点
pub fn T2卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2卖, K线, , , )
}
/// 创建 T2买 类型的基础买卖点
pub fn T2买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2买, K线, , , )
}
/// 创建 T2S卖 类型的基础买卖点
pub fn T2S卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2S卖, K线, , , )
}
/// 创建 T2S买 类型的基础买卖点
pub fn T2S买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T2S买, K线, , , )
}
/// 创建 T3A卖 类型的基础买卖点
pub fn T3A卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3A卖, K线, , , )
}
/// 创建 T3A买 类型的基础买卖点
pub fn T3A买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3A买, K线, , , )
}
/// 创建 T3B卖 类型的基础买卖点
pub fn T3B卖点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3B卖, K线, , , )
}
/// 创建 T3B买 类型的基础买卖点
pub fn T3B买点(
: Arc<>,
K线: Arc<K线>,
_标识: &str,
: String,
: f64,
) -> {
::new(::T3B买, K线, , , )
}
/// 生成买卖点 — 根据参数自动选择类型
@@ -238,12 +344,12 @@ impl 买卖点 {
""
};
let = format!("{}_{}{}{}", , , , );
let = .;
let = .();
// 当前K线 — 从缠K获取其标的K线
let K线 = Arc::clone(&*K.K线.read().unwrap());
// 当前序号 — 使用缠论K线序号(对齐 Python 偏移计算
let = K..load(Ordering::Relaxed);
// 当前缠K序号 — 与买卖点K线(分型.中.序号)同尺度,用于偏移计算
let K序号 = K..load(Ordering::Relaxed);
let = match (, ) {
("", "") => ::,
@@ -252,9 +358,238 @@ impl 买卖点 {
("", "") => ::,
("", "") => ::,
("", "") => ::,
("T1", "") => ::T1买,
("T1", "") => ::T1卖,
("T1P", "") => ::T1P买,
("T1P", "") => ::T1P卖,
("T2", "") => ::T2买,
("T2", "") => ::T2卖,
("T2S", "") => ::T2S买,
("T2S", "") => ::T2S卖,
("T3A", "") => ::T3A买,
("T3A", "") => ::T3A卖,
("T3B", "") => ::T3B买,
("T3B", "") => ::T3B卖,
_ => ::, // fallback
};
::new(, K线, , , , )
let mut bsp = ::new(, K线, , , );
bsp.K序号 = Some(K序号);
bsp
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::kline::bar::K线;
use crate::kline::chan_kline::K线;
use crate::structure::fractal_obj::;
use crate::types::;
use crate::types::;
use std::sync::Arc;
use std::sync::atomic::Ordering;
fn _创建普K(: i64, : i64) -> Arc<K线> {
Arc::new(K线 {
,
,
: 100.0,
: 90.0,
: 95.0,
: 95.0,
..Default::default()
})
}
fn _创建缠K(
: i64,
: i64,
: f64,
: f64,
: Option<>,
) -> Arc<K线> {
let K = _创建普K(, 0);
Arc::new(K线::K(
,
,
,
::,
,
,
K,
None,
))
}
fn _创建底分型_中(: i64, : i64) -> Arc<> {
let = _创建缠K(, , 100.0, 90.0, Some(::));
..store(, Ordering::Relaxed);
..set(90.0);
let = _创建缠K( - 1, - 100, 100.0, 92.0, Some(::));
..store( - 1, Ordering::Relaxed);
let = _创建缠K( + 1, + 100, 100.0, 92.0, Some(::));
..store( + 1, Ordering::Relaxed);
Arc::new(::new(Some(), , Some()))
}
fn _创建顶分型_中(: i64, : i64) -> Arc<> {
let = _创建缠K(, , 100.0, 90.0, Some(::));
..store(, Ordering::Relaxed);
..set(100.0);
let = _创建缠K( - 1, - 100, 98.0, 88.0, Some(::));
..store( - 1, Ordering::Relaxed);
let = _创建缠K( + 1, + 100, 98.0, 88.0, Some(::));
..store( + 1, Ordering::Relaxed);
Arc::new(::new(Some(), , Some()))
}
// ========== 基础买卖点 构造测试 ==========
#[test]
fn test_基础买卖点_new() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 0);
let bsp = ::new(::, K, .clone(), "测试".into(), 90.0);
assert_eq!(bsp., "测试");
assert_eq!(bsp., ::);
assert_eq!(bsp., 90.0);
assert!(bsp.K线.is_none());
assert!(!bsp.());
}
// ========== 偏移 测试 ==========
#[test]
fn test_偏移_无缠K序号_退化为bar序号差值() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 15); // bar 序号=15
let bsp = ::new(::, K, .clone(), "".into(), 0.0);
// 无当前缠K序号: 偏移 = 15 - 10 = 5
assert_eq!(bsp.(), 5);
}
#[test]
fn test_偏移_有缠K序号_使用缠K序号差值() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 100); // bar 序号(不使用)
let mut bsp = ::new(::, K, .clone(), "".into(), 0.0);
bsp.K序号 = Some(15);
// 有当前缠K序号: 偏移 = 15 - 10 = 5
assert_eq!(bsp.(), 5);
}
// ========== 失效偏移 测试 ==========
#[test]
fn test_失效偏移_无失效K线返回负一() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 0);
let bsp = ::new(::, K, .clone(), "".into(), 0.0);
assert_eq!(bsp.(), -1);
}
#[test]
fn test_失效偏移_有失效K线() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 0);
let mut bsp = ::new(::, K, .clone(), "".into(), 0.0);
bsp.K线 = Some(_创建普K(1200, 20)); // 序号=20
// 失效偏移 = 20 - 10 = 10
assert_eq!(bsp.(), 10);
}
// ========== 有效性 测试 ==========
#[test]
fn test_有效性_无失效K线为false() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 0);
let bsp = ::new(::, K, .clone(), "".into(), 0.0);
assert!(!bsp.());
}
#[test]
fn test_有效性_有失效K线为true() {
let = _创建底分型_中(10, 1000);
let K = _创建普K(1100, 0);
let mut bsp = ::new(::, K, .clone(), "".into(), 0.0);
bsp.K线 = Some(_创建普K(1200, 0));
assert!(bsp.());
}
// ========== 生成买卖点 测试 ==========
#[test]
fn test_生成买卖点_一买() {
let = _创建底分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征A", "", "本级", .clone(), K);
assert_eq!(bsp., ::);
assert_eq!(bsp.K序号, Some(5));
}
#[test]
fn test_生成买卖点_一卖() {
let = _创建顶分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征A", "", "本级", .clone(), K);
assert_eq!(bsp., ::);
}
#[test]
fn test_生成买卖点_二买() {
let = _创建底分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征B", "", "同级", .clone(), K);
assert_eq!(bsp., ::);
}
#[test]
fn test_生成买卖点_三卖() {
let = _创建顶分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征C", "", "本级", .clone(), K);
assert_eq!(bsp., ::);
}
#[test]
fn test_生成买卖点_T1买() {
let = _创建底分型_中(5, 1000);
let K = ..clone();
let bsp = ::("事后", "T1", "次级", .clone(), K);
assert_eq!(bsp., ::T1买);
}
#[test]
fn test_生成买卖点_T2S卖() {
let = _创建顶分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征D", "T2S", "同级", .clone(), K);
assert_eq!(bsp., ::T2S卖);
}
#[test]
fn test_生成买卖点_T3A买() {
let = _创建底分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征E", "T3A", "本级", .clone(), K);
assert_eq!(bsp., ::T3A买);
}
#[test]
fn test_生成买卖点_T3B买() {
let = _创建底分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征F", "T3B", "本级", .clone(), K);
assert_eq!(bsp., ::T3B买);
}
#[test]
fn test_生成买卖点_破位值来自分型特征值() {
let = _创建底分型_中(5, 1000);
let K = ..clone();
let bsp = ::("特征G", "", "本级", .clone(), K);
assert_eq!(bsp., .());
}
}
+43 -8
View File
@@ -29,6 +29,7 @@ use crate::kline::bar::K线;
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
use tracing::{error, info};
/// 立体分析器 — 多周期协调器
///
@@ -42,6 +43,7 @@ pub struct 立体分析器 {
}
impl {
/// 创建立体分析器,自动创建K线合成器 + 每周期一个观察者
pub fn new(
: String,
: Vec<i64>,
@@ -51,6 +53,7 @@ impl 立体分析器 {
let mut = ;
.sort();
let = [0];
let = [1];
let = .unwrap_or_default();
let = .unwrap_or_default();
@@ -71,6 +74,33 @@ impl 立体分析器 {
.insert(, );
}
// 显示周期特殊配置
{
let = .get(&).expect("显示周期观察者不存在");
let mut guard = .write().unwrap();
guard..K线 = true;
guard.. = true;
guard..线 = true;
guard.. = true;
guard.();
}
// 非显示周期的基础缠K序列对齐至显示周期
{
let K序列 =
.get(&)
.map(|o| o.read().unwrap().K线序列.clone())
.unwrap_or_default();
for & in & {
if !=
&& let Some() = .get(&)
{
.write().unwrap().K序列 = K序列.clone();
}
}
}
Self {
,
,
@@ -83,11 +113,10 @@ impl 立体分析器 {
/// 匹配 Python __K线回调:合成器完成K线时喂给观察者
pub fn K线(&mut self, K: K线) {
if K. != self. {
eprintln!(
panic!(
"立体分析器.投喂K线 周期不匹配 {} != {}",
K., self.
);
return;
}
// Feed to synthesizer, get completion events
@@ -97,6 +126,9 @@ impl 立体分析器 {
for (, K线) in {
if let Some() = self..get(&) {
.write().unwrap().K线(K线);
if let Some(K线) = self.K线合成器.K线() {
.write().unwrap().K线(K线.clone());
}
}
}
}
@@ -108,10 +140,13 @@ impl 立体分析器 {
/// 测试_保存数据 — 多级别数据拆分保存
/// 创建父目录 PyM_{标识}_{起始时间}_{结束时间},各周期观察者保存到子目录
pub fn _保存数据(&self) {
let = std::env::var("CHANLUN_DATA_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::env::temp_dir());
pub fn _保存数据(&self, root: Option<&str>) {
let = match root {
Some(r) => std::path::PathBuf::from(r),
None => std::env::var("CHANLUN_DATA_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::env::temp_dir()),
};
let = self
.
@@ -139,7 +174,7 @@ impl 立体分析器 {
let = .join(&);
if let Err(e) = std::fs::create_dir_all(&) {
eprintln!("创建目录失败: {} -> {}", .display(), e);
error!("创建目录失败: {} -> {}", .display(), e);
return;
}
@@ -152,6 +187,6 @@ impl 立体分析器 {
}
}
println!("多级别数据拆分保存完成,目录:{}", .display());
info!("多级别数据拆分保存完成,目录:{}", .display());
}
}
+39 -36
View File
@@ -34,6 +34,7 @@ use crate::types::相对方向;
use crate::utils::datetime;
use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
use tracing::{error, info};
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
pub struct {
@@ -76,9 +77,9 @@ pub struct 观察者 {
}
impl {
/// 创建观察者,初始化所有序列为空,若配置了手动终止则解析时间戳
pub fn new(: String, : i64, : ) -> Arc<RwLock<Self>> {
let = if . != "1970-01-01 00:00:00" && !..is_empty()
{
let = if !..is_empty() {
datetime::(&.)
} else {
None
@@ -147,10 +148,10 @@ impl 观察者 {
/// 增加原始K线 — 单根K线投喂入口
pub fn K线(&mut self, K: K线) {
if let Some() = self. {
if K. > {
return;
}
if let Some() = self.
&& K. >
{
return;
}
self.__处理数据(K);
}
@@ -270,6 +271,9 @@ impl 观察者 {
}
}
/// 识别买卖点(占位方法,具体逻辑在子类或Rust核心中实现)
pub fn (&self) {}
/// 静态重新分析 — 遍历所有缠K重新生成分型/笔/线段
pub fn (&mut self) {
self..clear();
@@ -355,7 +359,7 @@ impl 观察者 {
}
/// 测试_保存数据 — 输出各序列数据文本到文件
pub fn _保存数据(&self, root: Option<&str>) {
pub fn _保存数据(&self, root: Option<&str>) -> String {
let _文本数据: Vec<String> = self..iter().map(|b| b.()).collect();
let 线_文本数据: Vec<String> =
self.线.iter().map(|s| s.()).collect();
@@ -402,9 +406,7 @@ impl 观察者 {
// 确定根目录
let = match root {
Some(r) => std::path::PathBuf::from(r),
None => std::env::var("CHANLUN_DATA_DIR")
.map(std::path::PathBuf::from)
.unwrap_or_else(|_| std::env::temp_dir()),
None => std::env::temp_dir(),
};
// 生成子目录名称
@@ -414,8 +416,8 @@ impl 观察者 {
let = .join(&);
if let Err(e) = std::fs::create_dir_all(&) {
eprintln!("创建目录失败: {} -> {}", .display(), e);
return;
error!("创建目录失败: {} -> {}", .display(), e);
return String::new();
}
// 缠K data for debugging
@@ -480,11 +482,12 @@ impl 观察者 {
let = .join(format!("{}.txt", ));
let = .join("\n") + "\n";
if let Err(e) = std::fs::write(&, &) {
eprintln!("写入文件失败: {} -> {}", .display(), e);
error!("写入文件失败: {} -> {}", .display(), e);
}
}
println!("全部数据拆分保存完成,目录:{}", .display());
info!("全部数据拆分保存完成,目录:{}", .display());
.display().to_string()
}
/// 加载本地数据 — 从 .nb 文件加载数据到当前观察者(先重置再投喂)
@@ -552,9 +555,9 @@ mod tests {
for (i, bi) in obs_ref..iter().enumerate() {
let pu_seq = bi.K序列(&obs_ref.K线序列);
if pu_seq.is_empty() {
println!("{}: 获取普K序列 返回空 (fallback failed)", i);
println!(" 文.中.标的K线 原始起始序号: {}", bi...);
println!(
info!("{}: 获取普K序列 返回空 (fallback failed)", i);
info!(" 文.中.标的K线 原始起始序号: {}", bi...);
info!(
" 武.中.标的K线 原始结束序号: {}",
bi.
.read()
@@ -563,7 +566,7 @@ mod tests {
.
.load(Ordering::Relaxed)
);
println!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
info!(" 普通K线序列.len: {}", obs_ref.K线序列.len());
} else {
let first_ptr = Arc::as_ptr(&pu_seq[0]);
let found = obs_ref
@@ -571,15 +574,15 @@ mod tests {
.iter()
.any(|k| Arc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
info!("{}: 获取普K序列[0] 的 Rc 指针不在 普通K线序列 中!", i);
let wen_ptr = Arc::as_ptr(&*bi...K线.read().unwrap());
let wen_found = obs_ref
.K线序列
.iter()
.any(|k| Arc::as_ptr(k) == wen_ptr);
println!(" 文.中.标的K线 在序列中: {}", wen_found);
info!(" 文.中.标的K线 在序列中: {}", wen_found);
} else {
println!("{}: OK, 获取普K序列[0] 在序列中找到", i);
info!("{}: OK, 获取普K序列[0] 在序列中找到", i);
}
}
}
@@ -602,21 +605,21 @@ mod tests {
}
let obs = obs_ref.read().unwrap();
println!("普通K线序列.len: {}", obs.K线序列.len());
println!("笔序列.len: {}", obs..len());
info!("普通K线序列.len: {}", obs.K线序列.len());
info!("笔序列.len: {}", obs..len());
for (i, bi) in obs..iter().enumerate() {
let pu_seq = bi.K序列(&obs.K线序列);
if pu_seq.is_empty() {
println!("{}: 获取普K序列 返回空!", i);
info!("{}: 获取普K序列 返回空!", i);
} else {
let first_ptr = Arc::as_ptr(&pu_seq[0]);
let found = obs.K线序列.iter().any(|k| Arc::as_ptr(k) == first_ptr);
if !found {
println!("{}: 获取普K序列[0] 指针不在序列中!", i);
info!("{}: 获取普K序列[0] 指针不在序列中!", i);
}
if i < 5 {
println!("{}: OK, len={}", i, pu_seq.len());
info!("{}: OK, len={}", i, pu_seq.len());
}
}
}
@@ -640,13 +643,13 @@ mod tests {
let _ptr = Arc::as_ptr(&bi.);
let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr);
if !_found {
println!("{}: 文(时间戳={}) 不在分型序列中!", i, bi..());
info!("{}: 文(时间戳={}) 不在分型序列中!", i, bi..());
}
let _ptr = Arc::as_ptr(&*bi..read().unwrap());
let _found = obs_ref..iter().any(|f| Arc::as_ptr(f) == _ptr);
if !_found {
println!(
info!(
"笔 {}: 武(时间戳={}) 不在分型序列中!",
i,
bi..read().unwrap().()
@@ -674,7 +677,7 @@ mod tests {
let bi_ptr = Arc::as_ptr(bi_in_seg);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found {
println!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j);
info!("线段 {} 的基础序列[{}] 不在笔序列中!", i, j);
}
}
}
@@ -698,7 +701,7 @@ mod tests {
let bi_ptr = Arc::as_ptr(bi_in_hub);
let found = obs_ref..iter().any(|b| Arc::as_ptr(b) == bi_ptr);
if !found {
println!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j);
info!("笔中枢 {} 的基础序列[{}] 不在笔序列中!", i, j);
}
}
}
@@ -708,7 +711,7 @@ mod tests {
let seg_ptr = Arc::as_ptr(seg_in_hub);
let found = obs_ref.线.iter().any(|s| Arc::as_ptr(s) == seg_ptr);
if !found {
println!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j);
info!("线段中枢 {} 的基础序列[{}] 不在线段序列中!", i, j);
}
}
}
@@ -749,7 +752,7 @@ mod tests {
assert_eq!(1, 2, "重复计算中枢数不一致");
assert_eq!(1, 2, "重复计算笔中枢数不一致");
println!(
info!(
"两次计算结果一致: 笔={}, 线段={}, 中枢={}, 笔中枢={}",
1, 1, 1, 1
);
@@ -795,7 +798,7 @@ mod tests {
assert_eq!(, , "重置后重新投喂笔数不一致");
assert_eq!(, , "重置后重新投喂线段数不一致");
println!("重置后重投一致: 笔={}, 线段={}", , );
info!("重置后重投一致: 笔={}, 线段={}", , );
}
// ============================================================
@@ -882,14 +885,14 @@ mod tests {
let _ptr = Arc::as_ptr(&f.);
let _found = obs_ref.K线序列.iter().any(|k| Arc::as_ptr(k) == _ptr);
if !_found {
println!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.);
info!("分型 {} 的 中(时间戳={}) 不在缠论K线序列中!", i, f.);
}
if let Some(ref ) = f. {
let _ptr = Arc::as_ptr();
let _found = obs_ref.K线序列.iter().any(|k| Arc::as_ptr(k) == _ptr);
if !_found {
println!("分型 {} 的 左 不在缠论K线序列中!", i);
info!("分型 {} 的 左 不在缠论K线序列中!", i);
}
}
@@ -897,7 +900,7 @@ mod tests {
let _ptr = Arc::as_ptr();
let _found = obs_ref.K线序列.iter().any(|k| Arc::as_ptr(k) == _ptr);
if !_found {
println!("分型 {} 的 右 不在缠论K线序列中!", i);
info!("分型 {} 的 右 不在缠论K线序列中!", i);
}
}
}
+16 -1
View File
@@ -34,6 +34,7 @@ pub struct K线合成器 {
}
impl K线合成器 {
/// 创建K线合成器,按周期升序排列,初始化当前K线和合成K线列表
pub fn new(: String, : Vec<i64>) -> Self {
let mut = ;
.sort();
@@ -53,6 +54,20 @@ impl K线合成器 {
}
}
/// 投喂 — 便捷入口,直接从 OHLCV 创建 K线 并投喂
pub fn (
&mut self,
: i64,
: f64,
: f64,
: f64,
: f64,
: f64,
) -> Vec<(i64, K线)> {
let K = K线::K(&self., , , , , , , 0, 0);
self.K线(K)
}
/// 投喂K线 — 输入最小周期K线,合成为所有目标周期
/// 返回本次投喂完成了哪些周期的K线(周期 → 完成K线)
pub fn K线(&mut self, K: K线) -> Vec<(i64, K线)> {
@@ -91,7 +106,7 @@ impl K线合成器 {
fn _对齐时间戳(&self, : i64, : i64) -> i64 {
if == 0 {
return ;
panic!("_对齐时间戳: 周期不能为0");
}
( / ) *
}
+180 -13
View File
@@ -23,6 +23,7 @@
*/
use serde::{Deserialize, Deserializer, Serialize};
use tracing::warn;
fn is_infinite_f64(v: &f64) -> bool {
v.is_infinite()
@@ -30,118 +31,223 @@ fn is_infinite_f64(v: &f64) -> bool {
/// 缠论配置 —— 控制所有分析阶段的行为
///
/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错
/// 50+ 参数集中控制缠K合并、笔/线段划分、中枢识别、买卖点生成等所有阶段。
/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错。
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct {
// ---- 基础 ----
/// 品种标识(如 "btcusd"
pub : String,
// ---- 缠K ----
/// 包含处理时使用合并替换模式(而非添加模式)
pub K合并替换: bool,
// ---- 笔 ----
/// 笔内最少缠K数量(含端点)
pub : i64,
/// 笔内相同终点取舍开关
pub : bool,
/// 笔内起始分型包含整笔
pub : bool,
/// 笔内起始分型包含整笔(含右端点)
pub _包括右: bool,
/// 笔内原始K线包含整笔
pub K线包含整笔: bool,
/// 笔次级成笔(允许在非分型处成笔)
pub : bool,
/// 笔弱化开关(允许更少元素成笔)
pub : bool,
/// 笔弱化模式下的最小原始K线数
pub _原始数量: i64,
// ---- 线段 ----
/// 线段非缺口下的穿刺处理
pub 线_非缺口下穿刺: bool,
/// 线段特征序列忽略老阴老阳
pub 线_特征序列忽视老阴老阳: bool,
/// 线段缺口后紧急修正
pub 线_缺口后紧急修正: bool,
/// 线段修正开关
pub 线_修正: bool,
/// 线段内部中枢图显示
pub 线: bool,
/// 扩展线段当下分析模式
pub 线_当下分析: bool,
// ---- 分析开关 ----
/// 是否分析笔
pub : bool,
/// 是否分析线段
pub 线: bool,
/// 是否分析扩展线段
pub 线: bool,
/// 是否分析笔中枢
pub : bool,
/// 是否分析线段中枢
pub 线: bool,
// ---- 终止 ----
/// 手动终止时间(时间字符串,非空时生效)
pub : String,
// ---- 指标 ----
/// 是否计算技术指标
pub : bool,
/// 是否计算布林带
pub BOLL: bool,
/// 指标计算方式(开/高/低/收/高低均值/高低收均值/开高低收均值)
#[serde(deserialize_with = "deserialize_指标计算方式")]
pub : String,
// ---- MACD ----
/// MACD 快线 EMA 周期
pub 线_快线周期: i64,
/// MACD 慢线 EMA 周期
pub 线_慢线周期: i64,
/// MACD 信号线周期
pub 线_信号周期: i64,
/// MACD 多参数列表: Vec<(key, 快线, 慢线, 信号)>
#[serde(default)]
pub MACD_参数列表: Vec<(String, i64, i64, i64)>,
// ---- RSI ----
/// RSI 计算周期
pub _周期: i64,
/// RSI SMA 平滑周期
pub _移动平均线周期: i64,
/// RSI 超买阈值
pub _超买阈值: f64,
/// RSI 超卖阈值
pub _超卖阈值: f64,
/// RSI 多周期列表: Vec<(key, 周期)>
#[serde(default)]
pub RSI_周期列表: Vec<(String, i64)>,
// ---- KDJ ----
/// KDJ RSV 周期
pub _RSV周期: i64,
/// KDJ K 值平滑周期
pub _K值平滑周期: i64,
/// KDJ D 值平滑周期
pub _D值平滑周期: i64,
/// KDJ 超买阈值
pub _超买阈值: f64,
/// KDJ 超卖阈值
pub _超卖阈值: f64,
/// KDJ 多参数列表: Vec<(key, RSV周期, K平滑, D平滑)>
#[serde(default)]
pub KDJ_参数列表: Vec<(String, i64, i64, i64)>,
// ---- BOLL ----
/// 布林带周期
pub _周期: i64,
/// 布林带标准差倍数
pub _标准差倍数: f64,
/// BOLL 多参数列表: Vec<(key, 周期, 标准差倍数)>
#[serde(default)]
pub BOLL_参数列表: Vec<(String, i64, f64)>,
// ---- 均线 ----
/// 均线类型列表: ["SMA", "EMA", ...]
#[serde(default)]
pub 线_类型列表: Vec<String>,
/// 均线周期列表: [5, 10, 20, ...]
#[serde(default)]
pub 线_周期列表: Vec<i64>,
// ---- 推送/显示 ----
/// 是否启用图表展示
pub : bool,
/// 是否推送K线
pub K线: bool,
/// 是否推送笔
pub : bool,
/// 是否推送线段
pub 线: bool,
/// 是否推送中枢
pub : bool,
// ---- 图表展示细分 ----
/// 图表展示笔
pub _笔: bool,
/// 图表展示线段
pub _线段: bool,
/// 图表展示扩展线段
pub _扩展线段: bool,
/// 图表展示扩展线段(线段级)
pub _扩展线段_线段: bool,
/// 图表展示线段之线段
pub _线段_线段: bool,
/// 图表展示笔中枢
pub _中枢_笔: bool,
/// 图表展示线段中枢
pub _中枢_线段: bool,
/// 图表展示扩展中枢
pub _中枢_扩展线段: bool,
/// 图表展示扩展中枢(线段级)
pub _中枢_扩展线段_线段: bool,
/// 图表展示线段之中枢
pub _中枢_线段_线段: bool,
/// 图表展示线段内部中枢
pub _中枢_线段内部: bool,
// ---- 买卖点 ----
/// 买卖点偏移量
pub : i64,
/// 买卖点激进识别模式
pub : bool,
/// 买卖点与MACD柱强相关
pub MACD柱强相关: bool,
/// 买卖点错过误差值
pub : f64,
/// 买卖点指标模式(任意/配置/全量/相对)
#[serde(deserialize_with = "deserialize_买卖点_指标模式")]
pub _指标模式: String,
/// 买卖点指标匹配 MACD
pub _指标匹配_MACD: bool,
/// 买卖点指标匹配 KDJ
pub _指标匹配_KDJ: bool,
/// 买卖点指标匹配 RSI
pub _指标匹配_RSI: bool,
/// 买卖点背离率阈值(Infinity 表示不使用)
#[serde(skip_serializing_if = "is_infinite_f64")]
pub _背离率: f64,
/// 买卖点 T2 回调阈值
pub _T2_回调阈值: f64,
/// 买卖点 T2S 最大层级
pub _T2S_最大层级: i64,
/// 买卖点峰值条件
pub _峰值条件: bool,
/// 买卖点计算方式(峰/谷等)
pub _计算方式: String,
/// 是否计算线段BSP1
pub _计算线段BSP1: bool,
/// 是否处理BSP2
pub _处理BSP2: bool,
/// 是否计算线段BSP3
pub _计算线段BSP3: bool,
/// 是否依赖T1买卖点
pub _依赖T1: bool,
/// 买卖点中枢来源(实/虚/合)
pub _中枢来源: String,
/// 买卖点调试输出
pub _调试输出: bool,
// ---- 背驰 ----
/// 线段内部背驰使用 MACD
pub 线_MACD: bool,
/// 线段内部背驰使用斜率
pub 线_斜率: bool,
/// 线段内部背驰使用测度
pub 线_测度: bool,
/// 线段内部背驰模式(任意/配置/全量/相对)
#[serde(deserialize_with = "deserialize_线段内部背驰_模式")]
pub 线_模式: String,
// ---- 文件 ----
/// 加载数据文件路径
pub : String,
}
@@ -163,7 +269,9 @@ where
if VALID.contains(&s.as_str()) {
Ok(s)
} else {
eprintln!("\x1b[33m[配置警告]\x1b[m 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
warn!(
"[配置警告] 指标计算方式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
);
Ok(DEFAULT.to_string())
}
}
@@ -178,7 +286,9 @@ where
if VALID.contains(&s.as_str()) {
Ok(s)
} else {
eprintln!("\x1b[33m[配置警告]\x1b[m 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
warn!(
"[配置警告] 买卖点_指标模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
);
Ok(DEFAULT.to_string())
}
}
@@ -193,7 +303,9 @@ where
if VALID.contains(&s.as_str()) {
Ok(s)
} else {
eprintln!("\x1b[33m[配置警告]\x1b[m 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\"");
warn!(
"[配置警告] 线段内部背驰_模式: \"{s}\" 不在有效值 {VALID:?} 内,已使用默认值 \"{DEFAULT}\""
);
Ok(DEFAULT.to_string())
}
}
@@ -224,6 +336,7 @@ impl Default for 缠论配置 {
线: true,
: String::new(),
: true,
BOLL: false,
: "".into(),
线_快线周期: 13,
线_慢线周期: 31,
@@ -237,6 +350,14 @@ impl Default for 缠论配置 {
_D值平滑周期: 5,
_超买阈值: 80.0,
_超卖阈值: 20.0,
MACD_参数列表: Vec::new(),
RSI_周期列表: Vec::new(),
KDJ_参数列表: Vec::new(),
_周期: 20,
_标准差倍数: 2.0,
BOLL_参数列表: Vec::new(),
线_类型列表: Vec::new(),
线_周期列表: Vec::new(),
: true,
K线: true,
: true,
@@ -282,18 +403,64 @@ impl Default for 缠论配置 {
}
impl {
/// 解析MACD参数列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析MACD参数列表(&self) -> Vec<(String, i64, i64, i64)> {
if !self.MACD_参数列表.is_empty() {
return self.MACD_参数列表.clone();
}
vec![(
"macd".into(),
self.线_快线周期,
self.线_慢线周期,
self.线_信号周期,
)]
}
/// 解析RSI周期列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析RSI周期列表(&self) -> Vec<(String, i64)> {
if !self.RSI_周期列表.is_empty() {
return self.RSI_周期列表.clone();
}
vec![("rsi".into(), self._周期)]
}
/// 解析KDJ参数列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析KDJ参数列表(&self) -> Vec<(String, i64, i64, i64)> {
if !self.KDJ_参数列表.is_empty() {
return self.KDJ_参数列表.clone();
}
vec![(
"kdj".into(),
self._RSV周期,
self._K值平滑周期,
self._D值平滑周期,
)]
}
/// 解析BOLL参数列表 — 如果列表非空则使用列表,否则返回默认单组
pub fn _解析BOLL参数列表(&self) -> Vec<(String, i64, f64)> {
if !self.BOLL_参数列表.is_empty() {
return self.BOLL_参数列表.clone();
}
vec![("boll".into(), self._周期, self._标准差倍数)]
}
/// 序列化为 JSON 字符串
pub fn to_json(&self) -> String {
serde_json::to_string_pretty(self).unwrap_or_default()
}
/// 从 JSON 字符串反序列化
pub fn from_json(json_str: &str) -> Result<Self, serde_json::Error> {
serde_json::from_str(json_str)
}
/// 保存配置到 JSON 文件
pub fn (&self, path: &str) -> std::io::Result<()> {
std::fs::write(path, self.to_json())
}
/// 从 JSON 文件加载配置
pub fn (path: &str) -> Result<Self, Box<dyn std::error::Error>> {
let content = std::fs::read_to_string(path)?;
let config = Self::from_json(&content)?;
@@ -336,11 +503,11 @@ impl 缠论配置 {
serde_json::Map<String, serde_json::Value>,
> = std::collections::BTreeMap::new();
for (key, value) in map {
if let Some(pos) = key.find('_') {
if let Ok(num) = key[..pos].parse::<i64>() {
let field = key[pos + 1..].to_string();
groups.entry(num).or_default().insert(field, value.clone());
}
if let Some(pos) = key.find('_')
&& let Ok(num) = key[..pos].parse::<i64>()
{
let field = key[pos + 1..].to_string();
groups.entry(num).or_default().insert(field, value.clone());
}
}
for (num, fields) in groups {
@@ -366,10 +533,10 @@ impl 缠论配置 {
(&self_json, &other_json)
{
for (key, self_val) in self_map {
if let Some(other_val) = other_map.get(key) {
if self_val != other_val {
diffs.push(key.clone());
}
if let Some(other_val) = other_map.get(key)
&& self_val != other_val
{
diffs.push(key.clone());
}
}
}
+169
View File
@@ -0,0 +1,169 @@
/*
* MIT License
*
* Copyright (c) 2026 YuYuKunKun
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use crate::kline::bar::K线;
use serde::{Deserialize, Serialize};
/// 布林带(BOLL)— 基于移动平均和标准差的波动率通道
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct {
/// 数据时间戳
pub : i64,
/// 计算周期
pub : usize,
/// 标准差倍数(通常为 2.0
pub : f64,
/// 上轨(中轨 + 倍数 × 标准差)
pub : f64,
/// 中轨(移动平均线)
pub : f64,
/// 下轨(中轨 - 倍数 × 标准差)
pub : f64,
/// 内部历史队列(不序列化)
#[serde(skip)]
_历史队列: Vec<f64>,
/// 内部均值缓存(不序列化)
#[serde(skip)]
_均值: f64,
/// 内部方差和缓存(不序列化)
#[serde(skip)]
_方差和: f64,
}
impl Default for {
fn default() -> Self {
Self {
: 0,
: 20,
: 2.0,
: 0.0,
: 0.0,
: 0.0,
_历史队列: Vec::new(),
_均值: 0.0,
_方差和: 0.0,
}
}
}
impl {
/// 首次计算 BOLL 指标 — 从 K线 取值后计算
pub fn _K线(
k线: &K线, : &str, : usize, : f64
) -> Self {
let = crate::indicators::K线取值(k线., k线., k线., k线., );
Self::(k线., , , )
}
/// 增量计算 BOLL 指标 — 从 K线 取值后递推
pub fn _K线(prev: &, K线: &K线, : &str) -> Self {
let = crate::indicators::K线取值(
K线.,
K线.,
K线.,
K线.,
,
);
Self::(prev, K线., )
}
/// 首次计算 — 初始时上中下轨都等于当前价格
pub fn (: i64, : f64, : usize, : f64) -> Self {
Self {
,
,
,
: ,
: ,
: ,
_历史队列: vec![],
_均值: ,
_方差和: 0.0,
}
}
/// 增量计算 — 基于前一个布林带状态递推计算新的布林带
pub fn (prev: &, : i64, : f64) -> Self {
let = prev.;
let = prev.;
let mut q = prev._历史队列.clone();
q.push();
if q.len() > {
q.remove(0);
}
let (_均值, _方差和) = if q.len() < {
let mean = q.iter().sum::<f64>() / q.len() as f64;
let var_sum = q.iter().map(|v| (v - mean).powi(2)).sum();
(mean, var_sum)
} else {
let n = as f64;
let old_val = if prev._历史队列.len() >= {
prev._历史队列[0]
} else {
q[0]
};
let new_mean = prev._均值 + ( - old_val) / n;
let new_var =
prev._方差和 + ( - old_val) * ( - new_mean + old_val - prev._均值);
(new_mean, new_var)
};
let std = (_方差和 / q.len() as f64).sqrt();
Self {
,
,
,
: _均值,
: _均值 + * std,
: _均值 - * std,
_历史队列: q,
_均值,
_方差和,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_boll_first() {
let b = ::(1000, 100.0, 20, 2.0);
assert!((b. - 100.0).abs() < 0.01);
assert!((b. - 100.0).abs() < 0.01);
assert!((b. - 100.0).abs() < 0.01);
}
#[test]
fn test_boll_incremental() {
let b1 = ::(1000, 100.0, 5, 2.0);
let b2 = ::(&b1, 1001, 102.0);
assert!(b2. >= b2.);
assert!(b2. <= b2.);
}
}
+301
View File
@@ -0,0 +1,301 @@
/*
* MIT License
*
* Copyright (c) 2026 YuYuKunKun
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use super::container::{, };
use super::{, 线, , };
use crate::config::;
use crate::kline::bar::K线;
use std::sync::Arc;
/// 指标计算器 — 在缠K合并之前,增量计算所有开启的指标并挂载到K线上
pub struct ;
impl {
/// 增量计算所有开启的指标,将结果写入 当前K线.指标
///
/// `现有序列` 不包含当前K线;prev 取自 现有序列.last()
/// 通过 RwLock 内部可变性,以 `&K线` 共享引用写入指标值
pub fn (K线: &K线, : &[Arc<K线>], : &) {
let prev_guard = .last().map(|k| k..read().unwrap());
let prev = prev_guard.as_deref();
if . {
Self::_计算MACD组(K线, prev, );
Self::_计算RSI组(K线, prev, );
Self::_计算KDJ组(K线, prev, );
Self::_计算BOLL组(K线, prev, );
}
Self::_更新均线(K线, , );
}
fn _计算MACD组(K线: &K线, prev: Option<&>, : &) {
let = &.;
for (i, (key, , , )) in ._解析MACD参数列表().into_iter().enumerate()
{
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::MACD(prev_macd) = prev_val {
::MACD(线::(
prev_macd,
super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
),
K线.,
))
} else {
continue;
}
} else {
::MACD(线::(
super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
),
K线.,
,
,
,
))
};
K线..write().unwrap().(&key, val.clone());
if i == 0 {
K线..write().unwrap().("macd", val);
}
}
}
fn _计算RSI组(K线: &K线, prev: Option<&>, : &) {
let = &.;
for (i, (key, )) in ._解析RSI周期列表().into_iter().enumerate() {
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::RSI(prev_rsi) = prev_val {
::RSI(::(
prev_rsi,
super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
),
K线.,
))
} else {
continue;
}
} else {
::RSI(::(
super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
),
K线.,
,
._超买阈值,
._超卖阈值,
Some(._移动平均线周期),
))
};
K线..write().unwrap().(&key, val.clone());
if i == 0 {
K线..write().unwrap().("rsi", val);
}
}
}
fn _计算KDJ组(K线: &K线, prev: Option<&>, : &) {
for (i, (key, rsv, k平滑, d平滑)) in ._解析KDJ参数列表().into_iter().enumerate()
{
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::KDJ(prev_kdj) = prev_val {
::KDJ(::(
prev_kdj,
K线.,
K线.,
K线.,
K线.,
))
} else {
continue;
}
} else {
::KDJ(::(
K线.,
K线.,
K线.,
K线.,
rsv,
k平滑,
d平滑,
._超买阈值,
._超卖阈值,
))
};
K线..write().unwrap().(&key, val.clone());
if i == 0 {
K线..write().unwrap().("kdj", val);
}
}
}
fn _计算BOLL组(K线: &K线, prev: Option<&>, : &) {
let = &.;
for (i, (key, , )) in ._解析BOLL参数列表().into_iter().enumerate()
{
let val = if let Some(prev_val) = prev.and_then(|p| p.(&key)) {
if let ::BOLL(prev_boll) = prev_val {
::BOLL(::(
prev_boll,
K线.,
super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
),
))
} else {
continue;
}
} else {
::BOLL(::(
K线.,
super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
),
as usize,
,
))
};
K线..write().unwrap().(&key, val.clone());
if i == 0 {
K线..write().unwrap().("boll", val);
}
}
}
fn _更新均线(K线: &K线, : &[Arc<K线>], : &) {
if .线_类型列表.is_empty() || .线_周期列表.is_empty() {
return;
}
let = &.;
let = super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
);
for ma_type in &.线_类型列表 {
for period in &.线_周期列表 {
let key = format!("{}_{}", ma_type, period);
let = match ma_type.as_str() {
"SMA" => Self::_增量SMA(, , *period, , &key),
"EMA" => Self::_增量EMA(, , *period, , &key),
_ => continue,
};
if let Some(线_map) = K线..write().unwrap().线_mut() {
线_map.insert(key, );
}
}
}
}
/// 增量 SMA: 现有序列 (不含当前K线) + 当前价
fn _增量SMA(
: &[Arc<K线>],
: f64,
period: i64,
: &str,
prev_key: &str,
) -> f64 {
let existing_len = .len();
let p = period as usize;
// 现有序列 + 当前 = total_len
let total_len = existing_len + 1;
if total_len <= p {
let mut sum: f64 = [existing_len.saturating_sub(p.saturating_sub(1))..]
.iter()
.map(|k| super::K线取值(k., k., k., k., ))
.sum();
sum += ;
return sum / (total_len as f64).max(1.0);
}
// 尝试从前一根K线获取缓存的SMA
if let Some(prev) = .last().and_then(|k| {
let guard = k..read().unwrap();
guard.线().and_then(|m| m.get(prev_key)).copied()
}) {
let oldest = super::K线取值(
[existing_len - p].,
[existing_len - p].,
[existing_len - p].,
[existing_len - p].,
,
);
return prev + ( - oldest) / period as f64;
}
// 回退:完整计算
let mut sum: f64 = [existing_len.saturating_sub(p.saturating_sub(1))..]
.iter()
.map(|k| super::K线取值(k., k., k., k., ))
.sum();
sum += ;
sum / (total_len as f64).min(p as f64)
}
/// 增量 EMA: 现有序列 (不含当前K线) + 当前价
fn _增量EMA(
: &[Arc<K线>],
: f64,
period: i64,
_计算方式: &str,
prev_key: &str,
) -> f64 {
let = .last().and_then(|k| {
let guard = k..read().unwrap();
guard.线().and_then(|m| m.get(prev_key)).copied()
});
match {
None => ,
Some(prev) => {
let k = 2.0 / (period as f64 + 1.0);
* k + prev * (1.0 - k)
}
}
}
}
+202
View File
@@ -0,0 +1,202 @@
/*
* MIT License
*
* Copyright (c) 2026 YuYuKunKun
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
use super::{, 线, , };
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
/// 统一指标值 — 支持所有指标类型的动态注册
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum {
/// MACD 指标
MACD(线),
/// RSI 指标
RSI(),
/// KDJ 指标
KDJ(),
/// 布林带指标
BOLL(),
/// 均线组 (key → 值)
线(HashMap<String, f64>),
/// 单值指标组 (key → 值)
(HashMap<String, f64>),
}
/// 指标容器 — 挂载在每根 K线上,基于注册表模式持有该时刻所有指标快照
///
/// 与 Python `指标容器` 保持一致:
/// - 复杂指标:MACD/RSI/KDJ/BOLL,通过默认 key"macd"/"rsi"/"kdj"/"boll")访问
/// - 多参数变体:key 格式 "MACD_{快}_{慢}_{信号}" / "RSI_{周期}" 等
/// - 均线组:通过 `均线` 子映射访问,key 格式 "{类型}_{周期}"
/// - 单值指标:通过 `单值` 子映射访问,key 格式 "{名称}_{周期}"
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct {
pub _数据: HashMap<String, Option<>>,
}
impl {
/// 创建指标容器,预注册 macd/rsi/kdj/boll/均线/单值 默认槽位
pub fn new() -> Self {
let mut _数据 = HashMap::new();
_数据.insert("macd".into(), None);
_数据.insert("rsi".into(), None);
_数据.insert("kdj".into(), None);
_数据.insert("boll".into(), None);
_数据.insert("均线".into(), Some(::线(HashMap::new())));
_数据.insert("单值".into(), Some(::(HashMap::new())));
Self { _数据 }
}
/// 预注册指标(不覆盖已有值)
pub fn (&mut self, : &str, : Option<>) {
if !self._数据.contains_key() {
self._数据.insert(.to_string(), );
}
}
/// 按名称获取指标值
pub fn (&self, : &str) -> Option<&> {
self._数据.get().and_then(|v| v.as_ref())
}
/// 按名称设置指标值
pub fn (&mut self, : &str, : ) {
self._数据.insert(.to_string(), Some());
}
/// 检查是否包含指定名称的指标
pub fn (&self, : &str) -> bool {
self._数据.contains_key()
}
// ---- 默认指标便捷访问 ----
/// 获取默认 MACD 指标
pub fn macd(&self) -> Option<&线> {
match self._数据.get("macd")?.as_ref()? {
::MACD(m) => Some(m),
_ => None,
}
}
/// 克隆获取默认 MACD 指标
pub fn macd_cloned(&self) -> Option<线> {
self.macd().cloned()
}
/// 设置默认 MACD 指标
pub fn set_macd(&mut self, m: 线) {
self._数据.insert("macd".into(), Some(::MACD(m)));
}
/// 获取默认 RSI 指标
pub fn rsi(&self) -> Option<&> {
match self._数据.get("rsi")?.as_ref()? {
::RSI(r) => Some(r),
_ => None,
}
}
/// 克隆获取默认 RSI 指标
pub fn rsi_cloned(&self) -> Option<> {
self.rsi().cloned()
}
/// 设置默认 RSI 指标
pub fn set_rsi(&mut self, r: ) {
self._数据.insert("rsi".into(), Some(::RSI(r)));
}
/// 获取默认 KDJ 指标
pub fn kdj(&self) -> Option<&> {
match self._数据.get("kdj")?.as_ref()? {
::KDJ(k) => Some(k),
_ => None,
}
}
/// 克隆获取默认 KDJ 指标
pub fn kdj_cloned(&self) -> Option<> {
self.kdj().cloned()
}
/// 设置默认 KDJ 指标
pub fn set_kdj(&mut self, k: ) {
self._数据.insert("kdj".into(), Some(::KDJ(k)));
}
/// 获取默认布林带指标
pub fn boll(&self) -> Option<&> {
match self._数据.get("boll")?.as_ref()? {
::BOLL(b) => Some(b),
_ => None,
}
}
/// 克隆获取默认布林带指标
pub fn boll_cloned(&self) -> Option<> {
self.boll().cloned()
}
/// 设置默认布林带指标
pub fn set_boll(&mut self, b: ) {
self._数据.insert("boll".into(), Some(::BOLL(b)));
}
/// 获取均线组
pub fn 线(&self) -> Option<&HashMap<String, f64>> {
match self._数据.get("均线")?.as_ref()? {
::线(m) => Some(m),
_ => None,
}
}
/// 获取均线组可变引用
pub fn 线_mut(&mut self) -> Option<&mut HashMap<String, f64>> {
match self._数据.get_mut("均线")?.as_mut()? {
::线(m) => Some(m),
_ => None,
}
}
/// 获取单值指标组
pub fn (&self) -> Option<&HashMap<String, f64>> {
match self._数据.get("单值")?.as_ref()? {
::(s) => Some(s),
_ => None,
}
}
}
impl std::fmt::Display for {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let keys: Vec<&str> = self
._数据
.iter()
.filter(|(_, v)| v.is_some())
.map(|(k, _)| k.as_str())
.collect();
write!(f, "指标容器({})", keys.join(", "))
}
}
+54
View File
@@ -22,6 +22,7 @@
* SOFTWARE.
*/
use crate::kline::bar::K线;
use serde::{Deserialize, Serialize};
/// 随机指标 (KDJ)
@@ -30,23 +31,41 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct {
/// 数据时间戳
pub : i64,
/// 最高价
pub : f64,
/// 最低价
pub : f64,
/// 收盘价
pub : f64,
/// RSV 周期
pub N: i64,
/// K 值平滑周期
pub M1: i64,
/// D 值平滑周期
pub M2: i64,
/// 超买阈值
pub : f64,
/// 超卖阈值
pub : f64,
/// RSV 值(未成熟随机值)
pub RSV: Option<f64>,
/// K 值
pub K: Option<f64>,
/// D 值
pub D: Option<f64>,
/// J 值 (3K - 2D)
pub J: Option<f64>,
/// 历史最高价队列(滑动窗口)
pub : Vec<f64>,
/// 历史最低价队列(滑动窗口)
pub : Vec<f64>,
/// 前一个 RSV(用于平滑递推)
pub RSV: Option<f64>,
/// 前一个 K(用于平滑递推)
pub K: Option<f64>,
/// 前一个 D(用于平滑递推)
pub D: Option<f64>,
}
@@ -78,6 +97,8 @@ impl Default for 随机指标 {
impl {
/// 首次计算 KDJ(无历史数据时)
#[allow(clippy::too_many_arguments)]
/// 首次计算 KDJ — 无历史数据时的初始计算
#[allow(clippy::too_many_arguments)]
pub fn (
: f64,
: f64,
@@ -111,6 +132,39 @@ impl 随机指标 {
}
}
/// 首次计算 KDJ 指标 — 从 K线 取值后计算(KDJ 始终使用 高/低/收)
pub fn _K线(
k线: &K线,
RSV周期: i64,
K值平滑周期: i64,
D值平滑周期: i64,
: f64,
: f64,
) -> Self {
Self::(
k线.,
k线.,
k线.,
k线.,
RSV周期,
K值平滑周期,
D值平滑周期,
,
,
)
}
/// 增量计算 KDJ 指标 — 从 K线 取值后递推
pub fn _K线(KDJ: &Self, K线: &K线) -> Self {
Self::(
KDJ,
K线.,
K线.,
K线.,
K线.,
)
}
/// 基于前一个 KDJ 增量计算当前 KDJ
pub fn (
KDJ: &Self,
+36
View File
@@ -22,6 +22,7 @@
* SOFTWARE.
*/
use crate::kline::bar::K线;
use serde::{Deserialize, Serialize};
/// 平滑异同移动平均线 (MACD)
@@ -30,18 +31,29 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct 线 {
/// 数据时间戳
pub : i64,
/// 收盘价
pub : f64,
/// 快线 EMA 周期
pub 线: i64,
/// 慢线 EMA 周期
pub 线: i64,
/// 信号线周期
pub : i64,
/// DIF 值(快线 - 慢线)
pub DIF: Option<f64>,
/// DEA 值(DIF 的信号线)
pub DEA: Option<f64>,
/// MACD 柱(2 * (DIF - DEA)
#[serde(rename = "MACD柱")]
#[serde(default)]
pub MACD柱: f64,
/// 快线 EMA 值
pub 线EMA: Option<f64>,
/// 慢线 EMA 值
pub 线EMA: Option<f64>,
/// DEA EMA 值
pub DEA_EMA: Option<f64>,
}
@@ -97,6 +109,30 @@ impl 平滑异同移动平均线 {
}
}
/// 首次计算 MACD 指标 — 从 K线 取值后计算
pub fn _K线(
k线: &K线,
: &str,
线: i64,
线: i64,
: i64,
) -> Self {
let = super::K线取值(k线., k线., k线., k线., );
Self::(, k线., 线, 线, )
}
/// 增量计算 MACD 指标 — 从 K线 取值后递推
pub fn _K线(MACD: &Self, K线: &K线, : &str) -> Self {
let = super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
);
Self::(MACD, , K线.)
}
/// 基于前一个 MACD 指标增量计算当前 MACD
pub fn (MACD: &Self, : f64, : i64) -> Self {
// 快线 EMA
+6
View File
@@ -22,10 +22,16 @@
* SOFTWARE.
*/
pub mod boll;
pub mod calculator;
pub mod container;
pub mod kdj;
pub mod macd;
pub mod rsi;
pub use boll::;
pub use calculator::;
pub use container::{, };
pub use kdj::;
pub use macd::线;
pub use rsi::;
+41 -5
View File
@@ -22,6 +22,7 @@
* SOFTWARE.
*/
use crate::kline::bar::K线;
use serde::{Deserialize, Serialize};
/// 相对强弱指数 (RSI)
@@ -30,19 +31,33 @@ use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(default)]
pub struct {
/// 数据时间戳
pub : i64,
/// 收盘价
pub : f64,
/// RSI 计算周期
pub : i64,
/// 超买阈值
pub : f64,
/// 超卖阈值
pub : f64,
/// RSI SMA 平滑周期
pub RSI_SMA周期: Option<i64>,
/// RSI 值
pub RSI: Option<f64>,
/// 平均上涨幅度
pub : Option<f64>,
/// 平均下跌幅度
pub : Option<f64>,
/// 当前上涨幅度
pub : f64,
/// 当前下跌幅度
pub : f64,
/// 平滑系数 (1/周期)
pub : f64,
/// RSI SMA 值
pub RSI_SMA: Option<f64>,
/// RSI 历史队列(用于滚动计算)
pub RSI历史队列: Vec<f64>,
}
@@ -95,6 +110,31 @@ impl 相对强弱指数 {
}
}
/// 首次计算 RSI 指标 — 从 K线 取值后计算
pub fn _K线(
k线: &K线,
: &str,
: i64,
: f64,
: f64,
RSI_SMA周期: Option<i64>,
) -> Self {
let = super::K线取值(k线., k线., k线., k线., );
Self::(, k线., , , , RSI_SMA周期)
}
/// 增量计算 RSI 指标 — 从 K线 取值后递推
pub fn _K线(RSI: &Self, K线: &K线, : &str) -> Self {
let = super::K线取值(
K线.,
K线.,
K线.,
K线.,
,
);
Self::(RSI, , K线.)
}
/// 基于前一个 RSI 增量计算当前 RSI
pub fn (RSI: &Self, : f64, : i64) -> Self {
let = RSI.;
@@ -120,11 +160,7 @@ impl 相对强弱指数 {
// RSI
let RSI = if == 0.0 {
if > 0.0 {
100.0
} else {
50.0
}
if > 0.0 { 100.0 } else { 50.0 }
} else {
let RS = / ;
100.0 - (100.0 / (1.0 + RS))
+66 -14
View File
@@ -22,30 +22,68 @@
* SOFTWARE.
*/
use crate::indicators::{线, , };
use crate::indicators::;
use crate::types::;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::Write;
use std::sync::Arc;
use std::sync::{Arc, RwLock};
/// 原始K线 (OHLCV + 指标)
#[derive(Debug, Clone, Serialize, Deserialize)]
mod rwlock_container_serde {
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::sync::RwLock;
/// Serde 序列化辅助(RwLock<指标容器> → 序列化器)
pub fn serialize<S>(
val: &RwLock<crate::indicators::>,
ser: S,
) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
val.read().unwrap().serialize(ser)
}
/// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>
pub fn deserialize<'de, D>(de: D) -> Result<RwLock<crate::indicators::>, D::Error>
where
D: Deserializer<'de>,
{
Ok(RwLock::new(crate::indicators::::deserialize(
de,
)?))
}
}
/// 原始K线 (OHLCV + 指标容器)
///
/// 所有指标统一通过 `指标容器` 访问。指标容器使用 RwLock 实现内部可变性,
/// 使 `计算并挂载` 能以 `&K线` 共享引用写入指标值。
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct K线 {
/// 品种标识(如 "btcusd"
pub : String,
/// K线序号(在序列中的位置)
pub : i64,
/// 周期(秒),如 300=5分钟, 86400=日线
pub : i64,
/// Unix 时间戳(秒)
pub : i64,
/// 最高价
pub : f64,
/// 最低价
pub : f64,
/// 开盘价
pub : f64,
/// 收盘价
pub : f64,
/// 成交量
pub : f64,
pub macd: Option<线>,
pub rsi: Option<>,
pub kdj: Option<>,
/// 指标容器(MACD/RSI/KDJ/BOLL/均线等)
#[serde(with = "rwlock_container_serde")]
pub : RwLock<>,
}
impl Default for K线 {
@@ -60,9 +98,24 @@ impl Default for K线 {
: 0.0,
: 0.0,
: 0.0,
macd: None,
rsi: None,
kdj: None,
: RwLock::new(::new()),
}
}
}
impl Clone for K线 {
fn clone(&self) -> Self {
Self {
: self..clone(),
: self.,
: self.,
: self.,
: self.,
: self.,
: self.,
: self.,
: self.,
: RwLock::new(self..read().unwrap().clone()),
}
}
}
@@ -79,6 +132,7 @@ impl K线 {
/// 序列化为大端字节序 48 字节
/// 格式: >6d (时间戳, 开盘价, 高, 低, 收盘价, 成交量)
/// TODO: 对齐 Python round(x, 8) 再序列化
pub fn to_bytes(&self) -> [u8; 48] {
let mut buf = [0u8; 48];
{
@@ -163,9 +217,7 @@ impl K线 {
,
,
,
macd: None,
rsi: None,
kdj: None,
: RwLock::new(::new()),
}
}
@@ -193,7 +245,7 @@ impl K线 {
let mut = 0.0f64;
let mut = 0.0f64;
for k in {
if let Some(ref macd) = k.macd {
if let Some(macd) = k..read().unwrap().macd() {
let hist = macd.MACD柱;
if hist >= 0.0 {
+= hist;
+46 -138
View File
@@ -23,36 +23,48 @@
*/
use crate::config::;
use crate::indicators::{
线, , , K线取值
};
use crate::indicators::;
use crate::kline::bar::K线;
use crate::structure::fractal_obj::;
use crate::types::SyncF64;
use crate::types::;
use crate::types::;
use crate::types::SyncF64;
use std::collections::HashSet;
use std::sync::atomic::{AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
/// 缠论K线 — 经包含处理过后的K线
///
/// 部分字段使用 Cell/RefCell 实现内部可变性,确保包含处理原地修改时
/// Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。
/// 部分字段使用 AtomicI64 / SyncF64 / RwLock 实现内部可变性,确保包含处理
/// 原地修改时 Rc 指针不变,所有持有该 Rc 的引用(如分型.右)能看到最新数据。
#[derive(Debug)]
pub struct K线 {
/// 缠K序号(在缠论K线序列中的位置)
pub : AtomicI64,
/// Unix 时间戳(秒)
pub : AtomicI64,
/// 缠K最高价(经包含处理后可能高于原始K线)
pub : SyncF64,
/// 缠K最低价(经包含处理后可能低于原始K线)
pub : SyncF64,
/// 缠K方向(向上/向下)
pub : RwLock<>,
/// 分型结构(顶/底/上/下/散)
pub : RwLock<Option<>>,
/// 周期(秒)
pub : i64,
/// 品种标识
pub : String,
/// 分型特征值(历史高低点极值,用于背驰判断)
pub : SyncF64,
/// 原始K线起始序号(包含处理前)
pub : i64,
/// 原始K线结束序号(包含处理后更新)
pub : AtomicI64,
/// 标的原始K线(该缠K对应的普K)
pub K线: RwLock<Arc<K线>>,
pub : RwLock<Vec<String>>,
/// 买卖点信息集合
pub : RwLock<HashSet<String>>,
}
impl Clone for K线 {
@@ -66,7 +78,7 @@ impl Clone for 缠论K线 {
: RwLock::new(*self..read().unwrap()),
: self.,
: self..clone(),
: SyncF64::new(self..get()),
: SyncF64::new(self..get()),
: self.,
: AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())),
@@ -108,7 +120,7 @@ impl 缠论K线 {
: RwLock::new(*self..read().unwrap()),
: self.,
: self..clone(),
: SyncF64::new(self..get()),
: SyncF64::new(self..get()),
: self.,
: AtomicI64::new(self..load(Ordering::Relaxed)),
K线: RwLock::new(Arc::clone(&self.K线.read().unwrap())),
@@ -118,16 +130,18 @@ impl 缠论K线 {
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
pub fn MACD柱子匹配(&self) -> bool {
let = self.K线.read().unwrap();
let = ..read().unwrap();
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref macd) = self.K线.read().unwrap().macd {
if let Some(macd) = .macd() {
macd.MACD柱 < 0.0
} else {
false
}
}
Some(::) | Some(::) => {
if let Some(ref macd) = self.K线.read().unwrap().macd {
if let Some(macd) = .macd() {
macd.MACD柱 > 0.0
} else {
false
@@ -139,9 +153,11 @@ impl 缠论K线 {
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
pub fn RSI匹配(&self) -> bool {
let = self.K线.read().unwrap();
let = ..read().unwrap();
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref rsi) = self.K线.read().unwrap().rsi {
if let Some(rsi) = .rsi() {
match (rsi.RSI, rsi.RSI_SMA) {
(Some(r), Some(sma)) => r < sma,
_ => false,
@@ -151,7 +167,7 @@ impl 缠论K线 {
}
}
Some(::) | Some(::) => {
if let Some(ref rsi) = self.K线.read().unwrap().rsi {
if let Some(rsi) = .rsi() {
match (rsi.RSI, rsi.RSI_SMA) {
(Some(r), Some(sma)) => r > sma,
_ => false,
@@ -166,9 +182,11 @@ impl 缠论K线 {
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
pub fn KDJ匹配(&self) -> bool {
let = self.K线.read().unwrap();
let = ..read().unwrap();
match *self..read().unwrap() {
Some(::) | Some(::) => {
if let Some(ref kdj) = self.K线.read().unwrap().kdj {
if let Some(kdj) = .kdj() {
match (kdj.K, kdj.D) {
(Some(k), Some(d)) => k < d,
_ => false,
@@ -178,7 +196,7 @@ impl 缠论K线 {
}
}
Some(::) | Some(::) => {
if let Some(ref kdj) = self.K线.read().unwrap().kdj {
if let Some(kdj) = .kdj() {
match (kdj.K, kdj.D) {
(Some(k), Some(d)) => k > d,
_ => false,
@@ -248,7 +266,7 @@ impl 缠论K线 {
: ,
: AtomicI64::new(),
K线: RwLock::new(k),
: RwLock::new(Vec::new()),
: RwLock::new(HashSet::new()),
};
if let Some() = {
@@ -270,7 +288,7 @@ impl 缠论K线 {
/// 兼并(合并)处理 — 缠论包含处理的核心算法
///
/// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None
pub fn (
pub fn _兼并(
K: Option<&K线>,
K: &K线,
K: &Arc<K线>,
@@ -303,7 +321,7 @@ impl 缠论K线 {
// 重复提交检测 — 当序号相同时认为是重复提交K线
if K. == K..load(Ordering::Relaxed) {
return (None, None);
// no-op: 对齐 Python ... (Ellipsis)
}
// 序号连续性检查
@@ -370,91 +388,14 @@ impl 缠论K线 {
K线. = ..clone();
// ---- 阶段1: 普K序列管理 + 指标增量计算 ----
// 对齐 Python: 先推入序列,再计算指标
if K序列.is_empty() {
if . {
K线.macd = Some(线::(
K线取值(
K线.,
K线.,
K线.,
K线.,
&.,
),
K线.,
.线_快线周期,
.线_慢线周期,
.线_信号周期,
));
K线.rsi = Some(::(
K线取值(
K线.,
K线.,
K线.,
K线.,
&.,
),
K线.,
._周期,
._超买阈值,
._超卖阈值,
Some(._移动平均线周期),
));
K线.kdj = Some(::(
K线.,
K线.,
K线.,
K线.,
._RSV周期,
._K值平滑周期,
._D值平滑周期,
._超买阈值,
._超卖阈值,
));
}
let K线_rc = Arc::new(K线);
K序列.push(K线_rc);
K序列.push(Arc::new(K线));
} else {
let K = K序列.last().unwrap();
if K. == K线. {
// 同时间戳更新
// 同时间戳更新 — 替换 [-1]
K线. = K.;
if . && K序列.len() >= 2 {
if let Some(ref prev_macd) = K序列[K序列.len() - 2].macd {
K线.macd = Some(线::(
prev_macd,
K线取值(
K线.,
K线.,
K线.,
K线.,
&.,
),
K线.,
));
}
if let Some(ref prev_rsi) = K序列[K序列.len() - 2].rsi {
K线.rsi = Some(::(
prev_rsi,
K线取值(
K线.,
K线.,
K线.,
K线.,
&.,
),
K线.,
));
}
if let Some(ref prev_kdj) = K序列[K序列.len() - 2].kdj {
K线.kdj = Some(::(
prev_kdj,
K线.,
K线.,
K线.,
K线.,
));
}
}
K序列.pop();
K序列.push(Arc::new(K线));
} else {
@@ -462,46 +403,14 @@ impl 缠论K线 {
panic!("时序错误: 之前={}, 当前={}", K., K线.);
}
K线. = K. + 1;
if . {
if let Some(ref prev_macd) = K.macd {
K线.macd = Some(线::(
prev_macd,
K线取值(
K线.,
K线.,
K线.,
K线.,
&.,
),
K线.,
));
}
if let Some(ref prev_rsi) = K.rsi {
K线.rsi = Some(::(
prev_rsi,
K线取值(
K线.,
K线.,
K线.,
K线.,
&.,
),
K线.,
));
}
if let Some(ref prev_kdj) = K.kdj {
K线.kdj = Some(::(
prev_kdj,
K线.,
K线.,
K线.,
K线.,
));
}
}
K序列.push(Arc::new(K线));
}
}
// 计算指标: 对齐 Python,仅当 计算指标 开启时执行
if . {
let n = K序列.len();
::(&K序列[n - 1], &K序列[..n - 1], );
}
// ---- 阶段2: 缠K合并 ----
let : String;
@@ -512,7 +421,7 @@ impl 缠论K线 {
let (, ) = K序列.split_at_mut(len - 1);
let K: Option<&K线> = .last().map(Arc::as_ref);
let K = &*[0];
let (K, ) = Self::(K, K, K线_ref, );
let (K, ) = Self::_兼并(K, K, K线_ref, );
if let Some(k) = K {
match .as_deref() {
@@ -521,8 +430,7 @@ impl 缠论K线 {
= "创建".into();
}
Some("替换") => {
// Cell::set 已原地更新数据,无需 pop+push 打破 Rc 身份
= "兼并".into();
= "替换".into();
}
_ => {
= "兼并".into();
+1 -1
View File
@@ -174,5 +174,5 @@ fn 测试_周期合成(文件路径: &str) {
}
println!("\n===== 保存分析数据 =====\n");
._保存数据();
._保存数据(None);
}
+270 -78
View File
@@ -29,43 +29,97 @@ use crate::kline::chan_kline::缠论K线;
use crate::structure::fractal_obj::;
use crate::structure::segment_feat::线;
use crate::types::{, , };
use cached::stores::LruCache;
use std::sync::atomic::{AtomicBool, AtomicI64, Ordering};
use std::sync::{Arc, RwLock};
use std::sync::{Arc, LazyLock, Mutex, RwLock};
use tracing::warn;
/// 虚线 — 笔和线段的通用数据结构
/// 虚线 — 笔和线段的通用数据结构
///
/// 笔和线段共享此 struct,通过 `标识` 字段区分 ("笔"/"线段"/"扩展线段"等)
/// 可变字段使用 Cell/RefCell 实现内部可变性,确保 Rc 指针身份一致
/// 笔和线段共享此 struct,通过 `标识` 字段区分"笔"/"线段"/"扩展线段"等)。
/// 文=起点分型,武=终点分型,方向由文→武的结构关系判定(顶→底=向下,底→顶=向上)。
///
/// 字段:
/// 标识: "笔" / "线段" / "扩展线段" / "线段<线段>" 等
/// 序号: 在同级序列中的编号
/// 级别: 层级深度(笔=1, 线段=2, 线段<线段>=3 ...
/// 文: 起点分型(不可变,确保指针身份一致)
/// 武: 终点分型(可变,用于动态更新终点)
/// 有效性: 该虚线是否有效
/// 基础序列: 构成该虚线的子级虚线序列(笔→空;线段→笔序列;线段<线段>→线段序列 ...
/// 特征序列: 线段特征序列(笔为空;线段为[None, None, None]初始化)
/// 实_中枢序列: 实中枢列表(根据配置识别的中枢)
/// 虚_中枢序列: 虚中枢列表(忽略配置约束的中枢)
/// 合_中枢序列: 合并中枢列表(实+虚+扩展)
/// 确认K线: 线段被破坏时的确认K线
/// 模式: 买卖点匹配模式("文武"/"全量"/"任意"/"配置"/"相对"
/// _特征序列_显示: 是否显示特征序列(图表渲染用)
/// 前一缺口: 特征序列第一二元素间的缺口
/// 前一结束位置: 前一虚线段结束时所在的位置
/// 短路修正: 是否启用短路修正(线段短路过时的紧急处理标记)
#[derive(Debug)]
pub struct 线 {
/// 标识字符串: "笔" / "线段" / "扩展线段" / "线段<线段>" 等
pub : RwLock<String>,
/// 同级序列中的编号
pub : AtomicI64,
/// 层级深度(笔=1, 线段=2)
pub : AtomicI64,
/// 起点分型(不可变)
pub : Arc<>,
/// 终点分型(可变)
pub : RwLock<Arc<>>,
/// 是否有效
pub : AtomicBool,
/// 构成该虚线的子级虚线序列
pub : RwLock<Vec<Arc<线>>>,
/// 线段特征序列
pub : RwLock<Vec<Option<Arc<线>>>>,
/// 实中枢列表
pub _中枢序列: RwLock<Vec<Arc<>>>,
/// 虚中枢列表
pub _中枢序列: RwLock<Vec<Arc<>>>,
/// 合并中枢列表(实+虚+扩展)
pub _中枢序列: RwLock<Vec<Arc<>>>,
/// 线段被破坏时的确认K线
pub K线: RwLock<Option<Arc<K线>>>,
/// 买卖点匹配模式
pub : RwLock<String>,
/// 是否显示特征序列(图表渲染)
pub _特征序列_显示: AtomicBool,
/// 特征序列第一二元素间的缺口
pub : RwLock<Option<>>,
/// 前一虚线段结束位置
pub : RwLock<Option<Arc<线>>>,
/// 短路紧急修正标记
pub : AtomicBool,
}
/// MACD行为统计 — 统计MACD行为 方法的返回类型
/// MACD行为统计 — `统计MACD行为` 方法的返回类型,汇总MACD穿轴和交叉信息。
///
/// 字段:
/// DIF上穿0: DIF从负穿正的次数
/// DIF下穿0: DIF从正穿负的次数
/// DEA上穿0: DEA从负穿正的次数
/// DEA下穿0: DEA从正穿负的次数
/// 金叉次数: DIF上穿DEA的次数
/// 死叉次数: DIF下穿DEA的次数
/// 密集交叉区域: (起始索引, 结束索引, 交叉次数) 列表
#[derive(Debug, Clone)]
pub struct MACD行为统计 {
/// DIF从负穿正的次数
pub DIF上穿0: i64,
/// DIF从正穿负的次数
pub DIF下穿0: i64,
/// DEA从负穿正的次数
pub DEA上穿0: i64,
/// DEA从正穿负的次数
pub DEA下穿0: i64,
/// DIF上穿DEA的次数
pub : i64,
/// DIF下穿DEA的次数
pub : i64,
/// 密集交叉区域 (起始索引, 结束索引, 交叉次数)
pub : Vec<(usize, usize, usize)>,
}
@@ -93,6 +147,12 @@ impl Clone for 虚线 {
}
}
type = LruCache<(usize, usize), (bool, String)>;
/// 买卖意义 LRU 缓存(max 128 条目,对齐 Python @lru_cache(maxsize=128)
static : LazyLock<Mutex<>> =
LazyLock::new(|| Mutex::new(LruCache::with_size(128)));
impl 线 {
pub fn new(
: i64,
@@ -110,7 +170,7 @@ impl 虚线 {
: RwLock::new(),
: AtomicBool::new(),
: RwLock::new(Vec::new()),
: RwLock::new(Vec::new()),
: RwLock::new(vec![None, None, None]),
_中枢序列: RwLock::new(Vec::new()),
_中枢序列: RwLock::new(Vec::new()),
_中枢序列: RwLock::new(Vec::new()),
@@ -123,6 +183,7 @@ impl 虚线 {
}
}
/// 图表标题 — 格式为 "符号:周期:标识:序号"
pub fn (&self) -> String {
format!(
"{}:{}:{}:{}",
@@ -133,14 +194,18 @@ impl 虚线 {
)
}
/// 方向 — 文到武的方向
/// 方向 — 文到武的方向(对齐 Python:无法识别时 panic
pub fn (&self) -> {
match (self.., self..read().unwrap().) {
(::, ::) => ::,
(::, ::) => ::,
(::, ::) => ::,
(::, ::) => ::,
_ => ::,
(::, ::) => ::,
(::, ::) => ::,
_ => panic!(
"虚线 方向 无法识别: 文.结构={:?}, 武.结构={:?}",
self..,
self..read().unwrap().
),
}
}
@@ -179,6 +244,7 @@ impl 虚线 {
}
/// 获取该虚线范围内的普K序列
/// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价
pub fn K序列(&self, K序列: &[Arc<K线>]) -> Vec<Arc<K线>> {
// 使用指针查找(与 Python list.index 身份匹配行为一致),
// 而非序号切片——因为序号可能与实际位置不一致。
@@ -192,7 +258,7 @@ impl 虚线 {
(Some(s), Some(e)) if s <= e => K序列[s..=e].to_vec(),
_ => {
// 指针查找失败时回退到序号方式
println!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>");
warn!("[警告]虚线.获取普K序列 <指针查找失败时回退到序号方式>");
let = self... as usize;
let = self
.
@@ -211,11 +277,13 @@ impl 虚线 {
}
/// 获取该虚线范围内的缠K序列
/// 对齐说明:Python 接收 观察者 对象,Rust 直接接收切片引用,行为等价
pub fn K序列(&self, K序列: &[Arc<K线>]) -> Vec<Arc<K线>> {
K线::(K序列, &self.., &self..read().unwrap().).unwrap_or_default()
}
/// 获取_武 — 递归获取虚线的终点分型(笔直接返回武,线段递归到底层笔的武)
/// 对齐说明:Python 是 @classmethodcls, 实线),Rust 是实例方法(&self),逻辑等价
pub fn _武(&self) -> Arc<> {
if *self..read().unwrap() == "" {
return self..read().unwrap().clone();
@@ -254,13 +322,7 @@ impl 虚线 {
// 非笔:线段/扩展线段等,完整输出
let (, , , 穿) = crate::algorithm::segment::线::(self, None);
let (_a, _b, _c) = crate::algorithm::segment::线::(self);
let _bool = |b: bool| -> &str {
if b {
"True"
} else {
"False"
}
};
let _bool = |b: bool| -> &str { if b { "True" } else { "False" } };
let _str = match &*self..read().unwrap() {
Some(g) => format!("{}", g),
@@ -334,7 +396,11 @@ impl 虚线 {
format_f64_g(self..),
self..read().unwrap().(),
format_f64_g(self..read().unwrap().),
if self..load(Ordering::Relaxed) { "True" } else { "False" },
if self..load(Ordering::Relaxed) {
"True"
} else {
"False"
},
self..read().unwrap().len(),
_bool(_a),
_bool(_b),
@@ -342,14 +408,21 @@ impl 虚线 {
_str,
_str,
_str,
match &穿 { Some(d) => format!("{}", d), None => "None".to_string() },
match &穿 {
Some(d) => format!("{}", d),
None => "None".to_string(),
},
_str,
_str,
_str,
self..read().unwrap(),
_str,
_str,
if self..load(Ordering::Relaxed) { "True" } else { "False" },
if self..load(Ordering::Relaxed) {
"True"
} else {
"False"
},
)
}
@@ -362,6 +435,13 @@ impl 虚线 {
/// 创建线段
pub fn 线(线: &[Arc<线>]) -> Self {
let = 线.len();
if < 3 {
panic!("创建线段 虚线序列 数量 {} < 3", );
}
if .is_multiple_of(2) {
panic!("创建线段 虚线序列 数量 {} 不是单数!", );
}
let = Arc::clone(&线[0].);
let = Arc::clone(&*线[线.len() - 1]..read().unwrap());
assert!(
@@ -377,6 +457,10 @@ impl 虚线 {
};
let = 线[0]..load(Ordering::Relaxed) + 1;
let = Self::new(0, , , , , true);
*..write().unwrap() = vec![None, None, None];
*._中枢序列.write().unwrap() = Vec::new();
*._中枢序列.write().unwrap() = Vec::new();
*._中枢序列.write().unwrap() = Vec::new();
*..write().unwrap() = 线.to_vec();
*..write().unwrap() = "文武".into();
@@ -443,13 +527,14 @@ impl 虚线 {
}
let : f64 = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
.filter_map(|k| k..read().unwrap().macd_cloned())
.map(|m| m.MACD柱.abs())
.sum();
/ K线序列.len() as f64
}
/// 计算MACD柱子均值_阴 — 负柱的绝对值均值
/// 对齐说明:Python 无数据时返回 Falsebool),Rust 返回 None,调用方处理等价
pub fn MACD柱子均值_阴(K序列: &[Arc<K线>], 线: &线) -> Option<f64> {
let K线序列 = K线::rc(
K序列,
@@ -458,7 +543,7 @@ impl 虚线 {
);
let : Vec<f64> = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
.filter_map(|k| k..read().unwrap().macd_cloned())
.filter(|m| m.MACD柱 < 0.0)
.map(|m| m.MACD柱.abs())
.collect();
@@ -470,6 +555,7 @@ impl 虚线 {
}
/// 计算MACD柱子均值_阳 — 正柱的绝对值均值
/// 对齐说明:Python 无数据时返回 Falsebool),Rust 返回 None,调用方处理等价
pub fn MACD柱子均值_阳(K序列: &[Arc<K线>], 线: &线) -> Option<f64> {
let K线序列 = K线::rc(
K序列,
@@ -478,7 +564,7 @@ impl 虚线 {
);
let : Vec<f64> = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
.filter_map(|k| k..read().unwrap().macd_cloned())
.filter(|m| m.MACD柱 > 0.0)
.map(|m| m.MACD柱.abs())
.collect();
@@ -495,7 +581,7 @@ impl 虚线 {
pub fn MACD均值(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
let _MACD = match ..read().unwrap().macd() {
Some(m) => m.MACD柱.abs(),
None => return false,
};
@@ -515,7 +601,7 @@ impl 虚线 {
pub fn MACD均值_阴(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
let _MACD = match ..read().unwrap().macd() {
Some(m) => m.MACD柱.abs(),
None => return false,
};
@@ -529,7 +615,7 @@ impl 虚线 {
pub fn MACD均值_阳(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
let _MACD = match ..read().unwrap().macd() {
Some(m) => m.MACD柱.abs(),
None => return false,
};
@@ -543,7 +629,7 @@ impl 虚线 {
pub fn MACD极值(K序列: &[Arc<K线>], 线: &线) -> bool {
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
let _MACD = match .macd.as_ref() {
let _MACD = match ..read().unwrap().macd() {
Some(m) => m.MACD柱,
None => return false,
};
@@ -554,7 +640,7 @@ impl 虚线 {
);
let : Vec<f64> = K线序列
.iter()
.filter_map(|k| k.macd.as_ref())
.filter_map(|k| k..read().unwrap().macd_cloned())
.map(|m| m.MACD柱)
.collect();
if .is_empty() {
@@ -583,7 +669,13 @@ impl 虚线 {
if == :: {
let : Vec<&Arc<K线>> = K序列
.iter()
.filter(|k| k.macd.as_ref().is_some_and(|m| m.MACD柱 > 0.0))
.filter(|k| {
k.
.read()
.unwrap()
.macd()
.is_some_and(|m| m.MACD柱 > 0.0)
})
.collect();
if .is_empty() {
return [false, false, false];
@@ -595,32 +687,51 @@ impl 虚线 {
let =
.iter()
.max_by(|a, b| {
a.macd
.as_ref()
a.
.read()
.unwrap()
.macd()
.unwrap()
.MACD柱
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱)
.partial_cmp(&b..read().unwrap().macd().unwrap().MACD柱)
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
let mut = [Arc::clone(*), Arc::clone()];
.sort_by_key(|k| k.);
if let (Some(m0), Some(m1)) = ([0].macd.as_ref(), [1].macd.as_ref()) {
if m0.MACD柱 > m1.MACD柱 && [0]. < [1]. {
[0] = true;
}
let m0_g = [0]..read().unwrap();
let m1_g = [1]..read().unwrap();
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd())
&& m0.MACD柱 > m1.MACD柱
&& [0]. < [1].
{
[0] = true;
}
// DIF背驰 (no sort — Python compares peak vs last directly)
let =
.iter()
.max_by(|a, b| {
let da = a.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0);
let db = b.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0);
let da = a
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DIF)
.unwrap_or(0.0);
let db = b
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DIF)
.unwrap_or(0.0);
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
if let (Some(m0), Some(m1)) = (.macd.as_ref(), .macd.as_ref()) {
let m0_g = ..read().unwrap();
let m1_g = ..read().unwrap();
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
let dif0 = m0.DIF.unwrap_or(0.0);
let dif1 = m1.DIF.unwrap_or(0.0);
if dif0 > dif1 && . < . {
@@ -632,12 +743,26 @@ impl 虚线 {
let 线 =
.iter()
.max_by(|a, b| {
let da = a.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0);
let db = b.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0);
let da = a
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DEA)
.unwrap_or(0.0);
let db = b
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DEA)
.unwrap_or(0.0);
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
if let (Some(m0), Some(m1)) = (线.macd.as_ref(), .macd.as_ref()) {
let m0_g = 线..read().unwrap();
let m1_g = ..read().unwrap();
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
let dea0 = m0.DEA.unwrap_or(0.0);
let dea1 = m1.DEA.unwrap_or(0.0);
if dea0 > dea1 && 线. < . {
@@ -649,7 +774,13 @@ impl 虚线 {
} else {
let : Vec<&Arc<K线>> = K序列
.iter()
.filter(|k| k.macd.as_ref().is_some_and(|m| m.MACD柱 < 0.0))
.filter(|k| {
k.
.read()
.unwrap()
.macd()
.is_some_and(|m| m.MACD柱 < 0.0)
})
.collect();
if .is_empty() {
return [false, false, false];
@@ -661,33 +792,54 @@ impl 虚线 {
let =
.iter()
.max_by(|a, b| {
a.macd
.as_ref()
a.
.read()
.unwrap()
.macd()
.unwrap()
.MACD柱
.abs()
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱.abs())
.partial_cmp(&b..read().unwrap().macd().unwrap().MACD柱.abs())
.unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
let mut = [Arc::clone(*), Arc::clone()];
.sort_by_key(|k| k.);
if let (Some(m0), Some(m1)) = ([0].macd.as_ref(), [1].macd.as_ref()) {
if m0.MACD柱 < m1.MACD柱 && [0]. > [1]. {
[0] = true;
}
let m0_g = [0]..read().unwrap();
let m1_g = [1]..read().unwrap();
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd())
&& m0.MACD柱 < m1.MACD柱
&& [0]. > [1].
{
[0] = true;
}
// DIF背驰 (no sort — Python compares peak vs last directly)
let =
.iter()
.max_by(|a, b| {
let da = a.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0).abs();
let db = b.macd.as_ref().and_then(|m| m.DIF).unwrap_or(0.0).abs();
let da = a
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DIF)
.unwrap_or(0.0)
.abs();
let db = b
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DIF)
.unwrap_or(0.0)
.abs();
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
if let (Some(m0), Some(m1)) = (.macd.as_ref(), .macd.as_ref()) {
let m0_g = ..read().unwrap();
let m1_g = ..read().unwrap();
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
let dif0 = m0.DIF.unwrap_or(0.0);
let dif1 = m1.DIF.unwrap_or(0.0);
if dif0 < dif1 && . > . {
@@ -699,12 +851,28 @@ impl 虚线 {
let 线 =
.iter()
.max_by(|a, b| {
let da = a.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0).abs();
let db = b.macd.as_ref().and_then(|m| m.DEA).unwrap_or(0.0).abs();
let da = a
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DEA)
.unwrap_or(0.0)
.abs();
let db = b
.
.read()
.unwrap()
.macd()
.and_then(|m| m.DEA)
.unwrap_or(0.0)
.abs();
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
})
.unwrap();
if let (Some(m0), Some(m1)) = (线.macd.as_ref(), .macd.as_ref()) {
let m0_g = 线..read().unwrap();
let m1_g = ..read().unwrap();
if let (Some(m0), Some(m1)) = (m0_g.macd(), m1_g.macd()) {
let dea0 = m0.DEA.unwrap_or(0.0);
let dea1 = m1.DEA.unwrap_or(0.0);
if dea0 < dea1 && 线. > . {
@@ -719,20 +887,15 @@ impl 虚线 {
// ---- MACD柱子分段 ----
/// 计算MACD柱子分段 — 按正负号将MACD柱子分段
/// 对齐说明:Python 丢弃末尾分段的末段残留(疑似 bug),Rust 通过 if !当前段.is_empty() 正确追加
pub fn MACD柱子分段(k线序列: &[Arc<K线>]) -> Vec<Vec<f64>> {
if k线序列.is_empty() {
return Vec::new();
}
let = |x: f64| -> &str {
if x > 0.0 {
""
} else {
""
}
};
let = |x: f64| -> &str { if x > 0.0 { "" } else { "" } };
let _MACD = match &k线序列[0].macd {
let _MACD = match k线序列[0]..read().unwrap().macd() {
Some(m) => m.MACD柱,
None => return Vec::new(),
};
@@ -741,7 +904,7 @@ impl 虚线 {
let mut = Vec::new();
for k线 in &k线序列[1..] {
let macd = match &k线.macd {
let macd = match k线..read().unwrap().macd() {
Some(m) => m.MACD柱,
None => continue,
};
@@ -812,13 +975,15 @@ impl 虚线 {
let mut dea_down = 0;
for i in 1..K序列.len() {
let pre = &K序列[i - 1].macd;
let cur = &K序列[i].macd;
let pre_guard = K序列[i - 1]..read().unwrap();
let cur_guard = K序列[i]..read().unwrap();
let pre = pre_guard.macd();
let cur = cur_guard.macd();
if pre.is_none() || cur.is_none() {
continue;
}
let (pre_dif, cur_dif) = (pre.as_ref().unwrap().DIF, cur.as_ref().unwrap().DIF);
let (pre_dea, cur_dea) = (pre.as_ref().unwrap().DEA, cur.as_ref().unwrap().DEA);
let (pre_dif, cur_dif) = (pre.unwrap().DIF, cur.unwrap().DIF);
let (pre_dea, cur_dea) = (pre.unwrap().DEA, cur.unwrap().DEA);
if let (Some(pd), Some(cd)) = (pre_dif, cur_dif) {
if pd < 0.0 && cd >= 0.0 {
@@ -843,16 +1008,18 @@ impl 虚线 {
let mut = vec![0i32];
for i in 1..K序列.len() {
let pre = &K序列[i - 1].macd;
let cur = &K序列[i].macd;
let pre_guard = K序列[i - 1]..read().unwrap();
let cur_guard = K序列[i]..read().unwrap();
let pre = pre_guard.macd();
let cur = cur_guard.macd();
if pre.is_none() || cur.is_none() {
.push(0);
continue;
}
let pre_dif = pre.as_ref().unwrap().DIF;
let pre_dea = pre.as_ref().unwrap().DEA;
let cur_dif = cur.as_ref().unwrap().DIF;
let cur_dea = cur.as_ref().unwrap().DEA;
let pre_dif = pre.unwrap().DIF;
let pre_dea = pre.unwrap().DEA;
let cur_dif = cur.unwrap().DIF;
let cur_dea = cur.unwrap().DEA;
if let (Some(pd), Some(cd), Some(pe), Some(ce)) = (pre_dif, cur_dif, pre_dea, cur_dea) {
if pd <= pe && cd > ce {
@@ -889,13 +1056,38 @@ impl 虚线 {
/// 返回 (是否有意义, 原因字符串)
pub fn (
线: &线, : &crate::business::observer::
) -> (bool, String) {
// LRU 缓存查找
let key = (
std::ptr::from_ref(线) as usize,
std::ptr::from_ref() as usize,
);
{
let mut cache = .lock().unwrap();
if let Some(val) = cached::Cached::cache_get(&mut *cache, &key) {
return val.clone();
}
}
let result = Self::_买卖意义_计算(线, );
{
let mut cache = .lock().unwrap();
cached::Cached::cache_set(&mut *cache, key, result.clone());
}
result
}
/// 买卖意义 实际计算(无缓存)
fn _买卖意义_计算(
线: &线,
: &crate::business::observer::,
) -> (bool, String) {
let K序列 = &.K线序列;
let = &.;
if *线..read().unwrap() != ""
&& *线..read().unwrap() != "线段"
&& !线..read().unwrap().starts_with("线段<")
&& *线..read().unwrap() != "线段<线段>"
{
return (false, "标识不在范围内".into());
}
@@ -903,7 +1095,7 @@ impl 虚线 {
// KDJ指标完整性检查
let _ref = 线..read().unwrap();
let = _ref..K线.read().unwrap();
match .kdj.as_ref() {
match ..read().unwrap().kdj() {
Some(kdj) if kdj.K.is_some() && kdj.D.is_some() && kdj.J.is_some() => {}
_ => return (false, "KDJ指标不完整".into()),
}
+11 -1
View File
@@ -26,12 +26,22 @@ use crate::structure::segment_feat::线段特征;
use crate::types::;
use std::sync::Arc;
/// 特征分型 — 由三个线段特征元素构成的分型
/// 特征分型 — 由三个线段特征元素构成的分型,用于线段特征序列的分型分析。
///
/// 字段:
/// 左: 左侧线段特征元素
/// 中: 中间线段特征元素
/// 右: 右侧线段特征元素
/// 结构: 由左中右三元素分析得出的分型结构(顶/底/散)
#[derive(Debug, Clone)]
pub struct {
/// 左侧线段特征元素
pub : Arc<线>,
/// 中间线段特征元素
pub : Arc<线>,
/// 右侧线段特征元素
pub : Arc<线>,
/// 由左中右分析得出的分型结构
pub : ,
}
+74 -29
View File
@@ -25,21 +25,39 @@
use crate::kline::chan_kline::K线;
use crate::types::;
use crate::types::;
use std::sync::Arc;
use std::sync::atomic::AtomicBool;
use std::sync::atomic::Ordering;
use std::sync::Arc;
use tracing::warn;
/// 分型模式 — True 时使用构造时缓存值(默认),False 时从 中 缠K 实时读取
pub static : AtomicBool = AtomicBool::new(true);
/// 分型 — 由三根缠K构成(可能缺左或右)
/// 分型 — 由左中右三根缠论K线构成的顶/底分型。
///
/// 字段:
/// 左: 左侧缠K(可能为 None,表示序列起始处分型左翼缺失)
/// 中: 中间缠K(必须存在),分型的核心K线,其 `分型` 字段记录了该位置的分型结构
/// 右: 右侧缠K(可能为 None,表示序列末尾处分型右翼缺失)
/// 结构: 构造时从 `中.分型` 缓存的分型结构(上/下/顶/底/散)
/// 时间戳: 构造时从 `中.时间戳` 缓存的时刻
/// 分型特征值: 构造时从 `中.分型特征值` 缓存的值
///
/// "分型模式" 全局开关控制 时间戳/结构/分型特征值 的读取行为:
/// True(默认)— 返回构造时缓存值;False — 从 中 缠K 实时读取
#[derive(Debug, Clone)]
pub struct {
/// 左侧缠K(序列起始处分型可能为 None)
pub : Option<Arc<K线>>,
/// 中间缠K(必须存在)
pub : Arc<K线>,
/// 右侧缠K(序列末尾处分型可能为 None)
pub : Option<Arc<K线>>,
/// 构造时缓存的 分型结构(上/下/顶/底/散)
pub : ,
/// 构造时缓存的 时间戳
pub : i64,
/// 构造时缓存的 分型特征值(历史高低点极值)
pub : f64,
}
@@ -47,6 +65,16 @@ impl 分型 {
pub fn new(
: Option<Arc<K线>>, : Arc<K线>, : Option<Arc<K线>>
) -> Self {
if let (Some(), Some()) = (&, &) {
debug_assert!(
..load(Ordering::Relaxed) < ..load(Ordering::Relaxed)
&& ..load(Ordering::Relaxed) < ..load(Ordering::Relaxed),
"分型时间戳断言失败: 左={}, 中={}, 右={}",
..load(Ordering::Relaxed),
..load(Ordering::Relaxed),
..load(Ordering::Relaxed),
);
}
let = ..read().unwrap().unwrap_or(::);
let = ..load(Ordering::Relaxed);
let = ..get();
@@ -69,6 +97,24 @@ impl 分型 {
}
}
/// 结构 — 根据 分型模式 决定返回缓存值(True)或实时值(False)
pub fn (&self) -> {
if .load(Ordering::Relaxed) {
self.
} else {
self...read().unwrap().unwrap_or(::) // FIXME 错误
}
}
/// 分型特征值 — 根据 分型模式 决定返回缓存值(True)或实时值(False)
pub fn (&self) -> f64 {
if .load(Ordering::Relaxed) {
self.
} else {
self...get()
}
}
/// 左中右三组关系
pub fn (&self) -> Option<(, , )> {
let = self..as_ref()?;
@@ -82,7 +128,7 @@ impl 分型 {
/// 分型强度 — 返回 "强"/"中"/"弱"/"未知"
pub fn (&self) -> &str {
if self. != :: && self. != :: {
if self.() != :: && self.() != :: {
return "未知";
}
if self..is_none() || self..is_none() {
@@ -90,7 +136,7 @@ impl 分型 {
}
if let Some(ref ) = self.() {
if self. == :: {
if self.() == :: {
if .2.() {
return "";
} else if .2.() {
@@ -98,7 +144,7 @@ impl 分型 {
} else {
return "";
}
} else if self. == :: {
} else if self.() == :: {
if .2.() {
return "";
} else if .2.() {
@@ -109,8 +155,8 @@ impl 分型 {
}
}
if let (Some(ref ), Some(ref )) = (&self., &self.) {
if self. == :: {
if let (Some(), Some()) = (&self., &self.) {
if self.() == :: {
if .K线.read().unwrap(). > .K线.read().unwrap(). {
return "";
} else if .K线.read().unwrap(). > self..K线.read().unwrap().
@@ -119,7 +165,7 @@ impl 分型 {
} else {
return "";
}
} else if self. == :: {
} else if self.() == :: {
if .K线.read().unwrap(). < .K线.read().unwrap(). {
return "";
} else if .K线.read().unwrap(). < self..K线.read().unwrap().
@@ -135,23 +181,29 @@ impl 分型 {
/// MACD柱子分型匹配 — 检查左中右MACD柱形成顶/底形态
pub fn MACD柱子分型匹配(&self) -> bool {
if let (Some(ref ), Some(ref )) = (&self., &self.) {
if self. == :: {
if let (Some(), Some()) = (&self., &self.) {
if self.() == :: {
let _k = .K线.read().unwrap();
let _k = self..K线.read().unwrap();
let _k = .K线.read().unwrap();
if let (Some(ref macd), Some(ref macd), Some(ref macd)) =
(&_k.macd, &_k.macd, &_k.macd)
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd())
{
return macd.MACD柱 > macd.MACD柱 && macd.MACD柱 < macd.MACD柱;
}
}
if self. == :: {
if self.() == :: {
let _k = .K线.read().unwrap();
let _k = self..K线.read().unwrap();
let _k = .K线.read().unwrap();
if let (Some(ref macd), Some(ref macd), Some(ref macd)) =
(&_k.macd, &_k.macd, &_k.macd)
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
let _m = _k..read().unwrap();
if let (Some(macd), Some(macd), Some(macd)) =
(_m.macd(), _m.macd(), _m.macd())
{
return macd.MACD柱 < macd.MACD柱 && macd.MACD柱 > macd.MACD柱;
}
@@ -161,11 +213,8 @@ impl 分型 {
}
/// 判断两个分型是否匹配
pub fn (: &Arc<>, : &Arc<>, : &str) -> bool {
match {
"" => Arc::as_ptr() == Arc::as_ptr(),
_ => false,
}
pub fn (: &Arc<>, : &Arc<>, _模式: &str) -> bool {
Arc::as_ptr() == Arc::as_ptr()
}
/// 从缠K序列中获取以指定缠K为中元素的分型
@@ -192,17 +241,17 @@ impl 分型 {
/// 向分型序列中添加新分型
pub fn (: &mut Vec<Arc<>>, : Arc<>) {
if .is_empty() {
if . != :: && . != ::
if .() != :: && .() != ::
{
panic!("首次添加分型不为 顶底 {:?}", );
}
} else {
let = &[.len() - 1];
if . == . {
if .() == .() {
panic!("分型相同无法添加 {:?} {:?}", , );
}
if ..is_none() {
eprintln!("分型.向序列中添加, 分型异常 {:?}", );
warn!("分型.向序列中添加, 分型异常 {:?}", );
}
}
.push();
@@ -223,13 +272,9 @@ impl std::fmt::Display for 分型 {
write!(
f,
"{}<{}, {}, None: {}, None: {}>",
self.
.
.read()
.unwrap()
.unwrap_or(crate::types::::),
self.(),
self.(),
crate::utils::format_f64_g(self.),
crate::utils::format_f64_g(self.()),
if self..is_none() { "True" } else { "False" },
if self..is_none() { "True" } else { "False" },
)
+74 -40
View File
@@ -27,36 +27,68 @@ use crate::structure::feat_fractal::特征分型;
use crate::structure::fractal_obj::;
use crate::types::{, };
use std::sync::atomic::Ordering;
use std::sync::Arc;
use std::sync::{Arc, RwLock};
/// 线段特征 — 特征序列元素内部是虚线的集合
#[derive(Debug, Clone)]
/// 线段特征 — 特征序列元素内部是虚线的集合
///
/// 在线段划分算法中,将同方向的笔归入一个特征序列元素,
/// 然后通过特征序列元素之间的分型关系判定线段终结。
///
/// 字段:
/// 序号: 特征序列元素编号
/// 标识: 标识字符串(如 "特征<虚线>"
/// 线段方向: 所属线段的方向(与元素包含的虚线方向相反)
/// 基础序列: 构成该特征序列元素的虚线序列
///
/// 计算属性:
/// 文 — 起点分型(取特征序列中分型特征值极值)
/// 武 — 终点分型(取特征序列中分型特征值极值)
/// 方向 — 线段方向的翻转
/// 高 / 低 — 文/武 中分型特征值的较大/较小者
#[derive(Debug)]
pub struct 线 {
/// 特征序列元素编号
pub : i64,
pub : String,
/// 标识字符串(如 "特征<虚线>"
pub : RwLock<String>,
/// 所属线段的方向
pub 线: ,
pub : Vec<Arc<线>>,
/// 构成该元素的虚线序列
pub : Vec<Arc<线>>,
}
impl Clone for 线 {
fn clone(&self) -> Self {
Self {
: self.,
: RwLock::new(self..read().unwrap().clone()),
线: self.线,
: self..clone(),
}
}
}
impl 线 {
/// 新建线段特征(给定标识、基础序列和线段方向)
pub fn new(: String, : Vec<Arc<线>>, 线: ) -> Self {
Self {
: 0,
,
: RwLock::new(),
线,
: ,
,
}
}
/// 图表标题 — 返回标识字符串
pub fn (&self) -> String {
self..clone()
self..read().unwrap().clone()
}
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Arc<> {
if self.线.() {
self.
self.
.iter()
.max_by(|a, b| {
a.
@@ -66,19 +98,19 @@ impl 线段特征 {
.then_with(|| a..().cmp(&b..()))
})
.map(|x| Arc::clone(&x.))
.unwrap_or_else(|| Arc::clone(&self.[0].))
.unwrap_or_else(|| Arc::clone(&self.[0].))
} else {
self.
self.
.iter()
.min_by(|a, b| {
a.
.max_by(|a, b| {
b.
.
.partial_cmp(&b..)
.partial_cmp(&a..)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| b..().cmp(&a..()))
.then_with(|| a..().cmp(&b..()))
})
.map(|x| Arc::clone(&x.))
.unwrap_or_else(|| Arc::clone(&self.[0].))
.unwrap_or_else(|| Arc::clone(&self.[0].))
}
}
@@ -86,7 +118,7 @@ impl 线段特征 {
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Arc<> {
if self.线.() {
self.
self.
.iter()
.max_by(|a, b| {
a.
@@ -104,36 +136,38 @@ impl 线段特征 {
})
})
.map(|x| x..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
} else {
self.
self.
.iter()
.min_by(|a, b| {
a.
.max_by(|a, b| {
b.
.read()
.unwrap()
.
.partial_cmp(&b..read().unwrap().)
.partial_cmp(&a..read().unwrap().)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| {
b.
a.
.read()
.unwrap()
.()
.cmp(&a..read().unwrap().())
.cmp(&b..read().unwrap().())
})
})
.map(|x| x..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
.unwrap_or_else(|| self.[0]..read().unwrap().clone())
}
}
/// 高 — 文和武中分型特征值的较大者
pub fn (&self) -> f64 {
let = self.();
let = self.();
..max(.)
}
/// 低 — 文和武中分型特征值的较小者
pub fn (&self) -> f64 {
let = self.();
let = self.();
@@ -146,25 +180,25 @@ impl 线段特征 {
}
/// 向特征序列元素中添加虚线
pub fn (&mut self, 线: Arc<线>) -> Result<(), String> {
pub fn _添加(&mut self, 线: Arc<线>) -> Result<(), String> {
if 线.() == self.线 {
return Err("添加方向与线段方向相同".into());
}
self..push(线);
self..push(线);
Ok(())
}
/// 从特征序列元素中删除虚线
pub fn (&mut self, 线: &Arc<线>) -> Result<(), String> {
pub fn _删除(&mut self, 线: &Arc<线>) -> Result<(), String> {
if 线.() == self.() {
return Err("删除方向与特征序列方向相同".into());
}
if let Some(pos) = self
.
.
.iter()
.position(|x| Arc::as_ptr(x) == Arc::as_ptr(线))
{
self..remove(pos);
self..remove(pos);
Ok(())
} else {
Err("待删除虚线不在特征序列中".into())
@@ -210,12 +244,12 @@ impl 线段特征 {
if {
let 线 =
.
.
.iter()
.min_by_key(|o| o..load(Ordering::Relaxed))
.unwrap();
let 线 =
.
.
.iter()
.max_by_key(|o| o..load(Ordering::Relaxed))
.unwrap();
@@ -250,7 +284,7 @@ impl 线段特征 {
)) {
// Clone-modify-replace
let mut = (*[_idx]).clone();
let _ = .(Arc::clone(线));
let _ = ._添加(Arc::clone(线));
[_idx] = Arc::new();
} else {
.push(Arc::new(Self::(vec![Arc::clone(线)], 线)));
@@ -260,7 +294,7 @@ impl 线段特征 {
}
/// 获取分型序列
/// 获取分型序列 — 从连续的特征序列元素中提取特征分型
pub fn (: &[Arc<线>]) -> Vec<> {
let mut = Vec::new();
if .len() < 3 {
@@ -295,17 +329,17 @@ impl crate::types::fractal::有高低 for 线段特征 {
impl std::fmt::Display for 线 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self..is_empty() {
write!(f, "{}<{}, 空>", self., self.线)
if self..is_empty() {
write!(f, "{}<{}, 空>", self..read().unwrap(), self.线)
} else {
write!(
f,
"{}<{}, {}, {}, {}>",
self.,
self..read().unwrap(),
self.线,
self.(),
self.(),
self..len()
self..len()
)
}
}
@@ -558,7 +592,7 @@ mod tests {
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let mut feat = 线::new("测试".into(), vec![], ::);
let result = feat.(Arc::clone(&1));
let result = feat._添加(Arc::clone(&1));
assert!(result.is_ok());
}
@@ -568,7 +602,7 @@ mod tests {
let 1 = _创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let mut feat = 线::new("测试".into(), vec![], ::);
let result = feat.(Arc::clone(&1));
let result = feat._添加(Arc::clone(&1));
assert!(result.is_err());
}
+2
View File
@@ -91,6 +91,7 @@ impl std::fmt::Display for 买卖点类型 {
}
impl {
/// 判断是否为买点类型
pub fn (&self) -> bool {
matches!(
self,
@@ -106,6 +107,7 @@ impl 买卖点类型 {
)
}
/// 判断是否为卖点类型
pub fn (&self) -> bool {
matches!(
self,
+6 -4
View File
@@ -48,6 +48,7 @@ pub enum 相对方向 {
}
impl {
/// 翻转方向(向上→向下,缺口向上→缺口向下,顺→逆 等)
pub fn (&self) -> Self {
match self {
Self:: => Self::,
@@ -62,32 +63,33 @@ impl 相对方向 {
}
}
/// 是否向上方向(向上/向上缺口/衔接向上)
pub fn (&self) -> bool {
matches!(self, Self:: | Self:: | Self::)
}
/// 是否向下方向(向下/向下缺口/衔接向下)
pub fn (&self) -> bool {
matches!(self, Self:: | Self:: | Self::)
}
/// 是否包含关系(顺/逆/同)
pub fn (&self) -> bool {
matches!(self, Self:: | Self:: | Self::)
}
/// 是否缺口(向上缺口/向下缺口)
pub fn (&self) -> bool {
matches!(self, Self:: | Self::)
}
/// 是否衔接(衔接向上/衔接向下)
pub fn (&self) -> bool {
matches!(self, Self:: | Self::)
}
/// 分析两个K线之间的相对方向
pub fn (: f64, : f64, : f64, : f64) -> Self {
// NaN 值无法判断方向,视为"同"避免 panic
if .is_nan() || .is_nan() || .is_nan() || .is_nan() {
return Self::;
}
if == && == {
return Self::;
}
+7 -17
View File
@@ -23,6 +23,7 @@
*/
use serde::{Deserialize, Serialize};
use tracing::warn;
/// 分型结构 —— 三根K线构成的结构形态
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
@@ -93,7 +94,9 @@ impl 分型结构 {
}
#[allow(clippy::too_many_arguments)]
fn _内部(
/// 分析三K线的分型结构(内部实现,接收裸f64值)
#[allow(clippy::too_many_arguments)]
pub fn _内部(
: f64,
: f64,
: f64,
@@ -112,36 +115,23 @@ impl 分型结构 {
let = |d: | d.();
match (, ) {
// 顺序包含 — 忽视时可以绕过
(::, _) if ! => {
panic!("顺序包含: {:?} {:?}", , );
panic!("顺序包含 左中相对方向");
}
(_, ::) if ! => {
panic!("顺序包含: {:?} {:?}", , );
panic!("顺序包含 中右相对方向");
}
// 向上 + 向上 = 三连上
(a, b) if (a) && (b) => Some(Self::),
// 向上 + 向下 = 顶分型
(a, b) if (a) && (b) => Some(Self::),
// 向上 + 逆序包含 = 上
(a, ::) if (a) && => Some(Self::),
// 向下 + 向上 = 底分型
(a, b) if (a) && (b) => Some(Self::),
// 向下 + 向下 = 三连下
(a, b) if (a) && (b) => Some(Self::),
// 向下 + 逆序包含 = 下
(a, ::) if (a) && => Some(Self::),
// 逆序包含 + 向上 = 底
(::, a) if (a) && => Some(Self::),
// 逆序包含 + 向下 = 顶
(::, a) if (a) && => Some(Self::),
// 逆序包含 + 逆序包含 = 散
(::, ::) if => Some(Self::),
_ => {
eprintln!(
"无法识别的分型结构: 左中={:?}, 中右={:?}",
,
);
warn!("未匹配的关系 {}, {}, {}", , , );
None
}
}
+3
View File
@@ -27,11 +27,14 @@ use serde::{Deserialize, Serialize};
/// 缺口 —— 两个价格区间之间的空隙
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub struct {
/// 缺口上沿
pub : f64,
/// 缺口下沿
pub : f64,
}
impl {
/// 创建缺口,高必须大于低
pub fn new(: f64, : f64) -> Self {
assert!( > , "缺口高必须大于低: 高={高}, 低={低}");
Self { , }
+4
View File
@@ -26,17 +26,21 @@ use std::sync::atomic::{AtomicU64, Ordering};
/// f64 原子类型 — 基于 AtomicU64 + 位转换,API 与 `Cell<f64>` 一致。
#[derive(Debug, Default)]
/// 线程安全的 f64 原子容器
pub struct SyncF64(AtomicU64);
impl SyncF64 {
/// 创建 SyncF64
pub fn new(v: f64) -> Self {
Self(AtomicU64::new(v.to_bits()))
}
/// 原子读取 f64 值
pub fn get(&self) -> f64 {
f64::from_bits(self.0.load(Ordering::Relaxed))
}
/// 原子写入 f64 值
pub fn set(&self, v: f64) {
self.0.store(v.to_bits(), Ordering::Relaxed);
}