原以为浪费在 add_indicators 算了太多用不到的指标,实测它只占全量构建的 1.3%——talib 是向量化 C 代码,便宜。真正的两处: cal_kl_data 占 96%:每根 K 线 df.iloc[i] 新建一个 40 列 Series,再在其上做 几十次逐键查找。改为预取 ndarray 后 2 万根 1946ms → 824ms。 ChanKLC.cal_all_ema_status 占 25%:每次合并 KLU 都立即重算,而它产出的 ema_status / ema52_pos / ema52_status 全仓无任何读取方(含前端)。改为惰性 求值,保留属性形式以防将来有人读。顺带删掉 get_klc_list 里累加一整轮后直接 丢弃的 ema_up_list / ema_down_list。 另加 TF_DF(lean=True):只构建到中枢,跳过线段/走势中枢/MACD 状态机——这些 只服务 bsp_list 与 web 展示,笔和中枢不依赖。研究与实盘走这条快 3.6x。 结果 2 万根 5m:full 1946 → 754ms,lean → 543ms。 step46_engine_parity.py 是配套的安全网,改引擎前先跑一次 --save。它对 KLC 端点与分型、笔起止价与 is_sure、中枢 zg/zd/available_ts/阶梯、信号全部输出列, 以及 26 个被下游消费的 dataframe 列取哈希。本次三处改动逐步验证,另用 git stash 切回改动前代码在 20 万根 × 5 用例上做了跨版本逐位对拍,全部一致; 增量路径与 web API 也各验一遍。 Co-authored-by: Cursor <cursoragent@cursor.com>
98 lines
3.6 KiB
Python
98 lines
3.6 KiB
Python
from datetime import timedelta
|
|
|
|
import numpy as np
|
|
import pandas as pd
|
|
from pandas import DataFrame
|
|
from chanlun.pipeline.resample import resample_to_interval
|
|
|
|
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
|
|
from chanlun.pipeline.builders.bi import BiBuilderMixin
|
|
from chanlun.pipeline.builders.bsp import BspBuilderMixin
|
|
from chanlun.pipeline.builders.fast_bsp import FastBspBuilderMixin
|
|
from chanlun.pipeline.builders.incremental import IncrementalBuilderMixin
|
|
from chanlun.pipeline.builders.indicators import IndicatorsBuilderMixin
|
|
from chanlun.pipeline.builders.kline import KlineBuilderMixin
|
|
from chanlun.pipeline.builders.seg import SegBuilderMixin
|
|
from chanlun.pipeline.builders.zs import ZsBuilderMixin
|
|
|
|
class TF_DF(IndicatorsBuilderMixin, KlineBuilderMixin, BiBuilderMixin, SegBuilderMixin, ZsBuilderMixin, BspBuilderMixin, FastBspBuilderMixin, IncrementalBuilderMixin):
|
|
def __init__(self, df=None, interval=0, timeframe=None, lean=False):
|
|
"""lean=True 只构建到中枢,跳过线段/走势中枢/MACD 状态机。
|
|
|
|
研究与实盘只吃 bi_list → 中枢 → fast_bsp 这条链;线段、zs、big_zs 和整套
|
|
MACD 背驰状态机是 web 展示与 bsp_list 才用的。实测这些占全量构建的约四成。
|
|
注意 lean 下 bsp_list/seg_list/chanmacd 均为空,**不要给 web 用**。
|
|
"""
|
|
self.lean = lean
|
|
if df is not None:
|
|
self.init_TF_DF(df, interval, timeframe, lean=lean)
|
|
def init_TF_DF(self, df, interval, timeframe, lean=False):
|
|
self.lean = lean
|
|
self.timeframe = timeframe
|
|
self.interval = interval
|
|
# 检查 DataFrame 是否为空或没有 date 列
|
|
if df is None or df.empty:
|
|
raise ValueError(f"DataFrame for {timeframe} is empty. Please download data first.")
|
|
if 'date' not in df.columns:
|
|
raise ValueError(f"DataFrame for {timeframe} missing 'date' column. Columns: {df.columns.tolist()}")
|
|
# interval=1 时不需要重采样
|
|
if interval == 1:
|
|
self.dataframe = df.copy()
|
|
else:
|
|
self.dataframe = resample_to_interval(df, interval)
|
|
#print(self.timeframe, len(self.dataframe))
|
|
self.dataframe = self.add_indicators(self.dataframe)
|
|
self.klu_list = []
|
|
self.klc_list = []
|
|
self.bi_list = []
|
|
self.zs_list = []
|
|
self.bi_zs_list = []
|
|
self.bsp_list = []
|
|
self.fast_bsp_list = []
|
|
self.seg_list = []
|
|
self.klc_fx_list = []
|
|
self.klu_list = self.cal_kl_data(self.dataframe)
|
|
self.klc_list = self.get_klc_list(self.klu_list)
|
|
self.bi_list = self.cal_bi_list(self.klc_list)
|
|
self.bi_zs_list = self.cal_bi_zs_list_pure(self.bi_list)
|
|
if self.lean:
|
|
self.big_zs_list = []
|
|
self.chanmacd = None
|
|
return
|
|
self.seg_list = self.get_seg_list(self.bi_list)
|
|
self.zs_list = self.get_zs_list(self.bi_list, self.seg_list)
|
|
self.big_zs_list = self.get_big_zs_list(self.zs_list)
|
|
# get_klc_list 内已算过 ChanMACD,直接复用
|
|
self.chanmacd = getattr(self, '_last_chan_macd', None)
|
|
if self.chanmacd is None:
|
|
self.chanmacd = ChanMACD(self.klu_list)
|
|
self.klu_list = self.chanmacd.klu_list
|
|
|
|
|
|
def get_current_klc(self):
|
|
if len(self.klc_list) > 0:
|
|
return self.klc_list[-2]
|
|
return None
|