添加动能理论chanmacd,顶底分型5隐形形态

This commit is contained in:
jackyu66git
2025-08-19 16:35:56 +08:00
parent b15ba06a00
commit 29e98d6eed
8 changed files with 128 additions and 24 deletions
+6 -8
View File
@@ -1,7 +1,7 @@
import copy import copy
from typing import Dict, Optional 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 ChanKLU
import ChanCTime import ChanCTime
@@ -78,17 +78,15 @@ class ChanKLC():
self.klc_fx_type = Chan_KLC_FX.BOTTOM4 self.klc_fx_type = Chan_KLC_FX.BOTTOM4
if self.fx == Chan_FX_TYPE.TOP: if self.fx == Chan_FX_TYPE.TOP:
#print(self.end_time, self.fx, self.macd, self.macdhist, len(self.klus)) #print(self.end_time, self.fx, self.macd, self.macdhist, len(self.klus))
if self.macd > 0: if self.macdhist < 0 and self.macd > 0 and self.macd > self.pre.macd and self.macd > self.next.macd:
self.bb_out = True
if self.macdhist < 0:
self.klc_fx_type = Chan_KLC_FX.TOP5 self.klc_fx_type = Chan_KLC_FX.TOP5
self.bb_out = True
else: else:
if self.fx == Chan_FX_TYPE.BOTTOM: if self.fx == Chan_FX_TYPE.BOTTOM:
if self.macd < 0: if self.macdhist > 0 and self.macd < 0 and self.macd < self.pre.macd and self.macd < self.next.macd:
self.bb_out = True
if self.macdhist > 0:
self.klc_fx_type = Chan_KLC_FX.BOTTOM5 self.klc_fx_type = Chan_KLC_FX.BOTTOM5
#self.bb_out = True self.bb_out = True
def cal_macd_state(self, dir): def cal_macd_state(self, dir):
macd_state = 0 macd_state = 0
return macd_state return macd_state
+3
View File
@@ -17,6 +17,7 @@ from technical.util import resample_to_interval
from decimal import Decimal from decimal import Decimal
import xgboost as xgb import xgboost as xgb
import numpy as np import numpy as np
from ChanMACD import ChanMACD
class ChanLun(): class ChanLun():
timeframes = ["5m", "15m", "30m", "60m", "4h"] timeframes = ["5m", "15m", "30m", "60m", "4h"]
@@ -1155,6 +1156,8 @@ class ChanLun():
klu_list = self.get_klu_list(dataframe) klu_list = self.get_klu_list(dataframe)
klc_list = [] klc_list = []
last_klu = None last_klu = None
macd = ChanMACD(klu_list)
klu_list = macd.cal_macd()
for klu in klu_list: for klu in klu_list:
if len(klc_list) > 0: if len(klc_list) > 0:
last_klc = klc_list[-1] last_klc = klc_list[-1]
+3 -1
View File
@@ -16,6 +16,7 @@ class ChanMACD():
self.return_zero_list = [] # 归零轴列表 self.return_zero_list = [] # 归零轴列表
self.cross0_up_list = [] # 向上穿越零轴列表 self.cross0_up_list = [] # 向上穿越零轴列表
self.cross0_down_list = [] # 向下穿越零轴列表 self.cross0_down_list = [] # 向下穿越零轴列表
# 计算段 / UnitTF / HistSet 及状态标记
self.cal_macd() self.cal_macd()
def cal_macd(self): def cal_macd(self):
last_seg = None last_seg = None
@@ -139,4 +140,5 @@ class ChanMACD():
last_seg.add_histset(histset) last_seg.add_histset(histset)
if last_unittf: if last_unittf:
last_unittf.add_histset(histset) last_unittf.add_histset(histset)
last_klu = klu last_klu = klu
return self.klu_list
+16 -1
View File
@@ -1,4 +1,4 @@
from ChanEnum import Chan_MACDHISTSET_DIR
class ChanMACDHistSet(): class ChanMACDHistSet():
def __init__(self, start_time, start_klu, pre_histset, dir): def __init__(self, start_time, start_klu, pre_histset, dir):
@@ -10,11 +10,26 @@ class ChanMACDHistSet():
self.histset_dir = dir self.histset_dir = dir
self.next = None self.next = None
self.pre = pre_histset self.pre = pre_histset
self.high_klu = start_klu
self.low_klu = start_klu
def set_next(self, next_histset): def set_next(self, next_histset):
self.next = next_histset self.next = next_histset
def add_klu(self, klu): def add_klu(self, klu):
self.klu_list.append(klu) self.klu_list.append(klu)
klu.set_histset(self) 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): def set_end_klu(self, end_klu):
self.add_klu(end_klu)
self.end_klu = end_klu self.end_klu = end_klu
self.end_time = end_klu.time self.end_time = end_klu.time
+15 -5
View File
@@ -14,7 +14,9 @@ class ChanMACDSeg():
self.seg_dir = seg_dir self.seg_dir = seg_dir
self.pre = pre_seg self.pre = pre_seg
self.next = None 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): def set_next(self, next_seg):
self.next = next_seg self.next = next_seg
def add_klu(self, klu): def add_klu(self, klu):
@@ -22,11 +24,19 @@ class ChanMACDSeg():
self.klu_list.append(klu) self.klu_list.append(klu)
klu.set_seg(self) klu.set_seg(self)
if self.seg_dir == Chan_MACDSEG_DIR.ABOVE: if self.seg_dir == Chan_MACDSEG_DIR.ABOVE:
if klu.high > self.peak_klu.high: if klu.macdhist > self.high_klu.macdhist:
self.peak_klu = klu self.high_klu = klu
else:
if klu.macdhist < self.low_klu.macdhist:
self.low_klu = klu
else: else:
if klu.low < self.peak_klu.low: if klu.macdhist < self.high_klu.macdhist:
self.peak_klu = klu 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): def add_unittf(self, unittf):
self.unittf_list.append(unittf) self.unittf_list.append(unittf)
unittf.set_next(self) unittf.set_next(self)
+15 -4
View File
@@ -1,4 +1,4 @@
from ChanEnum import Chan_MACD_STATE from ChanEnum import Chan_MACD_STATE, Chan_MACDUNITTF_DIR
class ChanMACDUnitTF(): class ChanMACDUnitTF():
@@ -15,8 +15,9 @@ class ChanMACDUnitTF():
self.unittf_dir = dir self.unittf_dir = dir
self.start_type = start_type self.start_type = start_type
self.end_type = None self.end_type = None
self.peak_hist = start_klu.macdhist
self.near0_count = 1 self.near0_count = 1
self.high_klu = start_klu
self.low_klu = start_klu
def set_next(self, next_unittf): def set_next(self, next_unittf):
self.next = next_unittf self.next = next_unittf
def add_histset(self, histset): def add_histset(self, histset):
@@ -26,8 +27,18 @@ class ChanMACDUnitTF():
if klu: if klu:
self.klu_list.append(klu) self.klu_list.append(klu)
klu.set_unittf(self) klu.set_unittf(self)
if klu.macdhist > self.peak_hist: if self.unittf_dir == Chan_MACDUNITTF_DIR.ABOVE:
self.peak_hist = klu.macdhist 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: if klu.macd_state == Chan_MACD_STATE.NEAR0 and len(self.histset_list) > 1:
#print(self.start_time, klu.time, self.near0_count) #print(self.start_time, klu.time, self.near0_count)
self.near0_count += 1 self.near0_count += 1
+1 -1
View File
@@ -138,7 +138,7 @@ MACD黄白线在零轴之下第一个单位调整周期 + 分立跳空背离(
如果在单位调整周期内发生了跳空的形态,当跳空能量堆的高度高于前一个能量堆的高度,此时的跳空则为非背离跳空。非背离跳空可以理解为单位周期的单边行情,前一个能量堆失效,以新的最高的能量堆作为后续行情的1号参考点。不管是连续跳空还是分立跳空都遵守这个法则。 如果在单位调整周期内发生了跳空的形态,当跳空能量堆的高度高于前一个能量堆的高度,此时的跳空则为非背离跳空。非背离跳空可以理解为单位周期的单边行情,前一个能量堆失效,以新的最高的能量堆作为后续行情的1号参考点。不管是连续跳空还是分立跳空都遵守这个法则。
单位调整周期之间的背离 单位调整周期之间的背离
单位调整周期之间的背离,是指两个或者两个以上的单位调整周期相比较而建立的关系,相比较的周期必须在同一个线段周期内,不可跨线段比较。当K线在某个时间级别的线段中,MACD经过了一个单位调整周期的运行,黄白线在此回零轴后,由于零轴的支撑反弹或压力反抽的作用,因此出现第二个单位调整周期,当第二个单位调整周期的黄白线这里特指白线离开零轴的距离小于第一个单位调整周期黄白线离开零轴的距离时,单位调整周期之间就产生了相悖的关系,即价格穿新高或新低,而白线能量出现了减弱或增强,这种背离称为单位调整周期之间的背离,单位调整周期之间的背离也叫区间背离。 单位调整周期之间的背离,是指两个或者两个以上的单位调整周期相比较而建立的关系,相比较的周期必须在同一个线段周期内,不可跨线段比较。当K线在某个时间级别的线段中,MACD经过了一个单位调整周期的运行,黄白线在此回零轴后,由于零轴的支撑反弹或压力反抽的作用,因此出现第二个单位调整周期,当第二个单位调整周期的黄白线这里特指白线离开零轴的距离小于第一个单位调整周期黄白线离开零轴的距离时,单位调整周期之间就产生了相悖的关系,即价格穿新高或新低,而白线能量出现了减弱或增强,这种背离称为单位调整周期之间的背离,单位调整周期之间的背离也叫区间背离。
上涨线段单位调整周期之间的顶背离为卖点(前提:长级别MACD在高位) 上涨线段单位调整周期之间的顶背离为卖点(前提:长级别MACD在高位)
下跌线段单位调整周期之间的底背离为买点(前提:长级别MACD在高位) 下跌线段单位调整周期之间的底背离为买点(前提:长级别MACD在高位)
+69 -4
View File
@@ -919,7 +919,7 @@
</div> </div>
<div class="form-check form-check-inline"> <div class="form-check form-check-inline">
<input class="form-check-input" type="checkbox" id="showMacd" checked> <input class="form-check-input" type="checkbox" id="showMacd" checked>
<label class="form-check-label" for="showMacd">MACD</label> <label class="form-check-label" for="showMacd">ChanMACD</label>
</div> </div>
</div> </div>
<div class="d-flex align-items-center mt-2"> <div class="d-flex align-items-center mt-2">
@@ -2160,6 +2160,9 @@
if (tvWidget.macdChart) { if (tvWidget.macdChart) {
tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange); tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange);
} }
if (tvWidget.chanMacdChart) {
tvWidget.chanMacdChart.timeScale().setVisibleLogicalRange(logicalRange);
}
} else if (visibleRange) { } else if (visibleRange) {
// 如果没有逻辑范围,使用时间戳范围 // 如果没有逻辑范围,使用时间戳范围
tvWidget.mainChart.timeScale().setVisibleRange(visibleRange); tvWidget.mainChart.timeScale().setVisibleRange(visibleRange);
@@ -2477,6 +2480,16 @@
chanMacdChartContainer.style.right = '0'; chanMacdChartContainer.style.right = '0';
chanMacdChartContainer.style.borderTop = '1px solid #e0e0e0'; chanMacdChartContainer.style.borderTop = '1px solid #e0e0e0';
chanMacdChartContainer.style.zIndex = '10'; 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 之下 // 成交量位于 ChanMACD 之下
volumeChartContainer.style.top = '70%'; volumeChartContainer.style.top = '70%';
@@ -2887,11 +2900,12 @@
isArray: Array.isArray(currentData.kline_data) isArray: Array.isArray(currentData.kline_data)
}); });
if (showMacd && chanMacdChart && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) { if (showMacd && chanMacdChart && currentData.macd && currentData.kline_data && Array.isArray(currentData.kline_data)) {
console.log('✅ 开始创建 ChanMACD 系列');
// 创建ChanMACD线系列 // 创建ChanMACD线系列
const chanMacdLineSeries = chanMacdChart.addLineSeries({ const chanMacdLineSeries = chanMacdChart.addLineSeries({
color: '#2962FF', color: '#2962FF',
lineWidth: 1, lineWidth: 1,
title: 'MACD', title: 'ChanMACD',
lastValueVisible: false, lastValueVisible: false,
priceLineVisible: false, priceLineVisible: false,
}); });
@@ -2900,7 +2914,7 @@
const chanMacdSignalSeries = chanMacdChart.addLineSeries({ const chanMacdSignalSeries = chanMacdChart.addLineSeries({
color: '#FF6B6B', color: '#FF6B6B',
lineWidth: 1, lineWidth: 1,
title: 'Signal', title: 'ChanSignal',
lastValueVisible: false, lastValueVisible: false,
priceLineVisible: false, priceLineVisible: false,
}); });
@@ -2908,7 +2922,7 @@
// 创建ChanMACD柱状图系列 // 创建ChanMACD柱状图系列
const chanMacdHistSeries = chanMacdChart.addHistogramSeries({ const chanMacdHistSeries = chanMacdChart.addHistogramSeries({
color: '#26a69a', color: '#26a69a',
title: 'Histogram', title: 'ChanHistogram',
priceFormat: { priceFormat: {
type: 'price', type: 'price',
precision: 4, precision: 4,
@@ -5477,6 +5491,9 @@
if (showMacd && macdChart) { if (showMacd && macdChart) {
macdChart.timeScale().setVisibleRange(visibleRange); macdChart.timeScale().setVisibleRange(visibleRange);
} }
if (showMacd && chanMacdChart) {
chanMacdChart.timeScale().setVisibleRange(visibleRange);
}
console.log('🔧 最终时间轴对齐完成'); console.log('🔧 最终时间轴对齐完成');
} }
@@ -5676,6 +5693,53 @@
tvWidget.series.histogramSeries.setData(histogramData); 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(); redrawFractalElements();
@@ -7191,6 +7255,7 @@
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(logicalRange); if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleLogicalRange(logicalRange);
if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleLogicalRange(logicalRange); if (tvWidget.atrChart) tvWidget.atrChart.timeScale().setVisibleLogicalRange(logicalRange);
if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange); if (tvWidget.macdChart) tvWidget.macdChart.timeScale().setVisibleLogicalRange(logicalRange);
if (tvWidget.chanMacdChart) tvWidget.chanMacdChart.timeScale().setVisibleLogicalRange(logicalRange);
} else if (visibleRange) { } else if (visibleRange) {
tvWidget.mainChart.timeScale().setVisibleRange(visibleRange); tvWidget.mainChart.timeScale().setVisibleRange(visibleRange);
if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(visibleRange); if (tvWidget.volumeChart) tvWidget.volumeChart.timeScale().setVisibleRange(visibleRange);