第四版
This commit is contained in:
@@ -345,7 +345,7 @@ class 缠论配置(BaseModel):
|
||||
线段内部背驰_测度: bool = True
|
||||
线段内部背驰_模式: str = "相对" # 【任意,配置,全量,相对】
|
||||
|
||||
加载文件路径: str = "./templates/last.nb"
|
||||
加载文件路径: str = ""
|
||||
|
||||
@model_validator(mode="before")
|
||||
def 兼容旧版本配置(cls, values: Dict[str, Any]) -> Dict[str, Any]:
|
||||
@@ -4178,7 +4178,8 @@ def 测试_周期合成(配置: 缠论配置, 配置组: Dict[int, 缠论配置]
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
当前配置 = 缠论配置.不推送()
|
||||
当前配置.加载文件路径 = "./btcusd-300-1761327300-1776327900.nb"
|
||||
当前配置.加载文件路径 = str(Path(__file__).parent / "btcusd-300-1761327300-1776327900.nb")
|
||||
测试_读取数据(当前配置)().测试_保存数据()
|
||||
测试_周期合成(当前配置)().测试_保存数据()
|
||||
|
||||
+6
-1
@@ -86,7 +86,12 @@ cmd_test() {
|
||||
local wheel=$(ls -t target/wheels/*.whl 2>/dev/null | head -1)
|
||||
pip install --force-reinstall "$wheel" 2>&1 | tail -3
|
||||
}
|
||||
python3 test_integration.py
|
||||
# 复制到临时目录运行,避免本地 chanlun/ 目录被优先导入
|
||||
local tmp_dir=$(mktemp -d 2>/dev/null || echo "${TMPDIR:-/tmp}/chanlun-test-$$")
|
||||
mkdir -p "$tmp_dir"
|
||||
cp test_integration.py "$tmp_dir/test_integration.py"
|
||||
CHANLUN_PROJECT_ROOT="$PROJECT_DIR/.." python3 "$tmp_dir/test_integration.py" "$@"
|
||||
rm -rf "$tmp_dir"
|
||||
}
|
||||
|
||||
cmd_sdist() {
|
||||
|
||||
@@ -279,7 +279,7 @@ impl 买卖点Py {
|
||||
|
||||
// ========== 观察者 ==========
|
||||
|
||||
#[pyclass(name = "观察者", unsendable)]
|
||||
#[pyclass(name = "观察者", subclass, unsendable)]
|
||||
pub struct 观察者Py {
|
||||
pub(crate) inner: chanlun::business::observer::观察者,
|
||||
}
|
||||
|
||||
+297
-10
@@ -4,9 +4,21 @@
|
||||
import sys
|
||||
import os
|
||||
import struct
|
||||
import tempfile
|
||||
|
||||
import chanlun
|
||||
|
||||
# 项目根目录(test_integration.py 位于 <root>/chanlun-py/ 下)
|
||||
# 当脚本被复制到其他路径运行时,通过环境变量 CHANLUN_PROJECT_ROOT 指定
|
||||
_PROJECT_ROOT = os.environ.get(
|
||||
"CHANLUN_PROJECT_ROOT",
|
||||
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
||||
)
|
||||
|
||||
NB_PATH = os.path.join(_PROJECT_ROOT, "btcusd-300-1761327300-1776327900.nb")
|
||||
_PY_REF_DIR = os.path.join(_PROJECT_ROOT, "Py_btcusd:300_1761327300_1776327900")
|
||||
_RUST_REF_DIR = os.path.join(_PROJECT_ROOT, "chanlun", "Rust_btcusd:300_1761327300_1776327900")
|
||||
|
||||
|
||||
def read_nb_bars(path, max_bars=None):
|
||||
"""Read bars from .nb file (48 bytes each: 6 × f64 big-endian)."""
|
||||
@@ -25,14 +37,267 @@ def read_nb_bars(path, max_bars=None):
|
||||
return bars
|
||||
|
||||
|
||||
def main():
|
||||
nb_path = "/home/moscow/chanlun.rs/btcusd-300-1761327300-1776327900.nb"
|
||||
ref_dir = "/home/moscow/chanlun.rs/Py_btcusd:300_1761327300_1776327900"
|
||||
out_dir = "/tmp/chanlun_py_test_output"
|
||||
# ============================================================
|
||||
# 观察者 子类化 / 方法重写 测试
|
||||
# ============================================================
|
||||
|
||||
|
||||
def test_subclass_basic():
|
||||
"""子类可创建,isinstance 正确."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
pass
|
||||
|
||||
obs = Sub("btcusd", 300)
|
||||
assert isinstance(obs, chanlun.观察者)
|
||||
assert type(obs).__name__ == "Sub"
|
||||
assert obs.标识 == "btcusd:300"
|
||||
assert obs.周期 == 300
|
||||
print(" ✓ test_subclass_basic")
|
||||
|
||||
|
||||
def test_subclass_init_extra_attrs():
|
||||
"""子类 __init__ 可添加自定义属性."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
def __init__(self, 符号, 周期):
|
||||
self.tag = "custom"
|
||||
self.count = 0
|
||||
|
||||
obs = Sub("btcusd", 300)
|
||||
assert obs.tag == "custom"
|
||||
assert obs.count == 0
|
||||
# 基类字段不受影响
|
||||
assert obs.标识 == "btcusd:300"
|
||||
print(" ✓ test_subclass_init_extra_attrs")
|
||||
|
||||
|
||||
def test_subclass_new_filter_kwargs():
|
||||
"""__new__ 过滤子类专属参数,只把父类需要的传给 super().__new__."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
def __new__(cls, 符号, 周期, *, extra=None, **kwargs):
|
||||
return super().__new__(cls, 符号, 周期)
|
||||
|
||||
def __init__(self, 符号, 周期, *, extra=None, **kwargs):
|
||||
self.extra = extra
|
||||
|
||||
obs = Sub("btcusd", 300, extra={"debug": True})
|
||||
assert obs.extra == {"debug": True}
|
||||
assert obs.标识 == "btcusd:300"
|
||||
|
||||
obs2 = Sub("ethusd", 60)
|
||||
assert obs2.extra is None
|
||||
print(" ✓ test_subclass_new_filter_kwargs")
|
||||
|
||||
|
||||
def test_subclass_new_pass_config():
|
||||
"""__new__ 透传 配置 参数到父类."""
|
||||
cfg = chanlun.缠论配置()
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
def __new__(cls, 符号, 周期, 配置=None, *, tag="", **kwargs):
|
||||
return super().__new__(cls, 符号, 周期, 配置=配置)
|
||||
|
||||
def __init__(self, 符号, 周期, 配置=None, *, tag="", **kwargs):
|
||||
self.tag = tag
|
||||
|
||||
obs = Sub("btcusd", 300, cfg, tag="test-tag")
|
||||
assert obs.标识 == "btcusd:300"
|
||||
assert obs.tag == "test-tag"
|
||||
print(" ✓ test_subclass_new_pass_config")
|
||||
|
||||
|
||||
def test_override_method_super_call():
|
||||
"""重写 增加原始K线,super() 调用父类,全线管线运行."""
|
||||
bars = read_nb_bars(NB_PATH, max_bars=500)
|
||||
|
||||
# 基类对照组
|
||||
base_obs = chanlun.观察者("btcusd", 300)
|
||||
for i, (ts, o, h, l, c, v) in enumerate(bars):
|
||||
k = chanlun.K线.创建普K(f"base_{i}", ts, o, h, l, c, v, i, 300)
|
||||
base_obs.增加原始K线(k)
|
||||
|
||||
# 子类实验组
|
||||
class Sub(chanlun.观察者):
|
||||
def __init__(self, 符号, 周期):
|
||||
self.intercept_count = 0
|
||||
self.intercept_timestamps = []
|
||||
|
||||
def 增加原始K线(self, 普K):
|
||||
self.intercept_count += 1
|
||||
self.intercept_timestamps.append(普K.时间戳)
|
||||
super().增加原始K线(普K)
|
||||
|
||||
sub_obs = Sub("btcusd", 300)
|
||||
for i, (ts, o, h, l, c, v) in enumerate(bars):
|
||||
k = chanlun.K线.创建普K(f"sub_{i}", ts, o, h, l, c, v, i, 300)
|
||||
sub_obs.增加原始K线(k)
|
||||
|
||||
# 拦截次数
|
||||
assert sub_obs.intercept_count == 500
|
||||
assert len(sub_obs.intercept_timestamps) == 500
|
||||
|
||||
# 各层级序列与基类完全一致
|
||||
sequences = [
|
||||
"普通K线序列",
|
||||
"缠论K线序列",
|
||||
"分型序列",
|
||||
"笔序列",
|
||||
"线段序列",
|
||||
"中枢序列",
|
||||
]
|
||||
for attr in sequences:
|
||||
base_len = len(getattr(base_obs, attr))
|
||||
sub_len = len(getattr(sub_obs, attr))
|
||||
assert base_len == sub_len, f"{attr}: base={base_len}, sub={sub_len}"
|
||||
|
||||
# 笔时间戳精确对比
|
||||
base_pens = base_obs.笔序列
|
||||
sub_pens = sub_obs.笔序列
|
||||
for j, (bp, sp) in enumerate(zip(base_pens, sub_pens)):
|
||||
assert bp.文.中.时间戳 == sp.文.中.时间戳, f"笔[{j}] 时间戳不一致"
|
||||
|
||||
print(" ✓ test_override_method_super_call")
|
||||
|
||||
|
||||
def test_override_getter():
|
||||
"""重写 @property getter,super() 取基类值."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
@property
|
||||
def 标识(self):
|
||||
return f"[MOCKED] {super().标识}"
|
||||
|
||||
obs = Sub("btcusd", 300)
|
||||
assert obs.标识 == "[MOCKED] btcusd:300"
|
||||
# 其他 getter 不受影响
|
||||
assert obs.周期 == 300
|
||||
print(" ✓ test_override_getter")
|
||||
|
||||
|
||||
def test_override_str_repr():
|
||||
"""重写 __str__ / __repr__."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
def __str__(self):
|
||||
return f"Custom({self.标识})"
|
||||
|
||||
def __repr__(self):
|
||||
return self.__str__()
|
||||
|
||||
obs = Sub("btcusd", 300)
|
||||
assert str(obs) == "Custom(btcusd:300)"
|
||||
assert repr(obs) == "Custom(btcusd:300)"
|
||||
print(" ✓ test_override_str_repr")
|
||||
|
||||
|
||||
def test_multi_level_inheritance():
|
||||
"""多层继承,MRO 调用链完整."""
|
||||
|
||||
class Level1(chanlun.观察者):
|
||||
def 增加原始K线(self, 普K):
|
||||
self.l1_log = getattr(self, "l1_log", [])
|
||||
self.l1_log.append("L1")
|
||||
super().增加原始K线(普K)
|
||||
|
||||
class Level2(Level1):
|
||||
def 增加原始K线(self, 普K):
|
||||
self.l2_log = getattr(self, "l2_log", [])
|
||||
self.l2_log.append("L2")
|
||||
super().增加原始K线(普K)
|
||||
|
||||
obs = Level2("btcusd", 300)
|
||||
k = chanlun.K线.创建普K("test", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
|
||||
obs.增加原始K线(k)
|
||||
|
||||
assert obs.l2_log == ["L2"], f"L2 log: {obs.l2_log}"
|
||||
assert obs.l1_log == ["L1"], f"L1 log: {obs.l1_log}"
|
||||
assert len(obs.普通K线序列) == 1
|
||||
print(" ✓ test_multi_level_inheritance")
|
||||
|
||||
|
||||
def test_unoverridden_method_inherited():
|
||||
"""未重写的方法从基类直接继承."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
pass
|
||||
|
||||
obs = Sub("btcusd", 120)
|
||||
k = chanlun.K线.创建普K("test", 1761327900, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
|
||||
obs.增加原始K线(k)
|
||||
|
||||
assert obs.标识 == "btcusd:120"
|
||||
assert obs.周期 == 120
|
||||
assert len(obs.普通K线序列) == 1
|
||||
assert len(obs.缠论K线序列) == 1
|
||||
# 静态重新分析 也能正常继承
|
||||
obs.静态重新分析()
|
||||
print(" ✓ test_unoverridden_method_inherited")
|
||||
|
||||
|
||||
def test_override_reset():
|
||||
"""重写 重置基础序列,子类状态也重置."""
|
||||
|
||||
class Sub(chanlun.观察者):
|
||||
def __init__(self, 符号, 周期):
|
||||
self.my_log = []
|
||||
|
||||
def 重置基础序列(self):
|
||||
self.my_log.clear()
|
||||
super().重置基础序列()
|
||||
|
||||
obs = Sub("btcusd", 300)
|
||||
k = chanlun.K线.创建普K("test", 1761327300, 100.0, 105.0, 99.0, 103.0, 1000.0, 0, 300)
|
||||
obs.增加原始K线(k)
|
||||
obs.my_log.append("test")
|
||||
|
||||
assert len(obs.普通K线序列) == 1
|
||||
obs.重置基础序列()
|
||||
assert len(obs.普通K线序列) == 0
|
||||
assert obs.my_log == []
|
||||
print(" ✓ test_override_reset")
|
||||
|
||||
|
||||
def run_subclass_tests():
|
||||
print("=== 观察者 子类化/重写 测试 ===")
|
||||
tests = [
|
||||
test_subclass_basic,
|
||||
test_subclass_init_extra_attrs,
|
||||
test_subclass_new_filter_kwargs,
|
||||
test_subclass_new_pass_config,
|
||||
test_override_method_super_call,
|
||||
test_override_getter,
|
||||
test_override_str_repr,
|
||||
test_multi_level_inheritance,
|
||||
test_unoverridden_method_inherited,
|
||||
test_override_reset,
|
||||
]
|
||||
for test in tests:
|
||||
try:
|
||||
test()
|
||||
except Exception as e:
|
||||
print(f" ✗ {test.__name__} FAILED: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
return 1
|
||||
print(" ✓ 全部通过")
|
||||
return 0
|
||||
|
||||
|
||||
# ============================================================
|
||||
# 集成对比测试
|
||||
# ============================================================
|
||||
|
||||
|
||||
def run_integration_test():
|
||||
"""全量集成测试:喂入 .nb 数据,与 Python 参考输出对比。"""
|
||||
out_dir = os.path.join(tempfile.gettempdir(), "chanlun_py_test_output")
|
||||
|
||||
# Read all bars
|
||||
print("Reading bars from .nb file...")
|
||||
bars = read_nb_bars(nb_path)
|
||||
bars = read_nb_bars(NB_PATH)
|
||||
print(f" Read {len(bars)} bars")
|
||||
|
||||
# Create observer (default config)
|
||||
@@ -67,13 +332,13 @@ def main():
|
||||
|
||||
# Compare with Python reference
|
||||
print("\nComparing with Python reference...")
|
||||
ref_files = sorted(os.listdir(ref_dir))
|
||||
ref_files = sorted(os.listdir(_PY_REF_DIR))
|
||||
|
||||
match_count = 0
|
||||
diff_count = 0
|
||||
all_match = True
|
||||
for fname in ref_files:
|
||||
ref_path = os.path.join(ref_dir, fname)
|
||||
ref_path = os.path.join(_PY_REF_DIR, fname)
|
||||
out_path = os.path.join(actual_out_dir, fname)
|
||||
|
||||
if not os.path.exists(out_path):
|
||||
@@ -103,13 +368,12 @@ def main():
|
||||
all_match = False
|
||||
|
||||
# Also compare extra files against Rust reference
|
||||
rust_ref_dir = "/home/moscow/chanlun.rs/chanlun/Rust_btcusd:300_1761327300_1776327900"
|
||||
extra_files = set(out_files) - set(ref_files)
|
||||
if extra_files:
|
||||
print("\nComparing extra files with Rust reference...")
|
||||
for fname in sorted(extra_files):
|
||||
out_path = os.path.join(actual_out_dir, fname)
|
||||
rust_ref_path = os.path.join(rust_ref_dir, fname)
|
||||
rust_ref_path = os.path.join(_RUST_REF_DIR, fname)
|
||||
if os.path.exists(rust_ref_path):
|
||||
with open(rust_ref_path) as f:
|
||||
ref_lines = f.readlines()
|
||||
@@ -131,5 +395,28 @@ def main():
|
||||
return 1
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(description="chanlun PyO3 集成测试")
|
||||
parser.add_argument("test", nargs="?", default="all", choices=["all", "subclass", "integration"], help="运行哪组测试 (默认: all)")
|
||||
args = parser.parse_args()
|
||||
|
||||
exit_code = 0
|
||||
|
||||
if args.test in ("all", "subclass"):
|
||||
if run_subclass_tests() != 0:
|
||||
exit_code = 1
|
||||
|
||||
if args.test in ("all", "integration"):
|
||||
if run_integration_test() != 0:
|
||||
exit_code = 1
|
||||
|
||||
if exit_code == 0:
|
||||
print("\n✓ 所有测试通过")
|
||||
|
||||
sys.exit(exit_code)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
main()
|
||||
|
||||
@@ -40,10 +40,16 @@ impl 背驰分析 {
|
||||
/// 斜率背驰 — 价格斜率背驰
|
||||
pub fn 斜率背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let dx = (进入段.武.时间戳 - 进入段.文.时间戳) as f64;
|
||||
if dx == 0.0 {
|
||||
return false;
|
||||
}
|
||||
let dy = 进入段.武.分型特征值 - 进入段.文.分型特征值;
|
||||
let 进入斜率 = dy / dx;
|
||||
|
||||
let dx = (离开段.武.时间戳 - 离开段.文.时间戳) as f64;
|
||||
if dx == 0.0 {
|
||||
return false;
|
||||
}
|
||||
let dy = 离开段.武.分型特征值 - 离开段.文.分型特征值;
|
||||
let 离开斜率 = dy / dx;
|
||||
|
||||
|
||||
@@ -88,7 +88,9 @@ impl 立体分析器 {
|
||||
/// 测试_保存数据 — 多级别数据拆分保存
|
||||
/// 创建父目录 PyM_{标识}_{起始时间}_{结束时间},各周期观察者保存到子目录
|
||||
pub fn 测试_保存数据(&self) {
|
||||
let 根目录 = std::env::current_dir().unwrap_or_default();
|
||||
let 根目录 = std::env::var("CHANLUN_DATA_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|_| std::env::temp_dir());
|
||||
|
||||
let 起始时间 = self
|
||||
.单体分析器
|
||||
@@ -124,7 +126,7 @@ impl 立体分析器 {
|
||||
|
||||
for 周期 in &self.周期组 {
|
||||
if let Some(观察员) = self.单体分析器.get(周期) {
|
||||
观察员.测试_保存数据(Some(保存路径.to_str().unwrap()));
|
||||
观察员.测试_保存数据(Some(&保存路径.to_string_lossy()));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -376,7 +376,9 @@ impl 观察者 {
|
||||
// 确定根目录
|
||||
let 根目录 = match root {
|
||||
Some(r) => std::path::PathBuf::from(r),
|
||||
None => std::env::current_dir().unwrap_or_default(),
|
||||
None => std::env::var("CHANLUN_DATA_DIR")
|
||||
.map(std::path::PathBuf::from)
|
||||
.unwrap_or_else(|_| std::env::temp_dir()),
|
||||
};
|
||||
|
||||
// 生成子目录名称
|
||||
|
||||
@@ -196,7 +196,7 @@ impl Default for 缠论配置 {
|
||||
线段内部背驰_斜率: true,
|
||||
线段内部背驰_测度: true,
|
||||
线段内部背驰_模式: "相对".into(),
|
||||
加载文件路径: "./templates/last.nb".into(),
|
||||
加载文件路径: String::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +172,9 @@ impl 缠论K线 {
|
||||
普k: Rc<K线>,
|
||||
之前: Option<&缠论K线>,
|
||||
) -> Self {
|
||||
if 高.is_nan() || 低.is_nan() {
|
||||
panic!("缠K高/低不能为NaN: 高={高}, 低={低}");
|
||||
}
|
||||
assert!(高 >= 低, "缠K高必须>=低: 高={高}, 低={低}");
|
||||
|
||||
let 周期 = 普k.周期;
|
||||
|
||||
@@ -464,7 +464,7 @@ impl 虚线 {
|
||||
.unwrap()
|
||||
.MACD柱
|
||||
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱)
|
||||
.unwrap()
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对 = vec![Rc::clone(*最高柱子), Rc::clone(最后)];
|
||||
@@ -481,7 +481,7 @@ impl 虚线 {
|
||||
.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);
|
||||
da.partial_cmp(&db).unwrap()
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高离差值.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
@@ -498,7 +498,7 @@ impl 虚线 {
|
||||
.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);
|
||||
da.partial_cmp(&db).unwrap()
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高信号线.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
@@ -531,7 +531,7 @@ impl 虚线 {
|
||||
.MACD柱
|
||||
.abs()
|
||||
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱.abs())
|
||||
.unwrap()
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对 = vec![Rc::clone(*最高柱子), Rc::clone(最后)];
|
||||
@@ -548,7 +548,7 @@ impl 虚线 {
|
||||
.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();
|
||||
da.partial_cmp(&db).unwrap()
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高离差值.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
@@ -565,7 +565,7 @@ impl 虚线 {
|
||||
.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();
|
||||
da.partial_cmp(&db).unwrap()
|
||||
da.partial_cmp(&db).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.unwrap();
|
||||
if let (Some(m0), Some(m1)) = (最高信号线.macd.as_ref(), 最后.macd.as_ref()) {
|
||||
|
||||
@@ -60,6 +60,10 @@ impl 相对方向 {
|
||||
|
||||
/// 分析两个K线之间的相对方向
|
||||
pub fn 分析(前高: f64, 前低: f64, 后高: f64, 后低: f64) -> Self {
|
||||
// NaN 值无法判断方向,视为"同"避免 panic
|
||||
if 前高.is_nan() || 前低.is_nan() || 后高.is_nan() || 后低.is_nan() {
|
||||
return Self::同;
|
||||
}
|
||||
if 前高 == 后高 && 前低 == 后低 {
|
||||
return Self::同;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user