Add klc and klu fx strength check

This commit is contained in:
jackyu66git
2025-05-28 19:45:08 +08:00
parent 0754b5ae59
commit 843534a039
13 changed files with 885 additions and 25 deletions
Vendored
BIN
View File
Binary file not shown.
+8 -2
View File
@@ -49,7 +49,13 @@ class ChanKLC():
self.volume_ratio = self.volume_ratio / len(self.klus) self.volume_ratio = self.volume_ratio / len(self.klus)
self.volume = self.volume / len(self.klus) self.volume = self.volume / len(self.klus)
self.macdhist = self.macdhist / 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): def set_next(self, klc):
self.next = klc self.next = klc
def set_pre(self, klc): def set_pre(self, klc):
@@ -1221,7 +1227,7 @@ class ChanKLC():
# 获取分型后的几根K线数据 # 获取分型后的几根K线数据
subsequent_klcs = [] subsequent_klcs = []
temp = self.next temp = self.next
for i in range(5): # 检查后续5根K线 for i in range(2): # 检查后续5根K线
if temp: if temp:
subsequent_klcs.append(temp) subsequent_klcs.append(temp)
temp = temp.next if hasattr(temp, 'next') else None temp = temp.next if hasattr(temp, 'next') else None
+385
View File
@@ -1,3 +1,4 @@
from ChanEnum import Chan_FX_TYPE
class ChanKLU: class ChanKLU:
def __init__(self, time, open, high, low, close, volume): def __init__(self, time, open, high, low, close, volume):
# _time, _close, _open, _high, _low, _extra_info={} # _time, _close, _open, _high, _low, _extra_info={}
@@ -21,9 +22,375 @@ class ChanKLU:
self.ma250 = 0 self.ma250 = 0
self.rsi = 0 self.rsi = 0
self.volume_ratio = 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): def set_idx(self, idx):
self.idx = idx self.idx = idx
self.index = idx self.index = idx
def set_indicators(self, item): def set_indicators(self, item):
self.macd = float(item['macd']) if 'macd' in item and item['macd'] else 0 self.macd = float(item['macd']) if 'macd' in item and item['macd'] else 0
self.signal = float(item['macdsignal']) if 'macdsignal' in item and item['macdsignal'] else 0 self.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.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.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): def get_feature_data(self):
features = dict() features = dict()
features['klu_close'] = self.close features['klu_close'] = self.close
@@ -58,4 +429,18 @@ class ChanKLU:
features['klu_ma250'] = self.ma250 features['klu_ma250'] = self.ma250
features['klu_rsi'] = self.rsi features['klu_rsi'] = self.rsi
features['klu_volume_ratio'] = self.volume_ratio 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 return features
+10 -4
View File
@@ -67,12 +67,12 @@ class ChanLun():
else: else:
print(bi.start_klc.end_time, bi.dir, bi.is_sure) print(bi.start_klc.end_time, bi.dir, bi.is_sure)
def check_fx(self, klc): 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: if klc.high > klc.pre.high and klc.high > klc.next.high:
klc.set_fx(Chan_FX_TYPE.TOP) 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") #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP")
return Chan_FX_TYPE.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: if klc.low < klc.pre.low and klc.low < klc.next.low:
klc.set_fx(Chan_FX_TYPE.BOTTOM) 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") #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] klc = klc_list[klc_index]
if klc.end_klu and klc.end_klu.idx == index: if klc.end_klu and klc.end_klu.idx == index:
klc_index += 1 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) 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) fx_list.append(-1)
else: else:
fx_list.append(0) fx_list.append(0)
@@ -235,6 +235,7 @@ class ChanLun():
def get_kl_data(self, dataframe:DataFrame): def get_kl_data(self, dataframe:DataFrame):
fields = "time,open,high,low,close,volume" fields = "time,open,high,low,close,volume"
klu_list = [] klu_list = []
last_klu = None
for i in range(0, len(dataframe)): for i in range(0, len(dataframe)):
item = dataframe.iloc[i] item = dataframe.iloc[i]
date = item['date'] date = item['date']
@@ -258,8 +259,13 @@ class ChanLun():
klu = ChanKLU(time_str, o, h, l, c, v) klu = ChanKLU(time_str, o, h, l, c, v)
klu.set_idx(i) klu.set_idx(i)
klu_list.append(klu) 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: if 'macd' in item:
klu.set_indicators(item) klu.set_indicators(item)
last_klu = klu
return klu_list return klu_list
def cal_volume_ratio(self, dataframe, window=10): def cal_volume_ratio(self, dataframe, window=10):
df = dataframe.copy() df = dataframe.copy()
Binary file not shown.
Binary file not shown.
Binary file not shown.
+223
View File
@@ -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)
+220
View File
@@ -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()
+20 -18
View File
@@ -14,16 +14,19 @@ import talib.abstract as ta
from pandas import DataFrame from pandas import DataFrame
from datetime import datetime, timedelta from datetime import datetime, timedelta
from freqtrade.persistence import Trade from freqtrade.persistence import Trade
from typing import Optional from typing import Optional, List, Dict
import logging import logging
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
from freqtrade.optimize.space import Categorical, Dimension, Integer, SKDecimal
### Now you can use logger.info('asfd') to log ### 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 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 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 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 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 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 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 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- # 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" # This attribute will be overridden if the config file contains "minimal_roi"
# 30m and 1h # 30m and 1h
minimal_roi = { minimal_roi = {
"0": 0.60, "0": 0.15,
"360": 0.2, "240": 0.1,
"640": 0.1, "480": 0.02,
"1200": 0 "960": 0
} }
# 5m and 15m # 5m and 15m
minimal_roi_1 = { minimal_roi_1 = {
@@ -61,14 +64,13 @@ class ChanLun_BTC_15(IStrategy):
"3600": 0 "3600": 0
} }
can_short = True can_short = True
lev = 50.0 lev = 10
stoploss = -0.3 stoploss = -0.8
trailing_stop = False trailing_stop = False
trailing_stop_positive = 0.025 trailing_stop_positive = 0.025
trailing_stop_positive_offset = 0.045 trailing_stop_positive_offset = 0.045
trailing_only_offset_is_reached = False trailing_only_offset_is_reached = False
position_adjustment_enable = True
startup_candle_count = 600 startup_candle_count = 600
time5 = 5 time5 = 5
@@ -174,8 +176,8 @@ class ChanLun_BTC_15(IStrategy):
dataframe.loc[ dataframe.loc[
( (
#(dataframe['state'] == "-30") #(dataframe['state'] == "-30")
(dataframe[state_str].shift(self.time5) > 1.0) & (dataframe[state_str].shift(self.time5*2) > 0) &
(dataframe[fx_str].shift(self.time5) == -1) (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.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.time30)] == "-10") &
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-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.loc[
( (
#(dataframe['state'] == "-30") #(dataframe['state'] == "-30")
(dataframe[state_str].shift(self.time5) > 1.0) & (dataframe[state_str].shift(self.time5*2) > 0) &
(dataframe[fx_str].shift(self.time5) == 1) (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.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.time30)] == "-10") &
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time5)].shift(self.time5) == "-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.loc[
( (
#(dataframe['state']== "30") #(dataframe['state']== "30")
(dataframe[state_str].shift(self.time5) > 1.0) & (dataframe[state_str].shift(self.time5*2) > 0) &
(dataframe[fx_str].shift(self.time5) == 1) (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.time30)] == "10") &
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10")
), ),
@@ -209,8 +211,8 @@ class ChanLun_BTC_15(IStrategy):
dataframe.loc[ dataframe.loc[
( (
#(dataframe['state']== "30") #(dataframe['state']== "30")
(dataframe[state_str].shift(self.time5) > 1.0) & (dataframe[state_str].shift(self.time5*2) > 0) &
(dataframe[fx_str].shift(self.time5) == -1) (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.time30)] == "10") &
#(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10") #(dataframe['resample_{}_state'.format(self.get_ticker_indicator()*self.time60)] == "10")
), ),
View File
+18
View File
@@ -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
+1 -1
View File
@@ -2941,7 +2941,7 @@
is_strong_fx: fx.is_strong_fx is_strong_fx: fx.is_strong_fx
}); });
const displayText = `${fx.fx_strength_level} ${fx.fx_strength.toFixed(1)}`; const displayText = `${fx.fx_strength_level} ${fx.fx_strength.toFixed(1)}`;
if (fx.fx_strength < 1) { if (fx.fx_strength < 1.4) {
displayText = '' displayText = ''
} }
console.log('显示文本:', displayText); console.log('显示文本:', displayText);