diff --git a/ChanKLC.py b/ChanKLC.py index 2a5b8f0..d728911 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -50,6 +50,13 @@ class ChanKLC(): self.trend = Chan_PRICE_TREND.UNKNOWN def set_trend(self, trend): self.trend = trend + def to_string(self): + out = "" + start = self.start_time if self.start_time is not None else "" + end = self.end_time if self.end_time is not None else "" + price_diff = getattr(self, 'price_diff', None) + out += str(start) + " " + str(end) + " " + str(self.close) + " " + str(self.ema24) + " " + str(self.ema52) + " " + str(self.trend) + " " + str(self.close - self.ema52) + return out def set_klc_fx_type(self, klc_fx_type): #print(self.start_time, klc_fx_type, self.get_feature_data()['klu_macd'], self.get_feature_data()['klu_macdhist'], self.get_feature_data()['klu_rsi']) self.klc_fx_type = klc_fx_type diff --git a/ChanLun.py b/ChanLun.py index a7e16f0..7606a15 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -74,18 +74,24 @@ class ChanLun(): if len(self.tf_df_dict) > 0: return {key: self.tf_df_dict[key].get_ema24() for key in self.ema_symbols} return None + def get_current_klc_dict(self): + if len(self.tf_df_dict) > 0: + return {key: self.tf_df_dict[key].get_current_klc() for key in self.ema_symbols} + return None def cal_bsp(self): return def check_fx(self, klc): if klc.pre and klc.next: if klc.high > klc.pre.high and klc.high > klc.next.high: - 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") + if klc.close > klc.ema52 or klc.next.close > klc.next.ema52: + 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 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 + if klc.close < klc.ema52 or klc.next.close < klc.next.ema52: + 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 return Chan_FX_TYPE.UNKNOWN def add_indicators(self, df): fast = 12 diff --git a/ChanMACDHistSet.py b/ChanMACDHistSet.py index d1d59d5..f585ba6 100644 --- a/ChanMACDHistSet.py +++ b/ChanMACDHistSet.py @@ -26,7 +26,7 @@ class ChanMACDHistSet(): def set_middle_klu(self, middle_klu): self.middle_klu = middle_klu #self.middle_area = abs(middle_klu.macdhist) - #self.middle_klu = None + self.middle_klu = None def set_unittf_div(self, unittf_div): self.unittf_div = unittf_div def add_klu(self, klu): diff --git a/TF_DF.py b/TF_DF.py index a195ade..f335c63 100644 --- a/TF_DF.py +++ b/TF_DF.py @@ -56,6 +56,10 @@ class TF_DF(): return None return float(ema24_value) return None + def get_current_klc(self): + if len(self.klc_list) > 0: + return self.klc_list[-2] + return None def add_indicators(self, df): fast = 12 slow = 26 @@ -93,14 +97,33 @@ class TF_DF(): df['macd'] = macd['macd'] df['macdsignal'] = macd['macdsignal'] df['macdhist'] = macd['macdhist'] - df['ema5'] = ta.EMA(df, timeperiod=5) - df['ema10'] = ta.EMA(df, timeperiod=10) - df['ema24'] = ta.EMA(df, timeperiod=24) - df['ema26'] = ta.EMA(df, timeperiod=26) - df['ema52'] = ta.EMA(df, timeperiod=52) + df['ema5'] = self.cal_ema(df, 5) + df['ema10'] = self.cal_ema(df, 10) + df['ema24'] = self.cal_ema(df, 24) + df['ema26'] = self.cal_ema(df, 26) + df['ema52'] = self.cal_ema(df, 52) df['rsi'] = ta.RSI(df, timeperiod=14) df['volume_ratio'] = self.cal_volume_ratio(df) return df + @staticmethod + def cal_ema(df, timeperiod): + """ + 计算 EMA,优先使用 pandas ewm(adjust=False) 以贴近前端/TradingView 显示; + 必要时回退到 TA-Lib(abstract)。 + """ + try: + series = df['close'].astype(float) if isinstance(df, pd.DataFrame) else pd.Series(df).astype(float) + return series.ewm(span=int(timeperiod), adjust=False).mean() + except Exception: + try: + if isinstance(df, pd.DataFrame): + return ta.EMA(df, timeperiod=int(timeperiod)) + except Exception: + pass + # 最后回退:返回同索引的 NaN 序列 + if isinstance(df, pd.DataFrame) and 'close' in df: + return pd.Series(np.nan, index=df.index) + return pd.Series(dtype=float) def check_fx(self, klc): if klc.pre and klc.next: if klc.high > klc.pre.high and klc.high > klc.next.high: diff --git a/strategies/ChanLun_BTC.py b/strategies/ChanLun_BTC.py index b2e78f5..dba6a92 100644 --- a/strategies/ChanLun_BTC.py +++ b/strategies/ChanLun_BTC.py @@ -22,7 +22,7 @@ logger = logging.getLogger(__name__) # freqtrade trade -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies # freqtrade backtesting -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange=20250901- -# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m --pairs BTC/USDT:USDT --timerange=20250405- +# freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m 1m 1h 1d 1M --pairs BTC/USDT:USDT --timerange=20250405- # freqtrade download-data -c ./user_data/Chan/config/ChanLun_BTC_30.json -t 1m 1h 1d 1M --pairs BTC/USDT --timerange=20170101- # freqtrade hyperopt --hyperopt-loss SharpeHyperOptLossDaily --spaces roi --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies -c ./user_data/Chan/config/ChanLun_BTC_30.json -e 200 --timerange=20250201-20250901 # freqtrade edge -c ./user_data/Chan/config/ChanLun_BTC_30.json --strategy ChanLun_BTC --strategy-path ./user_data/Chan/strategies --timerange 20250721-20250901 @@ -120,13 +120,16 @@ class ChanLun_BTC(IStrategy): dataframe_1d = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1d') dataframe_1M = self.dp.get_pair_dataframe(pair=self.pair, timeframe='1M') self.chan.init_dataframes(dataframe_m, dataframe_1h, dataframe_1d, dataframe_1M) - self.print_all_ema52() + self.print_all_current_klc() def print_all_ema52(self): for key, value in self.chan.get_ema52_dict().items(): print(key, value) def print_all_ema24(self): for key, value in self.chan.get_ema24_dict().items(): print(key, value) + def print_all_current_klc(self): + for key, value in self.chan.get_current_klc_dict().items(): + print(key, value.to_string()) def add_indicators(self, df): fast = 12 slow = 26 diff --git a/web/app.py b/web/app.py index 92ac231..38d4384 100644 --- a/web/app.py +++ b/web/app.py @@ -352,12 +352,13 @@ def analyze_chan(df, symbol=None, timeframe=None): # 初始化多时间周期数据以获取EMA52 ema52_dict = None - if symbol and timeframe: + # 先暂时不用这个功能,太慢了 + if symbol and timeframe and False: try: # 获取不同时间周期的数据用于初始化 - df_1h = get_kl_data(symbol, '1h', limit=800) if timeframe != '1h' else df - df_1d = get_kl_data(symbol, '1d', limit=800) if timeframe != '1d' else df - df_1M = get_kl_data(symbol, '1M', limit=800) if timeframe != '1M' else df + df_1h = get_kl_data(symbol, '1h', limit=1500) if timeframe != '1h' else df + df_1d = get_kl_data(symbol, '1d', limit=2000) if timeframe != '1d' else df + df_1M = get_kl_data(symbol, '1M', limit=1500) if timeframe != '1M' else df # 添加指标 if df_1h is not None and len(df_1h) > 0: @@ -1659,6 +1660,24 @@ def analyze(): else: replay_data = None + # 基于已有 KLC 列表生成趋势标记(不做额外计算) + klc_trend = [] + try: + for klc in analysis_result.get('klc_list', []): + trend_val = getattr(klc, 'trend', None) + t_obj = getattr(klc, 'end_time', None) or getattr(klc, 'start_time', None) + if trend_val is None or t_obj is None: + continue + # 统一成字符串:UP/DOWN/FLAT/UNKNOWN + trend_name = str(trend_val) + if '.' in trend_name: + trend_name = trend_name.split('.')[-1] + time_str = format_time_safely(t_obj, client_tz) + if time_str: + klc_trend.append({'time': time_str, 'trend': trend_name}) + except Exception: + klc_trend = [] + # 添加主周期分析结果到返回数据 result.update({ 'kline_data': clean_dataframe_for_json(df).to_dict('records'), @@ -1753,7 +1772,9 @@ def analyze(): # 添加ChanMACD分析数据 'chan_macd': serialize_chan_macd_data(analysis_result.get('chan_macd', {}), client_tz), # 添加多时间周期EMA52数据 - 'ema52_dict': analysis_result.get('ema52_dict', {}) + 'ema52_dict': analysis_result.get('ema52_dict', {}), + # 直接输出KLC趋势标记(使用已有trend字段) + 'klc_trend': klc_trend }) # 如果生成了回放数据,添加到返回结果中 @@ -1775,6 +1796,23 @@ def analyze(): # 计算小周期MACD数据 element_macd_data = calculate_macd(element_df) + # 组装小周期 KLC 趋势(仅提取已有 trend,不做重算) + try: + element_klc_trend = [] + for klc in element_analysis.get('klc_list', []): + trend_val = getattr(klc, 'trend', None) + t_obj = getattr(klc, 'end_time', None) or getattr(klc, 'start_time', None) + if trend_val is None or t_obj is None: + continue + trend_name = str(trend_val) + if '.' in trend_name: + trend_name = trend_name.split('.')[-1] + time_str = format_time_safely(t_obj, client_tz) + if time_str: + element_klc_trend.append({'time': time_str, 'trend': trend_name}) + except Exception: + element_klc_trend = [] + # 添加小周期分析结果到返回数据 result['element_timeframe'] = element_timeframe result['element_macd'] = element_macd_data # 添加小周期MACD数据 @@ -1880,6 +1918,9 @@ def analyze(): # 添加次周期ChanMACD分析数据 result['element_chan_macd'] = serialize_chan_macd_data(element_analysis.get('chan_macd', {}), client_tz) + # 添加小周期 KLC 趋势标记 + result['element_klc_trend'] = element_klc_trend + pass return jsonify(result) diff --git a/web/templates/index.html b/web/templates/index.html index 7a01d72..528aade 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -801,8 +801,8 @@
-
-
+
+
-
- - -
-
+
-
+
@@ -860,7 +852,7 @@
-
+
@@ -876,11 +868,41 @@
- +
+
+ + +
+
+ + +
+ + +
+ +
- + +
+ +
@@ -897,33 +919,28 @@
-
- - -
- - -
-
- - + +
-
- - -
- + +
+ +
@@ -940,21 +957,13 @@
-
- - -
- - -
-
- - + +
@@ -962,64 +971,6 @@
-
-
- - -
- -
- - -
- - -
- - -
- - -
- - -
- - - -
- - -
-
@@ -2025,19 +1976,9 @@ refreshChart(currentData); }); - // 添加分型类型复选框变更事件 - $('#showKlcFxType').change(function() { - refreshChartOnly(); - }); - - // 添加布林带显示变更事件 - $('#showMainBollinger').change(function() { - updateChartDisplay(); - }); - - $('#showElementBollinger').change(function() { - updateChartDisplay(); - }); + // Trend 显示开关 + $('#showMainTrend').change(function() { refreshChartOnly(); }); + $('#showElementTrend').change(function() { refreshChartOnly(); }); // 添加K线周期切换事件监听器 $('input[name="klinePeriod"]').change(function() { @@ -3029,8 +2970,8 @@ // 添加ChanMACD分析标注 // 根据主/次周期开关与各自的“显示U”独立控制 const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd; - const allowU = useElementPeriod ? (typeof window.showUOnElement === 'undefined' ? true : window.showUOnElement) - : (typeof window.showUOnMain === 'undefined' ? true : window.showUOnMain); + // 默认不显示,必须用户勾选对应复选框 + const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain; if (cm && allowU) { console.log('添加ChanMACD分析标注:', { segListLength: cm.seg_list ? cm.seg_list.length : 0, @@ -3088,7 +3029,7 @@ ); // 主周期 U 标记(蓝/橙,与原样式一致) - if ((typeof window.showUOnMain === 'undefined' ? true : window.showUOnMain) && Array.isArray(mainCm.klu_list)) { + if (window.showUOnMain && Array.isArray(mainCm.klu_list)) { mainCm.klu_list.forEach((item) => { if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); @@ -3110,7 +3051,7 @@ } // 次周期 U 标记(使用不同配色以区分) - if ((typeof window.showUOnElement === 'undefined' ? true : window.showUOnElement) && Array.isArray(elementCm.klu_list)) { + if (window.showUOnElement && Array.isArray(elementCm.klu_list)) { elementCm.klu_list.forEach((item) => { if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); @@ -5488,12 +5429,86 @@ window.fxMarkers = elementFxMarkers; } - // 合并主周期、小周期分型与 UnitTF 标记,统一设置到K线数据系列 + // 基于后端提供的 KLC 趋势生成标记(不进行任何计算) + let klcTrendMarkers = []; + try { + if (currentData.klc_trend && currentData.klc_trend.length > 0) { + console.log('KLC趋势点数量:', currentData.klc_trend.length, currentData.klc_trend.slice(0, 3)); + // 当前图表的bar时间集合(秒)用于对齐标记到最近的K线 + const seriesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set(); + const nearestTime = (target) => { + if (!Array.isArray(candles) || candles.length === 0) return target; + // 简单线性查找(数据量通常可接受),必要时可替换为二分 + let best = candles[0].time; + let bestDiff = Math.abs(best - target); + for (let i = 1; i < candles.length; i++) { + const t = candles[i].time; + const d = Math.abs(t - target); + if (d < bestDiff) { best = t; bestDiff = d; } + } + return best; + }; + + klcTrendMarkers = currentData.klc_trend.map(t => { + const ts = Math.floor(new Date(t.time).getTime() / 1000); + const trendRaw = (t.trend || '').toString().toUpperCase(); + let timeAligned = seriesTimes.has(ts) ? ts : nearestTime(ts); + let marker = { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'square', size: 0.8 }; + if (trendRaw === 'UP') { + marker = { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 }; + } else if (trendRaw === 'DOWN') { + marker = { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 }; + } else if (trendRaw === 'FLAT') { + marker = { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 }; + } else { + // UNKNOWN 或其他 + marker = { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 }; + } + return marker; + }); + console.log('KLC趋势标记(对齐后)示例:', klcTrendMarkers.slice(0, 5)); + } + } catch (e) { + klcTrendMarkers = []; + } + // 暴露到全局以便调试或后续合并 + window.klcTrendMarkers = klcTrendMarkers; + + // 无论当前显示主/小周期,只要勾选对应Trend,就叠加出来 + let trendMarkersToUse = []; + if ($('#showMainTrend').is(':checked')) { + trendMarkersToUse = trendMarkersToUse.concat(window.klcTrendMarkers || []); + } + if ($('#showElementTrend').is(':checked') && currentData.element_klc_trend) { + const candlesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set(); + const nearestTime = (target) => { + if (!Array.isArray(candles) || candles.length === 0) return target; + let best = candles[0].time, bestDiff = Math.abs(best - target); + for (let i = 1; i < candles.length; i++) { + const t = candles[i].time, d = Math.abs(t - target); + if (d < bestDiff) { best = t; bestDiff = d; } + } + return best; + }; + const elementMarkers = currentData.element_klc_trend.map(t => { + const ts = Math.floor(new Date(t.time).getTime() / 1000); + const trendRaw = (t.trend || '').toString().toUpperCase(); + const timeAligned = candlesTimes.has(ts) ? ts : nearestTime(ts); + if (trendRaw === 'UP') return { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 }; + if (trendRaw === 'DOWN') return { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 }; + if (trendRaw === 'FLAT') return { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 }; + return { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 }; + }); + trendMarkersToUse = trendMarkersToUse.concat(elementMarkers); + } + + // 合并标记并设置 const combinedMarkers = [ ...(window.mainFxMarkers || []), ...allElementFxMarkers, ...(window.kluDivMarkersMain || []), - ...(window.kluDivMarkersElement || []) + ...(window.kluDivMarkersElement || []), + ...trendMarkersToUse ]; if (combinedMarkers.length > 0) { console.log('合并设置', combinedMarkers.length, '个标记(主周期分型:', (window.mainFxMarkers || []).length, '个,小周期分型:', allElementFxMarkers.length, '个,UnitTF:', (window.unittfMarkers || []).length, '个)'); @@ -5513,11 +5528,76 @@ } else { console.log('绘制小周期分型标记 - 已禁用或无数据'); - // 只设置主周期分型 + UnitTF 标记 + // 计算并缓存KLC趋势标记(即使未启用小周期分型,也应显示趋势) + try { + let klcTrendMarkers = []; + if (currentData.klc_trend && currentData.klc_trend.length > 0) { + console.log('KLC趋势点数量:', currentData.klc_trend.length, currentData.klc_trend.slice(0, 3)); + const seriesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set(); + const nearestTime = (target) => { + if (!Array.isArray(candles) || candles.length === 0) return target; + let best = candles[0].time; + let bestDiff = Math.abs(best - target); + for (let i = 1; i < candles.length; i++) { + const t = candles[i].time; + const d = Math.abs(t - target); + if (d < bestDiff) { best = t; bestDiff = d; } + } + return best; + }; + klcTrendMarkers = currentData.klc_trend.map(t => { + const ts = Math.floor(new Date(t.time).getTime() / 1000); + const trendRaw = (t.trend || '').toString().toUpperCase(); + const timeAligned = seriesTimes.has(ts) ? ts : nearestTime(ts); + if (trendRaw === 'UP') { + return { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 }; + } else if (trendRaw === 'DOWN') { + return { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 }; + } else if (trendRaw === 'FLAT') { + return { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 }; + } else { + return { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 }; + } + }); + console.log('KLC趋势标记(对齐后)示例:', klcTrendMarkers.slice(0, 5)); + } + window.klcTrendMarkers = klcTrendMarkers; + } catch (e) { + window.klcTrendMarkers = []; + } + + // 与上方一致:勾选哪个Trend就显示哪个 + let trendMarkersToUse = []; + if ($('#showMainTrend').is(':checked')) { + trendMarkersToUse = trendMarkersToUse.concat(window.klcTrendMarkers || []); + } + if ($('#showElementTrend').is(':checked') && currentData.element_klc_trend) { + const candlesTimes = (typeof candles !== 'undefined' && Array.isArray(candles)) ? new Set(candles.map(c => c.time)) : new Set(); + const nearestTime = (target) => { + if (!Array.isArray(candles) || candles.length === 0) return target; + let best = candles[0].time, bestDiff = Math.abs(best - target); + for (let i = 1; i < candles.length; i++) { + const t = candles[i].time, d = Math.abs(t - target); + if (d < bestDiff) { best = t; bestDiff = d; } + } + return best; + }; + const elementMarkers = currentData.element_klc_trend.map(t => { + const ts = Math.floor(new Date(t.time).getTime() / 1000); + const trendRaw = (t.trend || '').toString().toUpperCase(); + const timeAligned = candlesTimes.has(ts) ? ts : nearestTime(ts); + if (trendRaw === 'UP') return { time: timeAligned, position: 'aboveBar', color: '#00C853', shape: 'arrowUp', size: 0.5 }; + if (trendRaw === 'DOWN') return { time: timeAligned, position: 'belowBar', color: '#D32F2F', shape: 'arrowDown', size: 0.5 }; + if (trendRaw === 'FLAT') return { time: timeAligned, position: 'inBar', color: '#9E9E9E', shape: 'circle', size: 0.8 }; + return { time: timeAligned, position: 'inBar', color: '#2196F3', shape: 'square', size: 0.8 }; + }); + trendMarkersToUse = trendMarkersToUse.concat(elementMarkers); + } const onlyMainAndU = [ ...(window.mainFxMarkers || []), ...(window.kluDivMarkersMain || []), - ...(window.kluDivMarkersElement || []) + ...(window.kluDivMarkersElement || []), + ...trendMarkersToUse ]; if (onlyMainAndU.length > 0) { console.log('仅设置', onlyMainAndU.length, '个主周期/UnitTF标记(主周期分型:', (window.mainFxMarkers || []).length, ',UnitTF:', (window.unittfMarkers || []).length, ')'); @@ -5675,6 +5755,9 @@ } // 添加买卖点提示 + // 初始化 tooltip 与 U 显示状态 + window.showUOnMain = $('#toggleUOnMain').is(':checked'); + window.showUOnElement = $('#toggleUOnElement').is(':checked'); setupTooltip(mainChart, [], [], mainChartContainer, volumeChartContainer, atrChartContainer, macdChartContainer, chanMacdChartContainer, volumeChart, atrChart, macdChart, chanMacdChart, showMacd); // 显示买卖点 @@ -5895,7 +5978,8 @@ try { if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers(); const cm = useElementPeriod ? (currentData.element_chan_macd || currentData.chan_macd) : currentData.chan_macd; - if (cm) { + const allowU = useElementPeriod ? !!window.showUOnElement : !!window.showUOnMain; + if (cm && allowU) { addAllChanMacdMarkers( cm.seg_list || [], cm.unittf_list || [], @@ -10279,9 +10363,10 @@ }); } - // 保存到全局,供主图与分型一起统一合并绘制 + // 保存到全局,供主图与分型一起统一合并绘制(仅在开关开启时) console.log('DEBUG: U 标记数量:', signalMarkers.length); - window.unittfMarkers = [...signalMarkers, ...boundaryMarkers]; + const allowUMerge = (window.showUOnMain && window.showUOnElement); + window.unittfMarkers = allowUMerge ? [...signalMarkers, ...boundaryMarkers] : []; if (uTooltipMarkers.length > 0) { if (window.fxMarkers) { window.fxMarkers = [ ...window.fxMarkers, ...uTooltipMarkers ];