diff --git a/.DS_Store b/.DS_Store index 34a17da..d4c4352 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/ChanKLC.py b/ChanKLC.py index 98e3074..74ddc33 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -49,7 +49,13 @@ class ChanKLC(): self.volume_ratio = self.volume_ratio / len(self.klus) self.volume = self.volume / len(self.klus) self.macdhist = self.macdhist / len(self.klus) - + def contain_klu_fx(self): + if len(self.klus) > 0: + for klu in self.klus: + klu.update_realtime_analysis() + if klu.fx_type == self.fx and klu.fx_strength > 1.8: + return True + return False def set_next(self, klc): self.next = klc def set_pre(self, klc): @@ -1221,7 +1227,7 @@ class ChanKLC(): # 获取分型后的几根K线数据 subsequent_klcs = [] temp = self.next - for i in range(5): # 检查后续5根K线 + for i in range(2): # 检查后续5根K线 if temp: subsequent_klcs.append(temp) temp = temp.next if hasattr(temp, 'next') else None diff --git a/ChanKLU.py b/ChanKLU.py index cf62525..2ceb0bc 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -1,3 +1,4 @@ +from ChanEnum import Chan_FX_TYPE class ChanKLU: def __init__(self, time, open, high, low, close, volume): # _time, _close, _open, _high, _low, _extra_info={} @@ -21,9 +22,375 @@ class ChanKLU: self.ma250 = 0 self.rsi = 0 self.volume_ratio = 0 + + # === 新增:实时分型相关属性 === + 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 # 分型是否确认 + + def set_next(self, next): + self.next = next + self.update_realtime_analysis() + #if self.fx_type != Chan_FX_TYPE.UNKNOWN and self.fx_strength > 1: + #print(self.index, self.time, self.fx_type, self.fx_confirmed, self.fx_strength) + def set_pre(self, pre): + self.pre = pre + def detect_realtime_fx(self): + """ + 实时检测K线分型(不等待KLC确认) + 基于原始K线的即时分型识别 + """ + if not self.pre or not self.next: + self.fx_type = Chan_FX_TYPE.UNKNOWN + return False + + # 顶分型检测 + if (self.high > self.pre.high and + self.high > self.next.high): + self.fx_type = Chan_FX_TYPE.TOP + self.fx_confirmed = True + return True + + # 底分型检测 + elif (self.low < self.pre.low and + self.low < self.next.low): + self.fx_type = Chan_FX_TYPE.BOTTOM + self.fx_confirmed = True + return True + + self.fx_type = Chan_FX_TYPE.UNKNOWN + self.fx_confirmed = False + return False + + def calculate_realtime_fx_strength(self): + """ + 用self.pre和self.next实现分型强弱判断(与KLC中cal_fx_strength一致) + + 核心缠论原理: + - 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折 + - 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸 + + 返回值: + 3: 极强分型(笔终结+强确认) + 2: 强分型(笔终结) + 1: 偏强分型(可能终结笔) + 0: 中性分型 + -1: 偏弱分型(中继特征明显) + -2: 弱分型(明显中继) + -3: 极弱分型(无效分型) + """ + # 检查是否为分型,且有前后K线数据 + if self.fx_type == Chan_FX_TYPE.UNKNOWN: + return 0 + if not self.pre or not self.next: + return 100 + # === 核心判断:分型在笔中的位置 === + + # 1. 检查这个分型是否能够终结当前笔 + is_bi_end = self._check_if_bi_ending_fx() + + # 2. 检查分型的后续走势确认 + post_fx_confirmation = self._check_post_fx_confirmation() + + # 3. 检查分型的标准性和强度 + fx_quality = self._check_fx_quality() + + # === 综合评分 === + base_score = 0 + + # 笔位置是最重要的判断标准 + if is_bi_end == 2: # 强烈确认笔终结 + base_score = 2 + elif is_bi_end == 1: # 可能笔终结 + base_score = 1 + elif is_bi_end == -1: # 明显中继 + base_score = -2 + elif is_bi_end == -2: # 强烈中继特征 + base_score = -3 + else: # 不确定 + base_score = 0 + + # 后续确认调整 + base_score += post_fx_confirmation + + # 分型质量调整 + base_score += fx_quality + + # 限制在-3到3范围内 + final_score = max(-3, min(3, base_score)) + self.fx_strength = final_score + # 转换为0-100分制以保持接口一致性 + #self.fx_strength = int((final_score + 3) * 100 / 6) # -3到3映射到0-100 + if final_score > 1.8: + print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality) + #print(self.time, final_score, is_bi_end, post_fx_confirmation, fx_quality) + return self.fx_strength + + def _check_if_bi_ending_fx(self): + """ + 检查分型是否为笔终结分型 + 返回值: + 2: 强烈确认笔终结 + 1: 可能笔终结 + 0: 不确定 + -1: 明显中继 + -2: 强烈中继特征 + """ + # 检查是否有足够的后续数据来判断 + if not self.next or not hasattr(self.next, 'next'): + return 0 + + # 获取分型后的几根K线数据 + subsequent_klus = [] + temp = self.next + for i in range(2): # 检查后续2根K线 + if temp: + subsequent_klus.append(temp) + temp = temp.next if hasattr(temp, 'next') else None + else: + break + + if len(subsequent_klus) < 2: + return 0 + + if self.fx_type == Chan_FX_TYPE.TOP: + return self._check_top_bi_ending(subsequent_klus) + else: # BOTTOM + return self._check_bottom_bi_ending(subsequent_klus) + + def _check_top_bi_ending(self, subsequent_klus): + """检查顶分型是否为笔终结""" + # 强烈笔终结特征: + # 1. 后续K线持续下跌,且跌破关键位置 + # 2. 没有新的更高的高点出现 + + broken_key_levels = 0 + new_highs = 0 + downward_trend = 0 + + # 检查关键价位突破 + first_low = self.pre.low + middle_low = self.low + key_support = min(first_low, middle_low) + + for i, klu in enumerate(subsequent_klus): + # 检查是否跌破关键支撑 + if klu.low < key_support: + broken_key_levels += 1 + + # 检查是否出现新高 + if klu.high > self.high: + new_highs += 1 + + # 检查下跌趋势 + if i > 0 and klu.close < subsequent_klus[i-1].close: + downward_trend += 1 + + # 强烈笔终结:跌破关键位且无新高 + if broken_key_levels >= 1 and new_highs == 0 and downward_trend >= 2: + return 2 + + # 可能笔终结:部分条件满足 + if (broken_key_levels >= 1 and new_highs <= 1) or (new_highs == 0 and downward_trend >= 3): + return 1 + + # 明显中继:出现新高且未跌破关键位 + if new_highs >= 2 and broken_key_levels == 0: + return -2 + + # 中继倾向:出现新高 + if new_highs >= 1: + return -1 + + return 0 + + def _check_bottom_bi_ending(self, subsequent_klus): + """检查底分型是否为笔终结""" + # 强烈笔终结特征: + # 1. 后续K线持续上涨,且突破关键位置 + # 2. 没有新的更低的低点出现 + + broken_key_levels = 0 + new_lows = 0 + upward_trend = 0 + + # 检查关键价位突破 + first_high = self.pre.high + middle_high = self.high + key_resistance = max(first_high, middle_high) + + for i, klu in enumerate(subsequent_klus): + # 检查是否突破关键阻力 + if klu.high > key_resistance: + broken_key_levels += 1 + + # 检查是否出现新低 + if klu.low < self.low: + new_lows += 1 + + # 检查上涨趋势 + if i > 0 and klu.close > subsequent_klus[i-1].close: + upward_trend += 1 + + # 强烈笔终结:突破关键位且无新低 + if broken_key_levels >= 1 and new_lows == 0 and upward_trend >= 2: + return 2 + + # 可能笔终结:部分条件满足 + if (broken_key_levels >= 1 and new_lows <= 1) or (new_lows == 0 and upward_trend >= 3): + return 1 + + # 明显中继:出现新低且未突破关键位 + if new_lows >= 2 and broken_key_levels == 0: + return -2 + + # 中继倾向:出现新低 + if new_lows >= 1: + return -1 + + return 0 + + def _check_post_fx_confirmation(self): + """ + 检查分型后的走势确认 + 返回值:-1到1的调整分数 + """ + if not self.next: + return 0 + + score = 0 + + # 检查第三根K线的确认 + third_klu = self.next + + if self.fx_type == Chan_FX_TYPE.TOP: + # 顶分型:第三根K线应该走弱 + middle_price = (self.high + self.low) / 2 + + if third_klu.close < middle_price: + score += 0.5 + if third_klu.low < self.pre.low: # 跌破第一根K线低点 + score += 0.5 + if third_klu.close < third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5: + score += 0.3 # 明显阴线 + + else: # BOTTOM + # 底分型:第三根K线应该走强 + middle_price = (self.high + self.low) / 2 + + if third_klu.close > middle_price: + score += 0.5 + if third_klu.high > self.pre.high: # 突破第一根K线高点 + score += 0.5 + if third_klu.close > third_klu.open and abs(third_klu.close - third_klu.open) > abs(self.close - self.open) * 0.5: + score += 0.3 # 明显阳线 + + return min(1, max(-1, score)) + + def _check_fx_quality(self): + """ + 检查分型本身的质量 + 返回值:-1到1的调整分数 + """ + score = 0 + + # 检查分型的标准性 + if self.fx_type == Chan_FX_TYPE.TOP: + # 高点突出程度 + high_diff1 = (self.high - self.pre.high) / self.high if self.high > 0 else 0 + high_diff2 = (self.high - self.next.high) / self.high if self.high > 0 else 0 + min_diff = min(high_diff1, high_diff2) + + if min_diff > 0.03: # 非常突出 + score += 0.5 + elif min_diff > 0.01: # 比较突出 + score += 0.2 + elif min_diff < 0.003: # 不够突出 + score -= 0.5 + + else: # BOTTOM + # 低点突出程度 + low_diff1 = (self.pre.low - self.low) / self.pre.low if self.pre.low > 0 else 0 + low_diff2 = (self.next.low - self.low) / self.next.low if self.next.low > 0 else 0 + min_diff = min(low_diff1, low_diff2) + + if min_diff > 0.03: # 非常突出 + score += 0.5 + elif min_diff > 0.01: # 比较突出 + score += 0.2 + elif min_diff < 0.003: # 不够突出 + score -= 0.5 + + # 检查量价配合 + avg_volume = self._get_avg_volume(lookback=5) + if avg_volume > 0: + volume_ratio = self.volume / avg_volume + if volume_ratio > 1.5: + score += 0.3 + elif volume_ratio < 0.7: + score -= 0.2 + + return min(1, max(-1, score)) + + def _get_avg_volume(self, lookback=5): + """获取前N根K线平均成交量""" + volumes = [] + temp = self.pre + + for i in range(lookback): + if temp: + volumes.append(temp.volume) + temp = temp.pre if hasattr(temp, 'pre') else None + else: + break + + return sum(volumes) / len(volumes) if volumes else self.volume + + def get_fx_signal(self): + """ + 获取分型交易信号 + 返回: (信号类型, 强度, 建议) + """ + if not self.fx_confirmed: + return ("无信号", 0, "等待分型确认") + + strength_level = "弱" + if self.fx_strength >= 80: + strength_level = "极强" + elif self.fx_strength >= 65: + strength_level = "强" + elif self.fx_strength >= 50: + strength_level = "中等" + + if self.fx_type == Chan_FX_TYPE.TOP: + signal_type = f"{strength_level}顶分型" + if self.fx_strength >= 65: + suggestion = "考虑减仓或止盈" + else: + suggestion = "谨慎观望" + else: + signal_type = f"{strength_level}底分型" + if self.fx_strength >= 65: + suggestion = "考虑建仓或加仓" + else: + suggestion = "谨慎观望" + + return (signal_type, self.fx_strength, suggestion) + + def update_realtime_analysis(self): + """ + 更新实时分析(在每根K线完成时调用) + """ + self.detect_realtime_fx() + if self.fx_confirmed: + self.calculate_realtime_fx_strength() + def set_idx(self, idx): self.idx = idx self.index = idx + def set_indicators(self, item): self.macd = float(item['macd']) if 'macd' in item and item['macd'] else 0 self.signal = float(item['macdsignal']) if 'macdsignal' in item and item['macdsignal'] else 0 @@ -39,6 +406,10 @@ class ChanKLU: self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0 self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0 + + # 设置指标后更新实时分析 + self.update_realtime_analysis() + def get_feature_data(self): features = dict() features['klu_close'] = self.close @@ -58,4 +429,18 @@ class ChanKLU: features['klu_ma250'] = self.ma250 features['klu_rsi'] = self.rsi features['klu_volume_ratio'] = self.volume_ratio + + # === 新增:实时分型特征 === + # 将枚举转换为数值:UNKNOWN=0, TOP=1, BOTTOM=-1 + if self.fx_type == Chan_FX_TYPE.TOP: + fx_type_value = 1 + elif self.fx_type == Chan_FX_TYPE.BOTTOM: + fx_type_value = -1 + else: + fx_type_value = 0 + + features['klu_fx_type'] = fx_type_value + features['klu_fx_strength'] = self.fx_strength + features['klu_fx_confirmed'] = 1 if self.fx_confirmed else 0 + return features \ No newline at end of file diff --git a/ChanLun.py b/ChanLun.py index 838f985..421b923 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -67,12 +67,12 @@ class ChanLun(): else: print(bi.start_klc.end_time, bi.dir, bi.is_sure) def check_fx(self, klc): - if klc.pre and klc.next: + if klc.pre and klc.next and klc.next.end_klu: if klc.high > klc.pre.high and klc.high > klc.next.high: klc.set_fx(Chan_FX_TYPE.TOP) #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP") return Chan_FX_TYPE.TOP - if klc.pre and klc.next: + if klc.pre and klc.next and klc.next.end_klu: if klc.low < klc.pre.low and klc.low < klc.next.low: klc.set_fx(Chan_FX_TYPE.BOTTOM) #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM") @@ -149,9 +149,9 @@ class ChanLun(): klc = klc_list[klc_index] if klc.end_klu and klc.end_klu.idx == index: klc_index += 1 - if klc.klc_fx_type == Chan_KLC_FX.TOP1 or klc.klc_fx_type == Chan_KLC_FX.TOP2: + if (klc.klc_fx_type == Chan_KLC_FX.TOP1 or klc.klc_fx_type == Chan_KLC_FX.TOP2) and klc.contain_klu_fx(): fx_list.append(1) - elif klc.klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc.klc_fx_type == Chan_KLC_FX.BOTTOM2: + elif (klc.klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc.klc_fx_type == Chan_KLC_FX.BOTTOM2) and klc.contain_klu_fx(): fx_list.append(-1) else: fx_list.append(0) @@ -235,6 +235,7 @@ class ChanLun(): def get_kl_data(self, dataframe:DataFrame): fields = "time,open,high,low,close,volume" klu_list = [] + last_klu = None for i in range(0, len(dataframe)): item = dataframe.iloc[i] date = item['date'] @@ -258,8 +259,13 @@ class ChanLun(): klu = ChanKLU(time_str, o, h, l, c, v) klu.set_idx(i) klu_list.append(klu) + if last_klu: + klu.set_pre(last_klu) + last_klu.set_next(klu) + last_klu.detect_realtime_fx() if 'macd' in item: klu.set_indicators(item) + last_klu = klu return klu_list def cal_volume_ratio(self, dataframe, window=10): df = dataframe.copy() diff --git a/__pycache__/ChanKLC.cpython-312.pyc b/__pycache__/ChanKLC.cpython-312.pyc index 5e54994..e8d3dda 100644 Binary files a/__pycache__/ChanKLC.cpython-312.pyc and b/__pycache__/ChanKLC.cpython-312.pyc differ diff --git a/__pycache__/ChanKLU.cpython-312.pyc b/__pycache__/ChanKLU.cpython-312.pyc index 2cfd466..2dc2c15 100644 Binary files a/__pycache__/ChanKLU.cpython-312.pyc and b/__pycache__/ChanKLU.cpython-312.pyc differ diff --git a/__pycache__/ChanLun.cpython-312.pyc b/__pycache__/ChanLun.cpython-312.pyc index 3a91489..0ff7fa4 100644 Binary files a/__pycache__/ChanLun.cpython-312.pyc and b/__pycache__/ChanLun.cpython-312.pyc differ diff --git a/algorithm_comparison_test.py b/algorithm_comparison_test.py new file mode 100644 index 0000000..43aca62 --- /dev/null +++ b/algorithm_comparison_test.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +KLU与KLC分型强度算法一致性测试 +验证两种算法在相同数据下是否产生一致的结果 +""" + +from ChanKLU import ChanKLU +from ChanKLC import ChanKLC +from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR +import pandas as pd +from datetime import datetime, timedelta + +def create_test_data(): + """创建测试用的K线数据""" + test_cases = [ + # 测试用例1:标准顶分型 + { + 'name': '标准顶分型', + 'data': [ + {'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1 + {'open': 101, 'high': 105, 'low': 100, 'close': 103, 'volume': 1500}, # K2 (顶分型中心) + {'open': 103, 'high': 104, 'low': 98, 'close': 99, 'volume': 1200}, # K3 + ] + }, + # 测试用例2:标准底分型 + { + 'name': '标准底分型', + 'data': [ + {'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1 + {'open': 101, 'high': 103, 'low': 95, 'close': 97, 'volume': 1500}, # K2 (底分型中心) + {'open': 97, 'high': 104, 'low': 96, 'close': 102, 'volume': 1200}, # K3 + ] + }, + # 测试用例3:强势顶分型(放量+下影线) + { + 'name': '强势顶分型', + 'data': [ + {'open': 100, 'high': 102, 'low': 99, 'close': 101, 'volume': 1000}, # K1 + {'open': 101, 'high': 108, 'low': 100, 'close': 102, 'volume': 2500}, # K2 (强顶分型) + {'open': 102, 'high': 103, 'low': 95, 'close': 96, 'volume': 1800}, # K3 (大阴线确认) + ] + } + ] + return test_cases + +def setup_klu_chain(data_list): + """设置KLU链""" + klus = [] + base_time = datetime.now() + + for i, data in enumerate(data_list): + time_str = (base_time + timedelta(minutes=i)).strftime("%Y-%m-%d %H:%M:%S") + klu = ChanKLU(time_str, data['open'], data['high'], data['low'], data['close'], data['volume']) + klu.set_idx(i) + + # 设置基础技术指标 + indicators = { + 'ma5': data['close'] + (i-1) * 0.1, + 'ma10': data['close'] + (i-1) * 0.05, + 'rsi': 50 + (i % 3 - 1) * 15, + 'macd': (i % 3 - 1) * 0.01, + 'macdhist': (i % 2) * 0.005, + 'volume_ratio': 1.0 + (i % 2) * 0.3 + } + klu.set_indicators(indicators) + klus.append(klu) + + # 建立前后关系 + for i in range(len(klus)): + if i > 0: + klus[i].set_pre(klus[i-1]) + if i < len(klus) - 1: + klus[i].set_next(klus[i+1]) + + return klus + +def setup_klc_chain(data_list): + """设置KLC链(基于KLU)""" + klus = setup_klu_chain(data_list) + klcs = [] + + # 为简化测试,假设每个KLU对应一个KLC(无包含关系处理) + for i, klu in enumerate(klus): + klc = ChanKLC(klu, i, Chan_KLINE_DIR.UP) + klc.set_end_klu(klu) + klcs.append(klc) + + # 建立前后关系 + for i in range(len(klcs)): + if i > 0: + klcs[i].set_pre(klcs[i-1]) + if i < len(klcs) - 1: + klcs[i].set_next(klcs[i+1]) + + # 设置分型类型 + if len(klcs) >= 3: + middle_klc = klcs[1] + if (middle_klc.high > klcs[0].high and middle_klc.high > klcs[2].high): + middle_klc.set_fx(Chan_FX_TYPE.TOP) + elif (middle_klc.low < klcs[0].low and middle_klc.low < klcs[2].low): + middle_klc.set_fx(Chan_FX_TYPE.BOTTOM) + + return klcs + +def compare_algorithms(test_cases): + """对比KLU和KLC算法""" + + print("=" * 80) + print("KLU与KLC分型强度算法一致性测试") + print("=" * 80) + + for case in test_cases: + print(f"\n🔍 测试用例: {case['name']}") + print("-" * 50) + + # 准备数据 + klus = setup_klu_chain(case['data']) + klcs = setup_klc_chain(case['data']) + + if len(klus) >= 3 and len(klcs) >= 3: + middle_klu = klus[1] + middle_klc = klcs[1] + + # KLU分析 + middle_klu.update_realtime_analysis() + klu_fx_type = middle_klu.fx_type + klu_strength = middle_klu.fx_strength + klu_confirmed = middle_klu.fx_confirmed + + # KLC分析 + klc_fx_type = middle_klc.fx + klc_strength_raw = middle_klc.cal_fx_strength() # -3到3 + klc_strength_converted = int((klc_strength_raw + 3) * 100 / 6) # 转换为0-100 + + # 输出对比结果 + print(f"K线数据: {case['data'][1]}") + print(f"\nKLU算法结果:") + print(f" 分型类型: {klu_fx_type}") + print(f" 分型强度: {klu_strength}") + print(f" 是否确认: {klu_confirmed}") + + print(f"\nKLC算法结果:") + print(f" 分型类型: {klc_fx_type}") + print(f" 分型强度(原始): {klc_strength_raw}") + print(f" 分型强度(转换): {klc_strength_converted}") + + # 一致性检查 + type_consistent = (klu_fx_type == klc_fx_type) + strength_diff = abs(klu_strength - klc_strength_converted) + strength_consistent = strength_diff <= 10 # 允许10分以内的差异 + + print(f"\n一致性检查:") + print(f" 分型类型一致: {'✅' if type_consistent else '❌'}") + print(f" 强度差异: {strength_diff}分 {'✅' if strength_consistent else '❌'}") + + if not type_consistent or not strength_consistent: + print(f" ⚠️ 算法结果不一致!") + else: + print(f" ✅ 算法结果一致") + else: + print("❌ 数据不足,无法进行对比") + +def detailed_strength_analysis(): + """详细的强度分析对比""" + print("\n" + "=" * 80) + print("详细强度分析对比") + print("=" * 80) + + # 创建一个明确的强分型案例 + strong_top_data = [ + {'open': 100, 'high': 101, 'low': 99, 'close': 100, 'volume': 1000}, + {'open': 100, 'high': 110, 'low': 99, 'close': 102, 'volume': 3000}, # 强顶分型 + {'open': 102, 'high': 103, 'low': 92, 'close': 93, 'volume': 2000}, # 强确认 + {'open': 93, 'high': 94, 'low': 90, 'close': 91, 'volume': 1500}, # 继续下跌 + {'open': 91, 'high': 92, 'low': 88, 'close': 89, 'volume': 1200}, # 进一步确认 + ] + + klus = setup_klu_chain(strong_top_data) + + if len(klus) >= 5: + target_klu = klus[1] # 目标分型K线 + + print(f"分析目标: 第2根K线 (索引1)") + print(f"K线数据: {strong_top_data[1]}") + + # 更新分析 + target_klu.update_realtime_analysis() + + print(f"\n分型检测结果:") + print(f" 分型类型: {target_klu.fx_type}") + print(f" 分型确认: {target_klu.fx_confirmed}") + print(f" 最终强度: {target_klu.fx_strength}") + + # 显示中间计算过程(需要重新调用以获取详细信息) + if target_klu.fx_confirmed: + print(f"\n强度计算过程:") + is_bi_end = target_klu._check_if_bi_ending_fx() + post_confirmation = target_klu._check_post_fx_confirmation() + fx_quality = target_klu._check_fx_quality() + + print(f" 笔终结判断: {is_bi_end}") + print(f" 后续确认: {post_confirmation}") + print(f" 分型质量: {fx_quality}") + + raw_score = is_bi_end + post_confirmation + fx_quality + final_raw = max(-3, min(3, raw_score)) + converted_score = int((final_raw + 3) * 100 / 6) + + print(f" 原始总分: {raw_score} -> {final_raw}") + print(f" 转换分数: {converted_score}") + +if __name__ == "__main__": + # 运行测试 + test_cases = create_test_data() + compare_algorithms(test_cases) + + # 详细分析 + detailed_strength_analysis() + + print("\n" + "=" * 80) + print("测试完成!") + print("=" * 80) \ No newline at end of file diff --git a/realtime_fx_example.py b/realtime_fx_example.py new file mode 100644 index 0000000..8c95897 --- /dev/null +++ b/realtime_fx_example.py @@ -0,0 +1,220 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- +""" +实时K线分型强弱判断示例 +解决KLC滞后问题,提供即时的分型信号 +""" + +from ChanKLU import ChanKLU +from ChanEnum import Chan_FX_TYPE +import pandas as pd +from datetime import datetime, timedelta + +class RealtimeFxAnalyzer: + """实时分型分析器""" + + def __init__(self): + self.klu_list = [] + self.latest_signals = [] + + def add_kline(self, time, open_price, high, low, close, volume, indicators=None): + """ + 添加新的K线数据并进行实时分析 + + Args: + time: 时间 + open_price, high, low, close, volume: K线数据 + indicators: 技术指标字典 {'macd': xx, 'rsi': xx, 'ma5': xx, ...} + """ + # 创建新的KLU对象 + new_klu = ChanKLU(time, open_price, high, low, close, volume) + + # 设置技术指标 + if indicators: + new_klu.set_indicators(indicators) + + # 设置索引 + new_klu.set_idx(len(self.klu_list)) + + # 建立前后关系链 + if len(self.klu_list) >= 1: + prev_klu = self.klu_list[-1] + new_klu.set_pre(prev_klu) + prev_klu.set_next(new_klu) + + # 如果有足够的数据,设置前一根K线的next关系 + if len(self.klu_list) >= 2: + prev_prev_klu = self.klu_list[-2] + prev_prev_klu.set_next(self.klu_list[-1]) + + self.klu_list.append(new_klu) + + # 实时分析最近的K线分型 + self._analyze_recent_fractals() + + return new_klu + + def _analyze_recent_fractals(self): + """分析最近的分型情况""" + if len(self.klu_list) < 3: + return + + # 检查倒数第二根K线的分型(因为需要左右两根K线确认) + target_idx = len(self.klu_list) - 2 + if target_idx >= 1: + target_klu = self.klu_list[target_idx] + + # 进行实时分型分析 + target_klu.update_realtime_analysis() + + # 如果发现分型,记录信号 + if target_klu.fx_confirmed: + signal = target_klu.get_fx_signal() + signal_info = { + 'time': target_klu.time, + 'price': target_klu.close, + 'signal_type': signal[0], + 'strength': signal[1], + 'suggestion': signal[2], + 'fx_type': target_klu.fx_type + } + + self.latest_signals.append(signal_info) + + # 保持最近20个信号 + if len(self.latest_signals) > 20: + self.latest_signals.pop(0) + + print(f"🔔 分型信号: {signal_info['time']} - {signal_info['signal_type']} " + f"(强度: {signal_info['strength']}) - {signal_info['suggestion']}") + + def get_latest_signal(self): + """获取最新的分型信号""" + return self.latest_signals[-1] if self.latest_signals else None + + def get_current_fx_status(self): + """获取当前分型状态统计""" + if len(self.klu_list) < 10: + return {"status": "数据不足"} + + recent_10 = self.klu_list[-10:] + + top_fx_count = sum(1 for klu in recent_10 if klu.fx_type == Chan_FX_TYPE.TOP) + bottom_fx_count = sum(1 for klu in recent_10 if klu.fx_type == Chan_FX_TYPE.BOTTOM) + + strong_fx_count = sum(1 for klu in recent_10 if klu.fx_strength >= 65) + + return { + "最近10根K线": len(recent_10), + "顶分型数量": top_fx_count, + "底分型数量": bottom_fx_count, + "强分型数量": strong_fx_count, + "最新K线时间": recent_10[-1].time, + "最新信号": self.get_latest_signal() + } + +def simulate_realtime_trading(): + """模拟实时交易场景""" + + print("=== 实时K线分型分析示例 ===\n") + + # 创建分析器 + analyzer = RealtimeFxAnalyzer() + + # 模拟实时K线数据流 + base_time = datetime.now() + base_price = 100.0 + + print("开始接收K线数据...\n") + + for i in range(20): + # 模拟价格波动 + if i < 5: # 上涨阶段 + price_change = 0.5 + elif i < 10: # 下跌阶段 + price_change = -0.8 + elif i < 15: # 震荡阶段 + price_change = 0.3 * ((-1) ** i) + else: # 再次上涨 + price_change = 0.6 + + current_price = base_price + price_change + + # 构造K线数据 + open_price = base_price + high = max(open_price, current_price) + abs(price_change) * 0.2 + low = min(open_price, current_price) - abs(price_change) * 0.2 + close = current_price + volume = 1000 + i * 50 + + # 模拟技术指标 + indicators = { + 'ma5': base_price + (i - 10) * 0.1, + 'ma10': base_price + (i - 10) * 0.05, + 'rsi': 50 + (i % 7 - 3) * 10, + 'macd': (i % 6 - 3) * 0.01, + 'macdhist': (i % 4 - 2) * 0.005, + 'volume_ratio': 1.0 + (i % 3 - 1) * 0.2 + } + + # 添加K线数据 + kline_time = base_time + timedelta(minutes=i) + analyzer.add_kline( + time=kline_time.strftime("%Y-%m-%d %H:%M:%S"), + open_price=open_price, + high=high, + low=low, + close=close, + volume=volume, + indicators=indicators + ) + + base_price = current_price + + # 每5根K线显示一次状态 + if (i + 1) % 5 == 0: + status = analyzer.get_current_fx_status() + print(f"\n--- 第{i+1}根K线后的状态 ---") + for key, value in status.items(): + if key != "最新信号": + print(f"{key}: {value}") + + if "最新信号" in status and status["最新信号"]: + signal = status["最新信号"] + print(f"最新信号: {signal['signal_type']} (强度: {signal['strength']})") + print() + + print("\n=== 所有分型信号汇总 ===") + for signal in analyzer.latest_signals: + print(f"{signal['time']} | {signal['signal_type']} | 强度: {signal['strength']} | {signal['suggestion']}") + +def compare_latency(): + """对比KLC和KLU方法的延迟差异""" + + print("\n=== 延迟对比分析 ===") + print("假设场景:连续包含关系的K线序列") + print("原始K线: K1, K2(包含K1), K3(包含K2), K4(突破), K5, K6") + print() + + print("KLC方法:") + print("- 需要等待K4确认包含关系结束") + print("- KLC1 = [K1+K2+K3], 在K4完成时才确定") + print("- 分型检测: 需要等待KLC1, KLC2, KLC3") + print("- 实际延迟: 可能6-8根原始K线") + print() + + print("KLU实时方法:") + print("- 每根K线完成时立即检测") + print("- K3完成时就能检测K2的分型状态") + print("- 实际延迟: 最多1根K线") + print() + + print("延迟改善: 从6-8根K线缩短到1根K线") + print("时间价值: 在5分钟K线下,可节省25-40分钟的反应时间") + +if __name__ == "__main__": + # 运行模拟 + simulate_realtime_trading() + + # 显示延迟对比 + compare_latency() \ No newline at end of file diff --git a/strategies/ChanLun_BTC_15.py b/strategies/ChanLun_BTC_15.py index 0f89fc4..e3c9961 100644 --- a/strategies/ChanLun_BTC_15.py +++ b/strategies/ChanLun_BTC_15.py @@ -14,16 +14,19 @@ import talib.abstract as ta from pandas import DataFrame from datetime import datetime, timedelta from freqtrade.persistence import Trade -from typing import Optional +from typing import Optional, List, Dict import logging logger = logging.getLogger(__name__) +from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal ### Now you can use logger.info('asfd') to log # freqtrade plot-dataframe --strategy ChanLun_BTC_15 --datadir user_data/data/binance -c ./user_data/ChanLun_SOL_15.json --timerange=20250309- # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies +# freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --export none --strategy-path ./user_data/Chan/strategies --timerange=20250525- # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- -# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405- -# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi stoploss --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_15.json -e 200 --timerange=20250201-20250401 +# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json -t 1m --pairs SOL/USDT:USDT --timerange=20250405- +# freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces stoploss --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_15.json -e 200 --timerange=20250201-20250501 +# freqtrade live-backtest -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # sudo docker compose run --rm chanlun_btc backtesting -c ./user_data/Chan/config/ChanLun_BTC_15.json --strategy ChanLun_BTC_15 --strategy-path ./user_data/Chan/strategies --timerange=20250525- # sudo docker compose run --rm chanlun_btc download-data -c ./user_data/Chan/config/ChanLun_BTC_15.json --pairs BTC/USDT:USDT -t 1m --timerange 20240101- @@ -35,10 +38,10 @@ class ChanLun_BTC_15(IStrategy): # This attribute will be overridden if the config file contains "minimal_roi" # 30m and 1h minimal_roi = { - "0": 0.60, - "360": 0.2, - "640": 0.1, - "1200": 0 + "0": 0.15, + "240": 0.1, + "480": 0.02, + "960": 0 } # 5m and 15m minimal_roi_1 = { @@ -61,14 +64,13 @@ class ChanLun_BTC_15(IStrategy): "3600": 0 } can_short = True - lev = 50.0 - stoploss = -0.3 + lev = 10 + stoploss = -0.8 trailing_stop = False trailing_stop_positive = 0.025 trailing_stop_positive_offset = 0.045 trailing_only_offset_is_reached = False - position_adjustment_enable = True startup_candle_count = 600 time5 = 5 @@ -174,8 +176,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state'] == "-30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == -1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == -1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") @@ -185,8 +187,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state'] == "-30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == 1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == 1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "-10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-10") @@ -200,8 +202,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state']== "30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == 1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == 1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") ), @@ -209,8 +211,8 @@ class ChanLun_BTC_15(IStrategy): dataframe.loc[ ( #(dataframe['state']== "30") - (dataframe[state_str].shift(self.time5) > 1.0) & - (dataframe[fx_str].shift(self.time5) == -1) + (dataframe[state_str].shift(self.time5*2) > 0) & + (dataframe[fx_str].shift(self.time5*2) == -1) #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time30)] == "10") & #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") ), diff --git a/web/nginx/logs/access.log b/web/nginx/logs/access.log new file mode 100644 index 0000000..e69de29 diff --git a/web/nginx/logs/error.log b/web/nginx/logs/error.log new file mode 100644 index 0000000..f9c73d6 --- /dev/null +++ b/web/nginx/logs/error.log @@ -0,0 +1,18 @@ +2025/05/27 01:57:54 [notice] 1#1: using the "epoll" event method +2025/05/27 01:57:54 [notice] 1#1: nginx/1.27.5 +2025/05/27 01:57:54 [notice] 1#1: built by gcc 12.2.0 (Debian 12.2.0-14) +2025/05/27 01:57:54 [notice] 1#1: OS: Linux 6.10.14-linuxkit +2025/05/27 01:57:54 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576 +2025/05/27 01:57:54 [notice] 1#1: start worker processes +2025/05/27 01:57:54 [notice] 1#1: start worker process 20 +2025/05/27 01:57:54 [notice] 1#1: start worker process 21 +2025/05/27 01:57:54 [notice] 1#1: start worker process 22 +2025/05/27 01:57:54 [notice] 1#1: start worker process 23 +2025/05/27 01:57:54 [notice] 1#1: start worker process 24 +2025/05/27 01:57:54 [notice] 1#1: start worker process 25 +2025/05/27 01:57:54 [notice] 1#1: start worker process 26 +2025/05/27 01:57:54 [notice] 1#1: start worker process 27 +2025/05/27 01:57:54 [notice] 1#1: start worker process 28 +2025/05/27 01:57:54 [notice] 1#1: start worker process 29 +2025/05/27 01:57:54 [notice] 1#1: start worker process 30 +2025/05/27 01:57:54 [notice] 1#1: start worker process 31 diff --git a/web/templates/index.html b/web/templates/index.html index 589933c..dcf9ec3 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2941,7 +2941,7 @@ is_strong_fx: fx.is_strong_fx }); const displayText = `${fx.fx_strength_level} ${fx.fx_strength.toFixed(1)}`; - if (fx.fx_strength < 1) { + if (fx.fx_strength < 1.4) { displayText = '' } console.log('显示文本:', displayText);