Files
Chan/chanlun/pipeline/builders/indicators.py
T
jackyu66gitandCursor bfdb2f2e2a 引擎四处「算了没人要的东西」,append_bar 13.9ms → 6.6ms
服务端把增量上线后回传两个热点:add_indicators 为加一根重算全表(占 34%)、
cal_bi_list 整表重扫(51%)。顺着查下来四处都不是算法慢,是算了没人读的结果。

1. cal_trend 挂到 lean 下。它不是笔的依赖(bi.py:221 在它自己的循环里读自身
   序列状态),服务端 verify_incr_parity 三币 1800 根已对拍定论。web 走非
   lean,klc_trend 图层不受影响。

2. check_fx_pattern 删掉拼完就丢的字符串。它把 klu.to_string() 拼成 p 只为
   一行注释掉的 print——2000 根上近 3 万次 f-string 加 6 万次 enum 格式化,
   而且在 cal_bi_list 内层。klu.pattern 只被 cal_klu_pattern 自己的双K/三K
   判定读,不出模块不进 web,所以整个调用在 lean 下也跳过。

3. ChanBI.add_klc 去二次方。去重原本线性扫 klc_list,且每加一根就把整笔所有
   KLU 的 macdhist 重累一遍,往一笔加 k 根是 O(k²)。改成下标集合加
   macd_hist/macd_div 惰性求值。这两个值只有背驰判定(bsp.py)读,lean 下
   bsp 根本不算。

4. add_indicators 批量挂列。2001 行上 TA 计算合计只有 2.5ms,而 30 多次
   df['x']= 要 3.6ms——开销大头是 BlockManager 逐列插入不是计算,改为一次
   concat。cal_volume_ratio 里为算一列 rolling 而 copy() 整张 40 列表,一并去掉。

实测(本机,2001 根窗口。服务端基线 21.8ms 是另一台机器,别直接比绝对值):
  append_bar        13.9 → 6.6ms
  └ rebuild_bi_zs    8.7 → 2.8ms
  └ add_indicators   4.4 → 3.5ms
  TF_DF lean        49.8 → 32.9ms
  TF_DF full        72.7 → 64.9ms

对拍用 git worktree 检出改动前的提交,同一份 BTC 1m 4000 根跑 38 项指纹:
full 模式 19 项全部一致(web 那条路没动);lean 模式差 2 项,正是设计要它差
的 klc.trend 和 klu.pattern,而 lean 下 bi/zs/seg/bsp/dataframe 全部一致——
这就是「这两个字段没人读」的实测证据:打空它们,下游一位不变。

瓶颈已经换位置了。新增 probe_inner.py 拆 inner_ms 分档:本机 TF_DF 两条腿占
70%、build_htf_zones 13%、htf_fx_timeline 6%,而服务端报的是 chan 构建 22ms /
信号链 86ms,机器差解释不了这个四倍差距。曾怀疑是 payload 反序列化,实测
_rebuild 只有 1.0ms,假设不成立。两边跑同一探针对分档表才能定位。

HANDOFF 顺带修掉一处 5.6 重号(增量落地那节改为 5.7,本节挂 5.71)。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-28 16:01:26 +08:00

142 lines
4.9 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""TF_DF builder mixin — 由 split_tfdf_builders 自动生成,逻辑与原 TF_DF 一致。"""
from __future__ import annotations
from datetime import timedelta
from decimal import Decimal
import numpy as np
import pandas as pd
from chanlun.indicators import ta
from pandas import DataFrame
from chanlun.core.ChanBI import ChanBI
from chanlun.core.ChanBIZS import ChanBIZS
from chanlun.core.ChanBSP import ChanBSP
from chanlun.core.ChanEnum import (
Chan_BI_DIR,
Chan_BSP_DIR,
Chan_BSP_TYPE,
Chan_FX_TYPE,
Chan_K_DIR,
Chan_KLC_FX,
Chan_KLC_STATE,
Chan_KLINE_DIR,
Chan_KLU_PATTERN,
Chan_PRICE_TREND,
Chan_SEG_DIR,
Chan_ZS_DIR,
)
from chanlun.core.ChanKLC import ChanKLC
from chanlun.core.ChanKLU import ChanKLU
from chanlun.core.ChanSBI import ChanSBI
from chanlun.core.ChanSEG import ChanSEG
from chanlun.core.ChanZS import ChanZS, ChanZS_Big
from chanlun.indicators.ChanMACD import ChanMACD
class IndicatorsBuilderMixin:
def get_ema52(self, index=-1):
if self.klu_list:
ema52_value = self.klu_list[index].ema52
# 处理NaN值
if pd.isna(ema52_value) or ema52_value is None:
return None
return float(ema52_value)
return None
def get_ema24(self, index=-1):
if self.klu_list:
ema24_value = self.klu_list[index].ema24
# 处理NaN值
if pd.isna(ema24_value) or ema24_value is None:
return None
return float(ema24_value)
return None
def add_indicators(self, df):
"""算指标并一次性挂到 df 上。
这里不逐列 `df['x'] = ...`:那样每一列都触发一次 BlockManager 插入,
30 多列的开销比全部 TA 计算本身还大(2001 行实测 TA 合计 2.5ms
逐列赋值 3.6ms)。增量路径每根都要走一遍,这笔开销是白付的。
"""
fast = 26
slow = 52
period = 9
macd = ta.MACD(df, fastperiod=fast, slowperiod=slow, signalperiod=period)
bb365 = ta.BBANDS(df, timeperiod=365, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb120 = ta.BBANDS(df, timeperiod=120, nbdevup=3.0, nbdevdn=3.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=41, nbdevup=2.3, nbdevdn=2.3, matype=0)
bb302 = ta.BBANDS(df, timeperiod=41, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb30 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb302 = ta.BBANDS(df, timeperiod=20, nbdevup=2.0, nbdevdn=2.0, matype=0)
bb2633 = ta.BBANDS(df, timeperiod=26, nbdevup=3.0, nbdevdn=3.0, matype=0)
# 计算布林带中轨(移动平均线)
bb30_middle = ta.SMA(df, timeperiod=90)
# 手动计算布林带 %B 指标 (BBP)
# %B = (Price - Lower Band) / (Upper Band - Lower Band)
bbp365 = (df['close'] - bb365['lowerband']) / (bb365['upperband'] - bb365['lowerband'])
bbp120 = (df['close'] - bb120['lowerband']) / (bb120['upperband'] - bb120['lowerband'])
bbp30 = (df['close'] - bb30['lowerband']) / (bb30['upperband'] - bb30['lowerband'])
bbp302 = (df['close'] - bb302['lowerband']) / (bb302['upperband'] - bb302['lowerband'])
bbp2633 = (df['close'] - bb2633['lowerband']) / (bb2633['upperband'] - bb2633['lowerband'])
cols = {
'bb2633upper': bb2633['upperband'],
'bb2633lower': bb2633['lowerband'],
'bbp2633': bbp2633,
'bb2633middle': bb2633['middleband'],
'atr': ta.ATR(df, timeperiod=14),
'bbup365': bb365['upperband'],
'bblow365': bb365['lowerband'],
'bbp365': bbp365,
'bbup120': bb120['upperband'],
'bblow120': bb120['lowerband'],
'bbp120': bbp120,
'bbup30': bb30['upperband'],
'bblow30': bb30['lowerband'],
'bbmiddle30': bb30_middle,
'bbp30': bbp30,
'bbup302': bb302['upperband'],
'bblow302': bb302['lowerband'],
'bbp302': bbp302,
'macd': macd['macd'],
'macdsignal': macd['macdsignal'],
'macdhist': macd['macdhist'],
}
for _p, _n in ((5, 'ema5'), (10, 'ema10'), (24, 'ema24'), (52, 'ema52'),
(104, 'ema104'), (156, 'ema156'), (208, 'ema208'),
(26, 'ema26'), (13, 'ema13'), (7, 'ema7')):
cols[_n] = ta.EMA(df, timeperiod=_p)
cols['rsi'] = ta.RSI(df, timeperiod=14)
cols['volume_ratio'] = self.cal_volume_ratio(df)
# 重复调用(增量路径每根都会)时先摘掉旧列,否则 concat 出重名列。
# 摘掉再接回末尾,列序与逐列覆盖的结果一致。
new = pd.DataFrame(cols, index=df.index)
dup = [c for c in new.columns if c in df.columns]
if dup:
df = df.drop(columns=dup)
return pd.concat([df, new], axis=1)
def get_ema_state(self, dataframe):
klu_list = self.get_klu_list(dataframe)
klc_list = self.get_klc_list(klu_list)
bi_list = self.cal_bi_list(klc_list)
klu_state_list = []
for klu in klu_list:
if klu.near0_return == 1:
klu_state_list.append("1")
elif klu.near0_return == 9:
klu_state_list.append("-1")
elif klu.candle_dir == Chan_K_DIR.BULL:
klu_state_list.append("2")
elif klu.candle_dir == Chan_K_DIR.BEAR:
klu_state_list.append("-2")
else:
klu_state_list.append("0")
return klu_state_list
def get_decimal(self, value):
return Decimal("{:.2f}".format(value))