Merge pull request #15 from jackyu66git/dev

Dev
This commit is contained in:
jackyu66git
2026-03-21 01:41:05 +08:00
committed by GitHub
7 changed files with 531 additions and 443 deletions
+70 -3
View File
@@ -4,7 +4,7 @@ from typing import Dict, Optional
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX, Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_EMA_POS, Chan_EMA_SEMANTIC, Chan_BSP_TYPE from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX, Chan_K_DIR, Chan_MACD_STATE, Chan_PRICE_TREND, Chan_EMA_POS, Chan_EMA_SEMANTIC, Chan_BSP_TYPE
import ChanKLU import ChanKLU
import ChanCTime import ChanCTime
import Chan_FX_Box
# 根据结合律合并K线后的K线 # 根据结合律合并K线后的K线
class ChanKLC(): class ChanKLC():
def __init__(self, klu: ChanKLU, index, ddir=Chan_KLINE_DIR.UP): def __init__(self, klu: ChanKLU, index, ddir=Chan_KLINE_DIR.UP):
@@ -51,6 +51,8 @@ class ChanKLC():
self.ema104 = klu.ema104 self.ema104 = klu.ema104
self.ema156 = klu.ema156 self.ema156 = klu.ema156
self.ema208 = klu.ema208 self.ema208 = klu.ema208
self.ema13 = klu.ema13
self.ema7 = klu.ema7
self.trend = Chan_PRICE_TREND.UNKNOWN self.trend = Chan_PRICE_TREND.UNKNOWN
self.exception = klu.exception self.exception = klu.exception
self.klc_dir = Chan_KLINE_DIR.UP if klu.close > klu.open else Chan_KLINE_DIR.DOWN self.klc_dir = Chan_KLINE_DIR.UP if klu.close > klu.open else Chan_KLINE_DIR.DOWN
@@ -67,6 +69,9 @@ class ChanKLC():
self.bb2633middle = klu.bb2633middle self.bb2633middle = klu.bb2633middle
self.ema5 = klu.ema5 self.ema5 = klu.ema5
self.ma5 = klu.ma5 self.ma5 = klu.ma5
self.fx_box = None
self.in_fx = False
self.fx_confirmed = False
# ==================== EMA 通用计算方法 ==================== # ==================== EMA 通用计算方法 ====================
@staticmethod @staticmethod
@@ -320,6 +325,64 @@ class ChanKLC():
#print(self.end_time, ema_name, self.ema_status[ema_name]['semantic'], hist_div) #print(self.end_time, ema_name, self.ema_status[ema_name]['semantic'], hist_div)
#self.cal_bb_out() #self.cal_bb_out()
#print(self.pre.start_time, self.next.end_time, self.klc_fx_type) #print(self.pre.start_time, self.next.end_time, self.klc_fx_type)
if klc_fx_type == Chan_KLC_FX.TOP1 or klc_fx_type == Chan_KLC_FX.TOP2 or klc_fx_type == Chan_KLC_FX.BOTTOM1 or klc_fx_type == Chan_KLC_FX.BOTTOM2:
self.cal_fx_box()
def cal_fx_box(self):
# 每次重算前先清空,避免旧box残留
self.fx_box = None
start_time = None
end_time = None
high = 0
low = 0
display = False
if self.pre and self.next and self.next.end_time:
self.next.in_fx = True
if self.fx == Chan_FX_TYPE.TOP:
start_time = self.pre.end_time
end_time = self.next.end_time
high = self.high
low = self.pre.low if self.pre.low < self.next.low else self.next.low
if self.next.close < self.pre.low:
display = True
elif self.fx == Chan_FX_TYPE.BOTTOM:
start_time = self.pre.end_time
end_time = self.next.end_time
high = self.pre.high if self.pre.high > self.next.high else self.next.high
low = self.low
if self.next.close > self.pre.high:
display = True
if high > 0 and self.next.end_time and display:
#print(start_time, end_time, high, low)
# Chan_FX_BOX 这里导入的是模块,类名在模块内部为 Chan_FX_Box
self.fx_confirmed = True
self.fx_box = Chan_FX_Box.Chan_FX_Box(start_time, end_time, high, low)
def check_fx_confirmed(self, last_top, last_bottom):
if last_top and last_bottom:
if last_top.index > last_bottom.index:
if self.in_fx == False and last_top.fx_confirmed == False:
pre = last_top.pre
if pre.low > self.close:
last_top.fx_confirmed = True
if last_top.fx_box:
last_top.fx_box.end_time = self.end_time
print(self.end_time, "fx_confirmed top")
else:
high = last_top.high
low = self.low
last_top.fx_box = Chan_FX_Box.Chan_FX_Box(last_top.pre.end_time, self.end_time, high, low)
print(self.end_time, "fx_confirmed new box top")
elif self.in_fx == False and last_bottom.fx_confirmed == False:
pre = last_bottom.pre
if pre.high < self.close:
last_bottom.fx_confirmed = True
if last_bottom.fx_box:
last_bottom.fx_box.end_time = self.end_time
print(self.end_time, "fx_confirmed bottom")
else:
high = self.high
low = last_bottom.low
last_bottom.fx_box = Chan_FX_Box.Chan_FX_Box(last_bottom.pre.end_time, self.end_time, high, low)
print(self.end_time, "fx_confirmed new box bottom")
def add_klu(self, klu): def add_klu(self, klu):
self.klu_list.append(klu) self.klu_list.append(klu)
def set_end_klu(self, klu): def set_end_klu(self, klu):
@@ -347,8 +410,8 @@ class ChanKLC():
self.close = self.high self.close = self.high
if self.close < self.low: if self.close < self.low:
self.close = self.low self.close = self.low
if self.close > self.high: if self.open < self.low:
self.close = self.high self.open = self.low
#print(self.end_time, self.open, self.close, self.high, self.low) #print(self.end_time, self.open, self.close, self.high, self.low)
#print(klu.time, klu.open, klu.close, klu.high, klu.low) #print(klu.time, klu.open, klu.close, klu.high, klu.low)
def cal_fx(self): def cal_fx(self):
@@ -397,6 +460,8 @@ class ChanKLC():
self.ema104 += self.klu_list[index].ema104 self.ema104 += self.klu_list[index].ema104
self.ema156 += self.klu_list[index].ema156 self.ema156 += self.klu_list[index].ema156
self.ema208 += self.klu_list[index].ema208 self.ema208 += self.klu_list[index].ema208
self.ema13 += self.klu_list[index].ema13
self.ema7 += self.klu_list[index].ema7
self.bb2633upper += self.klu_list[index].bb2633upper self.bb2633upper += self.klu_list[index].bb2633upper
self.bb2633lower += self.klu_list[index].bb2633lower self.bb2633lower += self.klu_list[index].bb2633lower
self.bb2633middle += self.klu_list[index].bb2633middle self.bb2633middle += self.klu_list[index].bb2633middle
@@ -415,6 +480,8 @@ class ChanKLC():
self.ema104 = self.ema104 / n self.ema104 = self.ema104 / n
self.ema156 = self.ema156 / n self.ema156 = self.ema156 / n
self.ema208 = self.ema208 / n self.ema208 = self.ema208 / n
self.ema13 = self.ema13 / n
self.ema7 = self.ema7 / n
self.ma5 = self.ma5 / n self.ma5 = self.ma5 / n
self.ema5 = self.ema5 / n self.ema5 = self.ema5 / n
self.bb2633upper = self.bb2633upper / n self.bb2633upper = self.bb2633upper / n
+2
View File
@@ -182,6 +182,8 @@ class ChanKLU:
self.ema104 = float(item['ema104']) if 'ema104' in item and item['ema104'] else 0 self.ema104 = float(item['ema104']) if 'ema104' in item and item['ema104'] else 0
self.ema156 = float(item['ema156']) if 'ema156' in item and item['ema156'] else 0 self.ema156 = float(item['ema156']) if 'ema156' in item and item['ema156'] else 0
self.ema208 = float(item['ema208']) if 'ema208' in item and item['ema208'] else 0 self.ema208 = float(item['ema208']) if 'ema208' in item and item['ema208'] else 0
self.ema13 = float(item['ema13']) if 'ema13' in item and item['ema13'] else 0
self.ema7 = float(item['ema7']) if 'ema7' in item and item['ema7'] else 0
self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0 self.rsi = float(item['rsi']) if 'rsi' in item and item['rsi'] else 0
self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0 self.volume_ratio = float(item['volume_ratio']) if 'volume_ratio' in item and item['volume_ratio'] else 0
self.bb52upper = float(item['bb52upper']) if 'bb52upper' in item and item['bb52upper'] else 0 self.bb52upper = float(item['bb52upper']) if 'bb52upper' in item and item['bb52upper'] else 0
+10
View File
@@ -19,6 +19,7 @@ class ChanMACDHistSet():
self.start_klu = start_klu self.start_klu = start_klu
self.peak_div_list = [] self.peak_div_list = []
self.middle_area = 0 self.middle_area = 0
self.total_macdhist = 0
def set_next(self, next_histset): def set_next(self, next_histset):
self.next = next_histset self.next = next_histset
def set_pre(self, pre_histset): def set_pre(self, pre_histset):
@@ -102,6 +103,15 @@ class ChanMACDHistSet():
for peak_div in self.peak_div_list: for peak_div in self.peak_div_list:
peak_str += f"{peak_div.time}, " peak_str += f"{peak_div.time}, "
state_str += f"{peak_div.macd_state}, " state_str += f"{peak_div.macd_state}, "
total_macdhist = 0
first_klu = self.klu_list[0]
last_klu = self.klu_list[-1]
if (first_klu.macd > 0 and last_klu.macd > 0 and first_klu.macdhist > 0) or (first_klu.macd < 0 and last_klu.macd < 0 and first_klu.macdhist < 0):
for klu in self.klu_list:
self.total_macdhist += klu.macdhist
if abs(self.total_macdhist) < 150:
#print(self.end_time, "Total MACDHist: ", self.total_macdhist)
last_klu.separate_div = 99999
#if self.peak_klu and len(self.peak_div_list) > 0: #if self.peak_klu and len(self.peak_div_list) > 0:
#print("Continue Div: ",self.start_time, "Peak:", self.peak_klu.time, "Div: ", peak_str, state_str) #print("Continue Div: ",self.start_time, "Peak:", self.peak_klu.time, "Div: ", peak_str, state_str)
+7
View File
@@ -0,0 +1,7 @@
class Chan_FX_Box():
def __init__(self, start_time, end_time, high, low):
self.start_time = start_time
self.end_time = end_time
self.high = high
self.low = low
+29 -328
View File
@@ -122,6 +122,8 @@ class TF_DF():
df['ema156'] = ta.EMA(df, timeperiod=156) df['ema156'] = ta.EMA(df, timeperiod=156)
df['ema208'] = ta.EMA(df, timeperiod=208) df['ema208'] = ta.EMA(df, timeperiod=208)
df['ema26'] = ta.EMA(df, timeperiod=26) df['ema26'] = ta.EMA(df, timeperiod=26)
df['ema13'] = ta.EMA(df, timeperiod=13)
df['ema7'] = ta.EMA(df, timeperiod=7)
df['rsi'] = ta.RSI(df, timeperiod=14) df['rsi'] = ta.RSI(df, timeperiod=14)
df['volume_ratio'] = self.cal_volume_ratio(df) df['volume_ratio'] = self.cal_volume_ratio(df)
return df return df
@@ -918,6 +920,7 @@ class TF_DF():
last_bottom = None last_bottom = None
bi_klc_min = 4 bi_klc_min = 4
for klc in klc_list: for klc in klc_list:
klc.check_fx_confirmed(last_top, last_bottom)
fx = self.check_fx(klc) fx = self.check_fx(klc)
if fx == Chan_FX_TYPE.TOP and False: if fx == Chan_FX_TYPE.TOP and False:
if last_bottom: if last_bottom:
@@ -986,7 +989,7 @@ class TF_DF():
if last_top.high > klc.high: if last_top.high > klc.high:
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
klc.set_klc_fx_type(Chan_KLC_FX.TOP3) #klc.set_klc_fx_type(Chan_KLC_FX.TOP3)
#print(klc.end_time, klc.fx, "二类卖点Sell 1") #print(klc.end_time, klc.fx, "二类卖点Sell 1")
else: else:
# A new top found # A new top found
@@ -1001,11 +1004,11 @@ class TF_DF():
# 不满足结合律的分型 # 不满足结合律的分型
else: else:
#klc.set_klc_fx_type(Chan_KLC_FX.TOP0) #klc.set_klc_fx_type(Chan_KLC_FX.TOP0)
print(klc.end_time, klc.klc_fx_type) #print(klc.end_time, klc.klc_fx_type)
if last_bottom.index + bi_klc_min > klc.index: if last_bottom.index + bi_klc_min > klc.index:
if last_top.high > klc.high: if last_top.high > klc.high:
#print(klc.start_time, klc.fx, "二类卖点Sell 1") #print(klc.start_time, klc.fx, "二类卖点Sell 1")
klc.set_klc_fx_type(Chan_KLC_FX.TOP8) #klc.set_klc_fx_type(Chan_KLC_FX.TOP8)
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
# New TOP Found前面的UKNOWN可能出现TOP7,但是这里的也可能出现TOP8分型 # New TOP Found前面的UKNOWN可能出现TOP7,但是这里的也可能出现TOP8分型
@@ -1104,7 +1107,7 @@ class TF_DF():
if last_bottom.low < klc.low: if last_bottom.low < klc.low:
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3) #klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3)
#print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1") #print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1")
#print(klc.end_time, klc.fx, "二类买点Buy 1") #print(klc.end_time, klc.fx, "二类买点Buy 1")
else: else:
@@ -1125,7 +1128,7 @@ class TF_DF():
#print(klc.end_time, klc.fx, "中枢买点Buy 1") #print(klc.end_time, klc.fx, "中枢买点Buy 1")
bi_list[-1].add_klc(klc) bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM8) #klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM8)
# Found new bottom没有意义,上面UNKNOWN的时候已经是笔破坏了 # Found new bottom没有意义,上面UNKNOWN的时候已经是笔破坏了
else: else:
#print(klc.end_time, last_bottom.end_time, "Found a new bottom") #print(klc.end_time, last_bottom.end_time, "Found a new bottom")
@@ -1213,331 +1216,29 @@ class TF_DF():
klc.set_bi(bi_list[-1]) klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5") #print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5")
#print(klc.start_time, klc.fx, "笔买点Buy 4") #print(klc.start_time, klc.fx, "笔买点Buy 4")
self.get_above_zero_bsp(klc_list)
return bi_list return bi_list
def cal_bi_list1(self, klc_list): def get_above_zero_bsp(self, klc_list):
bi_list = [] buy_bsp_list = []
last_top = None sell_bsp_list = []
last_bottom = None above_zero = False
buy_bsp = None
sell_bsp = None
for klc in klc_list: for klc in klc_list:
fx = self.check_fx(klc) if klc.pre and klc.pre.signal < 0 and klc.signal > 0:
if fx == Chan_FX_TYPE.TOP: above_zero = True
if last_bottom: if klc.pre and klc.pre.signal > 0 and klc.signal < 0:
if self.check_top_fx(last_bottom, klc) == False: above_zero = False
fx = Chan_FX_TYPE.UNKNOWN if above_zero and klc.klc_fx_type == Chan_KLC_FX.BOTTOM2 and klc.macd > 0:
if fx == Chan_FX_TYPE.BOTTOM: buy_bsp = klc
if last_top: buy_bsp_list.append(klc)
if self.check_bottom_fx(last_top, klc) == False: #print(klc.end_time, "MACD 0轴上穿,回调笔底分型做多")
#print(klc.end_time, last_top.end_time, "---") if buy_bsp and klc.pre and klc.pre.macdhist > 0 and klc.macdhist < 0:
fx = Chan_FX_TYPE.UNKNOWN sell_bsp = klc
# Do nothing sell_bsp_list.append(klc)
if fx == Chan_FX_TYPE.UNKNOWN: buy_bsp = None
if len(bi_list) > 0: #print(klc.end_time, "Sell BSP Found")
bi_list[-1].add_klc(klc) return buy_bsp_list
continue
if len(bi_list) > 0 and klc.end_klu:
last_bi = bi_list[-1]
#print(klc.start_time, last_bi.start_time, last_bi.end_time, last_bi.dir, last_bi.high, last_bi.low, last_bottom.end_time, "last bi")
if last_top and last_bi.dir == Chan_BI_DIR.DOWN:
print("fx=unknown, 1")
if last_bottom and klc.high > last_bi.high:
last_bi.set_end_klc(last_bottom, klc)
bi = ChanBI(last_bottom, len(bi_list), Chan_BI_DIR.UP)
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
#klc.bb_out = True
last_bi.set_next(bi)
bi.set_pre(last_bi)
for klc_index in range(last_bi.end_klc.index, len(klc_list)):
bi.add_klc(klc_list[klc_index])
bi_list.append(bi)
last_top = klc
klc.set_bi(bi)
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
else:
print("fx=unknown, 2")
if last_bottom and last_bi.dir == Chan_BI_DIR.UP:
if last_top and klc.low < last_bi.low:
last_bi.set_end_klc(last_top, klc)
bi = ChanBI(last_top, len(bi_list), Chan_BI_DIR.DOWN)
#klc.set_klc_fx_type(Chan_KLC_FX.TOP6)
#klc.bb_out = True
last_bi.set_next(bi)
bi.set_pre(last_bi)
for klc_index in range(last_bi.end_klc.index, len(klc_list)):
bi.add_klc(klc_list[klc_index])
bi_list.append(bi)
last_bottom = klc
klc.set_bi(bi)
#print(klc.start_time, bi.start_time, bi.end_time, bi.dir, bi.high, bi.low, bi.is_sure)
else:
if fx == Chan_FX_TYPE.TOP:
if last_top:
if last_bottom:
#print(klc.start_time, last_bottom.start_time, last_top.start_time)
if last_bottom.index < last_top.index:
# Second top lower to be second sell point
if last_top.high > klc.high:
#klc.set_fx(Chan_FX_TYPE.TT)
#klc.set_state("20")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.cal_invisible()
#klc.set_klc_fx_type(Chan_KLC_FX.TOP3)
#print(klc.start_time, klc.fx, "二类卖点Sell 1")
else:
# A new top found
#last_top.set_fx(Chan_FX_TYPE.UNKNOWN)
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)
self.check_fx_pattern(klc)
#print(klc.end_time, klc.fx, "一类卖点Sell 1")
#klc.set_fx(fx)
#klc.set_state("10")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
else:
# 不满足结合律的分型
if last_bottom.index + 4 > klc.index:
if last_top.high > klc.high:
#print(klc.start_time, klc.fx, "二类卖点Sell 1")
#klc.set_fx(Chan_FX_TYPE.PTOP)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# New TOP Found replace last top
else:
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 and False:
pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
#last_top.set_fx(Chan_FX_TYPE.PTOP)
last_top = klc
last_bottom = pre_last_bi.start_klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 1")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
#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) # when bi is down but the fx is top
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
else:
klc.set_fx(Chan_FX_TYPE.PTOP)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "无效分型")
# 满足结合律
else:
# New Temp TOP and last bottom confirmed ***** confirm last down bi(last bottom and last top)
last_bi = bi_list[-1]
if not last_bi.is_sure:
last_bi.set_end_klc(last_bottom, klc)
bi = ChanBI(last_bottom, len(bi_list), Chan_BI_DIR.UP)
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
#klc.bb_out = True
last_bi.set_next(bi)
bi.set_pre(last_bi)
bi.add_klc(klc)
bi_list.append(bi)
last_top = klc
#print(klc.end_time, klc.fx, bi_list[-1].dir, "Last Top Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.TOP2)
self.check_fx_pattern(klc)
#klc.set_state('30')
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, last_bottom.start_time, "Normal TOP Found, Confirm down bi 4")
#print(klc.start_time, klc.fx, "笔卖点Sell 2")
# last bottom = None
else:
if last_top.high < klc.high:
last_bi = bi_list[-1]
last_bi.set_start_klc(klc, Chan_BI_DIR.DOWN)
#last_top.set_fx(Chan_FX_TYPE.UNKNOWN)
last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 3")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "笔卖点Sell 3")
else:
klc.set_fx(Chan_FX_TYPE.TT)
#klc.set_state('20')
#print(klc.start_time, klc.fx, "二类卖点Sell 2")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
else:
if last_bottom:
# 不满足结合律的分型
if last_bottom.index + 4 > klc.index:
#klc.set_fx(Chan_FX_TYPE.PTOP)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "中枢卖点Sell 1")
else:
# First temp top and last bottom confirmed
last_top = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 4")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "一类卖点Sell 1")
# Last top = None, last bottom = None, create first down bi
else:
# First temp top
last_top = klc
bi = ChanBI(klc, len(bi_list), Chan_BI_DIR.DOWN)
#klc.set_klc_fx_type(Chan_KLC_FX.TOP6)
#klc.bb_out = True
bi_list.append(bi)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Change 5")
#print(klc.start_time, 'Create first top')
#print(klc.start_time, klc.fx, "笔卖点Sell 1")
#klc.fx = Bottom ========================
else:
if last_bottom:
if last_top:
# Bottom after top and find a new bottom
if last_top.index < last_bottom.index:
# Second bottom uppper to be second buy point and confirm last bi
if last_bottom.low < klc.low:
#klc.set_fx(Chan_FX_TYPE.BB)
#klc.set_state("-20")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#klc.cal_invisible()
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM3)
#print(last_bottom.start_time, last_bottom.end_time, "--------------------------------1")
#print(klc.start_time, klc.fx, "二类买点Buy 1")
else:
# A new bottom found
#last_bottom.set_fx(Chan_FX_TYPE.UNKNOWN)
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 1")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM1)
self.check_fx_pattern(klc)
#print(klc.start_time, klc.fx, "一类买点Buy 1")
#klc.set_state("-10")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
else:
# 不满足结合律的分型
if last_top.index + 4 > klc.index:
if last_bottom.low < klc.low:
#klc.set_fx(Chan_FX_TYPE.PBOTTOM)
#klc.set_fx(Chan_FX_TYPE.BB)
#klc.set_state("-100")
#print(klc.start_time, klc.fx, "中枢买点Buy 1")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# Found new bottom
else:
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 and False:
pre_last_bi.update_bi(klc)
bi_list.remove(last_bi)
pre_last_bi.set_next(None)
#last_bottom.set_fx(Chan_FX_TYPE.PBOTTOM)
last_bottom = klc
last_top = pre_last_bi.start_klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Top Bottom Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
#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) # when bi is up but the fx is bottom
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
else:
#klc.set_fx(Chan_FX_TYPE.UNKNOWN)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "无效分型")
# 满足结合律的分型
else:
# New Temp Bottom and last top confirmed ***** confirm last up bi(last bottom and last top)
last_bi = bi_list[-1]
if not last_bi.is_sure:
last_bi.set_end_klc(last_top, klc)
bi = ChanBI(last_top, len(bi_list), Chan_BI_DIR.DOWN)
#klc.set_klc_fx_type(Chan_KLC_FX.TOP6)
#klc.bb_out = True
last_bi.set_next(bi)
bi.set_pre(last_bi)
bi.add_klc(klc)
bi_list.append(bi)
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 2")
klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM2)
self.check_fx_pattern(klc)
#klc.set_state('-30')
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "笔买点Buy 2")
#print(klc.start_time, last_top.start_time, "Normal Bottom Found, Confirm up bi 6")
# last_top = None
else:
if last_bottom.low > klc.low:
last_bi = bi_list[-1]
last_bi.set_start_klc(klc, Chan_BI_DIR.UP)
#last_bottom.set_fx(Chan_FX_TYPE.UNKNOWN)
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 3")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "笔买点Buy 3")
else:
klc.set_fx(Chan_FX_TYPE.BB)
#klc.set_state('-20')
#print(klc.start_time, klc.fx, "二类买点Buy 2")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
# last_bottom = None
else:
if last_top:
# 不满足结合律的分型
if last_top.index + 4 > klc.index:
#klc.set_fx(Chan_FX_TYPE.PBOTTOM)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "中枢买点Buy 1")
else:
# First temp bottom and last top confirmed
last_bottom = klc
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 4")
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, "一类买点Buy 1")
# Last top = None, last bottom = None, create first up bi
else:
# First temp bottom and no top yet
last_bottom = klc
bi = ChanBI(klc, len(bi_list), Chan_BI_DIR.UP)
#klc.set_klc_fx_type(Chan_KLC_FX.BOTTOM7)
#klc.bb_out = True
bi_list.append(bi)
bi_list[-1].add_klc(klc)
klc.set_bi(bi_list[-1])
#print(klc.start_time, klc.fx, bi_list[-1].dir, "Last Bottom Change 5")
#print(klc.start_time, klc.fx, "笔买点Buy 4")
#if klc.fx != Chan_FX_TYPE.UNKNOWN:
#print(klc.start_time, klc.fx, klc.index)
"""
for klc in klc_list:
if klc.fx == Chan_FX_TYPE.TOP:
klc.state = "10"
#print(klc.time, klc.state)
if klc.fx == Chan_FX_TYPE.BOTTOM:
klc.state = "-10"
#print(klc.time, klc.state)
"""
#for index in range(0, 10):
#print(bi_list[index].start_time, bi_list[index].start_klc.start_time, bi_list[index].dir)
return bi_list
def check_top_fx(self, last_bottom, klc): def check_top_fx(self, last_bottom, klc):
if (last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low) and (klc.index - last_bottom.index < 10): if (last_bottom.high > klc.pre.low or last_bottom.high > klc.next.low) and (klc.index - last_bottom.index < 10):
return False return False
+70 -5
View File
@@ -439,6 +439,11 @@ def add_indicators(df):
df['ema24'] = (ta.EMA(df, timeperiod=24)).fillna(0) df['ema24'] = (ta.EMA(df, timeperiod=24)).fillna(0)
df['ema52'] = (ta.EMA(df, timeperiod=52)).fillna(0) df['ema52'] = (ta.EMA(df, timeperiod=52)).fillna(0)
df['ema26'] = (ta.EMA(df, timeperiod=26)).fillna(0) df['ema26'] = (ta.EMA(df, timeperiod=26)).fillna(0)
df['ema13'] = (ta.EMA(df, timeperiod=13)).fillna(0)
df['ema7'] = (ta.EMA(df, timeperiod=7)).fillna(0)
df['ema104'] = (ta.EMA(df, timeperiod=104)).fillna(0)
df['ema156'] = (ta.EMA(df, timeperiod=156)).fillna(0)
df['ema208'] = (ta.EMA(df, timeperiod=208)).fillna(0)
# 常用SMA 24/52 # 常用SMA 24/52
try: try:
df['sma24'] = (ta.SMA(df, timeperiod=24)).fillna(0) df['sma24'] = (ta.SMA(df, timeperiod=24)).fillna(0)
@@ -624,6 +629,16 @@ def analyze_chan(df, symbol=None, timeframe=None):
# 如果分型强度小于1,设为0 # 如果分型强度小于1,设为0
if fx_strength < 1: if fx_strength < 1:
fx_strength = 0 fx_strength = 0
# KLC 分型框(起止时间+高低价):
# 仅使用 cal_fx_box 通过 display 条件后生成的 klc.fx_box。
# 若无 fx_box,则前端不应绘制分型框。
fx_box = getattr(klc, 'fx_box', None)
box_start_time = getattr(fx_box, 'start_time', None) if fx_box else None
box_end_time = getattr(fx_box, 'end_time', None) if fx_box else None
box_high = getattr(fx_box, 'high', None) if fx_box else None
box_low = getattr(fx_box, 'low', None) if fx_box else None
if klc.bb_out: if klc.bb_out:
klc_fx_info.append({ klc_fx_info.append({
'time': klc.end_time, 'time': klc.end_time,
@@ -632,10 +647,22 @@ def analyze_chan(df, symbol=None, timeframe=None):
'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM, 'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM,
'fx_strength': fx_strength, # 分型强度分数 (0-100) 'fx_strength': fx_strength, # 分型强度分数 (0-100)
'fx_strength_level': fx_strength_level, # 分型强度等级 (极强/强/中等/弱/极弱) 'fx_strength_level': fx_strength_level, # 分型强度等级 (极强/强/中等/弱/极弱)
'is_strong_fx': is_strong_fx # 是否为强分型 'is_strong_fx': is_strong_fx, # 是否为强分型
# 虚线分型框信息(给前端画框用)
'start_time': box_start_time,
'end_time': box_end_time,
'high': float(box_high) if box_high is not None else None,
'low': float(box_low) if box_low is not None else None,
}) })
except Exception as e: except Exception as e:
# 如果出错,仍然添加基本信息,但分型强度为0 # 如果出错,仍然添加基本信息,但分型强度为0
fx_box = getattr(klc, 'fx_box', None)
box_start_time = getattr(fx_box, 'start_time', None) if fx_box else None
box_end_time = getattr(fx_box, 'end_time', None) if fx_box else None
box_high = getattr(fx_box, 'high', None) if fx_box else None
box_low = getattr(fx_box, 'low', None) if fx_box else None
klc_fx_info.append({ klc_fx_info.append({
'time': klc.end_time, 'time': klc.end_time,
'price': klc.low if klc.fx == Chan_FX_TYPE.BOTTOM else klc.high, 'price': klc.low if klc.fx == Chan_FX_TYPE.BOTTOM else klc.high,
@@ -643,7 +670,13 @@ def analyze_chan(df, symbol=None, timeframe=None):
'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM, 'is_bottom': klc.fx == Chan_FX_TYPE.BOTTOM,
'fx_strength': 0, 'fx_strength': 0,
'fx_strength_level': "", 'fx_strength_level': "",
'is_strong_fx': False 'is_strong_fx': False,
# 虚线分型框信息(给前端画框用)
'start_time': box_start_time,
'end_time': box_end_time,
'high': float(box_high) if box_high is not None else None,
'low': float(box_low) if box_low is not None else None,
}) })
@@ -1391,12 +1424,17 @@ def analyze():
# 添加K线分型信息 # 添加K线分型信息
'klc_fx_info': [{ 'klc_fx_info': [{
'time': format_time_safely(point['time'], client_tz), 'time': format_time_safely(point['time'], client_tz),
'start_time': format_time_safely(point['start_time'], client_tz),
'end_time': format_time_safely(point['end_time'], client_tz),
'price': float(point['price']), 'price': float(point['price']),
'fx_type': point['fx_type'], 'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']), 'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), # 分型强度分数 'fx_strength': float(point['fx_strength']), # 分型强度分数
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级 'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型 'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
# 分型框(虚线矩形)用到的高低价
'high': float(point['high']) if point.get('high') is not None else None,
'low': float(point['low']) if point.get('low') is not None else None
} for point in analysis_result['klc_fx_info']], } for point in analysis_result['klc_fx_info']],
# 添加ChanMACD分析数据 # 添加ChanMACD分析数据
'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz), 'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz),
@@ -1587,12 +1625,17 @@ def analyze():
# 添加小周期分型信息 # 添加小周期分型信息
result['element_klc_fx_info'] = [{ result['element_klc_fx_info'] = [{
'time': format_time_safely(point['time'], client_tz), 'time': format_time_safely(point['time'], client_tz),
'start_time': format_time_safely(point['start_time'], client_tz),
'end_time': format_time_safely(point['end_time'], client_tz),
'price': float(point['price']), 'price': float(point['price']),
'fx_type': point['fx_type'], 'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']), 'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), # 分型强度分数 'fx_strength': float(point['fx_strength']), # 分型强度分数
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级 'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型 'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
# 分型框(虚线矩形)用到的高低价
'high': float(point['high']) if point.get('high') is not None else None,
'low': float(point['low']) if point.get('low') is not None else None
} for point in element_analysis['klc_fx_info']] } for point in element_analysis['klc_fx_info']]
# 添加次周期ChanMACD分析数据 # 添加次周期ChanMACD分析数据
@@ -1619,6 +1662,9 @@ def analyze():
sub_sub_df = add_indicators(sub_sub_df) sub_sub_df = add_indicators(sub_sub_df)
sub_sub_analysis = analyze_chan(sub_sub_df, symbol, sub_sub_timeframe) sub_sub_analysis = analyze_chan(sub_sub_df, symbol, sub_sub_timeframe)
result['sub_sub_timeframe'] = sub_sub_timeframe result['sub_sub_timeframe'] = sub_sub_timeframe
result['sub_sub_kline_data'] = clean_dataframe_for_json(sub_sub_df).to_dict('records')
result['sub_sub_atr'] = sub_sub_df['atr'].tolist()
result['sub_sub_macd'] = calculate_macd(sub_sub_df)
result['sub_sub_bi_list'] = [{ result['sub_sub_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,
@@ -1637,6 +1683,20 @@ def analyze():
'direction': convert_direction(bi.dir), 'direction': convert_direction(bi.dir),
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0 'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
} for bi in sub_sub_analysis['bi_list'] if not bi.end_klc] } for bi in sub_sub_analysis['bi_list'] if not bi.end_klc]
# 次次周期 KLC 列表
result['sub_sub_klc_list'] = [{
'date': klc.end_time if isinstance(klc.end_time, str) else klc.end_time.astimezone(client_tz).isoformat(),
'open': float(klc.open),
'high': float(klc.high),
'low': float(klc.low),
'close': float(klc.close),
'volume': float(klc.volume) if hasattr(klc, 'volume') else 0,
'direction': str(klc.dir).replace('Chan_KLINE_DIR.', ''),
'fx_type': str(klc.fx).replace('Chan_FX_TYPE.', ''),
'klc_fx_type': str(klc.klc_fx_type).replace('Chan_KLC_FX.', ''),
'trend': str(klc.trend).replace('Chan_PRICE_TREND.', '')
} for klc in sub_sub_analysis.get('klc_list', []) if hasattr(klc, 'end_time') and klc.end_time]
result['sub_sub_seg_list'] = [{ result['sub_sub_seg_list'] = [{
'start_time': seg.start_bi.start_klc.end_time if isinstance(seg.start_bi.start_klc.end_time, str) else seg.start_bi.start_klc.end_time.astimezone(client_tz).isoformat(), 'start_time': seg.start_bi.start_klc.end_time if isinstance(seg.start_bi.start_klc.end_time, str) else seg.start_bi.start_klc.end_time.astimezone(client_tz).isoformat(),
'end_time': (seg.end_bi.end_klc.end_time if isinstance(seg.end_bi.end_klc.end_time, str) else seg.end_bi.end_klc.end_time.astimezone(client_tz).isoformat()) if seg.end_bi else None, 'end_time': (seg.end_bi.end_klc.end_time if isinstance(seg.end_bi.end_klc.end_time, str) else seg.end_bi.end_klc.end_time.astimezone(client_tz).isoformat()) if seg.end_bi else None,
@@ -1674,12 +1734,17 @@ def analyze():
} for zs in sub_sub_analysis.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)] } for zs in sub_sub_analysis.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)]
result['sub_sub_klc_fx_info'] = [{ result['sub_sub_klc_fx_info'] = [{
'time': format_time_safely(point['time'], client_tz), 'time': format_time_safely(point['time'], client_tz),
'start_time': format_time_safely(point['start_time'], client_tz),
'end_time': format_time_safely(point['end_time'], client_tz),
'price': float(point['price']), 'price': float(point['price']),
'fx_type': point['fx_type'], 'fx_type': point['fx_type'],
'is_bottom': bool(point['is_bottom']), 'is_bottom': bool(point['is_bottom']),
'fx_strength': float(point['fx_strength']), 'fx_strength': float(point['fx_strength']),
'fx_strength_level': str(point['fx_strength_level']), 'fx_strength_level': str(point['fx_strength_level']),
'is_strong_fx': bool(point['is_strong_fx']) 'is_strong_fx': bool(point['is_strong_fx']),
# 分型框(虚线矩形)用到的高低价
'high': float(point['high']) if point.get('high') is not None else None,
'low': float(point['low']) if point.get('low') is not None else None
} for point in sub_sub_analysis['klc_fx_info']] } for point in sub_sub_analysis['klc_fx_info']]
result['sub_sub_bsp_list'] = [{ result['sub_sub_bsp_list'] = [{
'time': format_time_safely(bsp.end_time, client_tz), 'time': format_time_safely(bsp.end_time, client_tz),
+343 -107
View File
@@ -851,6 +851,10 @@
<input class="form-check-input" type="radio" name="klinePeriod" id="elementPeriodKline"> <input class="form-check-input" type="radio" name="klinePeriod" id="elementPeriodKline">
<label class="form-check-label" for="elementPeriodKline">小周期</label> <label class="form-check-label" for="elementPeriodKline">小周期</label>
</div> </div>
<div class="form-check form-check-inline">
<input class="form-check-input" type="radio" name="klinePeriod" id="subSubPeriodKline">
<label class="form-check-label" for="subSubPeriodKline">次次周期</label>
</div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMacd" checked> <input class="form-check-input" type="checkbox" id="showMacd" checked>
<label class="form-check-label" for="showMacd">ChanMACD</label> <label class="form-check-label" for="showMacd">ChanMACD</label>
@@ -876,7 +880,7 @@
<div id="refreshLoadingSpinner" class="loading-spinner ms-2" style="display:none;"></div> <div id="refreshLoadingSpinner" class="loading-spinner ms-2" style="display:none;"></div>
</div> </div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-1">
<label class="form-label me-0 mb-0">主周期:</label> <label class="form-label me-0 mb-0">主周期:</label>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<select id="timeframe" class="form-select form-select-sm me-2" style="width: 100px;"> <select id="timeframe" class="form-select form-select-sm me-2" style="width: 100px;">
@@ -918,7 +922,7 @@
<label class="form-check-label" for="showMainBsp">买卖点</label> <label class="form-check-label" for="showMainBsp">买卖点</label>
</div> </div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-1">
<label class="form-label me-0 mb-0">次周期:</label> <label class="form-label me-0 mb-0">次周期:</label>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<select id="elementTimeframe" class="form-select form-select-sm me-2" style="width: 100px;"> <select id="elementTimeframe" class="form-select form-select-sm me-2" style="width: 100px;">
@@ -960,7 +964,7 @@
<label class="form-check-label" for="showElementBsp">买卖点</label> <label class="form-check-label" for="showElementBsp">买卖点</label>
</div> </div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-1">
<label class="form-label me-0 mb-0">次次周期:</label> <label class="form-label me-0 mb-0">次次周期:</label>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<select id="subSubTimeframe" class="form-select form-select-sm me-2" style="width: 100px;"> <select id="subSubTimeframe" class="form-select form-select-sm me-2" style="width: 100px;">
@@ -2183,26 +2187,31 @@
return; return;
} }
// 检查是否使用小周期K线数据 // 检查使用哪一档K线数据:次次周期 / 小周期 / 主周期
const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') &&
currentData.sub_sub_kline_data &&
Array.isArray(currentData.sub_sub_kline_data);
const useElementPeriod = $('#elementPeriodKline').is(':checked') && const useElementPeriod = $('#elementPeriodKline').is(':checked') &&
currentData.element_kline_data && currentData.element_kline_data &&
Array.isArray(currentData.element_kline_data); Array.isArray(currentData.element_kline_data);
// 输出K线周期选择状态 // 输出K线周期选择状态
console.log('K线周期选择:', useElementPeriod ? '小周期' : '主周期'); const klinePeriodLabel = useSubSubPeriod ? '次次周期' : (useElementPeriod ? '小周期' : '主周期');
console.log('K线周期选择:', klinePeriodLabel);
console.log('当前选择时区:', $('#timezone').val()); console.log('当前选择时区:', $('#timezone').val());
console.log('交易对类型:', symbolConfig.type); console.log('交易对类型:', symbolConfig.type);
let candles = []; let candles = [];
const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? (currentData.element_kline_data || []) : (currentData.kline_data || []));
if (useElementPeriod) { if (useSubSubPeriod || useElementPeriod) {
// 使用小周期K线数据 if (!klineDataSource.length) {
candles = currentData.element_kline_data.map((kline) => { console.error(useSubSubPeriod ? '次次周期K线数据不存在或为空' : '小周期K线数据不存在或为空', klineDataSource);
// 使用原始日期字符串创建Date对象 return;
}
candles = klineDataSource.map((kline) => {
const date = new Date(kline.date); const date = new Date(kline.date);
// 获取时间戳(秒)- 不手动调整时区
const timestamp = date.getTime() / 1000; const timestamp = date.getTime() / 1000;
return { return {
time: timestamp, time: timestamp,
open: parseFloat(kline.open), open: parseFloat(kline.open),
@@ -2212,17 +2221,13 @@
}; };
}); });
} else { } else {
// 使用主周期K线数据 - 检查数据是否存在
if (!currentData.kline_data || !Array.isArray(currentData.kline_data)) { if (!currentData.kline_data || !Array.isArray(currentData.kline_data)) {
console.error('主周期K线数据不存在或不是数组:', currentData.kline_data); console.error('主周期K线数据不存在或不是数组:', currentData.kline_data);
return; return;
} }
candles = currentData.kline_data.map((kline) => { candles = currentData.kline_data.map((kline) => {
// 使用原始日期字符串创建Date对象
const date = new Date(kline.date); const date = new Date(kline.date);
// 获取时间戳(秒)- 不手动调整时区
const timestamp = date.getTime() / 1000; const timestamp = date.getTime() / 1000;
return { return {
time: timestamp, time: timestamp,
open: parseFloat(kline.open), open: parseFloat(kline.open),
@@ -2655,12 +2660,11 @@
} }
})(); })();
// 转换成交量数据 - 始终使用主K线周期数据 // 转换成交量数据 - K线周期一致
let volumes = []; let volumes = [];
// 使用与K线和MACD相同的数据源选择逻辑 const volumeDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data);
const volumeDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data;
console.log('成交量数据源选择:', useElementPeriod ? '次周期' : '主周期'); console.log('成交量数据源选择:', klinePeriodLabel);
console.log('成交量数据长度:', volumeDataSource.length); console.log('成交量数据长度:', volumeDataSource.length);
if (volumeDataSource && Array.isArray(volumeDataSource)) { if (volumeDataSource && Array.isArray(volumeDataSource)) {
@@ -2699,13 +2703,10 @@
// 准备ATR数据 // 准备ATR数据
const atrData = []; const atrData = [];
// 使用与K线数据相同的数据源来确保时间对齐 const atrKlineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data);
const atrKlineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data; const atrDataSource = useSubSubPeriod ? (currentData.sub_sub_atr || currentData.atr) : (useElementPeriod ? (currentData.element_atr || currentData.atr) : currentData.atr);
const atrDataSource = useElementPeriod ?
(currentData.element_atr || currentData.atr) : // 如果有次周期ATR数据则使用,否则使用主周期
currentData.atr; // 主周期使用主周期ATR数据
console.log('ATR数据源选择:', useElementPeriod ? '次周期' : '主周期'); console.log('ATR数据源选择:', klinePeriodLabel);
console.log('ATR数据长度:', atrDataSource ? atrDataSource.length : 0); console.log('ATR数据长度:', atrDataSource ? atrDataSource.length : 0);
console.log('K线数据长度:', atrKlineDataSource ? atrKlineDataSource.length : 0); console.log('K线数据长度:', atrKlineDataSource ? atrKlineDataSource.length : 0);
@@ -2776,7 +2777,7 @@
const histogramData = []; const histogramData = [];
// 使用与K线数据相同的数据源来确保时间对齐 // 使用与K线数据相同的数据源来确保时间对齐
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data; const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data);
const macdDataSource = useElementPeriod ? const macdDataSource = useElementPeriod ?
(currentData.element_macd || currentData.macd) : // 如果有次周期MACD数据则使用,否则使用主周期 (currentData.element_macd || currentData.macd) : // 如果有次周期MACD数据则使用,否则使用主周期
currentData.macd; // 主周期使用主周期MACD数据 currentData.macd; // 主周期使用主周期MACD数据
@@ -2839,7 +2840,7 @@
if (typeof window.showUOnSubSub === 'undefined') { if (typeof window.showUOnSubSub === 'undefined') {
window.showUOnSubSub = $('#toggleUOnSubSub').is(':checked'); window.showUOnSubSub = $('#toggleUOnSubSub').is(':checked');
} }
if (showMacd && chanMacdChart && ((useElementPeriod && currentData.element_macd) || currentData.macd) && (useElementPeriod ? currentData.element_kline_data : currentData.kline_data)) { if (showMacd && chanMacdChart && ((useSubSubPeriod && currentData.sub_sub_macd) || (useElementPeriod && currentData.element_macd) || currentData.macd) && (useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data))) {
console.log('✅ 开始创建 ChanMACD 系列'); console.log('✅ 开始创建 ChanMACD 系列');
// 创建ChanMACD线系列 // 创建ChanMACD线系列
const chanMacdLineSeries = chanMacdChart.addLineSeries({ const chanMacdLineSeries = chanMacdChart.addLineSeries({
@@ -2883,8 +2884,8 @@
}); });
// 使用与主图一致的数据源(小周期开启时使用小周期MACD与K线) // 使用与主图一致的数据源(小周期开启时使用小周期MACD与K线)
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data; const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data);
const macdDataSource = useElementPeriod ? (currentData.element_macd || currentData.macd) : currentData.macd; const macdDataSource = useSubSubPeriod ? (currentData.sub_sub_macd || currentData.macd) : (useElementPeriod ? (currentData.element_macd || currentData.macd) : currentData.macd);
// 准备ChanMACD数据 // 准备ChanMACD数据
const chanMacdData = []; const chanMacdData = [];
@@ -2958,9 +2959,9 @@
// 添加ChanMACD分析标注 // 添加ChanMACD分析标注
// 根据主/次周期开关与各自的"显示U"独立控制 // 根据主/次周期开关与各自的"显示U"独立控制
const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd; const cm = useSubSubPeriod ? (currentData.sub_sub_chan_macd || currentData.chan_macd) : (useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd);
// 默认不显示,必须用户勾选对应复选框 // 默认不显示,必须用户勾选对应复选框
const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain; const allowU = useSubSubPeriod ? !!window.showUOnSubSub : (useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain);
if (cm && allowU) { if (cm && allowU) {
console.log('添加ChanMACD分析标注:', { console.log('添加ChanMACD分析标注:', {
segListLength: cm.seg_list ? cm.seg_list.length : 0, segListLength: cm.seg_list ? cm.seg_list.length : 0,
@@ -5816,6 +5817,64 @@
allMainFxMarkers.push(markerConfig); allMainFxMarkers.push(markerConfig);
// 画虚线分型框(根据 start/end + high/low
if (fx.start_time && fx.end_time && fx.high !== null && fx.high !== undefined && fx.low !== null && fx.low !== undefined) {
const startTs = Math.floor(new Date(fx.start_time).getTime() / 1000);
const endTs = Math.floor(new Date(fx.end_time).getTime() / 1000);
const high = parseFloat(fx.high);
const low = parseFloat(fx.low);
if (!isNaN(startTs) && !isNaN(endTs) && !isNaN(high) && !isNaN(low)) {
const boxHigh = Math.max(high, low);
const boxLow = Math.min(high, low);
const boxColor = strengthColor;
const topSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2, // 虚线
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
topSeries.setData([{ time: startTs, value: boxHigh }, { time: endTs, value: boxHigh }]);
const bottomSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2, // 虚线
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
bottomSeries.setData([{ time: startTs, value: boxLow }, { time: endTs, value: boxLow }]);
const leftSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2, // 虚线
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
// 左边竖线:同一 time 上下两个点(和你已有ZS绘制写法保持一致)
leftSeries.setData([{ time: startTs, value: boxLow }, { time: startTs, value: boxHigh }]);
const rightSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2, // 虚线
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
rightSeries.setData([{ time: endTs, value: boxLow }, { time: endTs, value: boxHigh }]);
if (!tvWidget.series.mainKlcFxBoxSeries) tvWidget.series.mainKlcFxBoxSeries = [];
tvWidget.series.mainKlcFxBoxSeries.push(topSeries, bottomSeries, leftSeries, rightSeries);
}
}
// 创建分型标记对象,包含tooltip信息 // 创建分型标记对象,包含tooltip信息
const fxMarker = { const fxMarker = {
time: timestamp, time: timestamp,
@@ -5965,6 +6024,63 @@
allElementFxMarkers.push(markerConfig); allElementFxMarkers.push(markerConfig);
// 画虚线分型框(小周期)
if (fx.start_time && fx.end_time && fx.high !== null && fx.high !== undefined && fx.low !== null && fx.low !== undefined) {
const startTs = Math.floor(new Date(fx.start_time).getTime() / 1000);
const endTs = Math.floor(new Date(fx.end_time).getTime() / 1000);
const high = parseFloat(fx.high);
const low = parseFloat(fx.low);
if (!isNaN(startTs) && !isNaN(endTs) && !isNaN(high) && !isNaN(low)) {
const boxHigh = Math.max(high, low);
const boxLow = Math.min(high, low);
const boxColor = strengthColor;
const topSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
topSeries.setData([{ time: startTs, value: boxHigh }, { time: endTs, value: boxHigh }]);
const bottomSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
bottomSeries.setData([{ time: startTs, value: boxLow }, { time: endTs, value: boxLow }]);
const leftSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
leftSeries.setData([{ time: startTs, value: boxLow }, { time: startTs, value: boxHigh }]);
const rightSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
rightSeries.setData([{ time: endTs, value: boxLow }, { time: endTs, value: boxHigh }]);
if (!tvWidget.series.elementKlcFxBoxSeries) tvWidget.series.elementKlcFxBoxSeries = [];
tvWidget.series.elementKlcFxBoxSeries.push(topSeries, bottomSeries, leftSeries, rightSeries);
}
}
// 创建小周期分型标记对象,包含tooltip信息 // 创建小周期分型标记对象,包含tooltip信息
const elementFxMarker = { const elementFxMarker = {
time: timestamp, time: timestamp,
@@ -6056,10 +6172,67 @@
position: fx.is_bottom ? 'belowBar' : 'aboveBar', position: fx.is_bottom ? 'belowBar' : 'aboveBar',
color: strengthColor, color: strengthColor,
shape: 'triangle', shape: 'triangle',
text: displayText || 's', text: displayText,
size: (fx.is_strong_fx ? 0.6 : 0.5) size: (fx.is_strong_fx ? 0.6 : 0.5)
}; };
allElementFxMarkers.push(markerConfig); allElementFxMarkers.push(markerConfig);
// 画虚线分型框(次次周期)
if (fx.start_time && fx.end_time && fx.high !== null && fx.high !== undefined && fx.low !== null && fx.low !== undefined) {
const startTs = Math.floor(new Date(fx.start_time).getTime() / 1000);
const endTs = Math.floor(new Date(fx.end_time).getTime() / 1000);
const high = parseFloat(fx.high);
const low = parseFloat(fx.low);
if (!isNaN(startTs) && !isNaN(endTs) && !isNaN(high) && !isNaN(low)) {
const boxHigh = Math.max(high, low);
const boxLow = Math.min(high, low);
const boxColor = strengthColor;
const topSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
topSeries.setData([{ time: startTs, value: boxHigh }, { time: endTs, value: boxHigh }]);
const bottomSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
bottomSeries.setData([{ time: startTs, value: boxLow }, { time: endTs, value: boxLow }]);
const leftSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
leftSeries.setData([{ time: startTs, value: boxLow }, { time: startTs, value: boxHigh }]);
const rightSeries = mainChart.addLineSeries({
color: boxColor,
lineWidth: 1,
lineStyle: 2,
lastValueVisible: false,
priceLineVisible: false,
crosshairMarkerVisible: false,
});
rightSeries.setData([{ time: endTs, value: boxLow }, { time: endTs, value: boxHigh }]);
if (!tvWidget.series.subSubKlcFxBoxSeries) tvWidget.series.subSubKlcFxBoxSeries = [];
tvWidget.series.subSubKlcFxBoxSeries.push(topSeries, bottomSeries, leftSeries, rightSeries);
}
}
} catch (e) { console.error('绘制次次周期KLC分型标记出错:', e); } } catch (e) { console.error('绘制次次周期KLC分型标记出错:', e); }
}); });
} }
@@ -6199,7 +6372,11 @@
else if (klineType === 'baseline') targetSeries = tvWidget.series.baselineSeries; else if (klineType === 'baseline') targetSeries = tvWidget.series.baselineSeries;
else if (klineType === 'klc') targetSeries = tvWidget.series.klcSeries; else if (klineType === 'klc') targetSeries = tvWidget.series.klcSeries;
if (targetSeries) { if (targetSeries) {
targetSeries.setMarkers(combinedMarkers); try {
targetSeries.setMarkers(combinedMarkers);
} catch (e) {
console.warn('设置主系列标记失败(可能series已释放):', e);
}
} else { } else {
console.log('未找到主数据系列,无法设置标记'); console.log('未找到主数据系列,无法设置标记');
} }
@@ -6323,7 +6500,11 @@
else if (klineType2 === 'baseline') targetSeries2 = tvWidget.series.baselineSeries; else if (klineType2 === 'baseline') targetSeries2 = tvWidget.series.baselineSeries;
else if (klineType2 === 'klc') targetSeries2 = tvWidget.series.klcSeries; else if (klineType2 === 'klc') targetSeries2 = tvWidget.series.klcSeries;
if (targetSeries2) { if (targetSeries2) {
targetSeries2.setMarkers(onlyMainAndU); try {
targetSeries2.setMarkers(onlyMainAndU);
} catch (e) {
console.warn('设置主系列标记失败(可能series已释放):', e);
}
} else { } else {
console.log('未找到主数据系列,无法设置标记'); console.log('未找到主数据系列,无法设置标记');
} }
@@ -6341,7 +6522,11 @@
else if (klineType3 === 'baseline') targetSeries3 = tvWidget.series.baselineSeries; else if (klineType3 === 'baseline') targetSeries3 = tvWidget.series.baselineSeries;
else if (klineType3 === 'klc') targetSeries3 = tvWidget.series.klcSeries; else if (klineType3 === 'klc') targetSeries3 = tvWidget.series.klcSeries;
if (targetSeries3) { if (targetSeries3) {
targetSeries3.setMarkers([]); try {
targetSeries3.setMarkers([]);
} catch (e) {
console.warn('清空主系列标记失败(可能series已释放):', e);
}
} }
} }
} }
@@ -6551,15 +6736,33 @@
// 检查是否显示原始K线 // 检查是否显示原始K线
const showOriginalKline = $('#showOriginalKline').is(':checked'); const showOriginalKline = $('#showOriginalKline').is(':checked');
// 检查是否使用小周期数据 // 检查是否使用次次周期 / 小周期数据
const useElementPeriod = $('#elementPeriodKline').is(':checked') && const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') &&
currentData.sub_sub_timeframe &&
currentData.sub_sub_kline_data &&
Array.isArray(currentData.sub_sub_kline_data);
const useElementPeriod = !useSubSubPeriod &&
$('#elementPeriodKline').is(':checked') &&
currentData.element_timeframe && currentData.element_timeframe &&
currentData.element_kline_data && currentData.element_kline_data &&
Array.isArray(currentData.element_kline_data); Array.isArray(currentData.element_kline_data);
// 转换K线数据 // 转换K线数据
let candles = []; let candles = [];
if (useElementPeriod) { if (useSubSubPeriod) {
console.log('使用次次周期K线数据');
candles = currentData.sub_sub_kline_data.map((kline) => {
const date = new Date(kline.date);
const timestamp = date.getTime() / 1000;
return {
time: timestamp,
open: parseFloat(kline.open),
high: parseFloat(kline.high),
low: parseFloat(kline.low),
close: parseFloat(kline.close),
};
});
} else if (useElementPeriod) {
console.log('使用小周期K线数据'); console.log('使用小周期K线数据');
candles = currentData.element_kline_data.map((kline) => { candles = currentData.element_kline_data.map((kline) => {
const date = new Date(kline.date); const date = new Date(kline.date);
@@ -6621,7 +6824,16 @@
// 更新成交量数据 // 更新成交量数据
let volumes = []; let volumes = [];
if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) { if (useSubSubPeriod && currentData.sub_sub_kline_data && Array.isArray(currentData.sub_sub_kline_data)) {
volumes = currentData.sub_sub_kline_data.map(kline => {
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
return {
time: timestamp,
value: parseFloat(kline.volume),
color: parseFloat(kline.close) >= parseFloat(kline.open) ? 'rgba(40, 167, 69, 0.5)' : 'rgba(220, 53, 69, 0.5)',
};
});
} else if (useElementPeriod && currentData.element_kline_data && Array.isArray(currentData.element_kline_data)) {
volumes = currentData.element_kline_data.map(kline => { volumes = currentData.element_kline_data.map(kline => {
const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); const timestamp = Math.floor(new Date(kline.date).getTime() / 1000);
return { return {
@@ -6648,12 +6860,12 @@
// 更新ATR数据 // 更新ATR数据
if (tvWidget.series.atrLineSeries) { if (tvWidget.series.atrLineSeries) {
const atrData = []; const atrData = [];
const atrDataSource = useElementPeriod ? const atrDataSource = useSubSubPeriod ?
(currentData.element_atr || currentData.atr) : (currentData.sub_sub_atr || currentData.atr) :
currentData.atr; (useElementPeriod ? (currentData.element_atr || currentData.atr) : currentData.atr);
if (atrDataSource && Array.isArray(atrDataSource)) { if (atrDataSource && Array.isArray(atrDataSource)) {
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data; const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data);
// 修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据 // 修复:为每个K线时间点都创建ATR数据点,包括没有ATR值的前期数据
for (let i = 0; i < klineDataSource.length; i++) { for (let i = 0; i < klineDataSource.length; i++) {
const kline = klineDataSource[i]; const kline = klineDataSource[i];
@@ -6721,9 +6933,9 @@
} }
// 更新 ChanMACD 数据与自定义标注 // 更新 ChanMACD 数据与自定义标注
if (tvWidget.series.chanMacdLineSeries && ((useElementPeriod && currentData.element_macd) || currentData.macd) && (useElementPeriod ? currentData.element_kline_data : currentData.kline_data)) { if (tvWidget.series.chanMacdLineSeries && ((useSubSubPeriod && currentData.sub_sub_macd) || (useElementPeriod && currentData.element_macd) || currentData.macd) && (useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data))) {
const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data; const klineDataSource = useSubSubPeriod ? (currentData.sub_sub_kline_data || []) : (useElementPeriod ? currentData.element_kline_data : currentData.kline_data);
const macdDataSource = useElementPeriod ? (currentData.element_macd || currentData.macd) : currentData.macd; const macdDataSource = useSubSubPeriod ? (currentData.sub_sub_macd || currentData.macd) : (useElementPeriod ? (currentData.element_macd || currentData.macd) : currentData.macd);
if (macdDataSource && macdDataSource.macd && macdDataSource.signal && macdDataSource.histogram) { if (macdDataSource && macdDataSource.macd && macdDataSource.signal && macdDataSource.histogram) {
const chanMacdData = []; const chanMacdData = [];
const chanSignalData = []; const chanSignalData = [];
@@ -6746,8 +6958,8 @@
// 重新应用自定义标注(段/UnitTF/HistSet/状态点) // 重新应用自定义标注(段/UnitTF/HistSet/状态点)
try { try {
if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers(); if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers();
const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd; const cm = useSubSubPeriod ? (currentData.sub_sub_chan_macd || currentData.chan_macd) : (useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd);
const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain; const allowU = useSubSubPeriod ? !!window.showUOnSubSub : (useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain);
if (cm && allowU) { if (cm && allowU) {
addAllChanMacdMarkers( addAllChanMacdMarkers(
cm.seg_list || [], cm.seg_list || [],
@@ -7334,18 +7546,21 @@
const data = currentData; const data = currentData;
// 检查用户选择的是主周期还是小周期数据 const useSubSubPeriod = $('#subSubPeriodKline').is(':checked');
const useElementPeriod = $('#elementPeriodKline').is(':checked'); const useElementPeriod = $('#elementPeriodKline').is(':checked');
console.log('数据表显示周期选择:', useElementPeriod ? '小周期' : '主周期'); const periodLabel = useSubSubPeriod ? '次次周期' : (useElementPeriod ? '小周期' : '主周期');
console.log('数据表显示周期选择:', periodLabel);
// 笔数据表更新 // 笔数据表更新
if (tables.bi) { if (tables.bi) {
tables.bi.clear().destroy(); tables.bi.clear().destroy();
} }
// 根据用户选择决定使用哪个周期的数据
let biData, biSource; let biData, biSource;
if (useElementPeriod && data.element_bi_list && data.element_bi_list.length > 0) { if (useSubSubPeriod && data.sub_sub_bi_list && data.sub_sub_bi_list.length > 0) {
biData = data.sub_sub_bi_list;
biSource = '次次周期';
} else if (useElementPeriod && data.element_bi_list && data.element_bi_list.length > 0) {
biData = data.element_bi_list; biData = data.element_bi_list;
biSource = '小周期'; biSource = '小周期';
} else { } else {
@@ -7374,9 +7589,12 @@
tables.seg.clear().destroy(); tables.seg.clear().destroy();
} }
// 根据用户选择决定使用哪个周期的数据
let segData, segSource, uncompletedSegData; let segData, segSource, uncompletedSegData;
if (useElementPeriod && data.element_seg_list && data.element_seg_list.length > 0) { if (useSubSubPeriod && data.sub_sub_seg_list && data.sub_sub_seg_list.length > 0) {
segData = data.sub_sub_seg_list;
uncompletedSegData = data.sub_sub_uncompleted_seg_list || [];
segSource = '次次周期';
} else if (useElementPeriod && data.element_seg_list && data.element_seg_list.length > 0) {
segData = data.element_seg_list; segData = data.element_seg_list;
uncompletedSegData = data.element_uncompleted_seg_list || []; uncompletedSegData = data.element_uncompleted_seg_list || [];
segSource = '小周期'; segSource = '小周期';
@@ -7433,9 +7651,11 @@
tables.zs.clear().destroy(); tables.zs.clear().destroy();
} }
// 根据用户选择决定使用哪个周期的数据
let zsData, zsSource; let zsData, zsSource;
if (useElementPeriod && data.element_zs_list && data.element_zs_list.length > 0) { if (useSubSubPeriod && data.sub_sub_zs_list && data.sub_sub_zs_list.length > 0) {
zsData = data.sub_sub_zs_list;
zsSource = '次次周期';
} else if (useElementPeriod && data.element_zs_list && data.element_zs_list.length > 0) {
zsData = data.element_zs_list; zsData = data.element_zs_list;
zsSource = '小周期'; zsSource = '小周期';
} else { } else {
@@ -7461,13 +7681,15 @@
tables.tradePoints.clear().destroy(); tables.tradePoints.clear().destroy();
} }
// 根据用户选择决定使用哪个周期的数据
let tradePointsData, tradePointsSource; let tradePointsData, tradePointsSource;
if (useElementPeriod && data.element_trade_points && data.element_trade_points.length > 0) { if (useSubSubPeriod && data.sub_sub_bsp_list && data.sub_sub_bsp_list.length > 0) {
tradePointsData = data.element_trade_points; tradePointsData = data.sub_sub_bsp_list;
tradePointsSource = '次次周期';
} else if (useElementPeriod && (data.element_trade_points && data.element_trade_points.length > 0 || data.element_bsp_list && data.element_bsp_list.length > 0)) {
tradePointsData = data.element_trade_points || data.element_bsp_list;
tradePointsSource = '小周期'; tradePointsSource = '小周期';
} else { } else {
tradePointsData = data.trade_points; tradePointsData = data.trade_points || data.bsp_list;
tradePointsSource = '主周期'; tradePointsSource = '主周期';
} }
console.log(`表格显示${tradePointsSource}买卖点数据,共${tradePointsData ? tradePointsData.length : 0}条`); console.log(`表格显示${tradePointsSource}买卖点数据,共${tradePointsData ? tradePointsData.length : 0}条`);
@@ -7485,8 +7707,8 @@
}); });
// 更新数据源信息显示 // 更新数据源信息显示
const selectedPeriod = useElementPeriod ? '小周期' : '主周期'; const selectedPeriod = useSubSubPeriod ? '次次周期' : (useElementPeriod ? '小周期' : '主周期');
const timeframe = useElementPeriod && data.element_timeframe ? data.element_timeframe : $('#timeframe').val(); const timeframe = useSubSubPeriod && data.sub_sub_timeframe ? data.sub_sub_timeframe : (useElementPeriod && data.element_timeframe ? data.element_timeframe : $('#timeframe').val());
$('#dataSourceText').html(`当前显示的是<strong>${selectedPeriod} (${timeframe})</strong> 数据`); $('#dataSourceText').html(`当前显示的是<strong>${selectedPeriod} (${timeframe})</strong> 数据`);
// K线数据表更新 // K线数据表更新
@@ -7496,7 +7718,10 @@
// 根据用户选择决定使用哪个周期的K线数据 // 根据用户选择决定使用哪个周期的K线数据
let klineData, klineSource; let klineData, klineSource;
if (useElementPeriod && data.element_kline_data && data.element_kline_data.length > 0) { if (useSubSubPeriod && data.sub_sub_kline_data && data.sub_sub_kline_data.length > 0) {
klineData = data.sub_sub_kline_data;
klineSource = '次次周期';
} else if (useElementPeriod && data.element_kline_data && data.element_kline_data.length > 0) {
klineData = data.element_kline_data; klineData = data.element_kline_data;
klineSource = '小周期'; klineSource = '小周期';
} else { } else {
@@ -7524,9 +7749,11 @@
tables.uncompletedZs.clear().destroy(); tables.uncompletedZs.clear().destroy();
} }
// 根据用户选择决定使用哪个周期的数据
let uncompletedZsData, uncompletedZsSource; let uncompletedZsData, uncompletedZsSource;
if (useElementPeriod && data.element_uncompleted_zs_list && data.element_uncompleted_zs_list.length > 0) { if (useSubSubPeriod && data.sub_sub_uncompleted_zs_list && data.sub_sub_uncompleted_zs_list.length > 0) {
uncompletedZsData = data.sub_sub_uncompleted_zs_list;
uncompletedZsSource = '次次周期';
} else if (useElementPeriod && data.element_uncompleted_zs_list && data.element_uncompleted_zs_list.length > 0) {
uncompletedZsData = data.element_uncompleted_zs_list; uncompletedZsData = data.element_uncompleted_zs_list;
uncompletedZsSource = '小周期'; uncompletedZsSource = '小周期';
} else { } else {
@@ -7599,15 +7826,17 @@
} }
// 设置数据源信息显示 // 设置数据源信息显示
function setupDataSourceInfo(data) { function setupDataSourceInfo(data) {
// 获取用户当前的周期选择 const useSubSubPeriod = $('#subSubPeriodKline').is(':checked');
const useElementPeriod = $('#elementPeriodKline').is(':checked'); const useElementPeriod = $('#elementPeriodKline').is(':checked');
const mainTimeframe = $('#timeframe').val(); const mainTimeframe = $('#timeframe').val();
const elementTimeframe = data.element_timeframe || mainTimeframe; const elementTimeframe = data.element_timeframe || mainTimeframe;
const subSubTimeframe = data.sub_sub_timeframe || elementTimeframe;
// 添加标签点击事件
$('#kline-tab, #macd-tab').off('click').on('click', function() { $('#kline-tab, #macd-tab').off('click').on('click', function() {
$('.data-source-info').show(); $('.data-source-info').show();
if (useElementPeriod && data.element_kline_data && data.element_kline_data.length > 0) { if (useSubSubPeriod && data.sub_sub_kline_data && data.sub_sub_kline_data.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>次次周期 (${subSubTimeframe})</strong> 数据`);
} else if (useElementPeriod && data.element_kline_data && data.element_kline_data.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 数据`); $('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 数据`);
} else { } else {
$('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 数据`); $('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 数据`);
@@ -7616,7 +7845,9 @@
$('#bi-tab').off('click').on('click', function() { $('#bi-tab').off('click').on('click', function() {
$('.data-source-info').show(); $('.data-source-info').show();
if (useElementPeriod && data.element_bi_list && data.element_bi_list.length > 0) { if (useSubSubPeriod && data.sub_sub_bi_list && data.sub_sub_bi_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>次次周期 (${subSubTimeframe})</strong> 笔数据`);
} else if (useElementPeriod && data.element_bi_list && data.element_bi_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 笔数据`); $('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 笔数据`);
} else { } else {
$('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 笔数据`); $('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 笔数据`);
@@ -7625,7 +7856,9 @@
$('#seg-tab').off('click').on('click', function() { $('#seg-tab').off('click').on('click', function() {
$('.data-source-info').show(); $('.data-source-info').show();
if (useElementPeriod && data.element_seg_list && data.element_seg_list.length > 0) { if (useSubSubPeriod && data.sub_sub_seg_list && data.sub_sub_seg_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>次次周期 (${subSubTimeframe})</strong> 线段数据`);
} else if (useElementPeriod && data.element_seg_list && data.element_seg_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 线段数据`); $('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 线段数据`);
} else { } else {
$('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 线段数据`); $('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 线段数据`);
@@ -7634,7 +7867,9 @@
$('#zs-tab').off('click').on('click', function() { $('#zs-tab').off('click').on('click', function() {
$('.data-source-info').show(); $('.data-source-info').show();
if (useElementPeriod && data.element_zs_list && data.element_zs_list.length > 0) { if (useSubSubPeriod && data.sub_sub_zs_list && data.sub_sub_zs_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>次次周期 (${subSubTimeframe})</strong> 中枢数据`);
} else if (useElementPeriod && data.element_zs_list && data.element_zs_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 中枢数据`); $('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 中枢数据`);
} else { } else {
$('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 中枢数据`); $('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 中枢数据`);
@@ -7643,7 +7878,9 @@
$('#trade-points-tab').off('click').on('click', function() { $('#trade-points-tab').off('click').on('click', function() {
$('.data-source-info').show(); $('.data-source-info').show();
if (useElementPeriod && data.element_trade_points && data.element_trade_points.length > 0) { if (useSubSubPeriod && data.sub_sub_bsp_list && data.sub_sub_bsp_list.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>次次周期 (${subSubTimeframe})</strong> 买卖点数据`);
} else if (useElementPeriod && data.element_trade_points && data.element_trade_points.length > 0) {
$('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 买卖点数据`); $('#dataSourceText').html(`当前显示的是<strong>小周期 (${elementTimeframe})</strong> 买卖点数据`);
} else { } else {
$('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 买卖点数据`); $('#dataSourceText').html(`当前显示的是<strong>主周期 (${mainTimeframe})</strong> 买卖点数据`);
@@ -9161,44 +9398,39 @@
try { updateIndicatorPanel(); } catch(e) {} try { updateIndicatorPanel(); } catch(e) {}
try { if ($('#maConfigModal').is(':visible')) { hideMAConfig(); } } catch(e) {} try { if ($('#maConfigModal').is(':visible')) { hideMAConfig(); } } catch(e) {}
} }
// 获取当前K线数据的辅助函数 // 获取当前K线数据的辅助函数(与基础显示的主/小/次次周期保持一致)
function getCurrentCandleData() { function getCurrentCandleData() {
if (!currentData || !currentData.kline_data) { if (!currentData || !currentData.kline_data) {
return []; return [];
} }
// 检查是否使用小周期数据 const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') &&
const useElementPeriod = $('#elementPeriodKline').is(':checked') && currentData.sub_sub_kline_data &&
Array.isArray(currentData.sub_sub_kline_data);
const useElementPeriod = !useSubSubPeriod &&
$('#elementPeriodKline').is(':checked') &&
currentData.element_kline_data && currentData.element_kline_data &&
Array.isArray(currentData.element_kline_data); Array.isArray(currentData.element_kline_data);
let candles = []; let source = currentData.kline_data;
if (useElementPeriod) { if (useSubSubPeriod) {
candles = currentData.element_kline_data.map((kline) => { source = currentData.sub_sub_kline_data;
const date = new Date(kline.date); } else if (useElementPeriod) {
const timestamp = date.getTime() / 1000; source = currentData.element_kline_data;
return {
time: timestamp,
open: parseFloat(kline.open),
high: parseFloat(kline.high),
low: parseFloat(kline.low),
close: parseFloat(kline.close),
};
});
} else {
candles = currentData.kline_data.map((kline) => {
const date = new Date(kline.date);
const timestamp = date.getTime() / 1000;
return {
time: timestamp,
open: parseFloat(kline.open),
high: parseFloat(kline.high),
low: parseFloat(kline.low),
close: parseFloat(kline.close),
};
});
} }
const candles = source.map((kline) => {
const date = new Date(kline.date);
const timestamp = date.getTime() / 1000;
return {
time: timestamp,
open: parseFloat(kline.open),
high: parseFloat(kline.high),
low: parseFloat(kline.low),
close: parseFloat(kline.close),
};
});
return candles; return candles;
} }
// 从蜡烛数据生成 Heikin-Ashi(平均K // 从蜡烛数据生成 Heikin-Ashi(平均K
@@ -9227,14 +9459,20 @@
function buildKLCFromAnalysis(data) { function buildKLCFromAnalysis(data) {
if (!data) return []; if (!data) return [];
// 检查是否使用小周期数据 // 根据基础显示的K线周期选择:次次周期 / 小周期 / 主周期
const useElementPeriod = $('#elementPeriodKline').is(':checked') && const useSubSubPeriod = $('#subSubPeriodKline').is(':checked') &&
data.element_klc_list && data.sub_sub_klc_list &&
Array.isArray(data.sub_sub_klc_list);
const useElementPeriod = !useSubSubPeriod &&
$('#elementPeriodKline').is(':checked') &&
data.element_klc_list &&
Array.isArray(data.element_klc_list); Array.isArray(data.element_klc_list);
const klcList = useElementPeriod ? data.element_klc_list : data.klc_list; const klcList = useSubSubPeriod
? data.sub_sub_klc_list
: (useElementPeriod ? data.element_klc_list : data.klc_list);
if (!klcList) return []; if (!klcList || !Array.isArray(klcList)) return [];
const klcCandles = []; const klcCandles = [];
@@ -9242,11 +9480,9 @@
klcList.forEach(klc => { klcList.forEach(klc => {
if (!klc || !klc.date) return; if (!klc || !klc.date) return;
// 使用KLC的date字段,转换为时间戳格式
const date = new Date(klc.date); const date = new Date(klc.date);
const timestamp = date.getTime() / 1000; const timestamp = date.getTime() / 1000;
// 创建KLC蜡烛数据
const candle = { const candle = {
time: timestamp, time: timestamp,
open: klc.open || 0, open: klc.open || 0,