chanlun 26.5.2

基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。
Documentation
/*
 * MIT License
 *
 * Copyright (c) 2026 YuYuKunKun
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

use crate::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,))
            .expect("获取MACD: 始K线不在序列中");
        let 终_idx = K线序列
            .iter()
            .position(|k| std::ptr::eq(*k,))
            .expect("获取MACD: 终K线不在序列中");
        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));
    }
}