Add ATR
This commit is contained in:
+34
-259
@@ -160,7 +160,7 @@ class ChanKLC():
|
||||
klu_list.append(klc3.klus)
|
||||
gap = klc3.end_klu.index - klc1.start_klu.index + 1
|
||||
if gap < 4:
|
||||
pass
|
||||
pass
|
||||
return gap
|
||||
def get_feature_data(self):
|
||||
features = dict()
|
||||
@@ -1162,269 +1162,44 @@ class ChanKLC():
|
||||
return features
|
||||
|
||||
def cal_fx_strength(self, klc_offset=2):
|
||||
"""
|
||||
用self.pre和self.next实现分型强弱判断
|
||||
|
||||
核心缠论原理:
|
||||
- 强分型:出现在笔的末端,能够终结当前笔,标志着趋势转折
|
||||
- 弱分型:出现在笔的中间,是中继性质,笔还会继续延伸
|
||||
|
||||
返回值:
|
||||
3: 极强分型(笔终结+强确认)
|
||||
2: 强分型(笔终结)
|
||||
1: 偏强分型(可能终结笔)
|
||||
0: 中性分型
|
||||
-1: 偏弱分型(中继特征明显)
|
||||
-2: 弱分型(明显中继)
|
||||
-3: 极弱分型(无效分型)
|
||||
"""
|
||||
# 检查是否为分型,且有前后K线数据
|
||||
if self.fx == Chan_FX_TYPE.UNKNOWN:
|
||||
strength = 0
|
||||
if self.fx == Chan_FX_TYPE.UNKNOWN or not self.pre or not self.next:
|
||||
return 0
|
||||
if not self.pre or not self.next:
|
||||
return 100
|
||||
# === 核心判断:分型在笔中的位置 ===
|
||||
|
||||
# 1. 检查这个分型是否能够终结当前笔
|
||||
is_bi_end = self._check_if_bi_ending_fx(klc_offset)
|
||||
|
||||
# 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
|
||||
#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:
|
||||
if self.pre and self.next:
|
||||
klc1 = self.pre
|
||||
klc2 = self
|
||||
klc3 = self.next
|
||||
if self.bi:
|
||||
if self.bi.dir == Chan_BI_DIR.UP and self.fx == Chan_FX_TYPE.BOTTOM:
|
||||
return 0
|
||||
if self.bi.dir == Chan_BI_DIR.DOWN and self.fx == Chan_FX_TYPE.TOP:
|
||||
return 0
|
||||
if self.bi.dir == Chan_BI_DIR.UP:
|
||||
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:
|
||||
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:
|
||||
strength += self.check_bi_end(self.bi)
|
||||
elif self.klc_fx_type == Chan_KLC_FX.BOTTOM2:
|
||||
strength += self.check_bi_end(self.bi)
|
||||
else:
|
||||
break
|
||||
|
||||
if len(subsequent_klcs) < 2:
|
||||
return 0
|
||||
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 0
|
||||
return strength
|
||||
def check_bi_end(self, bi):
|
||||
if bi.dir == Chan_BI_DIR.UP:
|
||||
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_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):
|
||||
else:
|
||||
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
|
||||
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)
|
||||
|
||||
# 检查第三根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))
|
||||
|
||||
return
|
||||
def calculate_fx_strength(self):
|
||||
"""
|
||||
基于专业缠论理论的分型强度评估体系
|
||||
|
||||
+6
-6
@@ -754,7 +754,7 @@ class ChanLun():
|
||||
last_top = klc
|
||||
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 1")
|
||||
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_state("10")
|
||||
bi_list[-1].add_klc(klc)
|
||||
@@ -772,7 +772,7 @@ class ChanLun():
|
||||
if last_top.index + 4 < klc.index and len(bi_list) > 1:
|
||||
pre_last_bi = bi_list[-2]
|
||||
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)
|
||||
bi_list.remove(last_bi)
|
||||
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")
|
||||
#klc.set_state("10")
|
||||
#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)
|
||||
klc.set_bi(bi_list[-1])
|
||||
else:
|
||||
@@ -804,7 +804,7 @@ class ChanLun():
|
||||
bi.add_klc(klc)
|
||||
bi_list.append(bi)
|
||||
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_state('30')
|
||||
bi_list[-1].add_klc(klc)
|
||||
@@ -892,7 +892,7 @@ class ChanLun():
|
||||
if last_bottom.index + 4 < klc.index and len(bi_list) > 1:
|
||||
pre_last_bi = bi_list[-2]
|
||||
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)
|
||||
bi_list.remove(last_bi)
|
||||
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")
|
||||
#klc.set_state("-10")
|
||||
#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)
|
||||
klc.set_bi(bi_list[-1])
|
||||
else:
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import sys
|
||||
import os
|
||||
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/Documents/GitHub/chan.py"))
|
||||
sys.path.append(os.path.abspath("/Users/jack/Project/chan.py"))
|
||||
from Chan import CChan
|
||||
from BuySellPoint.BS_Point import CBS_Point
|
||||
from ChanConfig import CChanConfig
|
||||
@@ -51,6 +51,7 @@ class ChanPY():
|
||||
autype=AUTYPE.QFQ,
|
||||
)
|
||||
klu_list = []
|
||||
bsps = []
|
||||
chanIn = True
|
||||
#def __init__(self, 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)
|
||||
self.chanIn = False
|
||||
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:
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
@@ -318,7 +319,6 @@ class ChanPY():
|
||||
return bsps
|
||||
def get_bsp_state(self, dataframe:DataFrame):
|
||||
fields = "time,open,high,low,close,volume"
|
||||
bsps = []
|
||||
if self.chanIn:
|
||||
kl_data = self.get_kl_data(dataframe)
|
||||
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)
|
||||
if bsp_list_pre_len > len(bsp_list):
|
||||
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)
|
||||
else:
|
||||
bsps.append(99)
|
||||
self.bsps.append(99)
|
||||
else:
|
||||
if bsp_list_pre_len == len(bsp_list):
|
||||
if klu.idx == last_bsp.klu.idx:
|
||||
if last_bsp.klu.idx - last_bsp_index > 3:
|
||||
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:
|
||||
bsps.append(0)
|
||||
self.bsps.append(0)
|
||||
last_bsp_index = last_bsp.klu.idx
|
||||
#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")
|
||||
else:
|
||||
bsps.append(0)
|
||||
self.bsps.append(0)
|
||||
else:
|
||||
if klu.idx == last_bsp.klu.idx:
|
||||
if last_bsp.klu.idx - last_bsp_index > 3:
|
||||
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:
|
||||
bsps.append(0)
|
||||
self.bsps.append(0)
|
||||
last_bsp_index = last_bsp.klu.idx
|
||||
#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")
|
||||
else:
|
||||
bsps.append(0)
|
||||
self.bsps.append(0)
|
||||
else:
|
||||
bsps.append(0)
|
||||
self.bsps.append(0)
|
||||
bsp_list_pre_len = len(bsp_list)
|
||||
self.chanIn = False
|
||||
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:
|
||||
self.chan.trigger_load({self.k_type: [klu]}) # 喂给CChan新增k线
|
||||
self.last_kline = klu
|
||||
bsp_list = self.chan.get_bsp()
|
||||
last_bsp = bsp_list[-1]
|
||||
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:
|
||||
bsps.append(0)
|
||||
for index in range(0, len(bsps)):
|
||||
if not (abs(bsps[index]) == 1 or abs(bsps[index]) == 2):
|
||||
bsps[index] = 0
|
||||
self.bsps.append(0)
|
||||
for index in range(0, len(self.bsps)):
|
||||
if not (abs(self.bsps[index]) == 1 or abs(self.bsps[index]) == 2):
|
||||
self.bsps[index] = 0
|
||||
else:
|
||||
if bsps[index] == 2:
|
||||
bsps[index] = 10
|
||||
if self.bsps[index] == 2:
|
||||
self.bsps[index] = 10
|
||||
else:
|
||||
if bsps[index] == -2:
|
||||
bsps[index] = -10
|
||||
if self.bsps[index] == -2:
|
||||
self.bsps[index] = -10
|
||||
else:
|
||||
if bsps[index] == 1:
|
||||
bsps[index] = 1
|
||||
if self.bsps[index] == 1:
|
||||
self.bsps[index] = 1
|
||||
else:
|
||||
if bsps[index] == -1:
|
||||
bsps[index] = -1
|
||||
if self.bsps[index] == -1:
|
||||
self.bsps[index] = -1
|
||||
else:
|
||||
bsps[index] = 0
|
||||
return bsps
|
||||
self.bsps[index] = 0
|
||||
return self.bsps
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ class ChanLun_BTC_30(IStrategy):
|
||||
"240": 0
|
||||
}
|
||||
# 15m and 30m
|
||||
minimal_roi = {
|
||||
minimal_roi_1 = {
|
||||
"0": 0.1,
|
||||
"240": 0.05,
|
||||
"480": 0.03,
|
||||
@@ -61,9 +61,9 @@ class ChanLun_BTC_30(IStrategy):
|
||||
"2400": 0.025,
|
||||
"3600": 0
|
||||
}
|
||||
can_short = True
|
||||
lev = 1.0
|
||||
stoploss = -0.3
|
||||
can_short = False
|
||||
lev = 2.0
|
||||
stoploss = -0.5
|
||||
trailing_stop = False
|
||||
trailing_stop_positive = 0.025
|
||||
trailing_stop_positive_offset = 0.045
|
||||
@@ -105,13 +105,22 @@ class ChanLun_BTC_30(IStrategy):
|
||||
dataframe_4h = self.add_indicators(dataframe_4h)
|
||||
dataframe_1d = self.add_indicators(dataframe_1d)
|
||||
#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)
|
||||
dataframe_30['state'] = state_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():
|
||||
|
||||
#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("-------------------------------------------------------------------------------")
|
||||
self.last_time = datetime.now()
|
||||
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_4h)
|
||||
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):
|
||||
fast = 8
|
||||
slow = 16
|
||||
@@ -167,16 +180,26 @@ class ChanLun_BTC_30(IStrategy):
|
||||
return new_exitprice
|
||||
|
||||
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,
|
||||
side: str, **kwargs) -> bool:
|
||||
time_in_force: str, current_time: datetime, entry_tag: str | None,
|
||||
side: str, **kwargs) -> bool:
|
||||
if self.last_trade:
|
||||
if self.last_trade.open_date + timedelta(minutes=30) > current_time:
|
||||
return False
|
||||
if self.last_trade.is_short:
|
||||
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:
|
||||
#print(self.last_trade.open_date, current_time, self.last_trade.open_date + timedelta(minutes=self.time5))
|
||||
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):
|
||||
#dataframe, _ = self.dp.get_analyzed_dataframe(pair, self.timeframe)
|
||||
#last_candle = dataframe.iloc[-1].squeeze()
|
||||
@@ -220,8 +243,8 @@ class ChanLun_BTC_30(IStrategy):
|
||||
#last_candle = dataframe.iloc[-1].squeeze()
|
||||
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)
|
||||
last_high = klc_list[-3].high
|
||||
last_low = klc_list[-3].low
|
||||
last_high = klc_list[-2].high
|
||||
last_low = klc_list[-2].low
|
||||
if trade.is_short:
|
||||
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)
|
||||
@@ -234,15 +257,15 @@ class ChanLun_BTC_30(IStrategy):
|
||||
def populate_entry_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||
state_str = 'resample_{}_state'.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)
|
||||
shift_time = self.time30*2
|
||||
strength = 2.2
|
||||
#chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5)
|
||||
shift_time = self.time30
|
||||
strength = 0.9
|
||||
dataframe.loc[
|
||||
(
|
||||
#(dataframe['state'] == "-30")
|
||||
(dataframe[state_str].shift(shift_time) > strength) &
|
||||
(dataframe[fx_str].shift(shift_time) == -1) &
|
||||
(dataframe[chanpy_state_str].shift(shift_time+30) == 1)
|
||||
(dataframe[fx_str].shift(shift_time) == -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.time30)] == "-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_str].shift(shift_time) > strength) &
|
||||
(dataframe[fx_str].shift(shift_time) == 1) &
|
||||
(dataframe[chanpy_state_str].shift(shift_time+30) == -1)
|
||||
(dataframe[fx_str].shift(shift_time) == 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.time30)] == "-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:
|
||||
state_str = 'resample_{}_state'.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)
|
||||
shift_time = self.time30*2
|
||||
strength = 2.2
|
||||
#chanpy_state_str = 'resample_{}_chanpy_state'.format(self.get_ticker_indicator()*self.time5)
|
||||
shift_time = self.time30
|
||||
strength = 0.9
|
||||
dataframe.loc[
|
||||
(
|
||||
#(dataframe['state']== "30")
|
||||
(dataframe[state_str].shift(shift_time) > strength) &
|
||||
(dataframe[fx_str].shift(shift_time) == 1) &
|
||||
(dataframe[chanpy_state_str].shift(shift_time+30) == -1)
|
||||
(dataframe[fx_str].shift(shift_time) == 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.time60)] == "10")
|
||||
),
|
||||
@@ -282,8 +305,8 @@ class ChanLun_BTC_30(IStrategy):
|
||||
(
|
||||
#(dataframe['state']== "30")
|
||||
(dataframe[state_str].shift(shift_time) > strength) &
|
||||
(dataframe[fx_str].shift(shift_time) == -1) &
|
||||
(dataframe[chanpy_state_str].shift(shift_time+30) == 1)
|
||||
(dataframe[fx_str].shift(shift_time) == -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.time60)] == "10")
|
||||
),
|
||||
|
||||
+14
@@ -299,6 +299,10 @@ def add_indicators(df):
|
||||
# 处理Infinity和-Infinity值
|
||||
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
|
||||
|
||||
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(),
|
||||
'lower': element_current_df['element_bb_lower'].tolist()
|
||||
},
|
||||
# 添加次周期ATR数据
|
||||
'element_atr': element_current_df['atr'].tolist(),
|
||||
'element_klc_fx_info': [{
|
||||
'time': format_time_safely(point['time'], client_tz),
|
||||
'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(),
|
||||
'lower': current_df['element_bb_lower'].tolist()
|
||||
},
|
||||
# 添加ATR数据
|
||||
'atr': current_df['atr'].tolist(),
|
||||
'klc_fx_info': [{
|
||||
'time': format_time_safely(point['time'], client_tz),
|
||||
'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_bollinger': {'upper': [], 'middle': [], 'lower': []},
|
||||
'element_element_bollinger': {'upper': [], 'middle': [], 'lower': []},
|
||||
'element_atr': [],
|
||||
'element_klc_fx_info': [],
|
||||
'element_klu_fx_info': []
|
||||
})
|
||||
@@ -1116,6 +1125,8 @@ def analyze():
|
||||
'middle': df['element_bb_middle'].tolist(),
|
||||
'lower': df['element_bb_lower'].tolist()
|
||||
},
|
||||
# 添加ATR数据
|
||||
'atr': df['atr'].tolist(),
|
||||
# 添加K线分型信息
|
||||
'klc_fx_info': [{
|
||||
'time': format_time_safely(point['time'], client_tz),
|
||||
@@ -1173,6 +1184,9 @@ def analyze():
|
||||
'lower': element_df['element_bb_lower'].tolist()
|
||||
}
|
||||
|
||||
# 添加小周期ATR数据
|
||||
result['element_atr'] = element_df['atr'].tolist()
|
||||
|
||||
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(),
|
||||
'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
@@ -407,6 +407,10 @@
|
||||
<input class="form-check-input" type="checkbox" id="showMainBollinger">
|
||||
<label class="form-check-label" for="showMainBollinger">布林带</label>
|
||||
</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 class="d-flex align-items-center mt-2">
|
||||
<label class="form-label me-3 mb-0">次周期:</label>
|
||||
@@ -741,6 +745,7 @@
|
||||
candleSeries: null,
|
||||
lineSeries: null,
|
||||
volumeSeries: null,
|
||||
atrLineSeries: null,
|
||||
macdLineSeries: null,
|
||||
signalLineSeries: null,
|
||||
histogramSeries: null,
|
||||
@@ -1387,7 +1392,7 @@
|
||||
container.style.height = '100%';
|
||||
|
||||
// 是否显示MACD
|
||||
const showMacd = true;
|
||||
const showMacd = $('#showMacd').is(':checked');
|
||||
const showOriginalKline = $('#showOriginalKline').is(':checked');
|
||||
|
||||
// 创建主图容器
|
||||
@@ -1406,31 +1411,46 @@
|
||||
volumeChartContainer.style.right = '0';
|
||||
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容器
|
||||
let macdChartContainer = null;
|
||||
if (showMacd) {
|
||||
// 设置各图表高度 - 为三个图表分配合理比例,主图表适度增加高度
|
||||
mainChartContainer.style.height = '55%'; // 主图占55%(约385px)
|
||||
volumeChartContainer.style.top = '55%';
|
||||
volumeChartContainer.style.height = '22.5%'; // 成交量图占22.5%(约157.5px)
|
||||
// 设置各图表高度 - 为四个图表分配合理比例
|
||||
mainChartContainer.style.height = '45%'; // 主图占45%
|
||||
volumeChartContainer.style.top = '45%';
|
||||
volumeChartContainer.style.height = '20%'; // 成交量图占20%
|
||||
|
||||
atrChartContainer.style.top = '65%'; // ATR图从65%位置开始
|
||||
atrChartContainer.style.height = '17.5%'; // ATR图占17.5%
|
||||
|
||||
macdChartContainer = document.createElement('div');
|
||||
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.top = '77.5%'; // 从77.5%位置开始
|
||||
macdChartContainer.style.top = '82.5%'; // 从82.5%位置开始
|
||||
macdChartContainer.style.left = '0';
|
||||
macdChartContainer.style.right = '0';
|
||||
macdChartContainer.style.borderTop = '1px solid #e0e0e0';
|
||||
} else {
|
||||
// 不显示MACD时的高度 - 主图和成交量图分配
|
||||
mainChartContainer.style.height = '72%'; // 主图占72%(约504px)
|
||||
volumeChartContainer.style.top = '72%';
|
||||
volumeChartContainer.style.height = '28%'; // 成交量图占28%(约196px)
|
||||
// 不显示MACD时的高度 - 主图、成交量图和ATR图分配
|
||||
mainChartContainer.style.height = '55%'; // 主图占55%
|
||||
volumeChartContainer.style.top = '55%';
|
||||
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(volumeChartContainer);
|
||||
container.appendChild(atrChartContainer);
|
||||
if (showMacd) container.appendChild(macdChartContainer);
|
||||
|
||||
// 防止同步过程中的无限循环
|
||||
@@ -1444,6 +1464,8 @@
|
||||
chartHeight = mainChartContainer.clientHeight;
|
||||
} else if (chartType === 'volume') {
|
||||
chartHeight = volumeChartContainer.clientHeight;
|
||||
} else if (chartType === 'atr') {
|
||||
chartHeight = atrChartContainer.clientHeight;
|
||||
} else if (chartType === 'macd') {
|
||||
chartHeight = macdChartContainer ? macdChartContainer.clientHeight : 0;
|
||||
} else {
|
||||
@@ -1556,6 +1578,8 @@
|
||||
visible: showTimeScale,
|
||||
borderColor: '#ddd',
|
||||
barSpacing: symbolConfig.type === 'a_stock' ? 6 : 10,
|
||||
// 确保所有图表使用相同的边距设置
|
||||
rightOffset: 12,
|
||||
// 移除可能影响拖动的固定边缘设置
|
||||
// fixLeftEdge: true,
|
||||
// fixRightEdge: true,
|
||||
@@ -1593,6 +1617,9 @@
|
||||
// 创建成交量图表 - 只显示底部的时间轴
|
||||
const volumeChart = LightweightCharts.createChart(volumeChartContainer, createChartOptions(false, 'volume'));
|
||||
|
||||
// 创建ATR图表
|
||||
const atrChart = LightweightCharts.createChart(atrChartContainer, createChartOptions(false, 'atr'));
|
||||
|
||||
// 创建MACD图表(如果需要)
|
||||
let macdChart = null;
|
||||
if (showMacd) {
|
||||
@@ -1663,6 +1690,59 @@
|
||||
volumeSeries.setData(volumes);
|
||||
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数据
|
||||
if (showMacd && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
|
||||
// 创建MACD线
|
||||
@@ -1758,7 +1838,8 @@
|
||||
syncInProgress = true;
|
||||
console.log('🚀 开始同步图表,来源:',
|
||||
sourceChart === mainChart ? '主图' :
|
||||
sourceChart === volumeChart ? '成交量图' : 'MACD图');
|
||||
sourceChart === volumeChart ? '成交量图' :
|
||||
sourceChart === atrChart ? 'ATR图' : 'MACD图');
|
||||
|
||||
try {
|
||||
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图
|
||||
if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) {
|
||||
try {
|
||||
@@ -1822,6 +1913,7 @@
|
||||
let localDragStates = {
|
||||
main: false,
|
||||
volume: false,
|
||||
atr: false,
|
||||
macd: false
|
||||
};
|
||||
|
||||
@@ -1838,10 +1930,15 @@
|
||||
|
||||
// 为每个图表添加事件监听
|
||||
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内置的时间范围变化事件(这是最可靠的方法)
|
||||
chart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||
@@ -1897,6 +1994,7 @@
|
||||
// 添加事件监听
|
||||
addChartSyncEvents(mainChartContainer, mainChart);
|
||||
addChartSyncEvents(volumeChartContainer, volumeChart);
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
if (showMacd && macdChart) {
|
||||
addChartSyncEvents(macdChartContainer, macdChart);
|
||||
}
|
||||
@@ -1915,6 +2013,12 @@
|
||||
height: volumeChartContainer.clientHeight
|
||||
});
|
||||
|
||||
// 调整ATR图大小
|
||||
atrChart.applyOptions({
|
||||
width: atrChartContainer.clientWidth,
|
||||
height: atrChartContainer.clientHeight
|
||||
});
|
||||
|
||||
// 调整MACD图大小
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
macdChart.applyOptions({
|
||||
@@ -2914,6 +3018,8 @@
|
||||
// 清除之前的十字线标记
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
|
||||
@@ -2941,6 +3047,26 @@
|
||||
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图上绘制垂直线
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
|
||||
@@ -2969,6 +3095,8 @@
|
||||
try {
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
@@ -3314,7 +3442,7 @@
|
||||
|
||||
// 构建显示文本,包含分型类型和强度信息
|
||||
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以下不显示文本
|
||||
}
|
||||
|
||||
@@ -3399,7 +3527,7 @@
|
||||
let strengthColor = fx.is_bottom ? '#11116B' : '#222222'; // 底分型用珊瑚红,顶分型用薄荷绿
|
||||
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以上显示点
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
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.volumeChart = volumeChart;
|
||||
tvWidget.atrChart = atrChart;
|
||||
tvWidget.macdChart = macdChart;
|
||||
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()) {
|
||||
@@ -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')) {
|
||||
@@ -3674,8 +3863,46 @@
|
||||
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数据
|
||||
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数据
|
||||
const macdData = [];
|
||||
const signalData = [];
|
||||
@@ -3714,16 +3941,20 @@
|
||||
// 重新显示笔、线段和中枢等图形
|
||||
redrawFractalElements();
|
||||
|
||||
// 恢复之前的可视范围
|
||||
// 恢复之前的可视范围 - 优先使用visibleRange以确保时间轴对齐
|
||||
if (tvWidget.mainChart) {
|
||||
if (tvWidget.state.logicalRange) {
|
||||
tvWidget.mainChart.timeScale().setVisibleLogicalRange(tvWidget.state.logicalRange);
|
||||
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) {
|
||||
if (tvWidget.state.visibleRange) {
|
||||
console.log('🔄 恢复可见范围:', tvWidget.state.visibleRange);
|
||||
tvWidget.mainChart.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);
|
||||
} 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;
|
||||
|
||||
@@ -3743,6 +3974,7 @@
|
||||
let localDragStates = {
|
||||
main: false,
|
||||
volume: false,
|
||||
atr: false,
|
||||
macd: false
|
||||
};
|
||||
|
||||
@@ -3757,7 +3989,8 @@
|
||||
syncInProgress = true;
|
||||
console.log('🚀 开始同步图表,来源:',
|
||||
sourceChart === mainChart ? '主图' :
|
||||
sourceChart === volumeChart ? '成交量图' : 'MACD图');
|
||||
sourceChart === volumeChart ? '成交量图' :
|
||||
sourceChart === atrChart ? 'ATR图' : 'MACD图');
|
||||
|
||||
try {
|
||||
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图
|
||||
if (showMacd && macdChart && sourceChart !== macdChart && macdChart.timeScale) {
|
||||
try {
|
||||
@@ -3799,6 +4042,14 @@
|
||||
// 保存当前的可见范围到全局状态
|
||||
if (tvWidget && tvWidget.state) {
|
||||
tvWidget.state.logicalRange = logicalRange;
|
||||
// 同时保存可见范围以确保精确对齐
|
||||
try {
|
||||
const visibleRange = sourceChart.timeScale().getVisibleRange();
|
||||
tvWidget.state.visibleRange = visibleRange;
|
||||
console.log('💾 保存状态 - 逻辑范围:', logicalRange, '可见范围:', visibleRange);
|
||||
} catch (e) {
|
||||
console.warn('⚠️ 保存可见范围失败:', e);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.warn('⚠️ 无效的逻辑范围:', logicalRange);
|
||||
@@ -3819,10 +4070,15 @@
|
||||
|
||||
// 为每个图表添加事件监听
|
||||
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内置的时间范围变化事件(这是最可靠的方法)
|
||||
chart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
||||
@@ -3882,6 +4138,9 @@
|
||||
if (volumeChartContainer && volumeChart) {
|
||||
addChartSyncEvents(volumeChartContainer, volumeChart);
|
||||
}
|
||||
if (atrChartContainer && atrChart) {
|
||||
addChartSyncEvents(atrChartContainer, atrChart);
|
||||
}
|
||||
if (showMacd && macdChartContainer && macdChart) {
|
||||
addChartSyncEvents(macdChartContainer, macdChart);
|
||||
}
|
||||
@@ -3904,6 +4163,14 @@
|
||||
});
|
||||
}
|
||||
|
||||
// 调整ATR图大小
|
||||
if (atrChart && atrChartContainer) {
|
||||
atrChart.applyOptions({
|
||||
width: atrChartContainer.clientWidth,
|
||||
height: atrChartContainer.clientHeight
|
||||
});
|
||||
}
|
||||
|
||||
// 调整MACD图大小
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
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;
|
||||
|
||||
@@ -3953,6 +4220,8 @@
|
||||
// 清除之前的十字线标记
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
|
||||
@@ -3980,6 +4249,26 @@
|
||||
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图上绘制垂直线
|
||||
if (showMacd && macdChart && macdChartContainer) {
|
||||
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
|
||||
@@ -4008,6 +4297,8 @@
|
||||
try {
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
@@ -4754,12 +5045,14 @@
|
||||
// 销毁主图表及其关联的线系列
|
||||
tvWidget.mainChart = null;
|
||||
tvWidget.volumeChart = null;
|
||||
tvWidget.atrChart = null;
|
||||
tvWidget.macdChart = null;
|
||||
// 重置系列数据
|
||||
tvWidget.series = {
|
||||
candleSeries: null,
|
||||
lineSeries: null,
|
||||
volumeSeries: null,
|
||||
atrLineSeries: null,
|
||||
macdLineSeries: null,
|
||||
signalLineSeries: null,
|
||||
histogramSeries: null,
|
||||
@@ -4809,6 +5102,8 @@
|
||||
// 清除所有十字线延长线,防止它们跟着页面滚动
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
@@ -5020,7 +5315,7 @@
|
||||
const logicalRange = mainChart.timeScale().getVisibleLogicalRange();
|
||||
|
||||
// 获取当前图表设置
|
||||
const showMacd = true;
|
||||
const showMacd = $('#showMacd').is(':checked');
|
||||
const showOriginalKline = $('#showOriginalKline').is(':checked');
|
||||
const showBi = $('#showMainBi').is(':checked');
|
||||
const showSeg = $('#showMainSeg').is(':checked');
|
||||
@@ -5264,7 +5559,7 @@
|
||||
'showMainZs': $('#showMainZs').is(':checked'),
|
||||
'showMainUncompletedZs': $('#showMainUncompletedZs').is(':checked'),
|
||||
'showVolume': false,
|
||||
'showMacd': true,
|
||||
'showMacd': $('#showMacd').is(':checked'),
|
||||
'showKlcFxType': $('#showKlcFxType').is(':checked'),
|
||||
'showKluFxType': $('#showKluFxType').is(':checked'),
|
||||
'showElementKlcFxType': $('#showElementKlcFxType').is(':checked'),
|
||||
@@ -6335,9 +6630,11 @@
|
||||
try {
|
||||
// 清除所有十字线延长线,防止它们跟着页面滚动
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.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');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
console.debug('清除滚动中的十字线时出错:', e);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user