Add klc and klu fx strength check
This commit is contained in:
+8
-2
@@ -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
@@ -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
@@ -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.
@@ -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)
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
),
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user