diff --git a/ChanKLC.py b/ChanKLC.py index 26253a6..898c8a8 100644 --- a/ChanKLC.py +++ b/ChanKLC.py @@ -57,6 +57,7 @@ class ChanKLC(): 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 + self.cal_invisible() self.cal_bb_out() def add_klu(self, klu): self.klus.append(klu) @@ -72,8 +73,32 @@ class ChanKLC(): if klu.macd_state != Chan_MACD_STATE.UNKNOWN: self.state = klu.macd_state self.cal_indicators() + def cal_invisible(self): + if self.klc_fx_type == Chan_KLC_FX.TOP1 or self.klc_fx_type == Chan_KLC_FX.TOP2: + #print(self.end_time, self.fx, self.macd, self.macdhist, len(self.klus)) + if self.macdhist < 0 and self.macd > 0: + self.klc_fx_type = Chan_KLC_FX.TOP5 + return + if self.state == Chan_MACD_STATE.HIGH_EMPTY and self.macd > 0: + #print(self.end_time, self.state, self.macd, self.klc_fx_type) + self.klc_fx_type = Chan_KLC_FX.TOP6 + if self.separate_div or self.continue_div: + self.klc_fx_type = Chan_KLC_FX.TOP7 + if self.signal > 0 and self.macd > self.signal: + self.klc_fx_type = Chan_KLC_FX.TOP8 + else: + if self.klc_fx_type == Chan_KLC_FX.BOTTOM1 or self.klc_fx_type == Chan_KLC_FX.BOTTOM2: + if self.macdhist > 0 and self.macd < 0: + self.klc_fx_type = Chan_KLC_FX.BOTTOM5 + return + if self.state == Chan_MACD_STATE.HIGH_EMPTY and self.macd < 0: + self.klc_fx_type = Chan_KLC_FX.BOTTOM6 + #print(self.end_time, self.state, self.macd, self.klc_fx_type) + if self.separate_div or self.continue_div: + self.klc_fx_type = Chan_KLC_FX.BOTTOM7 + if self.signal < 0 and self.macd < self.signal: + self.klc_fx_type = Chan_KLC_FX.BOTTOM8 def cal_bb_out(self): - return for klu in self.klus: if self.high >= klu.bbup302 and klu.bbup302 > 0 and (self.klc_fx_type == Chan_KLC_FX.TOP1 or self.klc_fx_type == Chan_KLC_FX.TOP2): #print(self.end_time, self.high, klu.bbup302, self.klc_fx_type) @@ -84,30 +109,6 @@ class ChanKLC(): if self.low <= klu.bblow30 and klu.bblow30 > 0 and self.next and (self.macd - self.next.macd) < 0: self.klc_fx_type = Chan_KLC_FX.BOTTOM4 #self.bb_out = True - if self.klc_fx_type == Chan_KLC_FX.TOP1 or self.klc_fx_type == Chan_KLC_FX.TOP2: - #print(self.end_time, self.fx, self.macd, self.macdhist, len(self.klus)) - if self.macdhist < 0 and self.macd > 0: - self.klc_fx_type = Chan_KLC_FX.TOP5 - self.bb_out = True - if self.state == Chan_MACD_STATE.HIGH_EMPTY and self.macd > 0: - #print(self.end_time, self.state, self.macd, self.klc_fx_type) - self.klc_fx_type = Chan_KLC_FX.TOP6 - self.bb_out = True - if self.continue_div: - self.klc_fx_type = Chan_KLC_FX.TOP7 - self.bb_out = True - else: - if self.klc_fx_type == Chan_KLC_FX.BOTTOM1 or self.klc_fx_type == Chan_KLC_FX.BOTTOM2: - if self.macdhist > 0 and self.macd < 0: - self.klc_fx_type = Chan_KLC_FX.BOTTOM5 - self.bb_out = True - if self.state == Chan_MACD_STATE.HIGH_EMPTY and self.macd < 0: - self.klc_fx_type = Chan_KLC_FX.BOTTOM6 - self.bb_out = True - #print(self.end_time, self.state, self.macd, self.klc_fx_type) - if self.continue_div: - self.klc_fx_type = Chan_KLC_FX.BOTTOM7 - self.bb_out = True def cal_macd_state(self, dir): macd_state = 0 return macd_state diff --git a/ChanKLU.py b/ChanKLU.py index 75044cf..aced1ef 100644 --- a/ChanKLU.py +++ b/ChanKLU.py @@ -14,6 +14,7 @@ class ChanKLU: self.macd = 0 self.signal = 0 self.macdhist = 0 + self.klc = None self.ma5 = 0 self.ma10 = 0 self.ma30 = 0 @@ -125,7 +126,8 @@ class ChanKLU: #print(self.index, self.time, self.fx_type, self.fx_confirmed, self.fx_strength) def set_pre(self, pre): self.pre = pre - + def set_klc(self, klc): + self.klc = klc def set_histset(self, histset): """设置HistSet关联""" self.histset = histset diff --git a/ChanLun.py b/ChanLun.py index 30d6a57..073b814 100644 --- a/ChanLun.py +++ b/ChanLun.py @@ -23,6 +23,7 @@ class ChanLun(): time1 = 1 time3 = 3 time5 = 5 + time10 = 10 time15 = 15 time30 = 30 time60 = 60 @@ -32,7 +33,7 @@ class ChanLun(): time8h = 480 time12h = 720 time1d = 1440 - timeframes = [time1, time3, time5, time15, time30, time60] + timeframes = [time1, time3, time5, time10, time15, time30, time60] tf_df_dict = {} def init_data(self, dataframe, ticker_indicator): for timeframe in self.timeframes: @@ -52,16 +53,14 @@ class ChanLun(): return klu_list, klc_list, bi_list def check_fx(self, klc): if klc.pre and klc.next: - if klc.high > klc.pre.high and klc.high > klc.next.high: - if klc.signal > 0 and klc.macd > klc.signal: - 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: - if klc.signal < 0 and klc.macd < klc.signal: - 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.high > klc.pre.high and klc.high > klc.next.high and klc.ema24 > klc.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 and klc.ema24 < klc.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 56d7a40..d1d59d5 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): @@ -58,6 +58,7 @@ class ChanMACDHistSet(): if klu.pre.macd * klu.pre.macdhist > 0: self.peak_div_list.append(klu.pre) self.div_count += 1 + klu.pre.continue_div = True else: self.peak_klu = klu.pre else: diff --git a/web/templates/index.html b/web/templates/index.html index 3e6636a..f884264 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -3066,6 +3066,25 @@ const mainMarkers = []; const elementMarkers = []; + // 基于时间构建 MACD 值映射,便于按时间快速获取对应的 MACD 值 + const buildMacdTimeMap = (macdObj, klineArr) => { + const map = new Map(); + if (!macdObj || !klineArr || !Array.isArray(klineArr)) return map; + for (let i = 0; i < klineArr.length; i++) { + const k = klineArr[i]; + if (!k || !k.date) continue; + const t = Math.floor(new Date(k.date).getTime() / 1000); + const val = (macdObj.macd && macdObj.macd[i] !== undefined && macdObj.macd[i] !== null) ? macdObj.macd[i] : null; + map.set(t, val); + } + return map; + }; + const mainMacdMap = buildMacdTimeMap(currentData.macd, currentData.kline_data); + const elementMacdMap = buildMacdTimeMap( + (currentData.element_macd || currentData.macd), + (currentData.element_kline_data || currentData.kline_data) + ); + // 主周期 U 标记(蓝/橙,与原样式一致) if ((typeof window.showUOnMain === 'undefined' ? true : window.showUOnMain) && Array.isArray(mainCm.klu_list)) { mainCm.klu_list.forEach((item) => { @@ -3073,10 +3092,14 @@ const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; if (Number(item.separate_div) > 0) { - mainMarkers.push({ time: ts, position: 'aboveBar', color: '#03a9f4', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); + const macdVal = mainMacdMap.get(ts); + const posSd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'aboveBar'; + mainMarkers.push({ time: ts, position: posSd, color: '#03a9f4', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { - mainMarkers.push({ time: ts, position: 'belowBar', color: '#ff9800', shape: 'arrowDown', text: 'CD', size: 0.6 }); + const macdVal = mainMacdMap.get(ts); + const posCd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'belowBar'; + mainMarkers.push({ time: ts, position: posCd, color: '#ff9800', shape: 'arrowDown', text: 'CD', size: 0.6 }); } if (item.near0_return && Number(item.near0_return) > 0) { mainMarkers.push({ time: ts, position: 'belowBar', color: '#8bc34a', shape: 'circle', text: `N${Number(item.near0_return)}`, size: 0.6 }); @@ -3091,10 +3114,14 @@ const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; if (Number(item.separate_div) > 0) { - elementMarkers.push({ time: ts, position: 'aboveBar', color: '#9c27b0', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); + const macdVal = elementMacdMap.get(ts); + const posSd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'aboveBar'; + elementMarkers.push({ time: ts, position: posSd, color: '#9c27b0', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { - elementMarkers.push({ time: ts, position: 'belowBar', color: '#4caf50', shape: 'arrowDown', text: 'CD', size: 0.6 }); + const macdVal = elementMacdMap.get(ts); + const posCd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'belowBar'; + elementMarkers.push({ time: ts, position: posCd, color: '#4caf50', shape: 'arrowDown', text: 'CD', size: 0.6 }); } if (item.near0_return && Number(item.near0_return) > 0) { elementMarkers.push({ time: ts, position: 'belowBar', color: '#009688', shape: 'circle', text: `N${Number(item.near0_return)}`, size: 0.6 }); @@ -3126,16 +3153,39 @@ const elementCmAll = currentData.element_chan_macd || {}; const mainMarkersAll = []; const elementMarkersAll = []; + + // 构建 MACD 时间映射,用于依据 MACD 正负决定 SD/CD 的显示上下位置 + const buildMacdTimeMapAll = (macdObj, klineArr) => { + const map = new Map(); + if (!macdObj || !klineArr || !Array.isArray(klineArr)) return map; + for (let i = 0; i < klineArr.length; i++) { + const k = klineArr[i]; + if (!k || !k.date) continue; + const t = Math.floor(new Date(k.date).getTime() / 1000); + const val = (macdObj.macd && macdObj.macd[i] !== undefined && macdObj.macd[i] !== null) ? macdObj.macd[i] : null; + map.set(t, val); + } + return map; + }; + const mainMacdMapAll = buildMacdTimeMapAll(currentData.macd, currentData.kline_data); + const elementMacdMapAll = buildMacdTimeMapAll( + (currentData.element_macd || currentData.macd), + (currentData.element_kline_data || currentData.kline_data) + ); if ((typeof window.showUOnMain === 'undefined' ? true : window.showUOnMain) && Array.isArray(mainCmAll.klu_list)) { mainCmAll.klu_list.forEach((item) => { if (!item || !item.time) return; const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; if (Number(item.separate_div) > 0) { - mainMarkersAll.push({ time: ts, position: 'aboveBar', color: '#03a9f4', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); + const macdVal = mainMacdMapAll.get(ts); + const posSd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'aboveBar'; + mainMarkersAll.push({ time: ts, position: posSd, color: '#03a9f4', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { - mainMarkersAll.push({ time: ts, position: 'belowBar', color: '#ff9800', shape: 'arrowDown', text: 'CD', size: 0.6 }); + const macdVal = mainMacdMapAll.get(ts); + const posCd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'belowBar'; + mainMarkersAll.push({ time: ts, position: posCd, color: '#ff9800', shape: 'arrowDown', text: 'CD', size: 0.6 }); } if (item.near0_return && Number(item.near0_return) > 0) { mainMarkersAll.push({ time: ts, position: 'belowBar', color: '#8bc34a', shape: 'circle', text: `N${Number(item.near0_return)}`, size: 0.6 }); @@ -3148,10 +3198,14 @@ const ts = Math.floor(new Date(item.time).getTime() / 1000); if (isNaN(ts)) return; if (Number(item.separate_div) > 0) { - elementMarkersAll.push({ time: ts, position: 'aboveBar', color: '#9c27b0', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); + const macdVal = elementMacdMapAll.get(ts); + const posSd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'aboveBar'; + elementMarkersAll.push({ time: ts, position: posSd, color: '#9c27b0', shape: 'arrowUp', text: `SD${Number(item.separate_div)}`, size: 0.6 }); } if (item.continue_div === true) { - elementMarkersAll.push({ time: ts, position: 'belowBar', color: '#4caf50', shape: 'arrowDown', text: 'CD', size: 0.6 }); + const macdVal = elementMacdMapAll.get(ts); + const posCd = (macdVal > 0) ? 'aboveBar' : (macdVal < 0) ? 'belowBar' : 'belowBar'; + elementMarkersAll.push({ time: ts, position: posCd, color: '#4caf50', shape: 'arrowDown', text: 'CD', size: 0.6 }); } if (item.near0_return && Number(item.near0_return) > 0) { elementMarkersAll.push({ time: ts, position: 'belowBar', color: '#009688', shape: 'circle', text: `N${Number(item.near0_return)}`, size: 0.6 });