服务端把增量上线后回传两个热点: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>
142 lines
4.9 KiB
Python
142 lines
4.9 KiB
Python
"""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))
|