diff --git a/ChanEnum.py b/ChanEnum.py index 1db70bb..63d1b98 100644 --- a/ChanEnum.py +++ b/ChanEnum.py @@ -30,8 +30,15 @@ class Chan_KLINE_DIR(Enum): DOWN = auto() COMBINE = auto() INCLUDED = auto() - - +class Chan_KLU_TYPE(Enum): + BigBull = auto() + MiddleBull = auto() + SmallBull = auto() + BigBear = auto() + MiddleBear = auto() + SmallBear = auto() + Cross = auto() + class Chan_FX_TYPE(Enum): BOTTOM = auto() TOP = auto() diff --git a/ChanKLC.py b/ChanKLC.py index 3ead7ae..d6fbcd0 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -31,6 +31,10 @@ class ChanKLC(): self.rsi = klu.rsi self.volume_ratio = klu.volume_ratio self.macdhist = 0 + + # === 新增:KLC类型 === + self.klc_type = None # KLC类型:大阳线、大阴线、小阳线、小阴线 + def set_klc_fx_type(self, klc_fx_type): #print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi']) self.klc_fx_type = klc_fx_type @@ -54,7 +58,6 @@ class ChanKLC(): for klu in self.klus: klu.update_realtime_analysis() if klu.fx_type == self.fx and klu.fx_strength > 1.8: - #print(klu.time, klu.fx_type, klu.fx_strength) return True return False def set_next(self, klc): @@ -135,229 +138,6 @@ class ChanKLC(): self.bi = bi self.distance = self.index - bi.start_klc.index #print(self.start_time, self.distance, bi.index, bi.dir) - def cal_fx(self): - """ - 根据缠论分型强弱判断规则计算分型强度 - 返回值: - - 0: 不是分型或无效分型 - - 1-100: 分型强度,数值越大表示分型越强 - """ - # 检查基本条件:必须是分型且有前后KLC - if (self.fx == Chan_FX_TYPE.UNKNOWN or - self.pre is None or self.next is None or - self.next.end_klu is None): - return 0 - - # 获取分型的三根K线(KLC) - klc1 = self.pre # 第1条 - klc2 = self # 第2条(分型中心) - klc3 = self.next # 第3条 - - if self.fx == Chan_FX_TYPE.TOP: - return self._calculate_top_fx_strength(klc1, klc2, klc3) - elif self.fx == Chan_FX_TYPE.BOTTOM: - return self._calculate_bottom_fx_strength(klc1, klc2, klc3) - else: - return 0 - - def _calculate_top_fx_strength(self, klc1, klc2, klc3): - """ - 计算顶分型强度 - """ - strength = 50 # 基础分数 - - # 1. 检查包含关系(规则1) - has_inclusion = self._has_inclusion_relationship(klc1, klc2, klc3) - if not has_inclusion: - strength += 20 # 没有包含关系加分 - else: - strength -= 10 # 有包含关系减分 - - # 检查最坏的包含关系(规则4) - if self._is_worst_inclusion_for_top(klc2, klc3): - strength -= 20 # 第3条大阴线"吃掉"第2条阳线 - - # 2. 检查第1条K线是否为大阳线,第2、3条为小K线(规则2) - if self._is_big_bullish_followed_by_small(klc1, klc2, klc3): - strength -= 25 # 中继顶分型可能性大 - - # 3. 检查第2条K线形态和第3条K线位置(规则3) - if self._has_strong_top_pattern(klc2, klc3): - strength += 25 # 力度比较大的分型 - - # 4. 检查第3条K线是否跌破第1条K线(规则5) - if self._breaks_first_klc_bottom_for_top(klc1, klc3): - strength -= 15 # 较弱的顶分型 - - # 5. 成交量确认 - volume_factor = self._get_volume_factor(klc2) - strength += volume_factor - - return max(0, min(100, strength)) - - def _calculate_bottom_fx_strength(self, klc1, klc2, klc3): - """ - 计算底分型强度 - """ - strength = 50 # 基础分数 - - # 1. 检查包含关系 - has_inclusion = self._has_inclusion_relationship(klc1, klc2, klc3) - if not has_inclusion: - strength += 20 # 没有包含关系加分 - else: - strength -= 10 # 有包含关系减分 - - # 2. 检查第3条K线高点与第1条K线高点的关系(规则1-3) - high_relationship = self._analyze_bottom_high_relationship(klc1, klc3) - if high_relationship == "strong": # 第3条高点远高于第1条 - strength += 25 - elif high_relationship == "normal": # 第3条高点接近第1条 - strength += 5 - else: # 第3条高点低于第1条 - strength -= 15 - - # 3. 检查后续K线确认(规则4) - if self._has_follow_through_for_bottom(): - strength += 15 - - # 4. 成交量确认 - volume_factor = self._get_volume_factor(klc2) - strength += volume_factor - - return max(0, min(100, strength)) - - def _has_inclusion_relationship(self, klc1, klc2, klc3): - """ - 检查构成分型的三根原始K线(KLU)是否存在包含关系 - """ - # 检查任意两根KLU之间是否存在包含关系 - return (klc1.start_klu.index - klc1.end_klu.index != 0 or klc2.start_klu.index - klc2.end_klu.index != 0 or klc3.start_klu.index - klc3.end_klu.index != 0) - - def _is_worst_inclusion_for_top(self, klc2, klc3): - """ - 检查是否为最坏的包含关系:第3根KLU大阴线"吃掉"第2根KLU阳线 - """ - # 获取代表性的KLU - # 第2根KLU:取klc2的最后一根KLU - klu2 = klc2.end_klu if klc2.end_klu else klc2.start_klu - # 第3根KLU:取klc3的第一根KLU - klu3 = klc3.start_klu - - if not klu2 or not klu3: - return False - - # 检查klu2是否为阳线 - klu2_is_bullish = klu2.close > klu2.open - - # 检查klu3是否为大阴线(实体占总区间70%以上) - klu3_range = klu3.high - klu3.low - klu3_body = abs(klu3.close - klu3.open) - klu3_is_big_bearish = (klu3.close < klu3.open and - klu3_range > 0 and - klu3_body > klu3_range * 0.7) - - # 检查klu3是否包含klu2(klu3的高点≥klu2的高点 且 klu3的低点≤klu2的低点) - klu3_contains_klu2 = (klu3.high >= klu2.high and klu3.low <= klu2.low) - - return klu2_is_bullish and klu3_is_big_bearish and klu3_contains_klu2 - - def _is_big_bullish_followed_by_small(self, klc1, klc2, klc3): - """ - 检查第1条是否为大阳线,第2、3条为小K线 - """ - # 第1条为大阳线 - klc1_big_bullish = (klc1.close > klc1.open and - abs(klc1.close - klc1.open) > (klc1.high - klc1.low) * 0.6) - - # 第2、3条为小K线 - klc2_small = abs(klc2.close - klc2.open) < (klc2.high - klc2.low) * 0.4 - klc3_small = abs(klc3.close - klc3.open) < (klc3.high - klc3.low) * 0.4 - - return klc1_big_bullish and klc2_small and klc3_small - - def _has_strong_top_pattern(self, klc2, klc3): - """ - 检查是否有强力度的顶分型模式 - """ - # 第2条K线有长上影线或为大阴线 - klc2_range = klc2.high - klc2.low - if klc2_range > 0: - upper_shadow_ratio = (klc2.high - max(klc2.open, klc2.close)) / klc2_range - has_long_upper_shadow = upper_shadow_ratio > 0.3 - else: - has_long_upper_shadow = False - - klc2_big_bearish = (klc2.close < klc2.open and - abs(klc2.close - klc2.open) > klc2_range * 0.6) - - klc2_strong = has_long_upper_shadow or klc2_big_bearish - - # 第3条K线不能以阳线收在第2条K线区间的一半之上 - klc2_mid = (klc2.high + klc2.low) / 2 - klc3_weak_position = (klc3.close <= klc2_mid or klc3.close < klc3.open) - - return klc2_strong and klc3_weak_position - - def _breaks_first_klc_bottom_for_top(self, klc1, klc3): - """ - 检查第3条是否跌破第1条K线底部且不能高于第1条区间一半之上 - """ - breaks_bottom = klc3.low < klc1.low - klc1_mid = (klc1.high + klc1.low) / 2 - below_mid = klc3.close <= klc1_mid - - return breaks_bottom and below_mid - - def _analyze_bottom_high_relationship(self, klc1, klc3): - """ - 分析底分型中第3条K线高点与第1条K线高点的关系 - """ - high_diff_ratio = (klc3.high - klc1.high) / klc1.high if klc1.high > 0 else 0 - - if high_diff_ratio > 0.02: # 高出2%以上 - return "strong" - elif high_diff_ratio >= -0.01: # 接近或略高 - return "normal" - else: # 明显低于 - return "weak" - - def _has_follow_through_for_bottom(self): - """ - 检查底分型后续是否有确认 - """ - # 检查后续第1条K线的低点是否高于底分型的上边沿 - if self.next and self.next.next: - follow_klc = self.next.next - bottom_fx_top = max(self.pre.high, self.high, self.next.high) - return follow_klc.low > bottom_fx_top - return False - - def _get_volume_factor(self, klc): - """ - 获取成交量因子 - """ - avg_volume = self._calculate_average_volume(lookback=5) - if avg_volume > 0: - volume_ratio = klc.volume / avg_volume - if volume_ratio > 2.0: - return 10 # 大量确认 - elif volume_ratio > 1.5: - return 5 # 放量 - elif volume_ratio < 0.5: - return -5 # 缩量 - return 0 - def check_pre_has_fx(self): - if self.pre: - return self.pre.fx != Chan_FX_TYPE.UNKNOWN - elif self.pre.pre: - return self.pre.pre.fx != Chan_FX_TYPE.UNKNOWN - elif self.pre.pre.pre: - return self.pre.pre.pre.fx != Chan_FX_TYPE.UNKNOWN - elif self.pre.pre.pre.pre: - return self.pre.pre.pre.pre.fx != Chan_FX_TYPE.UNKNOWN - else: - return False def cal_klu_features(self): features = dict() feature_sums = dict() @@ -1377,408 +1157,707 @@ class ChanKLC(): return features - def cal_fx_strength(self): + def check_fx_klu_strength(self): + if self.pre and self.next and self.next.end_klu: + strength = 0 + klc1 = self.pre + klc2 = self + klc3 = self.next + # 检查包含关系 + inc = self.check_include_relation(klc1, klc2, klc3) + if not inc: + # 检查分型类型 + if self.fx == Chan_FX_TYPE.TOP: + # (1)没有包含关系+1 + strength += 0 + print(self.start_time, "1") + # (2)第1条K线是一条大阳线,而第2、3条K线是小阴线、小阳线,那么这个分型结构的意义就不大了,强度 -1 + if self.pre.cal_klu_min_max() > 0.5 and self.cal_klu_min_max() < 0.2 and self.next.cal_klu_min_max() < 0.2: + strength += -1 + print(self.start_time, "2") + # (3)第2条K线有长上影线或者就是大阴线,而第3条K线不能以阳线收在第2条K线区间的一半之上,那么该顶分型的力度就比较大 + if ((self.close < self.open and self.cal_klu_min_max() > 0.5) or self.cal_klu_upper_shadow() > 0.6) and not (self.next.close > self.next.open and self.next.close > (self.high-self.low)/2): + strength += 1 + print(self.start_time, "3") + else: + if self.fx == Chan_FX_TYPE.TOP: + # (4)第2条K线和第3条K线为包含关系,而第3条K线为大阴线(直接把为阳线的第2条K线“吃掉”),这是最坏的一种包含关系。 + if self.start_klu.index - self.end_klu.index < 0: + klu1 = self.start_klu + klu2 = self.klus[1] + print(self.start_time, self.end_time, "length: ", len(self.klus), klu1.index, klu2.index) + if klu2.close < klu2.open and 100*abs(klu2.open - klu2.close) / klu2.open > 0.5 and klu1.high <= klu2.high and klu1.low >= klu2.low: + strength += 1 + print(self.start_time, "4") + # (5)第3条K线如果跌破第1条K线的底而且不能高于第1条K线区间的一半之上,则属于较弱的一种,也就是说这种顶分型出现后股价见顶的可能性不大。 + if self.next.low < self.pre.low and self.next.high < (self.pre.high + self.pre.low)/2: + strength += -1 + print(self.start_time, "5") + return strength + else: + return 0 + def cal_klu_upper_shadow(self): """ - 统一的分型强度计算函数 - 直接计算当前KLC的分型强度 - 包含技术指标确认 - - Returns: - int: 强度评分 15-80分 + 计算KLC的上影线长度 + 上影线 = 最高价 - max(开盘价, 收盘价) """ - return self.cal_fx() - # 如果不是分型,返回0 - if self.fx == Chan_FX_TYPE.UNKNOWN or self.pre == None or self.next == None or self.next.end_klu == None: + if self.high <= 0: # 避免无效数据 return 0 - # 如果没有前一个KLC,返回基础分 - if not self.pre: - return 15 - - score = 15 # 基础分数,任何分型都有基础分 + # 计算上影线长度 + upper_shadow = self.high - max(self.open, self.close) - # === 1. 突出程度评分(0-25分) === + # 计算相对上影线长度(相对于整个K线区间) + total_range = self.high - self.low + if total_range > 0: + upper_shadow_ratio = upper_shadow / total_range + else: + upper_shadow_ratio = 0 + + return upper_shadow_ratio + def cal_klu_min_max(self): + """ + 计算KLC类型:大阳线、大阴线、小阳线、小阴线 + """ + if self.open <= 0: # 避免除零错误 + return 0 + line_type = 1000*abs(self.open - self.close) / self.open + print(self.start_time, line_type) + return line_type + def check_include_relation(self, klc1, klc2, klc3): + if klc1.start_klu.index - klc1.end_klu.index == 0 and klc2.start_klu.index - klc2.end_klu.index == 0 and klc3.start_klu.index - klc3.end_klu.index == 0: + return False + else: + return True + def cal_fx_strength(self): + """ + 用self.pre和self.next实现分型强弱判断 + + 核心缠论原理: + - 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折 + - 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸 + + 返回值: + 3: 极强分型(笔终结+强确认) + 2: 强分型(笔终结) + 1: 偏强分型(可能终结笔) + 0: 中性分型 + -1: 偏弱分型(中继特征明显) + -2: 弱分型(明显中继) + -3: 极弱分型(无效分型) + """ + #return self.check_fx_klu_strength() + # 检查是否为分型,且有前后K线数据 + if self.fx == Chan_FX_TYPE.UNKNOWN or self.klc_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 + #base_score += self.check_fx_klu_strength() + #print(self.start_time, base_score, is_bi_end, post_fx_confirmation, fx_quality) + # 限制在-3到3范围内 + return max(-3, min(3, base_score)) + + 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_klcs = [] + temp = self.next + for i in range(5): # 检查后续5根K线 + if temp: + subsequent_klcs.append(temp) + temp = temp.next if hasattr(temp, 'next') else None + else: + break + + if len(subsequent_klcs) < 2: + return 0 + if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:当前高点与前一个高点的差异 - if self.pre.high > 0: - prominence = abs(self.high - self.pre.high) / self.pre.high - else: - prominence = 0 - else: # 底分型 - # 底分型:当前低点与前一个低点的差异 - if self.pre.low > 0: - prominence = abs(self.pre.low - self.low) / self.pre.low - else: - prominence = 0 + return self._check_top_bi_ending(subsequent_klcs) + else: # BOTTOM + return self._check_bottom_bi_ending(subsequent_klcs) + + def _check_top_bi_ending(self, subsequent_klcs): + """检查顶分型是否为笔终结""" + # 强烈笔终结特征: + # 1. 后续K线持续下跌,且跌破关键位置 + # 2. 没有新的更高的高点出现 - # 突出程度评分 - 极度宽松 - if prominence >= 0.03: # 3%以上突出 - score += 25 - elif prominence >= 0.02: # 2-3%突出 - score += 20 - elif prominence >= 0.015: # 1.5-2%突出 - score += 15 - elif prominence >= 0.01: # 1-1.5%突出 - score += 10 - elif prominence >= 0.005: # 0.5-1%突出 - score += 6 - elif prominence >= 0.002: # 0.2-0.5%突出 - score += 3 - else: - score += 1 # 任何突出度都给分 + broken_key_levels = 0 + new_highs = 0 + downward_trend = 0 - # === 2. K线形态评分(0-15分) === - kline_range = self.high - self.low - if kline_range > 0: - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型看上影线 - upper_shadow = self.high - max(self.open, self.close) - shadow_ratio = upper_shadow / kline_range - else: - # 底分型看下影线 - lower_shadow = min(self.open, self.close) - self.low - shadow_ratio = lower_shadow / kline_range + # 检查关键价位突破 + first_low = self.pre.low + middle_low = self.low + key_support = min(first_low, middle_low) + last_klc = None + for i, klc in enumerate(subsequent_klcs): + # 检查是否跌破关键支撑 + if klc.low < key_support: + broken_key_levels += 1 + + # 检查是否出现新高 + if klc.high > self.high: + new_highs += 1 + + # 检查下跌趋势 + if i > 0 and klc.close < subsequent_klcs[i-1].close: + downward_trend += 1 + last_klc = klc + print(last_klc.start_time, last_klc.end_time) + # 强烈笔终结:跌破关键位且无新高 + if broken_key_levels >= 1 and new_highs == 0 and downward_trend >= 2: + return 2 - # 影线评分 - 极度宽松 - if shadow_ratio >= 0.3: # 长影线 - score += 15 - elif shadow_ratio >= 0.2: # 明显影线 - score += 12 - elif shadow_ratio >= 0.1: # 一般影线 - score += 8 - elif shadow_ratio >= 0.05: # 短影线 - score += 5 - else: - score += 2 # 有一点影线就给分 - else: - score += 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_klcs): + """检查底分型是否为笔终结""" + # 强烈笔终结特征: + # 1. 后续K线持续上涨,且突破关键位置 + # 2. 没有新的更低的低点出现 - # === 3. 成交量评分(0-10分) === + 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) + last_klc = None + for i, klc in enumerate(subsequent_klcs): + # 检查是否突破关键阻力 + if klc.high > key_resistance: + broken_key_levels += 1 + + # 检查是否出现新低 + if klc.low < self.low: + new_lows += 1 + + # 检查上涨趋势 + if i > 0 and klc.close > subsequent_klcs[i-1].close: + upward_trend += 1 + last_klc = klc + print(last_klc.start_time, last_klc.end_time) + # 强烈笔终结:突破关键位且无新低 + 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_klc = self.next + + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型:第三根K线应该走弱 + middle_price = (self.high + self.low) / 2 + + if third_klc.close < middle_price: + score += 0.5 + if third_klc.low < self.pre.low: # 跌破第一根K线低点 + score += 0.5 + if third_klc.close < third_klc.open and abs(third_klc.close - third_klc.open) > abs(self.close - self.open) * 0.5: + score += 0.3 # 明显阴线 + + else: # BOTTOM + # 底分型:第三根K线应该走强 + middle_price = (self.high + self.low) / 2 + + if third_klc.close > middle_price: + score += 0.5 + if third_klc.high > self.pre.high: # 突破第一根K线高点 + score += 0.5 + if third_klc.close > third_klc.open and abs(third_klc.close - third_klc.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 == 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._calculate_average_volume(lookback=5) if avg_volume > 0: volume_ratio = self.volume / avg_volume - if volume_ratio >= 2.0: # 大量 - score += 10 - elif volume_ratio >= 1.5: # 明显放量 - score += 8 - elif volume_ratio >= 1.2: # 适度放量 - score += 6 - elif volume_ratio >= 1.0: # 正常量 - score += 4 - elif volume_ratio >= 0.8: # 略缩量 + if volume_ratio > 1.5: + score += 0.3 + elif volume_ratio < 0.7: + score -= 0.2 + + return min(1, max(-1, score)) + + def calculate_fx_strength(self): + """ + 基于专业缠论理论的分型强度评估体系 + 返回值:0-100的强度分数,数值越大表示分型越强 + + 评分卡系统(总分29分,转换为100分制): + - 振幅比例:25%权重,最高5分 + - 量能配合:20%权重,最高5分 + - 均线位置:15%权重,最高5分 + - 形成速度:10%权重,最高4分 + - 次级别确认:30%权重,最高10分 + """ + if self.fx == Chan_FX_TYPE.UNKNOWN or not self.pre or not self.next: + return 0 + + # ===== 一、基础要素确认(先决条件) ===== + if not self._verify_basic_fx_structure(): + return 0 + + total_score = 0 + max_score = 29 # 5+5+5+4+10 + + # ===== 二、振幅比例评估 (25%权重,最高5分) ===== + amplitude_score = self._calculate_amplitude_score() + total_score += amplitude_score + + # ===== 三、量能配合评估 (20%权重,最高5分) ===== + volume_score = self._calculate_volume_score() + total_score += volume_score + + # ===== 四、均线位置评估 (15%权重,最高5分) ===== + ma_score = self._calculate_ma_position_score() + total_score += ma_score + + # ===== 五、形成速度评估 (10%权重,最高4分) ===== + speed_score = self._calculate_formation_speed_score() + total_score += speed_score + + # ===== 六、次级别确认评估 (30%权重,最高10分) ===== + confirmation_score = self._calculate_confirmation_score() + total_score += confirmation_score + + # 转换为100分制 + final_score = (total_score / max_score) * 100 + + return round(final_score, 2) + + def _verify_basic_fx_structure(self): + """ + 验证基础分型要素(先决条件) + 只验证最核心的分型定义,避免过度严格 + """ + if not self.pre or not self.next: + return False + + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型核心要素:中间K线高点必须严格高于两侧 + if not (self.high > self.pre.high and self.high > self.next.high): + return False + + elif self.fx == Chan_FX_TYPE.BOTTOM: + # 底分型核心要素:中间K线低点必须严格低于两侧 + if not (self.low < self.pre.low and self.low < self.next.low): + return False + + return True + + def _calculate_amplitude_score(self): + """ + 计算振幅比例得分 (最高5分) + 强势分型:分型区间振幅>近期平均振幅的150% = 5分 + 标准分型:介于80%-150%之间 = 3分 + 弱势分型:<80% = 1分 + """ + score = 0 + + # 计算分型区间振幅 + if self.fx == Chan_FX_TYPE.TOP: + fx_amplitude = self.high - min(self.pre.low, self.next.low) + # 加分项:右侧K线低点低于左侧K线低点(经典缠论强势特征) + if self.next.low < self.pre.low: + score += 1 + else: # BOTTOM + fx_amplitude = max(self.pre.high, self.next.high) - self.low + # 加分项:右侧K线高点高于左侧K线高点(经典缠论强势特征) + if self.next.high > self.pre.high: + score += 1 + + # 计算近期平均振幅(前10根K线的ATR) + avg_amplitude = self._calculate_recent_atr(lookback=10) + + if avg_amplitude <= 0: + return max(1, score) # 确保至少有基础分 + + amplitude_ratio = fx_amplitude / avg_amplitude + + if amplitude_ratio >= 1.5: # >150% + score += 4 # 基础4分 + 可能的经典形态1分 = 最高5分 + elif amplitude_ratio >= 1.0: # 100%-150% + score += 2 + int((amplitude_ratio - 1.0) * 4) # 2-4分线性插值 + elif amplitude_ratio >= 0.8: # 80%-100% + score += 1 + int((amplitude_ratio - 0.8) * 5) # 1-2分线性插值 + else: # <80% + score += 1 + + return min(5, score) + + def _calculate_volume_score(self): + """ + 计算量能配合得分 (最高5分) + 顶分型:第二根K线放量滞涨为强烈信号 + 底分型:第三根K线放量回升为有效确认 + """ + # 计算前5根K线平均成交量 + avg_volume = self._calculate_average_volume(lookback=5) + + if avg_volume <= 0: + return 1 + + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型:检查第二根K线(当前)是否放量滞涨 + volume_ratio = self.volume / avg_volume + + # 判断是否滞涨:收盘价位于K线下半部分 + price_position = (self.close - self.low) / (self.high - self.low) if self.high > self.low else 0.5 + + if volume_ratio >= 2.0 and price_position <= 0.4: # 放量+滞涨 + return 5 + elif volume_ratio >= 1.5 and price_position <= 0.5: + return 4 + elif volume_ratio >= 1.2: + return 3 + else: + return 1 + + else: # BOTTOM + # 底分型:检查第三根K线是否放量回升 + next_volume_ratio = self.next.volume / avg_volume if hasattr(self.next, 'volume') else 1 + + # 判断是否回升:第三根K线收盘价相对位置较高 + if self.next.high > self.next.low: + next_price_position = (self.next.close - self.next.low) / (self.next.high - self.next.low) + else: + next_price_position = 0.5 + + if next_volume_ratio >= 2.0 and next_price_position >= 0.6: # 放量+回升 + return 5 + elif next_volume_ratio >= 1.5 and next_price_position >= 0.5: + return 4 + elif next_volume_ratio >= 1.2: + return 3 + else: + return 1 + + def _calculate_ma_position_score(self): + """ + 计算均线位置得分 (最高5分) + 强势顶分型需在5/10均线乖离率>5%时出现 + 有效底分型常伴随MACD底背离 + """ + score = 0 + + # 获取均线数据 + klu_features = self.cal_klu_features() + + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型:检查与5日和10日均线的乖离率 + ma5_bias = 0 + ma10_bias = 0 + + if 'klu_ma5' in klu_features and klu_features['klu_ma5'] > 0: + ma5_bias = (self.close - klu_features['klu_ma5']) / klu_features['klu_ma5'] + + if 'klu_ma10' in klu_features and klu_features['klu_ma10'] > 0: + ma10_bias = (self.close - klu_features['klu_ma10']) / klu_features['klu_ma10'] + + # 乖离率>5%为强势信号 + if ma5_bias > 0.05 or ma10_bias > 0.05: + score += 3 + elif ma5_bias > 0.03 or ma10_bias > 0.03: + score += 2 + elif ma5_bias > 0 or ma10_bias > 0: + score += 1 + + else: # BOTTOM + # 底分型:检查MACD背离和均线支撑 + # 简化处理:检查价格是否在均线附近或下方 + ma5_support = False + ma10_support = False + + if 'klu_ma5' in klu_features and klu_features['klu_ma5'] > 0: + ma5_bias = (self.close - klu_features['klu_ma5']) / klu_features['klu_ma5'] + if ma5_bias >= -0.05: # 在5日均线附近或上方 + ma5_support = True + + if 'klu_ma10' in klu_features and klu_features['klu_ma10'] > 0: + ma10_bias = (self.close - klu_features['klu_ma10']) / klu_features['klu_ma10'] + if ma10_bias >= -0.05: # 在10日均线附近或上方 + ma10_support = True + + if ma5_support and ma10_support: + score += 3 + elif ma5_support or ma10_support: score += 2 else: - score += 1 # 大幅缩量也给1分 + score += 1 + + # 检查MACD状态 + if hasattr(self, 'macdhist'): + if self.fx == Chan_FX_TYPE.BOTTOM and self.macdhist > 0: + score += 2 # MACD金叉附近的底分型加分 + elif self.fx == Chan_FX_TYPE.TOP and self.macdhist < 0: + score += 2 # MACD死叉附近的顶分型加分 + + return min(5, score) + + def _calculate_formation_speed_score(self): + """ + 计算形成速度得分 (最高4分) + 强势特征:分型形成时间小于对应级别平均周期的1/3 + 弱势特征:形成时间超过平均周期2倍 + """ + # 简化处理:基于分型K线的收敛程度 + # 分型区间内的价格收敛速度越快,形成速度越快 + + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型:检查左右两根K线相对于中间K线的收敛程度 + left_convergence = (self.high - self.pre.high) / self.high if self.high > 0 else 0 + right_convergence = (self.high - self.next.high) / self.high if self.high > 0 else 0 + else: # BOTTOM + left_convergence = (self.pre.low - self.low) / self.low if self.low > 0 else 0 + right_convergence = (self.next.low - self.low) / self.low if self.low > 0 else 0 + + avg_convergence = (left_convergence + right_convergence) / 2 + + if avg_convergence >= 0.03: # 快速形成 + return 4 + elif avg_convergence >= 0.02: + return 3 + elif avg_convergence >= 0.01: + return 2 else: - score += 4 # 无法计算时给默认分 - - # === 4. 价格位置评分(0-10分) === - if kline_range > 0: - close_position = (self.close - self.low) / kline_range - - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:收盘价越低越好 - if close_position <= 0.3: # 收盘在下部 - score += 10 - elif close_position <= 0.5: # 收盘在中下部 - score += 7 - elif close_position <= 0.7: # 收盘在中上部 - score += 4 - else: - score += 2 # 收盘位置偏高但还给分 - else: - # 底分型:收盘价越高越好 - if close_position >= 0.7: # 收盘在上部 - score += 10 - elif close_position >= 0.5: # 收盘在中上部 - score += 7 - elif close_position >= 0.3: # 收盘在中下部 - score += 4 - else: - score += 2 # 收盘位置偏低但还给分 - else: - score += 5 # 无区间时给中等分 - - # === 5. 技术指标确认(0-20分) === - tech_score = 0 - - # 5.1 RSI确认(0-4分) - if hasattr(self, 'rsi') and self.rsi is not None: - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:RSI超买确认 - if self.rsi >= 80: # 严重超买 - tech_score += 4 - elif self.rsi >= 70: # 超买 - tech_score += 3 - elif self.rsi >= 60: # 偏高 - tech_score += 2 - elif self.rsi >= 50: # 中性偏高 - tech_score += 1 - else: - # 底分型:RSI超卖确认 - if self.rsi <= 20: # 严重超卖 - tech_score += 4 - elif self.rsi <= 30: # 超卖 - tech_score += 3 - elif self.rsi <= 40: # 偏低 - tech_score += 2 - elif self.rsi <= 50: # 中性偏低 - tech_score += 1 - - # 5.2 MACD确认(0-4分) - if hasattr(self, 'macdhist') and self.macdhist is not None: - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:MACD背离或转弱 - if self.macdhist < 0: # MACD柱状图为负 - tech_score += 2 - # 检查是否从正转负 - if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None: - if self.pre.macdhist > 0: # 前一根为正 - tech_score += 2 # 从正转负,额外加分 - elif self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None: - # 检查MACD是否减弱 - if self.macdhist < self.pre.macdhist: - tech_score += 1 - else: - # 底分型:MACD转强 - if self.macdhist > 0: # MACD柱状图为正 - tech_score += 2 - # 检查是否从负转正 - if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None: - if self.pre.macdhist < 0: # 前一根为负 - tech_score += 2 # 从负转正,额外加分 - elif self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None: - # 检查MACD是否增强 - if self.macdhist > self.pre.macdhist: - tech_score += 1 - - # 5.3 布林带确认(0-4分) - bb_score = self._analyze_bollinger_for_fx() - tech_score += min(4, bb_score) - - # 5.4 EMA趋势确认(0-4分) - ema_score = self._analyze_ema_for_fx() - tech_score += min(4, ema_score) - - # 5.5 ATR波动率确认(0-4分) - atr_score = self._analyze_atr_for_fx() - tech_score += min(4, atr_score) - - score += min(20, tech_score) - - return min(80, max(15, score)) # 确保至少15分,最高80分 + return 1 - def _analyze_bollinger_for_fx(self): + def _calculate_confirmation_score(self): """ - 布林带分析 - 统一版本 + 计算次级别确认得分 (最高10分) + - 笔破坏检测:真实强势分型会破坏前一笔的趋势 + - 观察分型后3根K线能否站稳分型区间1/2以上 + - 结合技术指标确认 """ score = 0 - # 计算简化的布林带(基于收盘价) - closes = [self.close] - temp = self.pre + # 1. 检查分型后确认(如果有next的next数据) + if hasattr(self.next, 'next'): + next2 = self.next.next + if next2: + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型:检查后续2根K线是否持续走弱 + fx_mid_level = (self.high + min(self.pre.low, self.next.low)) / 2 + if self.next.close < fx_mid_level and next2.close < fx_mid_level: + score += 5 # 强确认 + elif self.next.close < fx_mid_level: + score += 3 # 中等确认 + else: # BOTTOM + # 底分型:检查后续2根K线是否持续走强 + fx_mid_level = (max(self.pre.high, self.next.high) + self.low) / 2 + if self.next.close > fx_mid_level and next2.close > fx_mid_level: + score += 5 # 强确认 + elif self.next.close > fx_mid_level: + score += 3 # 中等确认 - for i in range(19): # 布林带通常使用20周期 - if temp: - closes.append(temp.close) - temp = temp.pre - else: - break + # 2. 技术指标确认 + if hasattr(self, 'rsi'): + if self.fx == Chan_FX_TYPE.TOP and self.rsi > 70: + score += 2 # 超买区顶分型 + elif self.fx == Chan_FX_TYPE.BOTTOM and self.rsi < 30: + score += 2 # 超卖区底分型 - if len(closes) >= 20: - import numpy as np - - # 计算20周期移动平均线和标准差 - ma20 = np.mean(closes[:20]) - std = np.std(closes[:20]) - - # 布林带上轨和下轨 - upper_band = ma20 + 2 * std - lower_band = ma20 - 2 * std - - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:价格接近或突破上轨 - if self.high >= upper_band: # 触及或突破上轨 - score += 4 - elif self.close > ma20: - # 计算价格在上半区的位置 - if upper_band > ma20: - position = (self.close - ma20) / (upper_band - ma20) - if position > 0.8: # 接近上轨 - score += 3 - elif position > 0.6: - score += 2 - elif position > 0.3: - score += 1 - else: - # 底分型:价格接近或突破下轨 - if self.low <= lower_band: # 触及或突破下轨 - score += 4 - elif self.close < ma20: - # 计算价格在下半区的位置 - if ma20 > lower_band: - position = (ma20 - self.close) / (ma20 - lower_band) - if position > 0.8: # 接近下轨 - score += 3 - elif position > 0.6: - score += 2 - elif position > 0.3: - score += 1 + # 3. 分型强度自身确认(K线形态) + if self.fx == Chan_FX_TYPE.TOP: + # 长上影线确认 + upper_shadow = self.high - max(self.open, self.close) + candle_range = self.high - self.low + if candle_range > 0 and upper_shadow / candle_range > 0.5: + score += 2 + else: # BOTTOM + # 长下影线确认 + lower_shadow = min(self.open, self.close) - self.low + candle_range = self.high - self.low + if candle_range > 0 and lower_shadow / candle_range > 0.5: + score += 2 - return score + # 4. 与前一个分型的关系 + if self.pre and hasattr(self.pre, 'fx') and self.pre.fx != Chan_FX_TYPE.UNKNOWN: + # 检查是否形成有效的笔结构 + if self.fx != self.pre.fx: # 分型类型相反 + score += 1 + + return min(10, score) - def _analyze_ema_for_fx(self): + def _calculate_recent_atr(self, lookback=10): """ - EMA趋势分析 + 计算近期ATR(平均真实波动范围) """ - score = 0 + tr_values = [] + temp = self - # 获取多周期收盘价用于EMA计算 - closes = [self.close] - temp = self.pre - - for i in range(29): # 获取30根K线用于EMA计算 - if temp: - closes.append(temp.close) - temp = temp.pre - else: - break - - if len(closes) >= 12: # 至少需要12根K线 - import numpy as np - - # 计算EMA12和EMA26 - def calculate_ema(prices, period): - alpha = 2 / (period + 1) - ema = [prices[0]] - for price in prices[1:period]: - ema.append(alpha * price + (1 - alpha) * ema[-1]) - return ema[-1] if len(ema) > 0 else prices[0] - - if len(closes) >= 12: - ema12 = calculate_ema(closes[:12][::-1], 12) # 反转顺序,最新的在前 - - if len(closes) >= 26: - ema26 = calculate_ema(closes[:26][::-1], 26) - - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:价格高于EMA,EMA向上但可能转向 - if self.close > ema12 > ema26: # 多头排列 - score += 2 - elif self.close > ema12: # 价格在短期EMA上方 - score += 1 - - # 检查EMA12是否开始转向 - if self.pre and len(closes) >= 13: - prev_ema12 = calculate_ema(closes[1:13][::-1], 12) - if ema12 < prev_ema12: # EMA12开始下降 - score += 2 - else: - # 底分型:价格低于EMA,EMA向下但可能转向 - if self.close < ema12 < ema26: # 空头排列 - score += 2 - elif self.close < ema12: # 价格在短期EMA下方 - score += 1 - - # 检查EMA12是否开始转向 - if self.pre and len(closes) >= 13: - prev_ema12 = calculate_ema(closes[1:13][::-1], 12) - if ema12 > prev_ema12: # EMA12开始上升 - score += 2 - - return score - - def _analyze_atr_for_fx(self): - """ - ATR波动率分析 - """ - score = 0 - - # 计算ATR - recent_atr = self._calculate_recent_atr(lookback=14) - if recent_atr > 0: - # 当前K线的真实波动范围 - current_tr = self.high - self.low - if self.pre: - current_tr = max( - self.high - self.low, - abs(self.high - self.pre.close), - abs(self.low - self.pre.close) + for i in range(lookback): + if temp and temp.pre: + tr = max( + temp.high - temp.low, + abs(temp.high - temp.pre.close), + abs(temp.low - temp.pre.close) ) - - # ATR倍数 - atr_ratio = current_tr / recent_atr - - if self.fx == Chan_FX_TYPE.TOP: - # 顶分型:波动率放大确认反转 - if atr_ratio >= 2.5: # 波动率大幅放大 - score += 4 - elif atr_ratio >= 2.0: # 波动率明显放大 - score += 3 - elif atr_ratio >= 1.5: # 波动率适度放大 - score += 2 - elif atr_ratio >= 1.2: # 波动率略微放大 - score += 1 + tr_values.append(tr) + temp = temp.pre else: - # 底分型:波动率放大确认反转 - if atr_ratio >= 2.5: # 波动率大幅放大 - score += 4 - elif atr_ratio >= 2.0: # 波动率明显放大 - score += 3 - elif atr_ratio >= 1.5: # 波动率适度放大 - score += 2 - elif atr_ratio >= 1.2: # 波动率略微放大 - score += 1 - - # 额外检查:波动率从低到高的变化 - if self.pre: - prev_tr = self.pre.high - self.pre.low - if self.pre.pre: - prev_tr = max( - self.pre.high - self.pre.low, - abs(self.pre.high - self.pre.pre.close), - abs(self.pre.low - self.pre.pre.close) - ) - - # 波动率加速放大 - if current_tr > prev_tr * 1.5: - score += 1 + break - return score + return sum(tr_values) / len(tr_values) if tr_values else 0 + + def _calculate_average_volume(self, lookback=5): + """ + 计算平均成交量 + """ + volumes = [] + temp = self.pre # 从前一根K线开始计算 + + for i in range(lookback): + if temp: + volumes.append(temp.volume) + temp = temp.pre + else: + break + + return sum(volumes) / len(volumes) if volumes else 0 def get_fx_strength_level(self): """ 获取分型强度等级 - - Returns: - str: 强度等级描述 + 根据专业评分标准:≥80分为有效强势分型,≤40分建议忽略 """ - strength = self.cal_fx_strength() - - # 调整后的强度等级阈值(匹配15-80分范围) - if strength >= 70: - return "极强分型" # 极强分型:70分以上 - elif strength >= 60: - return "强分型" # 强分型:60-69分 + strength = self.calculate_fx_strength() + return "" + if strength >= 80: + return "极强" + elif strength >= 65: + return "强" elif strength >= 50: - return "中强分型" # 中强分型:50-59分 + return "中等" elif strength >= 40: - return "中等分型" # 中等分型:40-49分 - elif strength >= 25: - return "弱分型" # 弱分型:25-39分 + return "弱" else: - return "极弱分型" # 极弱分型:25分以下 + return "极弱" - def is_strong_fx(self, threshold=55): + def is_strong_fx(self, threshold=65): """ 判断是否为强分型 - - Args: - threshold: 强分型的阈值,调整为55分(适配80分制) - - Returns: - bool: 是否为强分型 + 根据专业标准调整阈值为65分 """ - return self.cal_fx_strength() >= threshold + return self.calculate_fx_strength() >= threshold def _default_top_strength_judgment(self, first_info, middle_info, last_info, first_kline, middle_kline, last_kline): """ @@ -1858,594 +1937,4 @@ class ChanKLC(): strong_signals += 1 # 需要至少4个强信号才判断为强分型,否则为弱分型 - return 1 if strong_signals >= 4 else -1 - - def get_real_price_range(self): - """ - 获取真实价格区间(合并前所有原始K线的最高最低点) - 返回: (real_high, real_low) - """ - if not self.klus: - return self.high, self.low - - real_high = max(klu.high for klu in self.klus) - real_low = min(klu.low for klu in self.klus) - - return real_high, real_low - - def get_real_range_size(self): - """ - 获取真实价格区间大小 - """ - real_high, real_low = self.get_real_price_range() - return real_high - real_low - - def get_real_close_position(self): - """ - 获取收盘价在真实价格区间中的位置 - """ - real_high, real_low = self.get_real_price_range() - real_range = real_high - real_low - - if real_range > 0: - return (self.close - real_low) / real_range - else: - return 0.5 - - def get_real_upper_shadow_ratio(self): - """ - 获取上影线在真实区间中的比例 - """ - real_high, real_low = self.get_real_price_range() - real_range = real_high - real_low - - if real_range > 0: - upper_shadow = real_high - max(self.open, self.close) - return upper_shadow / real_range - else: - return 0 - - def get_real_lower_shadow_ratio(self): - """ - 获取下影线在真实区间中的比例 - """ - real_high, real_low = self.get_real_price_range() - real_range = real_high - real_low - - if real_range > 0: - lower_shadow = min(self.open, self.close) - real_low - return lower_shadow / real_range - else: - return 0 - - def _analyze_macd_for_top_fx(self): - """ - MACD指标在顶分型中的综合分析 - """ - score = 0 - - # 检查MACD背离 - if self._check_macd_bearish_divergence(): - score += 3 - - # 检查MACD柱状图趋势 - if hasattr(self, 'macdhist') and self.macdhist is not None: - if self.macdhist < 0: # MACD柱状图为负 - score += 1 - - # 检查MACD柱状图是否从正转负 - if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None: - if self.pre.macdhist > 0 and self.macdhist < 0: - score += 2 - - # 检查MACD线是否在零轴上方形成顶背离 - klu_features = self.cal_klu_features() - if 'klu_macd' in klu_features and 'klu_signal' in klu_features: - macd_line = klu_features['klu_macd'] - signal_line = klu_features['klu_signal'] - - # MACD线高于信号线但趋势减弱 - if macd_line > signal_line and macd_line > 0: - score += 1 - - return score - - def _analyze_macd_for_bottom_fx(self): - """ - MACD指标在底分型中的综合分析 - """ - score = 0 - - # 检查MACD背离 - if self._check_macd_bullish_divergence(): - score += 3 - - # 检查MACD柱状图趋势 - if hasattr(self, 'macdhist') and self.macdhist is not None: - if self.macdhist > 0: # MACD柱状图为正 - score += 1 - - # 检查MACD柱状图是否从负转正 - if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist is not None: - if self.pre.macdhist < 0 and self.macdhist > 0: - score += 2 - - # 检查MACD线是否在零轴下方形成底背离 - klu_features = self.cal_klu_features() - if 'klu_macd' in klu_features and 'klu_signal' in klu_features: - macd_line = klu_features['klu_macd'] - signal_line = klu_features['klu_signal'] - - # MACD线低于信号线但趋势增强 - if macd_line < signal_line and macd_line < 0: - score += 1 - - return score - - def _analyze_kdj_for_top_fx(self): - """ - KDJ指标在顶分型中的分析 - """ - score = 0 - - # 模拟KDJ计算(基于真实价格区间) - real_high, real_low = self.get_real_price_range() - - # 获取前面几根K线的最高最低价 - temp = self.pre - highs = [real_high] - lows = [real_low] - closes = [self.close] - - for i in range(8): # KDJ通常使用9周期 - if temp: - temp_high, temp_low = temp.get_real_price_range() - highs.append(temp_high) - lows.append(temp_low) - closes.append(temp.close) - temp = temp.pre - else: - break - - if len(highs) >= 9: - # 计算9周期的最高价和最低价 - highest_high = max(highs[:9]) - lowest_low = min(lows[:9]) - - # 计算RSV(未成熟随机值) - if highest_high > lowest_low: - rsv = (self.close - lowest_low) / (highest_high - lowest_low) * 100 - - # 简化的K值计算 - k_value = rsv # 简化处理 - - # KDJ超买判断 - if k_value > 80: # K值超买 - score += 3 - elif k_value > 70: - score += 2 - - # 检查KDJ死叉形态 - if self.pre: - pre_highs = highs[1:10] if len(highs) > 9 else highs[1:] - pre_lows = lows[1:10] if len(lows) > 9 else lows[1:] - - if pre_highs and pre_lows: - pre_highest = max(pre_highs) - pre_lowest = min(pre_lows) - - if pre_highest > pre_lowest: - pre_rsv = (self.pre.close - pre_lowest) / (pre_highest - pre_lowest) * 100 - pre_k_value = pre_rsv - - # 检查K值是否从高位下降 - if pre_k_value > k_value and pre_k_value > 70: - score += 2 - - return score - - def _analyze_kdj_for_bottom_fx(self): - """ - KDJ指标在底分型中的分析 - """ - score = 0 - - # 模拟KDJ计算(基于真实价格区间) - real_high, real_low = self.get_real_price_range() - - # 获取前面几根K线的最高最低价 - temp = self.pre - highs = [real_high] - lows = [real_low] - closes = [self.close] - - for i in range(8): # KDJ通常使用9周期 - if temp: - temp_high, temp_low = temp.get_real_price_range() - highs.append(temp_high) - lows.append(temp_low) - closes.append(temp.close) - temp = temp.pre - else: - break - - if len(highs) >= 9: - # 计算9周期的最高价和最低价 - highest_high = max(highs[:9]) - lowest_low = min(lows[:9]) - - # 计算RSV(未成熟随机值) - if highest_high > lowest_low: - rsv = (self.close - lowest_low) / (highest_high - lowest_low) * 100 - - # 简化的K值计算 - k_value = rsv # 简化处理 - - # KDJ超卖判断 - if k_value < 20: # K值超卖 - score += 3 - elif k_value < 30: - score += 2 - - # 检查KDJ金叉形态 - if self.pre: - pre_highs = highs[1:10] if len(highs) > 9 else highs[1:] - pre_lows = lows[1:10] if len(lows) > 9 else lows[1:] - - if pre_highs and pre_lows: - pre_highest = max(pre_highs) - pre_lowest = min(pre_lows) - - if pre_highest > pre_lowest: - pre_rsv = (self.pre.close - pre_lowest) / (pre_highest - pre_lowest) * 100 - pre_k_value = pre_rsv - - # 检查K值是否从低位上升 - if k_value > pre_k_value and pre_k_value < 30: - score += 2 - - return score - - def _calculate_recent_atr(self, lookback=14): - """ - 计算最近的ATR(平均真实波动范围) - - Args: - lookback: 回看周期,默认14 - - Returns: - float: ATR值 - """ - if not self.pre: - return 0 - - true_ranges = [] - temp = self - - for i in range(lookback): - if temp and temp.pre: - # 计算真实波动范围(TR) - tr = max( - temp.high - temp.low, # 当前高低价差 - abs(temp.high - temp.pre.close), # 当前高价与前收盘价差的绝对值 - abs(temp.low - temp.pre.close) # 当前低价与前收盘价差的绝对值 - ) - true_ranges.append(tr) - temp = temp.pre - else: - break - - if true_ranges: - return sum(true_ranges) / len(true_ranges) - else: - return 0 - - def _calculate_average_volume(self, lookback=5): - """ - 计算平均成交量 - """ - volumes = [] - temp = self.pre # 从前一根K线开始计算 - - for i in range(lookback): - if temp: - volumes.append(temp.volume) - temp = temp.pre - else: - break - - return sum(volumes) / len(volumes) if volumes else 0 - - def _check_macd_bearish_divergence(self): - """ - 检查MACD看跌背离 - """ - # 简化版本,可以根据实际MACD数据进行更复杂的背离分析 - if hasattr(self, 'macdhist') and self.macdhist: - # 如果MACD柱状图在减弱,可能形成顶背离 - if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist: - if self.macdhist < self.pre.macdhist and self.macdhist < 0: - return True - return False - - def _check_macd_bullish_divergence(self): - """ - 检查MACD看涨背离 - """ - # 简化版本,可以根据实际MACD数据进行更复杂的背离分析 - if hasattr(self, 'macdhist') and self.macdhist: - # 如果MACD柱状图在增强,可能形成底背离 - if self.pre and hasattr(self.pre, 'macdhist') and self.pre.macdhist: - if self.macdhist > self.pre.macdhist and self.macdhist > 0: - return True - return False - - def _near_resistance_level(self): - """ - 检查是否接近阻力位(使用真实价格区间) - """ - # 简化版本:检查是否接近前面几根K线的最高点 - if self.pre: - real_high, _ = self.get_real_price_range() - temp = self.pre - max_high = 0 - for i in range(10): # 检查前10根K线 - if temp: - temp_real_high, _ = temp.get_real_price_range() - max_high = max(max_high, temp_real_high) - temp = temp.pre - else: - break - - if max_high > 0: - # 如果当前真实高点接近前期高点,可能是阻力位 - distance_ratio = abs(real_high - max_high) / max_high - return distance_ratio < 0.02 # 2%以内算接近 - - return False - - def _near_support_level(self): - """ - 检查是否接近支撑位(使用真实价格区间) - """ - # 简化版本:检查是否接近前面几根K线的最低点 - if self.pre: - _, real_low = self.get_real_price_range() - temp = self.pre - min_low = float('inf') - for i in range(10): # 检查前10根K线 - if temp: - _, temp_real_low = temp.get_real_price_range() - min_low = min(min_low, temp_real_low) - temp = temp.pre - else: - break - - if min_low != float('inf') and min_low > 0: - # 如果当前真实低点接近前期低点,可能是支撑位 - distance_ratio = abs(real_low - min_low) / min_low - return distance_ratio < 0.02 # 2%以内算接近 - - return False - - def cal_fx_strength_realtime(self): - """ - 实时计算分型强度 - 严格版本(不使用缓存,强制重新计算) - 基于最后两根K线评估分型强度,不使用未来数据 - Returns: - int: 强度评分 0-70分 - """ - if not self.pre or not self.pre.is_fx(): - return 0 - - fx_type = self.pre.fx_type - - # 强制重新计算,不使用任何缓存 - if fx_type == Chan_FX_TYPE.TOP: - strength = self._calculate_top_fx_power_realtime_v2() - elif fx_type == Chan_FX_TYPE.BOTTOM: - strength = self._calculate_bottom_fx_power_realtime_v2() - else: - strength = 0 - - return strength - - def _calculate_top_fx_power_realtime_v2(self): - """ - 宽松版本的实时顶分型力度计算 - 确保合理分型有分数 - """ - score = 10 # 提高基础分数,确认是分型就有基础分 - - # 获取前一个KLC的最高价用于比较 - if not self.pre: - return score - - # 1. 突出程度评分(0-25分)- 大幅放宽标准 - current_high = self.high - prev_high = self.pre.high - - # 计算突出程度 - 修正计算逻辑 - if prev_high > 0: - high_prominence = abs(current_high - prev_high) / prev_high - else: - high_prominence = 0 - - # 极度放宽的评分标准 - if high_prominence >= 0.05: # 5%以上突出 - 极强 - score += 25 - elif high_prominence >= 0.03: # 3-5%突出 - 很强 - score += 20 - elif high_prominence >= 0.02: # 2-3%突出 - 强 - score += 15 - elif high_prominence >= 0.015: # 1.5-2%突出 - 中等 - score += 12 - elif high_prominence >= 0.01: # 1-1.5%突出 - 较弱 - score += 8 - elif high_prominence >= 0.005: # 0.5-1%突出 - 弱 - score += 5 - elif high_prominence >= 0.002: # 0.2-0.5%突出 - 极弱 - score += 2 - else: - score += 1 # 有一定突出度就给点分 - - # 2. K线形态评分(0-20分)- 大幅放宽 - kline_range = self.high - self.low - if kline_range > 0: - upper_shadow = self.high - max(self.open, self.close) - upper_shadow_ratio = upper_shadow / kline_range - - if upper_shadow_ratio >= 0.4: # 长上影线 - score += 20 - elif upper_shadow_ratio >= 0.25: # 明显上影线 - score += 15 - elif upper_shadow_ratio >= 0.15: # 一般上影线 - score += 10 - elif upper_shadow_ratio >= 0.08: # 短上影线 - score += 6 - elif upper_shadow_ratio >= 0.03: # 很短上影线 - score += 3 - else: - score += 1 # 有一点上影线就给分 - - # 3. 成交量评分(0-15分)- 大幅放宽 - avg_volume = self._calculate_average_volume(lookback=5) - if avg_volume > 0: - volume_ratio = self.volume / avg_volume - if volume_ratio >= 2.5: # 大量 - score += 15 - elif volume_ratio >= 1.8: # 明显放量 - score += 12 - elif volume_ratio >= 1.3: # 适度放量 - score += 9 - elif volume_ratio >= 1.1: # 轻微放量 - score += 6 - elif volume_ratio >= 0.8: # 正常量 - score += 3 - elif volume_ratio >= 0.5: # 缩量但可接受 - score += 1 - else: - score += 0 # 极度缩量 - else: - score += 3 # 无法计算成交量时给默认分 - - # 4. 价格位置评分(0-10分)- 大幅放宽 - if kline_range > 0: - close_position = (self.close - self.low) / kline_range - if close_position <= 0.2: # 收盘在下部 - score += 10 - elif close_position <= 0.4: # 收盘在中下部 - score += 8 - elif close_position <= 0.6: # 收盘在中部 - score += 5 - elif close_position <= 0.8: # 收盘在中上部 - score += 3 - else: - score += 1 # 收盘位置偏高但还有分 - - # === 去除大部分惩罚机制,只保留最基本的 === - - # 只有在完全没有突出度时才轻微降分 - if high_prominence < 0.001: # 突出度低于0.1% - score = int(score * 0.8) - - return min(60, max(10, score)) # 确保至少有10分,最高60分 - - def _calculate_bottom_fx_power_realtime_v2(self): - """ - 宽松版本的实时底分型力度计算 - 确保合理分型有分数 - """ - score = 10 # 提高基础分数,确认是分型就有基础分 - - # 获取前一个KLC的最低价用于比较 - if not self.pre: - return score - - # 1. 突出程度评分(0-25分)- 大幅放宽标准 - current_low = self.low - prev_low = self.pre.low - - # 计算突出程度 - 修正计算逻辑 - if prev_low > 0: - low_prominence = abs(prev_low - current_low) / prev_low - else: - low_prominence = 0 - - # 极度放宽的评分标准 - if low_prominence >= 0.05: # 5%以上突出 - 极强 - score += 25 - elif low_prominence >= 0.03: # 3-5%突出 - 很强 - score += 20 - elif low_prominence >= 0.02: # 2-3%突出 - 强 - score += 15 - elif low_prominence >= 0.015: # 1.5-2%突出 - 中等 - score += 12 - elif low_prominence >= 0.01: # 1-1.5%突出 - 较弱 - score += 8 - elif low_prominence >= 0.005: # 0.5-1%突出 - 弱 - score += 5 - elif low_prominence >= 0.002: # 0.2-0.5%突出 - 极弱 - score += 2 - else: - score += 1 # 有一定突出度就给点分 - - # 2. K线形态评分(0-20分)- 大幅放宽 - kline_range = self.high - self.low - if kline_range > 0: - lower_shadow = min(self.open, self.close) - self.low - lower_shadow_ratio = lower_shadow / kline_range - - if lower_shadow_ratio >= 0.4: # 长下影线 - score += 20 - elif lower_shadow_ratio >= 0.25: # 明显下影线 - score += 15 - elif lower_shadow_ratio >= 0.15: # 一般下影线 - score += 10 - elif lower_shadow_ratio >= 0.08: # 短下影线 - score += 6 - elif lower_shadow_ratio >= 0.03: # 很短下影线 - score += 3 - else: - score += 1 # 有一点下影线就给分 - - # 3. 成交量评分(0-15分)- 大幅放宽 - avg_volume = self._calculate_average_volume(lookback=5) - if avg_volume > 0: - volume_ratio = self.volume / avg_volume - if volume_ratio >= 2.5: # 大量 - score += 15 - elif volume_ratio >= 1.8: # 明显放量 - score += 12 - elif volume_ratio >= 1.3: # 适度放量 - score += 9 - elif volume_ratio >= 1.1: # 轻微放量 - score += 6 - elif volume_ratio >= 0.8: # 正常量 - score += 3 - elif volume_ratio >= 0.5: # 缩量但可接受 - score += 1 - else: - score += 0 # 极度缩量 - else: - score += 3 # 无法计算成交量时给默认分 - - # 4. 价格位置评分(0-10分)- 大幅放宽 - if kline_range > 0: - close_position = (self.close - self.low) / kline_range - if close_position >= 0.8: # 收盘在上部 - score += 10 - elif close_position >= 0.6: # 收盘在中上部 - score += 8 - elif close_position >= 0.4: # 收盘在中部 - score += 5 - elif close_position >= 0.2: # 收盘在中下部 - score += 3 - else: - score += 1 # 收盘位置偏低但还有分 - - # === 去除大部分惩罚机制,只保留最基本的 === - - # 只有在完全没有突出度时才轻微降分 - if low_prominence < 0.001: # 突出度低于0.1% - score = int(score * 0.8) - - return min(60, max(10, score)) # 确保至少有10分,最高60分 \ No newline at end of file + return 1 if strong_signals >= 4 else -1 \ No newline at end of file diff --git a/ChanKLU.py b/ChanKLU.py index b921dc0..c284118 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -1,4 +1,4 @@ -from ChanEnum import Chan_FX_TYPE +from ChanEnum import Chan_FX_TYPE, Chan_KLU_TYPE class ChanKLU: def __init__(self, time, open, high, low, close, volume): # _time, _close, _open, _high, _low, _extra_info={} @@ -23,13 +23,45 @@ class ChanKLU: self.rsi = 0 self.volume_ratio = 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() + 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() diff --git a/ChanLun.py b/ChanLun.py index 421b923..51930bc 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -697,10 +697,11 @@ class ChanLun(): else: # A new top found #last_top.set_fx(Chan_FX_TYPE.UNKNOWN) + #print(klc.start_time, last_top.start_time, klc.cal_fx_strength(), "一类卖点Sell 1") last_top = klc #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1") klc.set_klc_fx_type(Chan_KLC_FX.TOP1) - #print(klc.start_time, klc.fx, "一类卖点Sell 1") + #klc.set_fx(fx) #klc.set_state("10") bi_list[-1].add_klc(klc) @@ -731,6 +732,7 @@ class ChanLun(): #klc.set_state("10") #print(klc.start_time, klc.fx, "笔卖点Sell 1") klc.set_klc_fx_type(Chan_KLC_FX.TOP2) + #print(klc.start_time, last_top.start_time, klc.fx, "二类卖点Sell 2") bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) else: @@ -755,7 +757,7 @@ class ChanLun(): #klc.set_state('30') bi_list[-1].add_klc(klc) klc.set_bi(bi_list[-1]) - #print(klc.start_time, last_bottom.start_time, "Normal TOP Found, Confirm down bi 4") + #print(klc.start_time, last_top.start_time, "Normal TOP Found, Confirm down bi 4") #print(klc.start_time, klc.fx, "笔卖点Sell 2") # last bottom = None else: @@ -1220,8 +1222,8 @@ class ChanLun(): klc.set_pre(last_klc) last_klc.set_end_klu(last_klu) klc.set_pre_fx() - else: - last_klc.add_klu(klu) + #else: + #last_klc.add_klu(klu) else: ddir = Chan_KLINE_DIR.UP if klu.open > klu.close: diff --git a/web/app.py b/web/app.py index 506cd03..a9d16a9 100644 --- a/web/app.py +++ b/web/app.py @@ -323,7 +323,7 @@ def add_indicators(df): def calculate_macd(df): """计算MACD指标""" - exp1 = df['close'].ewm(span=12, adjust=False).mean() + exp1 = df['close'].ewm(span=10, adjust=False).mean() exp2 = df['close'].ewm(span=26, adjust=False).mean() macd = exp1 - exp2 signal = macd.ewm(span=9, adjust=False).mean() diff --git a/web/templates/index.html b/web/templates/index.html index dec997c..a16e223 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -3241,20 +3241,16 @@ console.error('主周期KLC分型时间或价格转换错误:', fx.time, fx.price); return; } - + // 主周期 KLC // 确定颜色和位置 const color = fx.is_bottom ? '#28a745' : '#dc3545'; // 底分型绿色,顶分型红色 // 根据强度等级调整颜色强度 let strengthColor = color; - if (fx.is_strong_fx) { - // 强分型使用更亮的颜色 - strengthColor = fx.is_bottom ? '#00ff00' : '#ff0000'; - } - + // 构建显示文本,包含分型类型和强度信息 let displayText = `${fx.fx_strength.toFixed(1)}`; - if (fx.fx_strength < 50) { // 降低阈值,让更多分型显示 + if (fx.fx_strength < 1.4) { // 降低阈值,让更多分型显示 displayText = fx.fx_strength >= 0.8 ? '•' : '' // 0.8以上显示点,0.8以下不显示文本 } @@ -3265,7 +3261,7 @@ color: strengthColor, shape: 'triangle', text: displayText, - size: fx.is_strong_fx ? 1 : 0.6 // 调整尺寸,强分型稍大,普通分型更小 + size: 2 // 调整尺寸,强分型稍大,普通分型更小 }; allMainFxMarkers.push(markerConfig); @@ -3306,7 +3302,7 @@ return; } - // KLU分型使用不同的颜色区分 + // 主周期 KLU const color = fx.is_bottom ? '#17a2b8' : '#fd7e14'; // 底分型用青色,顶分型用橙色 // 根据强度等级调整颜色强度 @@ -3399,12 +3395,8 @@ return; } - // 小周期分型使用不同的颜色和样式,与主周期区分 - let strengthColor = fx.is_bottom ? '#FF6B6B' : '#4ECDC4'; // 底分型用珊瑚红,顶分型用薄荷绿 - if (fx.is_strong_fx) { - // 强分型使用更亮的颜色 - strengthColor = fx.is_bottom ? '#FF0000' : '#00CED1'; - } + // 小周期 KLC + let strengthColor = fx.is_bottom ? '#11116B' : '#222222'; // 底分型用珊瑚红,顶分型用薄荷绿 let displayText = `${fx.fx_strength.toFixed(1)}`; // 构建小周期分型显示文本 if (fx.fx_strength < 1.5){ // 调整小周期阈值 @@ -3459,12 +3451,8 @@ return; } - // 小周期KLU分型使用不同的颜色 + // 小周期 KLU let strengthColor = fx.is_bottom ? '#9A8C98' : '#F2CC8F'; // 底分型用灰紫色,顶分型用浅黄色 - if (fx.is_strong_fx) { - // 强分型使用更亮的颜色 - strengthColor = fx.is_bottom ? '#6C5B7B' : '#F9844A'; - } let displayText = `${fx.fx_strength.toFixed(1)}`; // 构建小周期分型显示文本 if (fx.fx_strength < 1.4){ // 调整小周期阈值