from chanlun.core.ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE, Chan_K_DIR, Chan_MACD_STATE, Chan_MACDHIST_STATE, Chan_PRICE_TREND, Chan_KLU_PATTERN, Chan_KLC_FX class ChanKLU: def __init__(self, time, open, high, low, close, volume): # _time, _close, _open, _high, _low, _extra_info={} self.kl_type = None self.time = time self.close = close self.open = open self.high = high self.low = low self.volume = volume self.idx = 0 self.index = 0 self.macd = 0 self.signal = 0 self.macdhist = 0 self.klc = None self.rsi = 0 self.volume_ratio = 0 self.bb52upper = 0 self.bb52lower = 0 # === 新增:K线类型 === self.kline_type = None # K线类型:大阳线、大阴线、小阳线、小阴线 self.pattern = Chan_KLU_PATTERN.UNKNOWN # === 新增:实时分型相关属性 === self.pre = None # 前一根K线 self.next = None # 后一根K线 self.fx_type = Chan_FX_TYPE.UNKNOWN # 分型类型:0=无分型,1=顶分型,-1=底分型 self.fx_strength = 0 # 分型强度:0-100 self.fx_confirmed = False # 分型是否确认 self.klu_type = None self.range = self.high - self.low self.body = abs(self.close - self.open) self.upper_shadow = self.high - max(self.close, self.open) self.lower_shadow = min(self.close, self.open) - self.low self.body_ratio = self.body / self.range if self.range != 0 else 0 self.upper_shadow_ratio = self.upper_shadow / self.body if self.body != 0 else float('inf') self.lower_shadow_ratio = self.lower_shadow / self.body if self.body != 0 else float('inf') self.exception = False #self.cal_exception() self.candle_dir = Chan_K_DIR.CROSS if self.close == self.open else Chan_K_DIR.BULL if self.close > self.open else Chan_K_DIR.BEAR self.continue_div = 0 self.separate_div = 0 self.near0_return = 0 self.ema52 = 0 self.ema24 = 0 self.ema26 = 0 self.ema104 = 0 self.ema156 = 0 self.ema208 = 0 self.macd_slop = 0 self.signal_slop = 0 self.hist_slop = 0 self.hist_state = Chan_MACDHIST_STATE.UNKNOWN self.macd_state = Chan_MACD_STATE.UNKNOWN self.macd_hist_gap = 0 self.trend = Chan_PRICE_TREND.UNKNOWN self.seg_histset_index = 0 # === 归零轴细化与模式/背离 === self.zero_axis = False # 是否归零轴(穿越或接近) self.zero_axis_state = "none" # {none,crossing,near} self.zero_axis_side = 0 # 1:above, -1:under, 0:none self.zero_axis_score = 0 # 0-100 综合评分 self.mode1_touch_ema52 = False # 单边后触碰EMA52 self.mode2_fast_to_zero = False # 快线向零收敛 self.mode3_double_tf = False # 双周期归零(近似占位,由上层填充高周期确认) self.mode3_dir = "none" # {long_strong_rebound, short_strong_rebound, none} self.mode4_touch52_no_zero = False # 先触碰EMA52但黄白线未归零 self.div_type = "none" # {bearish, bullish, hidden_bearish, hidden_bullish, none} self.div_score = 0.0 # 背离强度(0-100) self.ema_dir = 0 self.get_ema_dir() self.bb2633upper = 0 self.bb2633lower = 0 self.bb2633middle = 0 self.ma5 = 0 self.ema5 = 0 #print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength) def set_macd_state(self, state): self.macd_state = state def set_pattern(self, pattern): self.pattern = pattern def set_seg_histset_index(self, seg_histset_index): self.seg_histset_index = seg_histset_index #print(self.time, self.seg_histset_index) def to_string(self): return f"{self.time} {self.candle_dir} {self.pattern}" def cal_exception(self): if self.upper_shadow_ratio > 5 or self.lower_shadow_ratio > 5: self.exception = True #print(self.time, self.upper_shadow_ratio, self.lower_shadow_ratio, self.body, self.lower_shadow, self.upper_shadow, self.high, self.low, self.close, self.open) #self.exception = False def set_trend(self, trend): self.trend = trend def set_separate_div(self, separate_div): self.separate_div = separate_div if self.klc and self.klc.pre and self.klc.next: fx = self.check_fx_dir(self.klc.pre, self.klc.next) if fx == Chan_FX_TYPE.TOP: if self.macdhist > 0: self.separate_div = separate_div else: self.separate_div = 0 elif fx == Chan_FX_TYPE.BOTTOM: if self.macdhist < 0: self.separate_div = separate_div else: self.separate_div = 0 def check_fx_dir(self, pre, next): fx = Chan_FX_TYPE.UNKNOWN if pre.klc_fx_type == Chan_KLC_FX.TOP1 or pre.klc_fx_type == Chan_KLC_FX.TOP2 or next.klc_fx_type == Chan_KLC_FX.TOP1 or next.klc_fx_type == Chan_KLC_FX.TOP2 or self.klc.klc_fx_type == Chan_KLC_FX.TOP1 or self.klc.klc_fx_type == Chan_KLC_FX.TOP2: fx = Chan_FX_TYPE.TOP elif pre.klc_fx_type == Chan_KLC_FX.BOTTOM1 or pre.klc_fx_type == Chan_KLC_FX.BOTTOM2 or next.klc_fx_type == Chan_KLC_FX.BOTTOM1 or next.klc_fx_type == Chan_KLC_FX.BOTTOM2 or self.klc.klc_fx_type == Chan_KLC_FX.BOTTOM1 or self.klc.klc_fx_type == Chan_KLC_FX.BOTTOM2: fx = Chan_FX_TYPE.BOTTOM return fx def set_next(self, next): self.next = next #if self.fx_type != Chan_FX_TYPE.UNKNOWN and self.fx_strength > 1: #print(self.index, self.time, self.fx_type, self.fx_confirmed, self.fx_strength) def set_pre(self, pre): self.pre = pre def set_klc(self, klc): self.klc = klc def set_histset(self, histset): """设置HistSet关联""" self.histset = histset def set_seg(self, seg): """设置Seg关联""" self.seg = seg def set_unittf(self, unittf): """设置UnitTF关联""" self.unittf = unittf def set_idx(self, idx): self.idx = idx self.index = idx def check_price_ema156(self): if self.check_indicators(): if self.close > self.ema156: return 1 elif self.close < self.ema156: return -1 else: return 0 else: return 0 def get_ema_dir(self): if self.check_indicators(): if self.ema24 > self.ema52 and self.ema52 > self.ema104 and self.ema104 > self.ema156: self.ema_dir = 1 elif self.ema24 < self.ema52 and self.ema52 < self.ema104 and self.ema104 < self.ema156: self.ema_dir = -1 else: self.ema_dir = 0 def check_indicators(self): if self.ema156 == 0: return False else: return True # 属性名 ← dataframe 列名。两条赋值路径(逐行 Series / 预取 ndarray)共用这张表, # 免得将来加指标时只改一处、另一处静默漏掉。 INDICATOR_FIELDS = ( ('macd', 'macd'), ('signal', 'macdsignal'), ('macdhist', 'macdhist'), ('ema26', 'ema26'), ('ema52', 'ema52'), ('ema24', 'ema24'), ('ema104', 'ema104'), ('ema156', 'ema156'), ('ema208', 'ema208'), ('ema13', 'ema13'), ('ema7', 'ema7'), ('ema5', 'ema5'), ('rsi', 'rsi'), ('volume_ratio', 'volume_ratio'), ('bb52upper', 'bb52upper'), ('bb52lower', 'bb52lower'), ('bb2633upper', 'bb2633upper'), ('bb2633lower', 'bb2633lower'), ('bb2633middle', 'bb2633middle'), ('ma5', 'ma5'), ) def set_indicators(self, item): """单根赋值。增量追加时每次只有一根,走这条即可。 原写法是 `float(item[c]) if c in item and item[c] else 0`。其中的真值判断 是空转:值为 0.0 时 float(0.0) 仍是 0,值为 NaN 时 NaN 为真值、照样透传。 唯一起作用的是「列不存在则填 0」,所以这里只保留那一层。 """ for attr, col in self.INDICATOR_FIELDS: v = item[col] if col in item else 0 setattr(self, attr, float(v) if v else 0) def set_indicators_from(self, cols, i): """从预取的 {列名: ndarray} 按下标赋值,语义与 set_indicators 相同。 全量构建时用这条:避免每根 `df.iloc[i]` 构造一个 Series,再在其上做 几十次逐键查找——那是 TF_DF 构建 96% 的耗时所在。 """ for attr, col in self.INDICATOR_FIELDS: arr = cols.get(col) v = arr[i] if arr is not None else 0 setattr(self, attr, float(v) if v else 0) def cal_macd_state(self): # 按定义精简实现:优先级 CROSS0 > 位置(HIGH/HE/RETURN_ZERO) > NEAR0 > UNKNOWN # 首条或缺前一根 if not hasattr(self, 'pre') or self.pre is None: self.macd_state = Chan_MACD_STATE.START return self.macd_state # 基本校验 if (self.macd == 0 and self.signal == 0 and self.macdhist == 0) or self.ema52 == 0: self.macd_state = Chan_MACD_STATE.UNKNOWN return self.macd_state # 归零轴判断 if self.signal > 0: if self.macd < self.signal: if 0 < self.low - self.ema52 < 100: self.near0_return = 0 elif self.close > self.ema52 and self.low < self.ema52 and self.open > self.ema52: self.near0_return = 0 elif self.close < self.ema52 and self.open > self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 elif self.close < self.ema52 and self.open < self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 elif self.close > self.ema52 and self.open > self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 elif self.close > self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 else: if self.macd > self.signal: if 0 < self.ema52 - self.high < 100: self.near0_return = 0 elif self.close < self.ema52 and self.high > self.ema52 and self.open < self.ema52: self.near0_return = 0 elif self.close < self.ema52 and self.open > self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 elif self.close > self.ema52 and self.open < self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 elif self.close > self.ema52 and self.high > self.ema52 and self.open >= self.ema52 and self.low < self.ema52: self.near0_return = 0 elif self.close > self.ema52 and self.high > self.ema52 and self.low < self.ema52: self.near0_return = 0 # 向上穿越EMA52 7 if self.close > self.ema52 and self.open < self.ema52: self.near0_return = 0 # 向下穿越EMA52 8 elif self.close < self.ema52 and self.open > self.ema52: self.near0_return = 0 if self.pre.near0_return == 7: # 向上穿越后的一根价格再EMA52上方 9 if self.low > self.ema52 and self.close > self.open: self.near0_return = 9 if self.pre.near0_return == 8: # 向下穿越后的一根价格再EMA52下方 10 if self.high < self.ema52 and self.close < self.open: self.near0_return = 10 # CROSS0 仅以 Signal 穿越零轴判定 if self.pre.signal >= 0 and self.signal < 0: self.macd_state = Chan_MACD_STATE.CROSS0_DOWN return self.macd_state if self.pre.signal <= 0 and self.signal > 0: self.macd_state = Chan_MACD_STATE.CROSS0_UP return self.macd_state # 穿零轴后的形态:缠绕/倒挂(基于前一状态为CROSS0_*) if self.pre.macd_state == Chan_MACD_STATE.CROSS0_UP or self.pre.macd_state == Chan_MACD_STATE.CROSS0_DOWN: direction = 1 if self.pre.macd_state == Chan_MACD_STATE.CROSS0_UP else -1 hist_same_dir = (self.macdhist * direction) > 0 hist_decreasing = abs(self.macdhist) < abs(self.pre.macdhist) lines_tight = abs(self.macd - self.signal) <= 12 # 倒挂:能量柱衰减且黄白线相对方向不利/出现反向能量释放 if hist_decreasing and (((self.macd - self.signal) * direction) < 0 or not hist_same_dir): self.macd_state = Chan_MACD_STATE.CROSS_REV return self.macd_state # 缠绕/粘合:紧贴能量柱运行,无反向能量释放 if lines_tight and hist_same_dir: self.macd_state = Chan_MACD_STATE.CROSS_OS return self.macd_state # 趋近零轴:细化 NEAR0_* 判定 NEAR0_EPS = 15 lines_near_zero = abs(self.macd) <= NEAR0_EPS or abs(self.signal) <= NEAR0_EPS touch_52 = (self.ema52 != 0) and ((abs(self.close - self.ema52) <= NEAR0_EPS) or (self.low <= self.ema52 <= self.high)) touch_24 = (self.ema24 != 0) and ((abs(self.close - self.ema24) <= NEAR0_EPS) or (self.low <= self.ema24 <= self.high)) # 完美形态:白线接近零轴 + 价格触碰/轻破EMA52 + 黄线不穿零轴 if abs(self.macd) <= NEAR0_EPS and touch_52 and (not (self.pre.signal >= 0 and self.signal < 0)) and (not (self.pre.signal <= 0 and self.signal > 0)): self.macd_state = Chan_MACD_STATE.NEAR0_PERFECT #self.near0_return = 1 return self.macd_state # EMA24 附近 if lines_near_zero and touch_24: self.macd_state = Chan_MACD_STATE.NEAR0_24 #self.near0_return = 2 return self.macd_state # EMA52 附近 if lines_near_zero and touch_52: self.macd_state = Chan_MACD_STATE.NEAR0_52 #self.near0_return = 3 return self.macd_state # 白线接近零轴但价格未至EMA52 if abs(self.macd) <= NEAR0_EPS and not touch_52: self.macd_state = Chan_MACD_STATE.NEAR0_DIFF #self.near0_return = 4 return self.macd_state # 一般近零轴 if lines_near_zero or touch_52: self.macd_state = Chan_MACD_STATE.NEAR0 #self.near0_return = 5 return self.macd_state # 穿零轴后离开零轴 if self.pre.macd_state == Chan_MACD_STATE.CROSS0_UP and ((self.macd >= self.pre.macd and self.signal >= self.pre.signal) or (abs(self.macdhist) >= abs(self.pre.macdhist))): self.macd_state = Chan_MACD_STATE.UP return self.macd_state if self.pre.macd_state == Chan_MACD_STATE.CROSS0_DOWN and ((self.macd <= self.pre.macd and self.signal <= self.pre.signal) or (abs(self.macdhist) >= abs(self.pre.macdhist))): self.macd_state = Chan_MACD_STATE.DOWN return self.macd_state if self.pre.macd_state == Chan_MACD_STATE.UP and self.macd > self.pre.macd and self.signal > self.pre.signal: self.macd_state = Chan_MACD_STATE.UP return self.macd_state if self.pre.macd_state == Chan_MACD_STATE.DOWN and self.macd < self.pre.macd and self.signal < self.pre.signal: self.macd_state = Chan_MACD_STATE.DOWN return self.macd_state # 趋势兜底:强势同步上行/下行直接进入 UP/DOWN if self.macd > 0 and self.signal > 0 and (self.macd >= self.pre.macd and self.signal >= self.pre.signal): self.macd_state = Chan_MACD_STATE.UP return self.macd_state if self.macd < 0 and self.signal < 0 and (self.macd <= self.pre.macd and self.signal <= self.pre.signal): self.macd_state = Chan_MACD_STATE.DOWN return self.macd_state # 峰值:白线高位出现局部顶 if hasattr(self.pre, 'pre') and self.pre and self.pre.pre and self.macd > 0: if self.pre.macd > self.pre.pre.macd and self.pre.macd > self.macd: self.macd_state = Chan_MACD_STATE.PEAK return self.macd_state # 高位状态的位置状态, 高位,高位空,归零轴 if (self.pre.macd_state == Chan_MACD_STATE.UP or self.pre.macd_state == Chan_MACD_STATE.HIGH or self.pre.macd_state == Chan_MACD_STATE.RZ_UP or self.pre.macd_state == Chan_MACD_STATE.PEAK or self.pre.macd_state == Chan_MACD_STATE.HIGH_EMPTY) and self.macd > 0: # 高位空(正区间):能量柱衰减且黄白线间距较大 if abs(self.pre.macdhist) > 0 and abs(self.macdhist) < abs(self.pre.macdhist) and abs(self.macd - self.signal) > 5: self.macd_state = Chan_MACD_STATE.HIGH_EMPTY return self.macd_state if abs(self.pre.macd - self.macd) < 10: self.macd_state = Chan_MACD_STATE.HIGH return self.macd_state else: if self.macd > self.pre.macd and self.signal > self.pre.signal: self.macd_state = Chan_MACD_STATE.UP return self.macd_state elif self.macd < self.pre.macd and self.signal < self.pre.signal: self.macd_state = Chan_MACD_STATE.RETURN_ZERO return self.macd_state if (self.pre.macd_state == Chan_MACD_STATE.DOWN or self.pre.macd_state == Chan_MACD_STATE.HIGH or self.pre.macd_state == Chan_MACD_STATE.RZ_DOWN or self.pre.macd_state == Chan_MACD_STATE.PEAK or self.pre.macd_state == Chan_MACD_STATE.HIGH_EMPTY) and self.macd < 0: # 高位空(负区间):能量柱衰减且黄白线间距较大 if abs(self.pre.macdhist) > 0 and abs(self.macdhist) < abs(self.pre.macdhist) and abs(self.macd - self.signal) > 5: self.macd_state = Chan_MACD_STATE.HIGH_EMPTY return self.macd_state if abs(self.pre.macd - self.macd) < 10: self.macd_state = Chan_MACD_STATE.HIGH return self.macd_state else: if self.macd > self.pre.macd and self.signal > self.pre.signal: self.macd_state = Chan_MACD_STATE.RETURN_ZERO return self.macd_state elif self.macd < self.pre.macd and self.signal < self.pre.signal: self.macd_state = Chan_MACD_STATE.DOWN return self.macd_state # 离开0轴开始上涨或者下跌阶段,高位之前的 if self.macd > 0 and self.pre: if (self.pre.macd_state == Chan_MACD_STATE.NEAR0 or self.pre.macd_state == Chan_MACD_STATE.RZ_UP or self.pre.macd_state == Chan_MACD_STATE.CROSS0_UP) and (self.signal > self.pre.signal or self.close > self.ema52): self.macd_state = Chan_MACD_STATE.RZ_UP return self.macd_state elif self.macd < 0 and self.pre: if (self.pre.macd_state == Chan_MACD_STATE.NEAR0 or self.pre.macd_state == Chan_MACD_STATE.RZ_DOWN or self.pre.macd_state == Chan_MACD_STATE.CROSS0_DOWN) and (self.signal < self.pre.signal or self.close < self.ema52): self.macd_state = Chan_MACD_STATE.RZ_DOWN return self.macd_state # 归零轴走势 if self.pre.macd_state == Chan_MACD_STATE.RETURN_ZERO: if self.macd > 0: if self.pre.macd > self.macd or abs(self.macdhist) <= abs(self.pre.macdhist) or self.signal <= self.pre.signal: self.macd_state = Chan_MACD_STATE.RETURN_ZERO return self.macd_state else: if self.pre.macd < self.macd or abs(self.macdhist) <= abs(self.pre.macdhist) or self.signal >= self.pre.signal: self.macd_state = Chan_MACD_STATE.RETURN_ZERO return self.macd_state # 从 NEAR0 收敛到零轴的归零轴承接(正负两侧) if self.pre.macd_state == Chan_MACD_STATE.NEAR0: # 正区间朝零轴收敛 if self.macd > 0 and self.pre.macd > 0 and self.macd <= self.pre.macd and self.signal <= self.pre.signal: self.macd_state = Chan_MACD_STATE.RETURN_ZERO return self.macd_state # 负区间朝零轴收敛 if self.macd < 0 and self.pre.macd < 0 and self.macd >= self.pre.macd and self.signal >= self.pre.signal: self.macd_state = Chan_MACD_STATE.RETURN_ZERO return self.macd_state # 其余情况 if self.pre.macd_state == Chan_MACD_STATE.UNKNOWN: if self.macd > 0 and self.close > self.ema52 and self.pre.pre and (self.pre.pre.macd_state == Chan_MACD_STATE.UP or self.pre.pre.macd_state == Chan_MACD_STATE.RZ_UP): self.macd_state = self.pre.pre.macd_state return self.macd_state elif self.macd < 0 and self.close < self.ema52 and self.pre.pre and (self.pre.pre.macd_state == Chan_MACD_STATE.DOWN or self.pre.pre.macd_state == Chan_MACD_STATE.RZ_DOWN): self.macd_state = self.pre.pre.macd_state return self.macd_state else: self.macd_state = Chan_MACD_STATE.UNKNOWN return self.macd_state return self.macd_state