添加笔中枢,删除了很多没有用的方法
This commit is contained in:
@@ -19,6 +19,9 @@ class ChanBI():
|
|||||||
self.start_time = klc.start_time
|
self.start_time = klc.start_time
|
||||||
self.macd_hist = 0
|
self.macd_hist = 0
|
||||||
self.macd_div = 0
|
self.macd_div = 0
|
||||||
|
self.seg = None
|
||||||
|
def set_seg(self, seg):
|
||||||
|
self.seg = seg
|
||||||
def set_macdhist(self, macd_hist):
|
def set_macdhist(self, macd_hist):
|
||||||
self.macd_hist = macd_hist
|
self.macd_hist = macd_hist
|
||||||
def set_macd_div(self, macd_div):
|
def set_macd_div(self, macd_div):
|
||||||
@@ -39,8 +42,16 @@ class ChanBI():
|
|||||||
self.macd_hist += klu.macdhist
|
self.macd_hist += klu.macdhist
|
||||||
if self.dir == Chan_BI_DIR.DOWN and klu.macdhist < 0:
|
if self.dir == Chan_BI_DIR.DOWN and klu.macdhist < 0:
|
||||||
self.macd_hist -= klu.macdhist
|
self.macd_hist -= klu.macdhist
|
||||||
def check_overlap(self):
|
def check_bi_zs_overlap(self):
|
||||||
if self.next and self.next.next:
|
if self.next and self.next.next:
|
||||||
|
if self.dir == Chan_BI_DIR.UP:
|
||||||
|
return self.low < self.next.next.high
|
||||||
|
else:
|
||||||
|
return self.high > self.next.next.low
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
def check_overlap(self):
|
||||||
|
if self.next and self.next.next and self.next.next.is_sure:
|
||||||
if self.dir == Chan_BI_DIR.UP:
|
if self.dir == Chan_BI_DIR.UP:
|
||||||
return self.high > self.next.low and self.high < self.next.next.high
|
return self.high > self.next.low and self.high < self.next.next.high
|
||||||
else:
|
else:
|
||||||
|
|||||||
+14
-29
@@ -7,49 +7,34 @@ class ChanBIZS():
|
|||||||
self.start_time = self.start_klc.start_time
|
self.start_time = self.start_klc.start_time
|
||||||
self.end_time = None
|
self.end_time = None
|
||||||
self.index = index
|
self.index = index
|
||||||
self.next = None
|
|
||||||
self.pre = None
|
|
||||||
self.start_bi = start_bi
|
self.start_bi = start_bi
|
||||||
self.bi_list = []
|
self.bi_list = []
|
||||||
self.bi_list.append(start_bi)
|
self.bi_list.append(start_bi)
|
||||||
self.end_bi = None
|
self.end_bi = None
|
||||||
self.last_bi_in = None
|
|
||||||
self.bi_out = None
|
self.bi_out = None
|
||||||
self.is_sure = False
|
self.is_sure = False
|
||||||
self.zg = 0
|
self.zg = 0
|
||||||
self.zd = 0
|
self.zd = 0
|
||||||
|
self.gg = 0
|
||||||
|
self.dd = 0
|
||||||
self.dir = ddir
|
self.dir = ddir
|
||||||
self.sure_time = None
|
self.sure_time = None
|
||||||
self.end_klc = None
|
self.end_klc = None
|
||||||
self.bi_out_count = 0
|
def set_end_bi(self, end_bi, sure_bi):
|
||||||
self.bi_out_list = []
|
self.end_bi = end_bi
|
||||||
def set_last_bi_in(self, last_bi_in):
|
self.set_end_time(end_bi.end_klc.end_time)
|
||||||
self.last_bi_in = last_bi_in
|
|
||||||
def set_bi_out(self, bi_out):
|
|
||||||
if bi_out:
|
|
||||||
#print(bi_out.start_klc.start_time, bi_out.sure_time, bi_out.dir, bi_out_seg.dir, len(self.bi_out_list))
|
|
||||||
if len(self.bi_out_list) > 0:
|
|
||||||
last_bi = self.bi_out_list[-1]
|
|
||||||
if last_bi.index != bi_out.index:
|
|
||||||
self.bi_out_list.append(bi_out)
|
|
||||||
else:
|
|
||||||
self.bi_out_list.append(bi_out)
|
|
||||||
self.bi_out = bi_out
|
|
||||||
def set_end_klc(self, end_klc, sure_time, bi_out_count):
|
|
||||||
self.end_klc = end_klc
|
|
||||||
self.set_end_time(end_klc.end_time)
|
|
||||||
self.is_sure = True
|
self.is_sure = True
|
||||||
self.sure_time = sure_time
|
self.sure_time = sure_bi.sure_time
|
||||||
self.bi_out_count = bi_out_count
|
|
||||||
def set_pre(self, pre):
|
|
||||||
self.pre = pre
|
|
||||||
def set_next(self, next):
|
|
||||||
self.next = next
|
|
||||||
def set_end_time(self, end_time):
|
def set_end_time(self, end_time):
|
||||||
self.end_time = end_time
|
self.end_time = end_time
|
||||||
def add_klc(self, klc):
|
|
||||||
self.klc_list.append(klc)
|
|
||||||
def set_zg(self, zg):
|
def set_zg(self, zg):
|
||||||
self.zg = zg
|
self.zg = zg
|
||||||
def set_zd(self, zd):
|
def set_zd(self, zd):
|
||||||
self.zd = zd
|
self.zd = zd
|
||||||
|
def set_gg(self, gg):
|
||||||
|
self.gg = gg
|
||||||
|
def set_dd(self, dd):
|
||||||
|
self.dd = dd
|
||||||
|
def add_bi(self, bi: ChanBI):
|
||||||
|
if bi:
|
||||||
|
self.bi_list.append(bi)
|
||||||
@@ -48,6 +48,7 @@ class ChanKLC():
|
|||||||
self.ema52 = klu.ema52
|
self.ema52 = klu.ema52
|
||||||
self.ema24 = klu.ema24
|
self.ema24 = klu.ema24
|
||||||
self.trend = Chan_PRICE_TREND.UNKNOWN
|
self.trend = Chan_PRICE_TREND.UNKNOWN
|
||||||
|
self.exception = klu.exception
|
||||||
def set_trend(self, trend):
|
def set_trend(self, trend):
|
||||||
self.trend = trend
|
self.trend = trend
|
||||||
def to_string(self):
|
def to_string(self):
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class ChanKLU:
|
|||||||
self.body_ratio = self.body / self.range
|
self.body_ratio = self.body / self.range
|
||||||
self.upper_shadow_ratio = self.upper_shadow / self.body
|
self.upper_shadow_ratio = self.upper_shadow / self.body
|
||||||
self.lower_shadow_ratio = self.lower_shadow / self.body
|
self.lower_shadow_ratio = self.lower_shadow / self.body
|
||||||
|
self.exception = False
|
||||||
|
self.cal_exception()
|
||||||
self.candle_dir = Chan_K_DIR.CROSS if self.close == self.open else Chan_K_DIR.BULL if self.close > self.open else Chan_K_DIR.BEAR
|
self.candle_dir = Chan_K_DIR.CROSS if self.close == self.open else Chan_K_DIR.BULL if self.close > self.open else Chan_K_DIR.BEAR
|
||||||
|
|
||||||
self.continue_div = 0
|
self.continue_div = 0
|
||||||
@@ -65,6 +67,10 @@ class ChanKLU:
|
|||||||
#print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength)
|
#print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength)
|
||||||
def set_macd_state(self, state):
|
def set_macd_state(self, state):
|
||||||
self.macd_state = state
|
self.macd_state = state
|
||||||
|
def cal_exception(self):
|
||||||
|
if self.upper_shadow_ratio > 5 or self.lower_shadow_ratio > 5:
|
||||||
|
self.exception = True
|
||||||
|
#print(self.time, self.upper_shadow_ratio, self.lower_shadow_ratio, self.body, self.lower_shadow, self.upper_shadow, self.high, self.low, self.close, self.open)
|
||||||
def set_trend(self, trend):
|
def set_trend(self, trend):
|
||||||
self.trend = trend
|
self.trend = trend
|
||||||
def set_next(self, next):
|
def set_next(self, next):
|
||||||
|
|||||||
+35
-13
@@ -246,7 +246,7 @@ class ChanLun():
|
|||||||
if last_down_sbi.has_fx_gap:
|
if last_down_sbi.has_fx_gap:
|
||||||
look_for_bottom = True
|
look_for_bottom = True
|
||||||
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
|
last_seg.pre_set_end_bi(bi_list[last_down_sbi.start_bi.index - 1])
|
||||||
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN)
|
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg.set_next(seg)
|
last_seg.set_next(seg)
|
||||||
seg.set_pre(last_seg)
|
seg.set_pre(last_seg)
|
||||||
@@ -272,7 +272,7 @@ class ChanLun():
|
|||||||
#print(bi.start_time, look_for_top, "UP 3")
|
#print(bi.start_time, look_for_top, "UP 3")
|
||||||
else:
|
else:
|
||||||
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
|
last_seg.set_end_bi(bi_list[last_down_sbi.start_bi.index - 1], bi)
|
||||||
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN)
|
seg = ChanSEG(last_down_sbi.start_bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg.set_next(seg)
|
last_seg.set_next(seg)
|
||||||
seg.set_pre(last_seg)
|
seg.set_pre(last_seg)
|
||||||
@@ -340,7 +340,7 @@ class ChanLun():
|
|||||||
if last_up_sbi.has_fx_gap:
|
if last_up_sbi.has_fx_gap:
|
||||||
look_for_top = True
|
look_for_top = True
|
||||||
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
|
last_seg.pre_set_end_bi(bi_list[last_up_sbi.start_bi.index - 1])
|
||||||
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP)
|
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg.set_next(seg)
|
last_seg.set_next(seg)
|
||||||
seg.set_pre(last_seg)
|
seg.set_pre(last_seg)
|
||||||
@@ -366,7 +366,7 @@ class ChanLun():
|
|||||||
#print(bi.start_time, look_for_top, "DOWN 3")
|
#print(bi.start_time, look_for_top, "DOWN 3")
|
||||||
else:
|
else:
|
||||||
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
|
last_seg.set_end_bi(bi_list[last_up_sbi.start_bi.index - 1], bi)
|
||||||
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP)
|
seg = ChanSEG(last_up_sbi.start_bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
||||||
#print(last_up_sbi.start_bi.start_time)
|
#print(last_up_sbi.start_bi.start_time)
|
||||||
last_seg.set_next(seg)
|
last_seg.set_next(seg)
|
||||||
seg.set_pre(last_seg)
|
seg.set_pre(last_seg)
|
||||||
@@ -414,14 +414,14 @@ class ChanLun():
|
|||||||
else:
|
else:
|
||||||
if bi.check_overlap():
|
if bi.check_overlap():
|
||||||
if bi.dir == Chan_BI_DIR.UP:
|
if bi.dir == Chan_BI_DIR.UP:
|
||||||
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP)
|
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.UP, bi)
|
||||||
last_up_bi = bi
|
last_up_bi = bi
|
||||||
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
last_up_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg = seg
|
last_seg = seg
|
||||||
#print(bi.start_time, 'Create first UP SEG')
|
#print(bi.start_time, 'Create first UP SEG')
|
||||||
else:
|
else:
|
||||||
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN)
|
seg = ChanSEG(bi, len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||||||
last_down_bi = bi
|
last_down_bi = bi
|
||||||
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
last_down_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
@@ -448,7 +448,7 @@ class ChanLun():
|
|||||||
# The confirmed
|
# The confirmed
|
||||||
print("Last UP seg is broken, create a new seg. 1")
|
print("Last UP seg is broken, create a new seg. 1")
|
||||||
seg.pre_set_end_bi(bi_list[i])
|
seg.pre_set_end_bi(bi_list[i])
|
||||||
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN)
|
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.DOWN, bi)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg = seg_list[-2]
|
last_seg = seg_list[-2]
|
||||||
if len(last_seg.bi_list) > 3:
|
if len(last_seg.bi_list) > 3:
|
||||||
@@ -460,7 +460,7 @@ class ChanLun():
|
|||||||
if bi_list[i].low < last_seg_peak:
|
if bi_list[i].low < last_seg_peak:
|
||||||
print("Last DOWN seg is broken, create a new seg. 1")
|
print("Last DOWN seg is broken, create a new seg. 1")
|
||||||
seg.pre_set_end_bi(bi_list[i])
|
seg.pre_set_end_bi(bi_list[i])
|
||||||
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP)
|
seg = ChanSEG(bi_list[i+1], len(seg_list), Chan_SEG_DIR.UP, bi)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg = seg_list[-2]
|
last_seg = seg_list[-2]
|
||||||
if len(last_seg.bi_list) > 3:
|
if len(last_seg.bi_list) > 3:
|
||||||
@@ -478,12 +478,13 @@ class ChanLun():
|
|||||||
if bi_list[i].high > last_seg_peak:
|
if bi_list[i].high > last_seg_peak:
|
||||||
print("Last seg is broken, create a new seg. 2")
|
print("Last seg is broken, create a new seg. 2")
|
||||||
last_seg.pre_set_end_bi(bi_list[i-1])
|
last_seg.pre_set_end_bi(bi_list[i-1])
|
||||||
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP)
|
seg = ChanSEG(bi_list[i], len(seg_list), Chan_SEG_DIR.UP, bi)
|
||||||
seg_list.append(seg)
|
seg_list.append(seg)
|
||||||
last_seg = seg
|
last_seg = seg
|
||||||
last_seg_bi = bi_list[i]
|
last_seg_bi = bi_list[i]
|
||||||
break
|
break
|
||||||
"""
|
"""
|
||||||
|
self.cal_bi_zs(seg_list)
|
||||||
return seg_list
|
return seg_list
|
||||||
def cal_trend(self, klc_list):
|
def cal_trend(self, klc_list):
|
||||||
"""
|
"""
|
||||||
@@ -1170,7 +1171,7 @@ class ChanLun():
|
|||||||
# SEG is not in ZS
|
# SEG is not in ZS
|
||||||
if seg.is_sure:
|
if seg.is_sure:
|
||||||
if ((seg.low > last_zs.zg and seg.high > last_zs.zg) or (seg.high < last_zs.zd and seg.low < last_zs.zd)):
|
if ((seg.low > last_zs.zg and seg.high > last_zs.zg) or (seg.high < last_zs.zd and seg.low < last_zs.zd)):
|
||||||
last_zs.set_end_klc(seg.pre.pre.end_bi.end_klc, seg.sure_time, bi_out_count, seg)
|
last_zs.set_end_klc(seg.pre.end_bi.end_klc, seg.sure_time, bi_out_count, seg.pre)
|
||||||
bi_out_count = 0
|
bi_out_count = 0
|
||||||
#print(seg.start_bi.start_klc.start_time)
|
#print(seg.start_bi.start_klc.start_time)
|
||||||
first_bi_out = None
|
first_bi_out = None
|
||||||
@@ -1285,6 +1286,13 @@ class ChanLun():
|
|||||||
bsp_list.append(bsp)
|
bsp_list.append(bsp)
|
||||||
#self.print_zs(zs_list)
|
#self.print_zs(zs_list)
|
||||||
return zs_list
|
return zs_list
|
||||||
|
def cal_bi_zs(self, seg_list):
|
||||||
|
bi_zs_list = []
|
||||||
|
for seg in seg_list:
|
||||||
|
zs_list = seg.cal_bi_zs()
|
||||||
|
if len(zs_list) > 0:
|
||||||
|
bi_zs_list.append(zs_list)
|
||||||
|
return bi_zs_list
|
||||||
|
|
||||||
def get_decimal(self, value):
|
def get_decimal(self, value):
|
||||||
return Decimal("{:.2f}".format(value))
|
return Decimal("{:.2f}".format(value))
|
||||||
@@ -1297,19 +1305,33 @@ class ChanLun():
|
|||||||
for klu in klu_list:
|
for klu in klu_list:
|
||||||
if len(klc_list) > 0:
|
if len(klc_list) > 0:
|
||||||
last_klc = klc_list[-1]
|
last_klc = klc_list[-1]
|
||||||
included = last_klc.check_klu_included(klu)
|
if klu.exception:
|
||||||
if not included:
|
|
||||||
ddir = Chan_KLINE_DIR.DOWN
|
ddir = Chan_KLINE_DIR.DOWN
|
||||||
if last_klc.high < klu.high:
|
if last_klc.high < klu.high:
|
||||||
ddir = Chan_KLINE_DIR.UP
|
ddir = Chan_KLINE_DIR.UP
|
||||||
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
|
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
|
||||||
|
klc.high = klu.close if klu.close > klu.open else klu.open
|
||||||
|
klc.low = klu.open if klu.close > klu.open else klu.close
|
||||||
klc_list.append(klc)
|
klc_list.append(klc)
|
||||||
last_klc.set_next(klc)
|
last_klc.set_next(klc)
|
||||||
klc.set_pre(last_klc)
|
klc.set_pre(last_klc)
|
||||||
last_klc.set_end_klu(last_klu)
|
last_klc.set_end_klu(last_klu)
|
||||||
klc.set_pre_fx()
|
klc.set_pre_fx()
|
||||||
|
#print(klu.time, klu.high, klu.low, klu.close, klu.open, klu.exception)
|
||||||
else:
|
else:
|
||||||
last_klc.add_klu(klu)
|
included = last_klc.check_klu_included(klu)
|
||||||
|
if not included:
|
||||||
|
ddir = Chan_KLINE_DIR.DOWN
|
||||||
|
if last_klc.high < klu.high:
|
||||||
|
ddir = Chan_KLINE_DIR.UP
|
||||||
|
klc = ChanKLC(klu, index=len(klc_list), ddir=ddir)
|
||||||
|
klc_list.append(klc)
|
||||||
|
last_klc.set_next(klc)
|
||||||
|
klc.set_pre(last_klc)
|
||||||
|
last_klc.set_end_klu(last_klu)
|
||||||
|
klc.set_pre_fx()
|
||||||
|
else:
|
||||||
|
last_klc.add_klu(klu)
|
||||||
else:
|
else:
|
||||||
ddir = Chan_KLINE_DIR.UP
|
ddir = Chan_KLINE_DIR.UP
|
||||||
if klu.open > klu.close:
|
if klu.open > klu.close:
|
||||||
|
|||||||
+107
-6
@@ -1,12 +1,12 @@
|
|||||||
import copy
|
import copy
|
||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
|
|
||||||
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_SEG_DIR
|
from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_SEG_DIR, Chan_BI_DIR, Chan_ZS_DIR
|
||||||
import ChanKLU
|
|
||||||
import ChanCTime
|
import ChanCTime
|
||||||
from ChanBI import ChanBI
|
from ChanBI import ChanBI
|
||||||
|
from ChanBIZS import ChanBIZS
|
||||||
class ChanSEG():
|
class ChanSEG():
|
||||||
def __init__(self, start_bi: ChanBI, index, ddir=Chan_SEG_DIR.UP):
|
def __init__(self, start_bi: ChanBI, index, ddir=Chan_SEG_DIR.UP, pre_end_bi: ChanBI = None):
|
||||||
self.start_bi = start_bi
|
self.start_bi = start_bi
|
||||||
self.start_time = start_bi.start_time
|
self.start_time = start_bi.start_time
|
||||||
self.end_time = None
|
self.end_time = None
|
||||||
@@ -28,6 +28,17 @@ class ChanSEG():
|
|||||||
self.sure_time = None
|
self.sure_time = None
|
||||||
self.macd_hist = 0
|
self.macd_hist = 0
|
||||||
self.macd_div = 0
|
self.macd_div = 0
|
||||||
|
self.start_bi.set_seg(self)
|
||||||
|
self.pre_end_bi = pre_end_bi
|
||||||
|
if self.pre_end_bi:
|
||||||
|
self.ini_seg()
|
||||||
|
def ini_seg(self):
|
||||||
|
next_bi = self.start_bi.next
|
||||||
|
for index in range(self.start_bi.index+1, self.pre_end_bi.index):
|
||||||
|
if next_bi:
|
||||||
|
self.bi_list.append(next_bi)
|
||||||
|
next_bi.set_seg(self)
|
||||||
|
next_bi = next_bi.next
|
||||||
def set_macdhist(self, macd_hist):
|
def set_macdhist(self, macd_hist):
|
||||||
self.macd_hist = macd_hist
|
self.macd_hist = macd_hist
|
||||||
def set_macd_div(self, macd_div):
|
def set_macd_div(self, macd_div):
|
||||||
@@ -63,14 +74,104 @@ class ChanSEG():
|
|||||||
self.is_sure = True
|
self.is_sure = True
|
||||||
self.format_bi_list()
|
self.format_bi_list()
|
||||||
def format_bi_list(self):
|
def format_bi_list(self):
|
||||||
bi_list = []
|
self.bi_list = []
|
||||||
bi_list.append(self.start_bi)
|
self.bi_list.append(self.start_bi)
|
||||||
if self.end_bi:
|
if self.end_bi:
|
||||||
next_bi = self.start_bi.next
|
next_bi = self.start_bi.next
|
||||||
for i in range(self.start_bi.index, self.end_bi.index):
|
for i in range(self.start_bi.index, self.end_bi.index):
|
||||||
if next_bi:
|
if next_bi:
|
||||||
self.bi_list.append(next_bi)
|
self.bi_list.append(next_bi)
|
||||||
|
next_bi.set_seg(self)
|
||||||
next_bi = next_bi.next
|
next_bi = next_bi.next
|
||||||
def add_bi(self, bi: ChanBI):
|
def add_bi(self, bi: ChanBI):
|
||||||
if len(self.bi_list) > 0:
|
if len(self.bi_list) > 0:
|
||||||
self.bi_list.append(bi)
|
self.bi_list.append(bi)
|
||||||
|
bi.set_seg(self)
|
||||||
|
def cal_bi_zs(self):
|
||||||
|
zs_list = []
|
||||||
|
if len(self.bi_list) > 3:
|
||||||
|
last_zs = None
|
||||||
|
zs_count = 0
|
||||||
|
if self.dir == Chan_SEG_DIR.UP:
|
||||||
|
for index in range(1, len(self.bi_list)):
|
||||||
|
bi = self.bi_list[index]
|
||||||
|
if len(zs_list) == 0 or (last_zs and last_zs.is_sure):
|
||||||
|
if bi.next and bi.next.next and bi.next.next.is_sure and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN:
|
||||||
|
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP)
|
||||||
|
zs.set_zg(zg)
|
||||||
|
zs.set_zd(zd)
|
||||||
|
zs.set_gg(gg)
|
||||||
|
zs.set_dd(dd)
|
||||||
|
zs.add_bi(bi.next)
|
||||||
|
zs.add_bi(bi.next.next)
|
||||||
|
zs_list.append(zs)
|
||||||
|
last_zs = zs
|
||||||
|
else:
|
||||||
|
if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.DOWN and bi.is_sure:
|
||||||
|
if bi.low < last_zs.zg:
|
||||||
|
last_zs.add_bi(bi.pre)
|
||||||
|
last_zs.add_bi(bi)
|
||||||
|
else:
|
||||||
|
last_zs.set_end_bi(last_zs.bi_list[-1], bi)
|
||||||
|
if bi.next and bi.next.next and bi.next.next.is_sure and bi.next.next.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.DOWN:
|
||||||
|
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.UP)
|
||||||
|
zs.set_zg(zg)
|
||||||
|
zs.set_zd(zd)
|
||||||
|
zs.set_gg(gg)
|
||||||
|
zs.set_dd(dd)
|
||||||
|
zs.add_bi(bi.next)
|
||||||
|
zs.add_bi(bi.next.next)
|
||||||
|
zs_list.append(zs)
|
||||||
|
last_zs = zs
|
||||||
|
else:
|
||||||
|
for index in range(1, len(self.bi_list)):
|
||||||
|
bi = self.bi_list[index]
|
||||||
|
if len(zs_list) == 0 or (last_zs and last_zs.is_sure):
|
||||||
|
if bi.next and bi.next.next and bi.next.next.is_sure and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.UP:
|
||||||
|
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN)
|
||||||
|
zs.set_zg(zg)
|
||||||
|
zs.set_zd(zd)
|
||||||
|
zs.set_gg(gg)
|
||||||
|
zs.set_dd(dd)
|
||||||
|
zs.add_bi(bi.next)
|
||||||
|
zs.add_bi(bi.next.next)
|
||||||
|
zs_list.append(zs)
|
||||||
|
last_zs = zs
|
||||||
|
else:
|
||||||
|
if bi.index > last_zs.bi_list[-1].index and bi.dir == Chan_BI_DIR.UP and bi.is_sure:
|
||||||
|
if bi.high > last_zs.zd:
|
||||||
|
last_zs.add_bi(bi.pre)
|
||||||
|
last_zs.add_bi(bi)
|
||||||
|
else:
|
||||||
|
last_zs.set_end_bi(last_zs.bi_list[-1], bi)
|
||||||
|
if bi.next and bi.next.next and bi.next.next.is_sure and bi.next.next.index <= self.bi_list[-1].index and bi.check_bi_zs_overlap() and bi.dir == Chan_BI_DIR.UP:
|
||||||
|
zg = min(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
zd = max(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
gg = max(bi.high, bi.next.high, bi.next.next.high)
|
||||||
|
dd = min(bi.low, bi.next.low, bi.next.next.low)
|
||||||
|
zs = ChanBIZS(bi, len(zs_list), Chan_ZS_DIR.DOWN)
|
||||||
|
zs.set_zg(zg)
|
||||||
|
zs.set_zd(zd)
|
||||||
|
zs.set_gg(gg)
|
||||||
|
zs.set_dd(dd)
|
||||||
|
zs.add_bi(bi.next)
|
||||||
|
zs.add_bi(bi.next.next)
|
||||||
|
zs_list.append(zs)
|
||||||
|
last_zs = zs
|
||||||
|
if index == len(self.bi_list) - 1 and last_zs and not last_zs.is_sure:
|
||||||
|
last_zs.set_end_bi(last_zs.bi_list[-1], last_zs.bi_list[-1])
|
||||||
|
#print(self.start_time, len(zs_list))
|
||||||
|
print(self.bi_list[-1].end_time, "end_bi")
|
||||||
|
return zs_list
|
||||||
+68
-697
@@ -125,7 +125,7 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=
|
|||||||
|
|
||||||
# 添加请求计数和最大限制
|
# 添加请求计数和最大限制
|
||||||
request_count = 0
|
request_count = 0
|
||||||
max_requests = 50 # 最大请求次数,防止无限循环
|
max_requests = 300 # 最大请求次数,防止无限循环
|
||||||
|
|
||||||
# 分页加载数据
|
# 分页加载数据
|
||||||
while request_count < max_requests:
|
while request_count < max_requests:
|
||||||
@@ -188,8 +188,8 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time=
|
|||||||
# 限制数据条数的逻辑 - 优先考虑时间范围
|
# 限制数据条数的逻辑 - 优先考虑时间范围
|
||||||
if start_time and end_time:
|
if start_time and end_time:
|
||||||
# 如果指定了明确的时间范围,返回该时间范围内的所有数据
|
# 如果指定了明确的时间范围,返回该时间范围内的所有数据
|
||||||
if len(df) > 10000: # 防止数据量过大,设置一个合理的上限
|
if len(df) > 100000: # 防止数据量过大,设置一个合理的上限
|
||||||
df = df.tail(10000).reset_index(drop=True)
|
df = df.tail(100000).reset_index(drop=True)
|
||||||
elif limit and len(df) > limit:
|
elif limit and len(df) > limit:
|
||||||
# 如果没有指定明确时间范围,使用默认的limit限制
|
# 如果没有指定明确时间范围,使用默认的limit限制
|
||||||
df = df.tail(limit).reset_index(drop=True)
|
df = df.tail(limit).reset_index(drop=True)
|
||||||
@@ -391,8 +391,13 @@ def analyze_chan(df, symbol=None, timeframe=None):
|
|||||||
#print(bi_list[index].start_time, bi_list[index].start_klc.end_time, bi_list[index].dir)
|
#print(bi_list[index].start_time, bi_list[index].start_klc.end_time, bi_list[index].dir)
|
||||||
seg_list = chan.get_seg_list(bi_list)
|
seg_list = chan.get_seg_list(bi_list)
|
||||||
zs_list = chan.calculate_zs(bi_list, seg_list)
|
zs_list = chan.calculate_zs(bi_list, seg_list)
|
||||||
|
# 计算笔中枢(BI中枢)并拍平成列表
|
||||||
|
try:
|
||||||
|
bi_zs_nested = chan.cal_bi_zs(seg_list)
|
||||||
|
bi_zs_list = [zs for group in bi_zs_nested for zs in (group or [])] if bi_zs_nested else []
|
||||||
|
except Exception:
|
||||||
|
bi_zs_list = []
|
||||||
# 添加买卖点识别
|
# 添加买卖点识别
|
||||||
buy_sell_points = identify_trade_points(bi_list, seg_list, zs_list)
|
|
||||||
for bi in bi_list:
|
for bi in bi_list:
|
||||||
bi.cal_macdhist()
|
bi.cal_macdhist()
|
||||||
for bi in bi_list:
|
for bi in bi_list:
|
||||||
@@ -513,63 +518,6 @@ def analyze_chan(df, symbol=None, timeframe=None):
|
|||||||
'is_strong_fx': False
|
'is_strong_fx': False
|
||||||
})
|
})
|
||||||
|
|
||||||
# 提取KLU分型信息
|
|
||||||
klu_fx_info = []
|
|
||||||
for klu in klu_list:
|
|
||||||
if hasattr(klu, 'fx_type') and klu.fx_type != Chan_FX_TYPE.UNKNOWN:
|
|
||||||
try:
|
|
||||||
# 计算分型强度
|
|
||||||
fx_strength = 0
|
|
||||||
fx_strength_level = ""
|
|
||||||
is_strong_fx = False
|
|
||||||
|
|
||||||
# 尝试调用分型强度计算方法
|
|
||||||
if hasattr(klu, 'calculate_realtime_fx_strength'):
|
|
||||||
fx_strength = klu.calculate_realtime_fx_strength()
|
|
||||||
elif hasattr(klu, 'fx_strength'):
|
|
||||||
fx_strength = klu.fx_strength
|
|
||||||
|
|
||||||
# 尝试获取分型强度等级 - 基于强度值生成等级
|
|
||||||
if fx_strength >= 2:
|
|
||||||
fx_strength_level = "强"
|
|
||||||
is_strong_fx = True
|
|
||||||
elif fx_strength >= 1:
|
|
||||||
fx_strength_level = "中"
|
|
||||||
is_strong_fx = False
|
|
||||||
elif fx_strength >= 0:
|
|
||||||
fx_strength_level = "弱"
|
|
||||||
is_strong_fx = False
|
|
||||||
else:
|
|
||||||
fx_strength_level = "极弱"
|
|
||||||
is_strong_fx = False
|
|
||||||
|
|
||||||
# 确保分型确认状态
|
|
||||||
is_confirmed = getattr(klu, 'fx_confirmed', True)
|
|
||||||
|
|
||||||
klu_fx_info.append({
|
|
||||||
'time': klu.time,
|
|
||||||
'price': klu.low if klu.fx_type == Chan_FX_TYPE.BOTTOM else klu.high,
|
|
||||||
'fx_type': str(klu.fx_type).replace("Chan_FX_TYPE.", ""),
|
|
||||||
'is_bottom': klu.fx_type == Chan_FX_TYPE.BOTTOM,
|
|
||||||
'fx_strength': fx_strength, # 分型强度分数
|
|
||||||
'fx_strength_level': fx_strength_level, # 分型强度等级
|
|
||||||
'is_strong_fx': is_strong_fx, # 是否为强分型
|
|
||||||
'fx_confirmed': is_confirmed # 分型是否确认
|
|
||||||
})
|
|
||||||
except Exception as e:
|
|
||||||
# 如果出错,仍然添加基本信息,但分型强度为0
|
|
||||||
klu_fx_info.append({
|
|
||||||
'time': klu.time,
|
|
||||||
'price': klu.low if klu.fx_type == Chan_FX_TYPE.BOTTOM else klu.high,
|
|
||||||
'fx_type': str(klu.fx_type).replace("Chan_FX_TYPE.", ""),
|
|
||||||
'is_bottom': klu.fx_type == Chan_FX_TYPE.BOTTOM,
|
|
||||||
'fx_strength': 0,
|
|
||||||
'fx_strength_level': "",
|
|
||||||
'is_strong_fx': False,
|
|
||||||
'fx_confirmed': False
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
'klc_list': klc_list,
|
'klc_list': klc_list,
|
||||||
@@ -577,474 +525,12 @@ def analyze_chan(df, symbol=None, timeframe=None):
|
|||||||
'bi_list': bi_list,
|
'bi_list': bi_list,
|
||||||
'seg_list': seg_list,
|
'seg_list': seg_list,
|
||||||
'zs_list': zs_list,
|
'zs_list': zs_list,
|
||||||
'trade_points': buy_sell_points,
|
'bi_zs_list': bi_zs_list, # 添加BI中枢列表
|
||||||
'klc_fx_info': klc_fx_info, # KLC分型信息
|
'klc_fx_info': klc_fx_info, # KLC分型信息
|
||||||
'klu_fx_info': klu_fx_info, # 添加KLU分型信息
|
|
||||||
'chan_macd': chan_macd_data, # 添加ChanMACD分析数据
|
'chan_macd': chan_macd_data, # 添加ChanMACD分析数据
|
||||||
'ema52_dict': ema52_dict # 添加多时间周期EMA52数据
|
'ema52_dict': ema52_dict # 添加多时间周期EMA52数据
|
||||||
}
|
}
|
||||||
|
|
||||||
def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, start_time=None, end_time=None):
|
|
||||||
"""生成逐步计算的回放数据"""
|
|
||||||
replay_data = {}
|
|
||||||
|
|
||||||
# 预先获取完整的次周期数据(避免重复数据获取)
|
|
||||||
element_full_data = None
|
|
||||||
if element_timeframe and symbol:
|
|
||||||
# 一次性获取完整的次周期数据
|
|
||||||
element_full_data = get_kl_data(symbol, element_timeframe, start_time=start_time, end_time=end_time)
|
|
||||||
if element_full_data is not None and len(element_full_data) > 0:
|
|
||||||
# 一次性添加技术指标
|
|
||||||
element_full_data = add_indicators(element_full_data)
|
|
||||||
|
|
||||||
# 为每个K线索引计算分析结果
|
|
||||||
for i in range(1, len(df) + 1): # 从1开始,至少需要1根K线
|
|
||||||
try:
|
|
||||||
# 截取到当前索引的数据
|
|
||||||
current_df = df.iloc[:i].copy()
|
|
||||||
|
|
||||||
# 添加技术指标
|
|
||||||
current_df = add_indicators(current_df)
|
|
||||||
|
|
||||||
# 进行缠论分析
|
|
||||||
analysis_result = analyze_chan(current_df, symbol, timeframe)
|
|
||||||
|
|
||||||
# 计算MACD
|
|
||||||
macd_data = calculate_macd(current_df)
|
|
||||||
|
|
||||||
# 如果有次周期数据,筛选对应时间范围的数据
|
|
||||||
element_step_data = {}
|
|
||||||
if element_full_data is not None:
|
|
||||||
# 获取当前主周期时间范围
|
|
||||||
current_end_time = current_df['timestamp'].iloc[-1] if len(current_df) > 0 else None
|
|
||||||
|
|
||||||
if current_end_time:
|
|
||||||
# 筛选次周期数据:只取时间戳小于等于当前主周期结束时间的数据
|
|
||||||
element_current_df = element_full_data[element_full_data['timestamp'] <= current_end_time].copy()
|
|
||||||
|
|
||||||
if len(element_current_df) > 0:
|
|
||||||
# 重新对当前时间范围的次周期数据进行缠论分析
|
|
||||||
# 这样可以确保数据的准确性,避免时间筛选的复杂性
|
|
||||||
element_current_analysis = analyze_chan(element_current_df, symbol, element_timeframe)
|
|
||||||
|
|
||||||
# 直接使用分析结果,无需复杂的时间筛选
|
|
||||||
filtered_bi_list = element_current_analysis['bi_list']
|
|
||||||
filtered_seg_list = element_current_analysis['seg_list']
|
|
||||||
filtered_zs_list = element_current_analysis['zs_list']
|
|
||||||
filtered_trade_points = element_current_analysis['trade_points']
|
|
||||||
filtered_klc_fx = element_current_analysis['klc_fx_info']
|
|
||||||
filtered_klu_fx = element_current_analysis['klu_fx_info']
|
|
||||||
|
|
||||||
# 计算当前时间范围的MACD
|
|
||||||
element_macd_data = calculate_macd(element_current_df)
|
|
||||||
|
|
||||||
element_step_data = {
|
|
||||||
'element_kline_data': clean_dataframe_for_json(element_current_df).to_dict('records'),
|
|
||||||
'element_bi_list': [{
|
|
||||||
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
|
|
||||||
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
|
|
||||||
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
|
|
||||||
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
|
|
||||||
'direction': convert_direction(bi.dir),
|
|
||||||
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
|
|
||||||
} for bi in filtered_bi_list if bi.end_klc],
|
|
||||||
'element_uncompleted_bi_list': [{
|
|
||||||
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': None, # 未完成笔没有结束时间
|
|
||||||
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
|
|
||||||
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
|
|
||||||
'end_price': None, # 未完成笔没有结束价格
|
|
||||||
'direction': convert_direction(bi.dir),
|
|
||||||
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
|
|
||||||
} for bi in filtered_bi_list if not bi.end_klc],
|
|
||||||
'element_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(),
|
|
||||||
'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,
|
|
||||||
'sure_time': format_time_safely(seg.sure_time, client_tz) if seg.sure_time else None,
|
|
||||||
'start_price': seg.start_bi.start_klc.low if convert_direction(seg.dir) == 1 else seg.start_bi.start_klc.high,
|
|
||||||
'end_price': seg.end_bi.end_klc.high if convert_direction(seg.dir) == 1 else seg.end_bi.end_klc.low if seg.end_bi else None,
|
|
||||||
'direction': convert_direction(seg.dir)
|
|
||||||
} for seg in filtered_seg_list if seg.is_sure],
|
|
||||||
'element_uncompleted_seg_list': get_uncompleted_seg_list(filtered_seg_list, client_tz),
|
|
||||||
'element_zs_list': [{
|
|
||||||
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': (zs.end_klc.end_time if isinstance(zs.end_klc.end_time, str) else zs.end_klc.end_time.astimezone(client_tz).isoformat()) if zs.end_klc else None,
|
|
||||||
'zg': zs.zg,
|
|
||||||
'zd': zs.zd,
|
|
||||||
'gg': zs.gg,
|
|
||||||
'dd': zs.dd,
|
|
||||||
'is_sure': zs.is_sure
|
|
||||||
} for zs in filtered_zs_list if zs.end_klc],
|
|
||||||
'element_uncompleted_zs_list': [{
|
|
||||||
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': None,
|
|
||||||
'zg': zs.zg,
|
|
||||||
'zd': zs.zd,
|
|
||||||
'gg': zs.gg,
|
|
||||||
'dd': zs.dd,
|
|
||||||
'is_sure': zs.is_sure
|
|
||||||
} for zs in filtered_zs_list if not zs.is_sure],
|
|
||||||
'element_trade_points': [{
|
|
||||||
'type': point['type'],
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': point['price'],
|
|
||||||
'desc': point['desc']
|
|
||||||
} for point in filtered_trade_points],
|
|
||||||
'element_macd': element_macd_data,
|
|
||||||
'element_bollinger': {
|
|
||||||
'upper': element_current_df['bb_upper'].tolist(),
|
|
||||||
'middle': element_current_df['bb_middle'].tolist(),
|
|
||||||
'lower': element_current_df['bb_lower'].tolist()
|
|
||||||
},
|
|
||||||
'element_element_bollinger': {
|
|
||||||
'upper': element_current_df['element_bb_upper'].tolist(),
|
|
||||||
'middle': element_current_df['element_bb_middle'].tolist(),
|
|
||||||
'lower': element_current_df['element_bb_lower'].tolist()
|
|
||||||
},
|
|
||||||
# 添加次周期ATR数据
|
|
||||||
'element_atr': element_current_df['atr'].tolist(),
|
|
||||||
'element_klc_fx_info': [{
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': float(point['price']),
|
|
||||||
'fx_type': point['fx_type'],
|
|
||||||
'is_bottom': bool(point['is_bottom']),
|
|
||||||
'fx_strength': float(point['fx_strength']),
|
|
||||||
'fx_strength_level': str(point['fx_strength_level']),
|
|
||||||
'is_strong_fx': bool(point['is_strong_fx'])
|
|
||||||
} for point in filtered_klc_fx],
|
|
||||||
'element_klu_fx_info': [{
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': float(point['price']),
|
|
||||||
'fx_type': point['fx_type'],
|
|
||||||
'is_bottom': bool(point['is_bottom']),
|
|
||||||
'fx_strength': float(point['fx_strength']),
|
|
||||||
'fx_strength_level': str(point['fx_strength_level']),
|
|
||||||
'is_strong_fx': bool(point['is_strong_fx']),
|
|
||||||
'fx_confirmed': bool(point['fx_confirmed'])
|
|
||||||
} for point in filtered_klu_fx]
|
|
||||||
}
|
|
||||||
|
|
||||||
# 构建该索引对应的分析结果
|
|
||||||
step_data = {
|
|
||||||
'step_index': i-1, # 当前步骤索引
|
|
||||||
'total_steps': len(df), # 总步骤数
|
|
||||||
'has_element_data': element_timeframe is not None and len(element_step_data) > 0, # 是否包含次周期数据
|
|
||||||
'element_timeframe': element_timeframe, # 次周期时间框架
|
|
||||||
'kline_data': clean_dataframe_for_json(current_df).to_dict('records'),
|
|
||||||
'bi_list': [{
|
|
||||||
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': (bi.end_klc.end_time if isinstance(bi.end_klc.end_time, str) else bi.end_klc.end_time.astimezone(client_tz).isoformat()) if bi.end_klc else None,
|
|
||||||
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
|
|
||||||
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
|
|
||||||
'end_price': bi.end_klc.high if convert_direction(bi.dir) == 1 else bi.end_klc.low if bi.end_klc else None,
|
|
||||||
'direction': convert_direction(bi.dir),
|
|
||||||
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
|
|
||||||
} for bi in analysis_result['bi_list'] if bi.end_klc],
|
|
||||||
'uncompleted_bi_list': [{
|
|
||||||
'start_time': bi.start_klc.end_time if isinstance(bi.start_klc.end_time, str) else bi.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': None, # 未完成笔没有结束时间
|
|
||||||
'sure_time': format_time_safely(bi.sure_time, client_tz) if bi.sure_time else None,
|
|
||||||
'start_price': bi.start_klc.low if convert_direction(bi.dir) == 1 else bi.start_klc.high,
|
|
||||||
'end_price': None, # 未完成笔没有结束价格
|
|
||||||
'direction': convert_direction(bi.dir),
|
|
||||||
'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0
|
|
||||||
} for bi in analysis_result['bi_list'] if not bi.end_klc],
|
|
||||||
'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(),
|
|
||||||
'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,
|
|
||||||
'sure_time': format_time_safely(seg.sure_time, client_tz) if seg.sure_time else None,
|
|
||||||
'start_price': seg.start_bi.start_klc.low if convert_direction(seg.dir) == 1 else seg.start_bi.start_klc.high,
|
|
||||||
'end_price': seg.end_bi.end_klc.high if convert_direction(seg.dir) == 1 else seg.end_bi.end_klc.low if seg.end_bi else None,
|
|
||||||
'direction': convert_direction(seg.dir)
|
|
||||||
} for seg in analysis_result['seg_list'] if seg.is_sure],
|
|
||||||
'uncompleted_seg_list': get_uncompleted_seg_list(analysis_result['seg_list'], client_tz),
|
|
||||||
'zs_list': [{
|
|
||||||
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': (zs.end_klc.end_time if isinstance(zs.end_klc.end_time, str) else zs.end_klc.end_time.astimezone(client_tz).isoformat()) if zs.end_klc else None,
|
|
||||||
'zg': zs.zg,
|
|
||||||
'zd': zs.zd,
|
|
||||||
'gg': zs.gg,
|
|
||||||
'dd': zs.dd,
|
|
||||||
'is_sure': zs.is_sure
|
|
||||||
} for zs in analysis_result['zs_list'] if zs.end_klc],
|
|
||||||
'uncompleted_zs_list': [{
|
|
||||||
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
|
|
||||||
'end_time': None,
|
|
||||||
'zg': zs.zg,
|
|
||||||
'zd': zs.zd,
|
|
||||||
'gg': zs.gg,
|
|
||||||
'dd': zs.dd,
|
|
||||||
'is_sure': zs.is_sure
|
|
||||||
} for zs in analysis_result['zs_list'] if not zs.is_sure],
|
|
||||||
'trade_points': [{
|
|
||||||
'type': point['type'],
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': point['price'],
|
|
||||||
'desc': point['desc']
|
|
||||||
} for point in analysis_result['trade_points']],
|
|
||||||
'macd': macd_data,
|
|
||||||
'bollinger': {
|
|
||||||
'upper': current_df['bb_upper'].tolist(),
|
|
||||||
'middle': current_df['bb_middle'].tolist(),
|
|
||||||
'lower': current_df['bb_lower'].tolist()
|
|
||||||
},
|
|
||||||
'element_bollinger': {
|
|
||||||
'upper': current_df['element_bb_upper'].tolist(),
|
|
||||||
'middle': current_df['element_bb_middle'].tolist(),
|
|
||||||
'lower': current_df['element_bb_lower'].tolist()
|
|
||||||
},
|
|
||||||
# 添加ATR数据
|
|
||||||
'atr': current_df['atr'].tolist(),
|
|
||||||
'klc_fx_info': [{
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': float(point['price']),
|
|
||||||
'fx_type': point['fx_type'],
|
|
||||||
'is_bottom': bool(point['is_bottom']),
|
|
||||||
'fx_strength': float(point['fx_strength']), # 分型强度分数
|
|
||||||
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
|
|
||||||
'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型
|
|
||||||
} for point in analysis_result['klc_fx_info']],
|
|
||||||
'klu_fx_info': [{
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': float(point['price']),
|
|
||||||
'fx_type': point['fx_type'],
|
|
||||||
'is_bottom': bool(point['is_bottom']),
|
|
||||||
'fx_strength': float(point['fx_strength']), # 分型强度分数
|
|
||||||
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
|
|
||||||
'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
|
|
||||||
'fx_confirmed': bool(point['fx_confirmed']) # 分型是否确认
|
|
||||||
} for point in analysis_result['klu_fx_info']]
|
|
||||||
}
|
|
||||||
|
|
||||||
# 合并次周期数据到step_data中,如果没有次周期数据则提供空的占位符
|
|
||||||
if element_step_data:
|
|
||||||
step_data.update(element_step_data)
|
|
||||||
else:
|
|
||||||
# 提供空的次周期数据结构,确保前端可以统一处理
|
|
||||||
step_data.update({
|
|
||||||
'element_kline_data': [],
|
|
||||||
'element_bi_list': [],
|
|
||||||
'element_uncompleted_bi_list': [],
|
|
||||||
'element_seg_list': [],
|
|
||||||
'element_uncompleted_seg_list': [],
|
|
||||||
'element_zs_list': [],
|
|
||||||
'element_uncompleted_zs_list': [],
|
|
||||||
'element_trade_points': [],
|
|
||||||
'element_macd': {'macd': [], 'signal': [], 'histogram': []},
|
|
||||||
'element_bollinger': {'upper': [], 'middle': [], 'lower': []},
|
|
||||||
'element_element_bollinger': {'upper': [], 'middle': [], 'lower': []},
|
|
||||||
'element_atr': [],
|
|
||||||
'element_klc_fx_info': [],
|
|
||||||
'element_klu_fx_info': []
|
|
||||||
})
|
|
||||||
|
|
||||||
replay_data[i-1] = step_data # 使用0-based索引
|
|
||||||
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
continue
|
|
||||||
return replay_data
|
|
||||||
|
|
||||||
def identify_trade_points(bi_list, seg_list, zs_list):
|
|
||||||
"""识别缠论买卖点 - 多级别识别,减少滞后性"""
|
|
||||||
trade_points = []
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# 1. 基于笔的二三类买卖点识别(更及时)
|
|
||||||
trade_points.extend(identify_bi_trade_points(bi_list, zs_list))
|
|
||||||
|
|
||||||
# 2. 基于线段的一类买卖点识别(传统方法)
|
|
||||||
trade_points.extend(identify_seg_trade_points(seg_list))
|
|
||||||
|
|
||||||
# 3. 基于分型强度的预警点识别(最及时)
|
|
||||||
trade_points.extend(identify_fx_warning_points(bi_list))
|
|
||||||
|
|
||||||
# 4. 基于MACD背驰的买卖点识别
|
|
||||||
trade_points.extend(identify_macd_divergence_points(bi_list))
|
|
||||||
|
|
||||||
# 按时间排序
|
|
||||||
trade_points.sort(key=lambda x: x['time'])
|
|
||||||
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
def identify_bi_trade_points(bi_list, zs_list):
|
|
||||||
"""基于笔识别二三类买卖点 - 更及时的信号"""
|
|
||||||
trade_points = []
|
|
||||||
|
|
||||||
if len(bi_list) < 3:
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
# 构建中枢映射,便于快速查找
|
|
||||||
zs_map = {}
|
|
||||||
for zs in zs_list:
|
|
||||||
if zs.is_sure: # 只考虑已确认的中枢
|
|
||||||
zs_map[zs.start_klc.end_time] = zs
|
|
||||||
|
|
||||||
for i in range(2, len(bi_list)):
|
|
||||||
current_bi = bi_list[i]
|
|
||||||
prev_bi = bi_list[i-1]
|
|
||||||
prev_prev_bi = bi_list[i-2]
|
|
||||||
|
|
||||||
# 确保笔已完成
|
|
||||||
if not current_bi.end_klc or not prev_bi.end_klc or not prev_prev_bi.end_klc:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 二类买点:向下笔后的向上笔,且不创新低
|
|
||||||
if (convert_direction(prev_bi.dir) == -1 and
|
|
||||||
convert_direction(current_bi.dir) == 1):
|
|
||||||
|
|
||||||
prev_low = prev_bi.end_klc.low
|
|
||||||
current_end_price = current_bi.end_klc.high
|
|
||||||
|
|
||||||
# 检查是否不创新低(相对于前面的低点)
|
|
||||||
if i >= 4: # 至少需要5个笔来判断
|
|
||||||
earlier_lows = [bi.end_klc.low for bi in bi_list[max(0, i-4):i-1]
|
|
||||||
if convert_direction(bi.dir) == -1 and bi.end_klc]
|
|
||||||
if earlier_lows and prev_low > min(earlier_lows):
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.BUY2,
|
|
||||||
'time': current_bi.end_klc.end_time,
|
|
||||||
'price': current_end_price,
|
|
||||||
'desc': '二类买点(笔)'
|
|
||||||
})
|
|
||||||
|
|
||||||
# 二类卖点:向上笔后的向下笔,且不创新高
|
|
||||||
if (convert_direction(prev_bi.dir) == 1 and
|
|
||||||
convert_direction(current_bi.dir) == -1):
|
|
||||||
|
|
||||||
prev_high = prev_bi.end_klc.high
|
|
||||||
current_end_price = current_bi.end_klc.low
|
|
||||||
|
|
||||||
# 检查是否不创新高(相对于前面的高点)
|
|
||||||
if i >= 4: # 至少需要5个笔来判断
|
|
||||||
earlier_highs = [bi.end_klc.high for bi in bi_list[max(0, i-4):i-1]
|
|
||||||
if convert_direction(bi.dir) == 1 and bi.end_klc]
|
|
||||||
if earlier_highs and prev_high < max(earlier_highs):
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.SELL2,
|
|
||||||
'time': current_bi.end_klc.end_time,
|
|
||||||
'price': current_end_price,
|
|
||||||
'desc': '二类卖点(笔)'
|
|
||||||
})
|
|
||||||
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
def identify_seg_trade_points(seg_list):
|
|
||||||
"""基于线段识别一类买卖点 - 传统方法"""
|
|
||||||
trade_points = []
|
|
||||||
|
|
||||||
if len(seg_list) >= 3:
|
|
||||||
for i in range(2, len(seg_list)):
|
|
||||||
# 确保线段已完成
|
|
||||||
if seg_list[i].end_bi and seg_list[i-1].end_bi and seg_list[i-2].end_bi:
|
|
||||||
# 一类买点:向下-向上-向下的底分型
|
|
||||||
if (convert_direction(seg_list[i-2].dir) == -1 and
|
|
||||||
convert_direction(seg_list[i-1].dir) == 1 and
|
|
||||||
convert_direction(seg_list[i].dir) == -1):
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.BUY1,
|
|
||||||
'time': seg_list[i].end_bi.end_klc.end_time,
|
|
||||||
'price': seg_list[i].end_bi.end_klc.low,
|
|
||||||
'desc': '一类买点(线段)'
|
|
||||||
})
|
|
||||||
|
|
||||||
# 一类卖点:向上-向下-向上的顶分型
|
|
||||||
if (convert_direction(seg_list[i-2].dir) == 1 and
|
|
||||||
convert_direction(seg_list[i-1].dir) == -1 and
|
|
||||||
convert_direction(seg_list[i].dir) == 1):
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.SELL1,
|
|
||||||
'time': seg_list[i].end_bi.end_klc.end_time,
|
|
||||||
'price': seg_list[i].end_bi.end_klc.high,
|
|
||||||
'desc': '一类卖点(线段)'
|
|
||||||
})
|
|
||||||
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
def identify_fx_warning_points(bi_list):
|
|
||||||
"""基于分型强度识别预警点 - 最及时的信号"""
|
|
||||||
trade_points = []
|
|
||||||
|
|
||||||
if len(bi_list) < 2:
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
# 检查最近的几个笔
|
|
||||||
recent_bis = bi_list[-3:] if len(bi_list) >= 3 else bi_list
|
|
||||||
|
|
||||||
for bi in recent_bis:
|
|
||||||
if not bi.end_klc:
|
|
||||||
continue
|
|
||||||
|
|
||||||
# 获取分型强度(如果有的话)
|
|
||||||
fx_strength = 0
|
|
||||||
if hasattr(bi.end_klc, 'cal_fx_strength'):
|
|
||||||
try:
|
|
||||||
fx_strength = bi.end_klc.cal_fx_strength(5)
|
|
||||||
except:
|
|
||||||
fx_strength = 0
|
|
||||||
|
|
||||||
# 强分型预警(分型强度>=2)
|
|
||||||
if fx_strength >= 2:
|
|
||||||
if convert_direction(bi.dir) == -1: # 向下笔结束,可能的底部
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.BUY3,
|
|
||||||
'time': bi.end_klc.end_time,
|
|
||||||
'price': bi.end_klc.low,
|
|
||||||
'desc': f'强分型预警-买点(强度:{fx_strength})'
|
|
||||||
})
|
|
||||||
elif convert_direction(bi.dir) == 1: # 向上笔结束,可能的顶部
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.SELL3,
|
|
||||||
'time': bi.end_klc.end_time,
|
|
||||||
'price': bi.end_klc.high,
|
|
||||||
'desc': f'强分型预警-卖点(强度:{fx_strength})'
|
|
||||||
})
|
|
||||||
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
def identify_macd_divergence_points(bi_list):
|
|
||||||
"""基于MACD背驰识别买卖点"""
|
|
||||||
trade_points = []
|
|
||||||
|
|
||||||
if len(bi_list) < 4:
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
# 检查最近的笔是否有背驰
|
|
||||||
for i in range(2, len(bi_list)):
|
|
||||||
current_bi = bi_list[i]
|
|
||||||
|
|
||||||
if not current_bi.end_klc or not hasattr(current_bi, 'macd_div'):
|
|
||||||
continue
|
|
||||||
|
|
||||||
# MACD背驰阈值
|
|
||||||
divergence_threshold = 0.3
|
|
||||||
|
|
||||||
# 向下笔的底背驰 -> 买点
|
|
||||||
if (convert_direction(current_bi.dir) == -1 and
|
|
||||||
hasattr(current_bi, 'macd_div') and
|
|
||||||
current_bi.macd_div > divergence_threshold):
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.BUY2,
|
|
||||||
'time': current_bi.end_klc.end_time,
|
|
||||||
'price': current_bi.end_klc.low,
|
|
||||||
'desc': f'MACD底背驰买点(背驰度:{current_bi.macd_div:.2f})'
|
|
||||||
})
|
|
||||||
|
|
||||||
# 向上笔的顶背驰 -> 卖点
|
|
||||||
elif (convert_direction(current_bi.dir) == 1 and
|
|
||||||
hasattr(current_bi, 'macd_div') and
|
|
||||||
current_bi.macd_div > divergence_threshold):
|
|
||||||
trade_points.append({
|
|
||||||
'type': TRADE_POINT_TYPE.SELL2,
|
|
||||||
'time': current_bi.end_klc.end_time,
|
|
||||||
'price': current_bi.end_klc.high,
|
|
||||||
'desc': f'MACD顶背驰卖点(背驰度:{current_bi.macd_div:.2f})'
|
|
||||||
})
|
|
||||||
|
|
||||||
return trade_points
|
|
||||||
|
|
||||||
# 辅助函数,转换缠论方向枚举为整数
|
# 辅助函数,转换缠论方向枚举为整数
|
||||||
def convert_direction(direction):
|
def convert_direction(direction):
|
||||||
"""转换方向枚举为数字"""
|
"""转换方向枚举为数字"""
|
||||||
@@ -1719,6 +1205,20 @@ def analyze():
|
|||||||
'dd': zs.dd,
|
'dd': zs.dd,
|
||||||
'is_sure': zs.is_sure # 添加中枢是否完成的标志
|
'is_sure': zs.is_sure # 添加中枢是否完成的标志
|
||||||
} for zs in analysis_result['zs_list'] if zs.end_klc],
|
} for zs in analysis_result['zs_list'] if zs.end_klc],
|
||||||
|
# 添加主周期BI中枢列表(已完成)
|
||||||
|
'bi_zs_list': [{
|
||||||
|
'start_time': (
|
||||||
|
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
|
||||||
|
if getattr(zs.start_klc, 'end_time', None) else
|
||||||
|
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
|
||||||
|
),
|
||||||
|
'end_time': (zs.end_time if isinstance(zs.end_time, str) else zs.end_time.astimezone(client_tz).isoformat()) if getattr(zs, 'end_time', None) else None,
|
||||||
|
'zg': zs.zg,
|
||||||
|
'zd': zs.zd,
|
||||||
|
'gg': zs.gg,
|
||||||
|
'dd': zs.dd,
|
||||||
|
'is_sure': bool(getattr(zs, 'is_sure', False))
|
||||||
|
} for zs in analysis_result.get('bi_zs_list', []) if getattr(zs, 'is_sure', False)],
|
||||||
# 添加未完成中枢列表
|
# 添加未完成中枢列表
|
||||||
'uncompleted_zs_list': [{
|
'uncompleted_zs_list': [{
|
||||||
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
|
'start_time': zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat(),
|
||||||
@@ -1729,12 +1229,21 @@ def analyze():
|
|||||||
'dd': zs.dd,
|
'dd': zs.dd,
|
||||||
'is_sure': zs.is_sure # 未完成中枢的is_sure为False
|
'is_sure': zs.is_sure # 未完成中枢的is_sure为False
|
||||||
} for zs in analysis_result['zs_list'] if not zs.is_sure],
|
} for zs in analysis_result['zs_list'] if not zs.is_sure],
|
||||||
'trade_points': [{
|
# 添加未完成BI中枢列表
|
||||||
'type': point['type'],
|
'uncompleted_bi_zs_list': [{
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
'start_time': (
|
||||||
'price': point['price'],
|
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
|
||||||
'desc': point['desc']
|
if getattr(zs.start_klc, 'end_time', None) else
|
||||||
} for point in analysis_result['trade_points']],
|
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
|
||||||
|
),
|
||||||
|
'end_time': None,
|
||||||
|
'zg': zs.zg,
|
||||||
|
'zd': zs.zd,
|
||||||
|
'gg': zs.gg,
|
||||||
|
'dd': zs.dd,
|
||||||
|
'is_sure': bool(getattr(zs, 'is_sure', False))
|
||||||
|
} for zs in analysis_result.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)],
|
||||||
|
|
||||||
'macd': macd_data,
|
'macd': macd_data,
|
||||||
# 添加布林带数据
|
# 添加布林带数据
|
||||||
'bollinger': {
|
'bollinger': {
|
||||||
@@ -1759,16 +1268,6 @@ def analyze():
|
|||||||
'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']) # 是否为强分型
|
||||||
} for point in analysis_result['klc_fx_info']],
|
} for point in analysis_result['klc_fx_info']],
|
||||||
'klu_fx_info': [{
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': float(point['price']),
|
|
||||||
'fx_type': point['fx_type'],
|
|
||||||
'is_bottom': bool(point['is_bottom']),
|
|
||||||
'fx_strength': float(point['fx_strength']), # 分型强度分数
|
|
||||||
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
|
|
||||||
'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
|
|
||||||
'fx_confirmed': bool(point['fx_confirmed']) # 分型是否确认
|
|
||||||
} for point in analysis_result['klu_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),
|
||||||
# 添加多时间周期EMA52数据
|
# 添加多时间周期EMA52数据
|
||||||
@@ -1885,13 +1384,35 @@ def analyze():
|
|||||||
'dd': zs.dd,
|
'dd': zs.dd,
|
||||||
'is_sure': zs.is_sure # 未完成中枢的is_sure为False
|
'is_sure': zs.is_sure # 未完成中枢的is_sure为False
|
||||||
} for zs in element_analysis['zs_list'] if not zs.is_sure]
|
} for zs in element_analysis['zs_list'] if not zs.is_sure]
|
||||||
|
|
||||||
|
# 添加次周期 BI 中枢(已完成/未完成)
|
||||||
|
result['element_bi_zs_list'] = [{
|
||||||
|
'start_time': (
|
||||||
|
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
|
||||||
|
if getattr(zs.start_klc, 'end_time', None) else
|
||||||
|
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
|
||||||
|
),
|
||||||
|
'end_time': (zs.end_time if isinstance(zs.end_time, str) else zs.end_time.astimezone(client_tz).isoformat()) if getattr(zs, 'end_time', None) else None,
|
||||||
|
'zg': zs.zg,
|
||||||
|
'zd': zs.zd,
|
||||||
|
'gg': zs.gg,
|
||||||
|
'dd': zs.dd,
|
||||||
|
'is_sure': bool(getattr(zs, 'is_sure', False))
|
||||||
|
} for zs in element_analysis.get('bi_zs_list', []) if getattr(zs, 'is_sure', False)]
|
||||||
|
result['element_uncompleted_bi_zs_list'] = [{
|
||||||
|
'start_time': (
|
||||||
|
(zs.start_klc.end_time if isinstance(zs.start_klc.end_time, str) else zs.start_klc.end_time.astimezone(client_tz).isoformat())
|
||||||
|
if getattr(zs.start_klc, 'end_time', None) else
|
||||||
|
(zs.start_klc.start_time if isinstance(zs.start_klc.start_time, str) else zs.start_klc.start_time.astimezone(client_tz).isoformat())
|
||||||
|
),
|
||||||
|
'end_time': None,
|
||||||
|
'zg': zs.zg,
|
||||||
|
'zd': zs.zd,
|
||||||
|
'gg': zs.gg,
|
||||||
|
'dd': zs.dd,
|
||||||
|
'is_sure': bool(getattr(zs, 'is_sure', False))
|
||||||
|
} for zs in element_analysis.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)]
|
||||||
|
|
||||||
result['element_trade_points'] = [{
|
|
||||||
'type': point['type'],
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': point['price'],
|
|
||||||
'desc': point['desc']
|
|
||||||
} for point in element_analysis['trade_points']]
|
|
||||||
|
|
||||||
# 添加小周期分型信息
|
# 添加小周期分型信息
|
||||||
result['element_klc_fx_info'] = [{
|
result['element_klc_fx_info'] = [{
|
||||||
@@ -1903,18 +1424,7 @@ def analyze():
|
|||||||
'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']) # 是否为强分型
|
||||||
} for point in element_analysis['klc_fx_info']]
|
} for point in element_analysis['klc_fx_info']]
|
||||||
|
|
||||||
result['element_klu_fx_info'] = [{
|
|
||||||
'time': format_time_safely(point['time'], client_tz),
|
|
||||||
'price': float(point['price']),
|
|
||||||
'fx_type': point['fx_type'],
|
|
||||||
'is_bottom': bool(point['is_bottom']),
|
|
||||||
'fx_strength': float(point['fx_strength']), # 分型强度分数
|
|
||||||
'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级
|
|
||||||
'is_strong_fx': bool(point['is_strong_fx']), # 是否为强分型
|
|
||||||
'fx_confirmed': bool(point['fx_confirmed']) # 分型是否确认
|
|
||||||
} for point in element_analysis['klu_fx_info']]
|
|
||||||
|
|
||||||
# 添加次周期ChanMACD分析数据
|
# 添加次周期ChanMACD分析数据
|
||||||
result['element_chan_macd'] = serialize_chan_macd_data(element_analysis.get('chan_macd', {}), client_tz)
|
result['element_chan_macd'] = serialize_chan_macd_data(element_analysis.get('chan_macd', {}), client_tz)
|
||||||
|
|
||||||
@@ -1990,145 +1500,6 @@ def search_stock():
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
return jsonify({'error': str(e)})
|
return jsonify({'error': str(e)})
|
||||||
|
|
||||||
@app.route('/api/test_element_data')
|
|
||||||
def test_element_data():
|
|
||||||
"""测试次周期数据是否正确生成"""
|
|
||||||
try:
|
|
||||||
symbol = request.args.get('symbol', 'SOL/USDT:USDT')
|
|
||||||
timeframe = request.args.get('timeframe', '1h')
|
|
||||||
element_timeframe = request.args.get('element_timeframe', '15m')
|
|
||||||
|
|
||||||
# 获取主周期数据
|
|
||||||
main_df = get_kl_data(symbol, timeframe, limit=3)
|
|
||||||
if main_df is None or len(main_df) == 0:
|
|
||||||
return jsonify({'error': '无法获取主周期数据'})
|
|
||||||
|
|
||||||
# 获取次周期数据
|
|
||||||
element_df = get_kl_data(symbol, element_timeframe,
|
|
||||||
start_time=main_df['timestamp'].iloc[0],
|
|
||||||
end_time=main_df['timestamp'].iloc[-1])
|
|
||||||
|
|
||||||
if element_df is None or len(element_df) == 0:
|
|
||||||
return jsonify({'error': '无法获取次周期数据'})
|
|
||||||
|
|
||||||
# 分析次周期数据
|
|
||||||
element_df = add_indicators(element_df)
|
|
||||||
element_analysis = analyze_chan(element_df, symbol, element_timeframe)
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'main_data_count': len(main_df),
|
|
||||||
'element_data_count': len(element_df),
|
|
||||||
'element_analysis': {
|
|
||||||
'bi_count': len(element_analysis['bi_list']),
|
|
||||||
'seg_count': len(element_analysis['seg_list']),
|
|
||||||
'zs_count': len(element_analysis['zs_list']),
|
|
||||||
'klc_fx_count': len(element_analysis['klc_fx_info']),
|
|
||||||
'klu_fx_count': len(element_analysis['klu_fx_info']),
|
|
||||||
'trade_points_count': len(element_analysis['trade_points'])
|
|
||||||
},
|
|
||||||
'sample_bi': [{'has_end_klc': bi.end_klc is not None,
|
|
||||||
'direction': convert_direction(bi.dir)}
|
|
||||||
for bi in element_analysis['bi_list'][:2]] if len(element_analysis['bi_list']) > 0 else [],
|
|
||||||
'sample_klc_fx': element_analysis['klc_fx_info'][:3] if len(element_analysis['klc_fx_info']) > 0 else [],
|
|
||||||
'sample_klu_fx': element_analysis['klu_fx_info'][:3] if len(element_analysis['klu_fx_info']) > 0 else []
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
import traceback
|
|
||||||
return jsonify({'error': str(e), 'traceback': traceback.format_exc()})
|
|
||||||
|
|
||||||
@app.route('/api/debug_replay_sample')
|
|
||||||
def debug_replay_sample():
|
|
||||||
"""调试接口:返回回放数据样本,方便前端调试"""
|
|
||||||
try:
|
|
||||||
symbol = request.args.get('symbol', 'SOL/USDT:USDT')
|
|
||||||
timeframe = request.args.get('timeframe', '1h')
|
|
||||||
element_timeframe = request.args.get('element_timeframe', '15m')
|
|
||||||
step = int(request.args.get('step', 2)) # 返回第几步的数据
|
|
||||||
|
|
||||||
# 获取少量数据进行测试
|
|
||||||
df = get_kl_data(symbol, timeframe, limit=5)
|
|
||||||
if df is None or len(df) == 0:
|
|
||||||
return jsonify({'error': '无法获取测试数据'})
|
|
||||||
|
|
||||||
# 生成回放数据
|
|
||||||
client_tz = timezone('Asia/Shanghai')
|
|
||||||
replay_data = generate_replay_data(
|
|
||||||
df, client_tz, symbol, element_timeframe,
|
|
||||||
start_time=None, end_time=None
|
|
||||||
)
|
|
||||||
|
|
||||||
if step not in replay_data:
|
|
||||||
return jsonify({'error': f'步骤 {step} 不存在,可用步骤:{list(replay_data.keys())}'})
|
|
||||||
|
|
||||||
# 返回指定步骤的完整数据
|
|
||||||
step_data = replay_data[step]
|
|
||||||
|
|
||||||
return jsonify({
|
|
||||||
'step': step,
|
|
||||||
'data': step_data,
|
|
||||||
'summary': {
|
|
||||||
'has_element_data': step_data.get('has_element_data', False),
|
|
||||||
'element_timeframe': step_data.get('element_timeframe'),
|
|
||||||
'main_bi_count': len(step_data.get('bi_list', [])),
|
|
||||||
'main_klc_fx_count': len(step_data.get('klc_fx_info', [])),
|
|
||||||
'main_klu_fx_count': len(step_data.get('klu_fx_info', [])),
|
|
||||||
'element_bi_count': len(step_data.get('element_bi_list', [])),
|
|
||||||
'element_klc_fx_count': len(step_data.get('element_klc_fx_info', [])),
|
|
||||||
'element_klu_fx_count': len(step_data.get('element_klu_fx_info', [])),
|
|
||||||
'element_kline_count': len(step_data.get('element_kline_data', []))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({'error': str(e)})
|
|
||||||
|
|
||||||
@app.route('/api/debug_replay_structure')
|
|
||||||
def debug_replay_structure():
|
|
||||||
"""调试接口:检查回放数据结构"""
|
|
||||||
try:
|
|
||||||
# 获取一个简单的测试案例
|
|
||||||
symbol = request.args.get('symbol', 'SOL/USDT:USDT')
|
|
||||||
timeframe = request.args.get('timeframe', '1h')
|
|
||||||
element_timeframe = request.args.get('element_timeframe', '15m')
|
|
||||||
|
|
||||||
# 获取少量数据进行测试
|
|
||||||
df = get_kl_data(symbol, timeframe, limit=5) # 只取5根K线
|
|
||||||
if df is None or len(df) == 0:
|
|
||||||
return jsonify({'error': '无法获取测试数据'})
|
|
||||||
|
|
||||||
# 生成回放数据
|
|
||||||
client_tz = timezone('Asia/Shanghai')
|
|
||||||
replay_data = generate_replay_data(
|
|
||||||
df, client_tz, symbol, element_timeframe,
|
|
||||||
start_time=None, end_time=None
|
|
||||||
)
|
|
||||||
|
|
||||||
# 返回结构信息
|
|
||||||
result = {
|
|
||||||
'total_steps': len(replay_data),
|
|
||||||
'sample_step_keys': list(replay_data[0].keys()) if len(replay_data) > 0 else [],
|
|
||||||
'has_element_data_in_steps': [],
|
|
||||||
'element_data_counts': {}
|
|
||||||
}
|
|
||||||
|
|
||||||
# 检查每个步骤的次周期数据
|
|
||||||
for step_idx, step_data in replay_data.items():
|
|
||||||
has_element = step_data.get('has_element_data', False)
|
|
||||||
result['has_element_data_in_steps'].append({
|
|
||||||
'step': step_idx,
|
|
||||||
'has_element_data': has_element,
|
|
||||||
'element_bi_count': len(step_data.get('element_bi_list', [])),
|
|
||||||
'element_klc_fx_count': len(step_data.get('element_klc_fx_info', [])),
|
|
||||||
'element_klu_fx_count': len(step_data.get('element_klu_fx_info', []))
|
|
||||||
})
|
|
||||||
|
|
||||||
return jsonify(result)
|
|
||||||
|
|
||||||
except Exception as e:
|
|
||||||
return jsonify({'error': str(e)})
|
|
||||||
|
|
||||||
# 已移除:/api/filter_stocks 路由
|
|
||||||
|
|
||||||
def get_uncompleted_seg_list(seg_list, client_tz):
|
def get_uncompleted_seg_list(seg_list, client_tz):
|
||||||
"""获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段"""
|
"""获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段"""
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ table.dataTable tbody tr:hover {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.chart-container {
|
.chart-container {
|
||||||
height: 400px;
|
height: 800px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.controls .row {
|
.controls .row {
|
||||||
|
|||||||
+275
-519
@@ -48,9 +48,10 @@
|
|||||||
margin-bottom: 6px;
|
margin-bottom: 6px;
|
||||||
color: #495057;
|
color: #495057;
|
||||||
}
|
}
|
||||||
|
/*主图大小*/
|
||||||
.chart-container {
|
.chart-container {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 900px;
|
height: 1200px;
|
||||||
margin-top: 10px;
|
margin-top: 10px;
|
||||||
border: 1px solid #e9ecef;
|
border: 1px solid #e9ecef;
|
||||||
border-radius: 8px;
|
border-radius: 8px;
|
||||||
@@ -635,9 +636,6 @@
|
|||||||
margin-right: 4px;
|
margin-right: 4px;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.bb-config-modal {
|
.bb-config-modal {
|
||||||
position: fixed;
|
position: fixed;
|
||||||
top: 0;
|
top: 0;
|
||||||
@@ -877,22 +875,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showMainZs">
|
<input class="form-check-input" type="checkbox" id="showMainZs">
|
||||||
<label class="form-check-label" for="showMainZs">中枢</label>
|
<label class="form-check-label" for="showMainZs">SEG中枢</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showMainUncompletedZs">
|
<input class="form-check-input" type="checkbox" id="showMainBiZs">
|
||||||
<label class="form-check-label" for="showMainUncompletedZs">未完成中枢</label>
|
<label class="form-check-label" for="showMainBiZs">BI中枢</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showKlcFxType" checked>
|
<input class="form-check-input" type="checkbox" id="showKlcFxType" checked>
|
||||||
<label class="form-check-label" for="showKlcFxType">KLC分型</label>
|
<label class="form-check-label" for="showKlcFxType">KLC分型</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showMainTrend" checked>
|
<input class="form-check-input" type="checkbox" id="showMainTrend">
|
||||||
<label class="form-check-label" for="showMainTrend">Trend</label>
|
<label class="form-check-label" for="showMainTrend">Trend</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="toggleUOnMain" checked>
|
<input class="form-check-input" type="checkbox" id="toggleUOnMain">
|
||||||
<label class="form-check-label" for="toggleUOnMain">显示U</label>
|
<label class="form-check-label" for="toggleUOnMain">显示U</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -915,11 +913,11 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showElementZs">
|
<input class="form-check-input" type="checkbox" id="showElementZs">
|
||||||
<label class="form-check-label" for="showElementZs">中枢</label>
|
<label class="form-check-label" for="showElementZs">SEG中枢</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showElementUncompletedZs">
|
<input class="form-check-input" type="checkbox" id="showElementBiZs">
|
||||||
<label class="form-check-label" for="showElementUncompletedZs">未完成中枢</label>
|
<label class="form-check-label" for="showElementBiZs">BI中枢</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="form-check form-check-inline">
|
<div class="form-check form-check-inline">
|
||||||
<input class="form-check-input" type="checkbox" id="showElementKlcFxType">
|
<input class="form-check-input" type="checkbox" id="showElementKlcFxType">
|
||||||
@@ -1712,9 +1710,10 @@
|
|||||||
$('#showMainZs').change(function() {
|
$('#showMainZs').change(function() {
|
||||||
updateChartDisplay();
|
updateChartDisplay();
|
||||||
});
|
});
|
||||||
|
// 添加主周期BI中枢复选框变更事件(委托绑定,避免DOM更新后失效)
|
||||||
// 添加未完成中枢复选框变更事件
|
console.log('初始化BI中枢事件绑定');
|
||||||
$('#showMainUncompletedZs').change(function() {
|
$(document).on('change', '#showMainBiZs', function() {
|
||||||
|
console.log('主BI中枢切换为:', $('#showMainBiZs').is(':checked'));
|
||||||
updateChartDisplay();
|
updateChartDisplay();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1740,9 +1739,9 @@
|
|||||||
$('#showElementZs').change(function() {
|
$('#showElementZs').change(function() {
|
||||||
updateChartDisplay();
|
updateChartDisplay();
|
||||||
});
|
});
|
||||||
|
// 添加次周期BI中枢复选框变更事件(委托绑定,避免DOM更新后失效)
|
||||||
// 添加未完成中枢复选框变更事件
|
$(document).on('change', '#showElementBiZs', function() {
|
||||||
$('#showElementUncompletedZs').change(function() {
|
console.log('次BI中枢切换为:', $('#showElementBiZs').is(':checked'));
|
||||||
updateChartDisplay();
|
updateChartDisplay();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1843,11 +1842,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log('更新图表显示');
|
console.log('更新图表显示');
|
||||||
|
console.log('BI中枢输入状态:', {
|
||||||
|
main: $('#showMainBiZs').is(':checked'),
|
||||||
|
element: $('#showElementBiZs').is(':checked')
|
||||||
|
});
|
||||||
console.log('- 显示K线:', $('#showOriginalKline').is(':checked'));
|
console.log('- 显示K线:', $('#showOriginalKline').is(':checked'));
|
||||||
console.log('- 显示笔:', $('#showMainBi').is(':checked'));
|
console.log('- 显示笔:', $('#showMainBi').is(':checked'));
|
||||||
console.log('- 显示线段:', $('#showMainSeg').is(':checked'));
|
console.log('- 显示线段:', $('#showMainSeg').is(':checked'));
|
||||||
console.log('- 显示中枢:', $('#showMainZs').is(':checked'));
|
console.log('- 显示中枢:', $('#showMainZs').is(':checked'));
|
||||||
console.log('- 显示未完成中枢:', $('#showMainUncompletedZs').is(':checked'));
|
console.log('- 显示BI中枢(主):', $('#showMainBiZs').is(':checked'));
|
||||||
|
console.log('- 显示BI中枢(次):', $('#showElementBiZs').is(':checked'));
|
||||||
console.log('- 显示MACD:', true);
|
console.log('- 显示MACD:', true);
|
||||||
console.log('- 显示成交量:', false);
|
console.log('- 显示成交量:', false);
|
||||||
console.log('- 显示分型类型:', $('#showKlcFxType').is(':checked'));
|
console.log('- 显示分型类型:', $('#showKlcFxType').is(':checked'));
|
||||||
@@ -2033,6 +2037,11 @@
|
|||||||
|
|
||||||
// 检查和记录服务器返回的时区
|
// 检查和记录服务器返回的时区
|
||||||
console.log('服务器返回的时区:', data.timezone || '未指定');
|
console.log('服务器返回的时区:', data.timezone || '未指定');
|
||||||
|
// BI中枢调试输出
|
||||||
|
console.log('主周期BI中枢(完成):', Array.isArray(data.bi_zs_list) ? data.bi_zs_list.length : 0);
|
||||||
|
console.log('主周期BI中枢(未完成):', Array.isArray(data.uncompleted_bi_zs_list) ? data.uncompleted_bi_zs_list.length : 0);
|
||||||
|
console.log('次周期BI中枢(完成):', Array.isArray(data.element_bi_zs_list) ? data.element_bi_zs_list.length : 0);
|
||||||
|
console.log('次周期BI中枢(未完成):', Array.isArray(data.element_uncompleted_bi_zs_list) ? data.element_uncompleted_bi_zs_list.length : 0);
|
||||||
|
|
||||||
// 刷新图表
|
// 刷新图表
|
||||||
refreshChart(data);
|
refreshChart(data);
|
||||||
@@ -2077,7 +2086,6 @@
|
|||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn('释放旧图表资源失败(可忽略):', e);
|
console.warn('释放旧图表资源失败(可忽略):', e);
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('初始化TradingView图表:', symbol, timeframe);
|
console.log('初始化TradingView图表:', symbol, timeframe);
|
||||||
|
|
||||||
// 获取当前交易对的配置
|
// 获取当前交易对的配置
|
||||||
@@ -2624,7 +2632,6 @@
|
|||||||
console.log('处理后的ATR数据点数:', atrData.length);
|
console.log('处理后的ATR数据点数:', atrData.length);
|
||||||
console.log('ATR数据样本:', atrData.slice(0, 5));
|
console.log('ATR数据样本:', atrData.slice(0, 5));
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('处理后的ATR数据点数:', atrData.length);
|
console.log('处理后的ATR数据点数:', atrData.length);
|
||||||
atrLineSeries.setData(atrData);
|
atrLineSeries.setData(atrData);
|
||||||
tvWidget.series.atrLineSeries = atrLineSeries;
|
tvWidget.series.atrLineSeries = atrLineSeries;
|
||||||
@@ -3851,7 +3858,7 @@
|
|||||||
} else {
|
} else {
|
||||||
console.log('绘制线段 - 已禁用');
|
console.log('绘制线段 - 已禁用');
|
||||||
}
|
}
|
||||||
// 显示中枢的绘制 - 分别处理主周期和次周期
|
// 显示中枢的绘制 - 分别处理主周期和次周期(包含BI中枢,沿用同样样式与开关)
|
||||||
if ($('#showMainZs').is(':checked') || $('#showElementZs').is(':checked')) {
|
if ($('#showMainZs').is(':checked') || $('#showElementZs').is(':checked')) {
|
||||||
console.log('绘制中枢 - 已启用');
|
console.log('绘制中枢 - 已启用');
|
||||||
|
|
||||||
@@ -4133,12 +4140,199 @@
|
|||||||
} else {
|
} else {
|
||||||
console.log('绘制中枢 - 已禁用');
|
console.log('绘制中枢 - 已禁用');
|
||||||
}
|
}
|
||||||
|
// BI中枢(已完成)- 使用独立的BI开关
|
||||||
|
if ($('#showMainBiZs').is(':checked') && currentData.bi_zs_list && currentData.bi_zs_list.length > 0) {
|
||||||
|
try {
|
||||||
|
console.log(`绘制主周期BI中枢数据,共${currentData.bi_zs_list.length}条`);
|
||||||
|
} catch (e) {}
|
||||||
|
currentData.bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#F1C40F';
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
const rightSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
rightSeries.setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('主周期BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if ($('#showElementBiZs').is(':checked') && currentData.element_bi_zs_list && currentData.element_bi_zs_list.length > 0) {
|
||||||
|
try {
|
||||||
|
console.log(`绘制次周期BI中枢数据,共${currentData.element_bi_zs_list.length}条`);
|
||||||
|
} catch (e) {}
|
||||||
|
currentData.element_bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#3f51b5';
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
const rightSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
rightSeries.setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('次周期BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
// 显示未完成中枢 - 分别处理主周期和次周期
|
// 显示未完成中枢 - 分别处理主周期和次周期
|
||||||
if ($('#showMainUncompletedZs').is(':checked') || $('#showElementUncompletedZs').is(':checked')) {
|
if ($('#showMainZs').is(':checked') || $('#showElementZs').is(':checked')) {
|
||||||
console.log('绘制未完成中枢 - 已启用');
|
console.log('绘制未完成中枢 - 已启用');
|
||||||
|
// 显示BI中枢绘制(沿用中枢样式)
|
||||||
|
if ($('#showMainBiZs').is(':checked') || $('#showElementBiZs').is(':checked')) {
|
||||||
|
console.log('绘制BI中枢 - 已启用');
|
||||||
|
// 主周期 BI 中枢
|
||||||
|
console.log('主BI开关:', $('#showMainBiZs').is(':checked'), '数据长度:', currentData.bi_zs_list ? currentData.bi_zs_list.length : 0);
|
||||||
|
if ($('#showMainBiZs').is(':checked') && currentData.bi_zs_list && currentData.bi_zs_list.length > 0) {
|
||||||
|
console.log(`绘制主周期BI中枢数据,共${currentData.bi_zs_list.length}条`);
|
||||||
|
currentData.bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg), zd = parseFloat(zs.zd), gg = parseFloat(zs.gg), dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#9C27B0'; // 主周期BI中枢颜色(紫色)
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
const rightSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
rightSeries.setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('主周期BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 主周期 未完成 BI 中枢
|
||||||
|
console.log('主未完成BI长度:', currentData.uncompleted_bi_zs_list ? currentData.uncompleted_bi_zs_list.length : 0);
|
||||||
|
if ($('#showMainBiZs').is(':checked') && currentData.uncompleted_bi_zs_list && currentData.uncompleted_bi_zs_list.length > 0) {
|
||||||
|
console.log(`绘制主周期未完成BI中枢数据,共${currentData.uncompleted_bi_zs_list.length}条`);
|
||||||
|
currentData.uncompleted_bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg), zd = parseFloat(zs.zd), gg = parseFloat(zs.gg), dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#9C27B0';
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('主周期未完成BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 次周期 BI 中枢
|
||||||
|
console.log('次BI开关:', $('#showElementBiZs').is(':checked'), '数据长度:', currentData.element_bi_zs_list ? currentData.element_bi_zs_list.length : 0);
|
||||||
|
if ($('#showElementBiZs').is(':checked') && currentData.element_bi_zs_list && currentData.element_bi_zs_list.length > 0) {
|
||||||
|
console.log(`绘制次周期BI中枢数据,共${currentData.element_bi_zs_list.length}条`);
|
||||||
|
currentData.element_bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = zs.end_time ? Math.floor(new Date(zs.end_time).getTime() / 1000) : Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg), zd = parseFloat(zs.zd), gg = parseFloat(zs.gg), dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#8BC34A'; // 次周期BI中枢颜色(绿)
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
const rightSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
rightSeries.setData([{ time: endTime, value: zd }, { time: endTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('次周期BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 次周期 未完成 BI 中枢
|
||||||
|
console.log('次未完成BI长度:', currentData.element_uncompleted_bi_zs_list ? currentData.element_uncompleted_bi_zs_list.length : 0);
|
||||||
|
if ($('#showElementBiZs').is(':checked') && currentData.element_uncompleted_bi_zs_list && currentData.element_uncompleted_bi_zs_list.length > 0) {
|
||||||
|
console.log(`绘制次周期未完成BI中枢数据,共${currentData.element_uncompleted_bi_zs_list.length}条`);
|
||||||
|
currentData.element_uncompleted_bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg), zd = parseFloat(zs.zd), gg = parseFloat(zs.gg), dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#8BC34A';
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('次周期未完成BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 主周期未完成中枢
|
// 主周期未完成中枢
|
||||||
if ($('#showMainUncompletedZs').is(':checked') && currentData.uncompleted_zs_list && currentData.uncompleted_zs_list.length > 0) {
|
if ($('#showMainZs').is(':checked') && currentData.uncompleted_zs_list && currentData.uncompleted_zs_list.length > 0) {
|
||||||
console.log(`绘制主周期未完成中枢数据,共${currentData.uncompleted_zs_list.length}条`);
|
console.log(`绘制主周期未完成中枢数据,共${currentData.uncompleted_zs_list.length}条`);
|
||||||
|
|
||||||
currentData.uncompleted_zs_list.forEach(function(zs) {
|
currentData.uncompleted_zs_list.forEach(function(zs) {
|
||||||
@@ -4281,7 +4475,7 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 次周期未完成中枢
|
// 次周期未完成中枢
|
||||||
if ($('#showElementUncompletedZs').is(':checked') && currentData.element_uncompleted_zs_list && currentData.element_uncompleted_zs_list.length > 0) {
|
if ($('#showElementZs').is(':checked') && currentData.element_uncompleted_zs_list && currentData.element_uncompleted_zs_list.length > 0) {
|
||||||
console.log(`绘制次周期未完成中枢数据,共${currentData.element_uncompleted_zs_list.length}条`);
|
console.log(`绘制次周期未完成中枢数据,共${currentData.element_uncompleted_zs_list.length}条`);
|
||||||
|
|
||||||
currentData.element_uncompleted_zs_list.forEach(function(zs) {
|
currentData.element_uncompleted_zs_list.forEach(function(zs) {
|
||||||
@@ -4425,6 +4619,62 @@
|
|||||||
} else {
|
} else {
|
||||||
console.log('绘制未完成中枢 - 已禁用');
|
console.log('绘制未完成中枢 - 已禁用');
|
||||||
}
|
}
|
||||||
|
// 未完成BI中枢 - 使用独立的BI开关
|
||||||
|
if ($('#showMainBiZs').is(':checked') && currentData.uncompleted_bi_zs_list && currentData.uncompleted_bi_zs_list.length > 0) {
|
||||||
|
try { console.log(`绘制主周期未完成BI中枢数据,共${currentData.uncompleted_bi_zs_list.length}条`); } catch (e) {}
|
||||||
|
currentData.uncompleted_bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#F1C40F';
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('主周期未完成BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 次周期 未完成 BI 中枢
|
||||||
|
if ($('#showElementBiZs').is(':checked') && currentData.element_uncompleted_bi_zs_list && currentData.element_uncompleted_bi_zs_list.length > 0) {
|
||||||
|
try { console.log(`绘制次周期未完成BI中枢数据,共${currentData.element_uncompleted_bi_zs_list.length}条`); } catch (e) {}
|
||||||
|
currentData.element_uncompleted_bi_zs_list.forEach(function(zs) {
|
||||||
|
try {
|
||||||
|
const startTime = Math.floor(new Date(zs.start_time).getTime() / 1000);
|
||||||
|
const endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000);
|
||||||
|
if (isNaN(startTime) || isNaN(endTime)) { return; }
|
||||||
|
const zg = parseFloat(zs.zg); const zd = parseFloat(zs.zd); const gg = parseFloat(zs.gg); const dd = parseFloat(zs.dd);
|
||||||
|
if (isNaN(zg) || isNaN(zd)) { return; }
|
||||||
|
const color = '#3f51b5';
|
||||||
|
const topSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
topSeries.setData([{ time: startTime, value: zg }, { time: endTime, value: zg }]);
|
||||||
|
const bottomSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
bottomSeries.setData([{ time: startTime, value: zd }, { time: endTime, value: zd }]);
|
||||||
|
const leftSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
leftSeries.setData([{ time: startTime, value: zd }, { time: startTime, value: zg }]);
|
||||||
|
if (!isNaN(gg) && gg > 0) {
|
||||||
|
const ggSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ggSeries.setData([{ time: startTime, value: gg }, { time: endTime, value: gg }]);
|
||||||
|
}
|
||||||
|
if (!isNaN(dd) && dd > 0) {
|
||||||
|
const ddSeries = mainChart.addLineSeries({ color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false });
|
||||||
|
ddSeries.setData([{ time: startTime, value: dd }, { time: endTime, value: dd }]);
|
||||||
|
}
|
||||||
|
} catch (e) { console.error('次周期未完成BI中枢处理出错:', e); }
|
||||||
|
});
|
||||||
|
}
|
||||||
// 添加买卖点标记
|
// 添加买卖点标记
|
||||||
if ($('#showTradePoints').is(':checked')) {
|
if ($('#showTradePoints').is(':checked')) {
|
||||||
console.log('绘制买卖点 - 已启用');
|
console.log('绘制买卖点 - 已启用');
|
||||||
@@ -5044,7 +5294,6 @@
|
|||||||
if (currentData.klu_fx_info && currentData.klu_fx_info.length > 0) {
|
if (currentData.klu_fx_info && currentData.klu_fx_info.length > 0) {
|
||||||
console.log('前3个klu分型数据样本:', currentData.klu_fx_info.slice(0, 3));
|
console.log('前3个klu分型数据样本:', currentData.klu_fx_info.slice(0, 3));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 收集所有主周期分型标记
|
// 收集所有主周期分型标记
|
||||||
const allMainFxMarkers = [];
|
const allMainFxMarkers = [];
|
||||||
const mainFxMarkers = []; // 用于tooltip支持
|
const mainFxMarkers = []; // 用于tooltip支持
|
||||||
@@ -5674,17 +5923,6 @@
|
|||||||
setDefaultTimeRange();
|
setDefaultTimeRange();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加买卖点提示
|
|
||||||
// 初始化 tooltip 与 U 显示状态
|
|
||||||
window.showUOnMain = $('#toggleUOnMain').is(':checked');
|
|
||||||
window.showUOnElement = $('#toggleUOnElement').is(':checked');
|
|
||||||
setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd);
|
|
||||||
|
|
||||||
// 显示买卖点
|
|
||||||
if ($('#showTradePoints').is(':checked')) {
|
|
||||||
displayTradePoints();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 更新EMA52显示
|
// 更新EMA52显示
|
||||||
if (currentData) {
|
if (currentData) {
|
||||||
updateEMA52Display(currentData);
|
updateEMA52Display(currentData);
|
||||||
@@ -5962,7 +6200,6 @@
|
|||||||
initTradingView($('#symbol').val(), $('#timeframe').val());
|
initTradingView($('#symbol').val(), $('#timeframe').val());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, mainChart, volumeChart, atrChart, macdChart, chanMacdChart, showMacd) {
|
function bindSyncEvents(mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, mainChart, volumeChart, atrChart, macdChart, chanMacdChart, showMacd) {
|
||||||
// 防止同步过程中的无限循环
|
// 防止同步过程中的无限循环
|
||||||
let syncInProgress = false;
|
let syncInProgress = false;
|
||||||
@@ -6210,266 +6447,7 @@
|
|||||||
}, 200);
|
}, 200);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
function setupTooltip(mainChart, buyMarkers = [], sellMarkers = [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd) {
|
|
||||||
// 调试变量
|
|
||||||
window.debugMode = true;
|
|
||||||
// 初始化 U 显示状态(主/次周期分开控制)
|
|
||||||
const isShowUMain = $('#toggleUOnMain').is(':checked');
|
|
||||||
const isShowUElement = $('#toggleUOnElement').is(':checked');
|
|
||||||
window.showUOnMain = isShowUMain;
|
|
||||||
window.showUOnElement = isShowUElement;
|
|
||||||
if (!isShowUMain && !isShowUElement) {
|
|
||||||
// 隐藏时清空子图上的 U 标记
|
|
||||||
if (tvWidget.series && tvWidget.series.chanMacdLineSeries) {
|
|
||||||
try { tvWidget.series.chanMacdLineSeries.setMarkers([]); } catch (e) {}
|
|
||||||
}
|
|
||||||
if (tvWidget.series && tvWidget.series.chanMacdSignalSeries) {
|
|
||||||
try { tvWidget.series.chanMacdSignalSeries.setMarkers([]); } catch (e) {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 添加买卖点悬浮提示元素
|
|
||||||
const tooltipElement = document.createElement('div');
|
|
||||||
tooltipElement.className = 'point-tooltip';
|
|
||||||
// document.body.appendChild(tooltipElement);
|
|
||||||
|
|
||||||
// 添加自定义十字线信息显示
|
|
||||||
const crosshairTooltip = document.createElement('div');
|
|
||||||
crosshairTooltip.className = 'crosshair-tooltip';
|
|
||||||
crosshairTooltip.style.position = 'absolute';
|
|
||||||
crosshairTooltip.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
|
|
||||||
crosshairTooltip.style.color = 'white';
|
|
||||||
crosshairTooltip.style.padding = '5px 10px';
|
|
||||||
crosshairTooltip.style.borderRadius = '4px';
|
|
||||||
crosshairTooltip.style.fontSize = '12px';
|
|
||||||
crosshairTooltip.style.zIndex = '1000';
|
|
||||||
crosshairTooltip.style.pointerEvents = 'none';
|
|
||||||
crosshairTooltip.style.display = 'none';
|
|
||||||
// document.body.appendChild(crosshairTooltip);
|
|
||||||
|
|
||||||
// 添加鼠标悬停事件显示提示
|
|
||||||
if (mainChart) {
|
|
||||||
mainChart.subscribeCrosshairMove(param => {
|
|
||||||
// 十字线同步到其他图表 - 通过DOM元素绘制垂直线实现虚线延长效果
|
|
||||||
if (param.time && param.point && volumeChart) {
|
|
||||||
try {
|
|
||||||
// 清除之前的十字线标记
|
|
||||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
|
||||||
existingVolumeLines.forEach(line => line.remove());
|
|
||||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
|
||||||
existingAtrLines.forEach(line => line.remove());
|
|
||||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
|
||||||
existingMacdLines.forEach(line => line.remove());
|
|
||||||
const existingChanMacdLines = document.querySelectorAll('.chanmacd-crosshair-line');
|
|
||||||
existingChanMacdLines.forEach(line => line.remove());
|
|
||||||
|
|
||||||
// 获取时间对应的坐标位置
|
|
||||||
const mainTimeCoordinate = mainChart.timeScale().timeToCoordinate(param.time);
|
|
||||||
if (mainTimeCoordinate !== null) {
|
|
||||||
// 获取主图容器的位置
|
|
||||||
const mainChartRect = mainChartContainer.getBoundingClientRect();
|
|
||||||
|
|
||||||
// 在交易量图上绘制垂直线
|
|
||||||
const volumeTimeCoordinate = volumeChart.timeScale().timeToCoordinate(param.time);
|
|
||||||
if (volumeTimeCoordinate !== null) {
|
|
||||||
const volumeChartRect = volumeChartContainer.getBoundingClientRect();
|
|
||||||
const volumeLine = document.createElement('div');
|
|
||||||
volumeLine.className = 'volume-crosshair-line';
|
|
||||||
volumeLine.style.position = 'fixed'; // 改为fixed定位
|
|
||||||
volumeLine.style.left = (volumeChartRect.left + volumeTimeCoordinate) + 'px';
|
|
||||||
volumeLine.style.top = volumeChartRect.top + 'px';
|
|
||||||
volumeLine.style.width = '1px';
|
|
||||||
volumeLine.style.height = volumeChartRect.height + 'px';
|
|
||||||
volumeLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
|
||||||
volumeLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
|
|
||||||
volumeLine.style.pointerEvents = 'none';
|
|
||||||
volumeLine.style.zIndex = '1000';
|
|
||||||
document.body.appendChild(volumeLine);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 在ATR图上绘制垂直线
|
|
||||||
if (atrChart && atrChartContainer) {
|
|
||||||
const atrTimeCoordinate = atrChart.timeScale().timeToCoordinate(param.time);
|
|
||||||
if (atrTimeCoordinate !== null) {
|
|
||||||
const atrChartRect = atrChartContainer.getBoundingClientRect();
|
|
||||||
const atrLine = document.createElement('div');
|
|
||||||
atrLine.className = 'atr-crosshair-line';
|
|
||||||
atrLine.style.position = 'fixed'; // 改为fixed定位
|
|
||||||
atrLine.style.left = (atrChartRect.left + atrTimeCoordinate) + 'px';
|
|
||||||
atrLine.style.top = atrChartRect.top + 'px';
|
|
||||||
atrLine.style.width = '1px';
|
|
||||||
atrLine.style.height = atrChartRect.height + 'px';
|
|
||||||
atrLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
|
||||||
atrLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
|
|
||||||
atrLine.style.pointerEvents = 'none';
|
|
||||||
atrLine.style.zIndex = '1000';
|
|
||||||
document.body.appendChild(atrLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有MACD图,也在MACD图上绘制垂直线
|
|
||||||
if (showMacd && macdChart && macdChartContainer) {
|
|
||||||
const macdTimeCoordinate = macdChart.timeScale().timeToCoordinate(param.time);
|
|
||||||
if (macdTimeCoordinate !== null) {
|
|
||||||
const macdChartRect = macdChartContainer.getBoundingClientRect();
|
|
||||||
const macdLine = document.createElement('div');
|
|
||||||
macdLine.className = 'macd-crosshair-line';
|
|
||||||
macdLine.style.position = 'fixed'; // 改为fixed定位
|
|
||||||
macdLine.style.left = (macdChartRect.left + macdTimeCoordinate) + 'px';
|
|
||||||
macdLine.style.top = macdChartRect.top + 'px';
|
|
||||||
macdLine.style.width = '1px';
|
|
||||||
macdLine.style.height = macdChartRect.height + 'px';
|
|
||||||
macdLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
|
||||||
macdLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
|
|
||||||
macdLine.style.pointerEvents = 'none';
|
|
||||||
macdLine.style.zIndex = '1000';
|
|
||||||
document.body.appendChild(macdLine);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果有ChanMACD图,也在ChanMACD图上绘制垂直线
|
|
||||||
if (showMacd && chanMacdChart && chanMacdChartContainer) {
|
|
||||||
const chanMacdTimeCoordinate = chanMacdChart.timeScale().timeToCoordinate(param.time);
|
|
||||||
if (chanMacdTimeCoordinate !== null) {
|
|
||||||
const chanMacdChartRect = chanMacdChartContainer.getBoundingClientRect();
|
|
||||||
console.log('ChanMACD图表位置(第二个位置):', {
|
|
||||||
left: chanMacdChartRect.left,
|
|
||||||
top: chanMacdChartRect.top,
|
|
||||||
width: chanMacdChartRect.width,
|
|
||||||
height: chanMacdChartRect.height,
|
|
||||||
timeCoordinate: chanMacdTimeCoordinate
|
|
||||||
});
|
|
||||||
const chanMacdLine = document.createElement('div');
|
|
||||||
chanMacdLine.className = 'chanmacd-crosshair-line';
|
|
||||||
chanMacdLine.style.position = 'fixed';
|
|
||||||
chanMacdLine.style.left = (chanMacdChartRect.left + chanMacdTimeCoordinate) + 'px';
|
|
||||||
chanMacdLine.style.top = chanMacdChartRect.top + 'px';
|
|
||||||
chanMacdLine.style.width = '1px';
|
|
||||||
chanMacdLine.style.height = chanMacdChartRect.height + 'px';
|
|
||||||
chanMacdLine.style.backgroundColor = 'rgba(128, 128, 128, 0.5)';
|
|
||||||
chanMacdLine.style.borderLeft = '1px dashed rgba(128, 128, 128, 0.5)';
|
|
||||||
chanMacdLine.style.pointerEvents = 'none';
|
|
||||||
chanMacdLine.style.zIndex = '1000';
|
|
||||||
document.body.appendChild(chanMacdLine);
|
|
||||||
console.log('ChanMACD垂直线已创建(第二个位置),位置:', chanMacdLine.style.left, chanMacdLine.style.top);
|
|
||||||
} else {
|
|
||||||
console.log('ChanMACD时间坐标为空(第二个位置)');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.log('ChanMACD图表条件不满足(第二个位置):', {
|
|
||||||
showMacd: showMacd,
|
|
||||||
hasChanMacdChart: !!chanMacdChart,
|
|
||||||
hasChanMacdChartContainer: !!chanMacdChartContainer
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.debug('十字线同步出错:', e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 当十字线离开时,清除垂直线
|
|
||||||
try {
|
|
||||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
|
||||||
existingVolumeLines.forEach(line => line.remove());
|
|
||||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
|
||||||
existingAtrLines.forEach(line => line.remove());
|
|
||||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
|
||||||
existingMacdLines.forEach(line => line.remove());
|
|
||||||
const existingChanMacdLines = document.querySelectorAll('.chanmacd-crosshair-line');
|
|
||||||
existingChanMacdLines.forEach(line => line.remove());
|
|
||||||
} catch (e) {
|
|
||||||
console.debug('清除十字线时出错:', e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (param.time && param.point) {
|
|
||||||
const timeStr = param.time;
|
|
||||||
const markers = [...buyMarkers, ...sellMarkers].filter(m => m.time === timeStr);
|
|
||||||
|
|
||||||
// 同时检查分型标记
|
|
||||||
const fxMarkers = (window.fxMarkers || []).filter(m => m.time === timeStr);
|
|
||||||
const allMarkers = [...markers, ...fxMarkers];
|
|
||||||
|
|
||||||
// 显示时区调试信息
|
|
||||||
if (window.debugMode) {
|
|
||||||
const timezone = $('#timezone').val();
|
|
||||||
const formattedTime = formatTimeWithTimezone(timeStr * 1000, timezone);
|
|
||||||
|
|
||||||
// 获取当前价格 - 通过param.seriesPrices获取
|
|
||||||
let priceInfo = '';
|
|
||||||
if (param.seriesPrices && param.seriesPrices.size > 0) {
|
|
||||||
// 依次从当前可能的主系列中获取价格
|
|
||||||
if (tvWidget.series.candleSeries && param.seriesPrices.get(tvWidget.series.candleSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.candleSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
} else if (tvWidget.series.renkoSeries && param.seriesPrices.get(tvWidget.series.renkoSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.renkoSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
} else if (tvWidget.series.heikinSeries && param.seriesPrices.get(tvWidget.series.heikinSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.heikinSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
} else if (tvWidget.series.barSeries && param.seriesPrices.get(tvWidget.series.barSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.barSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
} else if (tvWidget.series.lineSeries && param.seriesPrices.get(tvWidget.series.lineSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.lineSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
} else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.areaSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
} else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.baselineSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
}
|
|
||||||
// 如果没有蜡烛图系列价格,尝试从区域图系列获取
|
|
||||||
else if (tvWidget.series.areaSeries && param.seriesPrices.get(tvWidget.series.areaSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.areaSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
}
|
|
||||||
// 如果没有蜡烛图系列价格,尝试从基线图系列获取
|
|
||||||
else if (tvWidget.series.baselineSeries && param.seriesPrices.get(tvWidget.series.baselineSeries)) {
|
|
||||||
const price = param.seriesPrices.get(tvWidget.series.baselineSeries);
|
|
||||||
priceInfo = `价格: ${price.toFixed(2)}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 仅记录最简短的调试信息
|
|
||||||
console.debug(`十字线: ${timeStr} -> ${formattedTime} (${timezone})`);
|
|
||||||
|
|
||||||
// 显示自定义时区工具提示,包含价格信息
|
|
||||||
crosshairTooltip.innerHTML = `<div style="font-weight:bold">时间: ${formattedTime}</div>` +
|
|
||||||
(priceInfo ? `<div>${priceInfo}</div>` : '');
|
|
||||||
crosshairTooltip.style.display = 'block';
|
|
||||||
crosshairTooltip.style.left = (param.point.x + 15) + 'px';
|
|
||||||
crosshairTooltip.style.top = (param.point.y - 30) + 'px';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (allMarkers.length > 0) {
|
|
||||||
// 有买卖点或分型标记,显示自定义提示
|
|
||||||
const tooltips = allMarkers.map(m => m.tooltip).join('<br><hr style="margin: 5px 0;">');
|
|
||||||
tooltipElement.innerHTML = tooltips;
|
|
||||||
tooltipElement.style.display = 'block';
|
|
||||||
tooltipElement.style.left = (param.point.x + 15) + 'px';
|
|
||||||
tooltipElement.style.top = (param.point.y + 15) + 'px';
|
|
||||||
} else {
|
|
||||||
// 隐藏提示
|
|
||||||
tooltipElement.style.display = 'none';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 隐藏提示
|
|
||||||
tooltipElement.style.display = 'none';
|
|
||||||
crosshairTooltip.style.display = 'none';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// 处理图表缩放、平移等事件,隐藏提示
|
|
||||||
mainChart.timeScale().subscribeVisibleTimeRangeChange(() => {
|
|
||||||
tooltipElement.style.display = 'none';
|
|
||||||
crosshairTooltip.style.display = 'none';
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 辅助函数:使用指定时区格式化时间戳
|
// 辅助函数:使用指定时区格式化时间戳
|
||||||
function formatTimeWithTimezone(timestamp, timezone) {
|
function formatTimeWithTimezone(timestamp, timezone) {
|
||||||
try {
|
try {
|
||||||
@@ -6487,220 +6465,6 @@
|
|||||||
return new Date(timestamp).toLocaleString();
|
return new Date(timestamp).toLocaleString();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
function displayTradePoints() {
|
|
||||||
// 优先使用小周期数据,如果不存在则使用主周期数据
|
|
||||||
const tradePointsData = currentData.element_trade_points || currentData.trade_points;
|
|
||||||
console.log(`绘制${currentData.element_trade_points ? '元素周期' : '主周期'}买卖点数据,共${tradePointsData ? tradePointsData.length : 0}条`);
|
|
||||||
|
|
||||||
// 调试信息 - 输出完整的买卖点数据
|
|
||||||
if (tradePointsData && tradePointsData.length > 0) {
|
|
||||||
console.log("买卖点数据样例:", tradePointsData[0]);
|
|
||||||
|
|
||||||
// 检查数据格式,如果time不是标准格式,进行格式化处理
|
|
||||||
const checkDataFormat = () => {
|
|
||||||
for (let i = 0; i < tradePointsData.length; i++) {
|
|
||||||
if (tradePointsData[i].time) {
|
|
||||||
// 确保时间是标准格式
|
|
||||||
try {
|
|
||||||
const timeValue = new Date(tradePointsData[i].time);
|
|
||||||
if (isNaN(timeValue.getTime())) {
|
|
||||||
console.error(`买卖点 #${i} 时间格式无效:`, tradePointsData[i].time);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`买卖点 #${i} 时间格式异常:`, e);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
console.error(`买卖点 #${i} 缺少时间属性`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// 执行格式检查
|
|
||||||
checkDataFormat();
|
|
||||||
|
|
||||||
// 对买卖点按时间排序,用于后续优化显示
|
|
||||||
const sortedPoints = [...tradePointsData].sort((a, b) => {
|
|
||||||
return new Date(a.time) - new Date(b.time);
|
|
||||||
});
|
|
||||||
|
|
||||||
// 记录已处理的时间点 - 按类型分开计数
|
|
||||||
const processedTimes = {};
|
|
||||||
|
|
||||||
// 创建买卖点标记系列
|
|
||||||
const buyMarkers = [];
|
|
||||||
const sellMarkers = [];
|
|
||||||
|
|
||||||
// 计数器,追踪成功和失败的处理次数
|
|
||||||
let successCount = 0;
|
|
||||||
let errorCount = 0;
|
|
||||||
|
|
||||||
sortedPoints.forEach(function(point, index) {
|
|
||||||
try {
|
|
||||||
// 检查所有必要的属性是否存在且有效
|
|
||||||
if (!point.time || !point.price || point.type === undefined) {
|
|
||||||
console.error(`买卖点 #${index} 数据不完整:`, point);
|
|
||||||
errorCount++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const time = Math.floor(new Date(point.time).getTime() / 1000);
|
|
||||||
const price = parseFloat(point.price);
|
|
||||||
const type = parseInt(point.type);
|
|
||||||
|
|
||||||
if (isNaN(time) || isNaN(price) || isNaN(type)) {
|
|
||||||
console.error(`买卖点 #${index} 数据格式错误:`,
|
|
||||||
{ time: isNaN(time), price: isNaN(price), type: isNaN(type) }, point);
|
|
||||||
errorCount++;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取买卖点样式
|
|
||||||
const style = TRADE_POINT_STYLE[type] || {
|
|
||||||
color: '#999999',
|
|
||||||
shape: 'circle',
|
|
||||||
text: '?',
|
|
||||||
size: 1
|
|
||||||
};
|
|
||||||
|
|
||||||
// 初始化该时间点的类型计数器
|
|
||||||
if (!processedTimes[time]) {
|
|
||||||
processedTimes[time] = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
// 优化:检查是否有相同时间点和相同类型的标记,如果有,进行类型内的偏移
|
|
||||||
let stackIndex = 0;
|
|
||||||
if (processedTimes[time][type]) {
|
|
||||||
// 已经有相同时间和类型的标记,记录堆叠索引
|
|
||||||
stackIndex = processedTimes[time][type];
|
|
||||||
processedTimes[time][type]++;
|
|
||||||
} else {
|
|
||||||
// 第一次出现这个时间点的这个类型
|
|
||||||
processedTimes[time][type] = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 为不同类型的买卖点获取基础垂直偏移系数
|
|
||||||
const baseOffset = TRADE_POINT_OFFSET[type] || 0;
|
|
||||||
|
|
||||||
// 创建标记对象,包含额外的信息用于悬停提示
|
|
||||||
const marker = {
|
|
||||||
time: time,
|
|
||||||
position: 'inBar', // 改为在K线内部显示,不影响数据
|
|
||||||
color: style.color,
|
|
||||||
shape: style.shape,
|
|
||||||
text: style.text,
|
|
||||||
size: style.size,
|
|
||||||
// 记录堆叠索引
|
|
||||||
stackIndex: stackIndex,
|
|
||||||
// 添加悬停提示的数据
|
|
||||||
tooltip: `<span class="${type > 0 ? 'buy-point' : 'sell-point'}">${point.desc || (type > 0 ? '买点' : '卖点')}</span><br>
|
|
||||||
时间: ${formatTime(point.time)}<br>
|
|
||||||
价格: ${price.toFixed(2)}`,
|
|
||||||
// 额外添加基础类型偏移
|
|
||||||
baseOffset: baseOffset,
|
|
||||||
// 添加边框
|
|
||||||
borderColor: 'white',
|
|
||||||
borderWidth: 1,
|
|
||||||
// 添加价格偏移系数
|
|
||||||
pricePercentOffset: PRICE_PERCENT_OFFSET[type] || 0,
|
|
||||||
// 保存实际价格用于计算
|
|
||||||
price: price,
|
|
||||||
// 保存类型
|
|
||||||
type: type
|
|
||||||
};
|
|
||||||
|
|
||||||
// 区分买卖点
|
|
||||||
if (type > 0) {
|
|
||||||
buyMarkers.push(marker);
|
|
||||||
} else {
|
|
||||||
sellMarkers.push(marker);
|
|
||||||
}
|
|
||||||
|
|
||||||
successCount++;
|
|
||||||
} catch (e) {
|
|
||||||
console.error(`处理买卖点 #${index} 出错:`, e, point);
|
|
||||||
errorCount++;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`买卖点处理完成: 成功=${successCount}, 失败=${errorCount}, 买点=${buyMarkers.length}, 卖点=${sellMarkers.length}`);
|
|
||||||
|
|
||||||
// 分别添加买卖点标记
|
|
||||||
if (buyMarkers.length > 0) {
|
|
||||||
const buyMarkersSeries = mainChart.addLineSeries({
|
|
||||||
lastValueVisible: false,
|
|
||||||
priceLineVisible: false,
|
|
||||||
lineVisible: false,
|
|
||||||
title: '买点'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置临时的数据点
|
|
||||||
buyMarkersSeries.setData([{ time: buyMarkers[0].time, value: 0 }]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 设置买点标记,并添加偏移
|
|
||||||
buyMarkersSeries.setMarkers(
|
|
||||||
buyMarkers.map(marker => {
|
|
||||||
// 使用固定偏移而非百分比
|
|
||||||
const fixedOffset = PRICE_FIXED_OFFSET[marker.type] || 20;
|
|
||||||
|
|
||||||
// 为同一时间点的多个买点额外增加堆叠偏移
|
|
||||||
const stackOffset = marker.stackIndex > 0 ?
|
|
||||||
10 * marker.stackIndex : 0;
|
|
||||||
|
|
||||||
// 使用实际价格位置添加标记,不使用偏移
|
|
||||||
return {
|
|
||||||
...marker,
|
|
||||||
position: 'aboveBar', // 显示在K线上方
|
|
||||||
// 使用原始价格
|
|
||||||
price: marker.price
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
console.log(`成功添加 ${buyMarkers.length} 个买点标记`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("设置买点标记时出错:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (sellMarkers.length > 0) {
|
|
||||||
const sellMarkersSeries = mainChart.addLineSeries({
|
|
||||||
lastValueVisible: false,
|
|
||||||
priceLineVisible: false,
|
|
||||||
lineVisible: false,
|
|
||||||
title: '卖点'
|
|
||||||
});
|
|
||||||
|
|
||||||
// 设置临时的数据点
|
|
||||||
sellMarkersSeries.setData([{ time: sellMarkers[0].time, value: 0 }]);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 设置卖点标记,并添加偏移
|
|
||||||
sellMarkersSeries.setMarkers(
|
|
||||||
sellMarkers.map(marker => {
|
|
||||||
// 使用固定偏移而非百分比
|
|
||||||
const fixedOffset = PRICE_FIXED_OFFSET[marker.type] || 20;
|
|
||||||
|
|
||||||
// 为同一时间点的多个卖点额外增加堆叠偏移
|
|
||||||
const stackOffset = marker.stackIndex > 0 ?
|
|
||||||
10 * marker.stackIndex : 0;
|
|
||||||
|
|
||||||
// 使用实际价格位置添加标记,不使用偏移
|
|
||||||
return {
|
|
||||||
...marker,
|
|
||||||
position: 'belowBar', // 显示在K线下方
|
|
||||||
// 使用原始价格
|
|
||||||
price: marker.price
|
|
||||||
};
|
|
||||||
})
|
|
||||||
);
|
|
||||||
console.log(`成功添加 ${sellMarkers.length} 个卖点标记`);
|
|
||||||
} catch (e) {
|
|
||||||
console.error("设置卖点标记时出错:", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateTables(currentData) {
|
function updateTables(currentData) {
|
||||||
// 检查数据有效性
|
// 检查数据有效性
|
||||||
if (!currentData) {
|
if (!currentData) {
|
||||||
@@ -7334,7 +7098,6 @@
|
|||||||
const timeStr = nextRefreshTime.toLocaleTimeString();
|
const timeStr = nextRefreshTime.toLocaleTimeString();
|
||||||
$('#nextRefreshTime').text(`下次刷新: ${timeStr}`);
|
$('#nextRefreshTime').text(`下次刷新: ${timeStr}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 倒计时定时器
|
// 倒计时定时器
|
||||||
let countdownTimer = null;
|
let countdownTimer = null;
|
||||||
|
|
||||||
@@ -7475,7 +7238,6 @@
|
|||||||
|
|
||||||
console.log('重绘分形元素 - 完成');
|
console.log('重绘分形元素 - 完成');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 只更新分形元素(笔、线段、中枢)的表格数据
|
// 只更新分形元素(笔、线段、中枢)的表格数据
|
||||||
function updateFractalTables() {
|
function updateFractalTables() {
|
||||||
if (!currentData) return;
|
if (!currentData) return;
|
||||||
@@ -7682,7 +7444,6 @@
|
|||||||
'showMainBi': $('#showMainBi').is(':checked'),
|
'showMainBi': $('#showMainBi').is(':checked'),
|
||||||
'showMainSeg': $('#showMainSeg').is(':checked'),
|
'showMainSeg': $('#showMainSeg').is(':checked'),
|
||||||
'showMainZs': $('#showMainZs').is(':checked'),
|
'showMainZs': $('#showMainZs').is(':checked'),
|
||||||
'showMainUncompletedZs': $('#showMainUncompletedZs').is(':checked'),
|
|
||||||
'showVolume': false,
|
'showVolume': false,
|
||||||
'showMacd': $('#showMacd').is(':checked'),
|
'showMacd': $('#showMacd').is(':checked'),
|
||||||
'showKlcFxType': $('#showKlcFxType').is(':checked'),
|
'showKlcFxType': $('#showKlcFxType').is(':checked'),
|
||||||
@@ -7908,7 +7669,6 @@
|
|||||||
|
|
||||||
console.log('✅ EMA52系列已清理');
|
console.log('✅ EMA52系列已清理');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 存储上次的EMA52数据,用于比较
|
// 存储上次的EMA52数据,用于比较
|
||||||
let lastEMA52Data = null;
|
let lastEMA52Data = null;
|
||||||
|
|
||||||
@@ -8264,7 +8024,6 @@
|
|||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新技术指标面板(统一面板)
|
// 更新技术指标面板(统一面板)
|
||||||
function updateIndicatorPanel() {
|
function updateIndicatorPanel() {
|
||||||
const panel = $('#indicatorPanel');
|
const panel = $('#indicatorPanel');
|
||||||
@@ -8547,7 +8306,6 @@
|
|||||||
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) {
|
||||||
@@ -9063,7 +8821,6 @@
|
|||||||
// 监听配置变化以更新预览
|
// 监听配置变化以更新预览
|
||||||
$(document).on('change', '#maColor, #maLineWidth, #maLineStyle', updateLinePreview);
|
$(document).on('change', '#maColor, #maLineWidth, #maLineStyle', updateLinePreview);
|
||||||
$(document).on('change', '#bbUpperColor, #bbMiddleColor, #bbLowerColor, #bbLineWidth, #bbLineStyle', updateBBLinePreview);
|
$(document).on('change', '#bbUpperColor, #bbMiddleColor, #bbLowerColor, #bbLineWidth, #bbLineStyle', updateBBLinePreview);
|
||||||
|
|
||||||
// 点击弹窗外部关闭
|
// 点击弹窗外部关闭
|
||||||
$(document).on('click', '#bbConfigModal', function(e) {
|
$(document).on('click', '#bbConfigModal', function(e) {
|
||||||
if (e.target === this) {
|
if (e.target === this) {
|
||||||
@@ -9159,7 +8916,6 @@
|
|||||||
// 清空全局UnitTF标记,避免旧数据残留影响主图合并
|
// 清空全局UnitTF标记,避免旧数据残留影响主图合并
|
||||||
window.unittfMarkers = [];
|
window.unittfMarkers = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加所有ChanMACD标记
|
// 添加所有ChanMACD标记
|
||||||
function addAllChanMacdMarkers(segList, unittfList, histsetList, stateMarkers) {
|
function addAllChanMacdMarkers(segList, unittfList, histsetList, stateMarkers) {
|
||||||
const macdMarkers = [];
|
const macdMarkers = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user