diff --git a/ChanKLU.py b/ChanKLU.py index eeb32b3..3a4855b 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -96,7 +96,6 @@ class ChanKLU: self.trend = trend def set_separate_div(self, separate_div): self.separate_div = separate_div - bb2633_status = self.check_bb2633() if self.klc and self.klc.pre and self.klc.next: fx = self.check_fx_dir(self.klc.pre, self.klc.next) if fx == Chan_FX_TYPE.TOP: @@ -109,17 +108,6 @@ class ChanKLU: self.separate_div = separate_div else: self.separate_div = 0 - if bb2633_status == 0: - self.separate_div = 0 - def check_bb2633(self, threadhold=300): - #print(self.time, self.high, self.bb2633upper, self.low, self.bb2633lower) - if abs(self.high - self.bb2633upper) < threadhold: - #print(self.time, self.high, self.bb2633upper) - return 1 - if abs(self.low - self.bb2633lower) < threadhold: - #print(self.time, self.low, self.bb2633lower) - return -1 - return 0 def check_fx_dir(self, pre, next): fx = Chan_FX_TYPE.UNKNOWN if pre.klc_fx_type == Chan_KLC_FX.TOP1 or pre.klc_fx_type == Chan_KLC_FX.TOP2 or next.klc_fx_type == Chan_KLC_FX.TOP1 or next.klc_fx_type == Chan_KLC_FX.TOP2 or self.klc.klc_fx_type == Chan_KLC_FX.TOP1 or self.klc.klc_fx_type == Chan_KLC_FX.TOP2: diff --git a/web/templates/index.html b/web/templates/index.html index 1bcd6d1..9728699 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -2052,12 +2052,7 @@ // 保存当前的可见范围(周期切换时不保留,避免范围越界) if (!periodChanged && tvWidget && tvWidget.mainChart) { try { - const ts = tvWidget.mainChart.timeScale(); - window._pendingRestoreView = { - barSpacing: ts.options().barSpacing, - scrollPosition: ts.scrollPosition(), - rightOffset: ts.options().rightOffset - }; + window._pendingRestoreView = captureChartViewState(tvWidget.mainChart); } catch (e) { window._pendingRestoreView = null; } @@ -2215,6 +2210,64 @@ } }); } + function captureChartViewState(chart) { + if (!chart || !chart.timeScale) return null; + const ts = chart.timeScale(); + const tsOptions = ts.options ? ts.options() : {}; + return { + barSpacing: tsOptions.barSpacing, + rightOffset: tsOptions.rightOffset, + scrollPosition: ts.scrollPosition ? ts.scrollPosition() : null, + visibleRange: ts.getVisibleRange ? ts.getVisibleRange() : null, + logicalRange: ts.getVisibleLogicalRange ? ts.getVisibleLogicalRange() : null + }; + } + + function restoreChartViewState(charts, viewState) { + if (!viewState || !Array.isArray(charts) || charts.length === 0) return; + const validCharts = charts.filter(c => c && c.timeScale); + if (validCharts.length === 0) return; + + validCharts.forEach(c => { + try { + const optionsPatch = {}; + if (typeof viewState.barSpacing === 'number') optionsPatch.barSpacing = viewState.barSpacing; + if (typeof viewState.rightOffset === 'number') optionsPatch.rightOffset = viewState.rightOffset; + if (Object.keys(optionsPatch).length) { + c.timeScale().applyOptions(optionsPatch); + } + } catch (e) {} + }); + + let restored = false; + + // 优先按逻辑范围恢复(对新数据更稳健) + if (viewState.logicalRange && viewState.logicalRange.from !== undefined && viewState.logicalRange.to !== undefined) { + validCharts.forEach(c => { + try { + c.timeScale().setVisibleLogicalRange(viewState.logicalRange); + restored = true; + } catch (e) {} + }); + } + + // 逻辑范围失败时,回退到时间可见范围 + if (!restored && viewState.visibleRange && viewState.visibleRange.from !== undefined && viewState.visibleRange.to !== undefined) { + validCharts.forEach(c => { + try { + c.timeScale().setVisibleRange(viewState.visibleRange); + restored = true; + } catch (e) {} + }); + } + + // 最后回退到滚动位置 + if (!restored && typeof viewState.scrollPosition === 'number') { + validCharts.forEach(c => { + try { c.timeScale().scrollToPosition(viewState.scrollPosition, false); } catch (e) {} + }); + } + } // 初始化图表 function initTradingView(symbol, timeframe) { try { @@ -6634,15 +6687,19 @@ // 首先同步时间轴设置 syncTimeScaleSettings(); - // 显示最近 200 根K线而非全部挤压(避免K线过多时重叠) + // 仅在没有待恢复视图时,设置默认可见范围 const totalBars = candles ? candles.length : 0; const visibleBarsCount = 200; - if (totalBars > visibleBarsCount) { - const rangeFrom = totalBars - visibleBarsCount; - const rangeTo = totalBars + 12; - mainChart.timeScale().setVisibleLogicalRange({ from: rangeFrom, to: rangeTo }); - } else { - mainChart.timeScale().fitContent(); + const hasPendingRestoreView = !!window._pendingRestoreView; + if (!hasPendingRestoreView) { + // 显示最近 200 根K线而非全部挤压(避免K线过多时重叠) + if (totalBars > visibleBarsCount) { + const rangeFrom = totalBars - visibleBarsCount; + const rangeTo = totalBars + 12; + mainChart.timeScale().setVisibleLogicalRange({ from: rangeFrom, to: rangeTo }); + } else { + mainChart.timeScale().fitContent(); + } } // 立即同步其他图表到主图表的范围 @@ -6760,19 +6817,14 @@ if (showMacd && macdChart) allCharts.push(macdChart); if (showMacd && chanMacdChart) allCharts.push(chanMacdChart); - // 检查是否有待恢复的视图(barSpacing + scrollPosition) + // 检查是否有待恢复的视图(缩放 + 位置) const pending = window._pendingRestoreView; window._pendingRestoreView = null; - if (pending && pending.barSpacing) { - // 恢复刷新前的缩放和滚动位置 - console.log('📌 恢复图表视图 - barSpacing:', pending.barSpacing, 'scrollPosition:', pending.scrollPosition); - allCharts.forEach(c => { - try { - c.timeScale().applyOptions({ barSpacing: pending.barSpacing }); - c.timeScale().scrollToPosition(pending.scrollPosition, false); - } catch(e) {} - }); + if (pending) { + // 恢复刷新前的缩放和位置(优先可见范围/逻辑范围,最后回退到滚动位置) + console.log('📌 恢复图表视图:', JSON.stringify(pending)); + restoreChartViewState(allCharts, pending); } else { // 无保存视图,正常同步主图到子图 const visibleRange = mainChart.timeScale().getVisibleRange(); @@ -8381,12 +8433,7 @@ // tvWidget 会在 initTradingView 内被重建,所以必须存到 window 上 if (tvWidget && tvWidget.mainChart) { try { - const ts = tvWidget.mainChart.timeScale(); - window._pendingRestoreView = { - barSpacing: ts.options().barSpacing, - scrollPosition: ts.scrollPosition(), - rightOffset: ts.options().rightOffset - }; + window._pendingRestoreView = captureChartViewState(tvWidget.mainChart); console.log('📌 保存图表视图:', JSON.stringify(window._pendingRestoreView)); } catch (e) { console.warn('保存图表视图失败:', e); diff --git a/操作策略.txt b/操作策略.txt new file mode 100644 index 0000000..a25e5b1 --- /dev/null +++ b/操作策略.txt @@ -0,0 +1,36 @@ +本级别没有背驰的,次级别背驰下跌后形成第三类卖点进而形成本级别的V型反转,小转大 + +大级别 判断趋势位置(是否进入末期) +中级别 识别第一类买卖点结构 +小级别 精准入场与风控 + +短线操作策略 +大级别1H/2H/4H,判断趋势方向, +上涨趋势 +趋势启动第一次穿零轴后归零轴反弹,EMA52和黄白线无限接近零轴,MACD黄白线在零轴以上 +下跌趋势 +趋势启动第一次穿零轴后归零轴反弹,EMA52和黄白线无限接近零轴,MACD黄白线在零轴以下 +MACD归零轴的两种情况,两者是或的关系,满足任意一种都是归零轴,归零轴的四种走势: +1. K线下跌或者上涨后触碰当前时间级别的EMA52附近 +2. MACD的白线快线无限接近零轴, +3. MACD归零轴时,如果此时k线始终保持在EMA24附近,如果一直是EMA24之上之后出现反弹行情就会很大(最强反弹)这种反弹是2个时间级别同时归零轴形成的反弹,容易创新高新低,一般出现在强势行情。 +4. K线先触碰EMA52,而MACD黄白线都未归零轴 +归零轴完美形态 +MACD归零轴反弹/反抽的完美形态是K线触碰EMA52附近,MACD的白线无限接近零轴之后出现上涨或下跌 +中级别 +识别EMA52和MACD黄白线归零轴,MACD能量柱形成背驰 +小级别 +识别最后下跌段MACD背驰和开始上涨K线形态,识别笔,精确定位第一类和第二类卖卖点 +在小级别第一类或者第二类卖卖点开仓 + +平仓规则 +大级别如果K线在EMA13/EMA7均线以上,根据倍数关系到相应的小周期观察顶底背离进行平仓操作,一般来说在大级别的1/4小级别观察背离顶底 +比如1H归零轴反弹,K线一直运行在EMA7以上,那么到15M级别进行平仓操作,观察背离 + +趋势策略 +1. 使用30分钟,5分钟,1分钟时间周期进行交易 +2. 使用顶底分型和MACD背驰信号作为趋势衰竭信号,但是不作为开仓信号 +3. 等待价格确认跌破EMA26,K线跌破EMA26后2根确认,EMA13和EMA26交叉作为验证信号 +4. 通过小级别5分钟和1分钟找到精确入场点 +5. 止损定在最后一个顶底分型的高低点,止盈先设置1:2,如果出现反向分型和macd衰竭信号,先减半保本损 +6. \ No newline at end of file