diff --git a/ChanBI.py b/ChanBI.py index 9426bb2..aaafaa3 100644 --- a/ChanBI.py +++ b/ChanBI.py @@ -19,6 +19,9 @@ class ChanBI(): self.start_time = klc.start_time self.macd_hist = 0 self.macd_div = 0 + self.seg = None + def set_seg(self, seg): + self.seg = seg def set_macdhist(self, macd_hist): self.macd_hist = macd_hist def set_macd_div(self, macd_div): @@ -39,8 +42,16 @@ class ChanBI(): self.macd_hist += klu.macdhist if self.dir == Chan_BI_DIR.DOWN and klu.macdhist < 0: self.macd_hist -= klu.macdhist - def check_overlap(self): + def check_bi_zs_overlap(self): 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: return self.high > self.next.low and self.high < self.next.next.high else: diff --git a/ChanBIZS.py b/ChanBIZS.py index 4cd496a..c90c1e2 100644 --- a/ChanBIZS.py +++ b/ChanBIZS.py @@ -7,49 +7,34 @@ class ChanBIZS(): self.start_time = self.start_klc.start_time self.end_time = None self.index = index - self.next = None - self.pre = None self.start_bi = start_bi self.bi_list = [] self.bi_list.append(start_bi) self.end_bi = None - self.last_bi_in = None self.bi_out = None self.is_sure = False self.zg = 0 self.zd = 0 + self.gg = 0 + self.dd = 0 self.dir = ddir self.sure_time = None self.end_klc = None - self.bi_out_count = 0 - self.bi_out_list = [] - def set_last_bi_in(self, last_bi_in): - 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) + def set_end_bi(self, end_bi, sure_bi): + self.end_bi = end_bi + self.set_end_time(end_bi.end_klc.end_time) self.is_sure = True - self.sure_time = 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 + self.sure_time = sure_bi.sure_time def set_end_time(self, end_time): self.end_time = end_time - def add_klc(self, klc): - self.klc_list.append(klc) def set_zg(self, zg): self.zg = zg def set_zd(self, zd): - self.zd = zd \ No newline at end of file + 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) \ No newline at end of file diff --git a/ChanKLC.py b/ChanKLC.py index 3d88e8d..8e8bc7d 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -48,6 +48,7 @@ class ChanKLC(): self.ema52 = klu.ema52 self.ema24 = klu.ema24 self.trend = Chan_PRICE_TREND.UNKNOWN + self.exception = klu.exception def set_trend(self, trend): self.trend = trend def to_string(self): diff --git a/ChanKLU.py b/ChanKLU.py index 3a15f81..552a1a0 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -36,6 +36,8 @@ class ChanKLU: self.body_ratio = self.body / self.range self.upper_shadow_ratio = self.upper_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.continue_div = 0 @@ -65,6 +67,10 @@ class ChanKLU: #print(self.open, self.close, self.high, self.low, self.candle_dir, self.strength) def set_macd_state(self, 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): self.trend = trend def set_next(self, next): diff --git a/ChanLun.py b/ChanLun.py index 29ad727..b97fd4d 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -246,7 +246,7 @@ class ChanLun(): if last_down_sbi.has_fx_gap: look_for_bottom = True 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) last_seg.set_next(seg) seg.set_pre(last_seg) @@ -272,7 +272,7 @@ class ChanLun(): #print(bi.start_time, look_for_top, "UP 3") else: 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) last_seg.set_next(seg) seg.set_pre(last_seg) @@ -340,7 +340,7 @@ class ChanLun(): if last_up_sbi.has_fx_gap: look_for_top = True 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) last_seg.set_next(seg) seg.set_pre(last_seg) @@ -366,7 +366,7 @@ class ChanLun(): #print(bi.start_time, look_for_top, "DOWN 3") else: 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) last_seg.set_next(seg) seg.set_pre(last_seg) @@ -414,14 +414,14 @@ class ChanLun(): else: if bi.check_overlap(): 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_sbi = ChanSBI(bi, len(up_sbi_list), bi.dir) seg_list.append(seg) last_seg = seg #print(bi.start_time, 'Create first UP SEG') 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_sbi = ChanSBI(bi, len(down_sbi_list), bi.dir) seg_list.append(seg) @@ -448,7 +448,7 @@ class ChanLun(): # The confirmed print("Last UP seg is broken, create a new seg. 1") 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) last_seg = seg_list[-2] if len(last_seg.bi_list) > 3: @@ -460,7 +460,7 @@ class ChanLun(): if bi_list[i].low < last_seg_peak: print("Last DOWN seg is broken, create a new seg. 1") 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) last_seg = seg_list[-2] if len(last_seg.bi_list) > 3: @@ -478,12 +478,13 @@ class ChanLun(): if bi_list[i].high > last_seg_peak: print("Last seg is broken, create a new seg. 2") 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) last_seg = seg last_seg_bi = bi_list[i] break """ + self.cal_bi_zs(seg_list) return seg_list def cal_trend(self, klc_list): """ @@ -1170,7 +1171,7 @@ class ChanLun(): # SEG is not in ZS 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)): - 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 #print(seg.start_bi.start_klc.start_time) first_bi_out = None @@ -1285,6 +1286,13 @@ class ChanLun(): bsp_list.append(bsp) #self.print_zs(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): return Decimal("{:.2f}".format(value)) @@ -1297,19 +1305,33 @@ class ChanLun(): for klu in klu_list: if len(klc_list) > 0: last_klc = klc_list[-1] - included = last_klc.check_klu_included(klu) - if not included: + if klu.exception: 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.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) last_klc.set_next(klc) klc.set_pre(last_klc) last_klc.set_end_klu(last_klu) klc.set_pre_fx() + #print(klu.time, klu.high, klu.low, klu.close, klu.open, klu.exception) 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: ddir = Chan_KLINE_DIR.UP if klu.open > klu.close: diff --git a/ChanSEG.py b/ChanSEG.py index e102875..ba0adfc 100644 --- a/ChanSEG.py +++ b/ChanSEG.py @@ -1,12 +1,12 @@ import copy from typing import Dict, Optional -from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_SEG_DIR -import ChanKLU +from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_SEG_DIR, Chan_BI_DIR, Chan_ZS_DIR import ChanCTime from ChanBI import ChanBI +from ChanBIZS import ChanBIZS 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_time = start_bi.start_time self.end_time = None @@ -28,6 +28,17 @@ class ChanSEG(): self.sure_time = None self.macd_hist = 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): self.macd_hist = macd_hist def set_macd_div(self, macd_div): @@ -63,14 +74,104 @@ class ChanSEG(): self.is_sure = True self.format_bi_list() def format_bi_list(self): - bi_list = [] - bi_list.append(self.start_bi) + self.bi_list = [] + self.bi_list.append(self.start_bi) if self.end_bi: next_bi = self.start_bi.next for i in range(self.start_bi.index, self.end_bi.index): if next_bi: self.bi_list.append(next_bi) + next_bi.set_seg(self) next_bi = next_bi.next def add_bi(self, bi: ChanBI): if len(self.bi_list) > 0: - self.bi_list.append(bi) \ No newline at end of file + 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 \ No newline at end of file diff --git a/web/app.py b/web/app.py index 56bf248..b813cf7 100644 --- a/web/app.py +++ b/web/app.py @@ -125,7 +125,7 @@ def get_crypto_kl_data(symbol, timeframe, limit=1000, start_time=None, end_time= # 添加请求计数和最大限制 request_count = 0 - max_requests = 50 # 最大请求次数,防止无限循环 + max_requests = 300 # 最大请求次数,防止无限循环 # 分页加载数据 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 len(df) > 10000: # 防止数据量过大,设置一个合理的上限 - df = df.tail(10000).reset_index(drop=True) + if len(df) > 100000: # 防止数据量过大,设置一个合理的上限 + df = df.tail(100000).reset_index(drop=True) elif limit and len(df) > limit: # 如果没有指定明确时间范围,使用默认的limit限制 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) seg_list = chan.get_seg_list(bi_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: bi.cal_macdhist() for bi in bi_list: @@ -513,63 +518,6 @@ def analyze_chan(df, symbol=None, timeframe=None): '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 { 'klc_list': klc_list, @@ -577,474 +525,12 @@ def analyze_chan(df, symbol=None, timeframe=None): 'bi_list': bi_list, 'seg_list': seg_list, 'zs_list': zs_list, - 'trade_points': buy_sell_points, + 'bi_zs_list': bi_zs_list, # 添加BI中枢列表 'klc_fx_info': klc_fx_info, # KLC分型信息 - 'klu_fx_info': klu_fx_info, # 添加KLU分型信息 'chan_macd': chan_macd_data, # 添加ChanMACD分析数据 '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): """转换方向枚举为数字""" @@ -1719,6 +1205,20 @@ def analyze(): 'dd': zs.dd, 'is_sure': zs.is_sure # 添加中枢是否完成的标志 } 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': [{ '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, 'is_sure': zs.is_sure # 未完成中枢的is_sure为False } 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']], + # 添加未完成BI中枢列表 + '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 analysis_result.get('bi_zs_list', []) if not getattr(zs, 'is_sure', False)], + 'macd': macd_data, # 添加布林带数据 'bollinger': { @@ -1759,16 +1268,6 @@ def analyze(): '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']], # 添加ChanMACD分析数据 'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz), # 添加多时间周期EMA52数据 @@ -1885,13 +1384,35 @@ def analyze(): 'dd': zs.dd, 'is_sure': zs.is_sure # 未完成中枢的is_sure为False } 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'] = [{ @@ -1903,18 +1424,7 @@ def analyze(): 'fx_strength_level': str(point['fx_strength_level']), # 分型强度等级 'is_strong_fx': bool(point['is_strong_fx']) # 是否为强分型 } 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分析数据 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: 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): """获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段""" diff --git a/web/static/css/style.css b/web/static/css/style.css index 596cf3f..a1ff895 100644 --- a/web/static/css/style.css +++ b/web/static/css/style.css @@ -122,7 +122,7 @@ table.dataTable tbody tr:hover { } .chart-container { - height: 400px; + height: 800px; } .controls .row { diff --git a/web/templates/index.html b/web/templates/index.html index 40f163b..c3ce2bd 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -48,9 +48,10 @@ margin-bottom: 6px; color: #495057; } + /*主图大小*/ .chart-container { width: 100%; - height: 900px; + height: 1200px; margin-top: 10px; border: 1px solid #e9ecef; border-radius: 8px; @@ -635,9 +636,6 @@ margin-right: 4px; display: inline-block; } - - - .bb-config-modal { position: fixed; top: 0; @@ -877,22 +875,22 @@