From 29e98d6eedb246fc5c2d61467852959f0a3bd491 Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Tue, 19 Aug 2025 16:35:56 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=8A=A8=E8=83=BD=E7=90=86?= =?UTF-8?q?=E8=AE=BAchanmacd=EF=BC=8C=E9=A1=B6=E5=BA=95=E5=88=86=E5=9E=8B5?= =?UTF-8?q?=E9=9A=90=E5=BD=A2=E5=BD=A2=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ChanKLC.py | 14 ++++---- ChanLun.py | 3 ++ ChanMACD.py | 4 ++- ChanMACDHistSet.py | 17 +++++++++- ChanMACDSeg.py | 20 ++++++++--- ChanMACDUnitTF.py | 19 ++++++++--- K线动能理论.txt | 2 +- web/templates/index.html | 73 +++++++++++++++++++++++++++++++++++++--- 8 files changed, 128 insertions(+), 24 deletions(-) diff --git a/ChanKLC.py b/ChanKLC.py index 530c4bd..70c8553 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -1,7 +1,7 @@ import copy from typing import Dict, Optional -from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX, Chan_K_DIR +from ChanEnum import Chan_FX_TYPE, Chan_KLINE_DIR, Chan_BI_DIR, Chan_KLC_FX, Chan_K_DIR, Chan_MACD_STATE import ChanKLU import ChanCTime @@ -78,17 +78,15 @@ class ChanKLC(): self.klc_fx_type = Chan_KLC_FX.BOTTOM4 if self.fx == Chan_FX_TYPE.TOP: #print(self.end_time, self.fx, self.macd, self.macdhist, len(self.klus)) - if self.macd > 0: - self.bb_out = True - if self.macdhist < 0: + if self.macdhist < 0 and self.macd > 0 and self.macd > self.pre.macd and self.macd > self.next.macd: self.klc_fx_type = Chan_KLC_FX.TOP5 + self.bb_out = True else: if self.fx == Chan_FX_TYPE.BOTTOM: - if self.macd < 0: - self.bb_out = True - if self.macdhist > 0: + if self.macdhist > 0 and self.macd < 0 and self.macd < self.pre.macd and self.macd < self.next.macd: self.klc_fx_type = Chan_KLC_FX.BOTTOM5 - #self.bb_out = True + self.bb_out = True + def cal_macd_state(self, dir): macd_state = 0 return macd_state diff --git a/ChanLun.py b/ChanLun.py index c32b657..9f0437b 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -17,6 +17,7 @@ from technical.util import resample_to_interval from decimal import Decimal import xgboost as xgb import numpy as np +from ChanMACD import ChanMACD class ChanLun(): timeframes = ["5m", "15m", "30m", "60m", "4h"] @@ -1155,6 +1156,8 @@ class ChanLun(): klu_list = self.get_klu_list(dataframe) klc_list = [] last_klu = None + macd = ChanMACD(klu_list) + klu_list = macd.cal_macd() for klu in klu_list: if len(klc_list) > 0: last_klc = klc_list[-1] diff --git a/ChanMACD.py b/ChanMACD.py index da1cdee..22cadb5 100644 --- a/ChanMACD.py +++ b/ChanMACD.py @@ -16,6 +16,7 @@ class ChanMACD(): self.return_zero_list = [] # 归零轴列表 self.cross0_up_list = [] # 向上穿越零轴列表 self.cross0_down_list = [] # 向下穿越零轴列表 + # 计算段 / UnitTF / HistSet 及状态标记 self.cal_macd() def cal_macd(self): last_seg = None @@ -139,4 +140,5 @@ class ChanMACD(): last_seg.add_histset(histset) if last_unittf: last_unittf.add_histset(histset) - last_klu = klu \ No newline at end of file + last_klu = klu + return self.klu_list \ No newline at end of file diff --git a/ChanMACDHistSet.py b/ChanMACDHistSet.py index 67d6a98..a7c04be 100644 --- a/ChanMACDHistSet.py +++ b/ChanMACDHistSet.py @@ -1,4 +1,4 @@ - +from ChanEnum import Chan_MACDHISTSET_DIR class ChanMACDHistSet(): def __init__(self, start_time, start_klu, pre_histset, dir): @@ -10,11 +10,26 @@ class ChanMACDHistSet(): self.histset_dir = dir self.next = None self.pre = pre_histset + self.high_klu = start_klu + self.low_klu = start_klu def set_next(self, next_histset): self.next = next_histset def add_klu(self, klu): self.klu_list.append(klu) klu.set_histset(self) + if self.histset_dir == Chan_MACDHISTSET_DIR.ABOVE: + if klu.macdhist > self.high_klu.macdhist: + self.high_klu = klu + else: + if klu.macdhist < self.low_klu.macdhist: + self.low_klu = klu + else: + if klu.macdhist < self.high_klu.macdhist: + self.high_klu = klu + else: + if klu.macdhist > self.low_klu.macdhist: + self.low_klu = klu def set_end_klu(self, end_klu): + self.add_klu(end_klu) self.end_klu = end_klu self.end_time = end_klu.time \ No newline at end of file diff --git a/ChanMACDSeg.py b/ChanMACDSeg.py index 8ad5072..c35a6e4 100644 --- a/ChanMACDSeg.py +++ b/ChanMACDSeg.py @@ -14,7 +14,9 @@ class ChanMACDSeg(): self.seg_dir = seg_dir self.pre = pre_seg self.next = None - self.peak_klu = start_klu + self.high_klu = start_klu + self.low_klu = start_klu + self.ref_klu = None def set_next(self, next_seg): self.next = next_seg def add_klu(self, klu): @@ -22,11 +24,19 @@ class ChanMACDSeg(): self.klu_list.append(klu) klu.set_seg(self) if self.seg_dir == Chan_MACDSEG_DIR.ABOVE: - if klu.high > self.peak_klu.high: - self.peak_klu = klu + if klu.macdhist > self.high_klu.macdhist: + self.high_klu = klu + else: + if klu.macdhist < self.low_klu.macdhist: + self.low_klu = klu else: - if klu.low < self.peak_klu.low: - self.peak_klu = klu + if klu.macdhist < self.high_klu.macdhist: + self.high_klu = klu + else: + if klu.macdhist > self.low_klu.macdhist: + self.low_klu = klu + if self.high_klu.index != self.start_klu.index: + self.ref_klu = self.high_klu def add_unittf(self, unittf): self.unittf_list.append(unittf) unittf.set_next(self) diff --git a/ChanMACDUnitTF.py b/ChanMACDUnitTF.py index 0e18236..8e5da82 100644 --- a/ChanMACDUnitTF.py +++ b/ChanMACDUnitTF.py @@ -1,4 +1,4 @@ -from ChanEnum import Chan_MACD_STATE +from ChanEnum import Chan_MACD_STATE, Chan_MACDUNITTF_DIR class ChanMACDUnitTF(): @@ -15,8 +15,9 @@ class ChanMACDUnitTF(): self.unittf_dir = dir self.start_type = start_type self.end_type = None - self.peak_hist = start_klu.macdhist self.near0_count = 1 + self.high_klu = start_klu + self.low_klu = start_klu def set_next(self, next_unittf): self.next = next_unittf def add_histset(self, histset): @@ -26,8 +27,18 @@ class ChanMACDUnitTF(): if klu: self.klu_list.append(klu) klu.set_unittf(self) - if klu.macdhist > self.peak_hist: - self.peak_hist = klu.macdhist + if self.unittf_dir == Chan_MACDUNITTF_DIR.ABOVE: + if klu.macdhist > self.high_klu.macdhist: + self.high_klu = klu + else: + if klu.macdhist < self.low_klu.macdhist: + self.low_klu = klu + else: + if klu.macdhist < self.high_klu.macdhist: + self.high_klu = klu + else: + if klu.macdhist > self.low_klu.macdhist: + self.low_klu = klu if klu.macd_state == Chan_MACD_STATE.NEAR0 and len(self.histset_list) > 1: #print(self.start_time, klu.time, self.near0_count) self.near0_count += 1 diff --git a/K线动能理论.txt b/K线动能理论.txt index b69a256..f82608f 100644 --- a/K线动能理论.txt +++ b/K线动能理论.txt @@ -138,7 +138,7 @@ MACD黄白线在零轴之下第一个单位调整周期 + 分立跳空背离( 如果在单位调整周期内发生了跳空的形态,当跳空能量堆的高度高于前一个能量堆的高度,此时的跳空则为非背离跳空。非背离跳空可以理解为单位周期的单边行情,前一个能量堆失效,以新的最高的能量堆作为后续行情的1号参考点。不管是连续跳空还是分立跳空都遵守这个法则。 单位调整周期之间的背离 -单位调整周期之间的背离,是指两个或者两个以上的单位调整周期相比较而建立的关系,相比较的周期必须在同一个线段周期内,不可跨线段比较。当K线在某个时间级别的线段中,MACD经过了一个单位调整周期的运行,黄白线在此回零轴后,由于零轴的支撑反弹或压力反抽的作用,因此出现第二个单位调整周期,当第二个单位调整周期的黄白线这里特指白线离开零轴的距离小于第一个单位调整周期黄白线离开零轴的距离时,单位调整周期之间就产生了相悖的关系,即价格穿新高或新低,而白线能量去出现了减弱或增强,这种背离称为单位调整周期之间的背离,单位调整周期之间的背离也叫区间背离。 +单位调整周期之间的背离,是指两个或者两个以上的单位调整周期相比较而建立的关系,相比较的周期必须在同一个线段周期内,不可跨线段比较。当K线在某个时间级别的线段中,MACD经过了一个单位调整周期的运行,黄白线在此回零轴后,由于零轴的支撑反弹或压力反抽的作用,因此出现第二个单位调整周期,当第二个单位调整周期的黄白线这里特指白线离开零轴的距离小于第一个单位调整周期黄白线离开零轴的距离时,单位调整周期之间就产生了相悖的关系,即价格穿新高或新低,而白线能量区出现了减弱或增强,这种背离称为单位调整周期之间的背离,单位调整周期之间的背离也叫区间背离。 上涨线段单位调整周期之间的顶背离为卖点(前提:长级别MACD在高位) 下跌线段单位调整周期之间的底背离为买点(前提:长级别MACD在高位) diff --git a/web/templates/index.html b/web/templates/index.html index cc67112..d95d97c 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -919,7 +919,7 @@
- +
@@ -2160,6 +2160,9 @@ if (tvWidget.macdChart) { tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange); } + if (tvWidget.chanMacdChart) { + tvWidget.chanMacdChart.timeScale().setVisibleLogicalRange(logicalRange); + } } else if (visibleRange) { // 如果没有逻辑范围,使用时间戳范围 tvWidget.mainChart.timeScale().setVisibleRange(visibleRange); @@ -2477,6 +2480,16 @@ chanMacdChartContainer.style.right = '0'; chanMacdChartContainer.style.borderTop = '1px solid #e0e0e0'; chanMacdChartContainer.style.zIndex = '10'; + // 水印:便于区分是新的 ChanMACD 子图 + const chanMacdWatermark = document.createElement('div'); + chanMacdWatermark.textContent = 'ChanMACD'; + chanMacdWatermark.style.position = 'absolute'; + chanMacdWatermark.style.top = '4px'; + chanMacdWatermark.style.left = '8px'; + chanMacdWatermark.style.fontSize = '11px'; + chanMacdWatermark.style.color = '#888'; + chanMacdWatermark.style.pointerEvents = 'none'; + chanMacdChartContainer.appendChild(chanMacdWatermark); // 成交量位于 ChanMACD 之下 volumeChartContainer.style.top = '70%'; @@ -2887,11 +2900,12 @@ isArray: Array.isArray(currentData.kline_data) }); if (showMacd && chanMacdChart && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) { + console.log('✅ 开始创建 ChanMACD 系列'); // 创建ChanMACD线系列 const chanMacdLineSeries = chanMacdChart.addLineSeries({ color: '#2962FF', lineWidth: 1, - title: 'MACD', + title: 'ChanMACD', lastValueVisible: false, priceLineVisible: false, }); @@ -2900,7 +2914,7 @@ const chanMacdSignalSeries = chanMacdChart.addLineSeries({ color: '#FF6B6B', lineWidth: 1, - title: 'Signal', + title: 'ChanSignal', lastValueVisible: false, priceLineVisible: false, }); @@ -2908,7 +2922,7 @@ // 创建ChanMACD柱状图系列 const chanMacdHistSeries = chanMacdChart.addHistogramSeries({ color: '#26a69a', - title: 'Histogram', + title: 'ChanHistogram', priceFormat: { type: 'price', precision: 4, @@ -5477,6 +5491,9 @@ if (showMacd && macdChart) { macdChart.timeScale().setVisibleRange(visibleRange); } + if (showMacd && chanMacdChart) { + chanMacdChart.timeScale().setVisibleRange(visibleRange); + } console.log('🔧 最终时间轴对齐完成'); } @@ -5676,6 +5693,53 @@ tvWidget.series.histogramSeries.setData(histogramData); } + // 更新 ChanMACD 数据与自定义标注 + if (tvWidget.series.chanMacdLineSeries && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) { + const klineDataSource = useElementPeriod ? currentData.element_kline_data : currentData.kline_data; + const macdDataSource = useElementPeriod ? (currentData.element_macd || currentData.macd) : currentData.macd; + if (macdDataSource && macdDataSource.macd && macdDataSource.signal && macdDataSource.histogram) { + const chanMacdData = []; + const chanSignalData = []; + const chanHistData = []; + for (let i = 0; i < klineDataSource.length; i++) { + const kline = klineDataSource[i]; + if (kline && kline.date && i < macdDataSource.macd.length && macdDataSource.macd[i] !== null && macdDataSource.macd[i] !== undefined) { + const timestamp = Math.floor(new Date(kline.date).getTime() / 1000); + chanMacdData.push({ time: timestamp, value: macdDataSource.macd[i] }); + chanSignalData.push({ time: timestamp, value: macdDataSource.signal[i] }); + chanHistData.push({ time: timestamp, value: macdDataSource.histogram[i], color: macdDataSource.histogram[i] >= 0 ? 'rgba(220, 53, 69, 0.5)' : 'rgba(40, 167, 69, 0.5)' }); + } + } + if (chanMacdData.length > 0) { + tvWidget.series.chanMacdLineSeries.setData(chanMacdData); + tvWidget.series.chanMacdSignalSeries.setData(chanSignalData); + tvWidget.series.chanMacdHistSeries.setData(chanHistData); + } + } + // 重新应用自定义标注(段/UnitTF/HistSet/状态点) + try { + if (typeof clearChanMacdMarkers === 'function') clearChanMacdMarkers(); + if (currentData.chan_macd) { + addAllChanMacdMarkers( + currentData.chan_macd.seg_list || [], + currentData.chan_macd.unittf_list || [], + currentData.chan_macd.histset_list || [], + { + high_position_list: currentData.chan_macd.high_position_list || [], + high_empty_list: currentData.chan_macd.high_empty_list || [], + low_position_list: currentData.chan_macd.low_position_list || [], + low_empty_list: currentData.chan_macd.low_empty_list || [], + return_zero_list: currentData.chan_macd.return_zero_list || [], + cross0_up_list: currentData.chan_macd.cross0_up_list || [], + cross0_down_list: currentData.chan_macd.cross0_down_list || [] + } + ); + } + } catch (e) { + console.warn('更新ChanMACD标注失败:', e); + } + } + // 重新显示笔、线段和中枢等图形 redrawFractalElements(); @@ -7191,6 +7255,7 @@ if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(logicalRange); if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleLogicalRange(logicalRange); if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange); + if (tvWidget.chanMacdChart) tvWidget.chanMacdChart.timeScale().setVisibleLogicalRange(logicalRange); } else if (visibleRange) { tvWidget.mainChart.timeScale().setVisibleRange(visibleRange); if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(visibleRange);