chanlun 26.5.2

基于缠论(缠中说禅)理论的量化技术分析核心库,支持流式数据处理和多周期联立分析。
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
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
/*
 * 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::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 买卖点信息: std::cell::RefCell<Vec<String>>,
}

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线),
            买卖点信息: std::cell::RefCell::new(self.买卖点信息.borrow().clone()),
        }
    }

    /// 与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 {
        if.is_nan() ||.is_nan() {
            panic!("缠K高/低不能为NaN: 高={高}, 低={低}");
        }
        assert!(>=, "缠K高必须>=低: 高={高}, 低={低}");

        let 周期 = 普k.周期;
        let 标识 = 普k.标识.clone();

        let mut 当前 = Self {
            序号: 0,
            时间戳,,,
            方向,
            分型: 结构,
            周期,
            标识,
            分型特征值:,
            原始起始序号: 原始序号,
            原始结束序号: 原始序号,
            标的K线: 普k,
            买卖点信息: std::cell::RefCell::new(Vec::new()),
        };

        if let Some(之前) = 之前 {
            当前.序号 = 之前.序号 + 1;
            let 关系 = 相对方向::分析(之前., 之前., 当前., 当前.);
            if 关系.是否包含() {
                panic!(
                    "创建缠K 包含关系: {:?}\n  之前: {}\n  当前: {}",
                    关系, 之前, 当前
                );
            }
        }
        当前
    }

    /// 兼并(合并)处理 — 缠论包含处理的核心算法
    ///
    /// 返回 (新缠K, 模式) — 模式: "添加"/"替换"/None
    pub fn 兼并(
        之前缠K: Option<&缠论K线>,
        当前缠K: &mut 缠论K线,
        当前普K: &Rc<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::clone(当前普K),
                Some(当前缠K),
            );
            新缠K.序号 = 当前缠K.序号 + 1;
            return (Some(Rc::new(新缠K)), Some("添加".into()));
        }

        // 重复提交检测 — 当序号相同时认为是重复提交K线
        if 当前普K.序号 == 当前缠K.原始结束序号 {
            return (None, None);
        }

        // 序号连续性检查
        if 当前普K.序号 - 1 != 当前缠K.原始结束序号 && 当前普K.序号 != 当前缠K.原始结束序号
        {
            panic!(
                "兼并: 不可追加不连续元素 缠K.原始结束序号: {}, 当前普K.序号: {}",
                当前缠K.原始结束序号, 当前普K.序号
            );
        }

        // 包含关系 — 原地合并到当前缠K
        let 取值函数: fn(f64, f64) -> f64 = if let Some(之前) = 之前缠K {
            if 相对方向::分析(之前., 之前., 当前缠K., 当前缠K.).是否向下()
            {
                f64::min
            } else {
                f64::max
            }
        } else {
            f64::max
        };

        // 逆序包含时更新时间和标的K线
        if 关系 != 相对方向::{
            当前缠K.时间戳 = 当前普K.时间戳;
            当前缠K.标的K线 = Rc::clone(当前普K);
        }
        当前缠K.= 取值函数(当前缠K., 当前普K.);
        当前缠K.= 取值函数(当前缠K., 当前普K.);
        当前缠K.原始结束序号 = 当前普K.序号;
        当前缠K.方向 = 当前普K.方向();

        if let Some(之前) = 之前缠K {
            当前缠K.序号 = 之前.序号 + 1;
        }

        if 配置.缠K合并替换 {
            (Some(Rc::new(当前缠K.镜像())), Some("替换".into()))
        } else {
            (None, None)
        }
    }

    /// 完整的缠K分析 — 普K → 缠K + 分型
    ///
    /// 返回 (状态, 形态)
    pub fn 分析(
        mut 当前K线: 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线);
            普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线));
            } 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线));
            }
        }

        // ---- 阶段2: 缠K合并 ----
        let 状态: String;
        let 当前K线_ref: &Rc<K线> = 普K序列.last().unwrap();

        if !缠K序列.is_empty() {
            let len = 缠K序列.len();
            let (左边, 右边) = 缠K序列.split_at_mut(len - 1);
            let 之前缠K: Option<&缠论K线> = 左边.last().map(|rc| Rc::as_ref(rc));
            let 最后一个缠K_mut = Rc::make_mut(&mut 右边[0]);
            let (新缠K, 模式) = Self::兼并(之前缠K, 最后一个缠K_mut, 当前K线_ref, 配置);

            if let Some(k) = 新缠K {
                match 模式.as_deref() {
                    Some("添加") => {
                        缠K序列.push(k);
                        状态 = "创建".into();
                    }
                    Some("替换") => {
                        缠K序列.pop();
                        缠K序列.push(k);
                        状态 = "替换".into();
                    }
                    _ => {
                        状态 = "兼并".into();
                    }
                }
            } else {
                状态 = "兼并".into();
            }
        } else {
            let 新缠K = Self::创建缠K(
                当前K线_ref.时间戳,
                当前K线_ref.,
                当前K线_ref.,
                当前K线_ref.方向(),
                None,
                当前K线_ref.序号,
                Rc::clone(普K序列.last().unwrap()),
                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!(结构, 分型结构::| 分型结构::) {
                // Python: 形态 = 分型(中, 右, None) — 左=中K线, 中=右K线, 右=None
                Rc::new(分型::new(
                    Some(Rc::clone(&缠K序列[idx - 2])),
                    Rc::clone(&缠K序列[idx - 1]),
                    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 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线::分析(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 pk1 = make_普K(1000, 100.0, 110.0, 95.0, 105.0, 0);
        let 状态1 = 缠论K线::分析(pk1, &mut 缠K序列, &mut 普K序列, &config);
        assert_eq!(状态1.0, "新建");

        let pk2 = make_普K(1001, 105.0, 115.0, 102.0, 112.0, 1);
        let 状态2 = 缠论K线::分析(pk2, &mut 缠K序列, &mut 普K序列, &config);
        assert!(状态2.1.is_none()); // 仍不够

        let pk3 = make_普K(1002, 112.0, 113.0, 100.0, 103.0, 2);
        let (_状态3, 形态) = 缠论K线::分析(pk3, &mut 缠K序列, &mut 普K序列, &config);
        assert!(形态.is_some()); // 分型产生了
    }
}