From ec46febceb07a188e02ad6ca9963349021d3b921 Mon Sep 17 00:00:00 2001 From: Porter Date: Tue, 3 Jun 2025 10:02:16 +0800 Subject: [PATCH] Change fx strength and add bb --- ChanKLC.py | 1536 +++++++++++++++++++++++--------------- ChanLun.py | 4 +- web/app.py | 4 +- web/templates/index.html | 50 +- 4 files changed, 999 insertions(+), 595 deletions(-) diff --git a/ChanKLC.py b/ChanKLC.py index 5bee3a3..b754f71 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -1156,629 +1156,405 @@ class ChanKLC(): def cal_fx_strength(self): """ - 用self.pre和self.next实现分型强弱判断 + 统一的分型强度计算函数 - 直接计算当前KLC的分型强度 + 包含技术指标确认 - 核心缠论原理: - - 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折 - - 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸 - - 返回值: - 3: 极强分型(笔终结+强确认) - 2: 强分型(笔终结) - 1: 偏强分型(可能终结笔) - 0: 中性分型 - -1: 偏弱分型(中继特征明显) - -2: 弱分型(明显中继) - -3: 极弱分型(无效分型) + Returns: + int: 强度评分 15-80分 """ - # 检查是否为分型,且有前后K线数据 + # 如果不是分型,返回0 if self.fx == 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 - #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 + # 如果没有前一个KLC,返回基础分 + if not self.pre: + return 15 - # 获取分型后的几根K线数据 - subsequent_klcs = [] - temp = self.next - for i in range(2): # 检查后续5根K线 - if temp: - subsequent_klcs.append(temp) - temp = temp.next if hasattr(temp, 'next') else None + score = 15 # 基础分数,任何分型都有基础分 + + # === 1. 突出程度评分(0-25分) === + if self.fx == Chan_FX_TYPE.TOP: + # 顶分型:当前高点与前一个高点的差异 + if self.pre.high > 0: + prominence = abs(self.high - self.pre.high) / self.pre.high else: - break + prominence = 0 + else: # 底分型 + # 底分型:当前低点与前一个低点的差异 + if self.pre.low > 0: + prominence = abs(self.pre.low - self.low) / self.pre.low + else: + prominence = 0 - if len(subsequent_klcs) < 2: - return 0 - - if self.fx == Chan_FX_TYPE.TOP: - 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 + + # 影线评分 - 极度宽松 + 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 # 十字星也给点分 - # 检查关键价位突破 - first_low = self.pre.low - middle_low = self.low - key_support = min(first_low, middle_low) - - 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 - - # 强烈笔终结:跌破关键位且无新高 - 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_klcs): - """检查底分型是否为笔终结""" - # 强烈笔终结特征: - # 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, 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 - - # 强烈笔终结:突破关键位且无新低 - 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 - - # 检查量价配合 + # === 3. 成交量评分(0-10分) === avg_volume = self._calculate_average_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 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: + 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: # 略缩量 score += 2 else: - 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 + score += 1 # 大幅缩量也给1分 else: - return 1 + 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分 - def _calculate_confirmation_score(self): + def _analyze_bollinger_for_fx(self): """ - 计算次级别确认得分 (最高10分) - - 笔破坏检测:真实强势分型会破坏前一笔的趋势 - - 观察分型后3根K线能否站稳分型区间1/2以上 - - 结合技术指标确认 + 布林带分析 - 统一版本 """ score = 0 - # 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 # 中等确认 + # 计算简化的布林带(基于收盘价) + closes = [self.close] + temp = self.pre - # 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 # 超卖区底分型 - - # 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 - - # 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 _calculate_recent_atr(self, lookback=10): - """ - 计算近期ATR(平均真实波动范围) - """ - tr_values = [] - temp = self - - 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) - ) - tr_values.append(tr) - temp = temp.pre - else: - break - - 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): + for i in range(19): # 布林带通常使用20周期 if temp: - volumes.append(temp.volume) + closes.append(temp.close) temp = temp.pre else: break - return sum(volumes) / len(volumes) if volumes else 0 + 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 + + return score + + def _analyze_ema_for_fx(self): + """ + EMA趋势分析 + """ + score = 0 + + # 获取多周期收盘价用于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) + ) + + # 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 + 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 + + return score def get_fx_strength_level(self): """ 获取分型强度等级 - 根据专业评分标准:≥80分为有效强势分型,≤40分建议忽略 + + Returns: + str: 强度等级描述 """ - strength = self.calculate_fx_strength() - return "" - if strength >= 80: - return "极强" - elif strength >= 65: - return "强" + strength = self.cal_fx_strength() + + # 调整后的强度等级阈值(匹配15-80分范围) + if strength >= 70: + return "极强分型" # 极强分型:70分以上 + elif strength >= 60: + return "强分型" # 强分型:60-69分 elif strength >= 50: - return "中等" + return "中强分型" # 中强分型:50-59分 elif strength >= 40: - return "弱" + return "中等分型" # 中等分型:40-49分 + elif strength >= 25: + return "弱分型" # 弱分型:25-39分 else: - return "极弱" + return "极弱分型" # 极弱分型:25分以下 - def is_strong_fx(self, threshold=65): + def is_strong_fx(self, threshold=55): """ 判断是否为强分型 - 根据专业标准调整阈值为65分 + + Args: + threshold: 强分型的阈值,调整为55分(适配80分制) + + Returns: + bool: 是否为强分型 """ - return self.calculate_fx_strength() >= threshold + return self.cal_fx_strength() >= threshold def _default_top_strength_judgment(self, first_info, middle_info, last_info, first_kline, middle_kline, last_kline): """ @@ -1858,4 +1634,594 @@ class ChanKLC(): strong_signals += 1 # 需要至少4个强信号才判断为强分型,否则为弱分型 - return 1 if strong_signals >= 4 else -1 \ No newline at end of file + 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 diff --git a/ChanLun.py b/ChanLun.py index 1c3dbe3..421b923 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -718,7 +718,7 @@ class ChanLun(): if last_top.index + 4 < klc.index and len(bi_list) > 1: pre_last_bi = bi_list[-2] last_bi = bi_list[-1] - if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP: + if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP and False: pre_last_bi.update_bi(klc) bi_list.remove(last_bi) pre_last_bi.set_next(None) @@ -838,7 +838,7 @@ class ChanLun(): if last_bottom.index + 4 < klc.index and len(bi_list) > 1: pre_last_bi = bi_list[-2] last_bi = bi_list[-1] - if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN: + if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN and False: pre_last_bi.update_bi(klc) bi_list.remove(last_bi) pre_last_bi.set_next(None) diff --git a/web/app.py b/web/app.py index c5298c3..5be503b 100644 --- a/web/app.py +++ b/web/app.py @@ -379,11 +379,9 @@ def analyze_chan(df): fx_strength_level = "" is_strong_fx = False - # 尝试调用分型强度计算方法 + # 统一使用cal_fx_strength函数 if hasattr(klc, 'cal_fx_strength'): fx_strength = klc.cal_fx_strength() - elif hasattr(klc, 'calculate_fx_strength'): - fx_strength = klc.calculate_fx_strength() # 尝试获取分型强度等级 if hasattr(klc, 'get_fx_strength_level'): diff --git a/web/templates/index.html b/web/templates/index.html index e79cd94..45dfd9e 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -861,6 +861,20 @@ tooltipElement.className = 'point-tooltip'; // document.body.appendChild(tooltipElement); + // 添加自定义十字线信息显示 + const crosshairTooltip = document.createElement('div'); + crosshairTooltip.className = 'crosshair-tooltip'; + crosshairTooltip.style.position = 'absolute'; + crosshairTooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)'; + crosshairTooltip.style.color = 'white'; + crosshairTooltip.style.padding = '5px 10px'; + crosshairTooltip.style.borderRadius = '4px'; + crosshairTooltip.style.fontSize = '12px'; + crosshairTooltip.style.zIndex = '1000'; + crosshairTooltip.style.pointerEvents = 'none'; + crosshairTooltip.style.display = 'none'; + // document.body.appendChild(crosshairTooltip); + // 助手函数:转换UTC时间到所选时区 function convertToTimezone(utcDate, timezone) { return new Date(utcDate).toLocaleString('zh-CN', { @@ -2915,7 +2929,7 @@ const volumeChartRect = volumeChartContainer.getBoundingClientRect(); const volumeLine = document.createElement('div'); volumeLine.className = 'volume-crosshair-line'; - volumeLine.style.position = 'absolute'; + volumeLine.style.position = 'fixed'; // 改为fixed定位 volumeLine.style.left = (volumeChartRect.left + volumeTimeCoordinate) + 'px'; volumeLine.style.top = volumeChartRect.top + 'px'; volumeLine.style.width = '1px'; @@ -2934,7 +2948,7 @@ const macdChartRect = macdChartContainer.getBoundingClientRect(); const macdLine = document.createElement('div'); macdLine.className = 'macd-crosshair-line'; - macdLine.style.position = 'absolute'; + macdLine.style.position = 'fixed'; // 改为fixed定位 macdLine.style.left = (macdChartRect.left + macdTimeCoordinate) + 'px'; macdLine.style.top = macdChartRect.top + 'px'; macdLine.style.width = '1px'; @@ -3240,7 +3254,7 @@ // 构建显示文本,包含分型类型和强度信息 let displayText = `${fx.fx_strength.toFixed(1)}`; - if (fx.fx_strength < 1.5) { // 降低阈值,让更多分型显示 + if (fx.fx_strength < 0) { // 降低阈值,让更多分型显示 displayText = fx.fx_strength >= 0.8 ? '•' : '' // 0.8以上显示点,0.8以下不显示文本 } @@ -3966,7 +3980,7 @@ const volumeChartRect = volumeChartContainer.getBoundingClientRect(); const volumeLine = document.createElement('div'); volumeLine.className = 'volume-crosshair-line'; - volumeLine.style.position = 'absolute'; + volumeLine.style.position = 'fixed'; // 改为fixed定位 volumeLine.style.left = (volumeChartRect.left + volumeTimeCoordinate) + 'px'; volumeLine.style.top = volumeChartRect.top + 'px'; volumeLine.style.width = '1px'; @@ -3985,7 +3999,7 @@ const macdChartRect = macdChartContainer.getBoundingClientRect(); const macdLine = document.createElement('div'); macdLine.className = 'macd-crosshair-line'; - macdLine.style.position = 'absolute'; + macdLine.style.position = 'fixed'; // 改为fixed定位 macdLine.style.left = (macdChartRect.left + macdTimeCoordinate) + 'px'; macdLine.style.top = macdChartRect.top + 'px'; macdLine.style.width = '1px'; @@ -4801,6 +4815,19 @@ // 初始化股票筛选表格 initStockFilterTable(); + // 添加页面滚动事件监听器,清除十字线延长线 + $(window).on('scroll', function() { + try { + // 清除所有十字线延长线,防止它们跟着页面滚动 + const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); + existingVolumeLines.forEach(line => line.remove()); + const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); + existingMacdLines.forEach(line => line.remove()); + } catch (e) { + console.debug('清除滚动中的十字线时出错:', e); + } + }); + // 突出显示股票筛选tab (已禁用) setTimeout(function() { const stockFilterTab = $('#stock-filter-tab'); @@ -6196,6 +6223,19 @@ // 初始化股票筛选表格 initStockFilterTable(); + // 添加页面滚动事件监听器,清除十字线延长线 + $(window).on('scroll', function() { + try { + // 清除所有十字线延长线,防止它们跟着页面滚动 + const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); + existingVolumeLines.forEach(line => line.remove()); + const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); + existingMacdLines.forEach(line => line.remove()); + } catch (e) { + console.debug('清除滚动中的十字线时出错:', e); + } + }); + // 突出显示股票筛选tab (已禁用) setTimeout(function() { const stockFilterTab = $('#stock-filter-tab');