diff --git a/ChanLun.py b/ChanLun.py index d745a20..bca059a 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -73,8 +73,7 @@ class ChanLun(): klc.set_fx(Chan_FX_TYPE.TOP) #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "TOP") return Chan_FX_TYPE.TOP - if klc.pre and klc.next: - if klc.low < klc.pre.low and klc.low < klc.next.low: + elif klc.low < klc.pre.low and klc.low < klc.next.low: klc.set_fx(Chan_FX_TYPE.BOTTOM) #print(klc.start_time, klc.end_time,klc.next.start_time, klc.next.end_time,klc.fx, "BOTTOM") return Chan_FX_TYPE.BOTTOM @@ -778,7 +777,7 @@ class ChanLun(): # Do nothing if fx == Chan_FX_TYPE.UNKNOWN: - if len(bi_list) > 0: + if len(bi_list) > 0 and klc.end_klu: last_bi = bi_list[-1] #print(klc.start_time, last_bi.start_time, last_bi.end_time, last_bi.dir, last_bi.high, last_bi.low, last_bottom.end_time, "last bi") if last_top and last_bi.dir == Chan_BI_DIR.DOWN: @@ -1082,6 +1081,8 @@ class ChanLun(): if seg.next.next.is_sure: zg = min(seg.high, seg.next.high, seg.next.next.high) zd = max(seg.low, seg.next.low, seg.next.next.low) + gg = max(seg.high, seg.next.high, seg.next.next.high) + dd = min(seg.low, seg.next.low, seg.next.next.low) ddir = Chan_ZS_DIR.UP ddir = None if last_zs: @@ -1104,6 +1105,8 @@ class ChanLun(): zs = ChanZS(seg, len(zs_list), ddir) zs.set_zg(zg) zs.set_zd(zd) + zs.set_gg(gg) + zs.set_dd(dd) if last_zs: last_zs.set_next(zs) zs.set_pre(last_zs) @@ -1130,6 +1133,8 @@ class ChanLun(): if seg.next.next.is_sure: zg = min(seg.high, seg.next.high, seg.next.next.high) zd = max(seg.low, seg.next.low, seg.next.next.low) + gg = max(seg.high, seg.next.high, seg.next.next.high) + dd = min(seg.low, seg.next.low, seg.next.next.low) ddir = None if last_zs: if zg < last_zs.zd: @@ -1151,6 +1156,8 @@ class ChanLun(): zs = ChanZS(seg, len(zs_list), ddir) zs.set_zg(zg) zs.set_zd(zd) + zs.set_gg(gg) + zs.set_dd(dd) last_zs.set_next(zs) zs.set_pre(last_zs) zs_list.append(zs) diff --git a/ChanZS.py b/ChanZS.py index ca577f8..367368a 100644 --- a/ChanZS.py +++ b/ChanZS.py @@ -21,6 +21,8 @@ class ChanZS(): 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 @@ -63,4 +65,8 @@ class ChanZS(): 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 \ No newline at end of file diff --git a/web/app.py b/web/app.py index af9889b..bbf1c66 100644 --- a/web/app.py +++ b/web/app.py @@ -538,6 +538,15 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta '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, @@ -545,12 +554,15 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta '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.end_bi], + } 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': [{ @@ -558,6 +570,8 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta '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': [{ @@ -616,6 +630,15 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta '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, @@ -623,12 +646,15 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta '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.end_bi], + } 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': [{ @@ -636,6 +662,8 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta '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': [{ @@ -686,7 +714,9 @@ def generate_replay_data(df, client_tz, symbol=None, element_timeframe=None, sta 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': [], @@ -1089,6 +1119,16 @@ def analyze(): '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, @@ -1096,12 +1136,16 @@ def analyze(): '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.end_bi], + } 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], # 添加未完成中枢列表 @@ -1110,6 +1154,8 @@ def analyze(): 'end_time': None, # 未完成中枢没有结束时间 'zg': zs.zg, 'zd': zs.zd, + 'gg': zs.gg, + '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': [{ @@ -1201,6 +1247,16 @@ def analyze(): 'direction': convert_direction(bi.dir), 'macd_div': float(bi.macd_div) if hasattr(bi, 'macd_div') else 0 } for bi in element_analysis['bi_list'] if bi.end_klc] + # 添加次周期未完成笔列表 + result['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 element_analysis['bi_list'] if not bi.end_klc] # 添加小周期K线数据 result['element_kline_data'] = clean_dataframe_for_json(element_df).to_dict('records') @@ -1212,13 +1268,17 @@ def analyze(): '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 element_analysis['seg_list'] if seg.end_bi] + } for seg in element_analysis['seg_list'] if seg.is_sure] + # 添加次周期未完成线段列表 + result['element_uncompleted_seg_list'] = get_uncompleted_seg_list(element_analysis['seg_list'], client_tz) result['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 element_analysis['zs_list'] if zs.end_klc] @@ -1227,6 +1287,8 @@ def analyze(): 'end_time': None, # 未完成中枢没有结束时间 'zg': zs.zg, 'zd': zs.zd, + 'gg': zs.gg, + 'dd': zs.dd, 'is_sure': zs.is_sure # 未完成中枢的is_sure为False } for zs in element_analysis['zs_list'] if not zs.is_sure] @@ -1605,6 +1667,43 @@ def filter_stocks(): else: return jsonify({'error': str(e)}) +def get_uncompleted_seg_list(seg_list, client_tz): + """获取未完成线段列表,正确处理倒数第二个和最后一个未完成线段""" + uncompleted_segs = [seg for seg in seg_list if not seg.is_sure] + + if len(uncompleted_segs) == 0: + return [] + + result = [] + + for i, seg in enumerate(uncompleted_segs): + is_last = (i == len(uncompleted_segs) - 1) # 是否为最后一个未完成线段 + + seg_data = { + '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(), + '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, + 'direction': convert_direction(seg.dir) + } + + if is_last: + # 最后一个未完成线段:没有结束时间和价格 + seg_data['end_time'] = None + seg_data['end_price'] = None + else: + # 倒数第二个及之前的未完成线段:使用实际的结束时间和价格 + if seg.end_bi and seg.end_bi.end_klc: + seg_data['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() + seg_data['end_price'] = seg.end_bi.end_klc.high if convert_direction(seg.dir) == 1 else seg.end_bi.end_klc.low + else: + # 如果没有结束笔,设为None + seg_data['end_time'] = None + seg_data['end_price'] = None + + result.append(seg_data) + + return result + def format_fx_type(fx_type): """格式化分型类型显示""" fx_type_map = { diff --git a/web/templates/index.html b/web/templates/index.html index e21f65e..1359660 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -1111,6 +1111,7 @@ 起始价格 结束价格 方向 + 状态 @@ -2578,6 +2579,7 @@ // 清空已有的主周期笔系列 tvWidget.series.mainBiSeries = []; + tvWidget.series.mainUncompletedBiSeries = []; // 遍历处理每个笔 currentData.bi_list.forEach(function(bi) { @@ -2658,6 +2660,10 @@ if ($('#showElementBi').is(':checked') && currentData.element_bi_list && currentData.element_bi_list.length > 0) { console.log(`绘制次周期笔数据,共${currentData.element_bi_list.length}条`); + // 清空已有的次周期笔系列 + tvWidget.series.elementBiSeries = []; + tvWidget.series.elementUncompletedBiSeries = []; + currentData.element_bi_list.forEach(function(bi) { try { // 直接使用UTC时间戳(秒) @@ -2732,11 +2738,130 @@ }); } + // 绘制未完成笔 - 主周期 + if ($('#showMainBi').is(':checked') && currentData.uncompleted_bi_list && currentData.uncompleted_bi_list.length > 0) { + console.log(`绘制主周期未完成笔数据,共${currentData.uncompleted_bi_list.length}条`); + + currentData.uncompleted_bi_list.forEach(function(bi) { + try { + // 直接使用UTC时间戳(秒) + const startTime = Math.floor(new Date(bi.start_time).getTime() / 1000); + // 未完成笔的结束时间设为当前K线的最后时间 + const endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000); + + if (isNaN(startTime) || isNaN(endTime)) { + console.error('主周期未完成笔时间转换错误:', bi.start_time); + return; + } + + const startPrice = parseFloat(bi.start_price); + + if (isNaN(startPrice)) { + console.error('主周期未完成笔价格转换错误:', bi.start_price); + return; + } + + // 根据笔的方向确定终点价格 + let endPrice; + const latestKline = currentData.kline_data[currentData.kline_data.length-1]; + if (bi.direction === 1) { + // 向上笔,终点为最新K线的最高点 + endPrice = parseFloat(latestKline.high); + } else { + // 向下笔,终点为最新K线的最低点 + endPrice = parseFloat(latestKline.low); + } + + // 添加未完成笔(红色虚线) + biLines.push({ + startTime: startTime, + endTime: endTime, + startPrice: startPrice, + endPrice: endPrice, + color: '#FF0000', // 红色 + lineWidth: 1, + lineStyle: 2 // 虚线 + }); + + // 添加到图表对象 + tvWidget.series.mainUncompletedBiSeries.push({ + time: startTime, + value: startPrice, + color: '#FF0000', + lineWidth: 1, + lineStyle: 2 + }); + } catch (e) { + console.error('主周期未完成笔处理出错:', e); + } + }); + } + + // 绘制未完成笔 - 次周期 + if ($('#showElementBi').is(':checked') && currentData.element_uncompleted_bi_list && currentData.element_uncompleted_bi_list.length > 0) { + console.log(`绘制次周期未完成笔数据,共${currentData.element_uncompleted_bi_list.length}条`); + + currentData.element_uncompleted_bi_list.forEach(function(bi) { + try { + // 直接使用UTC时间戳(秒) + const startTime = Math.floor(new Date(bi.start_time).getTime() / 1000); + // 未完成笔的结束时间设为当前K线的最后时间 + const endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000); + + if (isNaN(startTime) || isNaN(endTime)) { + console.error('次周期未完成笔时间转换错误:', bi.start_time); + return; + } + + const startPrice = parseFloat(bi.start_price); + + if (isNaN(startPrice)) { + console.error('次周期未完成笔价格转换错误:', bi.start_price); + return; + } + + // 根据笔的方向确定终点价格 + let endPrice; + const latestKline = currentData.kline_data[currentData.kline_data.length-1]; + if (bi.direction === 1) { + // 向上笔,终点为最新K线的最高点 + endPrice = parseFloat(latestKline.high); + } else { + // 向下笔,终点为最新K线的最低点 + endPrice = parseFloat(latestKline.low); + } + + // 添加未完成笔(红色虚线) + biLines.push({ + startTime: startTime, + endTime: endTime, + startPrice: startPrice, + endPrice: endPrice, + color: '#FF0000', // 红色 + lineWidth: 1, + lineStyle: 2 // 虚线 + }); + + // 添加到图表对象 + tvWidget.series.elementUncompletedBiSeries.push({ + time: startTime, + value: startPrice, + color: '#FF0000', + lineWidth: 1, + lineStyle: 2 + }); + } catch (e) { + console.error('次周期未完成笔处理出错:', e); + } + }); + } + // 添加所有笔到图表 biLines.forEach(line => { const lineSeries = mainChart.addLineSeries({ color: line.color, lineWidth: line.lineWidth, + lineStyle: line.lineStyle || 0, // 支持虚线样式 lastValueVisible: false, priceLineVisible: false, }); @@ -2759,6 +2884,10 @@ if ($('#showMainSeg').is(':checked') && currentData.seg_list && currentData.seg_list.length > 0) { console.log(`绘制主周期线段数据,共${currentData.seg_list.length}条`); + // 清空已有的主周期线段系列 + tvWidget.series.mainSegSeries = []; + tvWidget.series.mainUncompletedSegSeries = []; + currentData.seg_list.forEach(function(seg) { try { // 直接使用UTC时间戳(秒) @@ -2805,6 +2934,10 @@ if ($('#showElementSeg').is(':checked') && currentData.element_seg_list && currentData.element_seg_list.length > 0) { console.log(`绘制次周期线段数据,共${currentData.element_seg_list.length}条`); + // 清空已有的次周期线段系列 + tvWidget.series.elementSegSeries = []; + tvWidget.series.elementUncompletedSegSeries = []; + currentData.element_seg_list.forEach(function(seg) { try { // 直接使用UTC时间戳(秒) @@ -2847,11 +2980,166 @@ }); } + // 绘制未完成线段 - 主周期 + if ($('#showMainSeg').is(':checked') && currentData.uncompleted_seg_list && currentData.uncompleted_seg_list.length > 0) { + console.log(`绘制主周期未完成线段数据,共${currentData.uncompleted_seg_list.length}条`); + + currentData.uncompleted_seg_list.forEach(function(seg) { + try { + // 直接使用UTC时间戳(秒) + const startTime = Math.floor(new Date(seg.start_time).getTime() / 1000); + + if (isNaN(startTime)) { + console.error('主周期未完成线段时间转换错误:', seg.start_time); + return; + } + + const startPrice = parseFloat(seg.start_price); + + if (isNaN(startPrice)) { + console.error('主周期未完成线段价格转换错误:', seg.start_price); + return; + } + + let endTime, endPrice; + + if (seg.end_time && seg.end_price) { + // 有结束时间和价格的未完成线段(倒数第二个等) + endTime = Math.floor(new Date(seg.end_time).getTime() / 1000); + endPrice = parseFloat(seg.end_price); + + if (isNaN(endTime) || isNaN(endPrice)) { + console.error('主周期未完成线段结束时间或价格转换错误:', seg.end_time, seg.end_price); + return; + } + } else { + // 没有结束时间和价格的未完成线段(最后一个) + endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000); + + if (isNaN(endTime)) { + console.error('主周期未完成线段结束时间转换错误'); + return; + } + + // 根据线段的方向确定终点价格 + const latestKline = currentData.kline_data[currentData.kline_data.length-1]; + if (seg.direction === 1) { + // 向上线段,终点为最新K线的最高点 + endPrice = parseFloat(latestKline.high); + } else { + // 向下线段,终点为最新K线的最低点 + endPrice = parseFloat(latestKline.low); + } + } + + // 添加未完成线段(红色虚线) + segLines.push({ + startTime: startTime, + endTime: endTime, + startPrice: startPrice, + endPrice: endPrice, + color: '#FF0000', // 红色 + lineWidth: 2, + lineStyle: 2 // 虚线 + }); + + // 添加到图表对象 + tvWidget.series.mainUncompletedSegSeries.push({ + time: startTime, + value: startPrice, + color: '#FF0000', + lineWidth: 2, + lineStyle: 2 + }); + } catch (e) { + console.error('主周期未完成线段处理出错:', e); + } + }); + } + + // 绘制未完成线段 - 次周期 + if ($('#showElementSeg').is(':checked') && currentData.element_uncompleted_seg_list && currentData.element_uncompleted_seg_list.length > 0) { + console.log(`绘制次周期未完成线段数据,共${currentData.element_uncompleted_seg_list.length}条`); + + currentData.element_uncompleted_seg_list.forEach(function(seg) { + try { + // 直接使用UTC时间戳(秒) + const startTime = Math.floor(new Date(seg.start_time).getTime() / 1000); + + if (isNaN(startTime)) { + console.error('次周期未完成线段时间转换错误:', seg.start_time); + return; + } + + const startPrice = parseFloat(seg.start_price); + + if (isNaN(startPrice)) { + console.error('次周期未完成线段价格转换错误:', seg.start_price); + return; + } + + let endTime, endPrice; + + if (seg.end_time && seg.end_price) { + // 有结束时间和价格的未完成线段(倒数第二个等) + endTime = Math.floor(new Date(seg.end_time).getTime() / 1000); + endPrice = parseFloat(seg.end_price); + + if (isNaN(endTime) || isNaN(endPrice)) { + console.error('次周期未完成线段结束时间或价格转换错误:', seg.end_time, seg.end_price); + return; + } + } else { + // 没有结束时间和价格的未完成线段(最后一个) + endTime = Math.floor(new Date(currentData.kline_data[currentData.kline_data.length-1].date).getTime() / 1000); + + if (isNaN(endTime)) { + console.error('次周期未完成线段结束时间转换错误'); + return; + } + + // 根据线段的方向确定终点价格 + const latestKline = currentData.kline_data[currentData.kline_data.length-1]; + if (seg.direction === 1) { + // 向上线段,终点为最新K线的最高点 + endPrice = parseFloat(latestKline.high); + } else { + // 向下线段,终点为最新K线的最低点 + endPrice = parseFloat(latestKline.low); + } + } + + // 添加未完成线段(红色虚线) + segLines.push({ + startTime: startTime, + endTime: endTime, + startPrice: startPrice, + endPrice: endPrice, + color: '#FF0000', // 红色 + lineWidth: 2, + lineStyle: 2 // 虚线 + }); + + // 添加到图表对象 + tvWidget.series.elementUncompletedSegSeries.push({ + time: startTime, + value: startPrice, + color: '#FF0000', + lineWidth: 2, + lineStyle: 2 + }); + } catch (e) { + console.error('次周期未完成线段处理出错:', e); + } + }); + } + // 添加所有线段到图表 segLines.forEach(line => { const lineSeries = mainChart.addLineSeries({ color: line.color, lineWidth: line.lineWidth, + lineStyle: line.lineStyle || 0, // 支持虚线样式 lastValueVisible: false, priceLineVisible: false, }); @@ -2886,6 +3174,8 @@ 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)) { console.error('主周期中枢价格转换错误:', zs.zg, zs.zd); @@ -2944,6 +3234,36 @@ { time: endTime, value: zg } ]); + // 绘制gg线(中枢高高) + if (!isNaN(gg) && gg > 0) { + const ggSeries = mainChart.addLineSeries({ + color: '#F1C40F', // 使用中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ggSeries.setData([ + { time: startTime, value: gg }, + { time: endTime, value: gg } + ]); + } + + // 绘制dd线(中枢低低) + if (!isNaN(dd) && dd > 0) { + const ddSeries = mainChart.addLineSeries({ + color: '#F1C40F', // 使用中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ddSeries.setData([ + { time: startTime, value: dd }, + { time: endTime, value: dd } + ]); + } + // 添加到图表对象 tvWidget.series.mainZsSeries.push({ time: startTime, @@ -2992,6 +3312,8 @@ 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)) { console.error('次周期中枢价格转换错误:', zs.zg, zs.zd); @@ -3050,6 +3372,36 @@ { time: endTime, value: zg } ]); + // 绘制gg线(中枢高高) + if (!isNaN(gg) && gg > 0) { + const ggSeries = mainChart.addLineSeries({ + color: '#3f51b5', // 使用次周期中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ggSeries.setData([ + { time: startTime, value: gg }, + { time: endTime, value: gg } + ]); + } + + // 绘制dd线(中枢低低) + if (!isNaN(dd) && dd > 0) { + const ddSeries = mainChart.addLineSeries({ + color: '#3f51b5', // 使用次周期中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ddSeries.setData([ + { time: startTime, value: dd }, + { time: endTime, value: dd } + ]); + } + // 添加到图表对象 tvWidget.series.elementZsSeries.push({ time: startTime, @@ -3106,6 +3458,8 @@ 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)) { console.error('主周期未完成中枢价格转换错误:', zs.zg, zs.zd); @@ -3116,7 +3470,6 @@ const topSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, - lineStyle: 2, // 虚线 lastValueVisible: false, priceLineVisible: false, }); @@ -3130,7 +3483,6 @@ const bottomSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, - lineStyle: 2, // 虚线 lastValueVisible: false, priceLineVisible: false, }); @@ -3144,7 +3496,6 @@ const leftSeries = mainChart.addLineSeries({ color: '#F1C40F', // 主周期中枢颜色 lineWidth: 1, - lineStyle: 2, // 虚线 lastValueVisible: false, priceLineVisible: false, }); @@ -3171,34 +3522,60 @@ } ]); + // 绘制gg线(中枢高高) + if (!isNaN(gg) && gg > 0) { + const ggSeries = mainChart.addLineSeries({ + color: '#F1C40F', // 使用中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ggSeries.setData([ + { time: startTime, value: gg }, + { time: endTime, value: gg } + ]); + } + + // 绘制dd线(中枢低低) + if (!isNaN(dd) && dd > 0) { + const ddSeries = mainChart.addLineSeries({ + color: '#F1C40F', // 使用中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ddSeries.setData([ + { time: startTime, value: dd }, + { time: endTime, value: dd } + ]); + } + // 添加到图表对象 tvWidget.series.mainUncompletedZsSeries.push({ time: startTime, value: zg, color: '#F1C40F', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); tvWidget.series.mainUncompletedZsSeries.push({ time: endTime, value: zg, color: '#F1C40F', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); tvWidget.series.mainUncompletedZsSeries.push({ time: startTime, value: zd, color: '#F1C40F', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); tvWidget.series.mainUncompletedZsSeries.push({ time: endTime, value: zd, color: '#F1C40F', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); } catch (e) { console.error('主周期未完成中枢处理出错:', e); @@ -3224,6 +3601,8 @@ 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)) { console.error('次周期未完成中枢价格转换错误:', zs.zg, zs.zd); @@ -3234,7 +3613,6 @@ const topSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, - lineStyle: 2, // 虚线 lastValueVisible: false, priceLineVisible: false, }); @@ -3248,7 +3626,6 @@ const bottomSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, - lineStyle: 2, // 虚线 lastValueVisible: false, priceLineVisible: false, }); @@ -3262,7 +3639,6 @@ const leftSeries = mainChart.addLineSeries({ color: '#3f51b5', // 次周期中枢颜色 lineWidth: 1, - lineStyle: 2, // 虚线 lastValueVisible: false, priceLineVisible: false, }); @@ -3289,34 +3665,60 @@ } ]); + // 绘制gg线(中枢高高) + if (!isNaN(gg) && gg > 0) { + const ggSeries = mainChart.addLineSeries({ + color: '#3f51b5', // 使用次周期中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ggSeries.setData([ + { time: startTime, value: gg }, + { time: endTime, value: gg } + ]); + } + + // 绘制dd线(中枢低低) + if (!isNaN(dd) && dd > 0) { + const ddSeries = mainChart.addLineSeries({ + color: '#3f51b5', // 使用次周期中枢自己的颜色 + lineWidth: 1, + lastValueVisible: false, + priceLineVisible: false, + }); + + ddSeries.setData([ + { time: startTime, value: dd }, + { time: endTime, value: dd } + ]); + } + // 添加到图表对象 tvWidget.series.elementUncompletedZsSeries.push({ time: startTime, value: zg, color: '#3f51b5', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); tvWidget.series.elementUncompletedZsSeries.push({ time: endTime, value: zg, color: '#3f51b5', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); tvWidget.series.elementUncompletedZsSeries.push({ time: startTime, value: zd, color: '#3f51b5', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); tvWidget.series.elementUncompletedZsSeries.push({ time: endTime, value: zd, color: '#3f51b5', - lineWidth: 1, - lineStyle: 2 + lineWidth: 1 }); } catch (e) { console.error('次周期未完成中枢处理出错:', e); @@ -5233,27 +5635,56 @@ } // 根据用户选择决定使用哪个周期的数据 - let segData, segSource; + let segData, segSource, uncompletedSegData; if (useElementPeriod && data.element_seg_list && data.element_seg_list.length > 0) { segData = data.element_seg_list; + uncompletedSegData = data.element_uncompleted_seg_list || []; segSource = '小周期'; } else { segData = data.seg_list; + uncompletedSegData = data.uncompleted_seg_list || []; segSource = '主周期'; } - console.log(`表格显示${segSource}线段数据,共${segData ? segData.length : 0}条`); + + // 合并已完成和未完成的线段数据 + let allSegData = []; + if (segData && segData.length > 0) { + allSegData = allSegData.concat(segData.map(seg => ({...seg, status: '已完成'}))); + } + if (uncompletedSegData && uncompletedSegData.length > 0) { + allSegData = allSegData.concat(uncompletedSegData.map(seg => ({...seg, status: '未完成'}))); + } + + console.log(`表格显示${segSource}线段数据,已完成${segData ? segData.length : 0}条,未完成${uncompletedSegData ? uncompletedSegData.length : 0}条,总计${allSegData.length}条`); tables.seg = $('#segTable').DataTable({ - data: segData || [], + data: allSegData, order: [[0, 'desc']], pageLength: 25, columns: [ { data: 'start_time', render: formatTime }, - { data: 'end_time', render: formatTime }, + { data: 'end_time', render: function(data, type, row) { + if (data === null || data === undefined) { + return type === 'display' ? '未完成' : ''; + } + return formatTime(data, type, row); + }}, { data: 'sure_time', render: formatConfirmTime }, { data: 'start_price', render: formatPrice }, - { data: 'end_price', render: formatPrice }, - { data: 'direction', render: formatDirection } + { data: 'end_price', render: function(data, type, row) { + if (data === null || data === undefined) { + return type === 'display' ? '未完成' : ''; + } + return formatPrice(data, type, row); + }}, + { data: 'direction', render: formatDirection }, + { data: 'status', render: function(data, type, row) { + if (type === 'display') { + const color = data === '已完成' ? 'green' : 'red'; + return `${data}`; + } + return data; + }} ] });