chanlun 26.6.4

基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
/*
 * 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::info;
use crate::types::相对方向;
use byteorder::{BigEndian, ReadBytesExt, WriteBytesExt};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::io::Write;
use std::sync::Arc;

mod rwlock_container_serde {
    use parking_lot::RwLock;
    use serde::{Deserialize, Deserializer, Serialize, Serializer};

    /// Serde 序列化辅助(RwLock<指标容器> → 序列化器)
    pub fn serialize<S>(
        val: &RwLock<crate::indicators::指标容器>,
        ser: S,
    ) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        val.read().serialize(ser)
    }

    /// Serde 反序列化辅助(反序列化器 → RwLock<指标容器>)
    pub fn deserialize<'de, D>(de: D) -> Result<RwLock<crate::indicators::指标容器>, D::Error>
    where
        D: Deserializer<'de>,
    {
        Ok(RwLock::new(crate::indicators::指标容器::deserialize(
            de,
        )?))
    }
}

/// 原始K线 (OHLCV + 指标容器)
///
/// 所有指标统一通过 `指标容器` 访问。指标容器使用 RwLock 实现内部可变性,
/// 使 `计算并挂载` 能以 `&K线` 共享引用写入指标值。
#[derive(Debug, Serialize, Deserialize)]
#[serde(default)]
pub struct K线 {
    /// 品种标识(如 "btcusd")
    pub 标识: String,
    /// K线序号(在序列中的位置)
    pub 序号: i64,
    /// 周期(秒),如 300=5分钟, 86400=日线
    pub 周期: i64,
    /// Unix 时间戳(秒)
    pub 时间戳: i64,
    /// 最高价
    pub : f64,
    /// 最低价
    pub : f64,
    /// 开盘价
    pub 开盘价: f64,
    /// 收盘价
    pub 收盘价: f64,
    /// 成交量
    pub 成交量: f64,
    /// 指标容器(MACD/RSI/KDJ/BOLL/均线等)
    #[serde(with = "rwlock_container_serde")]
    pub 指标: RwLock<指标容器>,
}

impl Default for K线 {
    fn default() -> Self {
        Self {
            标识: "bar".into(),
            序号: 0,
            周期: 60,
            时间戳: 0,: 0.0,: 0.0,
            开盘价: 0.0,
            收盘价: 0.0,
            成交量: 0.0,
            指标: RwLock::new(指标容器::new()),
        }
    }
}

impl Clone for K线 {
    fn clone(&self) -> Self {
        Self {
            标识: self.标识.clone(),
            序号: self.序号,
            周期: self.周期,
            时间戳: self.时间戳,: self.,: self.,
            开盘价: self.开盘价,
            收盘价: self.收盘价,
            成交量: self.成交量,
            指标: RwLock::new(self.指标.read().clone()),
        }
    }
}

impl K线 {
    /// 方向:阳(收盘 > 开盘)为向上,否则向下
    pub fn 方向(&self) -> 相对方向 {
        if self.开盘价 < self.收盘价 {
            相对方向::向上
        } else {
            相对方向::向下
        }
    }

    /// 序列化为大端字节序 48 字节
    /// 格式: >6d (时间戳, 开盘价, 高, 低, 收盘价, 成交量)
    /// TODO: 对齐 Python round(x, 8) 再序列化
    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(字节组, 周期, 标识)
    }

    /// 解析原始数据 — 只提取时间戳+OHLCV,不构造 K线
    pub fn 解析原始数据(字节组: &[u8]) -> Option<(i64, f64, f64, f64, f64, f64)> {
        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((时间戳,,,,,))
    }

    /// 创建普通K线
    #[allow(clippy::too_many_arguments)]
    pub fn 创建普K(
        标识: &str,
        时间戳: i64,
        开盘价: f64,
        最高价: f64,
        最低价: f64,
        收盘价: f64,
        成交量: f64,
        序号: i64,
        周期: i64,
    ) -> Self {
        Self {
            标识: 标识.to_string(),
            序号,
            周期,
            时间戳,: 最高价,: 最低价,
            开盘价,
            收盘价,
            成交量,
            指标: RwLock::new(指标容器::new()),
        }
    }

    /// 保存K线序列到 DAT 文件
    pub fn 保存到DAT文件(路径: &str, K线序列: &[&Self]) -> std::io::Result<()> {
        info!("保存到DAT文件: {}", 路径);
        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(macd) = k.指标.read().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])
    }

    /// 结构化相等校验 — 比对各字段,浮点字段使用容差比较,返回 (是否相等, 差异描述)
    pub fn 相等(&self, other: &Self, 浮点容差: f64) -> (bool, String) {
        if self.标识 != other.标识 {
            return (
                false,
                format!("K线: [标识] 不等 A={},B={}", self.标识, other.标识),
            );
        }
        if self.序号 != other.序号 {
            return (
                false,
                format!("K线: [序号] 不等 A={},B={}", self.序号, other.序号),
            );
        }
        if self.周期 != other.周期 {
            return (
                false,
                format!("K线: [周期] 不等 A={},B={}", self.周期, other.周期),
            );
        }
        if self.时间戳 != other.时间戳 {
            return (
                false,
                format!("K线: [时间戳] 不等 A={},B={}", self.时间戳, other.时间戳),
            );
        }
        let 浮点字段 = [
            ("", self., other.),
            ("", self., other.),
            ("开盘价", self.开盘价, other.开盘价),
            ("收盘价", self.收盘价, other.收盘价),
            ("成交量", self.成交量, other.成交量),
        ];
        for (, a, b) in &浮点字段 {
            if (a - b).abs() > 浮点容差 {
                return (
                    false,
                    format!("K线: [{名}] 浮点超限 容差={浮点容差:.2e} A={a:.10},B={b:.10}"),
                );
            }
        }
        (true, "K线: 全部字段一致".into())
    }

    /// 根据当前K线和方向生成下一根K线(与 chan.py 对齐)
    pub fn 根据当前K线生成新K线(&self, 方向: 相对方向, 居中: bool) -> Self {
        let 高低差 = self.- self.;
        let 偏移 = if 居中 {
            高低差 * 0.5
        } else {
            let lo = (高低差 * 0.1279) as i64;
            let hi = (高低差 * 0.883) as i64;
            if hi > lo {
                fastrand::i64(lo..=hi) as f64
            } else {
                lo as f64
            }
        };
        let 缺口偏移 = if 居中 {
            高低差 * 1.5
        } else {
            let lo = (高低差 * 1.1279) as i64;
            let hi = (高低差 * 1.883) as i64;
            if hi > lo {
                fastrand::i64(lo..=hi) as f64
            } else {
                lo as f64
            }
        };
        let (,) = match 方向 {
            相对方向::向上 => (self.+ 偏移, self.+ 偏移),
            相对方向::向下 => (self.- 偏移, self.- 偏移),
            相对方向::向上缺口 => (self.+ 缺口偏移, self.+ 缺口偏移),
            相对方向::向下缺口 => (self.- 缺口偏移, self.- 缺口偏移),
            相对方向::衔接向上 => {
                let off = 高低差;
                (self.+ off, self.)
            }
            相对方向::衔接向下 => {
                let off = 高低差;
                (self., self.- off)
            }
            _ => (self., self.),
        };
        let 小数点 = [self.开盘价, self., self., self.收盘价]
            .iter()
            .map(|v| {
                let s = format!("{v}");
                s.split('.').nth(1).map(|d| d.len()).unwrap_or(0)
            })
            .max()
            .unwrap_or(2);
        let round = |v: f64| -> f64 {
            let scale = 10_f64.powi(小数点 as i32);
            (v * scale).round() / scale
        };
        let= round(+ (-) * fastrand::f64());
        let= round(+ (-) * fastrand::f64());
        Self::创建普K(
            &self.标识,
            self.时间戳 + self.周期,,
            round(),
            round(),,
            998.0 * fastrand::f64(),
            self.序号 + 1,
            self.周期,
        )
    }

    /// 截取Arc<K线>序列中从始到终的片段
    pub fn 截取rc(序列: &[Arc<Self>], : &Arc<Self>, : &Arc<Self>) -> Vec<Arc<Self>> {
        let 始_ptr = Arc::as_ptr();
        let 终_ptr = Arc::as_ptr();
        let 始_idx = 序列.iter().position(|k| Arc::as_ptr(k) == 始_ptr);
        let 终_idx = 序列.iter().position(|k| Arc::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::*;
    use crate::{error, info, warn};

    #[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));
    }

    // ---- 根据当前K线生成新K线 ----

    #[test]
    fn test_生成K线_居中向上() {
        let bar = K线::创建普K(
            "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
        );
        let new = bar.根据当前K线生成新K线(相对方向::向上, true);
        // 居中: 偏移 = (50200-49800)*0.5 = 200
        assert!((new.- 50400.0).abs() < 1.0); // 50200 + 200
        assert!((new.- 50000.0).abs() < 1.0); // 49800 + 200
        assert_eq!(new.序号, 1);
        assert_eq!(new.时间戳, 1300);
    }

    #[test]
    fn test_生成K线_居中向下() {
        let bar = K线::创建普K(
            "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
        );
        let new = bar.根据当前K线生成新K线(相对方向::向下, true);
        assert!((new.- 50000.0).abs() < 1.0); // 50200 - 200
        assert!((new.- 49600.0).abs() < 1.0); // 49800 - 200
    }

    #[test]
    fn test_生成K线_居中向上缺口() {
        let bar = K线::创建普K(
            "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
        );
        let new = bar.根据当前K线生成新K线(相对方向::向上缺口, true);
        // 居中缺口: 偏移 = 400*1.5 = 600
        assert!((new.- 50800.0).abs() < 1.0); // 50200 + 600
        assert!((new.- 50400.0).abs() < 1.0); // 49800 + 600
    }

    #[test]
    fn test_生成K线_衔接向上() {
        let bar = K线::创建普K(
            "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
        );
        let new = bar.根据当前K线生成新K线(相对方向::衔接向上, true);
        let 高低差 = 50200.0 - 49800.0;
        assert!((new.- (50200.0 + 高低差)).abs() < 1.0);
        assert!((new.- 50200.0).abs() < 1.0); // 衔接向上: 低 = 原高
    }

    #[test]
    fn test_生成K线_衔接向下() {
        let bar = K线::创建普K(
            "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
        );
        let new = bar.根据当前K线生成新K线(相对方向::衔接向下, true);
        assert!((new.- 49800.0).abs() < 1.0); // 衔接向下: 高 = 原低
    }

    #[test]
    fn test_生成K线_非居中随机范围() {
        let bar = K线::创建普K(
            "test", 1000, 50000.0, 50200.0, 49800.0, 50100.0, 100.0, 0, 300,
        );
        // 非居中:偏移在 [高低差*0.1279, 高低差*0.883] 范围内随机
        for _ in 0..20 {
            let new = bar.根据当前K线生成新K线(相对方向::向上, false);
            assert!(new.> bar., "向上:新高应高于原高");
            assert!(new.> bar., "向上:新低应高于原低");
            let 偏移 = new.- bar.;
            let 高低差 = bar.- bar.;
            let lo = 高低差 * 0.1279;
            let hi = 高低差 * 0.883;
            assert!(
                偏移 >= lo && 偏移 <= hi + 1.0,
                "偏移 {偏移} 应在 [{lo}, {hi}] 范围内"
            );
        }
    }

    // ---- 从序列中机选 ----

    #[test]
    fn test_从序列中机选_可重复() {
        let dirs = vec![相对方向::向上, 相对方向::向下, 相对方向::向上缺口];
        let result = 相对方向::从序列中机选(5, &dirs, true);
        assert_eq!(result.len(), 5);
        for d in &result {
            assert!(dirs.contains(d));
        }
    }

    #[test]
    fn test_从序列中机选_不可重复() {
        let dirs = vec![相对方向::向上, 相对方向::向下, 相对方向::向上缺口];
        let result = 相对方向::从序列中机选(3, &dirs, false);
        assert_eq!(result.len(), 3);
        for (i, d) in result.iter().enumerate() {
            for prev in result[..i].iter() {
                assert_ne!(prev, d, "重复方向: {:?}", d);
            }
        }
    }

    #[test]
    #[should_panic(expected = "数量超过可选方向数")]
    fn test_从序列中机选_数量超限() {
        let dirs = vec![相对方向::向上, 相对方向::向下];
        相对方向::从序列中机选(3, &dirs, false);
    }

    #[test]
    fn test_从序列中机选_空序列() {
        let result = 相对方向::从序列中机选(0, &[], true);
        assert!(result.is_empty());
    }
}