修复 观察者混合扩展边界问题

This commit is contained in:
YuWuKunCheng
2026-06-07 20:45:09 +08:00
parent 11d897ebaa
commit 8a2afb9ed0
4 changed files with 160 additions and 12 deletions
+5 -5
View File
@@ -53,7 +53,7 @@ from typing import (
) )
from collections.abc import Sequence from collections.abc import Sequence
from chanlun.termcolor import colored from termcolor import colored
from loguru import logger from loguru import logger
@@ -6383,7 +6383,7 @@ class 观察者:
线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置) 线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置)
中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0)
for i in range(self.混合扩展线段分析层次): for i in range(min(self.混合扩展线段分析层次, len(self.线段序列组))):
线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置) 线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置)
中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0) 中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0)
@@ -6429,7 +6429,7 @@ class 观察者:
for i in range(self.扩展线段分析层次): for i in range(self.扩展线段分析层次):
保存序列(self.扩展线段序列组[i]) 保存序列(self.扩展线段序列组[i])
保存序列(self.扩展中枢序列组[i]) 保存序列(self.扩展中枢序列组[i])
for i in range(self.线段分析层次): for i in range(self.混合扩展线段分析层次):
保存序列(self.混合扩展线段序列组[i]) 保存序列(self.混合扩展线段序列组[i])
保存序列(self.混合扩展中枢序列组[i]) 保存序列(self.混合扩展中枢序列组[i])
@@ -6487,7 +6487,7 @@ class 观察者:
线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置) 线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置)
中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0)
for i in range(self.混合扩展线段分析层次): for i in range(min(self.混合扩展线段分析层次, len(self.线段序列组))):
线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置) 线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置)
中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0) 中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0)
@@ -6771,7 +6771,7 @@ class 立体分析器:
def 测试_保存数据(self, root: str = None): def 测试_保存数据(self, root: str = None):
"""拆分各序列数据,单独存文件。 """拆分各序列数据,单独存文件。
:param root: 保存根目录默认取脚本所在目录 :param root: 保存根目录默认系统临时目录
:return: 数据保存目录路径 :return: 数据保存目录路径
""" """
# 生成存储根目录 # 生成存储根目录
+5 -5
View File
@@ -53,7 +53,7 @@ from typing import (
) )
from collections.abc import Sequence from collections.abc import Sequence
from chanlun.termcolor import colored from termcolor import colored
from loguru import logger from loguru import logger
@@ -6383,7 +6383,7 @@ class 观察者:
线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置) 线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置)
中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0)
for i in range(self.混合扩展线段分析层次): for i in range(min(self.混合扩展线段分析层次, len(self.线段序列组))):
线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置) 线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置)
中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0) 中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0)
@@ -6429,7 +6429,7 @@ class 观察者:
for i in range(self.扩展线段分析层次): for i in range(self.扩展线段分析层次):
保存序列(self.扩展线段序列组[i]) 保存序列(self.扩展线段序列组[i])
保存序列(self.扩展中枢序列组[i]) 保存序列(self.扩展中枢序列组[i])
for i in range(self.线段分析层次): for i in range(self.混合扩展线段分析层次):
保存序列(self.混合扩展线段序列组[i]) 保存序列(self.混合扩展线段序列组[i])
保存序列(self.混合扩展中枢序列组[i]) 保存序列(self.混合扩展中枢序列组[i])
@@ -6487,7 +6487,7 @@ class 观察者:
线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置) 线段.扩展分析(self.扩展线段序列组[i - 1], self.扩展线段序列组[i], self.配置)
中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0) 中枢.分析(self.扩展线段序列组[i], self.扩展中枢序列组[i], True, "", 0)
for i in range(self.混合扩展线段分析层次): for i in range(min(self.混合扩展线段分析层次, len(self.线段序列组))):
线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置) 线段.扩展分析(self.线段序列组[i], self.混合扩展线段序列组[i], self.配置)
中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0) 中枢.分析(self.混合扩展线段序列组[i], self.混合扩展中枢序列组[i], True, "", 0)
@@ -6771,7 +6771,7 @@ class 立体分析器:
def 测试_保存数据(self, root: str = None): def 测试_保存数据(self, root: str = None):
"""拆分各序列数据,单独存文件。 """拆分各序列数据,单独存文件。
:param root: 保存根目录默认取脚本所在目录 :param root: 保存根目录默认系统临时目录
:return: 数据保存目录路径 :return: 数据保存目录路径
""" """
# 生成存储根目录 # 生成存储根目录
+77
View File
@@ -1764,6 +1764,83 @@ class Test指标挂载(unittest.TestCase):
self.assertNotEqual(macd_before, macd_after) self.assertNotEqual(macd_before, macd_after)
# ============================================================
# 线段分析层次 = 0 时不崩溃
# ============================================================
class Test线段分析层次为零(unittest.TestCase):
"""验证 线段分析层次=0 时,各处理方法不会越界崩溃."""
@classmethod
def setUpClass(cls):
if not _has_nb():
raise unittest.SkipTest("需要 .nb 数据文件")
def test_处理数据_不崩溃(self):
"""投喂K线时 线段分析层次=0 → 跳过所有线段/扩展线段/混合扩展线段分析,不应崩溃."""
import chanlun
from chanlun import chan
# — Rust 侧 —
cfg_rs = chanlun.缠论配置()
obs_rs = chanlun.观察者("btcusd", 300, cfg_rs)
obs_rs.线段分析层次 = 0
obs_rs.重置基础序列()
for ts, o, h, l, c, v in read_nb_bars(NB_PATH)[:500]:
obs_rs.投喂原始数据(ts, o, h, l, c, v)
self.assertGreater(len(obs_rs.缠论K线序列), 0, "缠K序列应有数据")
self.assertGreater(len(obs_rs.分型序列), 0, "分型序列应有数据")
self.assertEqual(len(obs_rs.线段序列组), 0, "线段序列组应为空")
self.assertTrue(all(len(s) == 0 for s in obs_rs.混合扩展线段序列组), "混合扩展线段序列组所有条目应为空")
# — Python 侧 (chan.py) —
cfg_py = chan.缠论配置()
obs_py = chan.观察者("btcusd", 300, cfg_py)
obs_py.线段分析层次 = 0
obs_py.重置基础序列()
for ts, o, h, l, c, v in read_nb_bars(NB_PATH)[:500]:
obs_py.投喂原始数据(ts, o, h, l, c, v)
self.assertGreater(len(obs_py.缠论K线序列), 0, "chan.py 缠K序列应有数据")
self.assertGreater(len(obs_py.分型序列), 0, "chan.py 分型序列应有数据")
self.assertEqual(len(obs_py.线段序列组), 0, "chan.py 线段序列组应为空")
def test_静态重新分析_不崩溃(self):
"""静态重新分析时 线段分析层次=0 → 跳过所有线段分析,不应崩溃."""
import chanlun
from chanlun import chan
# — Rust 侧 —
cfg_rs = chanlun.缠论配置()
obs_rs = chanlun.观察者("btcusd", 300, cfg_rs)
for ts, o, h, l, c, v in read_nb_bars(NB_PATH)[:300]:
obs_rs.投喂原始数据(ts, o, h, l, c, v)
self.assertGreater(len(obs_rs.线段序列组), 0, "正常初始化后应有线段")
obs_rs.线段分析层次 = 0
obs_rs.静态重新分析()
self.assertEqual(len(obs_rs.线段序列组), 0, "静态重新分析后线段序列组应为空")
self.assertGreater(len(obs_rs.分型序列), 0, "静态重新分析后分型序列应有数据")
# — Python 侧 (chan.py) —
cfg_py = chan.缠论配置()
obs_py = chan.观察者("btcusd", 300, cfg_py)
for ts, o, h, l, c, v in read_nb_bars(NB_PATH)[:300]:
obs_py.投喂原始数据(ts, o, h, l, c, v)
self.assertGreater(len(obs_py.线段序列组), 0, "chan.py 正常初始化后应有线段")
obs_py.线段分析层次 = 0
obs_py.静态重新分析()
self.assertEqual(len(obs_py.线段序列组), 0, "chan.py 静态重新分析后线段序列组应为空")
self.assertGreater(len(obs_py.分型序列), 0, "chan.py 静态重新分析后分型序列应有数据")
# ============================================================ # ============================================================
# 集成对比测试 # 集成对比测试
# ============================================================ # ============================================================
+73 -2
View File
@@ -322,7 +322,8 @@ impl 观察者 {
} }
// Step 6: 混合扩展线段分析 — 3 级递归 (源 = 线段序列组[i]) // Step 6: 混合扩展线段分析 — 3 级递归 (源 = 线段序列组[i])
for i in 0..self.线 { // NOTE: 当 线段分析层次=0 时 线段序列组 为空,用 min 避免越界
for i in 0..self.线.min(self.线.len()) {
let = self.线[i].clone(); let = self.线[i].clone();
线::(&, &mut self.线[i], &self.); 线::(&, &mut self.线[i], &self.);
::( ::(
@@ -422,7 +423,7 @@ impl 观察者 {
); );
} }
for i in 0..self.线 { for i in 0..self.线.min(self.线.len()) {
let = self.线[i].clone(); let = self.线[i].clone();
线::(&, &mut self.线[i], &self.); 线::(&, &mut self.线[i], &self.);
::( ::(
@@ -1135,4 +1136,74 @@ mod tests {
assert_eq!(, "ethusd"); assert_eq!(, "ethusd");
assert_eq!(, 7200); assert_eq!(, 7200);
} }
#[test]
fn test_处理数据_线段分析层次为零_不崩溃() {
let mut config = ::default();
config. = test_data_path();
let obs = ::new("btcusd".into(), 300, config);
let mut obs_w = obs.write().unwrap();
obs_w.线 = 0;
obs_w.();
drop(obs_w);
// 逐根投喂K线,不应因 线段分析层次=0 而 panic
let data = std::fs::read(test_data_path()).unwrap();
let size = 48;
for i in 0..(data.len() / size).min(500) {
let offset = i * size;
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 300, "btcusd") {
obs.write().unwrap().K线(k线);
}
}
let obs_r = obs.read().unwrap();
assert!(obs_r.K线序列.len() > 0, "缠K序列应有数据");
assert!(obs_r..len() > 0, "分型序列应有数据");
assert!(obs_r.线.is_empty(), "线段序列组应为空");
// 混合扩展线段序列组 有 3 个空 Vec(因为 混合扩展线段分析层次 仍是 3),
// 但所有条目应为空(min(3, 0) = 0,循环未执行)
assert!(
obs_r.线.iter().all(|s| s.is_empty()),
"混合扩展线段序列组所有条目应为空"
);
info!(
"线段分析层次=0 处理数据 OK: {} 缠K, {} 分型, {} 笔",
obs_r.K线序列.len(),
obs_r..len(),
obs_r..len()
);
}
#[test]
fn test_静态重新分析_线段分析层次为零_不崩溃() {
let mut config = ::default();
config. = test_data_path();
let obs = ::new("btcusd".into(), 300, config);
// 先正常投喂数据
obs.write()
.unwrap()
.(&test_data_path(), Default::default())
.unwrap();
// 设为0后执行静态重新分析,不应 panic
let mut obs_w = obs.write().unwrap();
obs_w.线 = 0;
obs_w.();
drop(obs_w);
let obs_r = obs.read().unwrap();
assert!(obs_r..len() > 0, "静态重新分析后分型序列应有数据");
assert!(obs_r.线.is_empty(), "线段序列组应为空");
assert!(
obs_r.线.iter().all(|s| s.is_empty()),
"混合扩展线段序列组所有条目应为空"
);
info!(
"线段分析层次=0 静态重新分析 OK: {} 分型, {} 笔",
obs_r..len(),
obs_r..len()
);
}
} }