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 = 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
+385
View File
@@ -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
+10 -4
View File
@@ -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()
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 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")
),
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
});
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);