745 lines
25 KiB
Python
745 lines
25 KiB
Python
from ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE, Chan_K_DIR, Chan_MACD_STATE, Chan_MACDHIST_STATE
|
|
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.ma5 = 0
|
|
self.ma10 = 0
|
|
self.ma30 = 0
|
|
self.ma50 = 0
|
|
self.ma200 = 0
|
|
self.ma250 = 0
|
|
self.rsi = 0
|
|
self.volume_ratio = 0
|
|
self.bbp120 = 0
|
|
self.bbp365 = 0
|
|
self.bb120 = 0
|
|
self.bb365 = 0
|
|
self.bbp302 = 0
|
|
self.bbup302 = 0
|
|
self.bblow302 = 0
|
|
self.bbup30 = 0
|
|
self.bblow30 = 0
|
|
# === 新增:K线类型 ===
|
|
self.kline_type = None # K线类型:大阳线、大阴线、小阳线、小阴线
|
|
|
|
# === 新增:实时分型相关属性 ===
|
|
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.cal_klu_min_max()
|
|
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
|
|
self.upper_shadow_ratio = self.upper_shadow / self.body
|
|
self.lower_shadow_ratio = self.lower_shadow / self.body
|
|
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.strength = 0 if self.candle_dir == Chan_K_DIR.CROSS else self.cal_klu_strength()
|
|
|
|
self.ema52 = 0
|
|
self.ema24 = 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.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)
|
|
#print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength)
|
|
def cal_klu_strength(self):
|
|
strength = 0
|
|
if range != 0:
|
|
if self.candle_dir == Chan_K_DIR.BULL and (self.upper_shadow + self.lower_shadow) != 0:
|
|
strength += self.body / self.range
|
|
strength += self.body / (self.upper_shadow + self.lower_shadow)
|
|
elif self.candle_dir == Chan_K_DIR.BEAR and (self.upper_shadow + self.lower_shadow) != 0:
|
|
strength -=self.body / self.range
|
|
strength -= self.body / (self.upper_shadow + self.lower_shadow)
|
|
#print(self.time, self.body, self.range, self.upper_shadow, self.lower_shadow, self.candle_dir, strength)
|
|
return strength
|
|
return strength
|
|
def cal_klu_min_max(self):
|
|
"""
|
|
计算K线类型:大阳线、大阴线、小阳线、小阴线
|
|
"""
|
|
if self.open <= 0: # 避免除零错误
|
|
self.kline_type = None
|
|
return
|
|
|
|
# 计算涨跌幅
|
|
price_change_ratio = (self.close - self.open) / self.open
|
|
strength = 0
|
|
# 判断K线类型
|
|
if price_change_ratio > 0.005: # 涨幅超过2%
|
|
self.kline_type = Chan_KLU_TYPE.BigBull
|
|
strength += abs(price_change_ratio)
|
|
elif price_change_ratio > 0: # 涨幅0-2%
|
|
self.kline_type = Chan_KLU_TYPE.SmallBull
|
|
strength += abs(price_change_ratio)
|
|
elif price_change_ratio < -0.005: # 跌幅超过2%
|
|
self.kline_type = Chan_KLU_TYPE.BigBear
|
|
strength += abs(price_change_ratio)
|
|
elif price_change_ratio < 0: # 跌幅0-2%
|
|
self.kline_type = Chan_KLU_TYPE.SmallBear
|
|
strength += abs(price_change_ratio)
|
|
else: # 开盘价等于收盘价
|
|
self.kline_type = Chan_KLU_TYPE.Cross
|
|
strength += abs(price_change_ratio)
|
|
return strength
|
|
def set_next(self, next):
|
|
self.next = next
|
|
self.update_realtime_analysis()
|
|
#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_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 detect_realtime_fx(self):
|
|
"""
|
|
实时检测K线分型(不等待KLC确认)
|
|
基于原始K线的即时分型识别
|
|
"""
|
|
if not self.pre or not self.next:
|
|
self.fx_type = Chan_FX_TYPE.UNKNOWN
|
|
return False
|
|
|
|
# 顶分型检测
|
|
if (self.high > self.pre.high and
|
|
self.high > self.next.high):
|
|
self.fx_type = Chan_FX_TYPE.TOP
|
|
self.fx_confirmed = True
|
|
return True
|
|
|
|
# 底分型检测
|
|
elif (self.low < self.pre.low and
|
|
self.low < self.next.low):
|
|
self.fx_type = Chan_FX_TYPE.BOTTOM
|
|
self.fx_confirmed = True
|
|
return True
|
|
|
|
self.fx_type = Chan_FX_TYPE.UNKNOWN
|
|
self.fx_confirmed = False
|
|
return False
|
|
def cal_fx(self):
|
|
"""
|
|
根据缠论经典规则计算分型强弱
|
|
返回分型强度:3=极强,2=强,1=中等,0=弱,-1=极弱
|
|
"""
|
|
if self.fx_type == Chan_FX_TYPE.UNKNOWN or not self.pre or not self.next:
|
|
return 0
|
|
|
|
if self.fx_type == Chan_FX_TYPE.TOP:
|
|
return self._cal_top_fx_strength()
|
|
else: # BOTTOM
|
|
return self._cal_bottom_fx_strength()
|
|
|
|
def _check_contain_relation(self, k1, k2):
|
|
"""检查两根K线是否存在包含关系"""
|
|
return (k1.high >= k2.high and k1.low <= k2.low) or (k2.high >= k1.high and k2.low <= k1.low)
|
|
|
|
def _is_big_yang_line(self, klu):
|
|
"""判断是否为大阳线"""
|
|
return klu.close > klu.open and (klu.close - klu.open) / klu.open > 0.02
|
|
|
|
def _is_big_yin_line(self, klu):
|
|
"""判断是否为大阴线"""
|
|
return klu.close < klu.open and (klu.open - klu.close) / klu.open > 0.02
|
|
|
|
def _is_small_line(self, klu):
|
|
"""判断是否为小K线"""
|
|
return abs(klu.close - klu.open) / klu.open < 0.01
|
|
|
|
def _has_long_upper_shadow(self, klu):
|
|
"""判断是否有长上影线"""
|
|
body_size = abs(klu.close - klu.open)
|
|
upper_shadow = klu.high - max(klu.close, klu.open)
|
|
return upper_shadow > body_size * 1.5
|
|
|
|
def _cal_top_fx_strength(self):
|
|
"""计算顶分型强度"""
|
|
strength = 0
|
|
k1, k2, k3 = self.pre, self, self.next
|
|
|
|
# (1) 检查包含关系 - 没有包含关系加分
|
|
has_contain_12 = self._check_contain_relation(k1, k2)
|
|
has_contain_23 = self._check_contain_relation(k2, k3)
|
|
|
|
if not has_contain_12 and not has_contain_23:
|
|
strength += 1 # 完全没有包含关系,加1分
|
|
|
|
# (2) 检查第1条K线是大阳线,第2、3条是小K线的情况
|
|
if self._is_big_yang_line(k1) and self._is_small_line(k2) and self._is_small_line(k3):
|
|
strength -= 2 # 中继顶分型特征,减2分
|
|
|
|
# (3) 检查第2条K线有长上影线或大阴线,且第3条K线条件
|
|
k2_mid = (k2.high + k2.low) / 2
|
|
k3_is_yang = k3.close > k3.open
|
|
k3_close_above_mid = k3.close > k2_mid
|
|
|
|
if (self._has_long_upper_shadow(k2) or self._is_big_yin_line(k2)) and not (k3_is_yang and k3_close_above_mid):
|
|
strength += 2 # 力度大的顶分型,加2分
|
|
|
|
# (4) 检查第2、3条K线包含关系,第3条为大阴线"吃掉"第2条
|
|
if has_contain_23 and self._is_big_yin_line(k3) and k3.low < k2.low and k3.high < k2.high:
|
|
strength += 1 # 最坏包含关系,但对顶分型有利,加1分
|
|
|
|
# (5) 第3条K线跌破第1条K线底部且不能高于第1条K线区间一半之上
|
|
k1_mid = (k1.high + k1.low) / 2
|
|
if k3.low < k1.low and k3.high < k1_mid:
|
|
strength -= 1 # 较弱的顶分型,减1分
|
|
|
|
# 额外检查:第3条K线收盘价相对第1条K线的位置
|
|
if k3.close < k1.low:
|
|
strength += 1 # 强烈下跌确认,加1分
|
|
|
|
return max(-1, min(3, strength)) # 限制在-1到3范围内
|
|
|
|
def _cal_bottom_fx_strength(self):
|
|
"""计算底分型强度"""
|
|
strength = 0
|
|
k1, k2, k3 = self.pre, self, self.next
|
|
|
|
# 底分型上边沿
|
|
fx_top = max(k1.high, k2.high)
|
|
|
|
# (1) 第3条K线高点远高于第1条K线高点
|
|
if k3.high > k1.high * 1.02: # 高出2%以上认为是"远高于"
|
|
strength += 2 # 较强走势,加2分
|
|
|
|
# (2) 第3条K线高点正好是第1根K线高点,或略微高于底分型上边沿
|
|
elif k1.high * 0.99 <= k3.high <= fx_top * 1.01: # 在合理范围内
|
|
strength += 0 # 一般走势,不加分也不减分
|
|
|
|
# (3) 第3条K线高点低于第1条K线高点
|
|
elif k3.high < k1.high:
|
|
strength -= 1 # 较弱走势,减1分
|
|
|
|
# 检查包含关系
|
|
has_contain_12 = self._check_contain_relation(k1, k2)
|
|
has_contain_23 = self._check_contain_relation(k2, k3)
|
|
|
|
if not has_contain_12 and not has_contain_23:
|
|
strength += 1 # 完全没有包含关系,加1分
|
|
|
|
# 检查第3条K线是否为强阳线
|
|
if self._is_big_yang_line(k3):
|
|
strength += 1 # 强阳线确认,加1分
|
|
|
|
# (4) 检查后续第1条K线(如果存在)
|
|
if hasattr(k3, 'next') and k3.next:
|
|
next_k = k3.next
|
|
if next_k.low > fx_top:
|
|
strength += 2 # 后续K线低点高于底分型上边沿,强烈确认,加2分
|
|
elif next_k.low <= k2.low:
|
|
strength -= 1 # 后续K线跌破分型低点,减1分
|
|
|
|
return max(-1, min(3, strength)) # 限制在-1到3范围内
|
|
|
|
def calculate_realtime_fx_strength(self):
|
|
"""
|
|
用self.pre和self.next实现分型强弱判断(与KLC中cal_fx_strength一致)
|
|
|
|
核心缠论原理:
|
|
- 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折
|
|
- 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸
|
|
|
|
返回值:
|
|
3: 极强分型(笔终结+强确认)
|
|
2: 强分型(笔终结)
|
|
1: 偏强分型(可能终结笔)
|
|
0: 中性分型
|
|
-1: 偏弱分型(中继特征明显)
|
|
-2: 弱分型(明显中继)
|
|
-3: 极弱分型(无效分型)
|
|
"""
|
|
# 检查是否为分型,且有前后K线数据
|
|
if self.fx_type == Chan_FX_TYPE.UNKNOWN:
|
|
return 0
|
|
if not self.pre or not self.next:
|
|
return 100
|
|
# === 核心判断:分型在笔中的位置 ===
|
|
|
|
# 1. 检查这个分型是否能够终结当前笔
|
|
is_bi_end = self._check_if_bi_ending_fx()
|
|
|
|
# 2. 检查分型的后续走势确认
|
|
post_fx_confirmation = self._check_post_fx_confirmation()
|
|
|
|
# 3. 检查分型的标准性和强度
|
|
fx_quality = self._check_fx_quality()
|
|
|
|
# === 综合评分 ===
|
|
base_score = 0
|
|
|
|
# 笔位置是最重要的判断标准
|
|
if is_bi_end == 2: # 强烈确认笔终结
|
|
base_score = 2
|
|
elif is_bi_end == 1: # 可能笔终结
|
|
base_score = 1
|
|
elif is_bi_end == -1: # 明显中继
|
|
base_score = -2
|
|
elif is_bi_end == -2: # 强烈中继特征
|
|
base_score = -3
|
|
else: # 不确定
|
|
base_score = 0
|
|
|
|
# 后续确认调整
|
|
base_score += post_fx_confirmation
|
|
|
|
# 分型质量调整
|
|
base_score += fx_quality
|
|
|
|
# 限制在-3到3范围内
|
|
final_score = max(-3, min(3, base_score))
|
|
self.fx_strength = final_score
|
|
# 转换为0-100分制以保持接口一致性
|
|
#self.fx_strength = int((final_score + 3) * 100 / 6) # -3到3映射到0-100
|
|
#if final_score > 1.8:
|
|
#print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality)
|
|
#print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality)
|
|
#self.fx_strength = self.cal_fx()
|
|
return self.fx_strength
|
|
|
|
def _check_if_bi_ending_fx(self):
|
|
"""
|
|
检查分型是否为笔终结分型
|
|
返回值:
|
|
2: 强烈确认笔终结
|
|
1: 可能笔终结
|
|
0: 不确定
|
|
-1: 明显中继
|
|
-2: 强烈中继特征
|
|
"""
|
|
# 检查是否有足够的后续数据来判断
|
|
if not self.next or not hasattr(self.next, 'next'):
|
|
return 0
|
|
|
|
# 获取分型后的几根K线数据
|
|
subsequent_klus = []
|
|
temp = self.next
|
|
for i in range(2): # 检查后续2根K线
|
|
if temp:
|
|
subsequent_klus.append(temp)
|
|
temp = temp.next if hasattr(temp, 'next') else None
|
|
else:
|
|
break
|
|
|
|
if len(subsequent_klus) < 2:
|
|
return 0
|
|
|
|
if self.fx_type == Chan_FX_TYPE.TOP:
|
|
return self._check_top_bi_ending(subsequent_klus)
|
|
else: # BOTTOM
|
|
return self._check_bottom_bi_ending(subsequent_klus)
|
|
|
|
def _check_top_bi_ending(self, subsequent_klus):
|
|
"""检查顶分型是否为笔终结"""
|
|
# 强烈笔终结特征:
|
|
# 1. 后续K线持续下跌,且跌破关键位置
|
|
# 2. 没有新的更高的高点出现
|
|
|
|
broken_key_levels = 0
|
|
new_highs = 0
|
|
downward_trend = 0
|
|
|
|
# 检查关键价位突破
|
|
first_low = self.pre.low
|
|
middle_low = self.low
|
|
key_support = min(first_low, middle_low)
|
|
|
|
for i, klu in enumerate(subsequent_klus):
|
|
# 检查是否跌破关键支撑
|
|
if klu.low < key_support:
|
|
broken_key_levels += 1
|
|
|
|
# 检查是否出现新高
|
|
if klu.high > self.high:
|
|
new_highs += 1
|
|
|
|
# 检查下跌趋势
|
|
if i > 0 and klu.close < subsequent_klus[i-1].close:
|
|
downward_trend += 1
|
|
|
|
# 强烈笔终结:跌破关键位且无新高
|
|
if broken_key_levels >= 1 and new_highs == 0 and downward_trend >= 2:
|
|
return 2
|
|
|
|
# 可能笔终结:部分条件满足
|
|
if (broken_key_levels >= 1 and new_highs <= 1) or (new_highs == 0 and downward_trend >= 3):
|
|
return 1
|
|
|
|
# 明显中继:出现新高且未跌破关键位
|
|
if new_highs >= 2 and broken_key_levels == 0:
|
|
return -2
|
|
|
|
# 中继倾向:出现新高
|
|
if new_highs >= 1:
|
|
return -1
|
|
|
|
return 0
|
|
|
|
def _check_bottom_bi_ending(self, subsequent_klus):
|
|
"""检查底分型是否为笔终结"""
|
|
# 强烈笔终结特征:
|
|
# 1. 后续K线持续上涨,且突破关键位置
|
|
# 2. 没有新的更低的低点出现
|
|
|
|
broken_key_levels = 0
|
|
new_lows = 0
|
|
upward_trend = 0
|
|
|
|
# 检查关键价位突破
|
|
first_high = self.pre.high
|
|
middle_high = self.high
|
|
key_resistance = max(first_high, middle_high)
|
|
|
|
for i, klu in enumerate(subsequent_klus):
|
|
# 检查是否突破关键阻力
|
|
if klu.high > key_resistance:
|
|
broken_key_levels += 1
|
|
|
|
# 检查是否出现新低
|
|
if klu.low < self.low:
|
|
new_lows += 1
|
|
|
|
# 检查上涨趋势
|
|
if i > 0 and klu.close > subsequent_klus[i-1].close:
|
|
upward_trend += 1
|
|
|
|
# 强烈笔终结:突破关键位且无新低
|
|
if broken_key_levels >= 1 and new_lows == 0 and upward_trend >= 2:
|
|
return 2
|
|
|
|
# 可能笔终结:部分条件满足
|
|
if (broken_key_levels >= 1 and new_lows <= 1) or (new_lows == 0 and upward_trend >= 3):
|
|
return 1
|
|
|
|
# 明显中继:出现新低且未突破关键位
|
|
if new_lows >= 2 and broken_key_levels == 0:
|
|
return -2
|
|
|
|
# 中继倾向:出现新低
|
|
if new_lows >= 1:
|
|
return -1
|
|
|
|
return 0
|
|
|
|
def _check_post_fx_confirmation(self):
|
|
"""
|
|
检查分型后的走势确认
|
|
返回值:-1到1的调整分数
|
|
"""
|
|
if not self.next:
|
|
return 0
|
|
|
|
score = 0
|
|
|
|
# 检查第三根K线的确认
|
|
third_klu = self.next
|
|
|
|
if self.fx_type == Chan_FX_TYPE.TOP:
|
|
# 顶分型:第三根K线应该走弱
|
|
middle_price = (self.high + self.low) / 2
|
|
|
|
if third_klu.close < middle_price:
|
|
score += 0.5
|
|
if third_klu.low < self.pre.low: # 跌破第一根K线低点
|
|
score += 0.5
|
|
if third_klu.close < third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5:
|
|
score += 0.3 # 明显阴线
|
|
|
|
else: # BOTTOM
|
|
# 底分型:第三根K线应该走强
|
|
middle_price = (self.high + self.low) / 2
|
|
|
|
if third_klu.close > middle_price:
|
|
score += 0.5
|
|
if third_klu.high > self.pre.high: # 突破第一根K线高点
|
|
score += 0.5
|
|
if third_klu.close > third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5:
|
|
score += 0.3 # 明显阳线
|
|
|
|
return min(1, max(-1, score))
|
|
|
|
def _check_fx_quality(self):
|
|
"""
|
|
检查分型本身的质量
|
|
返回值:-1到1的调整分数
|
|
"""
|
|
score = 0
|
|
|
|
# 检查分型的标准性
|
|
if self.fx_type == Chan_FX_TYPE.TOP:
|
|
# 高点突出程度
|
|
high_diff1 = (self.high - self.pre.high) / self.high if self.high > 0 else 0
|
|
high_diff2 = (self.high - self.next.high) / self.high if self.high > 0 else 0
|
|
min_diff = min(high_diff1, high_diff2)
|
|
|
|
if min_diff > 0.03: # 非常突出
|
|
score += 0.5
|
|
elif min_diff > 0.01: # 比较突出
|
|
score += 0.2
|
|
elif min_diff < 0.003: # 不够突出
|
|
score -= 0.5
|
|
|
|
else: # BOTTOM
|
|
# 低点突出程度
|
|
low_diff1 = (self.pre.low - self.low) / self.pre.low if self.pre.low > 0 else 0
|
|
low_diff2 = (self.next.low - self.low) / self.next.low if self.next.low > 0 else 0
|
|
min_diff = min(low_diff1, low_diff2)
|
|
|
|
if min_diff > 0.03: # 非常突出
|
|
score += 0.5
|
|
elif min_diff > 0.01: # 比较突出
|
|
score += 0.2
|
|
elif min_diff < 0.003: # 不够突出
|
|
score -= 0.5
|
|
|
|
# 检查量价配合
|
|
avg_volume = self._get_avg_volume(lookback=5)
|
|
if avg_volume > 0:
|
|
volume_ratio = self.volume / avg_volume
|
|
if volume_ratio > 1.5:
|
|
score += 0.3
|
|
elif volume_ratio < 0.7:
|
|
score -= 0.2
|
|
|
|
return min(1, max(-1, score))
|
|
|
|
def _get_avg_volume(self, lookback=5):
|
|
"""获取前N根K线平均成交量"""
|
|
volumes = []
|
|
temp = self.pre
|
|
|
|
for i in range(lookback):
|
|
if temp:
|
|
volumes.append(temp.volume)
|
|
temp = temp.pre if hasattr(temp, 'pre') else None
|
|
else:
|
|
break
|
|
|
|
return sum(volumes) / len(volumes) if volumes else self.volume
|
|
|
|
def get_fx_signal(self):
|
|
"""
|
|
获取分型交易信号
|
|
返回: (信号类型, 强度, 建议)
|
|
"""
|
|
if not self.fx_confirmed:
|
|
return ("无信号", 0, "等待分型确认")
|
|
|
|
strength_level = "弱"
|
|
if self.fx_strength >= 80:
|
|
strength_level = "极强"
|
|
elif self.fx_strength >= 65:
|
|
strength_level = "强"
|
|
elif self.fx_strength >= 50:
|
|
strength_level = "中等"
|
|
|
|
if self.fx_type == Chan_FX_TYPE.TOP:
|
|
signal_type = f"{strength_level}顶分型"
|
|
if self.fx_strength >= 65:
|
|
suggestion = "考虑减仓或止盈"
|
|
else:
|
|
suggestion = "谨慎观望"
|
|
else:
|
|
signal_type = f"{strength_level}底分型"
|
|
if self.fx_strength >= 65:
|
|
suggestion = "考虑建仓或加仓"
|
|
else:
|
|
suggestion = "谨慎观望"
|
|
|
|
return (signal_type, self.fx_strength, suggestion)
|
|
|
|
def update_realtime_analysis(self):
|
|
"""
|
|
更新实时分析(在每根K线完成时调用)
|
|
"""
|
|
self.detect_realtime_fx()
|
|
if self.fx_confirmed:
|
|
self.calculate_realtime_fx_strength()
|
|
|
|
def set_idx(self, idx):
|
|
self.idx = idx
|
|
self.index = idx
|
|
|
|
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.ma5 = float(item['ma5']) if 'ma5' in item and item['ma5'] else 0
|
|
self.ma10 = float(item['ma10']) if 'ma10' in item and item['ma10'] else 0
|
|
self.ma30 = float(item['ma30']) if 'ma30' in item and item['ma30'] else 0
|
|
|
|
# 安全检查 ma250、ma50 和 ma200
|
|
self.ma250 = float(item['ma250']) if 'ma250' in item and item['ma250'] else 0
|
|
self.ma50 = float(item['ma50']) if 'ma50' in item and item['ma50'] else 0
|
|
self.ma200 = float(item['ma200']) if 'ma200' in item and item['ma200'] 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.bbp120 = float(item['bbp120']) if 'bbp120' in item and item['bbp120'] else 0
|
|
self.bbp365 = float(item['bbp365']) if 'bbp365' in item and item['bbp365'] else 0
|
|
self.bb120 = float(item['bb120']) if 'bb120' in item and item['bb120'] else 0
|
|
self.bb365 = float(item['bb365']) if 'bb365' in item and item['bb365'] else 0
|
|
self.bbp30 = float(item['bbp30']) if 'bbp30' in item and item['bbp30'] else 0
|
|
self.bbup30 = float(item['bbup30']) if 'bbup30' in item and item['bbup30'] else 0
|
|
self.bblow30 = float(item['bblow30']) if 'bblow30' in item and item['bblow30'] else 0
|
|
self.bbp302 = float(item['bbp302']) if 'bbp302' in item and item['bbp302'] else 0
|
|
self.bbup302 = float(item['bbup302']) if 'bbup302' in item and item['bbup302'] else 0
|
|
self.bblow302 = float(item['bblow302']) if 'bblow302' in item and item['bblow302'] else 0
|
|
self.bbup120 = float(item['bbup120']) if 'bbup120' in item and item['bbup120'] else 0
|
|
self.bblow120 = float(item['bblow120']) if 'bblow120' in item and item['bblow120'] else 0
|
|
self.bbup365 = float(item['bbup365']) if 'bbup365' in item and item['bbup365'] else 0
|
|
self.bblow365 = float(item['bblow365']) if 'bblow365' in item and item['bblow365'] else 0
|
|
# 设置指标后更新实时分析
|
|
self.update_realtime_analysis()
|
|
#self.cal_macd_state()
|
|
def cal_macd_state(self):
|
|
# 按定义精简实现:优先级 CROSS0 > 位置(HIGH/HE/RETURN_ZERO) > NEAR0 > UNKNOWN
|
|
# 1) 首条或缺前一根
|
|
if not hasattr(self, 'pre') or self.pre is None:
|
|
self.macd_state = Chan_MACD_STATE.START
|
|
return self.macd_state
|
|
|
|
# 2) 基本校验
|
|
if self.macd == 0 and self.signal == 0 and self.macdhist == 0:
|
|
self.macd_state = Chan_MACD_STATE.UNKNOWN
|
|
return self.macd_state
|
|
|
|
# 调试:当前值
|
|
#print(f"DEBUG: MACD值检查 - 时间: {self.time}, MACD: {self.macd:.4f}, Signal: {self.signal:.4f}, 前一根Signal: {self.pre.signal:.4f}")
|
|
|
|
# 3) CROSS0 仅以 Signal 穿越零轴判定
|
|
if self.pre.signal >= 0 and self.signal < 0:
|
|
self.macd_state = Chan_MACD_STATE.CROSS0_DOWN
|
|
#print(f"DEBUG: CROSS0触发(向下) - 时间: {self.time}, Signal: {self.pre.signal:.4f}->{self.signal:.4f}")
|
|
return self.macd_state
|
|
if self.pre.signal <= 0 and self.signal > 0:
|
|
self.macd_state = Chan_MACD_STATE.CROSS0_UP
|
|
#print(f"DEBUG: CROSS0触发(向上) - 时间: {self.time}, Signal: {self.pre.signal:.4f}->{self.signal:.4f}")
|
|
return self.macd_state
|
|
|
|
# 4) 高位状态的位置状态, 高位,高位空,归零轴
|
|
position_state = Chan_MACD_STATE.UNKNOWN
|
|
if abs(self.macd) > 50:
|
|
position_state = self._cal_position_state()
|
|
if position_state != Chan_MACD_STATE.UNKNOWN:
|
|
self.macd_state = position_state
|
|
#print(f"DEBUG: 位置状态设置 - 时间: {self.time}, 状态: {self.macd_state}, MACD: {self.macd:.4f}, Signal: {self.signal:.4f}")
|
|
return self.macd_state
|
|
|
|
# 5) 趋近零轴:任一线接近零轴即可,无限接近零轴,EMA52没有加
|
|
NEAR0_EPS = 15
|
|
if abs(self.macd) <= NEAR0_EPS or abs(self.signal) <= NEAR0_EPS:
|
|
self.macd_state = Chan_MACD_STATE.NEAR0
|
|
#print(f"DEBUG: 趋近零轴触发 - 时间: {self.time}, MACD: {self.macd:.4f}, Signal: {self.signal:.4f}")
|
|
return self.macd_state
|
|
# 6) 离开0轴开始上涨或者下跌阶段,高位之前的
|
|
if self.macd_state == Chan_MACD_STATE.UNKNOWN:
|
|
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) and self.signal > self.pre.signal:
|
|
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) and self.signal < self.pre.signal:
|
|
self.macd_state = Chan_MACD_STATE.RZ_DOWN
|
|
return self.macd_state
|
|
# 7) 其余情况
|
|
self.macd_state = Chan_MACD_STATE.UNKNOWN
|
|
return self.macd_state
|
|
|
|
def _cal_position_state(self):
|
|
"""计算MACD位置状态:高位、高位空、归零轴"""
|
|
if not hasattr(self, 'pre') or self.pre is None:
|
|
return Chan_MACD_STATE.UNKNOWN
|
|
pre_hist = abs(self.pre.macdhist)
|
|
cur_hist = abs(self.macdhist)
|
|
if self.next:
|
|
next_hist = abs(self.next.macdhist)
|
|
if cur_hist > pre_hist and cur_hist > next_hist:
|
|
return Chan_MACD_STATE.HIGH
|
|
if self.pre.macd_state == Chan_MACD_STATE.HIGH or self.pre.macd_state == Chan_MACD_STATE.HIGH_EMPTY:
|
|
if self.macd > 0:
|
|
if self.macd > self.signal:
|
|
return Chan_MACD_STATE.HIGH_EMPTY
|
|
else:
|
|
return Chan_MACD_STATE.RETURN_ZERO
|
|
elif self.macd < 0:
|
|
if self.macd < self.signal:
|
|
return Chan_MACD_STATE.HIGH_EMPTY
|
|
else:
|
|
return Chan_MACD_STATE.RETURN_ZERO
|
|
return Chan_MACD_STATE.UNKNOWN
|
|
def get_feature_data(self):
|
|
features = dict()
|
|
features['klu_close'] = self.close
|
|
features['klu_open'] = self.open
|
|
features['klu_high'] = self.high
|
|
features['klu_low'] = self.low
|
|
features['klu_volume'] = self.volume
|
|
features['klu_index'] = self.index
|
|
features['klu_macd'] = self.macd
|
|
features['klu_signal'] = self.signal
|
|
features['klu_macdhist'] = self.macdhist
|
|
features['klu_ma5'] = self.ma5
|
|
features['klu_ma10'] = self.ma10
|
|
features['klu_ma30'] = self.ma30
|
|
features['klu_ma50'] = self.ma50
|
|
features['klu_ma200'] = self.ma200
|
|
features['klu_ma250'] = self.ma250
|
|
features['klu_rsi'] = self.rsi
|
|
features['klu_volume_ratio'] = self.volume_ratio
|
|
|
|
# === 新增:实时分型特征 ===
|
|
# 将枚举转换为数值:UNKNOWN=0, TOP=1, BOTTOM=-1
|
|
if self.fx_type == Chan_FX_TYPE.TOP:
|
|
fx_type_value = 1
|
|
elif self.fx_type == Chan_FX_TYPE.BOTTOM:
|
|
fx_type_value = -1
|
|
else:
|
|
fx_type_value = 0
|
|
|
|
features['klu_fx_type'] = fx_type_value
|
|
features['klu_fx_strength'] = self.fx_strength
|
|
features['klu_fx_confirmed'] = 1 if self.fx_confirmed else 0
|
|
|
|
return features |