Files
chanlun.rs/chanlun/src/structure/segment_feat.rs
T

669 lines
25 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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::structure::dash_line::虚线;
use crate::structure::feat_fractal::特征分型;
use crate::structure::fractal_obj::分型;
use crate::types::{分型结构, 相对方向};
use std::sync::atomic::AtomicI64;
use std::sync::atomic::Ordering;
use std::sync::{Arc, RwLock};
/// 线段特征 — 特征序列元素,内部是虚线的集合。
///
/// 在线段划分算法中,将同方向的笔归入一个特征序列元素,
/// 然后通过特征序列元素之间的分型关系判定线段终结。
///
/// 字段:
/// 序号: 特征序列元素编号
/// 标识: 标识字符串(如 "特征<虚线>"
/// 线段方向: 所属线段的方向(与元素包含的虚线方向相反)
/// 基础序列: 构成该特征序列元素的虚线序列
///
/// 计算属性:
/// 文 — 起点分型(取特征序列中分型特征值极值)
/// 武 — 终点分型(取特征序列中分型特征值极值)
/// 方向 — 线段方向的翻转
/// 高 / 低 — 文/武 中分型特征值的较大/较小者
#[derive(Debug)]
pub struct 线段特征 {
/// 特征序列元素编号
pub 序号: AtomicI64,
/// 标识字符串(如 "特征<虚线>"
pub 标识: RwLock<String>,
/// 所属线段的方向
pub 线段方向: 相对方向,
/// 构成该元素的虚线序列
pub 基础序列: Vec<Arc<虚线>>,
}
impl Clone for 线段特征 {
fn clone(&self) -> Self {
Self {
序号: AtomicI64::new(self.序号.load(Ordering::Relaxed)),
标识: RwLock::new(self.标识.read().unwrap().clone()),
线段方向: self.线段方向,
基础序列: self.基础序列.clone(),
}
}
}
impl 线段特征 {
/// 新建线段特征(给定标识、基础序列和线段方向)
pub fn new(标识: String, 基础序列: Vec<Arc<虚线>>, 线段方向: 相对方向) -> Self {
Self {
序号: AtomicI64::new(0),
标识: RwLock::new(标识),
线段方向,
基础序列,
}
}
/// 图表标题 — 返回标识字符串
pub fn 图表标题(&self) -> String {
self.标识.read().unwrap().clone()
}
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Arc<分型> {
if self.线段方向.是否向上() {
self.基础序列
.iter()
.max_by(|a, b| {
a.
.分型特征值
.partial_cmp(&b..分型特征值)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a..时间戳().cmp(&b..时间戳()))
})
.map(|x| Arc::clone(&x.))
.unwrap_or_else(|| Arc::clone(&self.基础序列[0].))
} else {
self.基础序列
.iter()
.max_by(|a, b| {
b.
.分型特征值
.partial_cmp(&a..分型特征值)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| a..时间戳().cmp(&b..时间戳()))
})
.map(|x| Arc::clone(&x.))
.unwrap_or_else(|| Arc::clone(&self.基础序列[0].))
}
}
/// 武 — 取特征序列元素中分型特征值最大/最小的武分型
/// tiebreaker: later时间戳 wins when特征值 equal (matches Python)
pub fn (&self) -> Arc<分型> {
if self.线段方向.是否向上() {
self.基础序列
.iter()
.max_by(|a, b| {
a.
.read()
.unwrap()
.分型特征值
.partial_cmp(&b..read().unwrap().分型特征值)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| {
a.
.read()
.unwrap()
.时间戳()
.cmp(&b..read().unwrap().时间戳())
})
})
.map(|x| x..read().unwrap().clone())
.unwrap_or_else(|| self.基础序列[0]..read().unwrap().clone())
} else {
self.基础序列
.iter()
.max_by(|a, b| {
b.
.read()
.unwrap()
.分型特征值
.partial_cmp(&a..read().unwrap().分型特征值)
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| {
a.
.read()
.unwrap()
.时间戳()
.cmp(&b..read().unwrap().时间戳())
})
})
.map(|x| x..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.();
.分型特征值.min(.分型特征值)
}
/// 方向 — 线段方向的翻转
pub fn 方向(&self) -> 相对方向 {
self.线段方向.翻转()
}
/// 向特征序列元素中添加虚线
pub fn _添加(&mut self, 待添加虚线: Arc<虚线>) -> Result<(), String> {
if 待添加虚线.方向() == self.线段方向 {
return Err("添加方向与线段方向相同".into());
}
self.基础序列.push(待添加虚线);
Ok(())
}
/// 从特征序列元素中删除虚线
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);
Ok(())
} else {
Err("待删除虚线不在特征序列中".into())
}
}
/// 新建特征序列元素
pub fn 新建(虚线序列: Vec<Arc<虚线>>, 线段方向: 相对方向) -> Self {
let 标识 = "特征<虚线>".to_string();
Self::new(标识, 虚线序列, 线段方向)
}
/// 静态分析 — 从虚线序列生成特征序列元素列表
pub fn 静态分析(
虚线序列: &[Arc<虚线>],
线段方向: 相对方向,
四象: &str,
是否忽视: bool,
) -> Vec<Arc<线段特征>> {
let mut 结果: Vec<Arc<线段特征>> = Vec::new();
// 需要被合并的方向集合
let 需要合并: Vec<相对方向> = match 四象 {
"老阳" | "老阴" if !是否忽视 => vec![相对方向::, 相对方向::, 相对方向::],
_ => vec![相对方向::, 相对方向::],
};
for 虚线 in 虚线序列 {
// 情况1:方向相同(可能触发分型替换)
if 虚线.方向() == 线段方向 {
if 结果.len() >= 3 {
let = Arc::clone(&结果[结果.len() - 3]);
let = Arc::clone(&结果[结果.len() - 2]);
let = Arc::clone(&结果[结果.len() - 1]);
if let Some(结构) = 分型结构::分析(&*, &*, &*, true, true) {
let 应替换 = (线段方向 == 相对方向::向上
&& 结构 == 分型结构::
&& 虚线.() > .())
|| (线段方向 == 相对方向::向下
&& 结构 == 分型结构::
&& 虚线.() < .());
if 应替换 {
let 小号虚线 =
.基础序列
.iter()
.min_by_key(|o| o.序号.load(Ordering::Relaxed))
.unwrap();
let 大号虚线 =
.基础序列
.iter()
.max_by_key(|o| o.序号.load(Ordering::Relaxed))
.unwrap();
let fake = 虚线::创建笔(
Arc::clone(&小号虚线.),
大号虚线..read().unwrap().clone(),
false,
);
结果.pop();
let idx = 结果.len() - 1;
结果[idx] = Arc::new(Self::新建(vec![Arc::new(fake)], 线段方向));
}
}
}
continue;
}
// 情况2:方向不同(执行特征序列的合并/添加)
if 结果.is_empty() {
结果.push(Arc::new(Self::新建(vec![Arc::clone(虚线)], 线段方向)));
continue;
}
// 检查与最后一个特征序列的方向关系
let 最后_idx = 结果.len() - 1;
let 之前线段特征 = &*结果[最后_idx];
if 需要合并.contains(&相对方向::分析(
之前线段特征.(),
之前线段特征.(),
虚线.(),
虚线.(),
)) {
// Clone-modify-replace
let mut 新特征 = (*结果[最后_idx]).clone();
let _ = 新特征._添加(Arc::clone(虚线));
结果[最后_idx] = Arc::new(新特征);
} else {
结果.push(Arc::new(Self::新建(vec![Arc::clone(虚线)], 线段方向)));
}
}
结果
}
/// 获取分型序列 — 从连续的特征序列元素中提取特征分型
pub fn 获取分型序列(特征序列: &[Arc<线段特征>]) -> Vec<特征分型> {
let mut 结果 = Vec::new();
if 特征序列.len() < 3 {
return 结果;
}
for i in 2..特征序列.len() {
let = Arc::clone(&特征序列[i - 2]);
let = Arc::clone(&特征序列[i - 1]);
let = Arc::clone(&特征序列[i]);
let 结构 = 分型结构::分析_对象(
&* as &dyn crate::types::fractal::有高低,
&* as &dyn crate::types::fractal::有高低,
&* as &dyn crate::types::fractal::有高低,
true,
true,
);
结果.push(特征分型::new(, , , 结构.unwrap_or(分型结构::)));
}
结果
}
/// 结构化相等校验 — 逐项递归校验基础序列中的虚线,返回 (是否相等, 差异描述)
pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) {
if self.序号.load(Ordering::Relaxed) != other.序号.load(Ordering::Relaxed) {
return (
false,
format!(
"线段特征: [序号] 不等 A={},B={}",
self.序号.load(Ordering::Relaxed),
other.序号.load(Ordering::Relaxed)
),
);
}
if *self.标识.read().unwrap() != *other.标识.read().unwrap() {
return (
false,
format!(
"线段特征: [标识] 不等 A={},B={}",
self.标识.read().unwrap(),
other.标识.read().unwrap()
),
);
}
if self.线段方向 != other.线段方向 {
return (
false,
format!(
"线段特征: [线段方向] 不等 A={},B={}",
self.线段方向, other.线段方向
),
);
}
if self.基础序列.len() != other.基础序列.len() {
return (
false,
format!(
"线段特征: [基础序列] 长度不一致 A={},B={}",
self.基础序列.len(),
other.基础序列.len()
),
);
}
for (idx, (a, b)) in self.基础序列.iter().zip(other.基础序列.iter()).enumerate() {
let (eq, msg) = a.相等(b, 浮点容差);
if !eq {
return (false, format!("线段特征: 基础序列[{idx}]虚线异常 >> {msg}"));
}
}
(true, "线段特征: 全部字段一致".into())
}
}
impl crate::types::fractal::有高低 for 线段特征 {
fn (&self) -> f64 {
self.()
}
fn (&self) -> f64 {
self.()
}
}
impl std::fmt::Display for 线段特征 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.基础序列.is_empty() {
write!(f, "{}<{}, 空>", self.标识.read().unwrap(), self.线段方向)
} else {
write!(
f,
"{}<{}, {}, {}, {}>",
self.标识.read().unwrap(),
self.线段方向,
self.(),
self.(),
self.基础序列.len()
)
}
}
}
#[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::分型结构;
fn 辅助_创建K线(时间戳: i64, : f64, : f64, : f64, : f64) -> K线 {
K线 {
时间戳,
,
,
开盘价: ,
收盘价: ,
..Default::default()
}
}
fn 辅助_创建缠K(
时间戳: i64,
: f64,
: f64,
方向: 相对方向,
结构: Option<分型结构>,
序号: i64,
) -> Arc<缠论K线> {
let K = Arc::new(辅助_创建K线(时间戳, , , , ));
Arc::new(缠论K线::创建缠K(
时间戳, , , 方向, 结构, 序号, K, None,
))
}
fn 辅助_创建顶分型(时间戳: i64, : f64, : f64, 序号: i64) -> Arc<分型> {
let = 辅助_创建缠K(
时间戳 - 2,
- 2.0,
- 2.0,
相对方向::向上,
Some(分型结构::),
序号 - 2,
);
let = 辅助_创建缠K(时间戳, , , 相对方向::向上, Some(分型结构::), 序号);
let = 辅助_创建缠K(
时间戳 + 2,
- 1.0,
- 1.0,
相对方向::向下,
Some(分型结构::),
序号 + 2,
);
Arc::new(分型::new(Some(), , Some()))
}
fn 辅助_创建底分型(时间戳: i64, : f64, : f64, 序号: i64) -> Arc<分型> {
let = 辅助_创建缠K(
时间戳 - 2,
+ 2.0,
+ 2.0,
相对方向::向下,
Some(分型结构::),
序号 - 2,
);
let = 缠论K线::创建缠K(
时间戳,
,
,
相对方向::向下,
Some(分型结构::),
序号,
Arc::new(辅助_创建K线(时间戳, , , , )),
None,
);
.分型特征值.set();
let = Arc::new();
let = 辅助_创建缠K(
时间戳 + 2,
+ 1.0,
+ 1.0,
相对方向::向上,
Some(分型结构::),
序号 + 2,
);
Arc::new(分型::new(Some(), , Some()))
}
fn 辅助_创建笔(
文时间戳: i64,
文高: f64,
文低: f64,
武时间戳: i64,
武高: f64,
武低: f64,
) -> Arc<虚线> {
let = 辅助_创建顶分型(文时间戳, 文高, 文低, 1);
let = 辅助_创建底分型(武时间戳, 武高, 武低, 2);
Arc::new(虚线::创建笔(, , true))
}
// ============================================================
// 文 — 取特征值最大/最小的分型
// 特征序列元素方向与线段方向相反:
// 向上线段 → 元素为向下笔(顶→底) → 文=顶分型 → 取max
// 向下线段 → 元素为向上笔(底→顶) → 文=底分型 → 取min
// ============================================================
#[test]
fn test_文_向上线段取最大特征值分型() {
// 向上线段,元素用向下笔(顶→底),文=顶分型
// 笔1: 顶(特征值=100)→底(80), 文=顶(100)
let 1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
// 笔2: 顶(特征值=110)→底(90), 文=顶(110)
let 2 = 辅助_创建笔(200, 110.0, 100.0, 300, 90.0, 80.0);
let feat = 线段特征::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
相对方向::向上,
);
// 向上取max → 笔2.文=110
let = feat.();
assert!((.分型特征值 - 110.0).abs() < 0.01);
}
#[test]
fn test_文_向下线段取最小特征值分型() {
// 向下线段,元素用向上笔(底→顶),文=底分型
// 笔1: 底(特征值=80)→顶(100), 文=底(80)
let 1 = 辅助_创建底分型(100, 90.0, 80.0, 5);
let 1 = 辅助_创建顶分型(200, 100.0, 90.0, 10);
let 1 = Arc::new(虚线::创建笔(1, 1, true));
// 笔2: 底(特征值=70)→顶(95), 文=底(70)
let 2 = 辅助_创建底分型(200, 80.0, 70.0, 15);
let 2 = 辅助_创建顶分型(300, 95.0, 85.0, 20);
let 2 = Arc::new(虚线::创建笔(2, 2, true));
let feat = 线段特征::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
相对方向::向下,
);
// 向下取min → 笔2.文=70
let = feat.();
assert!((.分型特征值 - 70.0).abs() < 0.01);
}
// ============================================================
// 武 — 取特征值最大/最小的分型
// 向上线段 → 元素为向下笔 → 武=底分型 → 取max
// 向下线段 → 元素为向上笔 → 武=顶分型 → 取min
// ============================================================
#[test]
fn test_武_向上线段取最大特征值分型() {
// 向上线段,元素用向下笔(顶→底),武=底分型
// 笔1: 顶(100)→底(特征值=80), 武=底(80)
let 1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
// 笔2: 顶(110)→底(特征值=90), 武=底(90)
let 2 = 辅助_创建笔(200, 110.0, 100.0, 300, 100.0, 90.0);
let feat = 线段特征::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
相对方向::向上,
);
// 向上取max → 笔2.武=90
let = feat.();
assert!((.分型特征值 - 90.0).abs() < 0.01);
}
#[test]
fn test_武_向下线段取最小特征值分型() {
// 向下线段,元素用向上笔(底→顶),武=顶分型
// 笔1: 底(80)→顶(特征值=100), 武=顶(100)
let 1 = 辅助_创建底分型(100, 90.0, 80.0, 5);
let 1 = 辅助_创建顶分型(200, 100.0, 90.0, 10);
let 1 = Arc::new(虚线::创建笔(1, 1, true));
// 笔2: 底(60)→顶(特征值=85), 武=顶(85)
let 2 = 辅助_创建底分型(200, 70.0, 60.0, 15);
let 2 = 辅助_创建顶分型(300, 85.0, 75.0, 20);
let 2 = Arc::new(虚线::创建笔(2, 2, true));
let feat = 线段特征::new(
"测试".into(),
vec![Arc::clone(&1), Arc::clone(&2)],
相对方向::向下,
);
// 向下取min → 笔2.武=85
let = feat.();
assert!((.分型特征值 - 85.0).abs() < 0.01);
}
// ============================================================
// 文/武 tiebreaker — 同特征值取后时间戳
// ============================================================
#[test]
fn test_文_同特征值取后时间戳() {
// 两个笔的文特征值相同=100,但时间戳不同
let 1 = 辅助_创建顶分型(100, 100.0, 90.0, 5);
let 1 = 辅助_创建底分型(200, 90.0, 80.0, 10);
let 1 = Arc::new(虚线::创建笔(1, 1, true));
let 2 = 辅助_创建顶分型(300, 100.0, 90.0, 15); // 同特征值,后时间戳
let 2 = 辅助_创建底分型(400, 80.0, 70.0, 20);
let 2 = Arc::new(虚线::创建笔(2, 2, true));
let feat = 线段特征::new("测试".into(), vec![1, 2], 相对方向::向上);
let = feat.();
// 向上取最大特征值:都是100 → tiebreaker取后时间戳 → 笔2.文(300)
assert_eq!(.时间戳(), 300);
}
#[test]
fn test_武_同特征值取后时间戳_向上() {
let 1 = 辅助_创建顶分型(100, 100.0, 90.0, 5);
let 1 = 辅助_创建底分型(200, 80.0, 70.0, 10); // 特征值80
let 1 = Arc::new(虚线::创建笔(1, 1, true));
let 2 = 辅助_创建顶分型(300, 80.0, 70.0, 15); // 特征值80
let 2 = 辅助_创建底分型(400, 80.0, 70.0, 20); // 特征值80
let 2 = Arc::new(虚线::创建笔(2, 2, true));
let feat = 线段特征::new("测试".into(), vec![1, 2], 相对方向::向上);
let = feat.();
// 向上取最大特征值:都是80 → tiebreaker取后时间戳 → 笔2.武(400)
assert_eq!(.时间戳(), 400);
}
// ============================================================
// 添加/删除 操作
// ============================================================
#[test]
fn test_添加方向与线段方向相反的虚线可成功() {
// 向下笔(顶→底,方向=向下) 添加到 向上线段(方向=向上) → 方向不同, 可添加
let 1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let mut feat = 线段特征::new("测试".into(), vec![], 相对方向::向上);
let result = feat._添加(Arc::clone(&1));
assert!(result.is_ok());
}
#[test]
fn test_添加方向与线段方向相同的虚线应报错() {
// 向下笔(顶→底,方向=向下) 添加到 向下线段(方向=向下) → 方向相同, 应报错
let 1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let mut feat = 线段特征::new("测试".into(), vec![], 相对方向::向下);
let result = feat._添加(Arc::clone(&1));
assert!(result.is_err());
}
#[test]
fn test_空线段特征文返回第一个元素的文() {
let 1 = 辅助_创建笔(100, 100.0, 90.0, 200, 90.0, 80.0);
let feat = 线段特征::new("测试".into(), vec![Arc::clone(&1)], 相对方向::向上);
let = feat.();
assert_eq!(Arc::as_ptr(&), Arc::as_ptr(&1.));
}
}