/*
 * 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 parking_lot::RwLock;
use pyo3::prelude::*;
use pyo3::types::{PyDict, PyType};

use crate::algorithm_py::hub_to_py;
use crate::kline_py::bar_to_py;
use crate::structure_py::{dashed_to_py, fractal_to_py, 分型Py};
use std::collections::HashMap;
use std::sync::Arc;
use std::sync::atomic::{AtomicU64, Ordering};

use crate::config_py::缠论配置Py;
use crate::kline_py::{K线Py, 缠论K线Py};
use crate::types_py::买卖点类型Py;

// ========== 基础买卖点 ==========

/// 基础买卖点 — 买卖点的数据结构，描述偏离买入/卖出位置的程度。
///
/// 属性:
///   偏移: int — 买卖点距当前K线的偏差
///   失效偏移: int — 买卖点失效的最大偏差
///   有效性: bool — 买卖点是否仍有效
///   与MACD柱子匹配: bool|None — 是否与MACD柱状图方向匹配
///   与MACD柱子分型匹配: bool|None — 是否与MACD柱分型匹配
#[pyclass(
    name = "基础买卖点",
    module = "chanlun._chanlun",
    subclass,
    from_py_object
)]
#[derive(Clone)]
pub struct 基础买卖点Py {
    pub(crate) inner: chanlun::business::bsp::基础买卖点,
}

#[pymethods]
impl 基础买卖点Py {
    #[new]
    fn new(
        类型: &Bound<'_, 买卖点类型Py>,
        当前K线: &Bound<'_, K线Py>,
        买卖点分型: &Bound<'_, 分型Py>,
        备注: String,
        中枢破位值: f64,
    ) -> Self {
        Self {
            inner: chanlun::business::bsp::基础买卖点::new(
                类型.borrow().inner,
                当前K线.borrow().inner.clone(),
                Arc::clone(&买卖点分型.borrow().inner),
                备注,
                中枢破位值,
            ),
        }
    }

    #[getter]
    fn 备注(&self) -> String {
        self.inner.备注.clone()
    }

    #[setter]
    #[pyo3(name = "备注")]
    fn 设置_备注(&mut self, value: String) {
        self.inner.备注 = value;
    }

    #[getter]
    fn 类型(&self) -> 买卖点类型Py {
        买卖点类型Py {
            inner: self.inner.类型,
        }
    }

    #[getter]
    fn 买卖点分型(&self, py: Python<'_>) -> Py<分型Py> {
        fractal_to_py(py, Arc::clone(&self.inner.买卖点分型))
    }

    #[getter]
    fn 买卖点K线(&self, py: Python<'_>) -> Py<缠论K线Py> {
        crate::kline_py::chan_kline_to_py(py, Arc::clone(&self.inner.买卖点K线))
    }

    #[getter]
    /// 当前K线
    fn 当前K线(&self, py: Python<'_>) -> Py<K线Py> {
        bar_to_py(py, self.inner.当前K线.clone())
    }

    #[getter]
    fn 失效K线(&self, py: Python<'_>) -> Option<Py<K线Py>> {
        self.inner
            .失效K线
            .as_ref()
            .map(|k| bar_to_py(py, Arc::clone(k)))
    }

    #[getter]
    fn 终结K线(&self, py: Python<'_>) -> Option<Py<K线Py>> {
        self.inner
            .终结K线
            .as_ref()
            .map(|k| bar_to_py(py, Arc::clone(k)))
    }

    #[getter]
    /// 破位值
    fn 破位值(&self) -> f64 {
        self.inner.破位值
    }

    #[getter]
    fn 结构(&self) -> Option<crate::types_py::分型结构Py> {
        self.inner
            .结构
            .map(|f| crate::types_py::分型结构Py { inner: f })
    }

    #[getter]
    /// 偏移
    fn 偏移(&self) -> i64 {
        self.inner.偏移()
    }

    #[getter]
    /// 失效偏移
    fn 失效偏移(&self) -> i64 {
        self.inner.失效偏移()
    }

    #[getter]
    /// 有效性
    fn 有效性(&self) -> bool {
        self.inner.有效性()
    }

    #[getter]
    /// 与MACD柱子匹配
    fn 与MACD柱子匹配(&self) -> bool {
        self.inner.与MACD柱子匹配()
    }

    #[getter]
    /// 与MACD柱子分型匹配
    fn 与MACD柱子分型匹配(&self) -> bool {
        self.inner.与MACD柱子分型匹配()
    }

    /// pandas 兼容 — 返回关键标量字段构成的字典
    #[getter]
    fn __dict__(&self, py: Python<'_>) -> PyResult<Py<PyDict>> {
        let dict = PyDict::new(py);
        dict.set_item("备注", self.备注())?;
        dict.set_item("类型", self.类型())?;
        dict.set_item("破位值", self.破位值())?;
        dict.set_item("偏移", self.偏移())?;
        dict.set_item("失效偏移", self.失效偏移())?;
        dict.set_item("有效性", self.有效性())?;
        dict.set_item("与MACD柱子匹配", self.与MACD柱子匹配())?;
        dict.set_item("与MACD柱子分型匹配", self.与MACD柱子分型匹配())?;
        if let Some(v) = self.结构() {
            dict.set_item("结构", v)?;
        }
        Ok(dict.into())
    }

    fn __str__(&self) -> String {
        format!("{}", self.inner)
    }

    fn __repr__(&self) -> String {
        self.__str__()
    }
}

// ========== 买卖点 ==========

/// 买卖点 — 继承 基础买卖点，提供一二三类及扩展类型（T1/T1P/T2/T2S/T3A/T3B）的工厂类方法。
///
/// 类方法（均返回 买卖点 实例）:
///   :meth:`一卖点` / :meth:`一买点` / :meth:`二卖点` / :meth:`二买点` / :meth:`三卖点` / :meth:`三买点`
///   :meth:`T1卖点` / :meth:`T1买点` / :meth:`T1P卖点` / :meth:`T1P买点`
///   :meth:`T2卖点` / :meth:`T2买点` / :meth:`T2S卖点` / :meth:`T2S买点`
///   :meth:`T3A卖点` / :meth:`T3A买点` / :meth:`T3B卖点` / :meth:`T3B买点`
///   :meth:`生成买卖点` — 根据特征和序号路由到对应构造函数
#[pyclass(name = "买卖点", module = "chanlun._chanlun", extends=基础买卖点Py)]
pub struct 买卖点Py;

#[pymethods]
impl 买卖点Py {
    #[classmethod]
    fn 一卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::一卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn 一买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::一买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn 二卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::二卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn 二买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::二买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn 三卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::三卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn 三买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::三买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T1卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T1卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T1买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T1买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T1P卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T1P卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T1P买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T1P买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T2卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T2卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T2买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T2买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T2S卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T2S卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T2S买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T2S买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T3A卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T3A卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T3A买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T3A买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T3B卖点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T3B卖点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    fn T3B买点(
        _cls: &Bound<'_, PyType>,
        买卖点分型: &Bound<'_, 分型Py>,
        当前K线: &Bound<'_, K线Py>,
        标识: &str,
        备注: String,
        中枢破位值: f64,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::T3B买点(
                Arc::clone(&买卖点分型.borrow().inner),
                当前K线.borrow().inner.clone(),
                标识,
                备注,
                中枢破位值,
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }

    #[classmethod]
    #[pyo3(signature = (特征, 序号, 级别, 买卖点分型, 当前缠K))]
    fn 生成买卖点(
        _cls: &Bound<'_, PyType>,
        特征: &str,
        序号: &str,
        级别: &str,
        买卖点分型: &Bound<'_, 分型Py>,
        当前缠K: &Bound<'_, 缠论K线Py>,
        py: Python<'_>,
    ) -> PyResult<Py<Self>> {
        let base = 基础买卖点Py {
            inner: chanlun::business::bsp::买卖点::生成买卖点(
                特征,
                序号,
                级别,
                Arc::clone(&买卖点分型.borrow().inner),
                Arc::clone(&当前缠K.borrow().inner),
            ),
        };
        let init = PyClassInitializer::from(base).add_subclass(买卖点Py);
        Ok(Bound::new(py, init)?.unbind())
    }
}

// ========== 观察者 ==========

/// 观察者 — 单周期分析器，接收 K 线流式输入后逐层计算所有序列。
///
/// 支持 Python 继承（__new__ + __init__ 构造函数）。每根 K 线喂入后
/// 自动增量计算: 缠论K线 → 分型 → 笔 → 线段 → 中枢 等全部层级。
///
/// 构造:
///   观察者(符号, 周期, 配置) — 创建指定周期和配置的分析器
///
/// 属性（只读）:
///   符号: str / 周期: int / 标识: str
///   当前K线: K线|None / 当前缠K: 缠论K线|None
///
/// 序列 getter（只读，返回 list 副本）:
///   普通K线序列 / 缠论K线序列 / 分型序列
///   笔序列 / 笔_中枢序列
///   线段序列 / 中枢序列
///   扩展线段序列 / 扩展中枢序列
///   扩展线段序列_线段 / 扩展中枢序列_线段
///   线段_线段序列 / 线段_中枢序列
///   扩展线段序列_扩展线段 / 扩展中枢序列_扩展线段
///
/// 分析层次（可读写）:
///   线段分析层次: int (默认 3) / 扩展线段分析层次: int (默认 3) / 混合扩展线段分析层次: int (默认 3)
///
/// 序列组（只读，返回 list[list[...]]）:
///   线段序列组 / 中枢序列组 / 扩展线段序列组 / 扩展中枢序列组
///   混合扩展线段序列组 / 混合扩展中枢序列组
///
/// 核心方法:
///   增加原始K线(普K) — 喂入一根普通K线，触发全层级增量计算
///   重置基础序列() — 清空所有计算状态和序列
///   静态重新分析() — 对已有普通K线序列进行全量重新分析
///
/// 调试方法:
///   测试_保存数据(root?) — 将各层级序列保存到文件，用于与Python版对比
///   读取数据文件(文件路径, 配置) -> 观察者 (classmethod) — 从 .nb 文件加载数据
#[pyclass(name = "观察者", module = "chanlun._chanlun", subclass)]
pub struct 观察者Py {
    pub(crate) inner: Option<Arc<RwLock<chanlun::business::observer::观察者>>>,
    配置缓存: parking_lot::Mutex<Option<Py<缠论配置Py>>>,
    最后配置版本: AtomicU64,
}

impl 观察者Py {
    pub(crate) fn obs(
        &self,
    ) -> parking_lot::RwLockReadGuard<'_, chanlun::business::observer::观察者> {
        self.inner
            .as_ref()
            .expect("观察者 尚未初始化，请通过 __init__(符号, 周期, 配置) 构造")
            .read()
    }

    pub(crate) fn obs_mut(
        &self,
    ) -> parking_lot::RwLockWriteGuard<'_, chanlun::business::observer::观察者> {
        self.inner
            .as_ref()
            .expect("观察者 尚未初始化，请通过 __init__(符号, 周期, 配置) 构造")
            .write()
    }
}

#[pymethods]
impl 观察者Py {
    /// __new__ 从 *args/**kwargs 中提取 (符号, 周期, 配置)，完整初始化观察者。
    /// 由于形参可变（子类参数各异），不依赖固定签名，而是从 args/kwargs 中按位置和名称智能提取。
    #[new]
    #[pyo3(signature = (*args, **kwargs))]
    fn new(
        args: &Bound<'_, pyo3::types::PyTuple>,
        kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
    ) -> PyResult<Self> {
        let py = args.py();

        // 提取 符号（位置 0 或关键字）
        let 符号: String = if !args.is_empty() {
            args.get_item(0)?.extract()?
        } else {
            match kwargs.and_then(|kw| kw.get_item("符号").ok().flatten()) {
                Some(val) => val.extract()?,
                None => return Err(pyo3::exceptions::PyTypeError::new_err("缺少参数: 符号")),
            }
        };

        // 提取 周期（位置 1 或关键字）
        let 周期: i64 = if args.len() >= 2 {
            args.get_item(1)?.extract()?
        } else {
            match kwargs.and_then(|kw| kw.get_item("周期").ok().flatten()) {
                Some(val) => val.extract()?,
                None => return Err(pyo3::exceptions::PyTypeError::new_err("缺少参数: 周期")),
            }
        };

        // 提取 配置（关键字优先，然后扫描剩余位置参数）
        let mut config = None;
        if let Some(val) = kwargs.and_then(|kw| kw.get_item("配置").ok().flatten()) {
            let cfg: PyRef<'_, 缠论配置Py> = val.extract()?;
            config = Some(cfg.to_rust_config(py)?);
        }
        if config.is_none() {
            for i in 2..args.len() {
                if let Ok(cfg) = args.get_item(i)?.extract::<PyRef<'_, 缠论配置Py>>() {
                    config = Some(cfg.to_rust_config(py)?);
                    break;
                }
            }
        }
        let config = config.unwrap_or_else(chanlun::config::缠论配置::default);

        Ok(Self {
            inner: Some(chanlun::business::observer::观察者::new(
                符号, 周期, config,
            )),
            配置缓存: parking_lot::Mutex::new(None),
            最后配置版本: AtomicU64::new(0),
        })
    }

    /// __init__ 不重复构造 — __new__ 已完成初始化。
    /// 接收 *args/**kwargs 是为了兼容 Python 构造函数给 __init__ 传入的任意形参。
    #[pyo3(signature = (*args, **kwargs))]
    fn __init__(
        &mut self,
        args: &Bound<'_, pyo3::types::PyTuple>,
        kwargs: Option<&Bound<'_, pyo3::types::PyDict>>,
    ) -> PyResult<()> {
        let _ = (args, kwargs);
        Ok(())
    }

    #[getter]
    /// 观察员（自引用）
    fn 观察员(slf: PyRef<'_, Self>) -> PyRef<'_, Self> {
        slf
    }

    #[getter]
    /// :return: "{符号}:{周期}"
    fn 标识(&self) -> String {
        self.obs().标识()
    }

    #[getter]
    /// :return: 最后一根原始K线
    fn 当前K线(&self, py: Python<'_>) -> Option<Py<K线Py>> {
        self.obs().当前K线().map(|k| bar_to_py(py, Arc::clone(k)))
    }

    #[getter]
    /// :return: 最后一根缠论K线
    fn 当前缠K(&self, py: Python<'_>) -> Option<Py<缠论K线Py>> {
        self.obs()
            .当前缠K()
            .map(|k| crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))
    }

    #[getter]
    fn 符号(&self) -> String {
        self.obs().符号.clone()
    }

    #[getter]
    fn 周期(&self) -> i64 {
        self.obs().周期
    }

    #[getter]
    fn 配置(&self, py: Python<'_>) -> PyResult<Py<缠论配置Py>> {
        let mut cache = self.配置缓存.lock();
        if let Some(ref cached) = *cache {
            Ok(cached.clone_ref(py))
        } else {
            let cfg_py = 缠论配置Py::from_rust_config(&self.obs().配置)?;
            let obj = Py::new(py, cfg_py)?;
            *cache = Some(obj.clone_ref(py));
            Ok(obj)
        }
    }

    #[setter]
    fn set_配置(&self, value: &Bound<'_, 缠论配置Py>) -> PyResult<()> {
        let config = value.borrow().to_rust_config(value.py())?;
        *self.配置缓存.lock() = Some(value.clone().unbind());
        self.obs_mut().配置 = config;
        Ok(())
    }

    /// 清空所有分析序列，重置为初始状态（内部实现）
    #[pyo3(name = "_重置基础序列")]
    fn 重置基础序列_impl(&mut self) -> PyResult<()> {
        self.obs_mut().重置基础序列();
        Ok(())
    }

    /// 清空所有分析序列，重置为初始状态（公开分发器，支持子类重写）
    fn 重置基础序列(slf: &Bound<'_, Self>) -> PyResult<()> {
        slf.call_method1("_重置基础序列", ())?;
        Ok(())
    }

    /// 核心入口 — 投喂一根原始K线，增量更新所有层级（内部实现）
    #[pyo3(name = "_增加原始K线")]
    fn 增加原始K线_impl(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
        let kline = (*普K.borrow().inner).clone();
        let obs_arc = slf
            .borrow()
            .inner
            .clone()
            .expect("观察者 尚未初始化，请通过 __init__(符号, 周期, 配置) 构造");
        let symbol = obs_arc.read().符号.clone();
        let result = slf.py().detach(move || {
            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                obs_arc.write().增加原始K线(kline);
            }))
        });
        match result {
            Ok(()) => Ok(()),
            Err(e) => {
                let msg = e
                    .downcast_ref::<&str>()
                    .map(|s| s.to_string())
                    .or_else(|| e.downcast_ref::<String>().cloned())
                    .unwrap_or_else(|| "未知算法错误".into());
                Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
                    "[{symbol}] 算法异常: {msg}"
                )))
            }
        }
    }

    /// 核心入口 — 投喂一根原始K线，增量更新所有层级（公开分发器，支持子类重写）
    fn 增加原始K线(slf: &Bound<'_, Self>, 普K: &Bound<'_, K线Py>) -> PyResult<()> {
        // 版本对比，仅在配置变更时同步（支持 obs.配置.field = value 直接修改）
        {
            let me = slf.borrow();
            if let Some(ref cached) = *me.配置缓存.lock() {
                let py = slf.py();
                let cfg_ref = cached.bind(py).borrow();
                let current_version = cfg_ref.版本.load(Ordering::Relaxed);
                if current_version != me.最后配置版本.load(Ordering::Relaxed) {
                    if let Ok(config) = cfg_ref.to_rust_config(py) {
                        me.obs_mut().配置 = config;
                    }
                    me.最后配置版本.store(current_version, Ordering::Relaxed);
                }
            }
        }
        // 直接调用 Rust 实现，跳过 Python dispatch
        Self::增加原始K线_impl(slf, 普K)
    }

    /// 投喂原始数据 — 便捷入口，直接从 OHLCV 创建 K线 并通过 Python 分发 增加原始K线，
    /// 确保子类重写的 增加原始K线 被正确调用。
    fn 投喂原始数据(
        slf: &Bound<'_, Self>,
        时间戳: i64,
        开: f64,
        高: f64,
        低: f64,
        收: f64,
        量: f64,
    ) -> PyResult<()> {
        let (符号, 周期) = {
            let me = slf.borrow();
            let obs = me.obs();
            (obs.符号.clone(), obs.周期)
        };
        let kline = bar_to_py(
            slf.py(),
            Arc::new(chanlun::kline::bar::K线::创建普K(
                &符号, 时间戳, 开, 高, 低, 收, 量, 0, 周期,
            )),
        );
        slf.call_method1("增加原始K线", (kline,))?;
        Ok(())
    }

    /// 加载本地数据 — 从 .nb 文件加载K线数据（先重置，再通过 Python dispatch 逐根投喂，
    /// 确保子类重写的 增加原始K线 被正确调用）。
    fn 加载本地数据(slf: &Bound<'_, Self>, 文件路径: &str) -> PyResult<()> {
        // 重置基础序列（通过 Python 分发，支持子类重写）
        slf.call_method1("重置基础序列", ())?;

        // 读取文件，通过 Python dispatch 逐根投喂（支持子类重写 增加原始K线）
        let data = std::fs::read(文件路径)
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("read file: {}", e)))?;
        let size: usize = 48;
        for i in 0..data.len() / size {
            let offset = i * size;
            if let Some((时间戳, 开, 高, 低, 收, 量)) =
                chanlun::kline::bar::K线::解析原始数据(&data[offset..offset + size])
            {
                slf.call_method1("投喂原始数据", (时间戳, 开, 高, 低, 收, 量))?;
            }
        }
        Ok(())
    }

    /// 静态重新分析（内部实现）
    #[pyo3(name = "_静态重新分析")]
    fn 静态重新分析_impl(slf: &Bound<'_, Self>) -> PyResult<()> {
        let obs_arc = slf
            .borrow()
            .inner
            .clone()
            .expect("观察者 尚未初始化，请通过 __init__(符号, 周期, 配置) 构造");
        let symbol = obs_arc.read().符号.clone();
        let result = slf.py().detach(move || {
            std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
                obs_arc.write().静态重新分析();
            }))
        });
        match result {
            Ok(()) => Ok(()),
            Err(e) => {
                let msg = e
                    .downcast_ref::<&str>()
                    .map(|s| s.to_string())
                    .or_else(|| e.downcast_ref::<String>().cloned())
                    .unwrap_or_else(|| "未知算法错误".into());
                Err(pyo3::exceptions::PyRuntimeError::new_err(format!(
                    "[{symbol}] 静态重新分析异常: {msg}"
                )))
            }
        }
    }

    /// 静态重新分析（公开分发器，支持子类重写）
    fn 静态重新分析(slf: &Bound<'_, Self>) -> PyResult<()> {
        slf.call_method1("_静态重新分析", ())?;
        Ok(())
    }

    /// 拆分各序列数据，单独存文件，文件名为对应变量名（内部实现）
    #[pyo3(name = "_测试_保存数据", signature = (root = None))]
    fn 测试_保存数据_impl(&self, root: Option<&str>) -> String {
        self.obs().测试_保存数据(root)
    }

    /// 拆分各序列数据，单独存文件，文件名为对应变量名（公开分发器，支持子类重写）
    #[pyo3(signature = (root = None))]
    fn 测试_保存数据(slf: &Bound<'_, Self>, root: Option<&str>) -> PyResult<String> {
        let result = slf.call_method1("_测试_保存数据", (root,))?;
        result.extract::<String>()
    }

    #[classmethod]
    #[pyo3(signature = (文件路径, 配置 = None, 观察员 = None))]
    /// :param 文件路径: 数据文件路径 格式如: btcusd-300-1631772074-1632222374.nb
    /// :param 配置: 缠论配置
    /// :param 观察员: 可选，已有观察者实例；不传则自动创建
    /// :return: 观察者实例
    fn 读取数据文件(
        _cls: &Bound<'_, PyType>,
        文件路径: &str,
        配置: Option<&Bound<'_, 缠论配置Py>>,
        观察员: Option<&Bound<'_, Self>>,
        py: Python<'_>,
    ) -> PyResult<Py<PyAny>> {
        let config = match 配置 {
            Some(cfg) => cfg.borrow().to_rust_config(py)?,
            None => chanlun::config::缠论配置::default(),
        };

        // 从文件名解析 符号/周期: "btcusd-300-1761327300-1776327900.nb"
        let path = std::path::Path::new(文件路径);
        let name = path
            .file_stem()
            .and_then(|n| n.to_str())
            .ok_or_else(|| pyo3::exceptions::PyValueError::new_err("invalid filename"))?;
        let parts: Vec<&str> = name.split('-').collect();
        if parts.len() < 4 {
            return Err(pyo3::exceptions::PyValueError::new_err(format!(
                "invalid filename format: {}",
                name
            )));
        }
        let 符号 = parts[0].to_string();
        let 周期: i64 = parts[1]
            .parse()
            .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("parse period: {}", e)))?;

        let obs_ref = match 观察员 {
            Some(obs) => {
                // 更新已有观察员属性
                {
                    let slf_ref = obs.borrow_mut();
                    let mut obs_mut = slf_ref.obs_mut();
                    obs_mut.符号 = 符号;
                    obs_mut.周期 = 周期;
                    obs_mut.配置 = config;
                }
                obs.clone()
            }
            None => {
                // 创建新观察者：调用 cls(符号, 周期)，配置后续通过 obs_mut 设置
                let obj = _cls.call1((符号.as_str(), 周期))?;
                let obs: &Bound<'_, Self> = obj.cast().map_err(|_| {
                    pyo3::exceptions::PyTypeError::new_err("failed to create 观察者")
                })?;
                {
                    let slf_ref = obs.borrow_mut();
                    let mut obs_mut = slf_ref.obs_mut();
                    obs_mut.配置 = config;
                }
                obs.clone()
            }
        };

        // 调用加载本地数据（通过 Python dispatch，支持子类重写）
        obs_ref.call_method1("加载本地数据", (文件路径,))?;

        Ok(obs_ref.unbind().into())
    }

    // ---- 序列 getters ----

    #[getter]
    fn 普通K线序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for k in &self.obs().普通K线序列 {
            list.append(bar_to_py(py, Arc::clone(k)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 基础缠K序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for k in &self.obs().基础缠K序列 {
            list.append(crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))?;
        }
        Ok(list.into())
    }

    #[setter]
    #[pyo3(name = "基础缠K序列")]
    fn 设置_基础缠K序列(&mut self, value: &Bound<'_, PyAny>) -> PyResult<()> {
        let list: &Bound<'_, pyo3::types::PyList> = value.cast()?;
        let mut vec = Vec::new();
        for item in list {
            let 缠k: PyRef<'_, 缠论K线Py> = item.extract()?;
            vec.push(Arc::clone(&缠k.inner));
        }
        self.obs_mut().基础缠K序列 = vec;
        Ok(())
    }

    #[getter]
    fn 缠论K线序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for k in &self.obs().缠论K线序列 {
            list.append(crate::kline_py::chan_kline_to_py(py, Arc::clone(k)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 分型序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for f in &self.obs().分型序列 {
            list.append(fractal_to_py(py, Arc::clone(f)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 笔序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for d in &self.obs().笔序列 {
            list.append(dashed_to_py(py, Arc::clone(d)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 笔_中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for h in &self.obs().笔_中枢序列 {
            list.append(hub_to_py(py, Arc::clone(h)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 线段序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for d in self.obs().线段序列() {
            list.append(dashed_to_py(py, Arc::clone(d)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for h in self.obs().中枢序列() {
            list.append(hub_to_py(py, Arc::clone(h)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 扩展线段序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for d in self.obs().扩展线段序列() {
            list.append(dashed_to_py(py, Arc::clone(d)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 扩展中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for h in self.obs().扩展中枢序列() {
            list.append(hub_to_py(py, Arc::clone(h)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 扩展线段序列_线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for d in self.obs().扩展线段序列_线段() {
            list.append(dashed_to_py(py, Arc::clone(d)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 扩展中枢序列_线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for h in self.obs().扩展中枢序列_线段() {
            list.append(hub_to_py(py, Arc::clone(h)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 线段_线段序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for d in self.obs().线段_线段序列() {
            list.append(dashed_to_py(py, Arc::clone(d)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 线段_中枢序列(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for h in self.obs().线段_中枢序列() {
            list.append(hub_to_py(py, Arc::clone(h)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 扩展线段序列_扩展线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for d in self.obs().扩展线段序列_扩展线段() {
            list.append(dashed_to_py(py, Arc::clone(d)))?;
        }
        Ok(list.into())
    }

    #[getter]
    fn 扩展中枢序列_扩展线段(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let list = pyo3::types::PyList::empty(py);
        for h in self.obs().扩展中枢序列_扩展线段() {
            list.append(hub_to_py(py, Arc::clone(h)))?;
        }
        Ok(list.into())
    }

    // ---- 分析层次 getter/setter ----

    #[getter]
    fn 线段分析层次(&self) -> usize {
        self.obs().线段分析层次
    }

    #[setter]
    #[pyo3(name = "线段分析层次")]
    fn 设置_线段分析层次(&mut self, value: usize) {
        self.obs_mut().线段分析层次 = value;
    }

    #[getter]
    fn 扩展线段分析层次(&self) -> usize {
        self.obs().扩展线段分析层次
    }

    #[setter]
    #[pyo3(name = "扩展线段分析层次")]
    fn 设置_扩展线段分析层次(&mut self, value: usize) {
        self.obs_mut().扩展线段分析层次 = value;
    }

    #[getter]
    fn 混合扩展线段分析层次(&self) -> usize {
        self.obs().混合扩展线段分析层次
    }

    #[setter]
    #[pyo3(name = "混合扩展线段分析层次")]
    fn 设置_混合扩展线段分析层次(&mut self, value: usize) {
        self.obs_mut().混合扩展线段分析层次 = value;
    }

    // ---- 序列组 getters (返回 list[list[...]]) ----

    #[getter]
    fn 线段序列组(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        self._虚线序列组_to_py(&self.obs().线段序列组, py)
    }

    #[getter]
    fn 中枢序列组(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        self._中枢序列组_to_py(&self.obs().中枢序列组, py)
    }

    #[getter]
    fn 扩展线段序列组(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        self._虚线序列组_to_py(&self.obs().扩展线段序列组, py)
    }

    #[getter]
    fn 扩展中枢序列组(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        self._中枢序列组_to_py(&self.obs().扩展中枢序列组, py)
    }

    #[getter]
    fn 混合扩展线段序列组(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        self._虚线序列组_to_py(&self.obs().混合扩展线段序列组, py)
    }

    #[getter]
    fn 混合扩展中枢序列组(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        self._中枢序列组_to_py(&self.obs().混合扩展中枢序列组, py)
    }
}

impl 观察者Py {
    fn _虚线序列组_to_py(
        &self,
        组: &Vec<Vec<Arc<chanlun::structure::dash_line::虚线>>>,
        py: Python<'_>,
    ) -> PyResult<Py<PyAny>> {
        let outer = pyo3::types::PyList::empty(py);
        for inner_vec in 组 {
            let inner_list = pyo3::types::PyList::empty(py);
            for d in inner_vec {
                inner_list.append(dashed_to_py(py, Arc::clone(d)))?;
            }
            outer.append(inner_list)?;
        }
        Ok(outer.into())
    }

    fn _中枢序列组_to_py(
        &self,
        组: &Vec<Vec<Arc<chanlun::algorithm::hub::中枢>>>,
        py: Python<'_>,
    ) -> PyResult<Py<PyAny>> {
        let outer = pyo3::types::PyList::empty(py);
        for inner_vec in 组 {
            let inner_list = pyo3::types::PyList::empty(py);
            for h in inner_vec {
                inner_list.append(hub_to_py(py, Arc::clone(h)))?;
            }
            outer.append(inner_list)?;
        }
        Ok(outer.into())
    }
}

// ========== K线合成器 ==========

/// K线合成器 — 将小周期K线合成为大周期K线。
///
/// 构造:
///   K线合成器(标识, 周期组) — 周期组为升序排列的整数列表（如 [60, 300, 900]）
///
/// 方法:
///   投喂K线(普K) — 喂入普通K线，完成事件通过回调分发
///   投喂(时间戳, 开盘价, 最高价, 最低价, 收盘价, 成交量) — 快捷入口
///   获取当前K线(周期) -> K线|None — 获取指定周期的当前合成结果
#[pyclass(name = "K线合成器", module = "chanlun._chanlun")]
pub struct K线合成器Py {
    pub(crate) inner: chanlun::business::synthesizer::K线合成器,
}

#[pymethods]
impl K线合成器Py {
    #[new]
    fn new(标识: String, 周期组: Vec<i64>) -> Self {
        Self {
            inner: chanlun::business::synthesizer::K线合成器::new(标识, 周期组, None),
        }
    }

    /// 投喂K线 — 输入最小周期K线，合成为所有目标周期，完成事件通过回调分发
    fn 投喂K线(&mut self, 普K: &Bound<'_, K线Py>) {
        self.inner.投喂K线((*普K.borrow().inner).clone());
    }

    /// 投喂原始tick数据
    fn 投喂(&mut self, 时间戳: i64, 开: f64, 高: f64, 低: f64, 收: f64, 量: f64) {
        self.inner.投喂(时间戳, 开, 高, 低, 收, 量);
    }

    /// 获取指定周期当前正在合成的K线
    fn 获取当前K线(&self, 周期: i64, py: Python<'_>) -> Option<Py<K线Py>> {
        self.inner
            .获取当前K线(周期)
            .map(|k| bar_to_py(py, Arc::new(k.clone())))
    }

    #[getter]
    fn 标识(&self) -> String {
        self.inner.标识.clone()
    }

    #[getter]
    fn 周期组(&self) -> Vec<i64> {
        self.inner.周期组.clone()
    }
}

// ========== 立体分析器 ==========

/// 立体分析器 — 多周期分析器，内部包含 K线合成器 + 每周期一个观察者。
///
/// 通过最小周期数据合成大周期，并将各周期分析结果对齐。
///
/// 构造:
///   立体分析器(符号, 周期组, 配置?, 配置组?) — 周期组为升序列表（如 [60, 300, 900]）
///     配置为默认配置，配置组为各周期独立配置（dict[周期, 缠论配置]）
///
/// 方法:
///   投喂K线(普K) — 喂入最小周期K线，自动合成大周期并分发给各周期观察者
///   测试_保存数据(root?) — 保存各周期的分析数据到文件
#[pyclass(name = "立体分析器", module = "chanlun._chanlun")]
pub struct 立体分析器Py {
    pub(crate) inner: chanlun::business::multi_frame::立体分析器,
}

#[pymethods]
impl 立体分析器Py {
    #[new]
    #[pyo3(signature = (符号, 周期组, 配置 = None, 配置组 = None))]
    fn new(
        符号: String,
        周期组: Vec<i64>,
        配置: Option<&Bound<'_, 缠论配置Py>>,
        配置组: Option<&Bound<'_, PyAny>>,
        py: Python<'_>,
    ) -> PyResult<Self> {
        let config = match 配置 {
            Some(cfg) => Some(cfg.borrow().to_rust_config(py)?),
            None => None,
        };
        let cfg_map: Option<HashMap<i64, chanlun::config::缠论配置>> = match 配置组 {
            Some(dict_any) => {
                let dict = dict_any.cast::<pyo3::types::PyDict>()?;
                let mut map = HashMap::new();
                for (key, value) in dict.iter() {
                    let period: i64 = key.extract()?;
                    let cfg: PyRef<'_, 缠论配置Py> = value.extract()?;
                    map.insert(period, cfg.to_rust_config(py)?);
                }
                Some(map)
            }
            None => None,
        };
        Ok(Self {
            inner: chanlun::business::multi_frame::立体分析器::new(
                符号, 周期组, config, cfg_map,
            ),
        })
    }

    /// 统一入口 — 投喂最小周期K线，自动合成大周期并分发给各周期观察者
    fn 投喂K线(&mut self, 普K: &Bound<'_, K线Py>) {
        self.inner.投喂K线((*普K.borrow().inner).clone());
    }

    /// 拆分各序列数据，单独存文件，文件名为对应变量名
    fn 测试_保存数据(&self, root: Option<&str>) {
        self.inner.测试_保存数据(root);
    }

    #[getter]
    fn 周期组(&self) -> Vec<i64> {
        self.inner.周期组.clone()
    }

    /// _单体分析器 — 对应 Python 立体分析器._单体分析器: dict[周期, 观察者]
    #[getter(_单体分析器)]
    fn get_单体分析器(&self, py: Python<'_>) -> PyResult<Py<PyAny>> {
        let dict = pyo3::types::PyDict::new(py);
        for (周期, obs_rc) in &self.inner.单体分析器 {
            let obs_py = 观察者Py {
                inner: Some(obs_rc.clone()),
                配置缓存: parking_lot::Mutex::new(None),
                最后配置版本: AtomicU64::new(0),
            };
            dict.set_item(周期, obs_py)?;
        }
        Ok(dict.into())
    }
}

pub fn register(m: &Bound<'_, PyModule>) -> PyResult<()> {
    m.add_class::<基础买卖点Py>()?;
    m.add_class::<买卖点Py>()?;
    m.add_class::<观察者Py>()?;
    m.add_class::<K线合成器Py>()?;
    m.add_class::<立体分析器Py>()?;
    Ok(())
}
