Files
Chan/chan/core/ChanKLU.py
T
PorterandCursor 2c1232555e refactor: 缠论引擎迁入 chan/ 分层解耦,指标外置
将核心结构、指标与分析拆到 chan/{core,indicators,analysis,pipeline};
根目录保留兼容 shim;strategies 改为从 chan 包导入;买卖点经 bsp_macd 与 MACD 接合。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-03 14:47:13 +08:00

390 lines
19 KiB
Python

from .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
# 指标相关字段:非结构固有,由 indicators.attach / set_indicators 填充(兼容旧访问)
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
# 以下 EMA/MACD 状态字段同为兼容挂载,默认 0;正式来源为 IndicatorStore
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
def set_indicators(self, item):
self.macd = float(item['macd']) if 'macd' in item and item['macd'] else 0
self.signal = float(item['macdsignal']) if 'macdsignal' in item and item['macdsignal'] else 0
self.macdhist = float(item['macdhist']) if 'macdhist' in item and item['macdhist'] else 0
self.ema26 = float(item['ema26']) if 'ema26' in item and item['ema26'] else 0
self.ema52 = float(item['ema52']) if 'ema52' in item and item['ema52'] else 0
self.ema24 = float(item['ema24']) if 'ema24' in item and item['ema24'] else 0
self.ema104 = float(item['ema104']) if 'ema104' in item and item['ema104'] else 0
self.ema156 = float(item['ema156']) if 'ema156' in item and item['ema156'] else 0
self.ema208 = float(item['ema208']) if 'ema208' in item and item['ema208'] else 0
self.ema13 = float(item['ema13']) if 'ema13' in item and item['ema13'] else 0
self.ema7 = float(item['ema7']) if 'ema7' in item and item['ema7'] else 0
self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0
self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0
self.bb52upper = float(item['bb52upper']) if 'bb52upper' in item and item['bb52upper'] else 0
self.bb52lower = float(item['bb52lower']) if 'bb52lower' in item and item['bb52lower'] else 0
self.bb2633upper = float(item['bb2633upper']) if 'bb2633upper' in item and item['bb2633upper'] else 0
self.bb2633lower = float(item['bb2633lower']) if 'bb2633lower' in item and item['bb2633lower'] else 0
self.bb2633middle = float(item['bb2633middle']) if 'bb2633middle' in item and item['bb2633middle'] else 0
self.ma5 = float(item['ma5']) if 'ma5' in item and item['ma5'] else 0
self.ema5 = float(item['ema5']) if 'ema5' in item and item['ema5'] 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