feat: ECR-003 主站威科夫分析与图表叠层(已审)
独立 wyckoff 引擎 + 按需 include_wyckoff;主站 Lightweight 绘制区间/阶段/事件/VP。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -2199,6 +2199,165 @@ function initTradingView(symbol, timeframe) {
|
||||
}
|
||||
} catch (e) { console.error('结构区整体绘制出错:', e); }
|
||||
}
|
||||
// 威科夫叠层:区间 / 阶段 / 事件 / VP
|
||||
if ($('#showWyckoff').is(':checked') && currentData.wyckoff) {
|
||||
try {
|
||||
const w = currentData.wyckoff;
|
||||
const tr = w.trading_range;
|
||||
const parseTs = function(t) {
|
||||
if (t == null) return NaN;
|
||||
if (typeof t === 'number') return Math.floor(t > 1e12 ? t / 1000 : t);
|
||||
const ms = new Date(t).getTime();
|
||||
return isNaN(ms) ? NaN : Math.floor(ms / 1000);
|
||||
};
|
||||
const kd = currentData.kline_data || [];
|
||||
const chartEnd = kd.length
|
||||
? Math.floor(new Date(kd[kd.length - 1].date).getTime() / 1000)
|
||||
: NaN;
|
||||
|
||||
if ($('#showWyckoffRange').is(':checked') && tr) {
|
||||
const t0 = parseTs(tr.start_time);
|
||||
const t1 = tr.end_time ? parseTs(tr.end_time) : chartEnd;
|
||||
const hi = parseFloat(tr.high), lo = parseFloat(tr.low), mid = parseFloat(tr.mid);
|
||||
if (!isNaN(t0) && !isNaN(t1) && !isNaN(hi) && !isNaN(lo)) {
|
||||
const fill = 'rgba(52, 152, 219, 0.07)';
|
||||
const border = 'rgba(52, 152, 219, 0.75)';
|
||||
const fillLines = 6;
|
||||
const step = (hi - lo) / (fillLines + 1);
|
||||
for (let fi = 1; fi <= fillLines; fi++) {
|
||||
const fy = lo + step * fi;
|
||||
mainChart.addLineSeries({ color: fill, lineWidth: 2, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t0, value: fy }, { time: t1, value: fy }]);
|
||||
}
|
||||
mainChart.addLineSeries({ color: border, lineWidth: 2, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t0, value: hi }, { time: t1, value: hi }]);
|
||||
mainChart.addLineSeries({ color: border, lineWidth: 2, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t0, value: lo }, { time: t1, value: lo }]);
|
||||
if (!isNaN(mid)) {
|
||||
mainChart.addLineSeries({ color: border, lineWidth: 1, lineStyle: 2, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t0, value: mid }, { time: t1, value: mid }]);
|
||||
}
|
||||
mainChart.addLineSeries({ color: border, lineWidth: 1, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t0, value: lo }, { time: t0, value: hi }]);
|
||||
mainChart.addLineSeries({ color: border, lineWidth: 1, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t1, value: lo }, { time: t1, value: hi }]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($('#showWyckoffPhases').is(':checked') && w.phases && w.phases.length) {
|
||||
const phaseColors = {
|
||||
A: 'rgba(241, 196, 15, 0.85)',
|
||||
B: 'rgba(155, 89, 182, 0.85)',
|
||||
C: 'rgba(230, 126, 34, 0.85)',
|
||||
D: 'rgba(46, 204, 113, 0.85)',
|
||||
E: 'rgba(52, 152, 219, 0.85)'
|
||||
};
|
||||
const phaseMarkers = [];
|
||||
w.phases.forEach(function(ph) {
|
||||
const t0 = parseTs(ph.start_time);
|
||||
const t1 = ph.end_time ? parseTs(ph.end_time) : chartEnd;
|
||||
if (isNaN(t0) || isNaN(t1) || !tr) return;
|
||||
const hi = parseFloat(tr.high);
|
||||
if (isNaN(hi)) return;
|
||||
const col = phaseColors[ph.phase] || 'rgba(149,165,166,0.85)';
|
||||
// 阶段顶部分段色带(略高于区间高)
|
||||
const y = hi * 1.002;
|
||||
mainChart.addLineSeries({ color: col, lineWidth: 3, lastValueVisible: false, priceLineVisible: false })
|
||||
.setData([{ time: t0, value: y }, { time: t1, value: y }]);
|
||||
phaseMarkers.push({
|
||||
time: t0,
|
||||
position: 'aboveBar',
|
||||
color: col,
|
||||
shape: 'square',
|
||||
text: String(ph.phase || ph.label || ''),
|
||||
size: 1
|
||||
});
|
||||
});
|
||||
if (phaseMarkers.length) {
|
||||
const phSeries = mainChart.addLineSeries({ lastValueVisible: false, priceLineVisible: false });
|
||||
phSeries.setMarkers(phaseMarkers);
|
||||
}
|
||||
}
|
||||
|
||||
if ($('#showWyckoffEvents').is(':checked') && w.events && w.events.length) {
|
||||
const eventColors = {
|
||||
Spring: '#27ae60',
|
||||
SOS: '#2ecc71',
|
||||
LPS: '#16a085',
|
||||
UTAD: '#e74c3c',
|
||||
SOW: '#c0392b',
|
||||
LPSY: '#d35400'
|
||||
};
|
||||
const checks = (w.volume_confirm && w.volume_confirm.event_checks) || {};
|
||||
const markers = [];
|
||||
w.events.forEach(function(ev) {
|
||||
const t = parseTs(ev.time);
|
||||
if (isNaN(t)) return;
|
||||
const typ = ev.type || '';
|
||||
const chk = checks[typ] || {};
|
||||
const volOk = (chk.volume_ok != null) ? chk.volume_ok : ev.volume_ok;
|
||||
const ratioVal = (chk.volume_ratio != null) ? chk.volume_ratio : ev.volume_ratio;
|
||||
const ok = volOk === true ? '✓' : (volOk === false ? '✗' : '');
|
||||
const note = ev.note || '';
|
||||
const ratio = (ratioVal != null) ? (' vol×' + Number(ratioVal).toFixed(2)) : '';
|
||||
markers.push({
|
||||
time: t,
|
||||
position: (typ === 'Spring' || typ === 'LPS' || typ === 'SOW') ? 'belowBar' : 'aboveBar',
|
||||
color: eventColors[typ] || '#7f8c8d',
|
||||
shape: 'arrowUp',
|
||||
text: typ + (ok ? ' ' + ok : '') + (note ? ' ' + note : '') + ratio,
|
||||
size: 1
|
||||
});
|
||||
});
|
||||
if (markers.length) {
|
||||
const evSeries = mainChart.addLineSeries({ lastValueVisible: false, priceLineVisible: false });
|
||||
evSeries.setMarkers(markers);
|
||||
}
|
||||
}
|
||||
|
||||
if ($('#showWyckoffVP').is(':checked') && w.volume_profile && tr) {
|
||||
const vp = w.volume_profile;
|
||||
const t1 = tr.end_time ? parseTs(tr.end_time) : chartEnd;
|
||||
if (!isNaN(t1)) {
|
||||
const bins = vp.bins || [];
|
||||
let maxVol = 0;
|
||||
bins.forEach(function(b) { if (b.volume > maxVol) maxVol = b.volume; });
|
||||
const maxWidthSec = Math.max(60, Math.floor((t1 - parseTs(tr.start_time)) * 0.15));
|
||||
bins.forEach(function(b) {
|
||||
if (!b.volume || maxVol <= 0) return;
|
||||
const wSec = Math.max(1, Math.floor(maxWidthSec * (b.volume / maxVol)));
|
||||
const alpha = 0.15 + 0.55 * (b.volume / maxVol);
|
||||
mainChart.addLineSeries({
|
||||
color: 'rgba(142, 68, 173, ' + alpha.toFixed(2) + ')',
|
||||
lineWidth: 1,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false
|
||||
}).setData([
|
||||
{ time: t1 - wSec, value: b.price },
|
||||
{ time: t1, value: b.price }
|
||||
]);
|
||||
});
|
||||
const levels = [
|
||||
{ p: vp.poc, c: 'rgba(142, 68, 173, 0.95)', w: 2, style: 0 },
|
||||
{ p: vp.vah, c: 'rgba(155, 89, 182, 0.7)', w: 1, style: 2 },
|
||||
{ p: vp.val, c: 'rgba(155, 89, 182, 0.7)', w: 1, style: 2 }
|
||||
];
|
||||
const t0 = parseTs(tr.start_time);
|
||||
levels.forEach(function(lv) {
|
||||
const p = parseFloat(lv.p);
|
||||
if (isNaN(p) || isNaN(t0)) return;
|
||||
mainChart.addLineSeries({
|
||||
color: lv.c,
|
||||
lineWidth: lv.w,
|
||||
lineStyle: lv.style,
|
||||
lastValueVisible: false,
|
||||
priceLineVisible: false
|
||||
}).setData([{ time: t0, value: p }, { time: t1, value: p }]);
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) { console.error('威科夫绘制出错:', e); }
|
||||
}
|
||||
// 显示未完成中枢 - 分别处理主周期、次周期和次次周期
|
||||
if ($('#showMainZs').is(':checked') || $('#showElementZs').is(':checked') || $('#showSubSubZs').is(':checked') || $('#showSubSubBiZs').is(':checked')) {
|
||||
console.log('绘制未完成中枢 - 已启用');
|
||||
|
||||
@@ -62,7 +62,8 @@ function updateChart(options) {
|
||||
end_time: endTimeMs,
|
||||
elements_only: false,
|
||||
zone_kl_lines: parseInt($('#zoneKlLines').val()) || 1000,
|
||||
include_structure_zones: $('#showMainStructureZone').is(':checked') ? 1 : 0
|
||||
include_structure_zones: $('#showMainStructureZone').is(':checked') ? 1 : 0,
|
||||
include_wyckoff: $('#showWyckoff').is(':checked') ? 1 : 0
|
||||
},
|
||||
success: function(data) {
|
||||
// 隐藏加载图标
|
||||
|
||||
@@ -93,6 +93,26 @@ $(document).on('change', '#showMainStructureZone', function() {
|
||||
}
|
||||
});
|
||||
|
||||
// 威科夫主开关:勾选才请求;子项仅本地重绘
|
||||
function syncWyckoffSubControls() {
|
||||
const on = $('#showWyckoff').is(':checked');
|
||||
$('#showWyckoffRange, #showWyckoffPhases, #showWyckoffEvents, #showWyckoffVP').prop('disabled', !on);
|
||||
}
|
||||
$(document).on('change', '#showWyckoff', function() {
|
||||
const on = $('#showWyckoff').is(':checked');
|
||||
syncWyckoffSubControls();
|
||||
console.log('威科夫切换为:', on);
|
||||
if (on) {
|
||||
updateChart();
|
||||
} else {
|
||||
updateChartDisplay();
|
||||
}
|
||||
});
|
||||
$(document).on('change', '#showWyckoffRange, #showWyckoffPhases, #showWyckoffEvents, #showWyckoffVP', function() {
|
||||
updateChartDisplay();
|
||||
});
|
||||
$(function() { syncWyckoffSubControls(); });
|
||||
|
||||
// 添加趋势显示复选框变更事件(主/元素),变更后刷新主图
|
||||
$('#showMainTrend').change(function() {
|
||||
updateChartDisplay();
|
||||
|
||||
Reference in New Issue
Block a user