This commit is contained in:
jackyu66git
2025-06-18 00:56:21 +08:00
parent 28a14cf6d3
commit da5eb8dc18
7 changed files with 472 additions and 363 deletions
Vendored
BIN
View File
Binary file not shown.
+34 -259
View File
@@ -160,7 +160,7 @@ class ChanKLC():
klu_list.append(klc3.klus) klu_list.append(klc3.klus)
gap = klc3.end_klu.index - klc1.start_klu.index + 1 gap = klc3.end_klu.index - klc1.start_klu.index + 1
if gap < 4: if gap < 4:
pass pass
return gap return gap
def get_feature_data(self): def get_feature_data(self):
features = dict() features = dict()
@@ -1162,269 +1162,44 @@ class ChanKLC():
return features return features
def cal_fx_strength(self, klc_offset=2): def cal_fx_strength(self, klc_offset=2):
""" strength = 0
用self.pre和self.next实现分型强弱判断 if self.fx == Chan_FX_TYPE.UNKNOWN or not self.pre or not self.next:
核心缠论原理:
- 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折
- 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸
返回值:
3: 极强分型(笔终结+强确认)
2: 强分型(笔终结)
1: 偏强分型(可能终结笔)
0: 中性分型
-1: 偏弱分型(中继特征明显)
-2: 弱分型(明显中继)
-3: 极弱分型(无效分型)
"""
# 检查是否为分型,且有前后K线数据
if self.fx == Chan_FX_TYPE.UNKNOWN:
return 0 return 0
if not self.pre or not self.next: else:
return 100 if self.pre and self.next:
# === 核心判断:分型在笔中的位置 === klc1 = self.pre
klc2 = self
# 1. 检查这个分型是否能够终结当前笔 klc3 = self.next
is_bi_end = self._check_if_bi_ending_fx(klc_offset) if self.bi:
if self.bi.dir == Chan_BI_DIR.UP and self.fx == Chan_FX_TYPE.BOTTOM:
# 2. 检查分型的后续走势确认 return 0
post_fx_confirmation = self._check_post_fx_confirmation() if self.bi.dir == Chan_BI_DIR.DOWN and self.fx == Chan_FX_TYPE.TOP:
return 0
# 3. 检查分型的标准性和强度 if self.bi.dir == Chan_BI_DIR.UP:
fx_quality = self._check_fx_quality() if self.klc_fx_type == Chan_KLC_FX.TOP1:
strength += self.check_bi_end(self.bi)
# === 综合评分 === elif self.klc_fx_type == Chan_KLC_FX.TOP2:
base_score = 0 strength += self.check_bi_end(self.bi)
else:
# 笔位置是最重要的判断标准 if self.klc_fx_type == Chan_KLC_FX.BOTTOM1 or self.klc_fx_type == Chan_KLC_FX.BOTTOM2:
if is_bi_end == 2: # 强烈确认笔终结 strength += self.check_bi_end(self.bi)
base_score = 2 elif self.klc_fx_type == Chan_KLC_FX.BOTTOM2:
elif is_bi_end == 1: # 可能笔终结 strength += self.check_bi_end(self.bi)
base_score = 1
elif is_bi_end == -1: # 明显中继
base_score = -2
elif is_bi_end == -2: # 强烈中继特征
base_score = -3
else: # 不确定
base_score = 0
# 后续确认调整
base_score += post_fx_confirmation
# 分型质量调整
base_score += fx_quality
#print(self.start_time, base_score, is_bi_end, post_fx_confirmation, fx_quality)
# 2025-06-07 08:15:00 1.5 0 0.8 0.7
# 限制在-3到3范围内
return base_score
def _check_if_bi_ending_fx(self, klc_offset):
"""
检查分型是否为笔终结分型
返回值:
2: 强烈确认笔终结
1: 可能笔终结
0: 不确定
-1: 明显中继
-2: 强烈中继特征
"""
# 检查是否有足够的后续数据来判断
if not self.next or not hasattr(self.next, 'next'):
return 0
# 获取分型后的几根K线数据
subsequent_klcs = []
temp = self.next
for i in range(klc_offset):
if temp:
subsequent_klcs.append(temp)
temp = temp.next if hasattr(temp, 'next') else None
else: else:
break return 0
return strength
if len(subsequent_klcs) < 2: def check_bi_end(self, bi):
return 0 if bi.dir == Chan_BI_DIR.UP:
pass
if self.fx == Chan_FX_TYPE.TOP:
return self._check_top_bi_ending(subsequent_klcs)
else: # BOTTOM
return self._check_bottom_bi_ending(subsequent_klcs)
def _check_top_bi_ending(self, subsequent_klcs):
"""检查顶分型是否为笔终结"""
# 强烈笔终结特征:
# 1. 后续K线持续下跌,且跌破关键位置
# 2. 没有新的更高的高点出现
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, klc in enumerate(subsequent_klcs):
# 检查是否跌破关键支撑
if klc.low < key_support:
broken_key_levels += 1
# 检查是否出现新高
if klc.high > self.high:
new_highs += 1
# 检查下跌趋势
if i > 0 and klc.close < subsequent_klcs[i-1].close:
downward_trend += 1
# 强烈笔终结:跌破关键位且无新高
if broken_key_levels >= 1 and new_highs == 0 and downward_trend >= 1:
return 2
# 可能笔终结:部分条件满足
if (broken_key_levels >= 1 and new_highs <= 1) or (new_highs == 0 and downward_trend >= 1):
return 1 return 1
else:
# 明显中继:出现新高且未跌破关键位
if new_highs >= 2 and broken_key_levels == 0:
return -2
# 中继倾向:出现新高
if new_highs >= 1:
return -1
return 0
def _check_bottom_bi_ending(self, subsequent_klcs):
"""检查底分型是否为笔终结"""
# 强烈笔终结特征:
# 1. 后续K线持续上涨,且突破关键位置
# 2. 没有新的更低的低点出现
broken_key_levels = 0
new_lows = 0
upward_trend = 0
# 检查关键价位突破
first_high = self.pre.high
middle_high = self.high
key_resistance = max(first_high, middle_high)
for i, klc in enumerate(subsequent_klcs):
# 检查是否突破关键阻力
if klc.high > key_resistance:
broken_key_levels += 1
# 检查是否出现新低
if klc.low < self.low:
new_lows += 1
# 检查上涨趋势
if i > 0 and klc.close > subsequent_klcs[i-1].close:
upward_trend += 1
# 强烈笔终结:突破关键位且无新低
if broken_key_levels >= 1 and new_lows == 0 and upward_trend >= 1:
return 2
# 可能笔终结:部分条件满足
if (broken_key_levels >= 1 and new_lows <= 1) or (new_lows == 0 and upward_trend >= 1):
return 1 return 1
def cal_klu_strength(self, klc1, klc2, klc3):
klu_list = []
klu_list.extend(klc1.klus)
klu_list.extend(klc2.klus)
klu_list.extend(klc3.klus)
# 明显中继:出现新低且未突破关键位 return
if new_lows >= 2 and broken_key_levels == 0:
return -2
# 中继倾向:出现新低
if new_lows >= 1:
return -1
return 0
def _check_post_fx_confirmation(self):
"""
检查分型后的走势确认
返回值:-1到1的调整分数
"""
if not self.next:
return 0
score = 0
# 检查第三根K线的确认
third_klc = self.next
if self.fx == Chan_FX_TYPE.TOP:
# 顶分型:第三根K线应该走弱
middle_price = (self.high + self.low) / 2
if third_klc.close < middle_price:
score += 0.5
if third_klc.low < self.pre.low: # 跌破第一根K线低点
score += 0.5
if third_klc.close < third_klc.open and abs(third_klc.close - third_klc.open) > abs(self.close - self.open) * 0.5:
score += 0.3 # 明显阴线
else: # BOTTOM
# 底分型:第三根K线应该走强
middle_price = (self.high + self.low) / 2
if third_klc.close > middle_price:
score += 0.5
if third_klc.high > self.pre.high: # 突破第一根K线高点
score += 0.5
if third_klc.close > third_klc.open and abs(third_klc.close - third_klc.open) > abs(self.close - self.open) * 0.5:
score += 0.3 # 明显阳线
return min(1, max(-1, score))
def _check_fx_quality(self):
"""
检查分型本身的质量
返回值:-1到1的调整分数
"""
score = 0
# 检查分型的标准性
if self.fx == Chan_FX_TYPE.TOP:
# 高点突出程度
high_diff1 = (self.high - self.pre.high) / self.high if self.high > 0 else 0
high_diff2 = (self.high - self.next.high) / self.high if self.high > 0 else 0
min_diff = 33*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 = 33*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._calculate_average_volume(lookback=5)
if avg_volume > 0:
volume_ratio = self.volume / avg_volume
if volume_ratio > 1.5:
score += 0.5
elif volume_ratio < 0.7:
score -= 0.3
return min(1, max(-1, score))
def calculate_fx_strength(self): def calculate_fx_strength(self):
""" """
基于专业缠论理论的分型强度评估体系 基于专业缠论理论的分型强度评估体系
+6 -6
View File
@@ -754,7 +754,7 @@ class ChanLun():
last_top = klc last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1") #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1")
klc.set_klc_fx_type(Chan_KLC_FX.TOP1) klc.set_klc_fx_type(Chan_KLC_FX.TOP1)
#print(klc.start_time, klc.fx, "一类卖点Sell 1") print(klc.end_time, klc.fx, "一类卖点Sell 1")
#klc.set_fx(fx) #klc.set_fx(fx)
#klc.set_state("10") #klc.set_state("10")
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
@@ -772,7 +772,7 @@ class ChanLun():
if last_top.index + 4 < klc.index and len(bi_list) > 1: if last_top.index + 4 < klc.index and len(bi_list) > 1:
pre_last_bi = bi_list[-2] pre_last_bi = bi_list[-2]
last_bi = bi_list[-1] last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP: if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.UP and False:
pre_last_bi.update_bi(klc) pre_last_bi.update_bi(klc)
bi_list.remove(last_bi) bi_list.remove(last_bi)
pre_last_bi.set_next(None) pre_last_bi.set_next(None)
@@ -784,7 +784,7 @@ class ChanLun():
#print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi") #print(klc.start_time, last_bi.start_klc.start_time, "New TOP Found reset last bi")
#klc.set_state("10") #klc.set_state("10")
#print(klc.start_time, klc.fx, "笔卖点Sell 1") #print(klc.start_time, klc.fx, "笔卖点Sell 1")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2) ###klc.set_klc_fx_type(Chan_KLC_FX.TOP2) # when bi is down but the fx is top
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
else: else:
@@ -804,7 +804,7 @@ class ChanLun():
bi.add_klc(klc) bi.add_klc(klc)
bi_list.append(bi) bi_list.append(bi)
last_top = klc last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 2") print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Top Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2) klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
#klc.set_state('30') #klc.set_state('30')
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
@@ -892,7 +892,7 @@ class ChanLun():
if last_bottom.index + 4 < klc.index and len(bi_list) > 1: if last_bottom.index + 4 < klc.index and len(bi_list) > 1:
pre_last_bi = bi_list[-2] pre_last_bi = bi_list[-2]
last_bi = bi_list[-1] last_bi = bi_list[-1]
if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN: if pre_last_bi.is_sure and not last_bi.is_sure and pre_last_bi.dir == Chan_BI_DIR.DOWN and False:
pre_last_bi.update_bi(klc) pre_last_bi.update_bi(klc)
bi_list.remove(last_bi) bi_list.remove(last_bi)
pre_last_bi.set_next(None) pre_last_bi.set_next(None)
@@ -904,7 +904,7 @@ class ChanLun():
#print(klc.start_time, last_bi.start_klc.start_time, "New BOTTOM Found reset last bi") #print(klc.start_time, last_bi.start_klc.start_time, "New BOTTOM Found reset last bi")
#klc.set_state("-10") #klc.set_state("-10")
#print(klc.start_time, klc.fx, "笔买点Buy 1") #print(klc.start_time, klc.fx, "笔买点Buy 1")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2) ###klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2) # when bi is up but the fx is bottom
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
else: else:
+29 -29
View File
@@ -1,7 +1,7 @@
import sys import sys
import os import os
sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/chan.py")) #sys.path.append(os.path.abspath("/Users/jack/Documents/GitHub/chan.py"))
#sys.path.append(os.path.abspath("/Users/jack/Project/chan.py")) sys.path.append(os.path.abspath("/Users/jack/Project/chan.py"))
from Chan import CChan from Chan import CChan
from BuySellPoint.BS_Point import CBS_Point from BuySellPoint.BS_Point import CBS_Point
from ChanConfig import CChanConfig from ChanConfig import CChanConfig
@@ -51,6 +51,7 @@ class ChanPY():
autype=AUTYPE.QFQ, autype=AUTYPE.QFQ,
) )
klu_list = [] klu_list = []
bsps = []
chanIn = True chanIn = True
#def __init__(self, dataframe): #def __init__(self, dataframe):
#self.klu_list = self.get_kl_data(dataframe) #self.klu_list = self.get_kl_data(dataframe)
@@ -266,7 +267,7 @@ class ChanPY():
#print(klu.time, bsps[-1], updown[-1], bi_list[-1].is_sure) #print(klu.time, bsps[-1], updown[-1], bi_list[-1].is_sure)
self.chanIn = False self.chanIn = False
else: else:
klu = CKLine_Unit(self.create_item_dict(self.get_last_item_data(dataframe), GetColumnNameFromFieldList(fields)), autofix=True) klu = self.get_last_klu(dataframe)
if self.last_kline.time < klu.time: if self.last_kline.time < klu.time:
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线 self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
self.last_kline = klu self.last_kline = klu
@@ -318,7 +319,6 @@ class ChanPY():
return bsps return bsps
def get_bsp_state(self, dataframe:DataFrame): def get_bsp_state(self, dataframe:DataFrame):
fields = "time,open,high,low,close,volume" fields = "time,open,high,low,close,volume"
bsps = []
if self.chanIn: if self.chanIn:
kl_data = self.get_kl_data(dataframe) kl_data = self.get_kl_data(dataframe)
bsp_list = [] bsp_list = []
@@ -337,66 +337,66 @@ class ChanPY():
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, lst[-2].fx, bi_list[-1].dir, bi_list[-1].is_sure,klu.close) #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, lst[-2].fx, bi_list[-1].dir, bi_list[-1].is_sure,klu.close)
if bsp_list_pre_len > len(bsp_list): if bsp_list_pre_len > len(bsp_list):
if abs(last_bsp_value) == 1: if abs(last_bsp_value) == 1:
bsps.append(1) self.bsps.append(1)
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, 98) #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, 98)
else: else:
bsps.append(99) self.bsps.append(99)
else: else:
if bsp_list_pre_len == len(bsp_list): if bsp_list_pre_len == len(bsp_list):
if klu.idx == last_bsp.klu.idx: if klu.idx == last_bsp.klu.idx:
if last_bsp.klu.idx - last_bsp_index > 3: if last_bsp.klu.idx - last_bsp_index > 3:
last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy) last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)
bsps.append(last_bsp_value) self.bsps.append(last_bsp_value)
else: else:
bsps.append(0) self.bsps.append(0)
last_bsp_index = last_bsp.klu.idx last_bsp_index = last_bsp.klu.idx
#if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2: #if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2:
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, "Knonw") #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, "Knonw")
else: else:
bsps.append(0) self.bsps.append(0)
else: else:
if klu.idx == last_bsp.klu.idx: if klu.idx == last_bsp.klu.idx:
if last_bsp.klu.idx - last_bsp_index > 3: if last_bsp.klu.idx - last_bsp_index > 3:
last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy) last_bsp_value = self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)
bsps.append(last_bsp_value) self.bsps.append(last_bsp_value)
else: else:
bsps.append(0) self.bsps.append(0)
last_bsp_index = last_bsp.klu.idx last_bsp_index = last_bsp.klu.idx
#if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2: #if abs(last_bsp_value) == 1 or abs(last_bsp_value) == 2:
#print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, "Knonw") #print(klu.time, klu.idx, last_bsp.klu.time, last_bsp.klu.idx, last_bsp_value, "Knonw")
else: else:
bsps.append(0) self.bsps.append(0)
else: else:
bsps.append(0) self.bsps.append(0)
bsp_list_pre_len = len(bsp_list) bsp_list_pre_len = len(bsp_list)
self.chanIn = False self.chanIn = False
else: else:
klu = CKLine_Unit(self.create_item_dict(self.get_last_item_data(dataframe), GetColumnNameFromFieldList(fields)), autofix=True) klu = self.get_last_klu(dataframe)
if self.last_kline.time < klu.time: if self.last_kline.time < klu.time:
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线 self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
self.last_kline = klu self.last_kline = klu
bsp_list = self.chan.get_bsp() bsp_list = self.chan.get_bsp()
last_bsp = bsp_list[-1] last_bsp = bsp_list[-1]
if last_bsp.klu.idx == klu.idx: if last_bsp.klu.idx == klu.idx:
bsps.append(self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy)) self.bsps.append(self.get_bsp_type(last_bsp.type[0], last_bsp.is_buy))
else: else:
bsps.append(0) self.bsps.append(0)
for index in range(0, len(bsps)): for index in range(0, len(self.bsps)):
if not (abs(bsps[index]) == 1 or abs(bsps[index]) == 2): if not (abs(self.bsps[index]) == 1 or abs(self.bsps[index]) == 2):
bsps[index] = 0 self.bsps[index] = 0
else: else:
if bsps[index] == 2: if self.bsps[index] == 2:
bsps[index] = 10 self.bsps[index] = 10
else: else:
if bsps[index] == -2: if self.bsps[index] == -2:
bsps[index] = -10 self.bsps[index] = -10
else: else:
if bsps[index] == 1: if self.bsps[index] == 1:
bsps[index] = 1 self.bsps[index] = 1
else: else:
if bsps[index] == -1: if self.bsps[index] == -1:
bsps[index] = -1 self.bsps[index] = -1
else: else:
bsps[index] = 0 self.bsps[index] = 0
return bsps return self.bsps
+52 -29
View File
@@ -49,7 +49,7 @@ class ChanLun_BTC_30(IStrategy):
"240": 0 "240": 0
} }
# 15m and 30m # 15m and 30m
minimal_roi = { minimal_roi_1 = {
"0": 0.1, "0": 0.1,
"240": 0.05, "240": 0.05,
"480": 0.03, "480": 0.03,
@@ -61,9 +61,9 @@ class ChanLun_BTC_30(IStrategy):
"2400": 0.025, "2400": 0.025,
"3600": 0 "3600": 0
} }
can_short = True can_short = False
lev = 1.0 lev = 2.0
stoploss = -0.3 stoploss = -0.5
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
@@ -105,13 +105,22 @@ class ChanLun_BTC_30(IStrategy):
dataframe_4h = self.add_indicators(dataframe_4h) dataframe_4h = self.add_indicators(dataframe_4h)
dataframe_1d = self.add_indicators(dataframe_1d) dataframe_1d = self.add_indicators(dataframe_1d)
#self.chan.plot_dual(dataframe_5, dataframe_30) #self.chan.plot_dual(dataframe_5, dataframe_30)
dataframe_5['chanpy_state'] = self.chanpy.get_bsp_state(dataframe_5) chanpy_state = self.chanpy.get_bsp_state(dataframe_5)
dataframe_5['chanpy_state'] = chanpy_state
state_list, fx_list = self.chan.get_klc_strength_list(dataframe_30) state_list, fx_list = self.chan.get_klc_strength_list(dataframe_30)
dataframe_30['state'] = state_list dataframe_30['state'] = state_list
dataframe_30['fx'] = fx_list dataframe_30['fx'] = fx_list
#bi_list_1 = self.chan.get_bi_list(dataframe)
#bi_list_5 = self.chan.get_bi_list(dataframe_5)
#bi_list_15 = self.chan.get_bi_list(dataframe_15)
#bi_list_30 = self.chan.get_bi_list(dataframe_30)
#bi_list_60 = self.chan.get_bi_list(dataframe_60)
if self.last_time + timedelta(minutes=1) < datetime.now(): if self.last_time + timedelta(minutes=1) < datetime.now():
#self.print_bi(bi_list_1)
#self.print_bi(bi_list_5)
#self.print_bi(bi_list_15)
#self.print_bi(bi_list_30)
#self.print_bi(bi_list_60)
print("-------------------------------------------------------------------------------") print("-------------------------------------------------------------------------------")
self.last_time = datetime.now() self.last_time = datetime.now()
dataframe = resampled_merge(dataframe, dataframe_5) dataframe = resampled_merge(dataframe, dataframe_5)
@@ -120,7 +129,11 @@ class ChanLun_BTC_30(IStrategy):
#dataframe = resampled_merge(dataframe, dataframe_60) #dataframe = resampled_merge(dataframe, dataframe_60)
#dataframe = resampled_merge(dataframe, dataframe_4h) #dataframe = resampled_merge(dataframe, dataframe_4h)
return dataframe return dataframe
def print_bi(self, bi_list):
if bi_list and len(bi_list) > 2:
bi1 = bi_list[-1]
bi2 = bi_list[-2]
print(bi1.start_time, bi1.end_time, bi1.dir, bi2.start_time, bi2.end_time, bi2.dir)
def add_indicators(self, df): def add_indicators(self, df):
fast = 8 fast = 8
slow = 16 slow = 16
@@ -167,16 +180,26 @@ class ChanLun_BTC_30(IStrategy):
return new_exitprice return new_exitprice
def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float, def confirm_trade_entry(self, pair: str, order_type: str, amount: float, rate: float,
time_in_force: str, current_time: datetime, entry_tag: str | None, time_in_force: str, current_time: datetime, entry_tag: str | None,
side: str, **kwargs) -> bool: side: str, **kwargs) -> bool:
if self.last_trade: if self.last_trade:
if self.last_trade.open_date + timedelta(minutes=30) > current_time: if self.last_trade.is_short:
return False if side == 'short':
if self.last_trade.open_date + timedelta(minutes=30) > current_time:
return False
else:
return True
else:
if side == 'long':
if self.last_trade.open_date + timedelta(minutes=30) > current_time:
return True
else:
return False
#if self.last_trade: #if self.last_trade:
#print(self.last_trade.open_date, current_time, self.last_trade.open_date + timedelta(minutes=self.time5)) #print(self.last_trade.open_date, current_time, self.last_trade.open_date + timedelta(minutes=self.time5))
return True return True
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, def custom_exit1(self, pair: str, trade: Trade, current_time: datetime, current_rate: float,
current_profit: float, **kwargs): current_profit: float, **kwargs):
#dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe) #dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
#last_candle = dataframe.iloc[-1].squeeze() #last_candle = dataframe.iloc[-1].squeeze()
@@ -220,8 +243,8 @@ class ChanLun_BTC_30(IStrategy):
#last_candle = dataframe.iloc[-1].squeeze() #last_candle = dataframe.iloc[-1].squeeze()
klc_list = self.chan.get_klc_list(resample_to_interval(dataframe, self.get_ticker_indicator() * 30)) klc_list = self.chan.get_klc_list(resample_to_interval(dataframe, self.get_ticker_indicator() * 30))
bi_list = self.chan.cal_bi_list(klc_list) bi_list = self.chan.cal_bi_list(klc_list)
last_high = klc_list[-3].high last_high = klc_list[-2].high
last_low = klc_list[-3].low last_low = klc_list[-2].low
if trade.is_short: if trade.is_short:
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side): if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
trade.set_custom_data(key="entry_candle_high", value=last_high) trade.set_custom_data(key="entry_candle_high", value=last_high)
@@ -234,15 +257,15 @@ class ChanLun_BTC_30(IStrategy):
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time30) state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time30)
fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time30) fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time30)
chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5) #chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5)
shift_time = self.time30*2 shift_time = self.time30
strength = 2.2 strength = 0.9
dataframe.loc[ dataframe.loc[
( (
#(dataframe['state'] == "-30") #(dataframe['state'] == "-30")
(dataframe[state_str].shift(shift_time) > strength) & (dataframe[state_str].shift(shift_time) > strength) &
(dataframe[fx_str].shift(shift_time) == -1) & (dataframe[fx_str].shift(shift_time) == -1)
(dataframe[chanpy_state_str].shift(shift_time+30) == 1) #(dataframe[chanpy_state_str].shift(shift_time+30) == 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")
@@ -253,8 +276,8 @@ class ChanLun_BTC_30(IStrategy):
( (
#(dataframe['state'] == "-30") #(dataframe['state'] == "-30")
(dataframe[state_str].shift(shift_time) > strength) & (dataframe[state_str].shift(shift_time) > strength) &
(dataframe[fx_str].shift(shift_time) == 1) & (dataframe[fx_str].shift(shift_time) == 1)
(dataframe[chanpy_state_str].shift(shift_time+30) == -1) #(dataframe[chanpy_state_str].shift(shift_time+30) == -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")
@@ -265,15 +288,15 @@ class ChanLun_BTC_30(IStrategy):
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame: def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time30) state_str = 'resample_{}_state'.format(self.get_ticker_indicator()*self.time30)
fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time30) fx_str = 'resample_{}_fx'.format(self.get_ticker_indicator()*self.time30)
chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5) #chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5)
shift_time = self.time30*2 shift_time = self.time30
strength = 2.2 strength = 0.9
dataframe.loc[ dataframe.loc[
( (
#(dataframe['state']== "30") #(dataframe['state']== "30")
(dataframe[state_str].shift(shift_time) > strength) & (dataframe[state_str].shift(shift_time) > strength) &
(dataframe[fx_str].shift(shift_time) == 1) & (dataframe[fx_str].shift(shift_time) == 1)
(dataframe[chanpy_state_str].shift(shift_time+30) == -1) #(dataframe[chanpy_state_str].shift(shift_time+30) == -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")
), ),
@@ -282,8 +305,8 @@ class ChanLun_BTC_30(IStrategy):
( (
#(dataframe['state']== "30") #(dataframe['state']== "30")
(dataframe[state_str].shift(shift_time) > strength) & (dataframe[state_str].shift(shift_time) > strength) &
(dataframe[fx_str].shift(shift_time) == -1) & (dataframe[fx_str].shift(shift_time) == -1)
(dataframe[chanpy_state_str].shift(shift_time+30) == 1) #(dataframe[chanpy_state_str].shift(shift_time+30) == 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")
), ),
+14
View File
@@ -299,6 +299,10 @@ def add_indicators(df):
# 处理Infinity和-Infinity值 # 处理Infinity和-Infinity值
df['volume_ratio'] = df['volume_ratio'].replace([float('inf'), float('-inf')], 1.0) df['volume_ratio'] = df['volume_ratio'].replace([float('inf'), float('-inf')], 1.0)
# 计算ATR (Average True Range) - 14周期
df['atr'] = ta.ATR(df, timeperiod=14)
df['atr'] = df['atr'].fillna(0)
return df return df
def calculate_macd(df): def calculate_macd(df):
@@ -568,6 +572,8 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta
'middle': element_current_df['element_bb_middle'].tolist(), 'middle': element_current_df['element_bb_middle'].tolist(),
'lower': element_current_df['element_bb_lower'].tolist() 'lower': element_current_df['element_bb_lower'].tolist()
}, },
# 添加次周期ATR数据
'element_atr': element_current_df['atr'].tolist(),
'element_klc_fx_info': [{ 'element_klc_fx_info': [{
'time': format_time_safely(point['time'], client_tz), 'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']), 'price': float(point['price']),
@@ -644,6 +650,8 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta
'middle': current_df['element_bb_middle'].tolist(), 'middle': current_df['element_bb_middle'].tolist(),
'lower': current_df['element_bb_lower'].tolist() 'lower': current_df['element_bb_lower'].tolist()
}, },
# 添加ATR数据
'atr': current_df['atr'].tolist(),
'klc_fx_info': [{ 'klc_fx_info': [{
'time': format_time_safely(point['time'], client_tz), 'time': format_time_safely(point['time'], client_tz),
'price': float(point['price']), 'price': float(point['price']),
@@ -680,6 +688,7 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta
'element_macd': {'macd': [], 'signal': [], 'histogram': []}, 'element_macd': {'macd': [], 'signal': [], 'histogram': []},
'element_bollinger': {'upper': [], 'middle': [], 'lower': []}, 'element_bollinger': {'upper': [], 'middle': [], 'lower': []},
'element_element_bollinger': {'upper': [], 'middle': [], 'lower': []}, 'element_element_bollinger': {'upper': [], 'middle': [], 'lower': []},
'element_atr': [],
'element_klc_fx_info': [], 'element_klc_fx_info': [],
'element_klu_fx_info': [] 'element_klu_fx_info': []
}) })
@@ -1116,6 +1125,8 @@ def analyze():
'middle': df['element_bb_middle'].tolist(), 'middle': df['element_bb_middle'].tolist(),
'lower': df['element_bb_lower'].tolist() 'lower': df['element_bb_lower'].tolist()
}, },
# 添加ATR数据
'atr': df['atr'].tolist(),
# 添加K线分型信息 # 添加K线分型信息
'klc_fx_info': [{ 'klc_fx_info': [{
'time': format_time_safely(point['time'], client_tz), 'time': format_time_safely(point['time'], client_tz),
@@ -1173,6 +1184,9 @@ def analyze():
'lower': element_df['element_bb_lower'].tolist() 'lower': element_df['element_bb_lower'].tolist()
} }
# 添加小周期ATR数据
result['element_atr'] = element_df['atr'].tolist()
result['element_bi_list'] = [{ result['element_bi_list'] = [{
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(), 'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None, 'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
+337 -40
View File
@@ -407,6 +407,10 @@
<input class="form-check-input" type="checkbox" id="showMainBollinger"> <input class="form-check-input" type="checkbox" id="showMainBollinger">
<label class="form-check-label" for="showMainBollinger">布林带</label> <label class="form-check-label" for="showMainBollinger">布林带</label>
</div> </div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMacd" checked>
<label class="form-check-label" for="showMacd">MACD</label>
</div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-2">
<label class="form-label me-3 mb-0">次周期:</label> <label class="form-label me-3 mb-0">次周期:</label>
@@ -741,6 +745,7 @@
candleSeries: null, candleSeries: null,
lineSeries: null, lineSeries: null,
volumeSeries: null, volumeSeries: null,
atrLineSeries: null,
macdLineSeries: null, macdLineSeries: null,
signalLineSeries: null, signalLineSeries: null,
histogramSeries: null, histogramSeries: null,
@@ -1387,7 +1392,7 @@
container.style.height = '100%'; container.style.height = '100%';
// 是否显示MACD // 是否显示MACD
const showMacd = true; const showMacd = $('#showMacd').is(':checked');
const showOriginalKline = $('#showOriginalKline').is(':checked'); const showOriginalKline = $('#showOriginalKline').is(':checked');
// 创建主图容器 // 创建主图容器
@@ -1406,31 +1411,46 @@
volumeChartContainer.style.right = '0'; volumeChartContainer.style.right = '0';
volumeChartContainer.style.borderTop = '1px solid #e0e0e0'; volumeChartContainer.style.borderTop = '1px solid #e0e0e0';
// 添加ATR图表容器
const atrChartContainer = document.createElement('div');
atrChartContainer.style.width = '100%';
atrChartContainer.style.position = 'absolute';
atrChartContainer.style.left = '0';
atrChartContainer.style.right = '0';
atrChartContainer.style.borderTop = '1px solid #e0e0e0';
// 如果需要显示MACD,创建MACD容器 // 如果需要显示MACD,创建MACD容器
let macdChartContainer = null; let macdChartContainer = null;
if (showMacd) { if (showMacd) {
// 设置各图表高度 - 为个图表分配合理比例,主图表适度增加高度 // 设置各图表高度 - 为个图表分配合理比例
mainChartContainer.style.height = '55%'; // 主图占55%(约385px mainChartContainer.style.height = '45%'; // 主图占45%
volumeChartContainer.style.top = '55%'; volumeChartContainer.style.top = '45%';
volumeChartContainer.style.height = '22.5%'; // 成交量图占22.5%(约157.5px volumeChartContainer.style.height = '20%'; // 成交量图占20%
atrChartContainer.style.top = '65%'; // ATR图从65%位置开始
atrChartContainer.style.height = '17.5%'; // ATR图占17.5%
macdChartContainer = document.createElement('div'); macdChartContainer = document.createElement('div');
macdChartContainer.style.width = '100%'; macdChartContainer.style.width = '100%';
macdChartContainer.style.height = '22.5%'; // MACD图占22.5%(约157.5px macdChartContainer.style.height = '17.5%'; // MACD图占17.5%
macdChartContainer.style.position = 'absolute'; macdChartContainer.style.position = 'absolute';
macdChartContainer.style.top = '77.5%'; // 从77.5%位置开始 macdChartContainer.style.top = '82.5%'; // 从82.5%位置开始
macdChartContainer.style.left = '0'; macdChartContainer.style.left = '0';
macdChartContainer.style.right = '0'; macdChartContainer.style.right = '0';
macdChartContainer.style.borderTop = '1px solid #e0e0e0'; macdChartContainer.style.borderTop = '1px solid #e0e0e0';
} else { } else {
// 不显示MACD时的高度 - 主图成交量图分配 // 不显示MACD时的高度 - 主图成交量图和ATR图分配
mainChartContainer.style.height = '72%'; // 主图占72%(约504px mainChartContainer.style.height = '55%'; // 主图占55%
volumeChartContainer.style.top = '72%'; volumeChartContainer.style.top = '55%';
volumeChartContainer.style.height = '28%'; // 成交量图占28%(约196px volumeChartContainer.style.height = '22.5%'; // 成交量图占22.5%
atrChartContainer.style.top = '77.5%'; // ATR图从77.5%位置开始
atrChartContainer.style.height = '22.5%'; // ATR图占22.5%
} }
container.appendChild(mainChartContainer); container.appendChild(mainChartContainer);
container.appendChild(volumeChartContainer); container.appendChild(volumeChartContainer);
container.appendChild(atrChartContainer);
if (showMacd) container.appendChild(macdChartContainer); if (showMacd) container.appendChild(macdChartContainer);
// 防止同步过程中的无限循环 // 防止同步过程中的无限循环
@@ -1444,6 +1464,8 @@
chartHeight = mainChartContainer.clientHeight; chartHeight = mainChartContainer.clientHeight;
} else if (chartType === 'volume') { } else if (chartType === 'volume') {
chartHeight = volumeChartContainer.clientHeight; chartHeight = volumeChartContainer.clientHeight;
} else if (chartType === 'atr') {
chartHeight = atrChartContainer.clientHeight;
} else if (chartType === 'macd') { } else if (chartType === 'macd') {
chartHeight = macdChartContainer ? macdChartContainer.clientHeight : 0; chartHeight = macdChartContainer ? macdChartContainer.clientHeight : 0;
} else { } else {
@@ -1556,6 +1578,8 @@
visible: showTimeScale, visible: showTimeScale,
borderColor: '#ddd', borderColor: '#ddd',
barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10, barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10,
// 确保所有图表使用相同的边距设置
rightOffset: 12,
// 移除可能影响拖动的固定边缘设置 // 移除可能影响拖动的固定边缘设置
// fixLeftEdge: true, // fixLeftEdge: true,
// fixRightEdge: true, // fixRightEdge: true,
@@ -1593,6 +1617,9 @@
// 创建成交量图表 - 只显示底部的时间轴 // 创建成交量图表 - 只显示底部的时间轴
const volumeChart = LightweightCharts.createChart(volumeChartContainer, createChartOptions(false, 'volume')); const volumeChart = LightweightCharts.createChart(volumeChartContainer, createChartOptions(false, 'volume'));
// 创建ATR图表
const atrChart = LightweightCharts.createChart(atrChartContainer, createChartOptions(false, 'atr'));
// 创建MACD图表(如果需要) // 创建MACD图表(如果需要)
let macdChart = null; let macdChart = null;
if (showMacd) { if (showMacd) {
@@ -1663,6 +1690,59 @@
volumeSeries.setData(volumes); volumeSeries.setData(volumes);
tvWidget.series.volumeSeries = volumeSeries; tvWidget.series.volumeSeries = volumeSeries;
// 添加ATR图表
const atrLineSeries = atrChart.addLineSeries({
color: '#FF9800',
lineWidth: 2,
title: 'ATR',
lastValueVisible: false,
priceLineVisible: false,
});
// 准备ATR数据
const atrData = [];
// 使用与K线数据相同的数据源来确保时间对齐
const atrKlineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
const atrDataSource = useElementPeriod ?
(currentData.element_atr || currentData.atr) : // 如果有次周期ATR数据则使用,否则使用主周期
currentData.atr; // 主周期使用主周期ATR数据
console.log('ATR数据源选择:', useElementPeriod ? '次周期' : '主周期');
console.log('ATR数据长度:', atrDataSource ? atrDataSource.length : 0);
console.log('K线数据长度:', atrKlineDataSource ? atrKlineDataSource.length : 0);
if (atrDataSource && Array.isArray(atrDataSource) && atrKlineDataSource && Array.isArray(atrKlineDataSource)) {
// 关键修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据
for (let i = 0; i < atrKlineDataSource.length; i++) {
const kline = atrKlineDataSource[i];
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
// 为每个时间点都添加数据以保持时间轴对齐,但ATR为0时不显示
if (atrDataSource[i] !== undefined) {
if (atrDataSource[i] > 0) {
// ATR有效值,正常显示
atrData.push({
time: timestamp,
value: atrDataSource[i]
});
} else {
// ATR为0,添加时间点但不显示线条(使用undefined作为value
atrData.push({
time: timestamp,
value: undefined
});
}
}
}
console.log('处理后的ATR数据点数:', atrData.length);
console.log('ATR数据样本:', atrData.slice(0, 5));
}
console.log('处理后的ATR数据点数:', atrData.length);
atrLineSeries.setData(atrData);
tvWidget.series.atrLineSeries = atrLineSeries;
// 添加MACD图表 - 始终使用主K线周期的MACD数据 // 添加MACD图表 - 始终使用主K线周期的MACD数据
if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) { if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
// 创建MACD线 // 创建MACD线
@@ -1758,7 +1838,8 @@
syncInProgress = true; syncInProgress = true;
console.log('🚀 开始同步图表,来源:', console.log('🚀 开始同步图表,来源:',
sourceChart === mainChart ? '主图' : sourceChart === mainChart ? '主图' :
sourceChart === volumeChart ? '成交量图' : 'MACD图'); sourceChart === volumeChart ? '成交量图' :
sourceChart === atrChart ? 'ATR图' : 'MACD图');
try { try {
if (sourceChart && sourceChart.timeScale) { if (sourceChart && sourceChart.timeScale) {
@@ -1787,6 +1868,16 @@
} }
} }
// 同步ATR图
if (sourceChart !== atrChart && atrChart && atrChart.timeScale) {
try {
atrChart.timeScale().setVisibleLogicalRange(logicalRange);
console.log('✅ ATR图同步完成');
} catch (e) {
console.error('❌ ATR图同步失败:', e);
}
}
// 同步MACD图 // 同步MACD图
if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) { if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) {
try { try {
@@ -1822,6 +1913,7 @@
let localDragStates = { let localDragStates = {
main: false, main: false,
volume: false, volume: false,
atr: false,
macd: false macd: false
}; };
@@ -1838,10 +1930,15 @@
// 为每个图表添加事件监听 // 为每个图表添加事件监听
const addChartSyncEvents = (chartContainer, chart) => { const addChartSyncEvents = (chartContainer, chart) => {
console.log('为图表添加同步事件监听:', chart === mainChart ? '主图' : chart === volumeChart ? '成交量图' : 'MACD图'); console.log('为图表添加同步事件监听:',
chart === mainChart ? '主图' :
chart === volumeChart ? '成交量图' :
chart === atrChart ? 'ATR图' : 'MACD图');
// 确定当前图表类型 // 确定当前图表类型
const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : 'macd'; const chartType = chart === mainChart ? 'main' :
chart === volumeChart ? 'volume' :
chart === atrChart ? 'atr' : 'macd';
// 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法) // 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法)
chart.timeScale().subscribeVisibleTimeRangeChange(() => { chart.timeScale().subscribeVisibleTimeRangeChange(() => {
@@ -1897,6 +1994,7 @@
// 添加事件监听 // 添加事件监听
addChartSyncEvents(mainChartContainer, mainChart); addChartSyncEvents(mainChartContainer, mainChart);
addChartSyncEvents(volumeChartContainer, volumeChart); addChartSyncEvents(volumeChartContainer, volumeChart);
addChartSyncEvents(atrChartContainer, atrChart);
if (showMacd && macdChart) { if (showMacd && macdChart) {
addChartSyncEvents(macdChartContainer, macdChart); addChartSyncEvents(macdChartContainer, macdChart);
} }
@@ -1915,6 +2013,12 @@
height: volumeChartContainer.clientHeight height: volumeChartContainer.clientHeight
}); });
// 调整ATR图大小
atrChart.applyOptions({
width: atrChartContainer.clientWidth,
height: atrChartContainer.clientHeight
});
// 调整MACD图大小 // 调整MACD图大小
if (showMacd && macdChart && macdChartContainer) { if (showMacd && macdChart && macdChartContainer) {
macdChart.applyOptions({ macdChart.applyOptions({
@@ -2914,6 +3018,8 @@
// 清除之前的十字线标记 // 清除之前的十字线标记
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
existingVolumeLines.forEach(line => line.remove()); existingVolumeLines.forEach(line => line.remove());
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
existingAtrLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
existingMacdLines.forEach(line => line.remove()); existingMacdLines.forEach(line => line.remove());
@@ -2941,6 +3047,26 @@
document.body.appendChild(volumeLine); document.body.appendChild(volumeLine);
} }
// 在ATR图上绘制垂直线
if (atrChart && atrChartContainer) {
const atrTimeCoordinate = atrChart.timeScale().timeToCoordinate(param.time);
if (atrTimeCoordinate !== null) {
const atrChartRect = atrChartContainer.getBoundingClientRect();
const atrLine = document.createElement('div');
atrLine.className = 'atr-crosshair-line';
atrLine.style.position = 'fixed'; // 改为fixed定位
atrLine.style.left = (atrChartRect.left + atrTimeCoordinate) + 'px';
atrLine.style.top = atrChartRect.top + 'px';
atrLine.style.width = '1px';
atrLine.style.height = atrChartRect.height + 'px';
atrLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
atrLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
atrLine.style.pointerEvents = 'none';
atrLine.style.zIndex = '1000';
document.body.appendChild(atrLine);
}
}
// 如果有MACD图,也在MACD图上绘制垂直线 // 如果有MACD图,也在MACD图上绘制垂直线
if (showMacd && macdChart && macdChartContainer) { if (showMacd && macdChart && macdChartContainer) {
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time); const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
@@ -2969,6 +3095,8 @@
try { try {
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
existingVolumeLines.forEach(line => line.remove()); existingVolumeLines.forEach(line => line.remove());
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
existingAtrLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
existingMacdLines.forEach(line => line.remove()); existingMacdLines.forEach(line => line.remove());
} catch (e) { } catch (e) {
@@ -3314,7 +3442,7 @@
// 构建显示文本,包含分型类型和强度信息 // 构建显示文本,包含分型类型和强度信息
let displayText = `${fx.fx_strength.toFixed(1)}`; let displayText = `${fx.fx_strength.toFixed(1)}`;
if (fx.fx_strength < 2.0) { // 降低阈值让更多分型显示 if (fx.fx_strength < 1.0) { // 降低阈值让更多分型显示
displayText = fx.fx_strength >= 1.5 ? '' : '' // 0.8以上显示点,0.8以下不显示文本 displayText = fx.fx_strength >= 1.5 ? '' : '' // 0.8以上显示点,0.8以下不显示文本
} }
@@ -3399,7 +3527,7 @@
let strengthColor = fx.is_bottom ? '#11116B' : '#222222'; // 底分型用珊瑚红,顶分型用薄荷绿 let strengthColor = fx.is_bottom ? '#11116B' : '#222222'; // 底分型用珊瑚红,顶分型用薄荷绿
let displayText = `${fx.fx_strength.toFixed(1)}`; let displayText = `${fx.fx_strength.toFixed(1)}`;
// 构建小周期分型显示文本 // 构建小周期分型显示文本
if (fx.fx_strength < 2.0){ // 调整小周期阈值 if (fx.fx_strength < 1.0){ // 调整小周期阈值
displayText = fx.fx_strength >= 0.6 ? '' : '' // 0.6以上显示点 displayText = fx.fx_strength >= 0.6 ? '' : '' // 0.6以上显示点
} }
@@ -3544,21 +3672,82 @@
} }
} }
// 调整所有图表以适应数据 // 同步所有图表的时间轴配置
const syncTimeScaleSettings = () => {
// 获取主图表的时间轴设置
const mainTimeScale = mainChart.timeScale();
const baseOptions = {
timeVisible: true,
secondsVisible: false,
borderColor: '#ddd',
barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10,
rightOffset: 12,
lockVisibleTimeRangeOnResize: true,
// 关键:确保所有图表边缘行为完全一致
fixLeftEdge: false,
fixRightEdge: false,
// 确保时间刻度行为一致
ticksVisible: true,
minimumHeight: 0,
};
console.log('🔧 同步时间轴设置:', baseOptions);
// 应用相同的设置到所有图表
mainChart.timeScale().applyOptions(baseOptions);
volumeChart.timeScale().applyOptions(baseOptions);
atrChart.timeScale().applyOptions(baseOptions);
if (showMacd && macdChart) {
macdChart.timeScale().applyOptions(baseOptions);
}
};
// 首先同步时间轴设置
syncTimeScaleSettings();
// 然后让主图表适应内容
mainChart.timeScale().fitContent(); mainChart.timeScale().fitContent();
volumeChart.timeScale().fitContent();
if (showMacd && macdChart) { // 立即同步其他图表到主图表的范围
macdChart.timeScale().fitContent(); setTimeout(() => {
} const visibleRange = mainChart.timeScale().getVisibleRange();
if (visibleRange) {
console.log('🔧 同步可见范围:', visibleRange);
volumeChart.timeScale().setVisibleRange(visibleRange);
atrChart.timeScale().setVisibleRange(visibleRange);
if (showMacd && macdChart) {
macdChart.timeScale().setVisibleRange(visibleRange);
}
console.log('🔧 时间轴同步完成');
}
}, 50);
// 保存图表对象 // 保存图表对象
tvWidget.mainChart = mainChart; tvWidget.mainChart = mainChart;
tvWidget.volumeChart = volumeChart; tvWidget.volumeChart = volumeChart;
tvWidget.atrChart = atrChart;
tvWidget.macdChart = macdChart; tvWidget.macdChart = macdChart;
tvWidget.state.isInitialized = true; tvWidget.state.isInitialized = true;
// 绑定同步事件 // 绑定同步事件
bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, mainChart, volumeChart, macdChart, showMacd); bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd);
// 最终确保所有图表时间轴对齐
setTimeout(() => {
const visibleRange = mainChart.timeScale().getVisibleRange();
if (visibleRange) {
console.log('🔧 最终同步可见范围:', visibleRange);
// 强制重新设置所有图表的可见范围
volumeChart.timeScale().setVisibleRange(visibleRange);
atrChart.timeScale().setVisibleRange(visibleRange);
if (showMacd && macdChart) {
macdChart.timeScale().setVisibleRange(visibleRange);
}
console.log('🔧 最终时间轴对齐完成');
}
}, 150);
// 只有在时间输入框都为空时才设置图表默认时间范围 // 只有在时间输入框都为空时才设置图表默认时间范围
if (!$('#start_time').val() && !$('#end_time').val()) { if (!$('#start_time').val() && !$('#end_time').val()) {
@@ -3566,7 +3755,7 @@
} }
// 添加买卖点提示 // 添加买卖点提示
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, macdChartContainer, volumeChart, macdChart, showMacd); setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, volumeChart, atrChart, macdChart, showMacd);
// 显示买卖点 // 显示买卖点
if ($('#showTradePoints').is(':checked')) { if ($('#showTradePoints').is(':checked')) {
@@ -3674,8 +3863,46 @@
tvWidget.series.volumeSeries.setData(volumes); tvWidget.series.volumeSeries.setData(volumes);
} }
// 更新ATR数据
if (tvWidget.series.atrLineSeries) {
const atrData = [];
const atrDataSource = useElementPeriod ?
(currentData.element_atr || currentData.atr) :
currentData.atr;
if (atrDataSource && Array.isArray(atrDataSource)) {
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
// 修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据
for (let i = 0; i < klineDataSource.length; i++) {
const kline = klineDataSource[i];
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
// 为每个时间点都添加数据以保持时间轴对齐,但ATR为0时不显示
if (atrDataSource[i] !== undefined) {
if (atrDataSource[i] > 0) {
// ATR有效值,正常显示
atrData.push({
time: timestamp,
value: atrDataSource[i]
});
} else {
// ATR为0,添加时间点但不显示线条(使用undefined作为value
atrData.push({
time: timestamp,
value: undefined
});
}
}
}
console.log('🔄 增量更新ATR数据点数:', atrData.length);
}
tvWidget.series.atrLineSeries.setData(atrData);
}
// 更新MACD数据 // 更新MACD数据
if (true && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data) && tvWidget.series.macdLineSeries) { if (tvWidget.series.macdLineSeries && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
// 提取MACD数据 // 提取MACD数据
const macdData = []; const macdData = [];
const signalData = []; const signalData = [];
@@ -3714,16 +3941,20 @@
// 重新显示笔、线段和中枢等图形 // 重新显示笔、线段和中枢等图形
redrawFractalElements(); redrawFractalElements();
// 恢复之前的可视范围 // 恢复之前的可视范围 - 优先使用visibleRange以确保时间轴对齐
if (tvWidget.mainChart) { if (tvWidget.mainChart) {
if (tvWidget.state.logicalRange) { if (tvWidget.state.visibleRange) {
tvWidget.mainChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange); console.log('🔄 恢复可见范围:', tvWidget.state.visibleRange);
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
} else if (tvWidget.state.visibleRange) {
tvWidget.mainChart.timeScale().setVisibleRange(tvWidget.state.visibleRange); tvWidget.mainChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(tvWidget.state.visibleRange); if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(tvWidget.state.visibleRange); if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleRange(tvWidget.state.visibleRange);
} else if (tvWidget.state.logicalRange) {
console.log('🔄 恢复逻辑范围:', tvWidget.state.logicalRange);
tvWidget.mainChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
} }
} }
@@ -3735,7 +3966,7 @@
} }
} }
function bindSyncEvents(mainChartContainer, volumeChartContainer, macdChartContainer, mainChart, volumeChart, macdChart, showMacd) { function bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, mainChart, volumeChart, atrChart, macdChart, showMacd) {
// 防止同步过程中的无限循环 // 防止同步过程中的无限循环
let syncInProgress = false; let syncInProgress = false;
@@ -3743,6 +3974,7 @@
let localDragStates = { let localDragStates = {
main: false, main: false,
volume: false, volume: false,
atr: false,
macd: false macd: false
}; };
@@ -3757,7 +3989,8 @@
syncInProgress = true; syncInProgress = true;
console.log('🚀 开始同步图表,来源:', console.log('🚀 开始同步图表,来源:',
sourceChart === mainChart ? '主图' : sourceChart === mainChart ? '主图' :
sourceChart === volumeChart ? '成交量图' : 'MACD图'); sourceChart === volumeChart ? '成交量图' :
sourceChart === atrChart ? 'ATR图' : 'MACD图');
try { try {
if (sourceChart && sourceChart.timeScale) { if (sourceChart && sourceChart.timeScale) {
@@ -3786,6 +4019,16 @@
} }
} }
// 同步ATR图
if (sourceChart !== atrChart && atrChart && atrChart.timeScale) {
try {
atrChart.timeScale().setVisibleLogicalRange(logicalRange);
console.log('✅ ATR图同步完成');
} catch (e) {
console.error('❌ ATR图同步失败:', e);
}
}
// 同步MACD图 // 同步MACD图
if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) { if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) {
try { try {
@@ -3799,6 +4042,14 @@
// 保存当前的可见范围到全局状态 // 保存当前的可见范围到全局状态
if (tvWidget && tvWidget.state) { if (tvWidget && tvWidget.state) {
tvWidget.state.logicalRange = logicalRange; tvWidget.state.logicalRange = logicalRange;
// 同时保存可见范围以确保精确对齐
try {
const visibleRange = sourceChart.timeScale().getVisibleRange();
tvWidget.state.visibleRange = visibleRange;
console.log('💾 保存状态 - 逻辑范围:', logicalRange, '可见范围:', visibleRange);
} catch (e) {
console.warn('⚠️ 保存可见范围失败:', e);
}
} }
} else { } else {
console.warn('⚠️ 无效的逻辑范围:', logicalRange); console.warn('⚠️ 无效的逻辑范围:', logicalRange);
@@ -3819,10 +4070,15 @@
// 为每个图表添加事件监听 // 为每个图表添加事件监听
const addChartSyncEvents = (chartContainer, chart) => { const addChartSyncEvents = (chartContainer, chart) => {
console.log('为图表添加同步事件监听:', chart === mainChart ? '主图' : chart === volumeChart ? '成交量图' : 'MACD图'); console.log('为图表添加同步事件监听:',
chart === mainChart ? '主图' :
chart === volumeChart ? '成交量图' :
chart === atrChart ? 'ATR图' : 'MACD图');
// 确定当前图表类型 // 确定当前图表类型
const chartType = chart === mainChart ? 'main' : chart === volumeChart ? 'volume' : 'macd'; const chartType = chart === mainChart ? 'main' :
chart === volumeChart ? 'volume' :
chart === atrChart ? 'atr' : 'macd';
// 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法) // 使用LightweightCharts内置的时间范围变化事件(这是最可靠的方法)
chart.timeScale().subscribeVisibleTimeRangeChange(() => { chart.timeScale().subscribeVisibleTimeRangeChange(() => {
@@ -3882,6 +4138,9 @@
if (volumeChartContainer && volumeChart) { if (volumeChartContainer && volumeChart) {
addChartSyncEvents(volumeChartContainer, volumeChart); addChartSyncEvents(volumeChartContainer, volumeChart);
} }
if (atrChartContainer && atrChart) {
addChartSyncEvents(atrChartContainer, atrChart);
}
if (showMacd && macdChartContainer && macdChart) { if (showMacd && macdChartContainer && macdChart) {
addChartSyncEvents(macdChartContainer, macdChart); addChartSyncEvents(macdChartContainer, macdChart);
} }
@@ -3904,6 +4163,14 @@
}); });
} }
// 调整ATR图大小
if (atrChart && atrChartContainer) {
atrChart.applyOptions({
width: atrChartContainer.clientWidth,
height: atrChartContainer.clientHeight
});
}
// 调整MACD图大小 // 调整MACD图大小
if (showMacd && macdChart && macdChartContainer) { if (showMacd && macdChart && macdChartContainer) {
macdChart.applyOptions({ macdChart.applyOptions({
@@ -3921,7 +4188,7 @@
}); });
} }
function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, macdChartContainer, volumeChart, macdChart, showMacd) { function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, volumeChart, atrChart, macdChart, showMacd) {
// 调试变量 // 调试变量
window.debugMode = true; window.debugMode = true;
@@ -3953,6 +4220,8 @@
// 清除之前的十字线标记 // 清除之前的十字线标记
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
existingVolumeLines.forEach(line => line.remove()); existingVolumeLines.forEach(line => line.remove());
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
existingAtrLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
existingMacdLines.forEach(line => line.remove()); existingMacdLines.forEach(line => line.remove());
@@ -3980,6 +4249,26 @@
document.body.appendChild(volumeLine); document.body.appendChild(volumeLine);
} }
// 在ATR图上绘制垂直线
if (atrChart && atrChartContainer) {
const atrTimeCoordinate = atrChart.timeScale().timeToCoordinate(param.time);
if (atrTimeCoordinate !== null) {
const atrChartRect = atrChartContainer.getBoundingClientRect();
const atrLine = document.createElement('div');
atrLine.className = 'atr-crosshair-line';
atrLine.style.position = 'fixed'; // 改为fixed定位
atrLine.style.left = (atrChartRect.left + atrTimeCoordinate) + 'px';
atrLine.style.top = atrChartRect.top + 'px';
atrLine.style.width = '1px';
atrLine.style.height = atrChartRect.height + 'px';
atrLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
atrLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
atrLine.style.pointerEvents = 'none';
atrLine.style.zIndex = '1000';
document.body.appendChild(atrLine);
}
}
// 如果有MACD图,也在MACD图上绘制垂直线 // 如果有MACD图,也在MACD图上绘制垂直线
if (showMacd && macdChart && macdChartContainer) { if (showMacd && macdChart && macdChartContainer) {
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time); const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
@@ -4008,6 +4297,8 @@
try { try {
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
existingVolumeLines.forEach(line => line.remove()); existingVolumeLines.forEach(line => line.remove());
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
existingAtrLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
existingMacdLines.forEach(line => line.remove()); existingMacdLines.forEach(line => line.remove());
} catch (e) { } catch (e) {
@@ -4754,12 +5045,14 @@
// 销毁主图表及其关联的线系列 // 销毁主图表及其关联的线系列
tvWidget.mainChart = null; tvWidget.mainChart = null;
tvWidget.volumeChart = null; tvWidget.volumeChart = null;
tvWidget.atrChart = null;
tvWidget.macdChart = null; tvWidget.macdChart = null;
// 重置系列数据 // 重置系列数据
tvWidget.series = { tvWidget.series = {
candleSeries: null, candleSeries: null,
lineSeries: null, lineSeries: null,
volumeSeries: null, volumeSeries: null,
atrLineSeries: null,
macdLineSeries: null, macdLineSeries: null,
signalLineSeries: null, signalLineSeries: null,
histogramSeries: null, histogramSeries: null,
@@ -4809,6 +5102,8 @@
// 清除所有十字线延长线,防止它们跟着页面滚动 // 清除所有十字线延长线,防止它们跟着页面滚动
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
existingVolumeLines.forEach(line => line.remove()); existingVolumeLines.forEach(line => line.remove());
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
existingAtrLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
existingMacdLines.forEach(line => line.remove()); existingMacdLines.forEach(line => line.remove());
} catch (e) { } catch (e) {
@@ -5020,7 +5315,7 @@
const logicalRange = mainChart.timeScale().getVisibleLogicalRange(); const logicalRange = mainChart.timeScale().getVisibleLogicalRange();
// 获取当前图表设置 // 获取当前图表设置
const showMacd = true; const showMacd = $('#showMacd').is(':checked');
const showOriginalKline = $('#showOriginalKline').is(':checked'); const showOriginalKline = $('#showOriginalKline').is(':checked');
const showBi = $('#showMainBi').is(':checked'); const showBi = $('#showMainBi').is(':checked');
const showSeg = $('#showMainSeg').is(':checked'); const showSeg = $('#showMainSeg').is(':checked');
@@ -5264,7 +5559,7 @@
'showMainZs': $('#showMainZs').is(':checked'), 'showMainZs': $('#showMainZs').is(':checked'),
'showMainUncompletedZs': $('#showMainUncompletedZs').is(':checked'), 'showMainUncompletedZs': $('#showMainUncompletedZs').is(':checked'),
'showVolume': false, 'showVolume': false,
'showMacd': true, 'showMacd': $('#showMacd').is(':checked'),
'showKlcFxType': $('#showKlcFxType').is(':checked'), 'showKlcFxType': $('#showKlcFxType').is(':checked'),
'showKluFxType': $('#showKluFxType').is(':checked'), 'showKluFxType': $('#showKluFxType').is(':checked'),
'showElementKlcFxType': $('#showElementKlcFxType').is(':checked'), 'showElementKlcFxType': $('#showElementKlcFxType').is(':checked'),
@@ -6335,9 +6630,11 @@
try { try {
// 清除所有十字线延长线,防止它们跟着页面滚动 // 清除所有十字线延长线,防止它们跟着页面滚动
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line'); const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
existingVolumeLines.forEach(line => line.remove()); existingVolumeLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line'); const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
existingMacdLines.forEach(line => line.remove()); existingAtrLines.forEach(line => line.remove());
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
existingMacdLines.forEach(line => line.remove());
} catch (e) { } catch (e) {
console.debug('清除滚动中的十字线时出错:', e); console.debug('清除滚动中的十字线时出错:', e);
} }