# chan.py — 缠论技术分析库 AI 技能 > 约 6900 行 Python,从原始 K 线到买卖点的全链路流式缠论分析。所有标识符使用中文。 --- ## Q1: 如何从 .nb 文件加载数据并完成分析? ```python from chanlun import chan config = chan.缠论配置(标识="btcusd") obs = chan.观察者.读取数据文件("btcusd-300-xxx.nb", 配置=config) # 加载即完成全部分析,直接读取结果 obs.笔序列 # List[虚线],标识="笔" obs.线段序列 # List[虚线],标识="线段" obs.中枢序列 # List[中枢],标识="线段中枢" obs.笔_中枢序列 # List[中枢],标识="笔中枢" obs.分型序列 # List[分型] obs.缠论K线序列 # List[缠论K线] # 高级层级 obs.扩展线段序列 # 标识="扩展线段" obs.扩展中枢序列 # 标识="扩展线段中枢" obs.线段_线段序列 # 线段作为笔再划分 obs.扩展线段序列_扩展线段 # 扩展线段作为笔再划分 ``` `.nb` 文件格式:每根 K 线 48 字节大端 `struct.pack(">6d")`,依次为 时间戳/开/高/低/收/量(均为 float64)。 --- ## Q2: 如何流式实时分析? ```python obs = chan.观察者(符号="btcusd", 周期=300, 配置=config) # 方式一:投喂原始数据 obs.投喂原始数据(时间戳=datetime.now(), 开=50000, 高=50100, 低=49900, 收=50050, 量=100) # 方式二:投喂预构建 K 线对象 obs.增加原始K线(k线对象) # 每次投喂都增量更新全链路:缠K → 分型 → 笔 → 线段 → 中枢 # 结果即刻可用 ``` --- ## Q3: 如何做多周期立体分析? ```python ma = chan.立体分析器( 符号="btcusd", 周期组=[300, 1800], # [输入周期, 显示周期] 配置=config, 配置组={1800: chan.缠论配置(笔内元素数量=7)}, # 可选:按周期覆盖配置 ) # 只投喂最小周期 K 线,合成器自动合成大周期 for kline in klines_300: ma.投喂K线(kline) # 获取各周期结果 obs_300 = ma._单体分析器[300] obs_1800 = ma._单体分析器[1800] ``` 内部机制: - 内置 `K线合成器` 将小周期 K 线合成为大周期 - 合成器事件回调 → 大周期 K 线完成 → 触发对应 `观察者.__处理数据` - 非显示周期的 `基础缠K序列` 引用显示周期的 `缠论K线序列` --- ## Q4: 如何单独使用 K 线合成器? ```python def 回调(信号类型, 标识, 周期, 完成K线): """信号类型: "K线完成" """ print(f"{标识} {周期}s 完成一根K线: {完成K线.收盘价}") synth = chan.K线合成器(标识="btcusd", 周期组=[300, 900, 1800], 事件回调=回调) # 投喂最小周期数据 synth.投喂(时间戳, 开, 高, 低, 收, 量) # 或 synth.投喂K线(普K) # 查询当前合成中的 K 线 k = synth.获取当前K线(周期=900) # 支持后续设置回调 synth.设置事件回调(新回调函数) ``` 合成器按 `(时间戳 // 周期) * 周期` 对齐时间戳到周期边界。 --- ## Q5: 缠论配置有哪些关键参数? ```python config = chan.缠论配置( 标识="btcusd", # ---- 分析开关(按需关闭以跳过某层级)---- 分析笔=True, # 是否分析笔 分析线段=True, # 是否分析线段 分析扩展线段=True, # 是否分析扩展线段 分析笔中枢=True, # 是否分析笔中枢 分析线段中枢=True, # 是否分析线段中枢 计算指标=True, # 是否计算技术指标 # ---- 笔参数(影响笔划分粒度)---- 笔内元素数量=5, # 笔内最少缠K数(含端点) 笔内相同终点取舍=False, # True=取最后, False=取第一个 笔内起始分型包含整笔=False, 笔弱化=False, # 笔弱化开关 笔弱化_原始数量=3, # 弱化参考原始K线数量 笔次级成笔=False, # 次级成笔 # ---- 线段参数 ---- 线段_非缺口下穿刺=False, # 非缺口状态的贯穿伤回退 线段_特征序列忽视老阴老阳=False, # True=忽视缺口全以无缺口对待 线段_缺口后紧急修正=True, # 缺口后紧急修正 线段_修正=False, # 短路修正(不建议使用) 线段内部中枢图显=True, # 显示线段内部中枢 扩展线段_当下分析=False, # True=以当下分析, False=以事后分析 # ---- 指标参数 ---- 指标计算方式="收", # "开"/"高"/"低"/"收"/"高低均值"/"高低收均值"/"开高低收均值" 平滑异同移动平均线_快线周期=13, 平滑异同移动平均线_慢线周期=31, 平滑异同移动平均线_信号周期=11, 相对强弱指数_周期=13, 相对强弱指数_超买阈值=75, 相对强弱指数_超卖阈值=25, 随机指标_RSV周期=13, 随机指标_K值平滑周期=5, 随机指标_D值平滑周期=5, 随机指标_超买阈值=80, 随机指标_超卖阈值=20, 计算BOLL=False, # 是否计算布林带 布林带_周期=20, 布林带_标准差倍数=2.0, # ---- 多指标参数列表 ---- MACD_参数列表=[], # [(key, 快线, 慢线, 信号), ...] RSI_周期列表=[], # [(key, 周期), ...] KDJ_参数列表=[], # [(key, RSV周期, K平滑, D平滑), ...] BOLL_参数列表=[], # [(key, 周期, 标准差倍数), ...] 均线_类型列表=[], # SMA/EMA 类型列表 均线_周期列表=[], # 对应周期列表 # ---- 买卖点 ---- 买卖点偏移=1, # 最大偏移量 买卖点激进识别=False, # 激进模式不检查分型完整性 买卖点与MACD柱强相关=False, # True=卖点需正值 买点需负值 买卖点错过误差值=0.01, # 距离买卖点值的容差 买卖点_指标模式="配置", # "任意"/"配置"/"全量"/"相对" 买卖点_指标匹配_MACD=True, 买卖点_指标匹配_KDJ=True, 买卖点_指标匹配_RSI=True, # ---- 线段内部背驰 ---- 线段内部背驰_MACD=True, 线段内部背驰_斜率=True, 线段内部背驰_测度=True, 线段内部背驰_模式="相对", # "任意"/"配置"/"全量"/"相对" # ---- 推送/图显 ---- 推送K线=True, 推送笔=True, 推送线段=True, 推送中枢=True, # ...另有 图表展示, 图表展示_笔, _线段, _中枢_* 等 20+ 图显字段 ) ``` 全部 74 个字段通过 `config.model_fields()` 获取。 --- ## Q6: 如何创建和对比配置? ```python # 构造 c1 = chan.缠论配置(笔内元素数量=5, 线段_缺口后紧急修正=True) c2 = chan.缠论配置(笔内元素数量=7, 线段_缺口后紧急修正=False) # 差异对比 → {字段名: 新值} c1.对比(c2) # {"笔内元素数量": 7} # 深拷贝 + 部分更新 c3 = c1.model_copy(update={"笔内元素数量": 8}, deep=True) # 创建不推送变体(所有推送标志 = False) c_no_push = c1.不推送() # 序列化 c1.to_dict() # dict c1.to_json() # JSON 字符串 c1.保存配置("path.json") # 反序列化 chan.缠论配置.from_dict(data) chan.缠论配置.from_json(json_str) chan.缠论配置.加载配置("path.json") # 按序号重组(兼容旧版扁平键名:"1_笔模式" → {1: {笔模式: ...}}) chan.缠论配置.按序号重组字典(默认配置, 原始字典) ``` --- ## Q7: 虚线的标识有哪些?如何区分笔和线段? `虚线` 是笔和线段的通用数据结构: ```python 虚线(序号, 标识, 文, 武, 级别, 有效性=True) # 文=起点分型, 武=终点分型 # 标识类型 "笔" # 来自 笔.分析 "线段" # 来自 线段.分析 "扩展线段" # 来自 线段.扩展分析 "线段_线段" # 线段再做一次线段分析 "扩展线段_扩展线段" # 扩展线段再做一次线段分析 ``` 关键属性和方法: ```python seg.方向 # 相对方向.向上 / 相对方向.向下 seg.高 / seg.低 # max(文,武).高 / min(文,武).低 seg.文 / seg.武 # 起点分型 / 终点分型 seg.笔序列 # 内部笔序列(仅线段有) seg.之前是(prev) # prev.武 == seg.文 ? seg.之后是(next) # seg.武 == next.文 ? seg.获取普K序列(观察员) # 虚线覆盖的原始K线区间 seg.获取缠K序列(观察员) # 虚线覆盖的缠论K线区间 ``` 笔和线段类本身只包含 `@staticmethod` 算法方法,不实例化: ```python 笔.分析(当前分型, 分型序列, 笔序列, 缠K序列, 普K序列, 层级, 配置) 线段.分析(笔序列, 线段序列, 配置, 层级=0) ``` --- ## Q8: 如何判断背驰? ```python # 需要离开段和进入段(都是虚线对象) 进入段 = obs.线段序列[-2] # 倒数第二段 离开段 = obs.线段序列[-1] # 最后一段 # 单项背驰 chan.背驰分析.MACD背驰(进入段, 离开段, obs.普通K线序列, 方式="总") chan.背驰分析.斜率背驰(进入段, 离开段) chan.背驰分析.测度背驰(进入段, 离开段) # 组合背驰 chan.背驰分析.全量背驰(进入段, 离开段, obs.普通K线序列) # 三项全满足 chan.背驰分析.任意背驰(进入段, 离开段, obs.普通K线序列) # 至少一项 chan.背驰分析.任选背驰(进入段, 离开段, obs.普通K线序列) # 至少两项 # 按配置组合 chan.背驰分析.配置背驰(进入段, 离开段, obs.普通K线序列, config) chan.背驰分析.背驰模式(进入段, 离开段, obs.普通K线序列, config, "全量") # 判断线段内部是否背驰 chan.线段.判断线段内部是否背驰(离开段, obs) # 分析段内中枢背驰 ``` MACD 背驰也可通过虚线的类方法直接判断: ```python chan.虚线.武之全量MACD均值(obs.普通K线序列, 离开段) # 武端MACD < 均值? chan.虚线.武之MACD均值(obs.普通K线序列, 离开段) # 按方向比较 chan.虚线.武之MACD极值(obs.普通K线序列, 离开段) # 武端是否为极值? chan.虚线.买卖意义(离开段, obs) # 盘整背驰 → (bool, 描述) ``` --- ## Q9: 中枢如何操作? ```python # 中枢 = 三段连续虚线重叠区间 hub = chan.中枢.创建(左段, 中段, 右段, 级别=0, 标识="线段中枢") # 属性 hub.方向 # 基础序列[0].方向.翻转() hub.高 / hub.低 # 中枢区间: min(前三段.高) / max(前三段.低) hub.高高 / hub.低低 # 全部延伸段的最高/最低 hub.离开段 # 基础序列[-1] hub.第三买卖线 # 第三类买卖点参考线 hub.当前状态() # "中枢之中" / "中枢之上" / "中枢之下" # 延伸与扩展 hub._添加虚线(新段) # 中枢延伸 hub.获取扩展中枢(扩展中枢列表, config) # 9段以上获得扩展中枢 # 类方法 chan.中枢.基础检查(左, 中, 右) # 三段是否首尾相连 chan.中枢.从序列中获取中枢(虚线序列, 起始方向, 标识) chan.中枢.分析(虚线序列, 中枢序列) # 自动识别中枢 ``` --- ## Q10: 如何判断中枢的第三类买卖点? 第三类买卖点出自中枢"离开-回抽不破": ```python # 分割序列获取第三买卖线 前, 后, 第三买卖线, 贯穿伤 = chan.线段.分割序列(离开段, 所属中枢=hub) # 第三买卖线 != None → 存在第三类买卖点 # 或通过中枢完整性验证 hub.完整性(虚实="实") # 验证第三买卖点是否有效 ``` --- ## Q11: 买卖点如何生成? `观察者.识别买卖点()` 当前为空实现(`pass`)。买卖点类型已定义,可手动创建: ```python # 买卖点类型枚举 (18种) chan.买卖点类型.一买 / chan.买卖点类型.一卖 chan.买卖点类型.二买 / chan.买卖点类型.二卖 chan.买卖点类型.三买 / chan.买卖点类型.三卖 chan.买卖点类型.类一买 / chan.买卖点类型.类一卖 chan.买卖点类型.类二买 / chan.买卖点类型.类二卖 chan.买卖点类型.T1B买 / chan.买卖点类型.T1B卖 chan.买卖点类型.T2B买 / chan.买卖点类型.T2B卖 chan.买卖点类型.T3B买 / chan.买卖点类型.T3B卖 # 工厂方法 chan.买卖点.一买点(买卖点分型, 当前K线, 标识, 备注, 中枢破位值) chan.买卖点.一卖点(买卖点分型, 当前K线, 标识, 备注, 中枢破位值) # ... 二买点 / 二卖点 / 三买点 / 三卖点 / ... # 属性 bsp.类型 # 买卖点类型枚举 bsp.当前K线 # 关联K线 bsp.买卖点分型 # 关联分型 bsp.破位值 # 中枢破位值 bsp.有效性 # 是否有效 bsp.偏移 / bsp.失效偏移 ``` --- ## Q12: 技术指标如何计算和访问? 指标通过 `指标计算器` 自动挂载到每根 K 线的 `指标` 容器: ```python # 自动挂载(观察者内部调用) 指标计算器.计算并挂载(当前K线, 全序列, config) # 访问指标 k线.指标.macd # 平滑异同移动平均线 或 None k线.指标.rsi # 相对强弱指数 或 None k线.指标.kdj # 随机指标 或 None k线.指标.boll # 布林带 或 None # MACD 字段 macd.DIF / macd.DEA / macd.MACD柱 / macd.快线EMA / macd.慢线EMA # 手动计算(不依赖观察者) macd1 = 平滑异同移动平均线.首次计算(收盘价, 时间戳, 快线周期=12, 慢线周期=26, 信号周期=9) macd2 = 平滑异同移动平均线.增量计算(macd1, 新收盘价, 新时间戳) # 或从 K 线直接计算 macd = 平滑异同移动平均线.首次计算_K线(k线, 计算方式="收盘价") macd = 平滑异同移动平均线.增量计算_K线(前MACD, k线, 计算方式="收盘价") # RSI / KDJ / BOLL 同理 rsi = 相对强弱指数.首次计算(收盘价, 时间戳, 周期=14) rsi = 相对强弱指数.增量计算(前RSI, 新收盘价, 新时间戳) kdj = 随机指标.首次计算(最高价, 最低价, 收盘价, 时间戳, N=9, M1=3, M2=3) kdj = 随机指标.增量计算(前KDJ, 最高价, 最低价, 收盘价, 时间戳) boll = 布林带.首次计算(k线, 计算方式="收盘价", 周期=20, 标准差倍数=2.0) boll = 布林带.增量计算(前BOLL, k线, 计算方式="收盘价") ``` 配置变更后需回填指标: ```python 指标计算器._回填新指标(全序列, config) # 重算所有K线的指标 ``` --- ## Q13: 如何验证双端(Rust/Python)分析结果一致? ```python from chanlun import chan as chan_rs # Rust 绑定 from chanlun import chan as chan_py # Python 参考 # 逐类型相等性检查(返回 (bool, 原因)) chan.K线相等(k_rs, k_py, 浮点容差=1e-9) chan.缠论K线相等(ck_rs, ck_py, 浮点容差=1e-9) chan.分型相等(fx_rs, fx_py, 浮点容差=1e-9) chan.虚线相等(dl_rs, dl_py, 浮点容差=1e-9) chan.中枢相等(hub_rs, hub_py, 浮点容差=1e-9) chan.观察者相等(obs_rs, obs_py, 浮点容差=1e-9) chan.立体分析器相等(ma_rs, ma_py, 浮点容差=1e-9) ``` 所有相等函数返回 `(True, "")` 或 `(False, "差异描述")`。 --- ## Q14: K 线如何创建和序列化? ```python # 创建 k = chan.K线.创建普K( 标识="btcusd", 时间戳=datetime.now(), 开盘价=50000, 最高价=50100, 最低价=49900, 收盘价=50050, 成交量=100, 序号=0, 周期=300, ) # 属性 k.方向 # 相对方向(开盘价 vs 收盘价) k.指标 # 指标容器 # 二进制序列化 (48字节大端,兼容 Rust) data = bytes(k) # __bytes__ → struct.pack(">6d") # 从二进制解析 k2 = chan.K线.读取大端字节数组(data, 周期=300, 标识="btcusd") # 批量保存/加载 chan.K线.保存到DAT文件("output.dat", k线列表) # 区间截取 subset = chan.K线.截取(序列, 始=k起始, 终=k结束) # 区间MACD面积 chan.K线.获取MACD(序列, 始, 终) # {"MACD正面积": ..., "MACD负面积": ...} ``` --- ## Q15: 缠论K线包含处理逻辑是什么? ```python # 缠论K线.分析 的返回值 状态, 分型 = chan.缠论K线.分析(当前K线, 缠K序列, 普K序列, config) # 状态值含义 "缠K完成" # 形成独立的缠论K线,可能伴随顶/底分型 "包含处理中" # 当前K线被包含处理,未形成独立缠K "等" # 等待更多K线(如方向未确定) ``` 缠K 方向由包含处理决定,有向上和向下两种状态。包含处理规则: - 同向:取 高者之高、低者之低(向上)/ 高者之低、低者之高(向下) - 异向:先确定方向,再按方向处理 --- ## Q16: 分型如何识别? ```python # 分型由左中右三根缠K构成 fx = chan.分型(左=缠K_左, 中=缠K_中, 右=缠K_右) fx.结构 # 分型结构.顶分型 / 分型结构.底分型 / 分型结构.三连向上 / 分型结构.三连向下 # 分型结构分析 chan.分型结构.分析(左, 中, 右, 可以逆序包含=False, 忽视顺序包含=False) # 返回: 三连向上 / 三连向下 / 顶分型 / 底分型 / 向右扩散 / None # 分型辅助方法 chan.分型.判断分型(左分型, 右分型, 模式="中") chan.分型.从缠K序列中获取分型(缠K序列, 中间缠K) chan.分型.向序列中添加(分型序列, 新分型) # 自动处理冲突 # 分型属性 fx.分型特征值 # 中.高 + 中.低 * sign(顶=-1, 底=+1) fx.关系组 # (左→中, 中→右, 左→右) 相对方向元组 fx.强度 # 高低差比例 ``` --- ## Q17: 如何获取虚线范围内的所有停顿位置? ```python # 获取线段中所有笔的端点停顿位置 停顿 = chan.线段.获取所有停顿位置(段, 观察员) # 返回笔级虚线列表,可用于判断中枢区间 # 四象归类 象 = chan.线段.四象(段) # "老阳" / "小阳" / "少阴" / "老阴" ``` --- ## Q18: 数据如何保存和恢复? ```python # 观察者保存到 .nb 文件(48字节/根格式) path = obs.测试_保存数据(root="/tmp") # 返回文件路径 # 从 .nb 文件恢复 obs.加载本地数据(path) # 立体分析器保存 ma.测试_保存数据(root="/tmp") # 静态重新分析(从已有K线全量重建所有序列) obs.静态重新分析() ``` --- ## Q19: 相对方向的判断逻辑? ```python chan.相对方向.分析(前高, 前低, 后高, 后低) # 返回: 向上 / 向下 / 包含 / 向上缺口 / 向下缺口 / 衔接 # 方向翻转 方向.翻转() # 向上 ←→ 向下 # 判断方法 方向.是否向上() / 方向.是否向下() 方向.是否包含() / 方向.是否缺口() / 方向.是否衔接() ``` --- ## Q20: 常见错误排查 | 现象 | 可能原因 | |------|----------| | 笔序列为空 | `配置.分析笔=False` 或笔内缠K不足(需 ≥ 笔内元素数量) | | 线段序列为空 | `配置.分析线段=False` 或笔序列长度不足 | | 中枢序列为空 | 虚线序列长度不足 3 段,或三段不重叠 | | 指标为 None | 指标未挂载,需先调用 `指标计算器.计算并挂载` | | 配置变更不生效 | 需要 `指标计算器._回填新指标(全序列, 配置)` 和 `obs.静态重新分析()` | | 买卖点序列为空 | `识别买卖点()` 是空实现,需自行调用 `买卖点.生成买卖点` | | 多周期结果不一致 | 检查 `配置组` 是否覆盖了对应周期的配置 | | 浮点比较不通过 | 双端一致性测试使用 `浮点容差=1e-9` | --- ## Q21: 三个线段序列组有什么不同?分别用于什么场景? `观察者` 内部有三条并行的多级序列树,每条树有 3 层(由 `线段分析层次=3` 控制): ```python # 1. 线段序列组 — 标准线段递归 obs.线段序列组[0] # 标识="线段" 笔→线段(标准) obs.线段序列组[1] # 标识="线段<线段>" 线段→线段(段作为笔再划段) obs.线段序列组[2] # 标识="线段<线段<线段>" 再递归一层 # 2. 扩展线段序列组 — 扩展分析递归 obs.扩展线段序列组[0] # 标识="扩展线段" 笔→扩展线段 obs.扩展线段序列组[1] # 标识="扩展线段<扩展线段>" 扩展段→段 obs.扩展线段序列组[2] # 标识="扩展线段<扩展线段<扩展线段>>" 再递归 # 3. 混合扩展线段序列组 — 混合递归(扩展线段分析标准线段) obs.混合扩展线段序列组[0] # 标识="扩展线段<线段>" 线段→扩展线段 obs.混合扩展线段序列组[1] # 标识="扩展线段<线段<线段>>" 线段<线段>→扩展线段 obs.混合扩展线段序列组[2] # 标识="扩展线段<线段<线段<线段>>>" 再递归 ``` **生成流程**(`静态重新分析` 中可见): 1. 笔序列 → `线段.分析()` → 线段序列组[0] 2. 线段序列组[0] → `线段.分析()` → 线段序列组[1](线段作为笔再划分) 3. 线段序列组[1] → `线段.分析()` → 线段序列组[2] 4. 笔序列 → `线段.扩展分析()` → 扩展线段序列组[0] 5. 扩展线段序列组[0] → `线段.扩展分析()` → 扩展线段序列组[1] 6. 扩展线段序列组[1] → `线段.扩展分析()` → 扩展线段序列组[2] 7. 线段序列组[i] → `线段.扩展分析()` → 混合扩展线段序列组[i] **实际验证**(2500 根 BTCUSD 300s K 线): ``` 线段序列组: 26段, 3段<线段>, 无更高级 扩展线段序列组: 48段, 14段<扩展>, 4段<扩展<扩展>> 混合扩展线段组: 8段<线段>, 1段<线段<线段>> ``` **关键差异**: - `线段.分析` 要求笔/线段之间**方向交替**(上下上下) - `线段.扩展分析` 允许**同向连续**(将同向虚线合并处理),因此产生更多扩展段 等效属性访问(@property): ```python obs.线段序列 = obs.线段序列组[0] obs.线段_线段序列 = obs.线段序列组[1] obs.扩展线段序列 = obs.扩展线段序列组[0] obs.扩展线段序列_扩展线段 = obs.扩展线段序列组[1] obs.混合扩展线段序列 = obs.混合扩展线段序列组[0] ``` --- ## Q22: 特征序列是什么?老阴/老阳/小阳/少阴如何区分? **特征序列**是线段划分算法的核心概念。当从笔划分线段时,每根笔的方向与线段方向之间有一个关系: - **线段方向向上** → 特征序列取每笔的"向下"特征(特征方向=向下) - **线段方向向下** → 特征序列取每笔的"向上"特征(特征方向=向上) 特征序列用于处理**缺口**和**包含**:多笔同特征方向的要素需要先做包含处理,再通过特征序列的分型来判断线段是否终结。 **线段特征**(`线段特征` 类)是对应笔的抽象: ```python 线段特征(标识, 基础序列, 线段方向) # 持有一组同向虚线(笔),是线段划分的中间结构 # 特征.方向 = 线段方向的翻转 # 特征.高/特征.低 → 按线段方向取极值 ``` **四象**描述线段与缺口的关系: ```python 线段.四象(段) # "老阳": 向下线段 + 存在前一缺口(向下线段后有向上缺口) # "老阴": 向上线段 + 存在前一缺口(向上线段后有向下缺口) # "小阳": 向上线段 + 无缺口(普通向上) # "少阴": 向下线段 + 无缺口(普通向下) ``` **老阴老阳在特征序列中的作用**: 当 `线段_特征序列忽视老阴老阳=False`(默认)时: - 老阳/老阴的特征序列**不参与**包含处理——缺口状态下的特征序列元素直接跳过包含合并 - 相当于"缺口破坏了特征序列的连续性" 当 `线段_特征序列忽视老阴老阳=True` 时: - **忽略缺口**,所有特征序列元素都按无缺口处理(严格包含) - 实际效果:线段和中枢数量都**增加**(更多特征序列参与包含处理,产生更多分型终结) **验证数据**(1500 根 K 线): ``` 忽视老阴老阳=False: 线段数=374, 中枢数=48 忽视老阴老阳=True: 线段数=390, 中枢数=56 (+16段, +8中枢) ``` --- ## Q23: 笔弱化、笔次级成笔到底改变了什么? ### 笔弱化 (`笔弱化=True`) 当一笔只有 3 个内部缠K(`武.中.序号 - 文.中.序号 + 1 == 3`)且下一分型直接穿透它时: - 向上笔 + 下一底分型低于该笔的低点 → 弹出这笔 → 重分析 - 向下笔 + 下一顶分型高于该笔的高点 → 弹出这笔 → 重分析 效果是**移除过短的阻挡笔**,让分型能连接更合理的笔。实测效果: ``` 1500根K线: 笔弱化=False: 笔数=2796, 线段数=374 笔弱化=True: 笔数=3630, 线段数=500 5000根K线: 笔弱化=False: 笔数=9358, 线段数=1261 笔弱化=True: 笔数=11878, 线段数=1678 ``` 笔弱化开启后笔数和线段数都**显著增加**(~30%),因为移除阻挡笔后产生了更多有效笔和段。 ### 笔弱化_原始数量 (`笔弱化_原始数量=3`) 控制笔弱化判断时参考的原始K线数量。不同的值对结果影响: ``` 笔弱化_原始数量=3: 笔数=3556 笔弱化_原始数量=5: 笔数=3556 (无变化) 笔弱化_原始数量=7: 笔数=3556 (无变化) ``` 当前实现中此参数变化不明显,因为笔弱化主要受"原始分型数量为3"这个硬编码条件驱动。 ### 笔次级成笔 (`笔次级成笔=False`) 控制是否在笔的分析中启用次级递归成笔。默认关闭。实测效果: ``` 1500根K线: 笔次级成笔=False: 笔数=2796, 线段数=374 笔次级成笔=True: 笔数=3228, 线段数=468 (+432笔, +94段) ``` 开启后笔数和线段数都有明显增加,因为允许在笔内部递归划分子笔,产生更多有效分型。 --- ## Q24: 线段修正算法 _修正 / _缺口突破 / _非缺口下穿刺 做什么? ### _修正 (短路修正, 配置.线段_修正) 当线段基础序列 ≥ 9 且后半段满足特定条件时,将一段拆分为两段: 1. 分割序列 → 前段+后段 2. 后段 ≥ 6 个元素且偶数 3. 后段倒数第3和第1元素方向 = 线段方向 4. 满足条件 → 拆分为 新段1(后段[:-3]) + 新段2(后段[-3:]) 5. 标记 `短路修正=True`,清空老阴老阳段的缺口 ### _缺口突破 (缺省启用,无配置开关) 处理有缺口的线段序列: - 当前线段特征序列右元素 ≠ None - 特征序列方向与线段方向匹配 - 连续特征序列长度 ≥ 5 且奇数 - 弹出当前段 → 根据特征分型终结点切分为两段 ### _非缺口下穿刺 (配置.线段_非缺口下穿刺) 处理"贯穿伤":当一段线段的基础序列被另一段完全穿透时修复。只有在小阳/少阴(非缺口)状态下才触发。 --- ## Q25: 买卖点的实际集成路径是什么?为什么识别买卖点()是空的? `观察者.识别买卖点()` 是 `pass` 占位——具体买卖点识别逻辑**原本计划实现但未在 chan.py 中完成**。 但这不意味着买卖点不可用——可以通过以下路径手动生成: ```python # 路径1:手动创建(已有工厂方法) bsp = chan.买卖点.一卖点( 买卖点分型=obs.分型序列[-1], # 或中枢离开段终点的分型 当前K线=obs.普通K线序列[-1], 标识="btcusd-300", 备注="第一类卖点", 中枢破位值=hub.低, # 中枢下沿 ) # 路径2:通过生成买卖点路由 chan.买卖点.生成买卖点( 特征="一卖", # 特征字符串路由到工厂 序号="1", 级别="线段", 买卖点分型=fx, 当前缠K=ck, ) # 路径3:中枢完整性验证 → 判断第三买卖点 hub.完整性(虚实="实") # 检查是否有第三买卖点条件 ``` 买卖点类型的完整列表(18种): ``` 一买/一卖 第一类(中枢背驰转折) 二买/二卖 第二类(回抽不进中枢) 三买/三卖 第三类(离开后回抽不破中枢) T1买/T1卖 第一类扩展 T1P买/T1P卖 盘整型第一类 T2买/T2卖 第二类扩展 T2S买/T2S卖 强势第二类 T3A买/T3A卖 第三类A型 T3B买/T3B卖 第三类B型 ``` Rust 绑定层(`chanlun-py`)中的 `识别买卖点` 已实现完整逻辑,使用 `生成买卖点` 路由。 --- ## 内部算法机制 以下方法不是公开 API,但理解它们的逻辑才能理解线段为什么这样划分、中枢为什么这样识别。 ### 线段.分割序列 — 用终点切开基础序列 ``` 输入: 段 (虚线), 所属中枢 (可选) 输出: (前序列, 后序列, 第三买卖线列表, 贯穿伤) ``` **切割规则**:遍历段.基础序列,找到第一笔满足 `笔.文 is 段.武` 的位置——从此笔开始全部归入"后",之前的归入"前"。 **为什么**:线段的终点分型(武)同时是下一笔的起点分型(文)。段.武 为起点的笔暂挂在当前段的基础序列里,但它已属于"下一段"范畴。分割序列区分"已确认"和"暂挂"。 **9 处调用各自取什么**: | 调用方 | 取值 | 用途 | |--------|------|------| | `获取内部中枢序列` | 前, 后 | 分别在前后找中枢 | | `_缺口突破` | `[0]` 前 | 只在确认部分判断缺口 | | `_缺口后紧急修正` | `[1]` 后 | 检查暂挂部分是否需要修正 | | `_修正` | 前, 后 | 后≥6且方向一致 → 拆段 | | `分析`(递归)| `[1]` 后 | 用暂挂部分做下一轮划分 | | `判断线段内部是否背驰` | 前=阳, 后=阴 | 进入段 vs 离开段 | | `获取所有停顿位置` | 前, 后 | 前后端点都算停顿 | | `获取数据文本` | 全部 | 格式化输出 | **贯穿伤**:后[0].武 穿透了 段.文——向上段的"后"终点比"文"还低、或向下段的"后"终点比"文"还高。意味着这段根本没真正转折。 ### 线段.获取内部中枢序列 — 阴阳合三套中枢 ``` 输入: 段, 配置 输出: (虚中枢列表, 实中枢列表, 合中枢列表) # 注释: 阴 阳 合 ``` 先用 `分割序列` 把线段基础序列切成"前"(确认部分)和"后"(暂挂部分),然后分别在三块区域跑中枢识别: | 返回值 | 别称 | 识别范围 | 含义 | |--------|------|----------|------| | `实中枢序列` | 阳 | 分割序列的"前" | 段已确认部分的中枢——段主体运行阶段形成的 | | `虚中枢序列` | 阴 | 分割序列的"后" | 段暂挂部分的中枢——离开段所在的 | | `合中枢序列` | 合 | 整个基础序列(前+后) | 全景视角——但跨了段边界,含下一段笔 | **核心用途**:给 `判断线段内部是否背驰` 提供判定依据。 背驰判断的三种情况(对照 `判断线段内部是否背驰` 源码): 1. **有阴**(暂挂部分非空)→ 段还未确认终点,不判断背驰(直接返回 False) 2. **有实中枢** → 检查最后一笔是否在末个阳中枢里: - 在:取中枢起点前一笔作进入段,最后一笔作离开段 → 按配置模式判断背驰 - 不在但末中枢有第三买卖线:取倒数第三笔和倒数第一笔比较 → 判断盘整背驰 3. **无中枢** → 只有 3 笔时判断盘整背驰 **阴阳为什么分开**:同一个段内,前半(阳)是已完成的走势,后半(阴)是终点出现后的暂挂部分。背驰判断时,取阳中枢的起点前一笔作"进入段"、阳的最后一笔作"离开段",比较两者是否发生趋势衰减——这是段内部是否背驰的判定基础。 实测验证(2000 根 K 线): ``` 段6 [向上] 10笔: 实中枢1个[114596-114189] 虚中枢1个[115382-114523] 合中枢1个[114596-114189] 段7 [向下] 10笔: 实中枢1个[115382-114523] 虚中枢0个 合中枢2个[115382-114523][113297-112218] 段8 [向上] 8笔: 实中枢0个 虚中枢1个[111409-111008] 合中枢2个[113641-112754][111409-111008] ``` 注意段7:虚中枢为空但合中枢有2个——因为跨"前/后"边界的笔凑出了合中枢里的第二个。但后部的笔属于下一段,这个中枢不应该算在此段头上。**判断单段是否趋势,应该用实中枢,不能用合中枢**:实中枢只看"前"(本段自己的笔),不会混入下一段的结构。 实测对比(2000根K线): ``` 段7: 实中枢=1(盘整) vs 合中枢=2(趋势) ← 合中枢误判 段8: 实中枢=0(盘整) vs 合中枢=2(趋势) ← 合中枢误判 ``` 两例都被合中枢误标为趋势,实际是盘整。 --- ## Q26: 如何判断线段内部是趋势还是盘整? ```python 虚, 实, 合 = chan.线段.获取内部中枢序列(段, 观察员.配置) if len(实) >= 2: if 段.方向.是否向上(): return "上涨趋势" else: return "下跌趋势" else: return "盘整" ``` 趋势必须有方向——段方向是向上 + 实中枢≥2 = 上涨趋势,段方向向下 + 实中枢≥2 = 下跌趋势。 **为什么是实中枢,不是合中枢?** `分割序列` 把基础序列切成"前"(本段)和"后"(下一段起始)。合中枢在"前+后"上跑,会混入下一段的笔。段7 前7笔只有1个实中枢,但跨边界的笔在合中枢凑出第2个——这个多出来的中枢不属于此段。 **辅助信号**: | 信号 | 上涨趋势 | 下跌趋势 | 盘整 | |------|---------|---------|------| | 段.方向 | 向上 | 向下 | 皆可 | | 实中枢数 | ≥2 | ≥2 | ≤1 | | `买卖意义` | 可能 False | 可能 False | 可能 True(盘整背驰) | | `判断线段内部是否背驰` | 可能 True | 可能 True | True=盘整衰竭 | ### 线段._刷新特征序列 — 线段级别包含处理 ``` 输入: 段, 配置 ``` 1. 取段.基础序列,若存在 `前一结束位置` 则从此位置-1处截取 2. 调用 `线段特征.静态分析(基础序列, 段.方向, 四象, 忽视老阴老阳)` — 将笔序列做包含处理得到特征序列 3. 从特征序列提取分型序列 4. 若首分型方向与段.方向相同(特征分型终结条件)→ 截取基础序列到特征分型终点 + 调用 `_设置特征序列` **特征序列方向 = 线段方向的翻转**。向上线段取向下的特征(调整段),向下线段取向上的特征(反弹段)。 ### 线段特征.静态分析 — 笔的包含处理 将多笔同向特征元素做包含处理(与缠K的包含处理原理相同,但在笔级别操作): - 同向特征:取极值合并 - 反向特征:独立成新元素 - 老阴/老阳(有缺口)且不忽视时:跳过包含处理 输出一段 `List[线段特征]`,每个特征持有一组被合并的笔。 ### 线段._武斗 — 线段终结判断 ``` 输入: 段, 特征, 行号 ``` 当特征序列右元素 ≠ None 时,判断线段是否被终结: 1. 特征序列方向应与线段方向相同(特征分型终结) 2. 检查特征分型是否有效(顶底交替,特征序列包含处理完毕) 3. 满足条件 → 用 `_武终` 终结线段,重置基础序列,开启新段 ### 线段._添加线段 — 含缺口处理 ``` 输入: 线段序列, 待添加线段, 配置, 行号 ``` 在追加线段前,检查前一段的缺口状态: - 若前一段 `短路修正=True` → 缺口 = None - 否则 → 调用 `获取缺口(前一段)` - 将缺口存入新段的 `前一缺口` 缺口影响四象判断和后续的特征序列包含处理。 ### 笔._相对关系 — 判断笔是否反向 ``` 输入: 筆, 配置 ``` 验证笔的方向是否"真正"匹配分型方向。如果 `笔内起始分型包含整笔=True`,会用分型的左中右三根缠K构造缺口区间,判断区间方向与笔方向是否一致。防止"分型形态在但实际价格不匹配"的假笔。 ### 笔弱化触发条件 ``` if 配置.笔弱化 and 笔序列: 前一笔.武.中.序号 - 前一笔.文.中.序号 + 1 == 3: # 只有3根内部缠K if (向上笔 and 前一笔.低 > 当前分型.分型特征值 and 当前分型是底) or (向下笔 and 前一笔.高 < 当前分型.分型特征值 and 当前分型是顶): 弹出旧笔 → 递归重分析 ``` 只有**最短的笔(3缠K)**且被下一分型**完全穿透**时才会触发弱化。这就是为什么笔弱化能增加 30% 的笔——它移除了那些"看起来像笔但实际上阻挡了更合理划分"的过短笔。 ### 中枢._校验合法性 — 确保中枢不包含已删除的元素 遍历中枢.基础序列,检查每个元素是否仍存在于源序列中。如果某段已被线段修正删除,中枢需要修剪。这保证了中枢始终反映当前有效的线段结构。 ### 中枢.当前状态 — 判断价格相对中枢的位置 ```python if 中枢.高 >= 尾部.分型特征值 >= 中枢.低 → "中枢之中" elif 中枢.高 < 尾部.分型特征值 → "中枢之上" elif 中枢.低 > 尾部.分型特征值 → "中枢之下" ``` 第三买卖点判断依赖此状态——只有离开中枢(之上/之下)后才可能产生第三类买卖点。 --- ## 类型速查 | 类型 | 职责 | 关键方法 | |------|------|----------| | `K线` | OHLCV 数据 + 指标容器 | `创建普K`, `读取大端字节数组`, `截取` | | `缠论K线` | 包含处理后的K线 | `分析`, `创建缠K`, `与MACD柱子匹配` | | `分型` | 顶/底分型 | `从缠K序列中获取分型`, `向序列中添加` | | `分型结构` | 分型形态枚举 | `分析` | | `虚线` | 笔/线段数据结构 | `创建笔`, `创建线段`, `买卖意义`, `武之MACD极值` | | `笔` | 笔算法 (@staticmethod) | `分析` | | `线段` | 线段算法 (@staticmethod) | `分析`, `扩展分析`, `判断线段内部是否背驰` | | `中枢` | 三段重叠区间 | `创建`, `分析`, `获取扩展中枢`, `当前状态` | | `买卖点` | 买卖点工厂 | `一买点`~`T3B卖点` 18 个工厂方法 | | `背驰分析` | 背驰判断 (@staticmethod) | `MACD背驰`, `斜率背驰`, `测度背驰`, `全量背驰` | | `观察者` | 单周期分析器 | `投喂原始数据`, `读取数据文件`, `静态重新分析` | | `立体分析器` | 多周期分析器 | `投喂K线`, `_单体分析器` | | `K线合成器` | 周期合成 | `投喂`, `投喂K线`, `获取当前K线` | | `缠论配置` | 全局参数 | `to_dict`, `from_dict`, `model_copy`, `对比`, `不推送` | | `指标计算器` | 指标挂载 | `计算并挂载`, `_回填新指标` | | `均线工具` | SMA/EMA 辅助 | `增量SMA`, `增量EMA` |