Add ATR
This commit is contained in:
+34
-259
@@ -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
@@ -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:
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user