Files
Chan/web/static/js/app/main.js
T
jackyu66gitandCursor c22cd48f36 Web 加上资金面/情绪叠图,并收紧默认图面。
默认主/次/次次改为 45m/15m/5m、开始时间一周;SD/CD 按 hist 摆位置和箭头;去掉笔线段背驰与第四类勾选。

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-12 02:25:44 +08:00

504 lines
20 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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 = [];
window.unittfMarkersMain = [];
window.unittfMarkersElement = [];
window.unittfMarkersSubSub = [];
}
function unittfDirSign(dir) {
if (dir === 'ABOVE' || dir === 1 || dir === '1' || dir === true) return 1;
if (dir === 'UNDER' || dir === -1 || dir === '-1') return -1;
const n = Number(dir);
if (n > 0) return 1;
if (n < 0) return -1;
return 0;
}
function buildUnittfOverlayMarkers(unittfList, opt) {
const markers = [];
if (!Array.isArray(unittfList) || !opt) return markers;
const up = opt.up, down = opt.down, prefix = opt.prefix || 'U';
const size = opt.size || 0.5;
for (let i = 0; i < unittfList.length; i++) {
const unittf = unittfList[i];
if (!unittf || !unittf.start_time || unittf.invalid) continue;
const startTime = Math.floor(new Date(unittf.start_time).getTime() / 1000);
if (isNaN(startTime)) continue;
const sign = unittfDirSign(unittf.dir);
const color = sign > 0 ? up : down;
const pos = sign > 0 ? 'aboveBar' : 'belowBar';
markers.push({ time: startTime, position: pos, color: color, shape: 'circle', text: prefix + i, size: size });
if (unittf.end_time) {
const endTime = Math.floor(new Date(unittf.end_time).getTime() / 1000);
if (!isNaN(endTime)) {
markers.push({ time: endTime, position: pos, color: color, shape: 'circle', text: prefix + i + 'E', size: size });
}
}
}
for (let i = 0; i + 1 < unittfList.length; i++) {
const cur = unittfList[i];
const nxt = unittfList[i + 1];
if (!cur || !nxt || !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 sign = unittfDirSign(nxt.dir);
markers.push({
time: Math.floor(tEnd / 1000),
position: sign > 0 ? 'aboveBar' : 'belowBar',
color: sign > 0 ? (opt.boundaryUp || up) : (opt.boundaryDown || down),
shape: 'square',
text: prefix + '↔',
size: 0.6
});
}
}
return markers;
}
function refreshUnittfOverlayFromData(data) {
// U/穿零轴只画在 MACD 副图,不再铺到主图
window.unittfMarkersMain = [];
window.unittfMarkersElement = [];
window.unittfMarkersSubSub = [];
}
// 添加所有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);
window.unittfMarkers = [...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);
}
}