第十版

This commit is contained in:
YuWuKunCheng
2026-06-07 17:02:10 +08:00
parent 4bf9461009
commit 11d897ebaa
8 changed files with 560 additions and 78 deletions
+2 -2
View File
@@ -1,6 +1,6 @@
[package]
name = "chanlun-py"
version = "26.6.42"
version = "26.6.44"
edition = "2024"
description = "缠论技术分析库 — Rust 高性能 Python 绑定"
authors = ["YuYuKunKun"]
@@ -12,7 +12,7 @@ crate-type = ["cdylib"]
name = "chanlun"
[dependencies]
chanlun = { path = "../chanlun" }
chanlun = "26.6.2" #{ path = "../chanlun" }
lru = "0.18"
pyo3 = { version = "0.28", features = ["experimental-inspect"] }
serde_json = "1"
+1 -1
View File
@@ -4,7 +4,7 @@ build-backend = "maturin"
[project]
name = "chanlun"
version = "2606.42"
version = "2606.44"
description = "缠论技术分析库 — Rust 高性能实现"
readme = { file = "README.md", content-type = "text/markdown" }
license = { file = "LICENSE", content-type = "text/plain" }
+93 -35
View File
@@ -25,7 +25,7 @@
use crate::kline_py::chan_kline_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py};
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};
use pyo3::types::{PyDict, PyList, PyType};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
@@ -308,11 +308,13 @@ impl 笔Py {
#[classmethod]
#[pyo3(signature = (当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 递归层次, 配置))]
/// 笔划分核心递归算法
/// 分型序列/笔序列 原地修改(与 chan.py 行为一致)
/// :return: 递归层次
fn (
_cls: &Bound<'_, PyType>,
: Option<&Bound<'_, Py>>,
: Vec<Py<Py>>,
: Vec<Py<线Py>>,
: &Bound<'_, PyList>,
: &Bound<'_, PyList>,
K序列: Vec<Py<crate::kline_py::K线Py>>,
K序列: Vec<Py<K线Py>>,
: i64,
@@ -321,14 +323,19 @@ impl 笔Py {
) -> PyResult<i64> {
let _ = ; // Python API 兼容参数,核心从0开始计数
let _rc = .map(|f| Arc::clone(&f.borrow().inner));
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();
// 从 Python 列表提取
let mut fr_seq = Vec::with_capacity(.len());
for item in .iter() {
let f: PyRef<'_, Py> = item.extract()?;
fr_seq.push(Arc::clone(&f.inner));
}
let mut bi_seq = Vec::with_capacity(.len());
for item in .iter() {
let d: PyRef<'_, 线Py> = item.extract()?;
bi_seq.push(Arc::clone(&d.inner));
}
let ck_list: Vec<Arc<chanlun::kline::chan_kline::K线>> = K序列
.iter()
.map(|k| Arc::clone(&k.bind(py).borrow().inner))
@@ -338,8 +345,8 @@ impl 笔Py {
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
let config = .borrow().to_rust_config(py)?;
match _rc {
Some(fr) => Ok(chanlun::algorithm::bi::::(
let depth = match _rc {
Some(fr) => chanlun::algorithm::bi::::(
fr,
&mut fr_seq,
&mut bi_seq,
@@ -347,9 +354,21 @@ impl 笔Py {
&bar_list,
,
&config,
)),
None => Ok(),
),
None => ,
};
// 写回 Python 列表
.call_method0("clear")?;
for f in fr_seq {
.call_method1("append", (fractal_to_py(py, f),))?;
}
.call_method0("clear")?;
for d in bi_seq {
.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(depth)
}
#[classmethod]
@@ -502,11 +521,13 @@ impl 线段Py {
}
#[classmethod]
#[pyo3(signature = (笔序列, 线段序列, 配置, 层级 = 0, 关系序列 = None))]
/// 线段划分核心递归算法
/// 线段序列 原地修改(与 chan.py 行为一致)
fn (
_cls: &Bound<'_, PyType>,
: Vec<Py<线Py>>,
线: Vec<Py<线Py>>,
线: &Bound<'_, PyList>,
: &Bound<'_, Py>,
: i64,
: Option<Vec<Py>>,
@@ -516,10 +537,13 @@ impl 线段Py {
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let mut seg_seq = Vec::with_capacity(线.len());
for item in 线.iter() {
let d: PyRef<'_, 线Py> = item.extract()?;
seg_seq.push(Arc::clone(&d.inner));
}
let config = .borrow().to_rust_config(py)?;
let default_rel = vec![
chanlun::types::::,
@@ -535,15 +559,22 @@ impl 线段Py {
,
&rel_list,
);
// 写回 Python 列表
线.call_method0("clear")?;
for d in seg_seq {
线.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(())
}
#[classmethod]
/// 即同级别分析
/// 线段序列 原地修改(与 chan.py 行为一致)
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
线: Vec<Py<线Py>>,
线: &Bound<'_, PyList>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<()> {
@@ -551,12 +582,21 @@ impl 线段Py {
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let mut seg_seq: Vec<Arc<chanlun::structure::dash_line::线>> = 线
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let mut seg_seq = Vec::with_capacity(线.len());
for item in 线.iter() {
let d: PyRef<'_, 线Py> = item.extract()?;
seg_seq.push(Arc::clone(&d.inner));
}
let config = .borrow().to_rust_config(py)?;
chanlun::algorithm::segment::线::(&dash_list, &mut seg_seq, &config);
// 写回 Python 列表
线.call_method0("clear")?;
for d in seg_seq {
线.call_method1("append", (dashed_to_py(py, d),))?;
}
Ok(())
}
@@ -792,18 +832,26 @@ impl 中枢Py {
}
/// 当基础序列>=9时,从中枢中提取扩展线段中枢
/// 扩展中枢 原地修改(与 chan.py 行为一致)
fn (
&self,
: Vec<Py<Self>>,
: &Bound<'_, PyList>,
: &Bound<'_, crate::config_py::Py>,
py: Python<'_>,
) -> PyResult<()> {
let mut hub_seq: Vec<Arc<chanlun::algorithm::hub::>> =
.iter()
.map(|h| Arc::clone(&h.bind(py).borrow().inner))
.collect();
let mut hub_seq = Vec::with_capacity(.len());
for item in .iter() {
let h: PyRef<'_, Py> = item.extract()?;
hub_seq.push(Arc::clone(&h.inner));
}
let config = .borrow().to_rust_config(.py())?;
self.inner.(&mut hub_seq, &config);
// 写回 Python 列表
.call_method0("clear")?;
for h in hub_seq {
.call_method1("append", (hub_to_py(py, h),))?;
}
Ok(())
}
@@ -889,10 +937,11 @@ impl 中枢Py {
#[classmethod]
#[pyo3(signature = (虚线序列, 中枢序列, 跳过首部 = true, 标识 = "", 层级 = 0))]
/// 中枢识别核心递归算法
/// 中枢序列 原地修改(与 chan.py 行为一致)
fn (
_cls: &Bound<'_, PyType>,
线: Vec<Py<线Py>>,
: Vec<Py<Self>>,
: &Bound<'_, PyList>,
: bool,
: &str,
: i64,
@@ -902,11 +951,20 @@ impl 中枢Py {
.iter()
.map(|d| Arc::clone(&d.bind(py).borrow().inner))
.collect();
let mut hub_seq: Vec<Arc<chanlun::algorithm::hub::>> =
.iter()
.map(|h| Arc::clone(&h.bind(py).borrow().inner))
.collect();
let mut hub_seq = Vec::with_capacity(.len());
for item in .iter() {
let h: PyRef<'_, Py> = item.extract()?;
hub_seq.push(Arc::clone(&h.inner));
}
chanlun::algorithm::hub::::(&rc_list, &mut hub_seq, , , );
// 写回 Python 列表
.call_method0("clear")?;
for h in hub_seq {
.call_method1("append", (hub_to_py(py, h),))?;
}
Ok(())
}
+26 -11
View File
@@ -23,7 +23,7 @@
*/
use pyo3::prelude::*;
use pyo3::types::{PyBytes, PyDict, PyType};
use pyo3::types::{PyBytes, PyDict, PyList, PyType};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::RwLock;
@@ -663,25 +663,30 @@ impl 缠论K线Py {
#[classmethod]
/// 分析K线,执行指标计算+包含处理+分型判定
/// 缠K序列/普K序列 原地修改(与 chan.py 行为一致)
/// :return: (状态, 分型|None)
fn (
_cls: &Bound<'_, PyType>,
K线: &Bound<'_, K线Py>,
K序列: Vec<Py<Self>>,
K序列: Vec<Py<K线Py>>,
K序列: &Bound<'_, PyList>,
K序列: &Bound<'_, PyList>,
: &Bound<'_, Py>,
py: Python<'_>,
) -> PyResult<(String, Option<Py<PyAny>>)> {
let ck_inner = (*K线.borrow().inner).clone();
let config = .borrow().to_rust_config(py)?;
let mut ck_seq: Vec<_> = K序列
.iter()
.map(|k| std::sync::Arc::clone(&k.bind(py).borrow().inner))
.collect();
let mut bar_seq: Vec<_> = K序列
.iter()
.map(|k| k.bind(py).borrow().inner.clone())
.collect();
// 从 Python 列表提取
let mut ck_seq = Vec::with_capacity(K序列.len());
for item in K序列.iter() {
let ck: PyRef<'_, Self> = item.extract()?;
ck_seq.push(std::sync::Arc::clone(&ck.inner));
}
let mut bar_seq = Vec::with_capacity(K序列.len());
for item in K序列.iter() {
let bar: PyRef<'_, K线Py> = item.extract()?;
bar_seq.push(bar.inner.clone());
}
let (status, fractal) = chanlun::kline::chan_kline::K线::(
ck_inner,
@@ -690,6 +695,16 @@ impl 缠论K线Py {
&config,
);
// 写回 Python 列表(clear + extend
K序列.call_method0("clear")?;
for k in ck_seq {
K序列.call_method1("append", (chan_kline_to_py(py, k),))?;
}
K序列.call_method0("clear")?;
for k in bar_seq {
K序列.call_method1("append", (bar_to_py(py, k),))?;
}
Ok((status, fractal.map(|f| fractal_to_py(py, f).into_any())))
}
+206 -9
View File
@@ -508,13 +508,10 @@ class Test观察者子类化(PyO3SubclassMixin, unittest.TestCase):
return obs
# 4. 读取数据文件(classmethod 重写
a = datetime.now()
obs = Sub.读取数据文件(NB_PATH)
self.assertIsInstance(obs, Sub)
self.assertTrue(obs._custom_classmethod_flag)
self.assertGreater(len(obs.普通K线序列), 0)
b = datetime.now()
print("读取数据文件 用时:", b - a)
# 1. 加载本地数据
obs.重置基础序列()
@@ -522,8 +519,6 @@ class Test观察者子类化(PyO3SubclassMixin, unittest.TestCase):
self.assertTrue(obs._loaded)
self.assertEqual(obs._load_count, 2)
self.assertGreater(len(obs.普通K线序列), 0)
c = datetime.now()
print("加载本地数据 用时:", c - b)
# 2. 静态重新分析(复用已加载数据的 obs)
obs.重置基础序列()
@@ -531,8 +526,6 @@ class Test观察者子类化(PyO3SubclassMixin, unittest.TestCase):
obs.静态重新分析()
self.assertEqual(obs._reanalyzed, 1)
self.assertGreaterEqual(len(obs.笔序列), 0)
d = datetime.now()
print("静态重新分析 用时:", d - c)
# 3. 保存数据
obs.重置基础序列()
@@ -544,8 +537,6 @@ class Test观察者子类化(PyO3SubclassMixin, unittest.TestCase):
# 5. 重置次数
self.assertEqual(obs._reload, 6)
e = datetime.now()
print("保存数据 用时:", e - d)
def test_override_completely_no_super(self):
k = self.make_data_item()
@@ -1954,5 +1945,211 @@ class TestApi一致性(ApiConsistencyMixin, unittest.TestCase):
# ============================================================
class Test导出函数双端等效(unittest.TestCase):
"""验证: 序列修改类导出函数与 chan.py 行为一致 (就地修改 + 返回值等效)"""
_TEST_COUNT = 300 # 投喂 K 线数
@classmethod
def setUpClass(cls):
if not _has_nb():
raise unittest.SkipTest("需要 .nb 数据文件")
cls.bars = read_nb_bars(NB_PATH)
# ---- 辅助 ----
def _make_observers(self):
import chanlun
from chanlun import chan
cfg_rs = chanlun.缠论配置()
cfg_rs.计算指标 = True
cfg_py = chan.缠论配置()
cfg_py.计算指标 = True
obs_rs = chanlun.观察者("btcusd", 300, cfg_rs)
obs_py = chan.观察者("btcusd", 300, cfg_py)
for i, (ts, o, h, l, c, v) in enumerate(self.bars[: self._TEST_COUNT]):
obs_rs.投喂原始数据(ts, o, h, l, c, v)
obs_py.投喂原始数据(ts, o, h, l, c, v)
return obs_rs, obs_py
# ================================================================
# 缠论K线.分析
# ================================================================
def test_缠论K线分析_等效(self):
"""缠论K线.分析 双端行为一致 (返回值 + 序列长度)."""
import chanlun
from chanlun import chan
obs_rs, obs_py = self._make_observers()
ck_rs: list = []
bar_rs: list = []
ck_py: list = []
bar_py: list = []
mismatches = []
for k_rs, k_py in zip(obs_rs.普通K线序列[100:200], obs_py.普通K线序列[100:200]):
st_rs, fx_rs = chanlun.缠论K线.分析(k_rs, ck_rs, bar_rs, obs_rs.配置)
st_py, fx_py = chan.缠论K线.分析(k_py, ck_py, bar_py, obs_py.配置)
if st_rs != st_py:
mismatches.append(f"状态: R={st_rs} P={st_py}")
if (fx_rs is None) != (fx_py is None):
mismatches.append(f"分型None: R={fx_rs is None} P={fx_py is None}")
self.assertEqual(len(mismatches), 0, f"缠论K线.分析 不一致 ({len(mismatches)}):\n" + "\n".join(mismatches[:5]))
self.assertEqual(len(ck_rs), len(ck_py), f"缠K序列长度: R={len(ck_rs)} P={len(ck_py)}")
self.assertEqual(len(bar_rs), len(bar_py), f"普K序列长度: R={len(bar_rs)} P={len(bar_py)}")
# ================================================================
# 笔.分析
# ================================================================
def test_笔分析_等效(self):
"""笔.分析 双端行为一致 (返回值 + 序列修改)."""
import chanlun
from chanlun import chan
obs_rs, obs_py = self._make_observers()
ck_rs = list(obs_rs.缠论K线序列)
ck_py = list(obs_py.缠论K线序列)
bar_rs = list(obs_rs.普通K线序列)
bar_py = list(obs_py.普通K线序列)
fr_rs: list = []
bi_rs: list = []
fr_py: list = []
bi_py: list = []
mismatches = []
for fx_rs, fx_py in zip(obs_rs.分型序列[2:], obs_py.分型序列[2:]):
d_rs = chanlun..分析(fx_rs, fr_rs, bi_rs, ck_rs, bar_rs, 0, obs_rs.配置)
d_py = chan..分析(fx_py, fr_py, bi_py, ck_py, bar_py, 0, obs_py.配置)
if d_rs != d_py:
mismatches.append(f"递归层次: R={d_rs} P={d_py}")
self.assertEqual(len(mismatches), 0, f"笔.分析 不一致 ({len(mismatches)}):\n" + "\n".join(mismatches[:3]))
self.assertEqual(len(fr_rs), len(fr_py), f"分型序列长度: R={len(fr_rs)} P={len(fr_py)}")
self.assertGreater(len(bi_rs), 0, "笔序列为空 (Rust)")
self.assertEqual(len(bi_rs), len(bi_py), f"笔序列长度: R={len(bi_rs)} P={len(bi_py)}")
# ================================================================
# 线段.分析
# ================================================================
def test_线段分析_等效(self):
"""线段.分析 双端行为一致 (序列修改)."""
import chanlun
from chanlun import chan
obs_rs, obs_py = self._make_observers()
seg_rs = list(obs_rs.线段序列)
seg_py = list(obs_py.线段序列)
# 先用 chanlun.线段.分析 重新分析
bi_list_rs = list(obs_rs.笔序列)
bi_list_py = list(obs_py.笔序列)
# 重置线段序列
seg_rs.clear()
seg_py.clear()
chanlun.线段.分析(bi_list_rs, seg_rs, obs_rs.配置)
chan.线段.分析(bi_list_py, seg_py, obs_py.配置)
self.assertGreater(len(seg_rs), 0, "线段序列为空 (Rust)")
self.assertEqual(len(seg_rs), len(seg_py), f"线段序列长度: R={len(seg_rs)} P={len(seg_py)}")
# ================================================================
# 线段.扩展分析
# ================================================================
def test_线段扩展分析_等效(self):
"""线段.扩展分析 双端行为一致."""
import chanlun
from chanlun import chan
obs_rs, obs_py = self._make_observers()
ext_rs = list(obs_rs.扩展线段序列)
ext_py = list(obs_py.扩展线段序列)
ext_rs.clear()
ext_py.clear()
bi_list_rs = list(obs_rs.笔序列)
bi_list_py = list(obs_py.笔序列)
chanlun.线段.扩展分析(bi_list_rs, ext_rs, obs_rs.配置)
chan.线段.扩展分析(bi_list_py, ext_py, obs_py.配置)
self.assertGreater(len(ext_rs), 0, "扩展线段序列为空 (Rust)")
self.assertEqual(len(ext_rs), len(ext_py), f"扩展线段: R={len(ext_rs)} P={len(ext_py)}")
# ================================================================
# 中枢.分析
# ================================================================
def test_中枢分析_等效(self):
"""中枢.分析 双端行为一致."""
import chanlun
from chanlun import chan
obs_rs, obs_py = self._make_observers()
hub_rs: list = []
hub_py: list = []
bi_list_rs = list(obs_rs.笔序列)
bi_list_py = list(obs_py.笔序列)
chanlun.中枢.分析(bi_list_rs, hub_rs, True, "", 0)
chan.中枢.分析(bi_list_py, hub_py, True, "", 0)
self.assertGreater(len(hub_rs), 0, "笔中枢序列为空 (Rust)")
self.assertEqual(len(hub_rs), len(hub_py), f"笔中枢: R={len(hub_rs)} P={len(hub_py)}")
# ================================================================
# 中枢.获取扩展中枢
# ================================================================
def test_获取扩展中枢_等效(self):
"""中枢.获取扩展中枢 双端行为一致."""
import chanlun
from chanlun import chan
obs_rs, obs_py = self._make_observers()
ext_hub_rs = list(obs_rs.扩展中枢序列)
ext_hub_py = list(obs_py.扩展中枢序列)
ext_hub_rs.clear()
ext_hub_py.clear()
# init with 笔中枢
bi_list_rs = list(obs_rs.笔序列)
bi_list_py = list(obs_py.笔序列)
chanlun.中枢.分析(bi_list_rs, ext_hub_rs, True, "", 0)
chan.中枢.分析(bi_list_py, ext_hub_py, True, "", 0)
# 若基础序列≥9,调用获取扩展中枢
for hub_rs, hub_py in zip(ext_hub_rs, ext_hub_py):
sub_rs: list = []
sub_py: list = []
hub_rs.获取扩展中枢(sub_rs, obs_rs.配置)
hub_py.获取扩展中枢(sub_py, obs_py.配置)
if len(sub_rs) != len(sub_py):
self.fail(f"获取扩展中枢 长度不一致: R={len(sub_rs)} P={len(sub_py)}")
# all passed (or vacuously true if no hubs with >=9 segments)
if __name__ == "__main__":
unittest.main()