refactor: 缠论引擎包化与 Web 分层(ECR-001)
将根目录引擎迁入 chanlun/ 并保留兼容 shim;拆分 TF_DF 与 web 服务; 前端模块化;strategies 改用 chanlun 导入;补充 ESS 文档与 golden 回归。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,443 @@
|
||||
/* main.js */
|
||||
$(document).ready(function() {
|
||||
// 初始化技术指标下拉菜单
|
||||
initIndicatorDropdown();
|
||||
|
||||
// 设置默认的筛选时间(最近7天)
|
||||
const now = new Date();
|
||||
const weekAgo = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
|
||||
|
||||
// 添加页面滚动事件监听器,清除十字线延长线
|
||||
$(window).on('scroll', function() {
|
||||
try {
|
||||
// 清除所有十字线延长线,防止它们跟着页面滚动
|
||||
const existingVolumeLines = document.querySelectorAll('.volume-crosshair-line');
|
||||
existingVolumeLines.forEach(line => line.remove());
|
||||
const existingAtrLines = document.querySelectorAll('.atr-crosshair-line');
|
||||
existingAtrLines.forEach(line => line.remove());
|
||||
const existingMacdLines = document.querySelectorAll('.macd-crosshair-line');
|
||||
existingMacdLines.forEach(line => line.remove());
|
||||
const existingChanMacdLines = document.querySelectorAll('.chanmacd-crosshair-line');
|
||||
existingChanMacdLines.forEach(line => line.remove());
|
||||
} catch (e) {
|
||||
console.debug('清除滚动中的十字线时出错:', e);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ====== ChanMACD图表相关函数 ======
|
||||
|
||||
// 清除ChanMACD标注
|
||||
function clearChanMacdMarkers() {
|
||||
// 清除所有系列的标记
|
||||
if (tvWidget.series.chanMacdLineSeries) {
|
||||
tvWidget.series.chanMacdLineSeries.setMarkers([]);
|
||||
}
|
||||
|
||||
if (tvWidget.series.chanMacdSignalSeries) {
|
||||
tvWidget.series.chanMacdSignalSeries.setMarkers([]);
|
||||
}
|
||||
|
||||
if (tvWidget.series.chanMacdHistSeries) {
|
||||
tvWidget.series.chanMacdHistSeries.setMarkers([]);
|
||||
}
|
||||
// 清空全局UnitTF标记,避免旧数据残留影响主图合并
|
||||
window.unittfMarkers = [];
|
||||
}
|
||||
// 添加所有ChanMACD标记
|
||||
function addAllChanMacdMarkers(segList, unittfList, histsetList, stateMarkers) {
|
||||
const macdMarkers = [];
|
||||
const signalMarkers = [];
|
||||
const histMarkers = [];
|
||||
const boundaryMarkers = [];
|
||||
const uTooltipMarkers = [];
|
||||
|
||||
// 添加段标记到MACD线
|
||||
console.log('处理段标记,段数量:', segList.length);
|
||||
segList.forEach((seg, index) => {
|
||||
console.log(`段${index}:`, {
|
||||
start_time: seg.start_time,
|
||||
end_time: seg.end_time,
|
||||
seg_dir: seg.seg_dir,
|
||||
has_start: !!seg.start_time,
|
||||
has_end: !!seg.end_time
|
||||
});
|
||||
|
||||
if (!seg.start_time) {
|
||||
console.log(`段${index}没有开始时间,跳过`);
|
||||
return;
|
||||
}
|
||||
|
||||
const startTime = new Date(seg.start_time).getTime() / 1000;
|
||||
console.log(`段${index}开始时间戳:`, startTime);
|
||||
macdMarkers.push({
|
||||
time: startTime,
|
||||
position: 'aboveBar',
|
||||
color: seg.seg_dir === 'ABOVE' ? '#e91e63' : '#4caf50',
|
||||
shape: 'square',
|
||||
text: `S${index}`,
|
||||
size: 0.5
|
||||
});
|
||||
|
||||
if (seg.end_time) {
|
||||
const endTime = new Date(seg.end_time).getTime() / 1000;
|
||||
console.log(`段${index}结束时间戳:`, endTime);
|
||||
macdMarkers.push({
|
||||
time: endTime,
|
||||
position: 'aboveBar',
|
||||
color: seg.seg_dir === 'ABOVE' ? '#e91e63' : '#4caf50',
|
||||
shape: 'square',
|
||||
text: `S${index}E`,
|
||||
size: 0.5
|
||||
});
|
||||
}
|
||||
});
|
||||
console.log('生成的段标记数量:', macdMarkers.length);
|
||||
|
||||
// 添加UnitTF标记(用于U)
|
||||
console.log('DEBUG: U 源数据条数:', Array.isArray(unittfList) ? unittfList.length : 'not array');
|
||||
unittfList.forEach((unittf, index) => {
|
||||
if (!unittf.start_time || unittf.invalid) return;
|
||||
|
||||
const startTime = new Date(unittf.start_time).getTime() / 1000;
|
||||
if (index === 0) {
|
||||
console.log('DEBUG: U0 示例:', unittf);
|
||||
}
|
||||
const startMarker = {
|
||||
time: startTime,
|
||||
position: unittf.dir > 0 ? 'aboveBar' : 'belowBar',
|
||||
color: unittf.dir > 0 ? '#ff9800' : '#9c27b0',
|
||||
shape: 'circle',
|
||||
text: `U${index}`,
|
||||
size: 0.5
|
||||
};
|
||||
signalMarkers.push(startMarker);
|
||||
// tooltip(开始)
|
||||
uTooltipMarkers.push({
|
||||
time: startTime,
|
||||
tooltip: `<div style="color: ${startMarker.color}; font-weight: bold;">
|
||||
UnitTF(${unittf.dir > 0 ? '正区' : '负区'}) 开始<br>
|
||||
峰值: ${unittf.peak_abs ?? '-'} 长度: ${unittf.length ?? '-'}<br>
|
||||
类型: ${unittf.start_type ?? '-'}<br>
|
||||
时间: ${unittf.start_time}
|
||||
</div>`
|
||||
});
|
||||
|
||||
if (unittf.end_time) {
|
||||
const endTime = new Date(unittf.end_time).getTime() / 1000;
|
||||
const endMarker = {
|
||||
time: endTime,
|
||||
position: unittf.dir > 0 ? 'aboveBar' : 'belowBar',
|
||||
color: unittf.dir > 0 ? '#ff9800' : '#9c27b0',
|
||||
shape: 'circle',
|
||||
text: `U${index}E`,
|
||||
size: 0.5
|
||||
};
|
||||
signalMarkers.push(endMarker);
|
||||
// tooltip(结束)
|
||||
uTooltipMarkers.push({
|
||||
time: endTime,
|
||||
tooltip: `<div style="color: ${endMarker.color}; font-weight: bold;">
|
||||
UnitTF(${unittf.dir > 0 ? '正区' : '负区'}) 结束<br>
|
||||
峰值: ${unittf.peak_abs ?? '-'} 长度: ${unittf.length ?? '-'}<br>
|
||||
类型: ${unittf.end_type ?? '-'}<br>
|
||||
时间: ${unittf.end_time}
|
||||
</div>`
|
||||
});
|
||||
}
|
||||
});
|
||||
// 识别"U 结束与新 U 开始同一根K"的边界,并显示合成标记(即使前一个U是 invalid 也显示边界)
|
||||
for (let i = 0; i + 1 < unittfList.length; i++) {
|
||||
const cur = unittfList[i];
|
||||
const nxt = unittfList[i + 1];
|
||||
if (!cur.end_time || !nxt.start_time) continue;
|
||||
const tEnd = new Date(cur.end_time).getTime();
|
||||
const tStart = new Date(nxt.start_time).getTime();
|
||||
if (!isNaN(tEnd) && tEnd === tStart) {
|
||||
const ts = Math.floor(tEnd / 1000);
|
||||
const color = nxt.dir > 0 ? '#ffb74d' : '#ba68c8';
|
||||
const marker = {
|
||||
time: ts,
|
||||
position: nxt.dir > 0 ? 'aboveBar' : 'belowBar',
|
||||
color: color,
|
||||
shape: 'square',
|
||||
text: 'U↔',
|
||||
size: 0.6
|
||||
};
|
||||
boundaryMarkers.push(marker);
|
||||
uTooltipMarkers.push({
|
||||
time: ts,
|
||||
tooltip: `<div style="color: ${color}; font-weight: bold;">\n U 结束 + 新 U 开始 (边界)<br>\n 结束方向: ${cur.dir > 0 ? '正区' : '负区'} → 新方向: ${nxt.dir > 0 ? '正区' : '负区'}<br>\n 时间: ${nxt.start_time}\n </div>`
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 添加HistSet标记到Histogram
|
||||
histsetList.forEach((histset, index) => {
|
||||
if (!histset.start_time) return;
|
||||
|
||||
const startTime = new Date(histset.start_time).getTime() / 1000;
|
||||
if (false) {
|
||||
histMarkers.push({
|
||||
time: startTime,
|
||||
position: histset.histset_dir === 'ABOVE' ? 'aboveBar' : 'belowBar',
|
||||
color: histset.histset_dir === 'ABOVE' ? '#4caf50' : '#f44336',
|
||||
shape: 'arrowUp',
|
||||
text: `H${index}`,
|
||||
size: 0.5
|
||||
});
|
||||
|
||||
if (histset.end_time) {
|
||||
const endTime = new Date(histset.end_time).getTime() / 1000;
|
||||
histMarkers.push({
|
||||
time: endTime,
|
||||
position: histset.histset_dir === 'ABOVE' ? 'aboveBar' : 'belowBar',
|
||||
color: histset.histset_dir === 'ABOVE' ? '#4caf50' : '#f44336',
|
||||
shape: 'arrowDown',
|
||||
text: `H${index}E`,
|
||||
size: 0.5
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 设置所有标记
|
||||
console.log('设置段标记到图表,标记数量:', macdMarkers.length);
|
||||
if (tvWidget.series.chanMacdLineSeries && macdMarkers.length > 0) {
|
||||
tvWidget.series.chanMacdLineSeries.setMarkers(macdMarkers);
|
||||
console.log('✅ 段标记已设置到chanMacdLineSeries');
|
||||
} else {
|
||||
console.log('⚠️ 无法设置段标记:', {
|
||||
hasSeries: !!tvWidget.series.chanMacdLineSeries,
|
||||
markersLength: macdMarkers.length
|
||||
});
|
||||
}
|
||||
|
||||
// 保存到全局,供主图与分型一起统一合并绘制(仅在开关开启时)
|
||||
console.log('DEBUG: U 标记数量:', signalMarkers.length);
|
||||
const allowUMerge = (window.showUOnMain && window.showUOnElement);
|
||||
window.unittfMarkers = allowUMerge ? [...signalMarkers, ...boundaryMarkers] : [];
|
||||
if (uTooltipMarkers.length > 0) {
|
||||
if (window.fxMarkers) {
|
||||
window.fxMarkers = [ ...window.fxMarkers, ...uTooltipMarkers ];
|
||||
} else {
|
||||
window.fxMarkers = uTooltipMarkers;
|
||||
}
|
||||
}
|
||||
// 同时在ChanMACD的Signal子图上标注U
|
||||
if (tvWidget.series.chanMacdSignalSeries && (signalMarkers.length > 0 || boundaryMarkers.length > 0)) {
|
||||
tvWidget.series.chanMacdSignalSeries.setMarkers([...signalMarkers, ...boundaryMarkers]);
|
||||
}
|
||||
|
||||
if (tvWidget.series.chanMacdHistSeries && histMarkers.length > 0) {
|
||||
tvWidget.series.chanMacdHistSeries.setMarkers(histMarkers);
|
||||
}
|
||||
|
||||
// 添加状态标记
|
||||
if (stateMarkers) {
|
||||
addStateMarkers(stateMarkers);
|
||||
}
|
||||
}
|
||||
// 添加状态标记
|
||||
function addStateMarkers(stateMarkers) {
|
||||
const stateMarkersList = [];
|
||||
const stateTooltips = [];
|
||||
|
||||
// 调试信息
|
||||
console.log('DEBUG: 状态标记数据:', stateMarkers);
|
||||
console.log('DEBUG: 高位列表长度:', stateMarkers.high_position_list ? stateMarkers.high_position_list.length : 0);
|
||||
console.log('DEBUG: 低位列表长度:', stateMarkers.low_position_list ? stateMarkers.low_position_list.length : 0);
|
||||
console.log('DEBUG: 高位空列表长度:', stateMarkers.high_empty_list ? stateMarkers.high_empty_list.length : 0);
|
||||
console.log('DEBUG: 低位空列表长度:', stateMarkers.low_empty_list ? stateMarkers.low_empty_list.length : 0);
|
||||
|
||||
// HP/HPE:仅使用高位术语(正负两侧统一展示为HP/HPE)
|
||||
const hpHeTemp = [];
|
||||
let hpIdx = 0; // 高位峰值计数
|
||||
let heIdx = 0; // 高位空(HPE)计数
|
||||
(stateMarkers.high_position_list || []).forEach(m => {
|
||||
if (!m.time) return;
|
||||
const t = new Date(m.time).getTime()/1000;
|
||||
const color = '#e91e63';
|
||||
hpHeTemp.push({ t, position: 'belowBar', color, text: `HP${hpIdx}` });
|
||||
stateTooltips.push({
|
||||
time: t,
|
||||
tooltip: `<div style="color:${color};font-weight:bold;">HP${hpIdx} 峰值<br>MACD:${(m.macd??'').toFixed?.(4)||m.macd}<br>SIGNAL:${(m.signal??'').toFixed?.(4)||m.signal}<br>HIST:${(m.macdhist??'').toFixed?.(4)||m.macdhist}</div>`
|
||||
});
|
||||
hpIdx++;
|
||||
});
|
||||
(stateMarkers.low_position_list || []).forEach(m => {
|
||||
if (!m.time) return;
|
||||
const t = new Date(m.time).getTime()/1000;
|
||||
const color = '#4caf50';
|
||||
// 低位峰值也统一标记为 HP(按需求不使用 LP)
|
||||
hpHeTemp.push({ t, position: 'aboveBar', color, text: `HP${hpIdx}` });
|
||||
stateTooltips.push({
|
||||
time: t,
|
||||
tooltip: `<div style=\"color:${color};font-weight:bold;\">HP${hpIdx} 峰值(正区)<br>MACD:${(m.macd??'').toFixed?.(4)||m.macd}<br>SIGNAL:${(m.signal??'').toFixed?.(4)||m.signal}<br>HIST:${(m.macdhist??'').toFixed?.(4)||m.macdhist}</div>`
|
||||
});
|
||||
hpIdx++;
|
||||
});
|
||||
(stateMarkers.high_empty_list || []).forEach(m => {
|
||||
if (!m.time) return;
|
||||
const t = new Date(m.time).getTime()/1000;
|
||||
const color = '#ff9800';
|
||||
hpHeTemp.push({ t, position: 'belowBar', color, text: `HPE${heIdx}` });
|
||||
stateTooltips.push({
|
||||
time: t,
|
||||
tooltip: `<div style="color:${color};font-weight:bold;">HPE${heIdx} 黄白交叉<br>MACD:${(m.macd??'').toFixed?.(4)||m.macd}<br>SIGNAL:${(m.signal??'').toFixed?.(4)||m.signal}<br>HIST:${(m.macdhist??'').toFixed?.(4)||m.macdhist}</div>`
|
||||
});
|
||||
heIdx++;
|
||||
});
|
||||
(stateMarkers.low_empty_list || []).forEach(m => {
|
||||
if (!m.time) return;
|
||||
const t = new Date(m.time).getTime()/1000;
|
||||
const color = '#17a2b8';
|
||||
// 低位空也统一标记为 HPE(按需求不使用 LPE)
|
||||
hpHeTemp.push({ t, position: 'aboveBar', color, text: `HPE${heIdx}` });
|
||||
stateTooltips.push({
|
||||
time: t,
|
||||
tooltip: `<div style=\"color:${color};font-weight:bold;\">HPE${heIdx} 黄白交叉(正区)<br>MACD:${(m.macd??'').toFixed?.(4)||m.macd}<br>SIGNAL:${(m.signal??'').toFixed?.(4)||m.signal}<br>HIST:${(m.macdhist??'').toFixed?.(4)||m.macdhist}</div>`
|
||||
});
|
||||
heIdx++;
|
||||
});
|
||||
hpHeTemp.sort((a,b)=>a.t-b.t).forEach(it => {
|
||||
stateMarkersList.push({
|
||||
time: it.t,
|
||||
position: it.position,
|
||||
color: it.color,
|
||||
shape: 'diamond',
|
||||
text: it.text,
|
||||
size: 0.65
|
||||
});
|
||||
});
|
||||
|
||||
// 设置状态标记到 MACD 线
|
||||
if (tvWidget.series.chanMacdLineSeries && stateMarkersList.length > 0) {
|
||||
tvWidget.series.chanMacdLineSeries.setMarkers(stateMarkersList);
|
||||
console.log('✅ 状态标记已设置到MACD线,数量:', stateMarkersList.length);
|
||||
}
|
||||
|
||||
// 将状态标记的 tooltip 合并入全局,主图悬浮可见
|
||||
if (stateTooltips.length > 0) {
|
||||
if (window.fxMarkers) {
|
||||
window.fxMarkers = [...window.fxMarkers, ...stateTooltips];
|
||||
} else {
|
||||
window.fxMarkers = stateTooltips;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 保留原函数用于向后兼容(但不使用)
|
||||
function addChanMacdSegMarkers(segList) {
|
||||
const markers = [];
|
||||
segList.forEach((seg, index) => {
|
||||
if (!seg.start_time) return;
|
||||
|
||||
const startTime = new Date(seg.start_time).getTime() / 1000;
|
||||
if (false){
|
||||
// 添加起点标记
|
||||
markers.push({
|
||||
time: startTime,
|
||||
position: 'aboveBar',
|
||||
color: seg.seg_dir === 'ABOVE' ? '#e91e63' : '#4caf50',
|
||||
shape: 'square',
|
||||
text: `S${index}`,
|
||||
size: 0.5
|
||||
});
|
||||
|
||||
// 如果有结束时间,添加结束标记
|
||||
if (seg.end_time) {
|
||||
const endTime = new Date(seg.end_time).getTime() / 1000;
|
||||
markers.push({
|
||||
time: endTime,
|
||||
position: 'aboveBar',
|
||||
color: seg.seg_dir === 'ABOVE' ? '#e91e63' : '#4caf50',
|
||||
shape: 'square',
|
||||
text: `S${index}E`,
|
||||
size: 0.5
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 设置标记到MACD线上
|
||||
if (tvWidget.series.chanMacdLineSeries && markers.length > 0) {
|
||||
tvWidget.series.chanMacdLineSeries.setMarkers(markers);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加UnitTF标注
|
||||
function addChanMacdUnitTFMarkers(unittfList) {
|
||||
const markers = [];
|
||||
unittfList.forEach((unittf, index) => {
|
||||
if (!unittf.start_time || unittf.invalid) return;
|
||||
|
||||
const startTime = new Date(unittf.start_time).getTime() / 1000;
|
||||
|
||||
// 添加起点标记
|
||||
markers.push({
|
||||
time: startTime,
|
||||
position: 'belowBar',
|
||||
color: unittf.dir > 0 ? '#ff9800' : '#9c27b0',
|
||||
shape: 'circle',
|
||||
text: `U${index}`,
|
||||
size: 0.5
|
||||
});
|
||||
|
||||
// 如果有结束时间,添加结束标记
|
||||
if (unittf.end_time) {
|
||||
const endTime = new Date(unittf.end_time).getTime() / 1000;
|
||||
markers.push({
|
||||
time: endTime,
|
||||
position: 'belowBar',
|
||||
color: unittf.dir > 0 ? '#ff9800' : '#9c27b0',
|
||||
shape: 'circle',
|
||||
text: `U${index}E`,
|
||||
size: 0.5
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 设置标记到信号线上
|
||||
if (tvWidget.series.chanMacdSignalSeries && markers.length > 0) {
|
||||
tvWidget.series.chanMacdSignalSeries.setMarkers(markers);
|
||||
}
|
||||
}
|
||||
|
||||
// 添加HistSet标注
|
||||
function addChanMacdHistSetMarkers(histsetList) {
|
||||
const markers = [];
|
||||
histsetList.forEach((histset, index) => {
|
||||
if (!histset.start_time) return;
|
||||
|
||||
const startTime = new Date(histset.start_time).getTime() / 1000;
|
||||
|
||||
// 添加起点标记
|
||||
markers.push({
|
||||
time: startTime,
|
||||
position: histset.histset_dir === 'ABOVE' ? 'aboveBar' : 'belowBar',
|
||||
color: histset.histset_dir === 'ABOVE' ? '#4caf50' : '#f44336',
|
||||
shape: 'arrowUp',
|
||||
text: `H${index}`,
|
||||
size: 0.5
|
||||
});
|
||||
|
||||
// 如果有结束时间,添加结束标记
|
||||
if (histset.end_time) {
|
||||
const endTime = new Date(histset.end_time).getTime() / 1000;
|
||||
markers.push({
|
||||
time: endTime,
|
||||
position: histset.histset_dir === 'ABOVE' ? 'aboveBar' : 'belowBar',
|
||||
color: histset.histset_dir === 'ABOVE' ? '#4caf50' : '#f44336',
|
||||
shape: 'arrowDown',
|
||||
text: `H${index}E`,
|
||||
size: 0.5
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 设置标记到柱状图上
|
||||
if (tvWidget.series.chanMacdHistSeries && markers.length > 0) {
|
||||
tvWidget.series.chanMacdHistSeries.setMarkers(markers);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user