第一版
This commit is contained in:
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "chanlun"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
byteorder = "1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
@@ -0,0 +1,821 @@
|
||||
use crate::business::observer::观察者;
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::dash_line::虚线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::{分型结构, 相对方向};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 笔 — 从分型生成笔的算法集合(静态方法命名空间)
|
||||
pub struct 笔;
|
||||
|
||||
impl 笔 {
|
||||
/// 获取可成笔的缠K数量(考虑弱化模式)
|
||||
pub fn 获取缠K数量(缠K序列: &[Rc<缠论K线>], 笔序列: &[Rc<虚线>], 配置: &缠论配置) -> usize {
|
||||
let 实际数量 = 缠K序列.len();
|
||||
if 实际数量 >= 配置.笔内元素数量 as usize {
|
||||
return 实际数量;
|
||||
}
|
||||
|
||||
if 配置.笔弱化 && 实际数量 >= 3 {
|
||||
let 实际高点 = Self::实际高点(缠K序列, 配置.笔内相同终点取舍);
|
||||
let 实际低点 = Self::实际低点(缠K序列, 配置.笔内相同终点取舍);
|
||||
|
||||
if let (Some(ref 高点), Some(ref 低点)) = (&实际高点, &实际低点) {
|
||||
let 原始数量 = 1 + (低点.标的K线.序号 - 高点.标的K线.序号).unsigned_abs() as usize;
|
||||
if 原始数量 >= 配置.笔内元素数量 as usize {
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
}
|
||||
|
||||
if !笔序列.is_empty() {
|
||||
// Try both high and low points (Python: 根据缠K找笔(笔序列, 实际高点) or 根据缠K找笔(笔序列, 实际低点))
|
||||
let 筆 = 实际高点.as_ref()
|
||||
.and_then(|h| Self::根据缠K找笔(笔序列, h, 1))
|
||||
.or_else(|| 实际低点.as_ref().and_then(|l| Self::根据缠K找笔(笔序列, l, 1)));
|
||||
|
||||
if let Some(ref 筆) = 筆 {
|
||||
if let (Some(ref 高_k), Some(ref 低_k)) = (&实际高点, &实际低点) {
|
||||
let 原始数量 = 1 + (低_k.标的K线.序号 - 高_k.标的K线.序号).unsigned_abs() as usize;
|
||||
// 向上笔
|
||||
if 筆.方向().是否向上() && 低_k.低 < 筆.低() {
|
||||
if 原始数量 >= 配置.笔弱化_原始数量 as usize {
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
}
|
||||
// 向下笔
|
||||
if 筆.方向().是否向下() && 低_k.低 > 筆.高() {
|
||||
if 原始数量 >= 配置.笔弱化_原始数量 as usize {
|
||||
return 配置.笔内元素数量 as usize;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
实际数量
|
||||
}
|
||||
|
||||
/// 次高 — 排除最高值后的次高点
|
||||
pub fn 次高(缠K序列: &[Rc<缠论K线>], 取舍: bool) -> Option<Rc<缠论K线>> {
|
||||
if 缠K序列.len() < 2 {
|
||||
return 缠K序列.first().cloned();
|
||||
}
|
||||
let max_高 = 缠K序列.iter().map(|k| k.高).fold(f64::NEG_INFINITY, f64::max);
|
||||
// 排除最高值
|
||||
let filtered: Vec<&Rc<缠论K线>> = 缠K序列.iter().filter(|k| k.高 != max_高).collect();
|
||||
if filtered.is_empty() {
|
||||
return 缠K序列.first().cloned();
|
||||
}
|
||||
// 筛选次高值
|
||||
let second_高 = filtered.iter().map(|k| k.高).fold(f64::NEG_INFINITY, f64::max);
|
||||
let mut candidates: Vec<&Rc<缠论K线>> = filtered.iter().filter(|k| k.高 == second_高).copied().collect();
|
||||
// 按时间戳排序
|
||||
candidates.sort_by(|a, b| a.时间戳.cmp(&b.时间戳));
|
||||
if 取舍 {
|
||||
Some(Rc::clone(candidates[candidates.len() - 1]))
|
||||
} else {
|
||||
Some(Rc::clone(candidates[0]))
|
||||
}
|
||||
}
|
||||
|
||||
/// 次低 — 排除最低值后的次低点
|
||||
pub fn 次低(缠K序列: &[Rc<缠论K线>], 取舍: bool) -> Option<Rc<缠论K线>> {
|
||||
if 缠K序列.len() < 2 {
|
||||
return 缠K序列.first().cloned();
|
||||
}
|
||||
let min_低 = 缠K序列.iter().map(|k| k.低).fold(f64::INFINITY, f64::min);
|
||||
// 排除最低值
|
||||
let filtered: Vec<&Rc<缠论K线>> = 缠K序列.iter().filter(|k| k.低 != min_低).collect();
|
||||
if filtered.is_empty() {
|
||||
return 缠K序列.first().cloned();
|
||||
}
|
||||
// 筛选次低值
|
||||
let second_低 = filtered.iter().map(|k| k.低).fold(f64::INFINITY, f64::min);
|
||||
let mut candidates: Vec<&Rc<缠论K线>> = filtered.iter().filter(|k| k.低 == second_低).copied().collect();
|
||||
// 按时间戳排序
|
||||
candidates.sort_by(|a, b| a.时间戳.cmp(&b.时间戳));
|
||||
if 取舍 {
|
||||
Some(Rc::clone(candidates[candidates.len() - 1]))
|
||||
} else {
|
||||
Some(Rc::clone(candidates[0]))
|
||||
}
|
||||
}
|
||||
|
||||
/// 实际高点
|
||||
pub fn 实际高点(缠K序列: &[Rc<缠论K线>], 取舍: bool) -> Option<Rc<缠论K线>> {
|
||||
if 缠K序列.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let max_高 = 缠K序列.iter().map(|k| k.高).fold(f64::NEG_INFINITY, f64::max);
|
||||
let mut candidates: Vec<&Rc<缠论K线>> = 缠K序列.iter().filter(|k| k.高 == max_高).collect();
|
||||
if candidates.is_empty() {
|
||||
return Some(Rc::clone(&缠K序列[0]));
|
||||
}
|
||||
// 按时间戳排序
|
||||
candidates.sort_by(|a, b| a.时间戳.cmp(&b.时间戳));
|
||||
if 取舍 {
|
||||
Some(Rc::clone(candidates[candidates.len() - 1]))
|
||||
} else {
|
||||
Some(Rc::clone(candidates[0]))
|
||||
}
|
||||
}
|
||||
|
||||
/// 实际低点
|
||||
pub fn 实际低点(缠K序列: &[Rc<缠论K线>], 取舍: bool) -> Option<Rc<缠论K线>> {
|
||||
if 缠K序列.is_empty() {
|
||||
return None;
|
||||
}
|
||||
let min_低 = 缠K序列.iter().map(|k| k.低).fold(f64::INFINITY, f64::min);
|
||||
let mut candidates: Vec<&Rc<缠论K线>> = 缠K序列.iter().filter(|k| k.低 == min_低).collect();
|
||||
if candidates.is_empty() {
|
||||
return Some(Rc::clone(&缠K序列[0]));
|
||||
}
|
||||
// 按时间戳排序
|
||||
candidates.sort_by(|a, b| a.时间戳.cmp(&b.时间戳));
|
||||
if 取舍 {
|
||||
Some(Rc::clone(candidates[candidates.len() - 1]))
|
||||
} else {
|
||||
Some(Rc::clone(candidates[0]))
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断笔的相对关系是否合理
|
||||
pub fn 相对关系(筆: &虚线, _配置: &缠论配置) -> bool {
|
||||
let 文 = &筆.文;
|
||||
let 武 = &筆.武;
|
||||
|
||||
// 向上笔:文(底)低 → 武(顶)高
|
||||
if 筆.方向().是否向上() {
|
||||
return 武.分型特征值 > 文.分型特征值;
|
||||
}
|
||||
// 向下笔:文(顶)高 → 武(底)低
|
||||
武.分型特征值 < 文.分型特征值
|
||||
}
|
||||
|
||||
/// 以文会友 — 根据起点分型找笔
|
||||
pub fn 以文会友(笔序列: &[Rc<虚线>], 文: &Rc<分型>) -> Option<Rc<虚线>> {
|
||||
笔序列.iter().find(|b| Rc::as_ptr(&b.文) == Rc::as_ptr(文)).cloned()
|
||||
}
|
||||
|
||||
/// 以武会友 — 根据终点分型找笔
|
||||
pub fn 以武会友(笔序列: &[Rc<虚线>], 武: &Rc<分型>) -> Option<Rc<虚线>> {
|
||||
笔序列.iter().find(|b| Rc::as_ptr(&b.武) == Rc::as_ptr(武)).cloned()
|
||||
}
|
||||
|
||||
/// 根据缠K找对应的笔
|
||||
pub fn 根据缠K找笔(笔序列: &[Rc<虚线>], 缠K: &Rc<缠论K线>, 偏移: i64) -> Option<Rc<虚线>> {
|
||||
// Python iterates in reverse: for 筆 in 笔序列[::-1]
|
||||
for b in 笔序列.iter().rev() {
|
||||
// Python: 筆.文.中.序号 - 偏移 <= 缠K.序号 <= 筆.武.中.序号
|
||||
if b.文.中.序号 - 偏移 <= 缠K.序号 && 缠K.序号 <= b.武.中.序号 {
|
||||
return Some(Rc::clone(b));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 从分型序列中弹出最后一个分型和对应的笔
|
||||
fn 弹出旧笔(分型序列: &mut Vec<Rc<分型>>, 笔序列: &mut Vec<Rc<虚线>>) {
|
||||
分型序列.pop();
|
||||
if !笔序列.is_empty() {
|
||||
// Python sets旧笔.有效性 = False; with Rc we just drop the笔
|
||||
笔序列.pop();
|
||||
}
|
||||
}
|
||||
|
||||
/// 核心笔分析 — 使用显式栈模拟递归
|
||||
///
|
||||
/// 返回: 递归层次数
|
||||
pub fn 分析(
|
||||
初始分型: Rc<分型>,
|
||||
分型序列: &mut Vec<Rc<分型>>,
|
||||
笔序列: &mut Vec<Rc<虚线>>,
|
||||
缠K序列: &[Rc<缠论K线>],
|
||||
_普K序列: &[Rc<K线>],
|
||||
配置: &缠论配置,
|
||||
) -> i64 {
|
||||
enum 栈项 {
|
||||
分型(Rc<分型>, i64),
|
||||
/// 修复错过笔哨兵: 若临时分型被接受为最后一个元素,则扫描武将之后的所有分型
|
||||
修复错过笔 {
|
||||
临时分型: Rc<分型>,
|
||||
武将缠K: Rc<缠论K线>,
|
||||
层次: i64,
|
||||
},
|
||||
}
|
||||
|
||||
let mut 栈: Vec<栈项> = Vec::new();
|
||||
栈.push(栈项::分型(初始分型, 0));
|
||||
|
||||
while let Some(项) = 栈.pop() {
|
||||
let (当前分型, 递归层次) = match 项 {
|
||||
栈项::分型(fx, lvl) => (fx, lvl),
|
||||
栈项::修复错过笔 {
|
||||
临时分型,
|
||||
武将缠K,
|
||||
层次,
|
||||
} => {
|
||||
// Python line 2406: only scan if临时分型 was accepted as last element
|
||||
if !分型序列.is_empty() {
|
||||
if let Some(last_fx) = 分型序列.last() {
|
||||
if Rc::as_ptr(last_fx) == Rc::as_ptr(&临时分型) {
|
||||
if let Some(武_idx) = 缠K序列
|
||||
.iter()
|
||||
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(&武将缠K))
|
||||
{
|
||||
let mut 错过: Vec<Rc<分型>> = Vec::new();
|
||||
for ck in &缠K序列[武_idx..] {
|
||||
if ck.分型 == Some(分型结构::底)
|
||||
|| ck.分型 == Some(分型结构::顶)
|
||||
{
|
||||
if let Some(fx) =
|
||||
分型::从缠K序列中获取分型(缠K序列, ck)
|
||||
{
|
||||
错过.push(Rc::new(fx));
|
||||
}
|
||||
}
|
||||
}
|
||||
// Push in reverse so first in slice is processed first
|
||||
for m in 错过.into_iter().rev() {
|
||||
栈.push(栈项::分型(m, 层次 + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// 当前分型 is already next on the stack (pushed before this哨兵)
|
||||
continue;
|
||||
}
|
||||
};
|
||||
|
||||
if 递归层次 > 256 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if !matches!(当前分型.结构, 分型结构::顶 | 分型结构::底) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Python line 2322-2325: 第一个分型直接追加
|
||||
if 分型序列.is_empty() {
|
||||
分型序列.push(当前分型);
|
||||
continue;
|
||||
}
|
||||
|
||||
let 之前分型 = Rc::clone(分型序列.last().unwrap());
|
||||
|
||||
// Python line 2330-2335: 清理无效数据
|
||||
if 之前分型.时间戳 == 当前分型.时间戳
|
||||
|| matches!(之前分型.结构, 分型结构::上 | 分型结构::下)
|
||||
{
|
||||
Self::弹出旧笔(分型序列, 笔序列);
|
||||
if 分型序列.is_empty() {
|
||||
if 当前分型.右.is_some() {
|
||||
分型序列.push(当前分型);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let 之前分型 = Rc::clone(分型序列.last().unwrap());
|
||||
|
||||
// Python line 2338: 时序检查 — skip out-of-order fractals
|
||||
if 之前分型.时间戳 > 当前分型.时间戳 && 之前分型.中.序号 - 当前分型.中.序号 > 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Python line 2343-2348: 笔弱化模式
|
||||
if 配置.笔弱化 && !笔序列.is_empty() {
|
||||
let 前一笔 = 笔序列.last().unwrap();
|
||||
let 前一笔缠K数 = 前一笔.武.中.序号 - 前一笔.文.中.序号 + 1;
|
||||
if 前一笔缠K数 == 3 {
|
||||
let 破位 = (前一笔.方向().是否向上()
|
||||
&& 前一笔.低() > 当前分型.分型特征值
|
||||
&& 当前分型.结构 == 分型结构::底)
|
||||
|| (前一笔.方向().是否向下()
|
||||
&& 前一笔.高() < 当前分型.分型特征值
|
||||
&& 当前分型.结构 == 分型结构::顶);
|
||||
if 破位 {
|
||||
Self::弹出旧笔(分型序列, 笔序列);
|
||||
栈.push(栈项::分型(当前分型, 递归层次 + 1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Re-read之前分型 again after笔弱化 pop
|
||||
let 之前分型 = Rc::clone(分型序列.last().unwrap());
|
||||
|
||||
// Python line 2350: 分型结构相反 → 可能成笔
|
||||
if 之前分型.结构 != 当前分型.结构 {
|
||||
let 文_idx = 缠K序列.iter().position(|k| Rc::as_ptr(k) == Rc::as_ptr(&之前分型.中));
|
||||
let 武_idx = 缠K序列.iter().position(|k| Rc::as_ptr(k) == Rc::as_ptr(&当前分型.中));
|
||||
|
||||
if let (Some(文_idx), Some(武_idx)) = (文_idx, 武_idx) {
|
||||
let 基础序列 = &缠K序列[文_idx..=武_idx];
|
||||
let 缠K数量 = Self::获取缠K数量(基础序列, 笔序列, 配置);
|
||||
|
||||
if 缠K数量 >= 配置.笔内元素数量 as usize {
|
||||
// Python line 2354-2357: 文官 always uses false (not 笔内相同终点取舍)
|
||||
let 文官 = match 之前分型.结构 {
|
||||
分型结构::顶 => Self::实际高点(基础序列, false),
|
||||
_ => Self::实际低点(基础序列, false),
|
||||
};
|
||||
|
||||
// Python line 2359-2367: 文官 != 之前分型.中 → adjust
|
||||
if let Some(ref 文官_k) = 文官 {
|
||||
if Rc::as_ptr(文官_k) != Rc::as_ptr(&之前分型.中) {
|
||||
if let Some(临时分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, 文官_k)
|
||||
{
|
||||
栈.push(栈项::分型(当前分型, 递归层次));
|
||||
栈.push(栈项::分型(Rc::new(临时分型), 递归层次 + 1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Python line 2369-2372: 武将
|
||||
let 武将 = match 当前分型.结构 {
|
||||
分型结构::底 => Self::实际低点(基础序列, 配置.笔内相同终点取舍),
|
||||
_ => Self::实际高点(基础序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
|
||||
let 新笔 = Rc::new(虚线::创建笔(
|
||||
Rc::clone(&之前分型),
|
||||
Rc::clone(&当前分型),
|
||||
true,
|
||||
));
|
||||
|
||||
// Python line 2374-2376: 相对关系 and武将 matches
|
||||
if Self::相对关系(&新笔, 配置) {
|
||||
if let Some(ref 武将_k) = 武将 {
|
||||
if Rc::as_ptr(武将_k) == Rc::as_ptr(&当前分型.中) {
|
||||
Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 新笔);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Python line 2378-2385: 笔次级成笔
|
||||
if 配置.笔次级成笔 {
|
||||
let 武将 = match 当前分型.结构 {
|
||||
分型结构::底 => Self::次低(基础序列, 配置.笔内相同终点取舍),
|
||||
_ => Self::次高(基础序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
if let Some(ref 武将_k) = 武将 {
|
||||
if Rc::as_ptr(武将_k) == Rc::as_ptr(&当前分型.中)
|
||||
&& Self::相对关系(&新笔, 配置)
|
||||
{
|
||||
Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 新笔);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Python line 2388-2390: 元素不足 → 右元素扩展
|
||||
if let Some(ref 右) = 当前分型.右 {
|
||||
if let Some(临时分型) = 分型::从缠K序列中获取分型(缠K序列, 右) {
|
||||
栈.push(栈项::分型(Rc::new(临时分型), 递归层次 + 1));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Python line 2392-2419: 分型结构相同 → 更强则替换 + 修复错过笔
|
||||
let 分型特征值 = 当前分型.分型特征值;
|
||||
|
||||
let 更强 = match 之前分型.结构 {
|
||||
分型结构::顶 => 之前分型.分型特征值 < 分型特征值,
|
||||
分型结构::底 => 之前分型.分型特征值 > 分型特征值,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if 更强 {
|
||||
let 被替换分型 = Rc::clone(&之前分型);
|
||||
Self::弹出旧笔(分型序列, 笔序列);
|
||||
|
||||
if let Some(k线序列) =
|
||||
缠论K线::截取(缠K序列, &被替换分型.中, &当前分型.中)
|
||||
{
|
||||
let 武将 = match 被替换分型.结构 {
|
||||
分型结构::顶 => {
|
||||
Self::实际低点(&k线序列, 配置.笔内相同终点取舍)
|
||||
}
|
||||
_ => Self::实际高点(&k线序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
|
||||
if let Some(ref 武将_k) = 武将 {
|
||||
if let Some(临时分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, 武将_k)
|
||||
{
|
||||
let 临时分型_rc = Rc::new(临时分型);
|
||||
|
||||
if !分型序列.is_empty() {
|
||||
// Push in reverse processing order (LIFO):
|
||||
栈.push(栈项::分型(Rc::clone(&当前分型), 递归层次 + 2));
|
||||
栈.push(栈项::修复错过笔 {
|
||||
临时分型: Rc::clone(&临时分型_rc),
|
||||
武将缠K: Rc::clone(武将_k),
|
||||
层次: 递归层次 + 1,
|
||||
});
|
||||
栈.push(栈项::分型(临时分型_rc, 递归层次 + 1));
|
||||
continue;
|
||||
} else {
|
||||
分型序列.push(当前分型);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if 分型序列.is_empty() {
|
||||
分型序列.push(当前分型);
|
||||
} else {
|
||||
栈.push(栈项::分型(当前分型, 递归层次 + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
栈.len() as i64
|
||||
}
|
||||
|
||||
/// 核心笔分析 — 递归实现,逐句对照 chan.py 笔.分析 / 笔递归分析
|
||||
///
|
||||
/// 返回: 递归层次数
|
||||
pub fn 分析递归(
|
||||
当前分型: Rc<分型>,
|
||||
分型序列: &mut Vec<Rc<分型>>,
|
||||
笔序列: &mut Vec<Rc<虚线>>,
|
||||
缠K序列: &[Rc<缠论K线>],
|
||||
_普K序列: &[Rc<K线>],
|
||||
递归层次: i64,
|
||||
配置: &缠论配置,
|
||||
) -> i64 {
|
||||
// Python line 2315-2317: 递归深度限制
|
||||
if 递归层次 > 64 {
|
||||
println!("笔.分析 递归深度超出 64 < {}", 递归层次);
|
||||
}
|
||||
|
||||
// Python line 2319-2320: 非顶底分型跳过
|
||||
if !matches!(当前分型.结构, 分型结构::顶 | 分型结构::底) {
|
||||
return 递归层次;
|
||||
}
|
||||
|
||||
// Python line 2322-2325: 第一个分型直接追加
|
||||
if 分型序列.is_empty() {
|
||||
if matches!(当前分型.结构, 分型结构::顶 | 分型结构::底) {
|
||||
分型序列.push(当前分型);
|
||||
}
|
||||
return 递归层次;
|
||||
}
|
||||
|
||||
// Python line 2329-2335: 清理无效数据
|
||||
let 之前分型 = Rc::clone(分型序列.last().unwrap());
|
||||
if 之前分型.时间戳 == 当前分型.时间戳
|
||||
|| matches!(之前分型.结构, 分型结构::上 | 分型结构::下)
|
||||
{
|
||||
Self::弹出旧笔(分型序列, 笔序列);
|
||||
if 分型序列.is_empty() {
|
||||
if 当前分型.右.is_some() {
|
||||
分型::向序列中添加(分型序列, 当前分型);
|
||||
}
|
||||
return 递归层次;
|
||||
}
|
||||
}
|
||||
|
||||
// Python line 2337-2341: 时序检查
|
||||
let 之前分型 = Rc::clone(分型序列.last().unwrap());
|
||||
if 之前分型.时间戳 > 当前分型.时间戳 && 之前分型.中.序号 - 当前分型.中.序号 > 1 {
|
||||
println!("时序错误-{}, {}, {}", 递归层次, 之前分型, 当前分型);
|
||||
return 递归层次;
|
||||
}
|
||||
|
||||
// Python line 2343-2348: 笔弱化模式
|
||||
if 配置.笔弱化 && !笔序列.is_empty() {
|
||||
let 前一笔 = 笔序列.last().unwrap();
|
||||
let 前一笔缠K数 = 前一笔.武.中.序号 - 前一笔.文.中.序号 + 1;
|
||||
if 前一笔缠K数 == 3 {
|
||||
let 破位 = (前一笔.方向().是否向上()
|
||||
&& 前一笔.低() > 当前分型.分型特征值
|
||||
&& 当前分型.结构 == 分型结构::底)
|
||||
|| (前一笔.方向().是否向下()
|
||||
&& 前一笔.高() < 当前分型.分型特征值
|
||||
&& 当前分型.结构 == 分型结构::顶);
|
||||
if 破位 {
|
||||
Self::弹出旧笔(分型序列, 笔序列);
|
||||
return Self::分析递归(
|
||||
当前分型, 分型序列, 笔序列, 缠K序列, _普K序列, 递归层次 + 1, 配置,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Python line 2350: 分型结构相反 → 可能成笔
|
||||
let 之前分型 = Rc::clone(分型序列.last().unwrap());
|
||||
if 之前分型.结构 != 当前分型.结构 {
|
||||
if let Some(基础序列) = 缠论K线::截取(缠K序列, &之前分型.中, &当前分型.中) {
|
||||
let 当前笔 = Rc::new(虚线::创建笔(
|
||||
Rc::clone(&之前分型),
|
||||
Rc::clone(&当前分型),
|
||||
true,
|
||||
));
|
||||
|
||||
if Self::获取缠K数量(&基础序列, 笔序列, 配置) >= 配置.笔内元素数量 as usize
|
||||
{
|
||||
// Python line 2354-2357: 文官
|
||||
let 文官 = match 之前分型.结构 {
|
||||
分型结构::顶 => Self::实际高点(&基础序列, false),
|
||||
_ => Self::实际低点(&基础序列, false),
|
||||
};
|
||||
|
||||
// Python line 2359-2367: 文官调整
|
||||
if let Some(ref 文官_k) = 文官 {
|
||||
if Rc::as_ptr(文官_k) != Rc::as_ptr(&之前分型.中) {
|
||||
if let Some(临时分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, 文官_k)
|
||||
{
|
||||
let 递归层次 = Self::分析递归(
|
||||
Rc::new(临时分型),
|
||||
分型序列,
|
||||
笔序列,
|
||||
缠K序列,
|
||||
_普K序列,
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
return Self::分析递归(
|
||||
当前分型, 分型序列, 笔序列, 缠K序列, _普K序列,
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Python line 2369-2375: 武将 and笔形成
|
||||
let 武将 = match 当前分型.结构 {
|
||||
分型结构::底 => {
|
||||
Self::实际低点(&基础序列, 配置.笔内相同终点取舍)
|
||||
}
|
||||
_ => Self::实际高点(&基础序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
|
||||
if Self::相对关系(&当前笔, 配置) {
|
||||
if let Some(ref 武将_k) = 武将 {
|
||||
if Rc::as_ptr(武将_k) == Rc::as_ptr(&当前分型.中) {
|
||||
// 直接添加(对照 Python _添加新笔:直接 append)
|
||||
Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 当前笔);
|
||||
return 递归层次;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Python line 2378-2385: 笔次级成笔
|
||||
if 配置.笔次级成笔 {
|
||||
let 武将 = match 当前分型.结构 {
|
||||
分型结构::底 => {
|
||||
Self::次低(&基础序列, 配置.笔内相同终点取舍)
|
||||
}
|
||||
_ => Self::次高(&基础序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
if let Some(ref 武将_k) = 武将 {
|
||||
if Rc::as_ptr(武将_k) == Rc::as_ptr(&当前分型.中)
|
||||
&& Self::相对关系(&当前笔, 配置)
|
||||
{
|
||||
Self::_添加新笔递归(分型序列, 笔序列, 当前分型, 当前笔);
|
||||
return 递归层次;
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Python line 2388-2390: 元素不足 → 右元素扩展
|
||||
if let Some(ref 右) = 当前分型.右 {
|
||||
if let Some(临时分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, 右)
|
||||
{
|
||||
return Self::分析递归(
|
||||
Rc::new(临时分型),
|
||||
分型序列,
|
||||
笔序列,
|
||||
缠K序列,
|
||||
_普K序列,
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Python line 2392-2419: 分型结构相同 → 更强则替换 + 修复错过笔
|
||||
let 分型特征值 = 当前分型.分型特征值;
|
||||
|
||||
let 更强 = match 之前分型.结构 {
|
||||
分型结构::顶 => 之前分型.分型特征值 < 分型特征值,
|
||||
分型结构::底 => 之前分型.分型特征值 > 分型特征值,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
if 更强 {
|
||||
// 保存被弹出的之前分型(用于修复错过笔的范围计算)
|
||||
let 被替换分型 = Rc::clone(&之前分型);
|
||||
Self::弹出旧笔(分型序列, 笔序列);
|
||||
|
||||
if let Some(k线序列) =
|
||||
缠论K线::截取(缠K序列, &被替换分型.中, &当前分型.中)
|
||||
{
|
||||
let 武将 = match 被替换分型.结构 {
|
||||
分型结构::顶 => {
|
||||
Self::实际低点(&k线序列, 配置.笔内相同终点取舍)
|
||||
}
|
||||
_ => Self::实际高点(&k线序列, 配置.笔内相同终点取舍),
|
||||
};
|
||||
|
||||
if let Some(ref 武将_k) = 武将 {
|
||||
if let Some(临时分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, 武将_k)
|
||||
{
|
||||
let 临时分型_rc = Rc::new(临时分型);
|
||||
|
||||
if !分型序列.is_empty() {
|
||||
let mut 递归层次 = Self::分析递归(
|
||||
Rc::clone(&临时分型_rc),
|
||||
分型序列,
|
||||
笔序列,
|
||||
缠K序列,
|
||||
_普K序列,
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
|
||||
// 修复错过的笔: 扫描武将之后的所有分型
|
||||
if !分型序列.is_empty()
|
||||
&& Rc::as_ptr(分型序列.last().unwrap())
|
||||
== Rc::as_ptr(&临时分型_rc)
|
||||
{
|
||||
if let Some(武_idx) = 缠K序列
|
||||
.iter()
|
||||
.position(|k| Rc::as_ptr(k) == Rc::as_ptr(武将_k))
|
||||
{
|
||||
for ck in &缠K序列[武_idx..] {
|
||||
if ck.分型 == Some(分型结构::底)
|
||||
|| ck.分型 == Some(分型结构::顶)
|
||||
{
|
||||
if let Some(错过分型) =
|
||||
分型::从缠K序列中获取分型(缠K序列, ck)
|
||||
{
|
||||
let 错过分型_rc =
|
||||
Rc::new(错过分型);
|
||||
递归层次 = Self::分析递归(
|
||||
Rc::clone(&错过分型_rc),
|
||||
分型序列,
|
||||
笔序列,
|
||||
缠K序列,
|
||||
_普K序列,
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return Self::分析递归(
|
||||
当前分型, 分型序列, 笔序列, 缠K序列, _普K序列,
|
||||
递归层次 + 1,
|
||||
配置,
|
||||
);
|
||||
} else {
|
||||
分型::向序列中添加(分型序列, 当前分型);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if 分型序列.is_empty() {
|
||||
分型::向序列中添加(分型序列, 当前分型);
|
||||
} else {
|
||||
return Self::分析递归(
|
||||
当前分型, 分型序列, 笔序列, 缠K序列, _普K序列, 递归层次 + 1, 配置,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
递归层次
|
||||
}
|
||||
|
||||
/// 添加新笔到序列(递归版本 — 直接追加,对应 Python _添加新笔)
|
||||
fn _添加新笔递归(
|
||||
分型序列: &mut Vec<Rc<分型>>,
|
||||
笔序列: &mut Vec<Rc<虚线>>,
|
||||
新分型: Rc<分型>,
|
||||
新笔: Rc<虚线>,
|
||||
) {
|
||||
分型序列.push(新分型);
|
||||
let 序号 = if 笔序列.is_empty() {
|
||||
0
|
||||
} else {
|
||||
笔序列.last().unwrap().序号 + 1
|
||||
};
|
||||
let mut 虚线笔 = (*新笔).clone();
|
||||
虚线笔.序号 = 序号;
|
||||
if 虚线笔.武.左.is_none() && 虚线笔.武.右.is_none() {
|
||||
虚线笔.有效性 = false;
|
||||
}
|
||||
笔序列.push(Rc::new(虚线笔));
|
||||
}
|
||||
|
||||
/// 自检 — 验证笔的有效性(文为实际高/低点,武为实际低/高点)
|
||||
pub fn 自检(筆: &虚线, 观察员: &观察者) -> bool {
|
||||
let 笔序列 = &观察员.笔序列;
|
||||
let 配置 = &观察员.配置;
|
||||
let 基础序列 = 筆.获取缠K序列(&观察员.缠论K线序列);
|
||||
if Self::获取缠K数量(&基础序列, 笔序列, 配置) >= 配置.笔内元素数量 as usize {
|
||||
if 筆.方向() == 相对方向::向下 {
|
||||
if let (Some(实际高), Some(实际低)) = (
|
||||
Self::实际高点(&基础序列, false),
|
||||
Self::实际低点(&基础序列, 配置.笔内相同终点取舍),
|
||||
) {
|
||||
if Rc::ptr_eq(&筆.文.中, &实际高) && Rc::ptr_eq(&筆.武.中, &实际低) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if 筆.方向() == 相对方向::向上 {
|
||||
if let (Some(实际低), Some(实际高)) = (
|
||||
Self::实际低点(&基础序列, false),
|
||||
Self::实际高点(&基础序列, 配置.笔内相同终点取舍),
|
||||
) {
|
||||
if Rc::ptr_eq(&筆.文.中, &实际低) && Rc::ptr_eq(&筆.武.中, &实际高) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// 获取所有停顿位置 — 在笔范围内找出所有能成笔的分型组合
|
||||
pub fn 获取所有停顿位置(筆: &虚线, 观察员: &观察者) -> Vec<虚线> {
|
||||
let mut 笔序列 = Vec::new();
|
||||
let 文 = Rc::clone(&筆.文);
|
||||
let 基础序列 = 筆.获取缠K序列(&观察员.缠论K线序列);
|
||||
|
||||
if 基础序列.len() < 5 {
|
||||
return 笔序列;
|
||||
}
|
||||
|
||||
for i in 3..基础序列.len() - 1 {
|
||||
let k = &基础序列[i];
|
||||
|
||||
if k.分型 == Some(分型结构::顶) && 筆.方向() == 相对方向::向上 {
|
||||
let 左 = Rc::clone(&基础序列[i - 1]);
|
||||
let 中 = Rc::clone(k);
|
||||
let 右 = Rc::clone(&基础序列[i + 1]);
|
||||
let 武 = 分型::new(Some(左), 中, Some(右));
|
||||
let mut 当前笔 = 虚线::创建笔(Rc::clone(&文), Rc::new(武), true);
|
||||
当前笔.序号 = 筆.序号;
|
||||
if Self::自检(&当前笔, 观察员) {
|
||||
笔序列.push(当前笔);
|
||||
}
|
||||
} else if k.分型 == Some(分型结构::底) && 筆.方向() == 相对方向::向下 {
|
||||
let 左 = Rc::clone(&基础序列[i - 1]);
|
||||
let 中 = Rc::clone(k);
|
||||
let 右 = Rc::clone(&基础序列[i + 1]);
|
||||
let 武 = 分型::new(Some(左), 中, Some(右));
|
||||
let mut 当前笔 = 虚线::创建笔(Rc::clone(&文), Rc::new(武), true);
|
||||
当前笔.序号 = 筆.序号;
|
||||
if Self::自检(&当前笔, 观察员) {
|
||||
笔序列.push(当前笔);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
笔序列
|
||||
}
|
||||
|
||||
/// 是否背驰过 — 判断笔是否在停顿位置出现过MACD趋向背驰
|
||||
pub fn 是否背驰过(当前筆: &虚线, 观察员: &观察者) -> Vec<Rc<分型>> {
|
||||
let 停顿位置 = Self::获取所有停顿位置(当前筆, 观察员);
|
||||
let mut 结果 = Vec::new();
|
||||
|
||||
for 筆 in &停顿位置 {
|
||||
let k线范围 = K线::截取rc(
|
||||
&观察员.普通K线序列,
|
||||
&当前筆.文.中.标的K线,
|
||||
&当前筆.武.中.标的K线,
|
||||
);
|
||||
let 背驰信号 = 虚线::计算K线序列MACD趋向背驰(&k线范围, 筆.方向());
|
||||
if 背驰信号.iter().all(|&x| x) {
|
||||
结果.push(Rc::clone(&筆.武));
|
||||
}
|
||||
}
|
||||
|
||||
结果
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::structure::dash_line::虚线;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 背驰分析 — 判断进入段和离开段之间是否存在背驰
|
||||
pub struct 背驰分析;
|
||||
|
||||
impl 背驰分析 {
|
||||
/// MACD背驰 — 离开段MACD柱子面积小于进入段
|
||||
pub fn MACD背驰(进入段: &虚线, 离开段: &虚线, K线序列: &[Rc<K线>], 方式: &str) -> bool {
|
||||
// 获取进入段和离开段对应的K线
|
||||
let 进入段_始 = 进入段.文.中.原始起始序号 as usize;
|
||||
let 进入段_终 = 进入段.武.中.原始结束序号 as usize;
|
||||
let 离开段_始 = 离开段.文.中.原始起始序号 as usize;
|
||||
let 离开段_终 = 离开段.武.中.原始结束序号 as usize;
|
||||
|
||||
if 进入段_终 >= K线序列.len() || 离开段_终 >= K线序列.len() {
|
||||
return false;
|
||||
}
|
||||
|
||||
let 进入MACD = Self::柱子面积(&K线序列[进入段_始..=进入段_终], 方式);
|
||||
let 离开MACD = Self::柱子面积(&K线序列[离开段_始..=离开段_终], 方式);
|
||||
|
||||
// 背驰条件: 离开段面积绝对值小于进入段
|
||||
let 进入总面积 = 进入MACD.阳 + 进入MACD.阴.abs();
|
||||
let 离开总面积 = 离开MACD.阳 + 离开MACD.阴.abs();
|
||||
|
||||
if 进入总面积 < f64::EPSILON {
|
||||
return false;
|
||||
}
|
||||
离开总面积 < 进入总面积
|
||||
}
|
||||
|
||||
/// 斜率背驰 — 离开段斜率小于进入段
|
||||
pub fn 斜率背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let 进入斜率 = (进入段.武.分型特征值 - 进入段.文.分型特征值).abs()
|
||||
/ (进入段.武.时间戳 - 进入段.文.时间戳).abs() as f64;
|
||||
let 离开斜率 = (离开段.武.分型特征值 - 离开段.文.分型特征值).abs()
|
||||
/ (离开段.武.时间戳 - 离开段.文.时间戳).abs() as f64;
|
||||
|
||||
离开斜率 < 进入斜率
|
||||
}
|
||||
|
||||
/// 测度背驰 — 价格测度的背驰判断
|
||||
pub fn 测度背驰(进入段: &虚线, 离开段: &虚线) -> bool {
|
||||
let 进入幅度 = (进入段.武.分型特征值 - 进入段.文.分型特征值).abs();
|
||||
let 离开幅度 = (离开段.武.分型特征值 - 离开段.文.分型特征值).abs();
|
||||
离开幅度 < 进入幅度
|
||||
}
|
||||
|
||||
/// 全量背驰 — MACD + 斜率 + 测度 三者全满足
|
||||
pub fn 全量背驰(进入段: &虚线, 离开段: &虚线, 普K序列: &[Rc<K线>]) -> bool {
|
||||
Self::MACD背驰(进入段, 离开段, 普K序列, "总")
|
||||
&& Self::斜率背驰(进入段, 离开段)
|
||||
&& Self::测度背驰(进入段, 离开段)
|
||||
}
|
||||
|
||||
/// 任意背驰 — 任一条件满足即可
|
||||
pub fn 任意背驰(进入段: &虚线, 离开段: &虚线, 普K序列: &[Rc<K线>]) -> bool {
|
||||
Self::MACD背驰(进入段, 离开段, 普K序列, "总")
|
||||
|| Self::斜率背驰(进入段, 离开段)
|
||||
|| Self::测度背驰(进入段, 离开段)
|
||||
}
|
||||
|
||||
/// 配置背驰 — 根据配置选择判断方式
|
||||
pub fn 配置背驰(进入段: &虚线, 离开段: &虚线, 普K序列: &[Rc<K线>], 配置: &缠论配置) -> bool {
|
||||
let mut result = true;
|
||||
if 配置.线段内部背驰_MACD {
|
||||
result = result && Self::MACD背驰(进入段, 离开段, 普K序列, "总");
|
||||
}
|
||||
if 配置.线段内部背驰_斜率 {
|
||||
result = result && Self::斜率背驰(进入段, 离开段);
|
||||
}
|
||||
if 配置.线段内部背驰_测度 {
|
||||
result = result && Self::测度背驰(进入段, 离开段);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// 任选背驰
|
||||
pub fn 任选背驰(进入段: &虚线, 离开段: &虚线, 普K序列: &[Rc<K线>]) -> bool {
|
||||
Self::任意背驰(进入段, 离开段, 普K序列)
|
||||
}
|
||||
|
||||
/// 背驰模式 — 根据模式字符串选择判断方式
|
||||
pub fn 背驰模式(
|
||||
进入段: &虚线,
|
||||
离开段: &虚线,
|
||||
普K序列: &[Rc<K线>],
|
||||
配置: &缠论配置,
|
||||
模式: &str,
|
||||
) -> bool {
|
||||
match 模式 {
|
||||
"全量" => Self::全量背驰(进入段, 离开段, 普K序列),
|
||||
"任意" => Self::任意背驰(进入段, 离开段, 普K序列),
|
||||
"配置" => Self::配置背驰(进入段, 离开段, 普K序列, 配置),
|
||||
"相对" => Self::测度背驰(进入段, 离开段),
|
||||
_ => Self::配置背驰(进入段, 离开段, 普K序列, 配置),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 内部辅助 ----
|
||||
|
||||
fn 柱子面积(K线序列: &[Rc<K线>], 方式: &str) -> MACD面积 {
|
||||
let mut 阳 = 0.0f64;
|
||||
let mut 阴 = 0.0f64;
|
||||
for k in K线序列 {
|
||||
if let Some(ref macd) = k.macd {
|
||||
let hist = macd.MACD柱;
|
||||
match 方式 {
|
||||
"阳" => {
|
||||
if hist > 0.0 {
|
||||
阳 += hist
|
||||
}
|
||||
}
|
||||
"阴" => {
|
||||
if hist < 0.0 {
|
||||
阴 += hist
|
||||
}
|
||||
}
|
||||
"总" | _ => {
|
||||
if hist >= 0.0 {
|
||||
阳 += hist
|
||||
} else {
|
||||
阴 += hist
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
MACD面积 { 阳, 阴 }
|
||||
}
|
||||
}
|
||||
|
||||
struct MACD面积 {
|
||||
阳: f64,
|
||||
阴: f64,
|
||||
}
|
||||
@@ -0,0 +1,348 @@
|
||||
use crate::structure::dash_line::虚线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::相对方向;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 中枢 — 三段虚线重叠区间构成的价格中枢
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 中枢 {
|
||||
pub 序号: i64,
|
||||
pub 标识: String,
|
||||
pub 级别: i64,
|
||||
pub 基础序列: Vec<Rc<虚线>>,
|
||||
pub 第三买卖线: Option<Rc<虚线>>,
|
||||
pub 本级_第三买卖线: Option<Rc<虚线>>,
|
||||
}
|
||||
|
||||
impl 中枢 {
|
||||
pub fn new(序号: i64, 标识: String, 级别: i64, 基础序列: Vec<Rc<虚线>>) -> Self {
|
||||
Self {
|
||||
序号,
|
||||
标识,
|
||||
级别,
|
||||
基础序列: 基础序列.into_iter().take(3).collect(),
|
||||
第三买卖线: None,
|
||||
本级_第三买卖线: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn 添加虚线(&mut self, 实线: Rc<虚线>) {
|
||||
self.基础序列.push(实线);
|
||||
self.本级_第三买卖线 = None;
|
||||
self.第三买卖线 = None;
|
||||
}
|
||||
|
||||
pub fn 图表标题(&self) -> String {
|
||||
format!(
|
||||
"{}:{}:{}:{}",
|
||||
self.文().中.标识, self.文().中.周期, self.标识, self.序号
|
||||
)
|
||||
}
|
||||
|
||||
pub fn 离开段(&self) -> Rc<虚线> {
|
||||
Rc::clone(&self.基础序列[self.基础序列.len() - 1])
|
||||
}
|
||||
|
||||
pub fn 方向(&self) -> 相对方向 {
|
||||
self.基础序列[0].方向().翻转()
|
||||
}
|
||||
|
||||
pub fn 高(&self) -> f64 {
|
||||
self.基础序列[..3]
|
||||
.iter()
|
||||
.map(|x| x.高())
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn 低(&self) -> f64 {
|
||||
self.基础序列[..3]
|
||||
.iter()
|
||||
.map(|x| x.低())
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn 高高(&self) -> f64 {
|
||||
self.基础序列
|
||||
.iter()
|
||||
.map(|x| x.高())
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn 低低(&self) -> f64 {
|
||||
self.基础序列
|
||||
.iter()
|
||||
.map(|x| x.低())
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0)
|
||||
}
|
||||
|
||||
pub fn 文(&self) -> Rc<分型> {
|
||||
Rc::clone(&self.基础序列[0].文)
|
||||
}
|
||||
|
||||
pub fn 武(&self) -> Rc<分型> {
|
||||
Rc::clone(&self.基础序列[self.基础序列.len() - 1].武)
|
||||
}
|
||||
|
||||
pub fn 设置第三买卖线(&mut self, 线: Rc<虚线>) {
|
||||
self.第三买卖线 = Some(线);
|
||||
}
|
||||
|
||||
pub fn 获取数据文本(&self) -> String {
|
||||
let 第三买卖线_str = match &self.第三买卖线 {
|
||||
Some(x) => format!("{}", x),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
let 本级_第三买卖线_str = match &self.本级_第三买卖线 {
|
||||
Some(x) => format!("{}", x),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
format!(
|
||||
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}",
|
||||
self.标识,
|
||||
self.序号,
|
||||
self.级别,
|
||||
self.文().时间戳,
|
||||
crate::utils::format_f64_g(self.文().分型特征值),
|
||||
self.武().时间戳,
|
||||
crate::utils::format_f64_g(self.武().分型特征值),
|
||||
第三买卖线_str,
|
||||
本级_第三买卖线_str,
|
||||
)
|
||||
}
|
||||
|
||||
/// 校验中枢合法性
|
||||
pub fn 校验合法性(&self, 虚线序列: &[Rc<虚线>], _中枢序列: &[Rc<中枢>]) -> bool {
|
||||
// 1. 检查基础序列中的元素是否仍在虚线序列中
|
||||
for 元素 in &self.基础序列 {
|
||||
if !虚线序列.iter().any(|x| Rc::as_ptr(x) == Rc::as_ptr(元素)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
// 2. 检查前三根重叠是否有效
|
||||
if self.基础序列.len() >= 3 {
|
||||
let 高 = self.高();
|
||||
let 低 = self.低();
|
||||
if 高 <= 低 {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
|
||||
// ---- 关联函数 ----
|
||||
|
||||
/// 基础检查 — 三根虚线是否能形成中枢
|
||||
pub fn 基础检查(左: &虚线, 中: &虚线, 右: &虚线) -> bool {
|
||||
if !左.之后是(中) || !中.之后是(右) {
|
||||
return false;
|
||||
}
|
||||
let 高 = [左.高(), 中.高(), 右.高()]
|
||||
.iter()
|
||||
.copied()
|
||||
.min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0);
|
||||
let 低 = [左.低(), 中.低(), 右.低()]
|
||||
.iter()
|
||||
.copied()
|
||||
.max_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
|
||||
.unwrap_or(0.0);
|
||||
高 > 低
|
||||
}
|
||||
|
||||
/// 创建中枢
|
||||
pub fn 创建(左: Rc<虚线>, 中: Rc<虚线>, 右: Rc<虚线>, 级别: i64, 标识: &str) -> Self {
|
||||
Self::new(0, format!("{}中枢<{}>", 标识, 中.标识), 级别, vec![左, 中, 右])
|
||||
}
|
||||
|
||||
/// 从序列中获取中枢
|
||||
pub fn 从序列中获取中枢(
|
||||
虚线序列: &[Rc<虚线>],
|
||||
起始方向: 相对方向,
|
||||
标识: &str,
|
||||
) -> Option<Rc<中枢>> {
|
||||
for i in 2..虚线序列.len() {
|
||||
let 左 = &虚线序列[i - 2];
|
||||
let 中 = &虚线序列[i - 1];
|
||||
let 右 = &虚线序列[i];
|
||||
if Self::基础检查(左, 中, 右) && 左.方向() == 起始方向 {
|
||||
let 中枢 = Self::创建(Rc::clone(左), Rc::clone(中), Rc::clone(右), 0, 标识);
|
||||
return Some(Rc::new(中枢));
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 向中枢序列尾部添加
|
||||
pub fn 向中枢序列尾部添加(中枢序列: &mut Vec<Rc<中枢>>, 待添加中枢: Rc<中枢>) {
|
||||
// Dedup
|
||||
if let Some(前一个) = 中枢序列.last() {
|
||||
if 前一个.高() == 待添加中枢.高() && 前一个.低() == 待添加中枢.低() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Set序号: Python — if中枢序列: 待添加中枢.序号 = 中枢序列[-1].序号 + 1 else: stays 0
|
||||
if let Some(前一个) = 中枢序列.last() {
|
||||
let 新序号 = 前一个.序号 + 1;
|
||||
// Clone to mutate since Rc is immutable
|
||||
let mut cloned = (*待添加中枢).clone();
|
||||
cloned.序号 = 新序号;
|
||||
中枢序列.push(Rc::new(cloned));
|
||||
} else {
|
||||
中枢序列.push(待添加中枢);
|
||||
}
|
||||
}
|
||||
|
||||
/// 从中枢序列尾部弹出
|
||||
pub fn 从中枢序列尾部弹出(中枢序列: &mut Vec<Rc<中枢>>, 待弹出: &Rc<中枢>) -> Option<Rc<中枢>> {
|
||||
if 中枢序列.last().map(|x| Rc::as_ptr(x)) == Some(Rc::as_ptr(待弹出)) {
|
||||
中枢序列.pop()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// 中枢分析 — 从虚线序列生成中枢序列(增量算法)
|
||||
///
|
||||
/// 每收到新的虚线序列数据后调用,更新中枢序列
|
||||
pub fn 分析(
|
||||
虚线序列: &[Rc<虚线>],
|
||||
中枢序列: &mut Vec<Rc<中枢>>,
|
||||
跳过首部: bool,
|
||||
标识: &str,
|
||||
层级: i64,
|
||||
) {
|
||||
if 虚线序列.len() < 3 {
|
||||
return;
|
||||
}
|
||||
|
||||
// 初始化第一个中枢
|
||||
if 中枢序列.is_empty() {
|
||||
for i in 1..虚线序列.len() - 1 {
|
||||
let 左 = &虚线序列[i - 1];
|
||||
let 中 = &虚线序列[i];
|
||||
let 右 = &虚线序列[i + 1];
|
||||
|
||||
if Self::基础检查(左, 中, 右) {
|
||||
// Python: 序号 = 虚线序列.index(左)
|
||||
let 序号 = 虚线序列.iter().position(|x| Rc::as_ptr(x) == Rc::as_ptr(左)).unwrap_or(i - 1);
|
||||
if 跳过首部 && (左.序号 == 0 || 序号 == 0) {
|
||||
continue;
|
||||
}
|
||||
if 序号 >= 2 {
|
||||
let 前 = &虚线序列[序号 - 2];
|
||||
let 同向相对关系 = crate::types::相对方向::分析(前.高(), 前.低(), 左.高(), 左.低());
|
||||
if 同向相对关系.是否向上() && 左.方向().是否向上() {
|
||||
continue;
|
||||
}
|
||||
if 同向相对关系.是否向下() && 左.方向().是否向下() {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
let 新中枢 = Rc::new(Self::创建(
|
||||
Rc::clone(左),
|
||||
Rc::clone(中),
|
||||
Rc::clone(右),
|
||||
中.级别,
|
||||
标识,
|
||||
));
|
||||
Self::向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
// Python: return 中枢递归分析(虚线序列, 中枢序列, ...)
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级);
|
||||
return;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// 增量更新
|
||||
let 当前中枢_idx = 中枢序列.len() - 1;
|
||||
let 当前中枢 = Rc::clone(&中枢序列[当前中枢_idx]);
|
||||
|
||||
if !当前中枢.校验合法性(虚线序列, 中枢序列) {
|
||||
Self::从中枢序列尾部弹出(中枢序列, &当前中枢);
|
||||
Self::分析(虚线序列, 中枢序列, 跳过首部, 标识, 层级);
|
||||
return;
|
||||
}
|
||||
|
||||
// 找到当前中枢最后一个元素在虚线序列中的位置
|
||||
let 最后元素 = &当前中枢.基础序列[当前中枢.基础序列.len() - 1];
|
||||
let 起始索引 = match 虚线序列
|
||||
.iter()
|
||||
.position(|x| Rc::as_ptr(x) == Rc::as_ptr(最后元素))
|
||||
{
|
||||
Some(idx) => idx + 1,
|
||||
None => return,
|
||||
};
|
||||
|
||||
let 中枢高 = 当前中枢.高();
|
||||
let 中枢低 = 当前中枢.低();
|
||||
let mut 候选序列: Vec<Rc<虚线>> = Vec::new();
|
||||
|
||||
let mut 新当前中枢 = (*当前中枢).clone();
|
||||
|
||||
for i in 起始索引..虚线序列.len() {
|
||||
let 当前虚线 = Rc::clone(&虚线序列[i]);
|
||||
|
||||
// 检查是否超出中枢范围(缺口)
|
||||
if crate::types::相对方向::分析(中枢高, 中枢低, 当前虚线.高(), 当前虚线.低()).是否缺口() {
|
||||
候选序列.push(当前虚线.clone());
|
||||
|
||||
// Python: if 当前中枢.基础序列[-1].之后是(当前虚线):
|
||||
if 新当前中枢.基础序列.last().unwrap().之后是(&当前虚线) {
|
||||
新当前中枢.设置第三买卖线(当前虚线.clone());
|
||||
}
|
||||
} else {
|
||||
if 候选序列.is_empty() {
|
||||
// 仍在范围内:延伸中枢
|
||||
新当前中枢.添加虚线(当前虚线);
|
||||
} else {
|
||||
候选序列.push(当前虚线);
|
||||
}
|
||||
}
|
||||
|
||||
// 候选序列积满3个:尝试创建新中枢
|
||||
while 候选序列.len() >= 3 {
|
||||
let 起始方向 = 新当前中枢.基础序列.last().unwrap().方向().翻转();
|
||||
match Self::从序列中获取中枢(&候选序列, 起始方向, 标识) {
|
||||
Some(新中枢) => {
|
||||
中枢序列[当前中枢_idx] = Rc::new(新当前中枢.clone());
|
||||
Self::向中枢序列尾部添加(中枢序列, 新中枢);
|
||||
return;
|
||||
}
|
||||
None => {
|
||||
候选序列.remove(0); // 滑动窗口
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新当前中枢
|
||||
中枢序列[当前中枢_idx] = Rc::new(新当前中枢);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 中枢 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
let 序列_str = self
|
||||
.基础序列
|
||||
.iter()
|
||||
.map(|d| format!("{}", d))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
write!(
|
||||
f,
|
||||
"{}({}, {}, 元素数量: {}, [{}], {} ===>>> {})",
|
||||
self.标识,
|
||||
crate::utils::format_f64_g(self.高()),
|
||||
crate::utils::format_f64_g(self.低()),
|
||||
self.基础序列.len(),
|
||||
序列_str,
|
||||
self.文(),
|
||||
self.武(),
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod bi;
|
||||
pub mod divergence;
|
||||
pub mod hub;
|
||||
pub mod segment;
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,181 @@
|
||||
use crate::kline::bar::K线;
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::bsp_type::买卖点类型;
|
||||
use crate::types::分型结构;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 基础买卖点 — 买卖点的基础数据结构
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 基础买卖点 {
|
||||
pub 备注: String,
|
||||
pub 类型: 买卖点类型,
|
||||
pub 买卖点分型: Rc<分型>,
|
||||
pub 买卖点K线: Rc<缠论K线>,
|
||||
pub 当前K线: Rc<K线>,
|
||||
pub 失效K线: Option<Rc<K线>>,
|
||||
pub 终结K线: Option<Rc<K线>>,
|
||||
pub 破位值: f64,
|
||||
pub 结构: Option<分型结构>,
|
||||
}
|
||||
|
||||
impl 基础买卖点 {
|
||||
pub fn new(
|
||||
类型: 买卖点类型,
|
||||
当前K线: Rc<K线>,
|
||||
买卖点分型: Rc<分型>,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> Self {
|
||||
let 买卖点K线 = Rc::clone(&买卖点分型.中);
|
||||
Self {
|
||||
备注,
|
||||
类型,
|
||||
买卖点分型,
|
||||
买卖点K线,
|
||||
当前K线,
|
||||
失效K线: None,
|
||||
终结K线: None,
|
||||
破位值: 中枢破位值,
|
||||
结构: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 偏移 — 当前K线与买卖点K线的序号差
|
||||
pub fn 偏移(&self) -> i64 {
|
||||
self.当前K线.序号 - self.买卖点K线.序号
|
||||
}
|
||||
|
||||
/// 失效偏移
|
||||
pub fn 失效偏移(&self) -> i64 {
|
||||
match &self.失效K线 {
|
||||
Some(k) => k.序号 - self.买卖点K线.序号,
|
||||
None => -1,
|
||||
}
|
||||
}
|
||||
|
||||
/// 有效性 — 失效K线是否存在
|
||||
pub fn 有效性(&self) -> bool {
|
||||
self.失效K线.is_some()
|
||||
}
|
||||
|
||||
/// 与MACD柱子匹配
|
||||
pub fn 与MACD柱子匹配(&self) -> bool {
|
||||
self.买卖点K线.与MACD柱子匹配()
|
||||
}
|
||||
|
||||
/// 与MACD柱子分型匹配
|
||||
pub fn 与MACD柱子分型匹配(&self) -> bool {
|
||||
self.买卖点分型.与MACD柱子分型匹配()
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 基础买卖点 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}>",
|
||||
self.类型,
|
||||
self.买卖点K线.as_ref(),
|
||||
self.偏移(),
|
||||
self.失效偏移(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// 买卖点 — 包含一二三类买卖点的工厂方法
|
||||
pub struct 买卖点;
|
||||
|
||||
impl 买卖点 {
|
||||
pub fn 一卖点(
|
||||
买卖点分型: Rc<分型>,
|
||||
当前K线: Rc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::一卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
pub fn 一买点(
|
||||
买卖点分型: Rc<分型>,
|
||||
当前K线: Rc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::一买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
pub fn 二卖点(
|
||||
买卖点分型: Rc<分型>,
|
||||
当前K线: Rc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::二卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
pub fn 二买点(
|
||||
买卖点分型: Rc<分型>,
|
||||
当前K线: Rc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::二买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
pub fn 三卖点(
|
||||
买卖点分型: Rc<分型>,
|
||||
当前K线: Rc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::三卖, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
pub fn 三买点(
|
||||
买卖点分型: Rc<分型>,
|
||||
当前K线: Rc<K线>,
|
||||
_标识: &str,
|
||||
备注: String,
|
||||
中枢破位值: f64,
|
||||
) -> 基础买卖点 {
|
||||
基础买卖点::new(买卖点类型::三买, 当前K线, 买卖点分型, 备注, 中枢破位值)
|
||||
}
|
||||
|
||||
/// 生成买卖点 — 根据参数自动选择类型
|
||||
pub fn 生成买卖点(
|
||||
特征: &str,
|
||||
序号: &str,
|
||||
级别: &str,
|
||||
买卖点分型: Rc<分型>,
|
||||
当前缠K: Rc<缠论K线>,
|
||||
) -> 基础买卖点 {
|
||||
let 买卖 = if matches!(买卖点分型.结构, 分型结构::底 | 分型结构::下) {
|
||||
"买"
|
||||
} else {
|
||||
"卖"
|
||||
};
|
||||
let 备注 = format!("{}_{}{}{}", 特征, 级别, 序号, 买卖);
|
||||
let 破位值 = 买卖点分型.分型特征值;
|
||||
|
||||
// 当前K线 — 从缠K获取其标的K线
|
||||
let 当前K线 = Rc::clone(&当前缠K.标的K线);
|
||||
|
||||
let 类型 = match (序号, 买卖) {
|
||||
("一", "买") => 买卖点类型::一买,
|
||||
("一", "卖") => 买卖点类型::一卖,
|
||||
("二", "买") => 买卖点类型::二买,
|
||||
("二", "卖") => 买卖点类型::二卖,
|
||||
("三", "买") => 买卖点类型::三买,
|
||||
("三", "卖") => 买卖点类型::三卖,
|
||||
_ => 买卖点类型::一买, // fallback
|
||||
};
|
||||
|
||||
基础买卖点::new(类型, 当前K线, 买卖点分型, 备注, 破位值)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod bsp;
|
||||
pub mod multi_frame;
|
||||
pub mod observer;
|
||||
pub mod synthesizer;
|
||||
@@ -0,0 +1,148 @@
|
||||
use crate::business::observer::观察者;
|
||||
use crate::business::synthesizer::K线合成器;
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::bar::K线;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// 立体分析器 — 多周期协调器
|
||||
///
|
||||
/// 包含一个K线合成器和每周期一个观察者。
|
||||
/// 输入最小周期K线,合成大周期后分发到对应观察者。
|
||||
pub struct 立体分析器 {
|
||||
pub 周期组: Vec<i64>,
|
||||
输入周期: i64,
|
||||
显示周期: i64,
|
||||
K线合成器: K线合成器,
|
||||
单体分析器: HashMap<i64, 观察者>,
|
||||
合成K线计数: HashMap<i64, usize>,
|
||||
}
|
||||
|
||||
impl 立体分析器 {
|
||||
pub fn new(
|
||||
符号: String,
|
||||
周期组: Vec<i64>,
|
||||
配置: Option<缠论配置>,
|
||||
配置组: Option<HashMap<i64, 缠论配置>>,
|
||||
) -> Self {
|
||||
let mut 周期组 = 周期组;
|
||||
周期组.sort();
|
||||
let 输入周期 = 周期组[0];
|
||||
let 显示周期 = if 周期组.len() > 1 {
|
||||
周期组[1]
|
||||
} else {
|
||||
周期组[0]
|
||||
};
|
||||
|
||||
let 默认配置 = 配置.unwrap_or_default();
|
||||
let 配置组 = 配置组.unwrap_or_default();
|
||||
|
||||
let K线合成器 = K线合成器::new(符号.clone(), 周期组.clone());
|
||||
|
||||
let mut 单体分析器 = HashMap::new();
|
||||
for &周期 in &周期组 {
|
||||
let mut 当前配置 = 配置组.get(&周期).cloned().unwrap_or_else(|| 默认配置.clone());
|
||||
当前配置.推送K线 = false;
|
||||
当前配置.推送线段 = false;
|
||||
// Set 标识 to match 符号
|
||||
当前配置.标识 = 符号.clone();
|
||||
|
||||
let 观察员 = 观察者::new(符号.clone(), 周期, 当前配置);
|
||||
单体分析器.insert(周期, 观察员);
|
||||
}
|
||||
|
||||
// Configure display period
|
||||
if let Some(显示观察员) = 单体分析器.get_mut(&显示周期) {
|
||||
显示观察员.配置.推送K线 = true;
|
||||
显示观察员.配置.推送笔 = true;
|
||||
显示观察员.配置.推送线段 = true;
|
||||
显示观察员.配置.图表展示 = true;
|
||||
显示观察员.重置基础序列();
|
||||
}
|
||||
|
||||
// Align other periods to display period's 缠K序列
|
||||
// (in practice, this is done during data loading)
|
||||
for &周期 in &周期组 {
|
||||
if 周期 != 显示周期 {
|
||||
// Other periods will reference display period's 缠论K线序列
|
||||
// This is done during the K-line callback flow
|
||||
}
|
||||
}
|
||||
|
||||
let mut 合成K线计数 = HashMap::new();
|
||||
for &周期 in &周期组 {
|
||||
合成K线计数.insert(周期, 0);
|
||||
}
|
||||
|
||||
Self {
|
||||
周期组,
|
||||
输入周期,
|
||||
显示周期,
|
||||
K线合成器,
|
||||
单体分析器,
|
||||
合成K线计数,
|
||||
}
|
||||
}
|
||||
|
||||
/// 投喂K线 — 统一入口,接收最小周期K线
|
||||
pub fn 投喂K线(&mut self, 普K: K线) {
|
||||
if 普K.周期 != self.输入周期 {
|
||||
eprintln!(
|
||||
"立体分析器.投喂K线 周期不匹配 {} != {}",
|
||||
普K.周期, self.输入周期
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Record current K-line counts before feeding
|
||||
let mut 之前计数 = HashMap::new();
|
||||
for &周期 in &self.周期组 {
|
||||
之前计数.insert(周期, self.K线合成器.合成K线列表.get(&周期).map(|v| v.len()).unwrap_or(0));
|
||||
}
|
||||
|
||||
// Feed to synthesizer
|
||||
self.K线合成器.投喂K线(普K);
|
||||
|
||||
// Dispatch new K-lines to observers
|
||||
for &周期 in &self.周期组 {
|
||||
let 新K线列表 = self.K线合成器.合成K线列表.get(&周期);
|
||||
let 之前计数 = 之前计数[&周期];
|
||||
if let Some(列表) = 新K线列表 {
|
||||
for k线 in 列表.iter().skip(之前计数) {
|
||||
// Clone the K-line for feeding to observer
|
||||
let 完成K线 = k线.clone();
|
||||
if let Some(观察员) = self.单体分析器.get_mut(&周期) {
|
||||
观察员.增加原始K线(完成K线);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also feed the current in-progress K-line (if any)
|
||||
if let Some(Some(当前K线)) = self.K线合成器.当前K线.get(&周期).cloned() {
|
||||
if let Some(观察员) = self.单体分析器.get_mut(&周期) {
|
||||
// Feed a clone of current in-progress K-line for real-time updates
|
||||
// Note: this may cause duplicate data, matching Python behavior
|
||||
观察员.增加原始K线(当前K线);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定周期的观察者
|
||||
pub fn 获取观察者(&self, 周期: i64) -> Option<&观察者> {
|
||||
self.单体分析器.get(&周期)
|
||||
}
|
||||
|
||||
/// 获取指定周期的观察者(可变)
|
||||
pub fn 获取观察者_mut(&mut self, 周期: i64) -> Option<&mut 观察者> {
|
||||
self.单体分析器.get_mut(&周期)
|
||||
}
|
||||
|
||||
/// 测试_保存数据
|
||||
pub fn 测试_保存数据(&self) {
|
||||
for 周期 in &self.周期组 {
|
||||
if let Some(观察员) = self.单体分析器.get(周期) {
|
||||
观察员.测试_保存数据(None);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,503 @@
|
||||
use crate::algorithm::bi::笔;
|
||||
use crate::algorithm::hub::中枢;
|
||||
use crate::algorithm::segment::线段;
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::structure::dash_line::虚线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::相对方向;
|
||||
use crate::utils::datetime;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 观察者 — 单周期分析器,持有所有层级序列,接收K线流式输入后逐层计算
|
||||
pub struct 观察者 {
|
||||
pub 符号: String,
|
||||
pub 周期: i64,
|
||||
pub 配置: 缠论配置,
|
||||
|
||||
// K线序列
|
||||
pub 普通K线序列: Vec<Rc<K线>>,
|
||||
pub 缠论K线序列: Vec<Rc<缠论K线>>,
|
||||
|
||||
// 分型与笔
|
||||
pub 分型序列: Vec<Rc<分型>>,
|
||||
pub 笔序列: Vec<Rc<虚线>>,
|
||||
pub 笔_中枢序列: Vec<Rc<中枢>>,
|
||||
|
||||
// 线段
|
||||
pub 线段序列: Vec<Rc<虚线>>,
|
||||
pub 中枢序列: Vec<Rc<中枢>>,
|
||||
|
||||
// 扩展线段(笔级)
|
||||
pub 扩展线段序列: Vec<Rc<虚线>>,
|
||||
pub 扩展中枢序列: Vec<Rc<中枢>>,
|
||||
|
||||
// 扩展线段(线段级)
|
||||
pub 扩展线段序列_线段: Vec<Rc<虚线>>,
|
||||
pub 扩展中枢序列_线段: Vec<Rc<中枢>>,
|
||||
|
||||
// 线段之线段
|
||||
pub 线段_线段序列: Vec<Rc<虚线>>,
|
||||
pub 线段_中枢序列: Vec<Rc<中枢>>,
|
||||
|
||||
// 扩展线段之扩展线段
|
||||
pub 扩展线段序列_扩展线段: Vec<Rc<虚线>>,
|
||||
pub 扩展中枢序列_扩展线段: Vec<Rc<中枢>>,
|
||||
|
||||
// 终止时间戳
|
||||
终止时间戳: Option<i64>,
|
||||
}
|
||||
|
||||
impl 观察者 {
|
||||
pub fn new(符号: String, 周期: i64, 配置: 缠论配置) -> Self {
|
||||
let 终止时间戳 = if 配置.手动终止 != "1970-01-01 00:00:00"
|
||||
&& !配置.手动终止.is_empty()
|
||||
{
|
||||
datetime::转化为时间戳(&配置.手动终止)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut instance = Self {
|
||||
符号: 符号.clone(),
|
||||
周期,
|
||||
配置,
|
||||
普通K线序列: Vec::new(),
|
||||
缠论K线序列: Vec::new(),
|
||||
分型序列: Vec::new(),
|
||||
笔序列: Vec::new(),
|
||||
笔_中枢序列: Vec::new(),
|
||||
线段序列: Vec::new(),
|
||||
中枢序列: Vec::new(),
|
||||
扩展线段序列: Vec::new(),
|
||||
扩展中枢序列: Vec::new(),
|
||||
扩展线段序列_线段: Vec::new(),
|
||||
扩展中枢序列_线段: Vec::new(),
|
||||
线段_线段序列: Vec::new(),
|
||||
线段_中枢序列: Vec::new(),
|
||||
扩展线段序列_扩展线段: Vec::new(),
|
||||
扩展中枢序列_扩展线段: Vec::new(),
|
||||
终止时间戳,
|
||||
};
|
||||
instance.配置.标识 = 符号;
|
||||
instance
|
||||
}
|
||||
|
||||
/// 标识
|
||||
pub fn 标识(&self) -> String {
|
||||
format!("{}:{}", self.符号, self.周期)
|
||||
}
|
||||
|
||||
/// 当前K线
|
||||
pub fn 当前K线(&self) -> Option<&Rc<K线>> {
|
||||
self.普通K线序列.last()
|
||||
}
|
||||
|
||||
/// 当前缠K
|
||||
pub fn 当前缠K(&self) -> Option<&Rc<缠论K线>> {
|
||||
self.缠论K线序列.last()
|
||||
}
|
||||
|
||||
/// 重置基础序列
|
||||
pub fn 重置基础序列(&mut self) {
|
||||
self.普通K线序列.clear();
|
||||
self.缠论K线序列.clear();
|
||||
self.分型序列.clear();
|
||||
self.笔序列.clear();
|
||||
self.笔_中枢序列.clear();
|
||||
self.线段序列.clear();
|
||||
self.中枢序列.clear();
|
||||
self.扩展线段序列.clear();
|
||||
self.扩展中枢序列.clear();
|
||||
self.扩展线段序列_线段.clear();
|
||||
self.扩展中枢序列_线段.clear();
|
||||
self.线段_线段序列.clear();
|
||||
self.线段_中枢序列.clear();
|
||||
self.扩展线段序列_扩展线段.clear();
|
||||
self.扩展中枢序列_扩展线段.clear();
|
||||
}
|
||||
|
||||
/// 增加原始K线 — 单根K线投喂入口
|
||||
pub fn 增加原始K线(&mut self, 普K: K线) {
|
||||
if let Some(终止) = self.终止时间戳 {
|
||||
if 普K.时间戳 > 终止 {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.__处理数据(普K);
|
||||
}
|
||||
|
||||
/// 核心数据处理管道
|
||||
fn __处理数据(&mut self, mut 普K: K线) {
|
||||
// Step 1: 缠论K线分析 (普K is consumed by 分析 as &mut)
|
||||
let (_, 当前分型) = 缠论K线::分析(
|
||||
&mut 普K,
|
||||
&mut self.缠论K线序列,
|
||||
&mut self.普通K线序列,
|
||||
&self.配置,
|
||||
);
|
||||
let 当前分型 = match 当前分型 {
|
||||
Some(fx) => fx,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// Step 2: 笔分析
|
||||
if self.配置.分析笔 {
|
||||
笔::分析(
|
||||
当前分型,
|
||||
&mut self.分型序列,
|
||||
&mut self.笔序列,
|
||||
&self.缠论K线序列,
|
||||
&self.普通K线序列,
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
if self.分型序列.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 3: 笔中枢分析
|
||||
if self.配置.分析笔中枢 {
|
||||
中枢::分析(&self.笔序列, &mut self.笔_中枢序列, true, "", 0);
|
||||
}
|
||||
if self.笔序列.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
// Step 4: 线段分析
|
||||
if self.配置.分析线段 {
|
||||
线段::分析(
|
||||
&self.笔序列,
|
||||
&mut self.线段序列,
|
||||
&self.配置,
|
||||
0,
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
// Clone to avoid borrow issues
|
||||
let 线段序列_克隆 = self.线段序列.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(&线段序列_克隆, &mut self.中枢序列, true, "", 0);
|
||||
}
|
||||
|
||||
// Step 5: 扩展线段(笔级)
|
||||
let 笔序列_克隆 = self.笔序列.clone();
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(&笔序列_克隆, &mut self.扩展线段序列, &self.配置);
|
||||
}
|
||||
let 扩展线段序列_克隆 = self.扩展线段序列.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(&扩展线段序列_克隆, &mut self.扩展中枢序列, true, "", 0);
|
||||
}
|
||||
|
||||
// Step 6: 扩展线段(线段级)
|
||||
let 线段序列_克隆2 = self.线段序列.clone();
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(&线段序列_克隆2, &mut self.扩展线段序列_线段, &self.配置);
|
||||
}
|
||||
let 扩展线段_线段_克隆 = self.扩展线段序列_线段.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
&扩展线段_线段_克隆,
|
||||
&mut self.扩展中枢序列_线段,
|
||||
true,
|
||||
"",
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Step 7: 线段之线段
|
||||
let 线段序列_克隆3 = self.线段序列.clone();
|
||||
if self.配置.分析线段 {
|
||||
线段::分析(
|
||||
&线段序列_克隆3,
|
||||
&mut self.线段_线段序列,
|
||||
&self.配置,
|
||||
0,
|
||||
&[
|
||||
相对方向::向下,
|
||||
相对方向::向上,
|
||||
相对方向::顺,
|
||||
相对方向::逆,
|
||||
相对方向::同,
|
||||
],
|
||||
);
|
||||
}
|
||||
let 线段_线段_克隆 = self.线段_线段序列.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
&线段_线段_克隆,
|
||||
&mut self.线段_中枢序列,
|
||||
true,
|
||||
"",
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
// Step 8: 扩展线段之扩展线段
|
||||
let 扩展线段序列_克隆2 = self.扩展线段序列.clone();
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(
|
||||
&扩展线段序列_克隆2,
|
||||
&mut self.扩展线段序列_扩展线段,
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
let 扩展线段_扩展线段_克隆 = self.扩展线段序列_扩展线段.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
&扩展线段_扩展线段_克隆,
|
||||
&mut self.扩展中枢序列_扩展线段,
|
||||
true,
|
||||
"",
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 静态重新分析 — 遍历所有缠K重新生成分型/笔/线段
|
||||
pub fn 静态重新分析(&mut self) {
|
||||
self.分型序列.clear();
|
||||
self.笔序列.clear();
|
||||
self.笔_中枢序列.clear();
|
||||
self.线段序列.clear();
|
||||
self.中枢序列.clear();
|
||||
self.扩展线段序列.clear();
|
||||
self.扩展中枢序列.clear();
|
||||
self.扩展线段序列_线段.clear();
|
||||
self.扩展中枢序列_线段.clear();
|
||||
self.线段_线段序列.clear();
|
||||
self.线段_中枢序列.clear();
|
||||
self.扩展线段序列_扩展线段.clear();
|
||||
self.扩展中枢序列_扩展线段.clear();
|
||||
|
||||
let 缠K克隆 = self.缠论K线序列.clone();
|
||||
let 普K克隆 = self.普通K线序列.clone();
|
||||
|
||||
for i in 1..缠K克隆.len() - 1 {
|
||||
let 当前分型 = 分型::new(
|
||||
Some(Rc::clone(&缠K克隆[i - 1])),
|
||||
Rc::clone(&缠K克隆[i]),
|
||||
Some(Rc::clone(&缠K克隆[i + 1])),
|
||||
);
|
||||
笔::分析(
|
||||
Rc::new(当前分型),
|
||||
&mut self.分型序列,
|
||||
&mut self.笔序列,
|
||||
&缠K克隆,
|
||||
&普K克隆,
|
||||
&self.配置,
|
||||
);
|
||||
}
|
||||
|
||||
if self.配置.分析笔中枢 {
|
||||
中枢::分析(&self.笔序列, &mut self.笔_中枢序列, true, "", 0);
|
||||
}
|
||||
|
||||
let 笔克隆 = self.笔序列.clone();
|
||||
if self.配置.分析线段 {
|
||||
线段::分析(
|
||||
&笔克隆,
|
||||
&mut self.线段序列,
|
||||
&self.配置,
|
||||
0,
|
||||
&[相对方向::向上, 相对方向::向下],
|
||||
);
|
||||
}
|
||||
let 线段克隆 = self.线段序列.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(&线段克隆, &mut self.中枢序列, true, "", 0);
|
||||
}
|
||||
|
||||
let 笔克隆2 = self.笔序列.clone();
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(&笔克隆2, &mut self.扩展线段序列, &self.配置);
|
||||
}
|
||||
let 扩展线段克隆 = self.扩展线段序列.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(&扩展线段克隆, &mut self.扩展中枢序列, true, "", 0);
|
||||
}
|
||||
|
||||
let 线段克隆2 = self.线段序列.clone();
|
||||
if self.配置.分析扩展线段 {
|
||||
线段::扩展分析(&线段克隆2, &mut self.扩展线段序列_线段, &self.配置);
|
||||
}
|
||||
let 扩展线段_线段_克隆 = self.扩展线段序列_线段.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
&扩展线段_线段_克隆,
|
||||
&mut self.扩展中枢序列_线段,
|
||||
true,
|
||||
"",
|
||||
0,
|
||||
);
|
||||
}
|
||||
|
||||
let 线段克隆3 = self.线段序列.clone();
|
||||
if self.配置.分析线段 {
|
||||
线段::分析(
|
||||
&线段克隆3,
|
||||
&mut self.线段_线段序列,
|
||||
&self.配置,
|
||||
0,
|
||||
&[
|
||||
相对方向::向下,
|
||||
相对方向::向上,
|
||||
相对方向::顺,
|
||||
相对方向::逆,
|
||||
相对方向::同,
|
||||
],
|
||||
);
|
||||
}
|
||||
let 线段_线段_克隆 = self.线段_线段序列.clone();
|
||||
if self.配置.分析线段中枢 {
|
||||
中枢::分析(
|
||||
&线段_线段_克隆,
|
||||
&mut self.线段_中枢序列,
|
||||
true,
|
||||
"",
|
||||
0,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// 测试_保存数据 — 输出各序列数据文本到文件
|
||||
pub fn 测试_保存数据(&self, root: Option<&str>) {
|
||||
|
||||
let 笔序列_文本数据: Vec<String> = self
|
||||
.笔序列
|
||||
.iter()
|
||||
.map(|b| b.获取数据文本())
|
||||
.collect();
|
||||
let 线段序列_文本数据: Vec<String> = self
|
||||
.线段序列
|
||||
.iter()
|
||||
.map(|s| s.获取数据文本())
|
||||
.collect();
|
||||
let 扩展线段序列_数据文本: Vec<String> = self
|
||||
.扩展线段序列
|
||||
.iter()
|
||||
.map(|s| s.获取数据文本())
|
||||
.collect();
|
||||
let 扩展线段序列_线段_数据文本: Vec<String> = self
|
||||
.扩展线段序列_线段
|
||||
.iter()
|
||||
.map(|s| s.获取数据文本())
|
||||
.collect();
|
||||
let 线段_线段序列_数据文本: Vec<String> = self
|
||||
.线段_线段序列
|
||||
.iter()
|
||||
.map(|s| s.获取数据文本())
|
||||
.collect();
|
||||
let 扩展线段序列_扩展线段_数据文本: Vec<String> = self
|
||||
.扩展线段序列_扩展线段
|
||||
.iter()
|
||||
.map(|s| s.获取数据文本())
|
||||
.collect();
|
||||
|
||||
let 笔_中枢序列_数据文本: Vec<String> = self
|
||||
.笔_中枢序列
|
||||
.iter()
|
||||
.map(|h| h.获取数据文本())
|
||||
.collect();
|
||||
let 中枢序列_数据文本: Vec<String> = self
|
||||
.中枢序列
|
||||
.iter()
|
||||
.map(|h| h.获取数据文本())
|
||||
.collect();
|
||||
let 扩展中枢序列_数据文本: Vec<String> = self
|
||||
.扩展中枢序列
|
||||
.iter()
|
||||
.map(|h| h.获取数据文本())
|
||||
.collect();
|
||||
let 扩展中枢序列_线段_数据文本: Vec<String> = self
|
||||
.扩展中枢序列_线段
|
||||
.iter()
|
||||
.map(|h| h.获取数据文本())
|
||||
.collect();
|
||||
let 线段_中枢序列_数据文本: Vec<String> = self
|
||||
.线段_中枢序列
|
||||
.iter()
|
||||
.map(|h| h.获取数据文本())
|
||||
.collect();
|
||||
let 扩展中枢序列_扩展线段_数据文本: Vec<String> = self
|
||||
.扩展中枢序列_扩展线段
|
||||
.iter()
|
||||
.map(|h| h.获取数据文本())
|
||||
.collect();
|
||||
|
||||
// 确定根目录
|
||||
let 根目录 = match root {
|
||||
Some(r) => std::path::PathBuf::from(r),
|
||||
None => std::env::current_dir().unwrap_or_default(),
|
||||
};
|
||||
|
||||
// 生成子目录名称
|
||||
let 起始时间 = self.普通K线序列.first().map(|k| k.时间戳).unwrap_or(0);
|
||||
let 结束时间 = self.普通K线序列.last().map(|k| k.时间戳).unwrap_or(0);
|
||||
let 目录标识 = format!("Rust_{}_{}_{}", self.符号, 起始时间, 结束时间);
|
||||
|
||||
let 保存路径 = 根目录.join(&目录标识);
|
||||
if let Err(e) = std::fs::create_dir_all(&保存路径) {
|
||||
eprintln!("创建目录失败: {} -> {}", 保存路径.display(), e);
|
||||
return;
|
||||
}
|
||||
|
||||
let 数据映射: Vec<(&str, &[String])> = vec![
|
||||
("笔序列_文本数据", &笔序列_文本数据),
|
||||
("线段序列_文本数据", &线段序列_文本数据),
|
||||
("扩展线段序列_数据文本", &扩展线段序列_数据文本),
|
||||
("扩展线段序列_线段_数据文本", &扩展线段序列_线段_数据文本),
|
||||
("线段_线段序列_数据文本", &线段_线段序列_数据文本),
|
||||
("扩展线段序列_扩展线段_数据文本", &扩展线段序列_扩展线段_数据文本),
|
||||
("笔_中枢序列_数据文本", &笔_中枢序列_数据文本),
|
||||
("中枢序列_数据文本", &中枢序列_数据文本),
|
||||
("扩展中枢序列_数据文本", &扩展中枢序列_数据文本),
|
||||
("扩展中枢序列_线段_数据文本", &扩展中枢序列_线段_数据文本),
|
||||
("线段_中枢序列_数据文本", &线段_中枢序列_数据文本),
|
||||
("扩展中枢序列_扩展线段_数据文本", &扩展中枢序列_扩展线段_数据文本),
|
||||
];
|
||||
|
||||
for (文件名, 数据列表) in &数据映射 {
|
||||
let 文件路径 = 保存路径.join(format!("{}.txt", 文件名));
|
||||
let 内容 = 数据列表.join("\n") + "\n";
|
||||
if let Err(e) = std::fs::write(&文件路径, &内容) {
|
||||
eprintln!("写入文件失败: {} -> {}", 文件路径.display(), e);
|
||||
}
|
||||
}
|
||||
|
||||
println!("全部数据拆分保存完成,目录:{}", 保存路径.display());
|
||||
}
|
||||
|
||||
/// 读取数据文件 — 从 .nb 文件加载数据
|
||||
pub fn 读取数据文件(文件路径: &str, 配置: Option<缠论配置>) -> Result<Self, String> {
|
||||
let 配置 = 配置.unwrap_or_default();
|
||||
|
||||
// Parse filename: btcusd-300-1631772074-1632222374.nb
|
||||
let path = std::path::Path::new(文件路径);
|
||||
let name = path
|
||||
.file_stem()
|
||||
.and_then(|n| n.to_str())
|
||||
.ok_or("invalid filename")?;
|
||||
let parts: Vec<&str> = name.split('-').collect();
|
||||
if parts.len() < 4 {
|
||||
return Err(format!("invalid filename format: {}", name));
|
||||
}
|
||||
let 符号 = parts[0].to_string();
|
||||
let 周期: i64 = parts[1].parse().map_err(|e| format!("parse period: {}", e))?;
|
||||
|
||||
let mut 实例 = Self::new(符号, 周期, 配置);
|
||||
|
||||
let data = std::fs::read(文件路径).map_err(|e| format!("read file: {}", e))?;
|
||||
let size = 48; // 6 × 8 bytes (big-endian double)
|
||||
for i in 0..data.len() / size {
|
||||
let offset = i * size;
|
||||
if let Some(k线) = K线::from_bytes(&data[offset..offset + size], 周期, "nb") {
|
||||
实例.增加原始K线(k线);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(实例)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
use crate::kline::bar::K线;
|
||||
use std::collections::HashMap;
|
||||
|
||||
/// K线合成器 — 将小周期K线合成为大周期K线
|
||||
pub struct K线合成器 {
|
||||
pub 标识: String,
|
||||
pub 周期组: Vec<i64>,
|
||||
pub 当前K线: HashMap<i64, Option<K线>>,
|
||||
pub 合成K线列表: HashMap<i64, Vec<K线>>,
|
||||
}
|
||||
|
||||
impl K线合成器 {
|
||||
pub fn new(标识: String, 周期组: Vec<i64>) -> Self {
|
||||
let mut 周期组 = 周期组;
|
||||
周期组.sort();
|
||||
|
||||
let mut 当前K线 = HashMap::new();
|
||||
let mut 合成K线列表 = HashMap::new();
|
||||
for &周期 in &周期组 {
|
||||
当前K线.insert(周期, None);
|
||||
合成K线列表.insert(周期, Vec::new());
|
||||
}
|
||||
|
||||
Self {
|
||||
标识,
|
||||
周期组,
|
||||
当前K线,
|
||||
合成K线列表,
|
||||
}
|
||||
}
|
||||
|
||||
/// 投喂K线 — 输入最小周期K线,合成为所有目标周期
|
||||
pub fn 投喂K线(&mut self, 普K: K线) {
|
||||
let 周期组 = self.周期组.clone();
|
||||
for 周期 in 周期组 {
|
||||
self._处理单个周期(周期, &普K);
|
||||
}
|
||||
}
|
||||
|
||||
fn _处理单个周期(&mut self, 周期: i64, 普K: &K线) {
|
||||
let 目标时间戳 = self._对齐时间戳(普K.时间戳, 周期);
|
||||
let 当前K线 = self.当前K线.get(&周期).unwrap().clone();
|
||||
|
||||
if 当前K线.is_none() {
|
||||
// 创建新K线
|
||||
let 新K线 = self._创建新K线(周期, 目标时间戳, 普K);
|
||||
*self.当前K线.get_mut(&周期).unwrap() = Some(新K线);
|
||||
} else if 当前K线.as_ref().unwrap().时间戳 == 目标时间戳 {
|
||||
// 更新当前K线
|
||||
let mut k线 = 当前K线.unwrap();
|
||||
self._更新K线(&mut k线, 普K);
|
||||
*self.当前K线.get_mut(&周期).unwrap() = Some(k线);
|
||||
} else {
|
||||
// 完成当前K线,创建新K线
|
||||
self._完成K线(周期);
|
||||
let 新K线 = self._创建新K线(周期, 目标时间戳, 普K);
|
||||
*self.当前K线.get_mut(&周期).unwrap() = Some(新K线);
|
||||
}
|
||||
}
|
||||
|
||||
fn _对齐时间戳(&self, 时间戳: i64, 周期: i64) -> i64 {
|
||||
if 周期 == 0 {
|
||||
return 时间戳;
|
||||
}
|
||||
(时间戳 / 周期) * 周期
|
||||
}
|
||||
|
||||
fn _创建新K线(&self, 周期: i64, 时间戳: i64, 普K: &K线) -> K线 {
|
||||
let 序号 = self
|
||||
.合成K线列表
|
||||
.get(&周期)
|
||||
.and_then(|list| list.last())
|
||||
.map(|k| k.序号 + 1)
|
||||
.unwrap_or(0);
|
||||
|
||||
K线::创建普K(
|
||||
&self.标识,
|
||||
时间戳,
|
||||
普K.开盘价,
|
||||
普K.高,
|
||||
普K.低,
|
||||
普K.收盘价,
|
||||
普K.成交量,
|
||||
序号,
|
||||
周期,
|
||||
)
|
||||
}
|
||||
|
||||
fn _更新K线(&self, 当前K线: &mut K线, 新数据: &K线) {
|
||||
当前K线.高 = 当前K线.高.max(新数据.高);
|
||||
当前K线.低 = 当前K线.低.min(新数据.低);
|
||||
当前K线.收盘价 = 新数据.收盘价;
|
||||
当前K线.成交量 += 新数据.成交量;
|
||||
}
|
||||
|
||||
fn _完成K线(&mut self, 周期: i64) {
|
||||
let 当前K线 = self.当前K线.get(&周期).and_then(|k| k.clone());
|
||||
if let Some(mut k线) = 当前K线 {
|
||||
k线.序号 = self
|
||||
.合成K线列表
|
||||
.get(&周期)
|
||||
.and_then(|list| list.last())
|
||||
.map(|k| k.序号 + 1)
|
||||
.unwrap_or(0);
|
||||
|
||||
self.合成K线列表.get_mut(&周期).unwrap().push(k线);
|
||||
*self.当前K线.get_mut(&周期).unwrap() = None;
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取指定周期当前正在合成的K线
|
||||
pub fn 获取当前K线(&self, 周期: i64) -> Option<&K线> {
|
||||
self.当前K线.get(&周期).and_then(|k| k.as_ref())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,338 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
fn is_infinite_f64(v: &f64) -> bool {
|
||||
v.is_infinite()
|
||||
}
|
||||
|
||||
/// 缠论配置 —— 控制所有分析阶段的行为
|
||||
///
|
||||
/// 所有字段带默认值,使用 `#[serde(default)]` 实现缺失字段容错
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 缠论配置 {
|
||||
// ---- 基础 ----
|
||||
pub 标识: String,
|
||||
|
||||
// ---- 缠K ----
|
||||
pub 缠K合并替换: bool,
|
||||
|
||||
// ---- 笔 ----
|
||||
pub 笔内元素数量: i64,
|
||||
pub 笔内相同终点取舍: bool,
|
||||
pub 笔内起始分型包含整笔: bool,
|
||||
pub 笔内起始分型包含整笔_包括右: bool,
|
||||
pub 笔内原始K线包含整笔: bool,
|
||||
pub 笔次级成笔: bool,
|
||||
pub 笔弱化: bool,
|
||||
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 指标计算方式: String,
|
||||
|
||||
// ---- MACD ----
|
||||
pub 平滑异同移动平均线_快线周期: i64,
|
||||
pub 平滑异同移动平均线_慢线周期: i64,
|
||||
pub 平滑异同移动平均线_信号周期: i64,
|
||||
|
||||
// ---- RSI ----
|
||||
pub 相对强弱指数_周期: i64,
|
||||
pub 相对强弱指数_移动平均线周期: i64,
|
||||
pub 相对强弱指数_超买阈值: f64,
|
||||
pub 相对强弱指数_超卖阈值: f64,
|
||||
|
||||
// ---- KDJ ----
|
||||
pub 随机指标_RSV周期: i64,
|
||||
pub 随机指标_K值平滑周期: i64,
|
||||
pub 随机指标_D值平滑周期: i64,
|
||||
pub 随机指标_超买阈值: f64,
|
||||
pub 随机指标_超卖阈值: f64,
|
||||
|
||||
// ---- 推送/显示 ----
|
||||
pub 图表展示: bool,
|
||||
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,
|
||||
pub 买卖点与MACD柱强相关: bool,
|
||||
pub 买卖点错过误差值: f64,
|
||||
pub 买卖点_指标模式: String,
|
||||
pub 买卖点_指标匹配_MACD: bool,
|
||||
pub 买卖点_指标匹配_KDJ: bool,
|
||||
pub 买卖点_指标匹配_RSI: bool,
|
||||
#[serde(skip_serializing_if = "is_infinite_f64")]
|
||||
pub 买卖点_背离率: f64,
|
||||
pub 买卖点_T2_回调阈值: f64,
|
||||
pub 买卖点_T2S_最大层级: i64,
|
||||
pub 买卖点_峰值条件: bool,
|
||||
pub 买卖点_计算方式: String,
|
||||
pub 买卖点_计算线段BSP1: bool,
|
||||
pub 买卖点_处理BSP2: bool,
|
||||
pub 买卖点_计算线段BSP3: bool,
|
||||
pub 买卖点_依赖T1: bool,
|
||||
pub 买卖点_中枢来源: String,
|
||||
pub 买卖点_调试输出: bool,
|
||||
|
||||
// ---- 背驰 ----
|
||||
pub 线段内部背驰_MACD: bool,
|
||||
pub 线段内部背驰_斜率: bool,
|
||||
pub 线段内部背驰_测度: bool,
|
||||
pub 线段内部背驰_模式: String,
|
||||
|
||||
// ---- 文件 ----
|
||||
pub 加载文件路径: String,
|
||||
}
|
||||
|
||||
impl Default for 缠论配置 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
标识: "bar".into(),
|
||||
缠K合并替换: false,
|
||||
笔内元素数量: 5,
|
||||
笔内相同终点取舍: false,
|
||||
笔内起始分型包含整笔: false,
|
||||
笔内起始分型包含整笔_包括右: false,
|
||||
笔内原始K线包含整笔: false,
|
||||
笔次级成笔: false,
|
||||
笔弱化: false,
|
||||
笔弱化_原始数量: 3,
|
||||
线段_非缺口下穿刺: false,
|
||||
线段_特征序列忽视老阴老阳: false,
|
||||
线段_缺口后紧急修正: true,
|
||||
线段_修正: false,
|
||||
线段内部中枢图显: true,
|
||||
扩展线段_当下分析: false,
|
||||
分析笔: true,
|
||||
分析线段: true,
|
||||
分析扩展线段: true,
|
||||
分析笔中枢: true,
|
||||
分析线段中枢: true,
|
||||
手动终止: String::new(),
|
||||
计算指标: true,
|
||||
指标计算方式: "收".into(),
|
||||
平滑异同移动平均线_快线周期: 13,
|
||||
平滑异同移动平均线_慢线周期: 31,
|
||||
平滑异同移动平均线_信号周期: 11,
|
||||
相对强弱指数_周期: 13,
|
||||
相对强弱指数_移动平均线周期: 13,
|
||||
相对强弱指数_超买阈值: 75.0,
|
||||
相对强弱指数_超卖阈值: 25.0,
|
||||
随机指标_RSV周期: 13,
|
||||
随机指标_K值平滑周期: 5,
|
||||
随机指标_D值平滑周期: 5,
|
||||
随机指标_超买阈值: 80.0,
|
||||
随机指标_超卖阈值: 20.0,
|
||||
图表展示: true,
|
||||
推送K线: true,
|
||||
推送笔: true,
|
||||
推送线段: true,
|
||||
推送中枢: true,
|
||||
图表展示_笔: true,
|
||||
图表展示_线段: true,
|
||||
图表展示_扩展线段: true,
|
||||
图表展示_扩展线段_线段: true,
|
||||
图表展示_线段_线段: true,
|
||||
图表展示_中枢_笔: true,
|
||||
图表展示_中枢_线段: true,
|
||||
图表展示_中枢_扩展线段: true,
|
||||
图表展示_中枢_扩展线段_线段: true,
|
||||
图表展示_中枢_线段_线段: true,
|
||||
图表展示_中枢_线段内部: true,
|
||||
买卖点偏移: 1,
|
||||
买卖点激进识别: false,
|
||||
买卖点与MACD柱强相关: false,
|
||||
买卖点错过误差值: 0.01,
|
||||
买卖点_指标模式: "配置".into(),
|
||||
买卖点_指标匹配_MACD: true,
|
||||
买卖点_指标匹配_KDJ: true,
|
||||
买卖点_指标匹配_RSI: true,
|
||||
买卖点_背离率: f64::INFINITY,
|
||||
买卖点_T2_回调阈值: 1.0,
|
||||
买卖点_T2S_最大层级: 3,
|
||||
买卖点_峰值条件: false,
|
||||
买卖点_计算方式: "峰".into(),
|
||||
买卖点_计算线段BSP1: true,
|
||||
买卖点_处理BSP2: true,
|
||||
买卖点_计算线段BSP3: true,
|
||||
买卖点_依赖T1: true,
|
||||
买卖点_中枢来源: "合".into(),
|
||||
买卖点_调试输出: false,
|
||||
线段内部背驰_MACD: true,
|
||||
线段内部背驰_斜率: true,
|
||||
线段内部背驰_测度: true,
|
||||
线段内部背驰_模式: "相对".into(),
|
||||
加载文件路径: "./templates/last.nb".into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 缠论配置 {
|
||||
pub fn to_json(&self) -> String {
|
||||
serde_json::to_string_pretty(self).unwrap_or_default()
|
||||
}
|
||||
|
||||
pub fn from_json(json_str: &str) -> Result<Self, serde_json::Error> {
|
||||
serde_json::from_str(json_str)
|
||||
}
|
||||
|
||||
pub fn 保存配置(&self, path: &str) -> std::io::Result<()> {
|
||||
std::fs::write(path, self.to_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)?;
|
||||
Ok(config)
|
||||
}
|
||||
|
||||
/// 返回一个关闭所有推送/显示的新配置
|
||||
pub fn 不推送(&self) -> Self {
|
||||
Self {
|
||||
图表展示: false,
|
||||
推送K线: false,
|
||||
推送笔: false,
|
||||
推送线段: false,
|
||||
推送中枢: false,
|
||||
图表展示_笔: false,
|
||||
图表展示_线段: false,
|
||||
图表展示_扩展线段: false,
|
||||
图表展示_扩展线段_线段: false,
|
||||
图表展示_线段_线段: false,
|
||||
图表展示_中枢_笔: false,
|
||||
图表展示_中枢_线段: false,
|
||||
图表展示_中枢_扩展线段: false,
|
||||
图表展示_中枢_扩展线段_线段: false,
|
||||
图表展示_中枢_线段_线段: false,
|
||||
图表展示_中枢_线段内部: false,
|
||||
..self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// 按序号重组字典 — 兼容旧版配置的复合key格式
|
||||
pub fn 按序号重组字典(默认配置: &Self, 原始字典: &serde_json::Value) -> Vec<(i64, Self)> {
|
||||
let mut result = Vec::new();
|
||||
if let serde_json::Value::Object(map) = 原始字典 {
|
||||
// 按数字前缀分组: "1_open" → group 1 key "open"
|
||||
let mut groups: std::collections::BTreeMap<i64, 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());
|
||||
}
|
||||
}
|
||||
}
|
||||
for (num, fields) in groups {
|
||||
let mut config = 默认配置.clone();
|
||||
if let Ok(partial) = serde_json::from_value::<缠论配置>(serde_json::Value::Object(fields)) {
|
||||
// merge partial into config (override matching fields)
|
||||
config = partial;
|
||||
}
|
||||
result.push((num, config));
|
||||
}
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
/// 对比两个配置,返回差异字段
|
||||
pub fn 对比(&self, other: &Self) -> Vec<String> {
|
||||
let mut diffs = Vec::new();
|
||||
let self_json = serde_json::to_value(self).unwrap();
|
||||
let other_json = serde_json::to_value(other).unwrap();
|
||||
if let (serde_json::Value::Object(self_map), serde_json::Value::Object(other_map)) =
|
||||
(&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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
diffs
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_default_config_roundtrip() {
|
||||
let config = 缠论配置::default();
|
||||
let json = config.to_json();
|
||||
let parsed: 缠论配置 = 缠论配置::from_json(&json).unwrap();
|
||||
let json2 = parsed.to_json();
|
||||
assert_eq!(json, json2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_default_values() {
|
||||
let config = 缠论配置::default();
|
||||
assert_eq!(config.标识, "bar");
|
||||
assert_eq!(config.笔内元素数量, 5);
|
||||
assert!(config.买卖点_背离率.is_infinite());
|
||||
assert_eq!(config.指标计算方式, "收");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_partial_deserialize() {
|
||||
let json = r#"{"标识": "custom", "笔内元素数量": 7}"#;
|
||||
let config: 缠论配置 = serde_json::from_str(json).unwrap();
|
||||
assert_eq!(config.标识, "custom");
|
||||
assert_eq!(config.笔内元素数量, 7);
|
||||
// 未指定字段使用默认值
|
||||
assert_eq!(config.买卖点偏移, 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_不推送() {
|
||||
let config = 缠论配置::default();
|
||||
let muted = config.不推送();
|
||||
assert!(!muted.推送K线);
|
||||
assert!(!muted.推送笔);
|
||||
assert!(!muted.图表展示);
|
||||
// 其他字段不变
|
||||
assert_eq!(muted.笔内元素数量, 5);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,204 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 随机指标 (KDJ)
|
||||
///
|
||||
/// 使用滑动窗口 + 逐值平滑进行增量计算
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 随机指标 {
|
||||
pub 时间戳: i64,
|
||||
pub 最高价: f64,
|
||||
pub 最低价: f64,
|
||||
pub 收盘价: f64,
|
||||
pub N: i64,
|
||||
pub M1: i64,
|
||||
pub M2: i64,
|
||||
pub 超买阈值: f64,
|
||||
pub 超卖阈值: f64,
|
||||
pub RSV: Option<f64>,
|
||||
pub K: Option<f64>,
|
||||
pub D: Option<f64>,
|
||||
pub J: Option<f64>,
|
||||
pub 历史最高价队列: Vec<f64>,
|
||||
pub 历史最低价队列: Vec<f64>,
|
||||
pub 前一个RSV: Option<f64>,
|
||||
pub 前一个K: Option<f64>,
|
||||
pub 前一个D: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for 随机指标 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
最高价: 0.0,
|
||||
最低价: 0.0,
|
||||
收盘价: 0.0,
|
||||
N: 9,
|
||||
M1: 3,
|
||||
M2: 3,
|
||||
超买阈值: 80.0,
|
||||
超卖阈值: 20.0,
|
||||
RSV: None,
|
||||
K: None,
|
||||
D: None,
|
||||
J: None,
|
||||
历史最高价队列: Vec::new(),
|
||||
历史最低价队列: Vec::new(),
|
||||
前一个RSV: None,
|
||||
前一个K: None,
|
||||
前一个D: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 随机指标 {
|
||||
/// 首次计算 KDJ(无历史数据时)
|
||||
pub fn 首次计算(
|
||||
初始最高价: f64,
|
||||
初始最低价: f64,
|
||||
初始收盘价: f64,
|
||||
初始时间: i64,
|
||||
N: i64,
|
||||
M1: i64,
|
||||
M2: i64,
|
||||
超买阈值: f64,
|
||||
超卖阈值: f64,
|
||||
) -> Self {
|
||||
Self {
|
||||
时间戳: 初始时间,
|
||||
最高价: 初始最高价,
|
||||
最低价: 初始最低价,
|
||||
收盘价: 初始收盘价,
|
||||
N,
|
||||
M1,
|
||||
M2,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSV: None,
|
||||
K: None,
|
||||
D: None,
|
||||
J: None,
|
||||
历史最高价队列: vec![初始最高价],
|
||||
历史最低价队列: vec![初始最低价],
|
||||
前一个RSV: None,
|
||||
前一个K: None,
|
||||
前一个D: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 基于前一个 KDJ 增量计算当前 KDJ
|
||||
pub fn 增量计算(前一个KDJ: &Self, 当前最高价: f64, 当前最低价: f64, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
let N = 前一个KDJ.N;
|
||||
let M1 = 前一个KDJ.M1;
|
||||
let M2 = 前一个KDJ.M2;
|
||||
let 超买阈值 = 前一个KDJ.超买阈值;
|
||||
let 超卖阈值 = 前一个KDJ.超卖阈值;
|
||||
|
||||
// 更新历史最高价队列
|
||||
let mut 历史最高价 = 前一个KDJ.历史最高价队列.clone();
|
||||
历史最高价.push(当前最高价);
|
||||
if 历史最高价.len() > N as usize {
|
||||
历史最高价.remove(0);
|
||||
}
|
||||
|
||||
// 更新历史最低价队列
|
||||
let mut 历史最低价 = 前一个KDJ.历史最低价队列.clone();
|
||||
历史最低价.push(当前最低价);
|
||||
if 历史最低价.len() > N as usize {
|
||||
历史最低价.remove(0);
|
||||
}
|
||||
|
||||
// RSV
|
||||
let RSV = if 历史最高价.len() == N as usize && 历史最低价.len() == N as usize {
|
||||
let highest = 历史最高价.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
|
||||
let lowest = 历史最低价.iter().cloned().fold(f64::INFINITY, f64::min);
|
||||
if (highest - lowest).abs() > f64::EPSILON {
|
||||
Some((当前收盘价 - lowest) / (highest - lowest) * 100.0)
|
||||
} else {
|
||||
Some(50.0)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
// K值
|
||||
let K = match RSV {
|
||||
Some(rsv) => match 前一个KDJ.K {
|
||||
None => Some(rsv),
|
||||
Some(prev_k) => Some((prev_k * (M1 - 1) as f64 + rsv) / M1 as f64),
|
||||
},
|
||||
None => 前一个KDJ.K,
|
||||
};
|
||||
|
||||
// D值
|
||||
let D = match K {
|
||||
Some(k) => match 前一个KDJ.D {
|
||||
None => Some(k),
|
||||
Some(prev_d) => Some((prev_d * (M2 - 1) as f64 + k) / M2 as f64),
|
||||
},
|
||||
None => 前一个KDJ.D,
|
||||
};
|
||||
|
||||
// J值
|
||||
let J = match (K, D) {
|
||||
(Some(k), Some(d)) => Some(3.0 * k - 2.0 * d),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
Self {
|
||||
时间戳: 当前时间,
|
||||
最高价: 当前最高价,
|
||||
最低价: 当前最低价,
|
||||
收盘价: 当前收盘价,
|
||||
N,
|
||||
M1,
|
||||
M2,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSV,
|
||||
K,
|
||||
D,
|
||||
J,
|
||||
历史最高价队列: 历史最高价,
|
||||
历史最低价队列: 历史最低价,
|
||||
前一个RSV: RSV,
|
||||
前一个K: K,
|
||||
前一个D: D,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let kdj = 随机指标::首次计算(110.0, 90.0, 100.0, 1000, 9, 3, 3, 80.0, 20.0);
|
||||
assert_eq!(kdj.历史最高价队列, vec![110.0]);
|
||||
assert_eq!(kdj.历史最低价队列, vec![90.0]);
|
||||
assert_eq!(kdj.K, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_after_n_bars() {
|
||||
let mut kdj = 随机指标::首次计算(110.0, 90.0, 100.0, 1000, 5, 3, 3, 80.0, 20.0);
|
||||
|
||||
// 喂入足够数据填充窗口
|
||||
let data = [
|
||||
(112.0, 91.0, 105.0),
|
||||
(115.0, 93.0, 110.0),
|
||||
(113.0, 95.0, 108.0),
|
||||
(116.0, 98.0, 112.0),
|
||||
(118.0, 100.0, 115.0),
|
||||
];
|
||||
for (i, (高, 低, 收)) in data.iter().enumerate() {
|
||||
kdj = 随机指标::增量计算(&kdj, *高, *低, *收, 1001 + i as i64);
|
||||
}
|
||||
|
||||
// 窗口填满后 KDJ 应有值
|
||||
assert!(kdj.K.is_some());
|
||||
assert!(kdj.D.is_some());
|
||||
assert!(kdj.J.is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,149 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 平滑异同移动平均线 (MACD)
|
||||
///
|
||||
/// 使用 EMA 递推算法进行增量计算
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 平滑异同移动平均线 {
|
||||
pub 时间戳: i64,
|
||||
pub 收盘价: f64,
|
||||
pub 快线周期: i64,
|
||||
pub 慢线周期: i64,
|
||||
pub 信号周期: i64,
|
||||
pub DIF: Option<f64>,
|
||||
pub DEA: Option<f64>,
|
||||
#[serde(rename = "MACD柱")]
|
||||
#[serde(default)]
|
||||
pub MACD柱: f64,
|
||||
pub 快线EMA: Option<f64>,
|
||||
pub 慢线EMA: Option<f64>,
|
||||
pub DEA_EMA: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for 平滑异同移动平均线 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
收盘价: 0.0,
|
||||
快线周期: 12,
|
||||
慢线周期: 26,
|
||||
信号周期: 9,
|
||||
DIF: None,
|
||||
DEA: None,
|
||||
MACD柱: 0.0,
|
||||
快线EMA: None,
|
||||
慢线EMA: None,
|
||||
DEA_EMA: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn 平滑系数(周期: i64) -> f64 {
|
||||
2.0 / (周期 as f64 + 1.0)
|
||||
}
|
||||
|
||||
impl 平滑异同移动平均线 {
|
||||
/// 首次计算 MACD 指标(无历史数据时使用)
|
||||
pub fn 首次计算(初始收盘价: f64, 初始时间: i64, 快线周期: i64, 慢线周期: i64, 信号周期: i64) -> Self {
|
||||
let 快线EMA = 初始收盘价;
|
||||
let 慢线EMA = 初始收盘价;
|
||||
let DIF = 快线EMA - 慢线EMA;
|
||||
let DEA_EMA = DIF;
|
||||
let MACD柱 = DIF - DEA_EMA;
|
||||
|
||||
Self {
|
||||
时间戳: 初始时间,
|
||||
收盘价: 初始收盘价,
|
||||
快线周期,
|
||||
慢线周期,
|
||||
信号周期,
|
||||
DIF: Some(DIF),
|
||||
DEA: Some(DEA_EMA),
|
||||
MACD柱,
|
||||
快线EMA: Some(快线EMA),
|
||||
慢线EMA: Some(慢线EMA),
|
||||
DEA_EMA: Some(DEA_EMA),
|
||||
}
|
||||
}
|
||||
|
||||
/// 基于前一个 MACD 指标增量计算当前 MACD
|
||||
pub fn 增量计算(前一个MACD: &Self, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
// 快线 EMA
|
||||
let 快线EMA = match 前一个MACD.快线EMA {
|
||||
Some(prev) => {
|
||||
当前收盘价 * 平滑系数(前一个MACD.快线周期)
|
||||
+ prev * ((前一个MACD.快线周期 - 1) as f64 / (前一个MACD.快线周期 + 1) as f64)
|
||||
}
|
||||
None => 当前收盘价,
|
||||
};
|
||||
|
||||
// 慢线 EMA
|
||||
let 慢线EMA = match 前一个MACD.慢线EMA {
|
||||
Some(prev) => {
|
||||
当前收盘价 * 平滑系数(前一个MACD.慢线周期)
|
||||
+ prev * ((前一个MACD.慢线周期 - 1) as f64 / (前一个MACD.慢线周期 + 1) as f64)
|
||||
}
|
||||
None => 当前收盘价,
|
||||
};
|
||||
|
||||
// DIF
|
||||
let DIF = 快线EMA - 慢线EMA;
|
||||
|
||||
// DEA_EMA
|
||||
let DEA_EMA = match 前一个MACD.DEA_EMA {
|
||||
Some(prev) => {
|
||||
DIF * 平滑系数(前一个MACD.信号周期)
|
||||
+ prev * ((前一个MACD.信号周期 - 1) as f64 / (前一个MACD.信号周期 + 1) as f64)
|
||||
}
|
||||
None => DIF,
|
||||
};
|
||||
|
||||
// MACD 柱 (注意: Python 版没有 ×2)
|
||||
let MACD柱 = DIF - DEA_EMA;
|
||||
|
||||
Self {
|
||||
时间戳: 当前时间,
|
||||
收盘价: 当前收盘价,
|
||||
快线周期: 前一个MACD.快线周期,
|
||||
慢线周期: 前一个MACD.慢线周期,
|
||||
信号周期: 前一个MACD.信号周期,
|
||||
DIF: Some(DIF),
|
||||
DEA: Some(DEA_EMA),
|
||||
MACD柱,
|
||||
快线EMA: Some(快线EMA),
|
||||
慢线EMA: Some(慢线EMA),
|
||||
DEA_EMA: Some(DEA_EMA),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let macd = 平滑异同移动平均线::首次计算(100.0, 1000, 12, 26, 9);
|
||||
assert_eq!(macd.DIF, Some(0.0));
|
||||
assert_eq!(macd.MACD柱, 0.0);
|
||||
assert_eq!(macd.快线EMA, Some(100.0));
|
||||
assert_eq!(macd.慢线EMA, Some(100.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_calc() {
|
||||
let first = 平滑异同移动平均线::首次计算(100.0, 1000, 12, 26, 9);
|
||||
|
||||
// 价格上升
|
||||
let second = 平滑异同移动平均线::增量计算(&first, 102.0, 1001);
|
||||
assert!(second.DIF.unwrap() > 0.0);
|
||||
// 快线EMA 应该比慢线EMA 变化更快
|
||||
assert!(second.快线EMA.unwrap() > second.慢线EMA.unwrap());
|
||||
|
||||
// 价格下降
|
||||
let third = 平滑异同移动平均线::增量计算(&second, 98.0, 1002);
|
||||
// DIF 应该变小
|
||||
assert!(third.DIF.unwrap() < second.DIF.unwrap());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
pub mod macd;
|
||||
pub mod kdj;
|
||||
pub mod rsi;
|
||||
|
||||
pub use macd::平滑异同移动平均线;
|
||||
pub use kdj::随机指标;
|
||||
pub use rsi::相对强弱指数;
|
||||
|
||||
/// K线取值 —— 根据计算方式从K线提取对应的价格
|
||||
pub fn K线取值(开盘价: f64, 高: f64, 低: f64, 收盘价: f64, 计算方式: &str) -> f64 {
|
||||
match 计算方式 {
|
||||
"开" => 开盘价,
|
||||
"高" => 高,
|
||||
"低" => 低,
|
||||
"收" => 收盘价,
|
||||
"高低均值" => (高 + 低) / 2.0,
|
||||
"高低收均值" => (高 + 低 + 收盘价) / 3.0,
|
||||
"开高低收均值" => (高 + 低 + 开盘价 + 收盘价) / 4.0,
|
||||
_ => 收盘价,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 相对强弱指数 (RSI)
|
||||
///
|
||||
/// 使用 Wilder 平滑(RMA)进行增量计算
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct 相对强弱指数 {
|
||||
pub 时间戳: i64,
|
||||
pub 收盘价: f64,
|
||||
pub 周期: i64,
|
||||
pub 超买阈值: f64,
|
||||
pub 超卖阈值: f64,
|
||||
pub RSI_SMA周期: Option<i64>,
|
||||
pub RSI: Option<f64>,
|
||||
pub 平均上涨: Option<f64>,
|
||||
pub 平均下跌: Option<f64>,
|
||||
pub 上涨幅度: f64,
|
||||
pub 下跌幅度: f64,
|
||||
pub 平滑系数: f64,
|
||||
pub RSI_SMA: Option<f64>,
|
||||
pub RSI历史队列: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Default for 相对强弱指数 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
时间戳: 0,
|
||||
收盘价: 0.0,
|
||||
周期: 14,
|
||||
超买阈值: 70.0,
|
||||
超卖阈值: 30.0,
|
||||
RSI_SMA周期: None,
|
||||
RSI: None,
|
||||
平均上涨: None,
|
||||
平均下跌: None,
|
||||
上涨幅度: 0.0,
|
||||
下跌幅度: 0.0,
|
||||
平滑系数: 0.0,
|
||||
RSI_SMA: None,
|
||||
RSI历史队列: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 相对强弱指数 {
|
||||
/// 首次计算 RSI(历史数据不足时)
|
||||
pub fn 首次计算(
|
||||
初始收盘价: f64,
|
||||
初始时间: i64,
|
||||
周期: i64,
|
||||
超买阈值: f64,
|
||||
超卖阈值: f64,
|
||||
RSI_SMA周期: Option<i64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
时间戳: 初始时间,
|
||||
收盘价: 初始收盘价,
|
||||
周期,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSI_SMA周期,
|
||||
RSI: None,
|
||||
平均上涨: Some(0.0),
|
||||
平均下跌: Some(0.0),
|
||||
上涨幅度: 0.0,
|
||||
下跌幅度: 0.0,
|
||||
平滑系数: 1.0 / 周期 as f64,
|
||||
RSI_SMA: None,
|
||||
RSI历史队列: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 基于前一个 RSI 增量计算当前 RSI
|
||||
pub fn 增量计算(前一个RSI: &Self, 当前收盘价: f64, 当前时间: i64) -> Self {
|
||||
let 周期 = 前一个RSI.周期;
|
||||
let 超买阈值 = 前一个RSI.超买阈值;
|
||||
let 超卖阈值 = 前一个RSI.超卖阈值;
|
||||
let RSI_SMA周期 = 前一个RSI.RSI_SMA周期;
|
||||
let 平滑系数 = 1.0 / 周期 as f64;
|
||||
|
||||
// 价格变化
|
||||
let 变化 = 当前收盘价 - 前一个RSI.收盘价;
|
||||
let 上涨 = 变化.max(0.0);
|
||||
let 下跌 = (-变化).max(0.0);
|
||||
|
||||
// Wilder 平滑
|
||||
let (平均上涨, 平均下跌) = match (前一个RSI.平均上涨, 前一个RSI.平均下跌) {
|
||||
(Some(prev_up), Some(prev_down)) => {
|
||||
let avg_up = prev_up * (1.0 - 平滑系数) + 上涨 * 平滑系数;
|
||||
let avg_down = prev_down * (1.0 - 平滑系数) + 下跌 * 平滑系数;
|
||||
(avg_up, avg_down)
|
||||
}
|
||||
_ => (上涨, 下跌),
|
||||
};
|
||||
|
||||
// RSI
|
||||
let RSI = if 平均下跌 == 0.0 {
|
||||
if 平均上涨 > 0.0 {
|
||||
100.0
|
||||
} else {
|
||||
50.0
|
||||
}
|
||||
} else {
|
||||
let RS = 平均上涨 / 平均下跌;
|
||||
100.0 - (100.0 / (1.0 + RS))
|
||||
};
|
||||
|
||||
// RSI_SMA
|
||||
let (RSI_SMA, RSI历史队列) = match RSI_SMA周期 {
|
||||
Some(sma周期) if sma周期 > 0 => {
|
||||
let mut 队列 = 前一个RSI.RSI历史队列.clone();
|
||||
队列.push(RSI);
|
||||
if 队列.len() > sma周期 as usize {
|
||||
队列.remove(0);
|
||||
}
|
||||
let sma = if 队列.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(队列.iter().sum::<f64>() / 队列.len() as f64)
|
||||
};
|
||||
(sma, 队列)
|
||||
}
|
||||
_ => (None, Vec::new()),
|
||||
};
|
||||
|
||||
Self {
|
||||
时间戳: 当前时间,
|
||||
收盘价: 当前收盘价,
|
||||
周期,
|
||||
超买阈值,
|
||||
超卖阈值,
|
||||
RSI_SMA周期,
|
||||
RSI: Some(RSI),
|
||||
平均上涨: Some(平均上涨),
|
||||
平均下跌: Some(平均下跌),
|
||||
上涨幅度: 上涨,
|
||||
下跌幅度: 下跌,
|
||||
平滑系数,
|
||||
RSI_SMA,
|
||||
RSI历史队列,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_first_calc() {
|
||||
let rsi = 相对强弱指数::首次计算(100.0, 1000, 14, 70.0, 30.0, None);
|
||||
assert_eq!(rsi.RSI, None);
|
||||
assert_eq!(rsi.平滑系数, 1.0 / 14.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_incremental_calc() {
|
||||
let first = 相对强弱指数::首次计算(100.0, 1000, 14, 70.0, 30.0, None);
|
||||
let second = 相对强弱指数::增量计算(&first, 102.0, 1001);
|
||||
// 价格上涨 → RSI > 50
|
||||
assert!(second.RSI.unwrap() > 50.0);
|
||||
|
||||
let third = 相对强弱指数::增量计算(&second, 98.0, 1002);
|
||||
// 价格低于之前 → RSI 下降
|
||||
assert!(third.RSI.unwrap() < second.RSI.unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rsi_sma() {
|
||||
let mut rsi = 相对强弱指数::首次计算(100.0, 1000, 14, 70.0, 30.0, Some(5));
|
||||
// 喂入多根K线来积累RSI历史队列
|
||||
let prices = [102.0, 103.0, 101.0, 104.0, 105.0, 103.0, 106.0];
|
||||
for (i, price) in prices.iter().enumerate() {
|
||||
rsi = 相对强弱指数::增量计算(&rsi, *price, 1001 + i as i64);
|
||||
}
|
||||
// SMA 应该已被计算(队列够长)
|
||||
assert!(rsi.RSI_SMA.is_some());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,244 @@
|
||||
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::rc::Rc;
|
||||
|
||||
/// 原始K线 (OHLCV + 指标)
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct K线 {
|
||||
pub 标识: String,
|
||||
pub 序号: i64,
|
||||
pub 周期: i64,
|
||||
pub 时间戳: i64,
|
||||
pub 高: f64,
|
||||
pub 低: f64,
|
||||
pub 开盘价: f64,
|
||||
pub 收盘价: f64,
|
||||
pub 成交量: f64,
|
||||
pub macd: Option<平滑异同移动平均线>,
|
||||
pub rsi: Option<相对强弱指数>,
|
||||
pub kdj: Option<随机指标>,
|
||||
}
|
||||
|
||||
impl Default for K线 {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
标识: "bar".into(),
|
||||
序号: 0,
|
||||
周期: 60,
|
||||
时间戳: 0,
|
||||
高: 0.0,
|
||||
低: 0.0,
|
||||
开盘价: 0.0,
|
||||
收盘价: 0.0,
|
||||
成交量: 0.0,
|
||||
macd: None,
|
||||
rsi: None,
|
||||
kdj: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl K线 {
|
||||
/// 方向:阳(收盘 > 开盘)为向上,否则向下
|
||||
pub fn 方向(&self) -> 相对方向 {
|
||||
if self.开盘价 < self.收盘价 {
|
||||
相对方向::向上
|
||||
} else {
|
||||
相对方向::向下
|
||||
}
|
||||
}
|
||||
|
||||
/// 序列化为大端字节序 48 字节
|
||||
/// 格式: >6d (时间戳, 开盘价, 高, 低, 收盘价, 成交量)
|
||||
pub fn to_bytes(&self) -> [u8; 48] {
|
||||
let mut buf = [0u8; 48];
|
||||
{
|
||||
let mut writer = &mut buf[..];
|
||||
writer.write_f64::<BigEndian>(self.时间戳 as f64).unwrap();
|
||||
writer.write_f64::<BigEndian>(self.开盘价).unwrap();
|
||||
writer.write_f64::<BigEndian>(self.高).unwrap();
|
||||
writer.write_f64::<BigEndian>(self.低).unwrap();
|
||||
writer.write_f64::<BigEndian>(self.收盘价).unwrap();
|
||||
writer.write_f64::<BigEndian>(self.成交量).unwrap();
|
||||
}
|
||||
buf
|
||||
}
|
||||
|
||||
/// 从大端字节序反序列化
|
||||
pub fn from_bytes(字节组: &[u8], 周期: i64, 标识: &str) -> Option<Self> {
|
||||
if 字节组.len() < 48 {
|
||||
return None;
|
||||
}
|
||||
let mut reader = &字节组[..48];
|
||||
let 时间戳 = reader.read_f64::<BigEndian>().ok()? as i64;
|
||||
let 开盘价 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 高 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 低 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 收盘价 = reader.read_f64::<BigEndian>().ok()?;
|
||||
let 成交量 = reader.read_f64::<BigEndian>().ok()?;
|
||||
|
||||
Some(Self {
|
||||
时间戳,
|
||||
开盘价,
|
||||
高,
|
||||
低,
|
||||
收盘价,
|
||||
成交量,
|
||||
周期,
|
||||
标识: 标识.to_string(),
|
||||
序号: 0,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
|
||||
/// 读取 .nb 文件中的所有 K线
|
||||
pub fn 读取大端字节数组(字节组: &[u8], 周期: i64, 标识: &str) -> Option<Self> {
|
||||
Self::from_bytes(字节组, 周期, 标识)
|
||||
}
|
||||
|
||||
/// 创建普通K线
|
||||
pub fn 创建普K(
|
||||
标识: &str,
|
||||
时间戳: i64,
|
||||
开盘价: f64,
|
||||
最高价: f64,
|
||||
最低价: f64,
|
||||
收盘价: f64,
|
||||
成交量: f64,
|
||||
序号: i64,
|
||||
周期: i64,
|
||||
) -> Self {
|
||||
Self {
|
||||
标识: 标识.to_string(),
|
||||
序号,
|
||||
周期,
|
||||
时间戳,
|
||||
高: 最高价,
|
||||
低: 最低价,
|
||||
开盘价,
|
||||
收盘价,
|
||||
成交量,
|
||||
macd: None,
|
||||
rsi: None,
|
||||
kdj: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 保存K线序列到 DAT 文件
|
||||
pub fn 保存到DAT文件(路径: &str, K线序列: &[&Self]) -> std::io::Result<()> {
|
||||
let mut f = std::fs::File::create(路径)?;
|
||||
for k in K线序列 {
|
||||
f.write_all(&k.to_bytes())?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 获取两K线之间的 MACD 柱面积
|
||||
pub fn 获取MACD(K线序列: &[&Self], 始: &Self, 终: &Self) -> HashMap<String, f64> {
|
||||
let 始_idx = K线序列.iter().position(|k| std::ptr::eq(*k, 始)).unwrap_or(0);
|
||||
let 终_idx = K线序列.iter().position(|k| std::ptr::eq(*k, 终)).unwrap_or(0);
|
||||
let 基序 = &K线序列[始_idx..=终_idx];
|
||||
|
||||
let mut 阳 = 0.0f64;
|
||||
let mut 阴 = 0.0f64;
|
||||
for k in 基序 {
|
||||
if let Some(ref macd) = k.macd {
|
||||
let hist = macd.MACD柱;
|
||||
if hist >= 0.0 {
|
||||
阳 += hist;
|
||||
} else {
|
||||
阴 += hist;
|
||||
}
|
||||
}
|
||||
}
|
||||
let 合 = 阳 + 阴;
|
||||
let mut map = HashMap::new();
|
||||
map.insert("阳".into(), 阳);
|
||||
map.insert("阴".into(), 阴);
|
||||
map.insert("合".into(), 合);
|
||||
map.insert("总".into(), 阳 + 阴.abs());
|
||||
map
|
||||
}
|
||||
|
||||
/// 截取K线序列中从始到终的片段
|
||||
pub fn 截取<'a>(序列: &'a [Self], 始: &'a Self, 终: &'a Self) -> Option<&'a [Self]> {
|
||||
let 始_idx = 序列.iter().position(|k| std::ptr::eq(k, 始))?;
|
||||
let 终_idx = 序列.iter().position(|k| std::ptr::eq(k, 终))?;
|
||||
Some(&序列[始_idx..=终_idx])
|
||||
}
|
||||
|
||||
/// 截取Rc<K线>序列中从始到终的片段
|
||||
pub fn 截取rc(序列: &[Rc<Self>], 始: &Rc<Self>, 终: &Rc<Self>) -> Vec<Rc<Self>> {
|
||||
let 始_ptr = Rc::as_ptr(始);
|
||||
let 终_ptr = Rc::as_ptr(终);
|
||||
let 始_idx = 序列.iter().position(|k| Rc::as_ptr(k) == 始_ptr);
|
||||
let 终_idx = 序列.iter().position(|k| Rc::as_ptr(k) == 终_ptr);
|
||||
match (始_idx, 终_idx) {
|
||||
(Some(s), Some(e)) => 序列[s..=e].to_vec(),
|
||||
_ => Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for K线 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use crate::utils::format_f64_g;
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}, {}, {}, {}, {}>",
|
||||
self.标识,
|
||||
self.序号,
|
||||
self.周期,
|
||||
self.方向(),
|
||||
self.时间戳,
|
||||
format_f64_g(self.开盘价),
|
||||
format_f64_g(self.高),
|
||||
format_f64_g(self.低),
|
||||
format_f64_g(self.收盘价)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_方向() {
|
||||
let 阳 = K线::创建普K("test", 1000, 100.0, 110.0, 95.0, 105.0, 1000.0, 0, 60);
|
||||
assert_eq!(阳.方向(), 相对方向::向上);
|
||||
|
||||
let 阴 = K线::创建普K("test", 1000, 105.0, 110.0, 95.0, 100.0, 1000.0, 0, 60);
|
||||
assert_eq!(阴.方向(), 相对方向::向下);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_serialization_roundtrip() {
|
||||
let k = K线::创建普K("test", 1600000000, 100.5, 110.2, 95.3, 105.7, 5000.0, 42, 60);
|
||||
let bytes = k.to_bytes();
|
||||
let restored = K线::from_bytes(&bytes, 60, "test").unwrap();
|
||||
|
||||
assert_eq!(restored.时间戳, 1600000000);
|
||||
assert!((restored.开盘价 - 100.5).abs() < 0.01);
|
||||
assert!((restored.高 - 110.2).abs() < 0.01);
|
||||
assert!((restored.低 - 95.3).abs() < 0.01);
|
||||
assert!((restored.收盘价 - 105.7).abs() < 0.01);
|
||||
assert!((restored.成交量 - 5000.0).abs() < 0.01);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_获取MACD_empty() {
|
||||
let k1 = K线::default();
|
||||
let k2 = K线::default();
|
||||
let seq = vec![&k1, &k2];
|
||||
let result = K线::获取MACD(&seq, &k1, &k2);
|
||||
assert_eq!(result.get("阳"), Some(&0.0));
|
||||
assert_eq!(result.get("阴"), Some(&0.0));
|
||||
assert_eq!(result.get("总"), Some(&0.0));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,601 @@
|
||||
use crate::config::缠论配置;
|
||||
use crate::indicators::{K线取值, 平滑异同移动平均线, 随机指标, 相对强弱指数};
|
||||
use crate::kline::bar::K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 缠论K线 — 经包含处理过后的K线
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 缠论K线 {
|
||||
pub 序号: i64,
|
||||
pub 时间戳: i64,
|
||||
pub 高: f64,
|
||||
pub 低: f64,
|
||||
pub 方向: 相对方向,
|
||||
pub 分型: Option<分型结构>,
|
||||
pub 周期: i64,
|
||||
pub 标识: String,
|
||||
pub 分型特征值: f64,
|
||||
pub 原始起始序号: i64,
|
||||
pub 原始结束序号: i64,
|
||||
pub 标的K线: Rc<K线>,
|
||||
pub 买卖点信息: Option<()>, // 占位,后续替换为实际类型
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 缠论K线 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
use crate::utils::format_f64_g;
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}, {}, {}, {}>",
|
||||
self.标识,
|
||||
self.序号,
|
||||
self.分型.map_or("None".to_string(), |fx| fx.to_string()),
|
||||
self.周期,
|
||||
self.方向,
|
||||
self.时间戳,
|
||||
format_f64_g(self.高),
|
||||
format_f64_g(self.低)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl 缠论K线 {
|
||||
/// 创建镜像(浅拷贝 Rc 引用)
|
||||
pub fn 镜像(&self) -> Self {
|
||||
Self {
|
||||
序号: self.序号,
|
||||
时间戳: self.时间戳,
|
||||
高: self.高,
|
||||
低: self.低,
|
||||
方向: self.方向,
|
||||
分型: self.分型,
|
||||
周期: self.周期,
|
||||
标识: self.标识.clone(),
|
||||
分型特征值: self.分型特征值,
|
||||
原始起始序号: self.原始起始序号,
|
||||
原始结束序号: self.原始结束序号,
|
||||
标的K线: Rc::clone(&self.标的K线),
|
||||
买卖点信息: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// 与MACD柱子匹配 — 底分型时MACD柱应<0, 顶分型时>0
|
||||
pub fn 与MACD柱子匹配(&self) -> bool {
|
||||
match self.分型 {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(ref macd) = self.标的K线.macd {
|
||||
macd.MACD柱 < 0.0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Some(分型结构::顶) | Some(分型结构::上) => {
|
||||
if let Some(ref macd) = self.标的K线.macd {
|
||||
macd.MACD柱 > 0.0
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 与RSI匹配 — 底分型时RSI应低于SMA, 顶分型时高于SMA
|
||||
pub fn 与RSI匹配(&self) -> bool {
|
||||
match self.分型 {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(ref rsi) = self.标的K线.rsi {
|
||||
match (rsi.RSI, rsi.RSI_SMA) {
|
||||
(Some(r), Some(sma)) => r < sma,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Some(分型结构::顶) | Some(分型结构::上) => {
|
||||
if let Some(ref rsi) = self.标的K线.rsi {
|
||||
match (rsi.RSI, rsi.RSI_SMA) {
|
||||
(Some(r), Some(sma)) => r > sma,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 与KDJ匹配 — 底分型时K应低于D(死叉后), 顶分型时K应高于D(金叉后)
|
||||
pub fn 与KDJ匹配(&self) -> bool {
|
||||
match self.分型 {
|
||||
Some(分型结构::底) | Some(分型结构::下) => {
|
||||
if let Some(ref kdj) = self.标的K线.kdj {
|
||||
match (kdj.K, kdj.D) {
|
||||
(Some(k), Some(d)) => k < d,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
Some(分型结构::顶) | Some(分型结构::上) => {
|
||||
if let Some(ref kdj) = self.标的K线.kdj {
|
||||
match (kdj.K, kdj.D) {
|
||||
(Some(k), Some(d)) => k > d,
|
||||
_ => false,
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 时间戳对齐 — 从基线序列中找匹配的时间戳
|
||||
pub fn 时间戳对齐(基线: &[Rc<缠论K线>], k线: &缠论K线) -> i64 {
|
||||
if let Some(基) = 基线.first() {
|
||||
for k in 基线.iter().rev() {
|
||||
if 基.周期 < k线.周期 {
|
||||
if k线.时间戳 <= k.时间戳 && k.时间戳 <= k线.时间戳 + k线.周期 {
|
||||
if (k线.分型特征值 - k.分型特征值).abs() < f64::EPSILON {
|
||||
return k.时间戳;
|
||||
}
|
||||
}
|
||||
} else if k.时间戳 <= k线.时间戳 && k线.时间戳 <= k.时间戳 + k.周期 {
|
||||
if (k线.分型特征值 - k.分型特征值).abs() < f64::EPSILON {
|
||||
return k.时间戳;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
k线.时间戳
|
||||
}
|
||||
|
||||
/// 创建缠K
|
||||
pub fn 创建缠K(
|
||||
时间戳: i64,
|
||||
高: f64,
|
||||
低: f64,
|
||||
方向: 相对方向,
|
||||
结构: Option<分型结构>,
|
||||
原始序号: i64,
|
||||
普k: Rc<K线>,
|
||||
之前: Option<&缠论K线>,
|
||||
) -> Self {
|
||||
assert!(高 >= 低, "缠K高必须>=低: 高={高}, 低={低}");
|
||||
|
||||
let 周期 = 普k.周期;
|
||||
let 标识 = 普k.标识.clone();
|
||||
|
||||
let mut 当前 = Self {
|
||||
序号: 0,
|
||||
时间戳,
|
||||
高,
|
||||
低,
|
||||
方向,
|
||||
分型: 结构,
|
||||
周期,
|
||||
标识,
|
||||
分型特征值: 高,
|
||||
原始起始序号: 原始序号,
|
||||
原始结束序号: 原始序号,
|
||||
标的K线: 普k,
|
||||
买卖点信息: None,
|
||||
};
|
||||
|
||||
if let Some(之前) = 之前 {
|
||||
当前.序号 = 之前.序号 + 1;
|
||||
let 关系 = 相对方向::分析(之前.高, 之前.低, 当前.高, 当前.低);
|
||||
if 关系.是否包含() {
|
||||
panic!(
|
||||
"创建缠K 包含关系: {:?}\n 之前: {}\n 当前: {}",
|
||||
关系, 之前, 当前
|
||||
);
|
||||
}
|
||||
}
|
||||
当前
|
||||
}
|
||||
|
||||
/// 兼并(合并)处理 — 缠论包含处理的核心算法
|
||||
///
|
||||
/// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None
|
||||
pub fn 兼并(
|
||||
之前缠K: Option<&缠论K线>,
|
||||
当前缠K: &缠论K线,
|
||||
当前普K: &K线,
|
||||
配置: &缠论配置,
|
||||
) -> (Option<Rc<缠论K线>>, Option<String>) {
|
||||
let 关系 = 相对方向::分析(当前缠K.高, 当前缠K.低, 当前普K.高, 当前普K.低);
|
||||
|
||||
// 无包含关系 — 创建新元素追加
|
||||
if !关系.是否包含() {
|
||||
let 结构 = if 关系.是否向下() {
|
||||
Some(分型结构::下)
|
||||
} else {
|
||||
Some(分型结构::上)
|
||||
};
|
||||
let mut 新缠K = Self::创建缠K(
|
||||
当前普K.时间戳,
|
||||
当前普K.高,
|
||||
当前普K.低,
|
||||
当前普K.方向(),
|
||||
结构,
|
||||
当前普K.序号,
|
||||
Rc::new(当前普K.clone()),
|
||||
Some(当前缠K),
|
||||
);
|
||||
新缠K.序号 = 当前缠K.序号 + 1;
|
||||
return (Some(Rc::new(新缠K)), Some("添加".into()));
|
||||
}
|
||||
|
||||
// 包含关系 — 合并到当前缠K
|
||||
// 取值方向:向下 → 取低低、高高(即全部取最低);向上 → 取高高、低低(即全部取最高)
|
||||
// 方向由之前缠K与当前缠K的关系决定
|
||||
let 取值函数: fn(f64, f64) -> f64 = if let Some(之前) = 之前缠K {
|
||||
if 相对方向::分析(之前.高, 之前.低, 当前缠K.高, 当前缠K.低).是否向下() {
|
||||
f64::min
|
||||
} else {
|
||||
f64::max
|
||||
}
|
||||
} else {
|
||||
f64::max // 默认向上取max
|
||||
};
|
||||
|
||||
let mut 合并后 = 当前缠K.镜像();
|
||||
|
||||
// 逆序包含时更新时间和标的K线
|
||||
if 关系 != 相对方向::顺 {
|
||||
合并后.时间戳 = 当前普K.时间戳;
|
||||
合并后.标的K线 = Rc::new(当前普K.clone());
|
||||
}
|
||||
合并后.高 = 取值函数(合并后.高, 当前普K.高);
|
||||
合并后.低 = 取值函数(合并后.低, 当前普K.低);
|
||||
合并后.原始结束序号 = 当前普K.序号;
|
||||
合并后.方向 = 当前普K.方向();
|
||||
|
||||
if let Some(之前) = 之前缠K {
|
||||
合并后.序号 = 之前.序号 + 1;
|
||||
}
|
||||
|
||||
if 配置.缠K合并替换 {
|
||||
(Some(Rc::new(合并后)), Some("替换".into()))
|
||||
} else {
|
||||
// 兼并:返回新值让调用方决定是原地修改还是替换
|
||||
(Some(Rc::new(合并后)), Some("兼并".into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// 完整的缠K分析 — 普K → 缠K + 分型
|
||||
///
|
||||
/// 返回 (状态, 形态)
|
||||
pub fn 分析(
|
||||
当前K线: &mut K线,
|
||||
缠K序列: &mut Vec<Rc<缠论K线>>,
|
||||
普K序列: &mut Vec<Rc<K线>>,
|
||||
配置: &缠论配置,
|
||||
) -> (String, Option<Rc<分型>>) {
|
||||
当前K线.标识 = 配置.标识.clone();
|
||||
|
||||
// ---- 阶段1: 普K序列管理 + 指标增量计算 ----
|
||||
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 = Rc::new(当前K线.clone());
|
||||
普K序列.push(当前K线_rc);
|
||||
} else {
|
||||
let 之前普K = 普K序列.last().unwrap();
|
||||
if 之前普K.时间戳 == 当前K线.时间戳 {
|
||||
// 同时间戳更新
|
||||
当前K线.序号 = 之前普K.序号;
|
||||
if 配置.计算指标 {
|
||||
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(Rc::new(当前K线.clone()));
|
||||
} else {
|
||||
if 之前普K.时间戳 > 当前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(Rc::new(当前K线.clone()));
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 阶段2: 缠K合并 ----
|
||||
let 之前缠K: Option<&缠论K线> = if 缠K序列.len() >= 2 {
|
||||
Some(&缠K序列[缠K序列.len() - 2])
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let 状态: String;
|
||||
if !缠K序列.is_empty() {
|
||||
let 最后一个缠K = &缠K序列[缠K序列.len() - 1];
|
||||
// 需要从 Rc 中取出引用
|
||||
let 最后一个缠K_ref = Rc::as_ref(最后一个缠K);
|
||||
let (新缠K, 模式) = Self::兼并(之前缠K, 最后一个缠K_ref, 当前K线, 配置);
|
||||
|
||||
if let Some(k) = 新缠K {
|
||||
if let Some(ref m) = 模式 {
|
||||
if m == "添加" {
|
||||
缠K序列.push(k);
|
||||
状态 = "创建".into();
|
||||
} else if m == "替换" {
|
||||
缠K序列.pop();
|
||||
缠K序列.push(k);
|
||||
状态 = "替换".into();
|
||||
} else {
|
||||
// 兼并:尝试原地修改以保持Rc指针不变
|
||||
let 最后_idx = 缠K序列.len() - 1;
|
||||
if let Some(原地) = Rc::get_mut(&mut 缠K序列[最后_idx]) {
|
||||
原地.高 = k.高;
|
||||
原地.低 = k.低;
|
||||
原地.时间戳 = k.时间戳;
|
||||
原地.方向 = k.方向;
|
||||
原地.原始结束序号 = k.原始结束序号;
|
||||
原地.标的K线 = k.标的K线.clone();
|
||||
原地.序号 = k.序号;
|
||||
状态 = "兼并".into();
|
||||
} else {
|
||||
// 有其他引用时回退到替换
|
||||
缠K序列.pop();
|
||||
缠K序列.push(k);
|
||||
状态 = "兼并".into();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
状态 = "兼并".into();
|
||||
}
|
||||
} else {
|
||||
状态 = "兼并".into();
|
||||
}
|
||||
} else {
|
||||
let 新缠K = Self::创建缠K(
|
||||
当前K线.时间戳,
|
||||
当前K线.高,
|
||||
当前K线.低,
|
||||
当前K线.方向(),
|
||||
None,
|
||||
当前K线.序号,
|
||||
Rc::new(当前K线.clone()),
|
||||
None,
|
||||
);
|
||||
缠K序列.push(Rc::new(新缠K));
|
||||
状态 = "新建".into();
|
||||
}
|
||||
|
||||
// ---- 阶段3: 分型识别 ----
|
||||
if 缠K序列.len() < 3 {
|
||||
return (状态, None);
|
||||
}
|
||||
|
||||
let idx = 缠K序列.len();
|
||||
let 左 = Rc::clone(&缠K序列[idx - 3]);
|
||||
let 中 = Rc::clone(&缠K序列[idx - 2]);
|
||||
let 右 = Rc::clone(&缠K序列[idx - 1]);
|
||||
|
||||
let 结构 = 分型结构::分析(&*左, &*中, &*右, false, false);
|
||||
|
||||
// 需要通过 Rc::get_mut 或 RefCell 修改 中.分型
|
||||
// 由于使用 Rc,中是不可变的。这里采用创建新 Rc 替换的方式。
|
||||
// 但这是在 Vec 内部修改,需要使用 Rc::make_mut 或重新构建
|
||||
if let Some(结构) = 结构 {
|
||||
// 只在分型未设置或需要更新时才修改缠K,以保持 Rc 指针不变
|
||||
let 当前分型标记 = 缠K序列[idx - 2].分型;
|
||||
let 中需要更新 = 当前分型标记.is_none() || 当前分型标记 != Some(结构);
|
||||
|
||||
if 中需要更新 {
|
||||
let 中_mut = Rc::make_mut(&mut 缠K序列[idx - 2]);
|
||||
中_mut.分型 = Some(结构);
|
||||
|
||||
match 结构 {
|
||||
分型结构::底 => {
|
||||
中_mut.分型特征值 = 中_mut.低;
|
||||
let 右标记 = 缠K序列[idx - 1].分型;
|
||||
if 右标记.is_none() {
|
||||
let 右_mut = Rc::make_mut(&mut 缠K序列[idx - 1]);
|
||||
右_mut.分型特征值 = 右_mut.高;
|
||||
右_mut.分型 = Some(分型结构::顶);
|
||||
}
|
||||
}
|
||||
分型结构::顶 => {
|
||||
中_mut.分型特征值 = 中_mut.高;
|
||||
let 右标记 = 缠K序列[idx - 1].分型;
|
||||
if 右标记.is_none() {
|
||||
let 右_mut = Rc::make_mut(&mut 缠K序列[idx - 1]);
|
||||
右_mut.分型特征值 = 右_mut.低;
|
||||
右_mut.分型 = Some(分型结构::底);
|
||||
}
|
||||
}
|
||||
分型结构::上 => {
|
||||
中_mut.分型特征值 = 中_mut.高;
|
||||
let 右标记 = 缠K序列[idx - 1].分型;
|
||||
if 右标记.is_none() {
|
||||
let 右_mut = Rc::make_mut(&mut 缠K序列[idx - 1]);
|
||||
右_mut.分型特征值 = 右_mut.高;
|
||||
右_mut.分型 = Some(分型结构::顶);
|
||||
}
|
||||
}
|
||||
分型结构::下 => {
|
||||
中_mut.分型特征值 = 中_mut.低;
|
||||
let 右标记 = 缠K序列[idx - 1].分型;
|
||||
if 右标记.is_none() {
|
||||
let 右_mut = Rc::make_mut(&mut 缠K序列[idx - 1]);
|
||||
右_mut.分型特征值 = 右_mut.低;
|
||||
右_mut.分型 = Some(分型结构::底);
|
||||
}
|
||||
}
|
||||
分型结构::散 => {}
|
||||
}
|
||||
}
|
||||
|
||||
let 形态 = if matches!(结构, 分型结构::上 | 分型结构::下) {
|
||||
// 三连方向不算完整分型 — 只有中+右
|
||||
Rc::new(分型::new(
|
||||
None,
|
||||
Rc::clone(&缠K序列[idx - 2]),
|
||||
None,
|
||||
))
|
||||
} else {
|
||||
Rc::new(分型::new(
|
||||
Some(Rc::clone(&缠K序列[idx - 3])),
|
||||
Rc::clone(&缠K序列[idx - 2]),
|
||||
Some(Rc::clone(&缠K序列[idx - 1])),
|
||||
))
|
||||
};
|
||||
|
||||
return (状态, Some(形态));
|
||||
}
|
||||
|
||||
(状态, None)
|
||||
}
|
||||
|
||||
/// 截取缠K序列从始到终
|
||||
pub fn 截取(序列: &[Rc<缠论K线>], 始: &缠论K线, 终: &缠论K线) -> Option<Vec<Rc<缠论K线>>> {
|
||||
let 始_idx = 序列.iter().position(|k| Rc::as_ptr(k) == (始 as *const _))?;
|
||||
let 终_idx = 序列.iter().position(|k| Rc::as_ptr(k) == (终 as *const _))?;
|
||||
Some(序列[始_idx..=终_idx].to_vec())
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::types::fractal::有高低 for 缠论K线 {
|
||||
fn 高(&self) -> f64 {
|
||||
self.高
|
||||
}
|
||||
fn 低(&self) -> f64 {
|
||||
self.低
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::缠论配置;
|
||||
|
||||
fn make_普K(时间戳: i64, 开: f64, 高: f64, 低: f64, 收: f64, 序号: i64) -> K线 {
|
||||
K线::创建普K("test", 时间戳, 开, 高, 低, 收, 1000.0, 序号, 60)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_创建缠K_basic() {
|
||||
let pk = Rc::new(make_普K(1000, 100.0, 110.0, 95.0, 105.0, 0));
|
||||
let ck = 缠论K线::创建缠K(1000, 110.0, 95.0, 相对方向::向上, None, 0, pk, None);
|
||||
assert_eq!(ck.高, 110.0);
|
||||
assert_eq!(ck.低, 95.0);
|
||||
assert_eq!(ck.序号, 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_分析_empty_sequence() {
|
||||
let config = 缠论配置::default();
|
||||
let mut pk = make_普K(1000, 100.0, 110.0, 95.0, 105.0, 0);
|
||||
let mut 缠K序列 = Vec::new();
|
||||
let mut 普K序列 = Vec::new();
|
||||
|
||||
let (状态, 形态) = 缠论K线::分析(&mut pk, &mut 缠K序列, &mut 普K序列, &config);
|
||||
assert_eq!(状态, "新建");
|
||||
assert_eq!(缠K序列.len(), 1);
|
||||
assert!(形态.is_none()); // 不够3根
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_分析_three_bars_fractal() {
|
||||
let config = 缠论配置::default();
|
||||
let mut 缠K序列 = Vec::new();
|
||||
let mut 普K序列 = Vec::new();
|
||||
|
||||
// 三根形成顶分型: 低高 → 更高高 → 低高
|
||||
let mut pk1 = make_普K(1000, 100.0, 110.0, 95.0, 105.0, 0);
|
||||
let 状态1 = 缠论K线::分析(&mut pk1, &mut 缠K序列, &mut 普K序列, &config);
|
||||
assert_eq!(状态1.0, "新建");
|
||||
|
||||
let mut pk2 = make_普K(1001, 105.0, 115.0, 102.0, 112.0, 1);
|
||||
let 状态2 = 缠论K线::分析(&mut pk2, &mut 缠K序列, &mut 普K序列, &config);
|
||||
assert!(状态2.1.is_none()); // 仍不够
|
||||
|
||||
let mut pk3 = make_普K(1002, 112.0, 113.0, 100.0, 103.0, 2);
|
||||
let (_状态3, 形态) = 缠论K线::分析(&mut pk3, &mut 缠K序列, &mut 普K序列, &config);
|
||||
assert!(形态.is_some()); // 分型产生了
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
pub mod bar;
|
||||
pub mod chan_kline;
|
||||
@@ -0,0 +1,11 @@
|
||||
#![allow(non_snake_case)]
|
||||
#![allow(non_camel_case_types)]
|
||||
|
||||
pub mod algorithm;
|
||||
pub mod business;
|
||||
pub mod config;
|
||||
pub mod indicators;
|
||||
pub mod kline;
|
||||
pub mod structure;
|
||||
pub mod types;
|
||||
pub mod utils;
|
||||
@@ -0,0 +1,34 @@
|
||||
use chanlun::business::observer::观察者;
|
||||
use std::env;
|
||||
|
||||
fn main() {
|
||||
let args: Vec<String> = env::args().collect();
|
||||
if args.len() < 2 {
|
||||
eprintln!("用法: {} <.nb文件路径>", args[0]);
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
let 文件路径 = &args[1];
|
||||
println!("读取文件: {}", 文件路径);
|
||||
|
||||
match 观察者::读取数据文件(文件路径, None) {
|
||||
Ok(观察员) => {
|
||||
println!("符号: {}", 观察员.符号);
|
||||
println!("周期: {}", 观察员.周期);
|
||||
println!("普K数量: {}", 观察员.普通K线序列.len());
|
||||
println!("缠K数量: {}", 观察员.缠论K线序列.len());
|
||||
println!("分型数量: {}", 观察员.分型序列.len());
|
||||
println!("笔数量: {}", 观察员.笔序列.len());
|
||||
println!("笔中枢数量: {}", 观察员.笔_中枢序列.len());
|
||||
println!("线段数量: {}", 观察员.线段序列.len());
|
||||
println!("中枢数量: {}", 观察员.中枢序列.len());
|
||||
|
||||
println!("\n===== 数据分析 =====\n");
|
||||
观察员.测试_保存数据(None);
|
||||
}
|
||||
Err(e) => {
|
||||
eprintln!("读取失败: {}", e);
|
||||
std::process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,771 @@
|
||||
use crate::algorithm::hub::中枢;
|
||||
use crate::config::缠论配置;
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::kline::bar::K线;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::structure::segment_feat::线段特征;
|
||||
use crate::types::{分型结构, 相对方向, 缺口};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 虚线 — 笔和线段的通用数据结构
|
||||
///
|
||||
/// 笔和线段共享此 struct,通过 `标识` 字段区分 ("笔"/"线段"/"扩展线段"等)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 虚线 {
|
||||
pub 标识: String,
|
||||
pub 序号: i64,
|
||||
pub 级别: i64,
|
||||
pub 文: Rc<分型>,
|
||||
pub 武: Rc<分型>,
|
||||
pub 有效性: bool,
|
||||
pub 基础序列: Vec<Rc<虚线>>,
|
||||
pub 特征序列: Vec<Option<Rc<线段特征>>>,
|
||||
pub 实_中枢序列: Vec<Rc<中枢>>,
|
||||
pub 虚_中枢序列: Vec<Rc<中枢>>,
|
||||
pub 合_中枢序列: Vec<Rc<中枢>>,
|
||||
pub 确认K线: Option<Rc<缠论K线>>,
|
||||
pub 模式: String,
|
||||
pub _特征序列_显示: bool,
|
||||
pub 前一缺口: Option<缺口>,
|
||||
pub 前一结束位置: Option<Rc<虚线>>,
|
||||
pub 短路修正: bool,
|
||||
}
|
||||
|
||||
impl 虚线 {
|
||||
pub fn new(序号: i64, 标识: String, 文: Rc<分型>, 武: Rc<分型>, 级别: i64, 有效性: bool) -> Self {
|
||||
Self {
|
||||
序号,
|
||||
标识,
|
||||
级别,
|
||||
文,
|
||||
武,
|
||||
有效性,
|
||||
基础序列: Vec::new(),
|
||||
特征序列: Vec::new(),
|
||||
实_中枢序列: Vec::new(),
|
||||
虚_中枢序列: Vec::new(),
|
||||
合_中枢序列: Vec::new(),
|
||||
确认K线: None,
|
||||
模式: "文武".into(),
|
||||
_特征序列_显示: false,
|
||||
前一缺口: None,
|
||||
前一结束位置: None,
|
||||
短路修正: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 笔序列(基础序列的别名)
|
||||
pub fn 笔序列(&self) -> &Vec<Rc<虚线>> {
|
||||
&self.基础序列
|
||||
}
|
||||
|
||||
pub fn 图表标题(&self) -> String {
|
||||
format!("{}:{}:{}:{}", self.文.中.标识, self.文.中.周期, self.标识, self.序号)
|
||||
}
|
||||
|
||||
/// 方向 — 文到武的方向
|
||||
pub fn 方向(&self) -> 相对方向 {
|
||||
match (self.文.结构, self.武.结构) {
|
||||
(分型结构::顶, 分型结构::底) => 相对方向::向下,
|
||||
(分型结构::顶, 分型结构::下) => 相对方向::向下,
|
||||
(分型结构::上, 分型结构::底) => 相对方向::向下,
|
||||
(分型结构::上, 分型结构::下) => 相对方向::向下,
|
||||
_ => 相对方向::向上,
|
||||
}
|
||||
}
|
||||
|
||||
/// 虚线高 — 文和武中高的那个
|
||||
pub fn 高(&self) -> f64 {
|
||||
self.武.分型特征值.max(self.文.分型特征值)
|
||||
}
|
||||
|
||||
/// 虚线低 — 文和武中低的那个
|
||||
pub fn 低(&self) -> f64 {
|
||||
self.武.分型特征值.min(self.文.分型特征值)
|
||||
}
|
||||
|
||||
/// 判断两个虚线是否首尾相连
|
||||
pub fn 之前是(&self, 之前: &虚线) -> bool {
|
||||
self.文.中.序号 == 之前.武.中.序号
|
||||
}
|
||||
|
||||
/// 判断两个虚线是否首尾相连
|
||||
pub fn 之后是(&self, 之后: &虚线) -> bool {
|
||||
self.武.中.序号 == 之后.文.中.序号
|
||||
}
|
||||
|
||||
/// 获取该虚线范围内的普K序列
|
||||
pub fn 获取普K序列(&self, 普K序列: &[Rc<K线>]) -> Vec<Rc<K线>> {
|
||||
let 始 = self.文.中.原始起始序号 as usize;
|
||||
let 终 = self.武.中.原始结束序号 as usize;
|
||||
if 始 < 普K序列.len() && 终 < 普K序列.len() && 始 <= 终 {
|
||||
普K序列[始..=终].to_vec()
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// 获取该虚线范围内的缠K序列
|
||||
pub fn 获取缠K序列(&self, 缠K序列: &[Rc<缠论K线>]) -> Vec<Rc<缠论K线>> {
|
||||
缠论K线::截取(缠K序列, &self.文.中, &self.武.中).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// 获取数据文本(用于保存/调试)
|
||||
pub fn 获取数据文本(&self) -> String {
|
||||
use crate::utils::format_f64_g;
|
||||
if self.标识 == "笔" {
|
||||
return format!(
|
||||
"{}, {}, {}, 文:({},{}), 武:({},{}), {}",
|
||||
self.标识,
|
||||
self.序号,
|
||||
self.级别,
|
||||
self.文.时间戳,
|
||||
format_f64_g(self.文.分型特征值),
|
||||
self.武.时间戳,
|
||||
format_f64_g(self.武.分型特征值),
|
||||
if self.有效性 { "True" } else { "False" },
|
||||
);
|
||||
}
|
||||
|
||||
// 非笔:线段/扩展线段等,完整输出
|
||||
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 前一缺口_str = match &self.前一缺口 {
|
||||
Some(g) => format!("{}", g),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
let 前一结束位置_str = match &self.前一结束位置 {
|
||||
Some(d) => format!("{}", d),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
|
||||
// Format中枢序列 as Python-style list representations
|
||||
let 实_str = format!("[{}]", self.实_中枢序列.iter().map(|h| format!("{}", h)).collect::<Vec<_>>().join(", "));
|
||||
let 虚_str = format!("[{}]", self.虚_中枢序列.iter().map(|h| format!("{}", h)).collect::<Vec<_>>().join(", "));
|
||||
let 合_str = format!("[{}]", self.合_中枢序列.iter().map(|h| format!("{}", h)).collect::<Vec<_>>().join(", "));
|
||||
|
||||
let 前_str = format!("[{}]", 前.iter().map(|d| format!("{}", d)).collect::<Vec<_>>().join(", "));
|
||||
let 后_str = format!("[{}]", 后.iter().map(|d| format!("{}", d)).collect::<Vec<_>>().join(", "));
|
||||
let 三_str = format!("[{}]", 三.iter().map(|d| format!("{}", d)).collect::<Vec<_>>().join(", "));
|
||||
|
||||
format!(
|
||||
"{}, {}, {}, 文:({},{}), 武:({},{}), {}, {}, ({}, {}, {}), (前: {}, 后: {}, 三: {}, 伤: {}), 实: {}, 虚: {}, 合: {}, {}, {}, {}, {}",
|
||||
self.标识,
|
||||
self.序号,
|
||||
self.级别,
|
||||
self.文.时间戳,
|
||||
format_f64_g(self.文.分型特征值),
|
||||
self.武.时间戳,
|
||||
format_f64_g(self.武.分型特征值),
|
||||
if self.有效性 { "True" } else { "False" },
|
||||
self.基础序列.len(),
|
||||
特征_bool(特征_a),
|
||||
特征_bool(特征_b),
|
||||
特征_bool(特征_c),
|
||||
前_str,
|
||||
后_str,
|
||||
三_str,
|
||||
match &贯穿伤 { Some(d) => format!("{}", d), None => "None".to_string() },
|
||||
实_str,
|
||||
虚_str,
|
||||
合_str,
|
||||
self.模式,
|
||||
前一缺口_str,
|
||||
前一结束位置_str,
|
||||
if self.短路修正 { "True" } else { "False" },
|
||||
)
|
||||
}
|
||||
|
||||
// ---- 关联函数(静态工厂方法) ----
|
||||
|
||||
/// 创建笔
|
||||
pub fn 创建笔(文: Rc<分型>, 武: Rc<分型>, 有效性: bool) -> Self {
|
||||
Self::new(0, "笔".into(), 文, 武, 1, 有效性)
|
||||
}
|
||||
|
||||
/// 创建线段
|
||||
pub fn 创建线段(虚线序列: &[Rc<虚线>]) -> Self {
|
||||
let 文 = Rc::clone(&虚线序列[0].文);
|
||||
let 武 = Rc::clone(&虚线序列[虚线序列.len() - 1].武);
|
||||
let 标识 = if 虚线序列[0].标识 == "笔" {
|
||||
"线段".into()
|
||||
} else {
|
||||
format!("线段<{}>", 虚线序列[0].标识)
|
||||
};
|
||||
let 级别 = 虚线序列[0].级别 + 1;
|
||||
let mut 段 = Self::new(0, 标识, 文, 武, 级别, true);
|
||||
段.基础序列 = 虚线序列.to_vec();
|
||||
段.模式 = "文武".into();
|
||||
段
|
||||
}
|
||||
|
||||
// ---- 买卖点模式匹配 ----
|
||||
|
||||
/// 缠K买卖点模式 — 根据模式字符串选择匹配方法
|
||||
pub fn 缠K买卖点模式(模式: &str, 缠K: &缠论K线, 配置: &缠论配置) -> bool {
|
||||
match 模式 {
|
||||
"全量" => Self::买卖点全量匹配(缠K),
|
||||
"任意" => Self::买卖点任意匹配(缠K),
|
||||
"配置" => Self::买卖点配置匹配(缠K, 配置),
|
||||
"相对" => Self::买卖点相对匹配(缠K),
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 买卖点配置匹配 — 根据配置中的指标开关组合判断
|
||||
pub fn 买卖点配置匹配(缠K: &缠论K线, 配置: &缠论配置) -> bool {
|
||||
match (配置.买卖点_指标匹配_MACD, 配置.买卖点_指标匹配_KDJ, 配置.买卖点_指标匹配_RSI) {
|
||||
(true, true, true) => 缠K.与MACD柱子匹配() && 缠K.与KDJ匹配() && 缠K.与RSI匹配(),
|
||||
(false, false, false) => false,
|
||||
(true, false, true) => 缠K.与MACD柱子匹配() && 缠K.与RSI匹配(),
|
||||
(false, true, false) => 缠K.与KDJ匹配(),
|
||||
(true, false, false) => 缠K.与MACD柱子匹配(),
|
||||
(false, true, true) => 缠K.与KDJ匹配() && 缠K.与RSI匹配(),
|
||||
(false, false, true) => 缠K.与RSI匹配(),
|
||||
(true, true, false) => 缠K.与MACD柱子匹配() && 缠K.与KDJ匹配(),
|
||||
}
|
||||
}
|
||||
|
||||
/// 买卖点任意匹配 — 任一指标匹配
|
||||
pub fn 买卖点任意匹配(缠K: &缠论K线) -> bool {
|
||||
缠K.与MACD柱子匹配() || 缠K.与KDJ匹配() || 缠K.与RSI匹配()
|
||||
}
|
||||
|
||||
/// 买卖点全量匹配 — 全部指标匹配
|
||||
pub fn 买卖点全量匹配(缠K: &缠论K线) -> bool {
|
||||
缠K.与MACD柱子匹配() && 缠K.与KDJ匹配() && 缠K.与RSI匹配()
|
||||
}
|
||||
|
||||
/// 买卖点相对匹配 — 至少两个指标匹配
|
||||
pub fn 买卖点相对匹配(缠K: &缠论K线) -> bool {
|
||||
let 混沌槽 = [
|
||||
缠K.与MACD柱子匹配(),
|
||||
缠K.与KDJ匹配(),
|
||||
缠K.与RSI匹配(),
|
||||
];
|
||||
混沌槽.iter().filter(|&&x| x).count() >= 2
|
||||
}
|
||||
|
||||
// ---- MACD柱子均值计算 ----
|
||||
|
||||
/// 计算MACD柱子均值 — 虚线范围内所有MACD柱的绝对值均值
|
||||
pub fn 计算MACD柱子均值(普K序列: &[Rc<K线>], 实线: &虚线) -> f64 {
|
||||
let K线序列 = K线::截取rc(普K序列, &实线.文.中.标的K线, &实线.武.中.标的K线);
|
||||
if K线序列.is_empty() {
|
||||
return 0.0;
|
||||
}
|
||||
let 总: f64 = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.map(|m| m.MACD柱.abs())
|
||||
.sum();
|
||||
总 / K线序列.len() as f64
|
||||
}
|
||||
|
||||
/// 计算MACD柱子均值_阴 — 负柱的绝对值均值
|
||||
pub fn 计算MACD柱子均值_阴(普K序列: &[Rc<K线>], 实线: &虚线) -> Option<f64> {
|
||||
let K线序列 = K线::截取rc(普K序列, &实线.文.中.标的K线, &实线.武.中.标的K线);
|
||||
let 总: Vec<f64> = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.filter(|m| m.MACD柱 < 0.0)
|
||||
.map(|m| m.MACD柱.abs())
|
||||
.collect();
|
||||
if 总.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(总.iter().sum::<f64>() / 总.len() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
/// 计算MACD柱子均值_阳 — 正柱的绝对值均值
|
||||
pub fn 计算MACD柱子均值_阳(普K序列: &[Rc<K线>], 实线: &虚线) -> Option<f64> {
|
||||
let K线序列 = K线::截取rc(普K序列, &实线.文.中.标的K线, &实线.武.中.标的K线);
|
||||
let 总: Vec<f64> = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.filter(|m| m.MACD柱 > 0.0)
|
||||
.map(|m| m.MACD柱.abs())
|
||||
.collect();
|
||||
if 总.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(总.iter().sum::<f64>() / 总.len() as f64)
|
||||
}
|
||||
}
|
||||
|
||||
// ---- 武之MACD比较 ----
|
||||
|
||||
/// 武之全量MACD均值 — 武端MACD柱是否小于均值(背驰)
|
||||
pub fn 武之全量MACD均值(普K序列: &[Rc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_MACD = match &实线.武.中.标的K线.macd {
|
||||
Some(m) => m.MACD柱.abs(),
|
||||
None => return false,
|
||||
};
|
||||
武_MACD < Self::计算MACD柱子均值(普K序列, 实线)
|
||||
}
|
||||
|
||||
/// 武之MACD均值 — 按方向选择阴/阳均值比对
|
||||
pub fn 武之MACD均值(普K序列: &[Rc<K线>], 实线: &虚线) -> bool {
|
||||
if 实线.方向() == 相对方向::向上 {
|
||||
Self::武之MACD均值_阳(普K序列, 实线)
|
||||
} else {
|
||||
Self::武之MACD均值_阴(普K序列, 实线)
|
||||
}
|
||||
}
|
||||
|
||||
/// 武之MACD均值_阴 — 武端负柱是否小于阴均值
|
||||
pub fn 武之MACD均值_阴(普K序列: &[Rc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_MACD = match &实线.武.中.标的K线.macd {
|
||||
Some(m) => m.MACD柱.abs(),
|
||||
None => return false,
|
||||
};
|
||||
match Self::计算MACD柱子均值_阴(普K序列, 实线) {
|
||||
Some(均值) => 武_MACD < 均值.abs(),
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 武之MACD均值_阳 — 武端正柱是否小于阳均值
|
||||
pub fn 武之MACD均值_阳(普K序列: &[Rc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_MACD = match &实线.武.中.标的K线.macd {
|
||||
Some(m) => m.MACD柱.abs(),
|
||||
None => return false,
|
||||
};
|
||||
match Self::计算MACD柱子均值_阳(普K序列, 实线) {
|
||||
Some(均值) => 武_MACD < 均值,
|
||||
None => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 武之MACD极值 — 武端MACD柱是否为区间极值
|
||||
pub fn 武之MACD极值(普K序列: &[Rc<K线>], 实线: &虚线) -> bool {
|
||||
let 武_MACD = match &实线.武.中.标的K线.macd {
|
||||
Some(m) => m.MACD柱,
|
||||
None => return false,
|
||||
};
|
||||
let K线序列 = K线::截取rc(普K序列, &实线.文.中.标的K线, &实线.武.中.标的K线);
|
||||
let 所有柱子: Vec<f64> = K线序列
|
||||
.iter()
|
||||
.filter_map(|k| k.macd.as_ref())
|
||||
.map(|m| m.MACD柱)
|
||||
.collect();
|
||||
if 所有柱子.is_empty() {
|
||||
return false;
|
||||
}
|
||||
if 武_MACD > 0.0 {
|
||||
let 极值 = 所有柱子.iter().cloned().fold(f64::NEG_INFINITY, f64::max);
|
||||
极值 == 武_MACD
|
||||
} else {
|
||||
let 极值 = 所有柱子.iter().cloned().fold(f64::INFINITY, f64::min);
|
||||
极值 == 武_MACD
|
||||
}
|
||||
}
|
||||
|
||||
// ---- MACD趋向背驰 ----
|
||||
|
||||
/// 计算K线序列MACD趋向背驰 — 分析 MACD柱/DIF/DEA 三项背驰信号
|
||||
pub fn 计算K线序列MACD趋向背驰(普K序列: &[Rc<K线>], 方向: 相对方向) -> [bool; 3] {
|
||||
if 普K序列.is_empty() {
|
||||
return [false, false, false];
|
||||
}
|
||||
let 最后 = &普K序列[普K序列.len() - 1];
|
||||
|
||||
if 方向 == 相对方向::向上 {
|
||||
let 柱子序列: Vec<&Rc<K线>> = 普K序列
|
||||
.iter()
|
||||
.filter(|k| k.macd.as_ref().map_or(false, |m| m.MACD柱 > 0.0))
|
||||
.collect();
|
||||
if 柱子序列.is_empty() {
|
||||
return [false, false, false];
|
||||
}
|
||||
|
||||
let mut 结果 = [false; 3];
|
||||
|
||||
// MACD柱背驰
|
||||
let 最高柱子 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.macd.as_ref().unwrap().MACD柱
|
||||
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱)
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对 = vec![Rc::clone(*最高柱子), Rc::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;
|
||||
}
|
||||
}
|
||||
|
||||
// DIF背驰
|
||||
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);
|
||||
da.partial_cmp(&db).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对2 = vec![Rc::clone(*最高离差值), Rc::clone(最后)];
|
||||
柱对2.sort_by_key(|k| k.时间戳);
|
||||
if let (Some(m0), Some(m1)) = (柱对2[0].macd.as_ref(), 柱对2[1].macd.as_ref()) {
|
||||
let dif0 = m0.DIF.unwrap_or(0.0);
|
||||
let dif1 = m1.DIF.unwrap_or(0.0);
|
||||
if dif0 > dif1 && 柱对2[0].高 < 柱对2[1].高 {
|
||||
结果[1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// DEA背驰
|
||||
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);
|
||||
da.partial_cmp(&db).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对3 = vec![Rc::clone(*最高信号线), Rc::clone(最后)];
|
||||
柱对3.sort_by_key(|k| k.时间戳);
|
||||
if let (Some(m0), Some(m1)) = (柱对3[0].macd.as_ref(), 柱对3[1].macd.as_ref()) {
|
||||
let dea0 = m0.DEA.unwrap_or(0.0);
|
||||
let dea1 = m1.DEA.unwrap_or(0.0);
|
||||
if dea0 > dea1 && 柱对3[0].高 < 柱对3[1].高 {
|
||||
结果[2] = true;
|
||||
}
|
||||
}
|
||||
|
||||
结果
|
||||
} else {
|
||||
let 柱子序列: Vec<&Rc<K线>> = 普K序列
|
||||
.iter()
|
||||
.filter(|k| k.macd.as_ref().map_or(false, |m| m.MACD柱 < 0.0))
|
||||
.collect();
|
||||
if 柱子序列.is_empty() {
|
||||
return [false, false, false];
|
||||
}
|
||||
|
||||
let mut 结果 = [false; 3];
|
||||
|
||||
// MACD柱背驰 (负向: absolute value comparison)
|
||||
let 最高柱子 = 柱子序列
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.macd.as_ref().unwrap().MACD柱.abs()
|
||||
.partial_cmp(&b.macd.as_ref().unwrap().MACD柱.abs())
|
||||
.unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对 = vec![Rc::clone(*最高柱子), Rc::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;
|
||||
}
|
||||
}
|
||||
|
||||
// DIF背驰
|
||||
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();
|
||||
da.partial_cmp(&db).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对2 = vec![Rc::clone(*最高离差值), Rc::clone(最后)];
|
||||
柱对2.sort_by_key(|k| k.时间戳);
|
||||
if let (Some(m0), Some(m1)) = (柱对2[0].macd.as_ref(), 柱对2[1].macd.as_ref()) {
|
||||
let dif0 = m0.DIF.unwrap_or(0.0);
|
||||
let dif1 = m1.DIF.unwrap_or(0.0);
|
||||
if dif0 < dif1 && 柱对2[0].低 > 柱对2[1].低 {
|
||||
结果[1] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// DEA背驰
|
||||
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();
|
||||
da.partial_cmp(&db).unwrap()
|
||||
})
|
||||
.unwrap();
|
||||
let mut 柱对3 = vec![Rc::clone(*最高信号线), Rc::clone(最后)];
|
||||
柱对3.sort_by_key(|k| k.时间戳);
|
||||
if let (Some(m0), Some(m1)) = (柱对3[0].macd.as_ref(), 柱对3[1].macd.as_ref()) {
|
||||
let dea0 = m0.DEA.unwrap_or(0.0);
|
||||
let dea1 = m1.DEA.unwrap_or(0.0);
|
||||
if dea0 < dea1 && 柱对3[0].低 > 柱对3[1].低 {
|
||||
结果[2] = true;
|
||||
}
|
||||
}
|
||||
|
||||
结果
|
||||
}
|
||||
}
|
||||
|
||||
// ---- MACD柱子分段 ----
|
||||
|
||||
/// 计算MACD柱子分段 — 按正负号将MACD柱子分段
|
||||
pub fn 计算MACD柱子分段(k线序列: &[Rc<K线>]) -> Vec<Vec<f64>> {
|
||||
if k线序列.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let 符号 = |x: f64| -> &str {
|
||||
if x > 0.0 { "正" } else { "负" }
|
||||
};
|
||||
|
||||
let 首_MACD = match &k线序列[0].macd {
|
||||
Some(m) => m.MACD柱,
|
||||
None => return Vec::new(),
|
||||
};
|
||||
let mut 当前符号 = 符号(首_MACD);
|
||||
let mut 当前段 = vec![首_MACD];
|
||||
let mut 结果 = Vec::new();
|
||||
|
||||
for k线 in &k线序列[1..] {
|
||||
let macd = match &k线.macd {
|
||||
Some(m) => m.MACD柱,
|
||||
None => continue,
|
||||
};
|
||||
let 新符号 = 符号(macd);
|
||||
if 新符号 == 当前符号 {
|
||||
当前段.push(macd);
|
||||
} else {
|
||||
结果.push(std::mem::take(&mut 当前段));
|
||||
当前段.push(macd);
|
||||
当前符号 = 新符号;
|
||||
}
|
||||
}
|
||||
if !当前段.is_empty() {
|
||||
结果.push(当前段);
|
||||
}
|
||||
结果
|
||||
}
|
||||
|
||||
// ---- 密集区域按间隔 ----
|
||||
|
||||
/// 密集区域按间隔 — 找出交叉标记中的密集区域
|
||||
pub fn 密集区域按间隔(
|
||||
交叉标记: &[i32],
|
||||
最大间隔: usize,
|
||||
最少交叉数: usize,
|
||||
) -> Vec<(usize, usize, usize)> {
|
||||
let 交叉索引: Vec<usize> = (0..交叉标记.len()).filter(|&i| 交叉标记[i] != 0).collect();
|
||||
if 交叉索引.is_empty() {
|
||||
return Vec::new();
|
||||
}
|
||||
|
||||
let mut 密集区 = Vec::new();
|
||||
let mut 当前块起始 = 交叉索引[0];
|
||||
let mut 当前块交叉数 = 1;
|
||||
|
||||
for i in 1..交叉索引.len() {
|
||||
let prev = 交叉索引[i - 1];
|
||||
let curr = 交叉索引[i];
|
||||
if curr - prev <= 最大间隔 {
|
||||
当前块交叉数 += 1;
|
||||
} else {
|
||||
if 当前块交叉数 >= 最少交叉数 {
|
||||
密集区.push((当前块起始, prev, 当前块交叉数));
|
||||
}
|
||||
当前块起始 = curr;
|
||||
当前块交叉数 = 1;
|
||||
}
|
||||
}
|
||||
|
||||
if 当前块交叉数 >= 最少交叉数 {
|
||||
密集区.push((当前块起始, 交叉索引[交叉索引.len() - 1], 当前块交叉数));
|
||||
}
|
||||
|
||||
密集区
|
||||
}
|
||||
|
||||
// ---- 统计MACD行为 ----
|
||||
|
||||
/// 统计MACD行为 — 分析DIF/DEA穿零轴和金叉死叉
|
||||
pub fn 统计MACD行为(普K序列: &[Rc<K线>], 最大间隔: usize, 最少交叉数: usize) -> std::collections::HashMap<String, String> {
|
||||
use std::collections::HashMap;
|
||||
|
||||
let mut dif_up = 0;
|
||||
let mut dif_down = 0;
|
||||
let mut dea_up = 0;
|
||||
let mut dea_down = 0;
|
||||
|
||||
for i in 1..普K序列.len() {
|
||||
let pre = &普K序列[i - 1].macd;
|
||||
let cur = &普K序列[i].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);
|
||||
|
||||
if let (Some(pd), Some(cd)) = (pre_dif, cur_dif) {
|
||||
if pd < 0.0 && cd >= 0.0 { dif_up += 1; }
|
||||
if pd > 0.0 && cd <= 0.0 { dif_down += 1; }
|
||||
}
|
||||
if let (Some(pd), Some(cd)) = (pre_dea, cur_dea) {
|
||||
if pd < 0.0 && cd >= 0.0 { dea_up += 1; }
|
||||
if pd > 0.0 && cd <= 0.0 { dea_down += 1; }
|
||||
}
|
||||
}
|
||||
|
||||
let mut golden = 0;
|
||||
let mut death = 0;
|
||||
let mut 交叉标记 = vec![0i32];
|
||||
|
||||
for i in 1..普K序列.len() {
|
||||
let pre = &普K序列[i - 1].macd;
|
||||
let cur = &普K序列[i].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;
|
||||
|
||||
if let (Some(pd), Some(cd), Some(pe), Some(ce)) = (pre_dif, cur_dif, pre_dea, cur_dea) {
|
||||
if pd <= pe && cd > ce {
|
||||
golden += 1;
|
||||
交叉标记.push(1);
|
||||
} else if pd >= pe && cd < ce {
|
||||
death += 1;
|
||||
交叉标记.push(-1);
|
||||
} else {
|
||||
交叉标记.push(0);
|
||||
}
|
||||
} else {
|
||||
交叉标记.push(0);
|
||||
}
|
||||
}
|
||||
|
||||
let 密集区 = Self::密集区域按间隔(&交叉标记, 最大间隔, 最少交叉数);
|
||||
|
||||
let mut map = HashMap::new();
|
||||
map.insert("DIF上穿0".into(), dif_up.to_string());
|
||||
map.insert("DIF下穿0".into(), dif_down.to_string());
|
||||
map.insert("DEA上穿0".into(), dea_up.to_string());
|
||||
map.insert("DEA下穿0".into(), dea_down.to_string());
|
||||
map.insert("金叉次数".into(), golden.to_string());
|
||||
map.insert("死叉次数".into(), death.to_string());
|
||||
map.insert("密集交叉区域".into(), format!("{:?}", 密集区));
|
||||
map
|
||||
}
|
||||
|
||||
// ---- 买卖意义 ----
|
||||
|
||||
/// 买卖意义 — 核心买卖点判断逻辑
|
||||
///
|
||||
/// 返回 (是否有意义, 原因字符串)
|
||||
pub fn 买卖意义(实线: &虚线, 观察员: &crate::business::observer::观察者) -> (bool, String) {
|
||||
let 普K序列 = &观察员.普通K线序列;
|
||||
let 配置 = &观察员.配置;
|
||||
|
||||
if 实线.标识 != "笔" && 实线.标识 != "线段" && !实线.标识.starts_with("线段<") {
|
||||
return (false, "标识不在范围内".into());
|
||||
}
|
||||
|
||||
// KDJ指标完整性检查
|
||||
match &实线.武.中.标的K线.kdj {
|
||||
Some(kdj) if kdj.K.is_some() && kdj.D.is_some() && kdj.J.is_some() => {}
|
||||
_ => return (false, "KDJ指标不完整".into()),
|
||||
}
|
||||
|
||||
let 意义 = Self::缠K买卖点模式(&配置.买卖点_指标模式, &实线.武.中, 配置);
|
||||
let 结果 = false;
|
||||
|
||||
let 背驰过: Vec<Rc<分型>> = if 实线.标识 == "笔" {
|
||||
crate::algorithm::bi::笔::是否背驰过(实线, 观察员)
|
||||
} else {
|
||||
crate::algorithm::segment::线段::是否背驰过(实线, 观察员)
|
||||
};
|
||||
|
||||
if 意义 {
|
||||
if 实线.标识 == "笔" {
|
||||
if Self::武之MACD均值(普K序列, 实线) {
|
||||
return (true, "武之MACD均值".into());
|
||||
}
|
||||
if Self::武之MACD极值(普K序列, 实线) && !背驰过.is_empty() {
|
||||
return (true, "背驰过且极值".into());
|
||||
} else if 实线.武.与MACD柱子分型匹配() {
|
||||
return (
|
||||
true,
|
||||
format!(
|
||||
"背驰过:{},极值:{},柱子分型匹配",
|
||||
背驰过.len(),
|
||||
Self::武之MACD极值(普K序列, 实线)
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
if 实线.标识 != "笔"
|
||||
&& crate::algorithm::segment::线段::判断线段内部是否背驰(实线, 观察员)
|
||||
{
|
||||
return (true, "线段内部背驰".into());
|
||||
}
|
||||
}
|
||||
|
||||
if !结果 && 意义 && 实线.武.中.与MACD柱子匹配() {
|
||||
if Self::武之MACD极值(普K序列, 实线) && 背驰过.len() > 2 {
|
||||
return (
|
||||
true,
|
||||
"没结果, 极值, 柱子分型匹配, 背驰过大于2次".into(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
(结果, "".into())
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 虚线 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
if self.标识 == "笔" {
|
||||
write!(
|
||||
f,
|
||||
"笔({}, {}, {}, {}, 周期: {}, 数量: {})",
|
||||
self.序号,
|
||||
self.方向(),
|
||||
self.文,
|
||||
self.武,
|
||||
self.文.中.周期,
|
||||
self.武.中.序号 - self.文.中.序号 + 1
|
||||
)
|
||||
} else {
|
||||
let 四象 = crate::algorithm::segment::线段::四象(self);
|
||||
let 缺口 = crate::algorithm::segment::线段::获取缺口(self);
|
||||
let 缺口_str = match 缺口 {
|
||||
Some(g) => format!("{}", g),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
let 确认K线_str = match &self.确认K线 {
|
||||
Some(k) => format!("{}", k),
|
||||
None => "None".to_string(),
|
||||
};
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}, {}, 数量: {}, 缺口: {}, {}>",
|
||||
self.标识,
|
||||
self.序号,
|
||||
四象,
|
||||
self.方向(),
|
||||
self.文,
|
||||
self.武,
|
||||
self.基础序列.len(),
|
||||
缺口_str,
|
||||
确认K线_str,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
use crate::structure::segment_feat::线段特征;
|
||||
use crate::types::分型结构;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 特征分型 — 由三个线段特征元素构成的分型
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 特征分型 {
|
||||
pub 左: Rc<线段特征>,
|
||||
pub 中: Rc<线段特征>,
|
||||
pub 右: Rc<线段特征>,
|
||||
pub 结构: 分型结构,
|
||||
}
|
||||
|
||||
impl 特征分型 {
|
||||
pub fn new(左: Rc<线段特征>, 中: Rc<线段特征>, 右: Rc<线段特征>, 结构: 分型结构) -> Self {
|
||||
Self { 左, 中, 右, 结构 }
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 特征分型 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "特征分型<{}, {}>", self.结构, self.中)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
use crate::kline::chan_kline::缠论K线;
|
||||
use crate::types::分型结构;
|
||||
use crate::types::相对方向;
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 分型 — 由三根缠K构成(可能缺左或右)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 分型 {
|
||||
pub 左: Option<Rc<缠论K线>>,
|
||||
pub 中: Rc<缠论K线>,
|
||||
pub 右: Option<Rc<缠论K线>>,
|
||||
pub 结构: 分型结构,
|
||||
pub 时间戳: i64,
|
||||
pub 分型特征值: f64,
|
||||
}
|
||||
|
||||
impl 分型 {
|
||||
pub fn new(左: Option<Rc<缠论K线>>, 中: Rc<缠论K线>, 右: Option<Rc<缠论K线>>) -> Self {
|
||||
let 结构 = 中.分型.unwrap_or(分型结构::散);
|
||||
let 时间戳 = 中.时间戳;
|
||||
let 分型特征值 = 中.分型特征值;
|
||||
Self {
|
||||
左,
|
||||
中,
|
||||
右,
|
||||
结构,
|
||||
时间戳,
|
||||
分型特征值,
|
||||
}
|
||||
}
|
||||
|
||||
/// 左中右三组关系
|
||||
pub fn 关系组(&self) -> Option<(相对方向, 相对方向, 相对方向)> {
|
||||
let 左 = self.左.as_ref()?;
|
||||
let 右 = self.右.as_ref()?;
|
||||
Some((
|
||||
相对方向::分析(左.高, 左.低, self.中.高, self.中.低),
|
||||
相对方向::分析(self.中.高, self.中.低, 右.高, 右.低),
|
||||
相对方向::分析(左.高, 左.低, 右.高, 右.低),
|
||||
))
|
||||
}
|
||||
|
||||
/// 分型强度
|
||||
pub fn 强度(&self) -> f64 {
|
||||
match self.结构 {
|
||||
分型结构::顶 | 分型结构::上 => {
|
||||
if let Some(ref 左) = self.左 {
|
||||
self.中.高 - 左.高
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
分型结构::底 | 分型结构::下 => {
|
||||
if let Some(ref 左) = self.左 {
|
||||
左.低 - self.中.低
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
分型结构::散 => 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
/// MACD柱子分型匹配
|
||||
pub fn 与MACD柱子分型匹配(&self) -> bool {
|
||||
match self.结构 {
|
||||
分型结构::顶 | 分型结构::上 => {
|
||||
if let Some(ref 左) = self.左 {
|
||||
左.与MACD柱子匹配()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
分型结构::底 | 分型结构::下 => {
|
||||
if let Some(ref 右) = self.右 {
|
||||
右.与MACD柱子匹配()
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
分型结构::散 => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 判断两个分型是否匹配
|
||||
pub fn 判断分型(左: &分型, 右: &分型, 模式: &str) -> bool {
|
||||
match 模式 {
|
||||
"中" => 左.中.序号 == 右.中.序号,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
/// 从缠K序列中获取以指定缠K为中元素的分型
|
||||
pub fn 从缠K序列中获取分型(K线序列: &[Rc<缠论K线>], 中: &Rc<缠论K线>) -> Option<Self> {
|
||||
let idx = K线序列.iter().position(|k| Rc::as_ptr(k) == Rc::as_ptr(中))?;
|
||||
let 左 = if idx > 0 {
|
||||
Some(Rc::clone(&K线序列[idx - 1]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let 右 = if idx + 1 < K线序列.len() {
|
||||
Some(Rc::clone(&K线序列[idx + 1]))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
Some(Self::new(左, Rc::clone(中), 右))
|
||||
}
|
||||
|
||||
/// 向分型序列中添加新分型
|
||||
pub fn 向序列中添加(分型序列: &mut Vec<Rc<分型>>, 当前分型: Rc<分型>) {
|
||||
if let Some(前一个) = 分型序列.last() {
|
||||
if 前一个.时间戳 == 当前分型.时间戳 {
|
||||
// 同一时间戳: 比较强度,保留更强的
|
||||
match 当前分型.结构 {
|
||||
分型结构::顶 | 分型结构::上 => {
|
||||
if 当前分型.分型特征值 >= 前一个.分型特征值 {
|
||||
分型序列.pop();
|
||||
分型序列.push(当前分型);
|
||||
}
|
||||
return;
|
||||
}
|
||||
分型结构::底 | 分型结构::下 => {
|
||||
if 当前分型.分型特征值 <= 前一个.分型特征值 {
|
||||
分型序列.pop();
|
||||
分型序列.push(当前分型);
|
||||
}
|
||||
return;
|
||||
}
|
||||
分型结构::散 => {}
|
||||
}
|
||||
}
|
||||
// 相同结构只保留更强的
|
||||
if 前一个.结构 == 当前分型.结构 {
|
||||
if (当前分型.结构 == 分型结构::顶 || 当前分型.结构 == 分型结构::上)
|
||||
&& 当前分型.分型特征值 >= 前一个.分型特征值
|
||||
{
|
||||
分型序列.pop();
|
||||
分型序列.push(当前分型);
|
||||
} else if (当前分型.结构 == 分型结构::底 || 当前分型.结构 == 分型结构::下)
|
||||
&& 当前分型.分型特征值 <= 前一个.分型特征值
|
||||
{
|
||||
分型序列.pop();
|
||||
分型序列.push(当前分型);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
分型序列.push(当前分型);
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, None: {}, None: {}>",
|
||||
self.中.分型.unwrap_or(crate::types::分型结构::散),
|
||||
self.时间戳,
|
||||
crate::utils::format_f64_g(self.分型特征值),
|
||||
if self.左.is_none() { "True" } else { "False" },
|
||||
if self.右.is_none() { "True" } else { "False" },
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
pub mod dash_line;
|
||||
pub mod feat_fractal;
|
||||
pub mod fractal_obj;
|
||||
pub mod segment_feat;
|
||||
@@ -0,0 +1,254 @@
|
||||
use crate::structure::dash_line::虚线;
|
||||
use crate::structure::feat_fractal::特征分型;
|
||||
use crate::structure::fractal_obj::分型;
|
||||
use crate::types::{分型结构, 相对方向};
|
||||
use std::rc::Rc;
|
||||
|
||||
/// 线段特征 — 特征序列元素(内部是虚线的集合)
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct 线段特征 {
|
||||
pub 序号: i64,
|
||||
pub 标识: String,
|
||||
pub 线段方向: 相对方向,
|
||||
pub 元素: Vec<Rc<虚线>>,
|
||||
}
|
||||
|
||||
impl 线段特征 {
|
||||
pub fn new(标识: String, 基础序列: Vec<Rc<虚线>>, 线段方向: 相对方向) -> Self {
|
||||
Self {
|
||||
序号: 0,
|
||||
标识,
|
||||
线段方向,
|
||||
元素: 基础序列,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn 图表标题(&self) -> String {
|
||||
self.标识.clone()
|
||||
}
|
||||
|
||||
/// 文 — 取特征序列元素中分型特征值最大/最小的文分型
|
||||
pub fn 文(&self) -> Rc<分型> {
|
||||
if self.线段方向.是否向上() {
|
||||
// 向上 → 取最大的文
|
||||
self.元素
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.文
|
||||
.分型特征值
|
||||
.partial_cmp(&b.文.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.map(|x| Rc::clone(&x.文))
|
||||
.unwrap_or_else(|| Rc::clone(&self.元素[0].文))
|
||||
} else {
|
||||
// 向下 → 取最小的文
|
||||
self.元素
|
||||
.iter()
|
||||
.min_by(|a, b| {
|
||||
a.文
|
||||
.分型特征值
|
||||
.partial_cmp(&b.文.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.map(|x| Rc::clone(&x.文))
|
||||
.unwrap_or_else(|| Rc::clone(&self.元素[0].文))
|
||||
}
|
||||
}
|
||||
|
||||
/// 武 — 取特征序列元素中分型特征值最大/最小的武分型
|
||||
pub fn 武(&self) -> Rc<分型> {
|
||||
if self.线段方向.是否向上() {
|
||||
self.元素
|
||||
.iter()
|
||||
.max_by(|a, b| {
|
||||
a.武
|
||||
.分型特征值
|
||||
.partial_cmp(&b.武.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.map(|x| Rc::clone(&x.武))
|
||||
.unwrap_or_else(|| Rc::clone(&self.元素[0].武))
|
||||
} else {
|
||||
self.元素
|
||||
.iter()
|
||||
.min_by(|a, b| {
|
||||
a.武
|
||||
.分型特征值
|
||||
.partial_cmp(&b.武.分型特征值)
|
||||
.unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
.map(|x| Rc::clone(&x.武))
|
||||
.unwrap_or_else(|| Rc::clone(&self.元素[0].武))
|
||||
}
|
||||
}
|
||||
|
||||
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, 待添加虚线: Rc<虚线>) -> Result<(), String> {
|
||||
if 待添加虚线.方向() == self.线段方向 {
|
||||
return Err("添加方向与线段方向相同".into());
|
||||
}
|
||||
self.元素.push(待添加虚线);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// 新建特征序列元素
|
||||
pub fn 新建(虚线序列: Vec<Rc<虚线>>, 线段方向: 相对方向) -> Self {
|
||||
let 标识 = format!("特征<虚线>");
|
||||
Self::new(标识, 虚线序列, 线段方向)
|
||||
}
|
||||
|
||||
/// 静态分析 — 从虚线序列生成特征序列元素列表
|
||||
pub fn 静态分析(
|
||||
虚线序列: &[Rc<虚线>],
|
||||
线段方向: 相对方向,
|
||||
四象: &str,
|
||||
是否忽视: bool,
|
||||
) -> Vec<Rc<线段特征>> {
|
||||
let mut 结果: Vec<Rc<线段特征>> = Vec::new();
|
||||
|
||||
// 需要被合并的方向集合
|
||||
let 需要合并: Vec<相对方向> = match 四象 {
|
||||
"老阳" | "老阴" if !是否忽视 => vec![相对方向::顺, 相对方向::逆, 相对方向::同],
|
||||
_ => vec![相对方向::顺, 相对方向::同],
|
||||
};
|
||||
|
||||
for 虚线 in 虚线序列 {
|
||||
// 情况1:方向相同(可能触发分型替换)
|
||||
if 虚线.方向() == 线段方向 {
|
||||
if 结果.len() >= 3 {
|
||||
let 左 = Rc::clone(&结果[结果.len() - 3]);
|
||||
let 中 = Rc::clone(&结果[结果.len() - 2]);
|
||||
let 右 = Rc::clone(&结果[结果.len() - 1]);
|
||||
|
||||
if let Some(结构) = 分型结构::分析(&*左, &*中, &*右, true, true) {
|
||||
let 应替换 = (线段方向 == 相对方向::向上
|
||||
&& 结构 == 分型结构::顶
|
||||
&& 虚线.高() > 中.高())
|
||||
|| (线段方向 == 相对方向::向下
|
||||
&& 结构 == 分型结构::底
|
||||
&& 虚线.低() < 中.低());
|
||||
|
||||
if 应替换 {
|
||||
let 小号虚线 = if 中.元素[0].序号 < 右.元素[0].序号 {
|
||||
&中.元素[0]
|
||||
} else {
|
||||
&右.元素[0]
|
||||
};
|
||||
let 大号虚线 = if 中.元素.last().unwrap().序号
|
||||
> 右.元素.last().unwrap().序号
|
||||
{
|
||||
&中.元素[中.元素.len() - 1]
|
||||
} else {
|
||||
&右.元素[右.元素.len() - 1]
|
||||
};
|
||||
let fake = 虚线::创建笔(
|
||||
Rc::clone(&小号虚线.文),
|
||||
Rc::clone(&大号虚线.武),
|
||||
false,
|
||||
);
|
||||
结果.pop();
|
||||
let idx = 结果.len() - 1;
|
||||
结果[idx] =
|
||||
Rc::new(Self::新建(vec![Rc::new(fake)], 线段方向));
|
||||
}
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// 情况2:方向不同(执行特征序列的合并/添加)
|
||||
if 结果.is_empty() {
|
||||
结果.push(Rc::new(Self::新建(vec![Rc::clone(虚线)], 线段方向)));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 检查与最后一个特征序列的方向关系
|
||||
let 最后_idx = 结果.len() - 1;
|
||||
let 之前线段特征 = &*结果[最后_idx];
|
||||
if 需要合并.contains(&相对方向::分析(
|
||||
之前线段特征.高(),
|
||||
之前线段特征.低(),
|
||||
虚线.高(),
|
||||
虚线.低(),
|
||||
)) {
|
||||
// Clone-modify-replace
|
||||
let mut 新特征 = (*结果[最后_idx]).clone();
|
||||
let _ = 新特征.添加(Rc::clone(虚线));
|
||||
结果[最后_idx] = Rc::new(新特征);
|
||||
} else {
|
||||
结果.push(Rc::new(Self::新建(vec![Rc::clone(虚线)], 线段方向)));
|
||||
}
|
||||
}
|
||||
|
||||
结果
|
||||
}
|
||||
|
||||
/// 获取分型序列
|
||||
pub fn 获取分型序列(特征序列: &[Rc<线段特征>]) -> Vec<特征分型> {
|
||||
let mut 结果 = Vec::new();
|
||||
if 特征序列.len() < 3 {
|
||||
return 结果;
|
||||
}
|
||||
for i in 2..特征序列.len() {
|
||||
let 左 = Rc::clone(&特征序列[i - 2]);
|
||||
let 中 = Rc::clone(&特征序列[i - 1]);
|
||||
let 右 = Rc::clone(&特征序列[i]);
|
||||
|
||||
if let Some(结构) = 分型结构::分析_对象(
|
||||
&*左 as &dyn crate::types::fractal::有高低,
|
||||
&*中 as &dyn crate::types::fractal::有高低,
|
||||
&*右 as &dyn crate::types::fractal::有高低,
|
||||
true,
|
||||
true,
|
||||
) {
|
||||
结果.push(特征分型::new(左, 中, 右, 结构));
|
||||
}
|
||||
}
|
||||
结果
|
||||
}
|
||||
}
|
||||
|
||||
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.标识, self.线段方向)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"{}<{}, {}, {}, {}>",
|
||||
self.标识,
|
||||
self.线段方向,
|
||||
self.文(),
|
||||
self.武(),
|
||||
self.元素.len()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 买卖点类型 —— 六类买卖点(一二三 + T1/T1P/T2/T2S/T3A/T3B)
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum 买卖点类型 {
|
||||
#[serde(rename = "一买")]
|
||||
一买,
|
||||
#[serde(rename = "一卖")]
|
||||
一卖,
|
||||
#[serde(rename = "二买")]
|
||||
二买,
|
||||
#[serde(rename = "二卖")]
|
||||
二卖,
|
||||
#[serde(rename = "三买")]
|
||||
三买,
|
||||
#[serde(rename = "三卖")]
|
||||
三卖,
|
||||
#[serde(rename = "T1买")]
|
||||
T1买,
|
||||
#[serde(rename = "T1卖")]
|
||||
T1卖,
|
||||
#[serde(rename = "T1P买")]
|
||||
T1P买,
|
||||
#[serde(rename = "T1P卖")]
|
||||
T1P卖,
|
||||
#[serde(rename = "T2买")]
|
||||
T2买,
|
||||
#[serde(rename = "T2卖")]
|
||||
T2卖,
|
||||
#[serde(rename = "T2S买")]
|
||||
T2S买,
|
||||
#[serde(rename = "T2S卖")]
|
||||
T2S卖,
|
||||
#[serde(rename = "T3A买")]
|
||||
T3A买,
|
||||
#[serde(rename = "T3A卖")]
|
||||
T3A卖,
|
||||
#[serde(rename = "T3B买")]
|
||||
T3B买,
|
||||
#[serde(rename = "T3B卖")]
|
||||
T3B卖,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 买卖点类型 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::一买 => write!(f, "一买"),
|
||||
Self::一卖 => write!(f, "一卖"),
|
||||
Self::二买 => write!(f, "二买"),
|
||||
Self::二卖 => write!(f, "二卖"),
|
||||
Self::三买 => write!(f, "三买"),
|
||||
Self::三卖 => write!(f, "三卖"),
|
||||
Self::T1买 => write!(f, "T1买"),
|
||||
Self::T1卖 => write!(f, "T1卖"),
|
||||
Self::T1P买 => write!(f, "T1P买"),
|
||||
Self::T1P卖 => write!(f, "T1P卖"),
|
||||
Self::T2买 => write!(f, "T2买"),
|
||||
Self::T2卖 => write!(f, "T2卖"),
|
||||
Self::T2S买 => write!(f, "T2S买"),
|
||||
Self::T2S卖 => write!(f, "T2S卖"),
|
||||
Self::T3A买 => write!(f, "T3A买"),
|
||||
Self::T3A卖 => write!(f, "T3A卖"),
|
||||
Self::T3B买 => write!(f, "T3B买"),
|
||||
Self::T3B卖 => write!(f, "T3B卖"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 买卖点类型 {
|
||||
pub fn 是买点(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::一买
|
||||
| Self::二买
|
||||
| Self::三买
|
||||
| Self::T1买
|
||||
| Self::T1P买
|
||||
| Self::T2买
|
||||
| Self::T2S买
|
||||
| Self::T3A买
|
||||
| Self::T3B买
|
||||
)
|
||||
}
|
||||
|
||||
pub fn 是卖点(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::一卖
|
||||
| Self::二卖
|
||||
| Self::三卖
|
||||
| Self::T1卖
|
||||
| Self::T1P卖
|
||||
| Self::T2卖
|
||||
| Self::T2S卖
|
||||
| Self::T3A卖
|
||||
| Self::T3B卖
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 相对方向 —— K线之间的相对位置关系
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum 相对方向 {
|
||||
#[serde(rename = "交叠向上")]
|
||||
向上,
|
||||
#[serde(rename = "交叠向下")]
|
||||
向下,
|
||||
#[serde(rename = "向上缺口")]
|
||||
向上缺口,
|
||||
#[serde(rename = "向下缺口")]
|
||||
向下缺口,
|
||||
#[serde(rename = "衔接向上")]
|
||||
衔接向上,
|
||||
#[serde(rename = "衔接向下")]
|
||||
衔接向下,
|
||||
#[serde(rename = "顺序包含")]
|
||||
顺,
|
||||
#[serde(rename = "逆序包含")]
|
||||
逆,
|
||||
#[serde(rename = "相同包含")]
|
||||
同,
|
||||
}
|
||||
|
||||
impl 相对方向 {
|
||||
pub fn 翻转(&self) -> Self {
|
||||
match self {
|
||||
Self::向上 => Self::向下,
|
||||
Self::向下 => Self::向上,
|
||||
Self::向上缺口 => Self::向下缺口,
|
||||
Self::向下缺口 => Self::向上缺口,
|
||||
Self::衔接向上 => Self::衔接向下,
|
||||
Self::衔接向下 => Self::衔接向上,
|
||||
Self::顺 => Self::逆,
|
||||
Self::逆 => Self::顺,
|
||||
other => *other,
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
if (前高 - 后高).abs() < f64::EPSILON && (前低 - 后低).abs() < f64::EPSILON {
|
||||
return Self::同;
|
||||
}
|
||||
if 前高 > 后高 && 前低 > 后低 {
|
||||
if (前低 - 后高).abs() < f64::EPSILON {
|
||||
return Self::衔接向下;
|
||||
}
|
||||
if 前低 > 后高 {
|
||||
return Self::向下缺口;
|
||||
}
|
||||
return Self::向下;
|
||||
}
|
||||
if 前高 < 后高 && 前低 < 后低 {
|
||||
if (前高 - 后低).abs() < f64::EPSILON {
|
||||
return Self::衔接向上;
|
||||
}
|
||||
if 前高 < 后低 {
|
||||
return Self::向上缺口;
|
||||
}
|
||||
return Self::向上;
|
||||
}
|
||||
if 前高 >= 后高 && 前低 <= 后低 {
|
||||
return Self::顺;
|
||||
}
|
||||
if 前高 <= 后高 && 前低 >= 后低 {
|
||||
return Self::逆;
|
||||
}
|
||||
panic!("无法识别的方向: 前({},{}), 后({},{})", 前高, 前低, 后高, 后低);
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 相对方向 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::向上 => write!(f, "相对方向.向上"),
|
||||
Self::向下 => write!(f, "相对方向.向下"),
|
||||
Self::向上缺口 => write!(f, "相对方向.向上缺口"),
|
||||
Self::向下缺口 => write!(f, "相对方向.向下缺口"),
|
||||
Self::衔接向上 => write!(f, "相对方向.衔接向上"),
|
||||
Self::衔接向下 => write!(f, "相对方向.衔接向下"),
|
||||
Self::顺 => write!(f, "相对方向.顺"),
|
||||
Self::逆 => write!(f, "相对方向.逆"),
|
||||
Self::同 => write!(f, "相对方向.同"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// 分型结构 —— 三根K线构成的结构形态
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum 分型结构 {
|
||||
#[serde(rename = "三连向上")]
|
||||
上,
|
||||
#[serde(rename = "三连向下")]
|
||||
下,
|
||||
#[serde(rename = "顶分型")]
|
||||
顶,
|
||||
#[serde(rename = "底分型")]
|
||||
底,
|
||||
#[serde(rename = "向右扩散")]
|
||||
散,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 分型结构 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::上 => write!(f, "上"),
|
||||
Self::下 => write!(f, "下"),
|
||||
Self::顶 => write!(f, "顶"),
|
||||
Self::底 => write!(f, "底"),
|
||||
Self::散 => write!(f, "散"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl 分型结构 {
|
||||
/// 分析三根K线构成的分型结构(泛型版本)
|
||||
pub fn 分析(
|
||||
左: &impl 有高低,
|
||||
中: &impl 有高低,
|
||||
右: &impl 有高低,
|
||||
可以逆序包含: bool,
|
||||
忽视顺序包含: bool,
|
||||
) -> Option<Self> {
|
||||
Self::分析_内部(左.高(), 左.低(), 中.高(), 中.低(), 右.高(), 右.低(), 可以逆序包含, 忽视顺序包含)
|
||||
}
|
||||
|
||||
/// 分析三元素构成的分型结构(trait object 版本)
|
||||
pub fn 分析_对象(
|
||||
左: &dyn 有高低,
|
||||
中: &dyn 有高低,
|
||||
右: &dyn 有高低,
|
||||
可以逆序包含: bool,
|
||||
忽视顺序包含: bool,
|
||||
) -> Option<Self> {
|
||||
Self::分析_内部(左.高(), 左.低(), 中.高(), 中.低(), 右.高(), 右.低(), 可以逆序包含, 忽视顺序包含)
|
||||
}
|
||||
|
||||
fn 分析_内部(
|
||||
左高: f64, 左低: f64,
|
||||
中高: f64, 中低: f64,
|
||||
右高: f64, 右低: f64,
|
||||
可以逆序包含: bool,
|
||||
忽视顺序包含: bool,
|
||||
) -> Option<Self> {
|
||||
use crate::types::direction::相对方向;
|
||||
|
||||
let 左中关系 = 相对方向::分析(左高, 左低, 中高, 中低);
|
||||
let 中右关系 = 相对方向::分析(中高, 中低, 右高, 右低);
|
||||
|
||||
let 向上类 = |d: 相对方向| d.是否向上();
|
||||
let 向下类 = |d: 相对方向| d.是否向下();
|
||||
|
||||
match (左中关系, 中右关系) {
|
||||
// 顺序包含 — 忽视时可以绕过
|
||||
(相对方向::顺, _) if !忽视顺序包含 => {
|
||||
panic!("顺序包含: {:?} {:?}", 左中关系, 中右关系);
|
||||
}
|
||||
(_, 相对方向::顺) if !忽视顺序包含 => {
|
||||
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!(
|
||||
"无法识别的分型结构: 左中={:?}, 中右={:?}",
|
||||
左中关系, 中右关系
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Trait for types that have 高 and 低 (used by 分型结构::分析)
|
||||
pub trait 有高低 {
|
||||
fn 高(&self) -> f64;
|
||||
fn 低(&self) -> f64;
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
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 { 高, 低 }
|
||||
}
|
||||
|
||||
/// 在 [起点, 终点] 区间内居中截取一个子区间
|
||||
pub fn 居中截取区间(起点: f64, 终点: f64, 比例: f64) -> Option<Self> {
|
||||
let (起点, 终点) = if 起点 <= 终点 {
|
||||
(起点, 终点)
|
||||
} else {
|
||||
(终点, 起点)
|
||||
};
|
||||
|
||||
let 总长 = 终点 - 起点;
|
||||
let 偏移 = 总长 * 比例;
|
||||
let 中心 = (起点 + 终点) / 2.0;
|
||||
|
||||
let 下界 = 中心 - 偏移;
|
||||
let 上界 = 中心 + 偏移;
|
||||
|
||||
if 下界 > 终点 || 上界 < 起点 {
|
||||
return None;
|
||||
}
|
||||
|
||||
let 下界 = 下界.max(起点);
|
||||
let 上界 = 上界.min(终点);
|
||||
|
||||
Some(Self::new(上界.max(下界), 上界.min(下界)))
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for 缺口 {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"缺口区间<{} <=> {}>",
|
||||
crate::utils::format_f64_g(self.低),
|
||||
crate::utils::format_f64_g(self.高)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
pub mod bsp_type;
|
||||
pub mod direction;
|
||||
pub mod fractal;
|
||||
pub mod gap;
|
||||
|
||||
pub use bsp_type::买卖点类型;
|
||||
pub use direction::相对方向;
|
||||
pub use fractal::分型结构;
|
||||
pub use gap::缺口;
|
||||
@@ -0,0 +1,27 @@
|
||||
use chrono::DateTime;
|
||||
|
||||
/// 将多种类型统一转为时间戳 (Unix epoch 秒)
|
||||
pub fn 转化为时间戳(ts: &str) -> Option<i64> {
|
||||
// 尝试解析为整数时间戳
|
||||
if let Ok(v) = ts.parse::<i64>() {
|
||||
return Some(v);
|
||||
}
|
||||
// 尝试解析为浮点时间戳
|
||||
if let Ok(v) = ts.parse::<f64>() {
|
||||
return Some(v as i64);
|
||||
}
|
||||
// 尝试 ISO 格式日期字符串
|
||||
if let Ok(dt) = DateTime::parse_from_rfc3339(ts) {
|
||||
return Some(dt.timestamp());
|
||||
}
|
||||
// 尝试 "YYYY-MM-DD HH:MM:SS" 格式
|
||||
if let Ok(dt) = chrono::NaiveDateTime::parse_from_str(ts, "%Y-%m-%d %H:%M:%S") {
|
||||
return Some(dt.and_utc().timestamp());
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// 将多种类型转为时间戳数字 (i64)
|
||||
pub fn 转化为时间戳_数字(ts: &str) -> Option<i64> {
|
||||
转化为时间戳(ts)
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
/// Format f64 with Python :g semantics — strip trailing zeros, no scientific notation for common values
|
||||
pub fn format_f64_g(value: f64) -> String {
|
||||
if value.is_nan() {
|
||||
return "nan".to_string();
|
||||
}
|
||||
if value.is_infinite() {
|
||||
return if value > 0.0 { "inf".to_string() } else { "-inf".to_string() };
|
||||
}
|
||||
|
||||
// Use high precision then trim trailing zeros
|
||||
let s = format!("{:.15}", value);
|
||||
let s = s.trim_end_matches('0');
|
||||
s.trim_end_matches('.').to_string()
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
pub mod datetime;
|
||||
pub mod format;
|
||||
|
||||
pub use datetime::{转化为时间戳, 转化为时间戳_数字};
|
||||
pub use format::format_f64_g;
|
||||
Reference in New Issue
Block a user