From 5c10e35b76507f618fac8c219f98d331d49abe8f Mon Sep 17 00:00:00 2001 From: jackyu66git Date: Wed, 26 Aug 2026 01:27:51 +0800 Subject: [PATCH] =?UTF-8?q?refactor(web):=20=E7=A7=BB=E9=99=A4=E4=B8=BB?= =?UTF-8?q?=E5=9B=BE=E5=A8=81=E7=A7=91=E5=A4=AB=E9=80=89=E9=A1=B9=E4=B8=8E?= =?UTF-8?q?=E5=8F=A0=E5=B1=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 去掉区间/阶段/时间/VP 开关、Cycle 摘要面板及绘制逻辑;分析请求默认 include_wyckoff=0。 Co-authored-by: Cursor --- web/static/js/app/chart_format.js | 3 - web/static/js/app/chart_tv_lifecycle.js | 6 - web/static/js/app/chart_tv_overlays.js | 232 +--------------------- web/static/js/app/chart_tv_shell.js | 15 -- web/static/js/app/chart_view.js | 9 +- web/static/js/app/macd_ui.js | 11 -- web/static/js/app/ui.js | 246 ------------------------ web/templates/index.html | 138 +------------ 8 files changed, 14 insertions(+), 646 deletions(-) diff --git a/web/static/js/app/chart_format.js b/web/static/js/app/chart_format.js index cdc34c5..6a261e9 100644 --- a/web/static/js/app/chart_format.js +++ b/web/static/js/app/chart_format.js @@ -1,9 +1,6 @@ /* chart_format.js — split from chart.js */ /* chart.js */ function updateChartDisplay() { - if (typeof renderWyckoffCycleSummary === 'function') { - renderWyckoffCycleSummary(); - } if (currentData) { // 检测K线周期是否切换 const curPeriod = $('#subSubPeriodKline').is(':checked') ? 'subsub' : diff --git a/web/static/js/app/chart_tv_lifecycle.js b/web/static/js/app/chart_tv_lifecycle.js index a17a7b6..3f1c19f 100644 --- a/web/static/js/app/chart_tv_lifecycle.js +++ b/web/static/js/app/chart_tv_lifecycle.js @@ -40,12 +40,6 @@ function disposeTradingViewCharts() { var chartRoot = document.getElementById('tradingview_chart'); if (chartRoot) { - // 重建前救出 Cycle Summary,避免 innerHTML 清空时被销毁 - var summaryEl = document.getElementById('wyckoffCycleSummary'); - var chartHost = chartRoot.parentElement; - if (summaryEl && chartRoot.contains(summaryEl) && chartHost) { - chartHost.appendChild(summaryEl); - } chartRoot.innerHTML = ''; } } catch (e) { diff --git a/web/static/js/app/chart_tv_overlays.js b/web/static/js/app/chart_tv_overlays.js index 3815e90..bee3542 100644 --- a/web/static/js/app/chart_tv_overlays.js +++ b/web/static/js/app/chart_tv_overlays.js @@ -1,4 +1,4 @@ -/* chart_tv_overlays.js — structure zones / wyckoff / BSP / FX / bollinger */ +/* chart_tv_overlays.js — structure zones / BSP / FX / bollinger */ /** 标记 time 必须落在主 series 的 K 线 time 上,否则 LWC 会抛 Value is null */ function alignMarkersToCandles(markers, candles) { @@ -331,229 +331,6 @@ function chartTvRenderOverlays(ctx) { } } catch (e) { console.error('结构区整体绘制出错:', e); } } - // 区间/阶段/时间/VP:框线同中枢;标记并入主 K(同 BSP),时间对齐 candles - (function drawWrLikeChan() { - const candleSeries = tvWidget.series.candleSeries - || tvWidget.series.barSeries - || tvWidget.series.lineSeries - || tvWidget.series.areaSeries - || tvWidget.series.heikinSeries - || tvWidget.series.renkoSeries; - const candleTimes = (candles || []).map(function(c) { return c.time; }) - .filter(function(t) { return t != null && !isNaN(t); }); - const barStep = (candleTimes.length >= 2) - ? Math.max(1, candleTimes[1] - candleTimes[0]) - : 3600; - const lastKlineTime = candleTimes.length ? candleTimes[candleTimes.length - 1] : NaN; - const chartStart = candleTimes.length ? candleTimes[0] : NaN; - - const toSec = function(t) { - if (t == null) return NaN; - if (typeof t === 'number') return t > 1e12 ? Math.floor(t / 1000) : Math.floor(t); - const ms = new Date(t).getTime(); - return isNaN(ms) ? NaN : Math.floor(ms / 1000); - }; - // 标记必须落在主 series 的 time 上(与 KLC 趋势 nearestTime 同思路) - const snapToCandle = function(t) { - if (!candleTimes.length || isNaN(t)) return t; - let best = candleTimes[0], bd = Math.abs(candleTimes[0] - t); - for (let i = 1; i < candleTimes.length; i++) { - const d = Math.abs(candleTimes[i] - t); - if (d < bd) { bd = d; best = candleTimes[i]; } - } - return best; - }; - const ensureSpan = function(t0, t1) { - if (isNaN(t0) || isNaN(t1)) return [t0, t1]; - if (t1 < t0) { const x = t0; t0 = t1; t1 = x; } - if (t1 <= t0) t1 = t0 + barStep; - return [t0, t1]; - }; - const drawBox = function(t0, t1, hi, lo, color) { - const span = ensureSpan(t0, t1); - t0 = span[0]; t1 = span[1]; - if (isNaN(t0) || isNaN(t1) || isNaN(hi) || isNaN(lo)) return; - const opt = { color: color, lineWidth: 1, lastValueVisible: false, priceLineVisible: false }; - mainChart.addLineSeries(opt).setData([{ time: t0, value: hi }, { time: t1, value: hi }]); - mainChart.addLineSeries(opt).setData([{ time: t0, value: lo }, { time: t1, value: lo }]); - mainChart.addLineSeries(opt).setData([{ time: t0, value: lo }, { time: t0, value: hi }]); - mainChart.addLineSeries(opt).setData([{ time: t1, value: lo }, { time: t1, value: hi }]); - }; - const mkPL = function(price, color, title, style) { - if (!candleSeries) return; - const p = parseFloat(price); - if (isNaN(p)) return; - try { - candleSeries.createPriceLine({ - price: p, color: color, - lineWidth: style === 2 ? 1 : 2, - lineStyle: style || 0, - axisLabelVisible: true, - title: title - }); - } catch (e) { console.warn('价位线失败', title, e); } - }; - const phaseColors = { A: '#f1c40f', B: '#9b59b6', C: '#e67e22', D: '#2ecc71', E: '#3498db' }; - const eventColors = { - Spring: '#27ae60', SOS: '#2ecc71', LPS: '#16a085', - UTAD: '#e74c3c', SOW: '#c0392b', LPSY: '#d35400' - }; - - const wrMarkers = []; - window.wrMarkers = []; - const pushMarker = function(m) { - if (!m || isNaN(m.time)) return; - m.time = snapToCandle(m.time); - if (!isNaN(chartStart) && (m.time < chartStart || m.time > lastKlineTime)) return; - wrMarkers.push(m); - }; - - const drawOne = function(w, cfg) { - if (!w) return; - const showR = $(cfg.rangeSel).is(':checked'); - const showP = $(cfg.phasesSel).is(':checked'); - const showE = $(cfg.eventsSel).is(':checked'); - const showV = $(cfg.vpSel).is(':checked'); - if (!showR && !showP && !showE && !showV) return; - // WYCKOFF-MULTI-CYCLE-001:遍历 cycles;无则退化为顶层单段 - const cycles = (w.cycles && w.cycles.length) - ? w.cycles - : (w.trading_range ? [{ - id: 0, status: 'ACTIVE', role: 'latest', - trading_range: w.trading_range, phases: w.phases, events: w.events, - volume_profile: w.volume_profile, volume_confirm: w.volume_confirm, - period: { start_time: w.trading_range.start_time, end_time: w.trading_range.end_time, bars: w.trading_range.bars } - }] : []); - const tfLabel = (cfg.tfLabel || cfg.tag || 'TF').toString().toUpperCase(); - - cycles.forEach(function(cycle) { - const tr = cycle.trading_range; - if (!tr) return; - const cid = (cycle.id != null) ? cycle.id : 0; - const isActive = String(cycle.status || '').toUpperCase() === 'ACTIVE'; - const cTag = tfLabel + ' C' + cid + ' '; - try { - let t0 = toSec(tr.start_time || (cycle.period && cycle.period.start_time)); - let t1 = toSec(tr.end_time || (cycle.period && cycle.period.end_time)); - // 仅 ACTIVE 可拉到最新 K;历史用 period.end - if (isActive && !isNaN(lastKlineTime)) { - t1 = lastKlineTime; - } else if (cycle.period && cycle.period.end_time) { - t1 = toSec(cycle.period.end_time); - } - const hi = parseFloat(tr.high), lo = parseFloat(tr.low); - if (showR && !isNaN(hi) && !isNaN(lo)) { - drawBox(t0, t1, hi, lo, cfg.color); - if (isActive) { - mkPL(hi, cfg.color, cfg.hiTag, 0); - mkPL(lo, cfg.color, cfg.loTag, 0); - mkPL(tr.mid, cfg.color, cfg.midTag, 2); - } - } - if (showP && cycle.phases && cycle.phases.length && !isNaN(hi)) { - cycle.phases.forEach(function(ph) { - let p0 = toSec(ph.start_time); - let p1 = ph.end_time ? toSec(ph.end_time) : t1; - const sp = ensureSpan(p0, p1); - p0 = sp[0]; p1 = sp[1]; - if (isNaN(p0) || isNaN(p1)) return; - const col = phaseColors[ph.phase] || '#95a5a6'; - mainChart.addLineSeries({ - color: col, lineWidth: 3, lastValueVisible: false, priceLineVisible: false - }).setData([{ time: p0, value: hi }, { time: p1, value: hi }]); - pushMarker({ - time: p0, position: 'aboveBar', color: col, shape: 'square', - text: cTag + 'Phase ' + String(ph.phase || ''), size: 1 - }); - }); - } - if (showV && isActive && cycle.volume_profile) { - const vp = cycle.volume_profile; - mkPL(vp.poc, cfg.vpColor, cfg.pocTag, 0); - mkPL(vp.vah, cfg.vpColor, cfg.vahTag, 2); - mkPL(vp.val, cfg.vpColor, cfg.valTag, 2); - const bins = (vp.bins || []).filter(function(b) { return b && b.volume > 0; }) - .slice().sort(function(a, b) { return b.volume - a.volume; }).slice(0, 8); - let maxVol = 0; - bins.forEach(function(b) { if (b.volume > maxVol) maxVol = b.volume; }); - const span = (!isNaN(t0) && !isNaN(t1) && t1 > t0) ? (t1 - t0) : barStep * 12; - bins.forEach(function(b) { - if (!b.volume || maxVol <= 0 || isNaN(t1)) return; - const wSec = Math.max(barStep, Math.floor(span * 0.12 * (b.volume / maxVol))); - let leftT = Math.max(isNaN(t0) ? (t1 - wSec) : t0, t1 - wSec); - if (leftT >= t1) leftT = t1 - barStep; - if (leftT >= t1) return; - const alpha = 0.25 + 0.55 * (b.volume / maxVol); - mainChart.addLineSeries({ - color: cfg.vpRgb.replace('ALPHA', alpha.toFixed(2)), - lineWidth: 2, lastValueVisible: false, priceLineVisible: false - }).setData([{ time: leftT, value: b.price }, { time: t1, value: b.price }]); - }); - } - if (showE && cycle.events && cycle.events.length) { - const checks = (cycle.volume_confirm && cycle.volume_confirm.event_checks) || {}; - cycle.events.forEach(function(ev) { - const t = toSec(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 ok = volOk === true ? '✓' : (volOk === false ? '✗' : ''); - pushMarker({ - time: t, - position: (typ === 'Spring' || typ === 'LPS' || typ === 'SOW') ? 'belowBar' : 'aboveBar', - color: eventColors[typ] || '#7f8c8d', - shape: (typ === 'Spring' || typ === 'SOW' || typ === 'LPS') ? 'arrowDown' : 'arrowUp', - text: cTag + typ + ok, - size: 1 - }); - }); - } - } catch (e) { console.error('区间叠层出错', cfg.name, 'C' + cid, e); } - }); - }; - - if ($('#showMainWrRange').is(':checked') || $('#showMainWrPhases').is(':checked') - || $('#showMainWrEvents').is(':checked') || $('#showMainWrVP').is(':checked')) { - drawOne(currentData.wyckoff, { - name: '主', tag: '', tfLabel: (currentData.timeframe || '4H'), color: '#3498db', vpColor: '#8e44ad', - vpRgb: 'rgba(142, 68, 173, ALPHA)', - rangeSel: '#showMainWrRange', phasesSel: '#showMainWrPhases', - eventsSel: '#showMainWrEvents', vpSel: '#showMainWrVP', - hiTag: 'WR.H', loTag: 'WR.L', midTag: 'WR.M', - pocTag: 'POC', vahTag: 'VAH', valTag: 'VAL' - }); - } - if ($('#showElementWrRange').is(':checked') || $('#showElementWrPhases').is(':checked') - || $('#showElementWrEvents').is(':checked') || $('#showElementWrVP').is(':checked')) { - drawOne(currentData.element_wyckoff, { - name: '次', tag: 'e', tfLabel: (currentData.element_timeframe || '2H'), color: '#e67e22', vpColor: '#d35400', - vpRgb: 'rgba(211, 84, 0, ALPHA)', - rangeSel: '#showElementWrRange', phasesSel: '#showElementWrPhases', - eventsSel: '#showElementWrEvents', vpSel: '#showElementWrVP', - hiTag: 'eWR.H', loTag: 'eWR.L', midTag: 'eWR.M', - pocTag: 'ePOC', vahTag: 'eVAH', valTag: 'eVAL' - }); - } - if ($('#showSubSubWrRange').is(':checked') || $('#showSubSubWrPhases').is(':checked') - || $('#showSubSubWrEvents').is(':checked') || $('#showSubSubWrVP').is(':checked')) { - drawOne(currentData.sub_sub_wyckoff, { - name: '次次', tag: 's', tfLabel: (currentData.sub_sub_timeframe || '1H'), color: '#27ae60', vpColor: '#16a085', - vpRgb: 'rgba(22, 160, 133, ALPHA)', - rangeSel: '#showSubSubWrRange', phasesSel: '#showSubSubWrPhases', - eventsSel: '#showSubSubWrEvents', vpSel: '#showSubSubWrVP', - hiTag: 'sWR.H', loTag: 'sWR.L', midTag: 'sWR.M', - pocTag: 'sPOC', vahTag: 'sVAH', valTag: 'sVAL' - }); - } - - // 同 BSP:写入 window,稍后与分型/买卖点一并 setMarkers - wrMarkers.sort(function(a, b) { return a.time - b.time; }); - window.wrMarkers = wrMarkers; - if (typeof renderWyckoffCycleSummary === 'function') { - renderWyckoffCycleSummary(); - } - })(); // 显示未完成中枢 - 分别处理主周期、次周期和次次周期 if ($('#showMainZs').is(':checked') || $('#showElementZs').is(':checked') || $('#showSubSubZs').is(':checked') || $('#showSubSubBiZs').is(':checked')) { console.log('绘制未完成中枢 - 已启用'); @@ -2336,8 +2113,7 @@ function chartTvRenderOverlays(ctx) { ...(window.kluDivMarkersElement || []), ...(window.kluDivMarkersSubSub || []), ...trendMarkersToUse, - ...(window.bspMarkers || []), - ...(window.wrMarkers || []) + ...(window.bspMarkers || []) ]; if (combinedMarkers.length > 0) { console.log( @@ -2346,7 +2122,6 @@ function chartTvRenderOverlays(ctx) { '个,小周期分型:', allElementFxMarkers.length, '个,UnitTF:', (window.unittfMarkers || []).length, '个,BSP标记:', (window.bspMarkers || []).length, - '个,区间标记:', (window.wrMarkers || []).length, '个)' ); @@ -2455,8 +2230,7 @@ function chartTvRenderOverlays(ctx) { ...(window.kluDivMarkersElement || []), ...(window.kluDivMarkersSubSub || []), ...trendMarkersToUse, - ...(window.bspMarkers || []), - ...(window.wrMarkers || []) + ...(window.bspMarkers || []) ]; if (onlyMainAndU.length > 0) { console.log('仅设置', onlyMainAndU.length, '个主周期/UnitTF标记(主周期分型:', (window.mainFxMarkers || []).length, ',UnitTF:', (window.unittfMarkers || []).length, ')'); diff --git a/web/static/js/app/chart_tv_shell.js b/web/static/js/app/chart_tv_shell.js index 97b53b5..eae4258 100644 --- a/web/static/js/app/chart_tv_shell.js +++ b/web/static/js/app/chart_tv_shell.js @@ -367,21 +367,6 @@ function chartTvBuildShell(ctx) { // 创建主图表 const mainChart = LightweightCharts.createChart(mainChartContainer, createChartOptions(true, 'main')); - // Cycle Summary 挂到主图左下角(相对 K 线主图 pane,而非整图底边) - (function mountWyckoffCycleSummary() { - var summaryEl = document.getElementById('wyckoffCycleSummary'); - if (!summaryEl) { - summaryEl = document.createElement('div'); - summaryEl.id = 'wyckoffCycleSummary'; - summaryEl.className = 'wyckoff-cycle-summary'; - summaryEl.setAttribute('aria-live', 'polite'); - } - mainChartContainer.appendChild(summaryEl); - if (typeof renderWyckoffCycleSummary === 'function') { - try { renderWyckoffCycleSummary(); } catch (e) {} - } - })(); - // 创建成交量图表 - 只显示底部的时间轴 const volumeChart = LightweightCharts.createChart(volumeChartContainer, createChartOptions(false, 'volume')); diff --git a/web/static/js/app/chart_view.js b/web/static/js/app/chart_view.js index 412ae07..06a7c95 100644 --- a/web/static/js/app/chart_view.js +++ b/web/static/js/app/chart_view.js @@ -164,9 +164,6 @@ function applyAnalyzeSuccess(data, symbol, options) { currentData = data; window._lastChartSymbol = symbol; window._lastFullAnalyzeAt = Date.now(); - if (typeof renderWyckoffCycleSummary === 'function') { - renderWyckoffCycleSummary(); - } const ready = !!(tvWidget && tvWidget.state && tvWidget.state.isInitialized && tvWidget.mainChart); const structureZonesOn = $('#showMainStructureZone').is(':checked'); @@ -212,7 +209,8 @@ function analyzeChart(options) { end_time: ctx.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: 0 }, success: function(data) { $('#refreshLoadingSpinner').hide(); @@ -327,7 +325,8 @@ function autoRefreshChart(options) { end_time: ctx.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: 0 }, success: function(data) { $('#refreshLoadingSpinner').hide(); diff --git a/web/static/js/app/macd_ui.js b/web/static/js/app/macd_ui.js index a30f244..16b1f91 100644 --- a/web/static/js/app/macd_ui.js +++ b/web/static/js/app/macd_ui.js @@ -93,17 +93,6 @@ $(document).on('change', '#showMainStructureZone', function() { } }); -// 区间/阶段/时间/VP:与缠论笔开关一样,本地重绘 -$(document).on( - 'change', - '#showMainWrRange, #showMainWrPhases, #showMainWrEvents, #showMainWrVP,' + - '#showElementWrRange, #showElementWrPhases, #showElementWrEvents, #showElementWrVP,' + - '#showSubSubWrRange, #showSubSubWrPhases, #showSubSubWrEvents, #showSubSubWrVP', - function() { - updateChartDisplay(); - } -); - // 添加趋势显示复选框变更事件(主/元素),变更后刷新主图 $('#showMainTrend').change(function() { updateChartDisplay(); diff --git a/web/static/js/app/ui.js b/web/static/js/app/ui.js index ecc7591..3a762d4 100644 --- a/web/static/js/app/ui.js +++ b/web/static/js/app/ui.js @@ -1,251 +1,5 @@ /* ui.js */ -/** Trading OS 可消费的威科夫 Cycle 摘要(Confirmed + Live 分区;cycles[0]=ACTIVE) */ -function buildWyckoffCycleSummaryPayload(w, tf) { - if (!w) return null; - const cycles = (w.cycles && w.cycles.length) - ? w.cycles - : (w.trading_range ? [{ - id: 0, status: 'ACTIVE', role: 'latest', lifecycle: w.lifecycle || 'UNKNOWN', - trading_range: w.trading_range, bias: w.bias, - phases: w.phases || [], events: w.events || [], - confirmed: { phases: w.phases || [], events: w.events || [] }, - live: w.live || null, - confidence: { overall: null }, - period: { - start_time: w.trading_range.start_time, - end_time: w.trading_range.end_time, - bars: w.trading_range.bars - } - }] : []); - if (!cycles.length) return null; - const active = cycles[0]; // 禁止 cycles[-1] - const confirmed = active.confirmed || { - phases: active.phases || w.phases || [], - events: active.events || w.events || [] - }; - const live = active.live || w.live || null; - const cPhases = confirmed.phases || []; - const cEvents = confirmed.events || []; - const lastPhase = cPhases.length ? cPhases[cPhases.length - 1] : null; - const lastEvent = cEvents.length ? cEvents[cEvents.length - 1] : null; - const tr = active.trading_range || {}; - const prev = cycles.length > 1 ? cycles[1] : null; - const biasLabel = ({ - accumulation: 'Accumulation', - distribution: 'Distribution', - unknown: 'Unknown' - })[active.bias] || (active.bias || 'Unknown'); - const liveCand = (live && live.event_candidates && live.event_candidates[0]) || null; - const liveConf = live && live.confidence ? live.confidence.overall : null; - return { - symbol: (typeof currentData !== 'undefined' && currentData && currentData.symbol) || $('#symbol').val() || '', - timeframe: (tf || w.timeframe || $('#timeframe').val() || '').toString().toUpperCase(), - active: { - cycle_id: active.id != null ? active.id : 0, - status: active.status || 'ACTIVE', - lifecycle: active.lifecycle || (live && live.lifecycle) || 'UNKNOWN', - structure: biasLabel, - phase_confirmed: lastPhase ? String(lastPhase.phase || '') : null, - event_confirmed: lastEvent ? String(lastEvent.type || '') : null, - phase_candidate: live ? live.phase_candidate : null, - event_candidate: liveCand ? liveCand.type : null, - event_candidate_confidence: liveCand ? liveCand.confidence : null, - next_expected: live ? live.next_expected : null, - range: { - low: tr.low, - high: tr.high, - start_time: (active.period && active.period.start_time) || tr.start_time, - end_time: (active.period && active.period.end_time) || tr.end_time, - bars: (active.period && active.period.bars) != null ? active.period.bars : tr.bars - }, - confidence_confirmed: (active.confidence && active.confidence.overall != null) - ? active.confidence.overall - : null, - confidence_live: liveConf - }, - confirmed_history: cycles.slice(1, 4).map(function(c) { - const evs = ((c.confirmed && c.confirmed.events) || c.events || []) - .map(function(e) { return e.type; }).filter(Boolean); - return { - cycle_id: c.id, - structure: ({ - accumulation: 'Accumulation', - distribution: 'Distribution', - unknown: 'Unknown' - })[c.bias] || c.bias, - events: evs, - lifecycle: c.lifecycle || 'COMPLETED' - }; - }), - live: live, - cycle_count: cycles.length - }; -} - -function _wrLayerTogglesOn(prefix) { - // prefix: Main | Element | SubSub - return $('#show' + prefix + 'WrRange').is(':checked') - || $('#show' + prefix + 'WrPhases').is(':checked') - || $('#show' + prefix + 'WrEvents').is(':checked') - || $('#show' + prefix + 'WrVP').is(':checked'); -} - -/** 面板展示用中文(机器可读 payload 仍保留英文原值) */ -function _wcsLifecycleZh(v) { - return ({ - UNKNOWN: '未知', - FORMING: '形成中', - CONFIRMED: '已确认', - COMPLETED: '已完成', - ACTIVE: '当前' - })[v] || v || '未知'; -} - -function _wcsStructureZh(v) { - if (!v) return '—'; - const key = String(v).toLowerCase(); - return ({ - accumulation: '吸筹', - distribution: '派发', - unknown: '未知' - })[key] || ({ - Accumulation: '吸筹', - Distribution: '派发', - Unknown: '未知' - })[v] || v; -} - -function _wcsEventZh(v) { - if (v == null || v === '') return '—'; - return ({ - Spring: '弹簧', - UTAD: '上升后派发', - SOS: '强势信号', - SOW: '弱势信号', - LPS: '最后支撑', - LPSY: '最后供应', - Test: '回测', - PSY: '初步供应', - BC: '买气高潮', - AR: '自动回落', - ST: '二次测试', - SC: '卖气高潮' - })[v] || v; -} - -function _htmlWyckoffSummaryBlock(payload, blockClass) { - if (!payload || !payload.active) return ''; - const a = payload.active; - const fmtPx = function(v) { - if (v == null || isNaN(Number(v))) return '—'; - const n = Number(v); - return n >= 1000 ? n.toFixed(1) : n.toFixed(4); - }; - const pct = function(v) { - if (v == null || isNaN(Number(v))) return '—'; - return Math.round(Number(v) * 100) + '%'; - }; - let html = '
'; - html += '
' + (payload.symbol || '') + ' ' - + (payload.timeframe || '') + '
'; - html += '
当前 C' + a.cycle_id + ' ' - + '' - + _wcsLifecycleZh(a.lifecycle) + '
'; - html += '
'; - html += '
结构' - + _wcsStructureZh(a.structure) + '
'; - html += '
阶段' - + (a.phase_candidate - ? ('阶段 ' + a.phase_candidate + '(候选)') - : (a.phase_confirmed ? ('阶段 ' + a.phase_confirmed) : '—')) - + '
'; - html += '
事件' - + (a.event_candidate - ? (_wcsEventZh(a.event_candidate) + '(候选)') - : _wcsEventZh(a.event_confirmed)) - + '
'; - if (a.event_confirmed && a.event_candidate) { - html += '
已确认' - + _wcsEventZh(a.event_confirmed) + '
'; - } - html += '
区间' - + fmtPx(a.range && a.range.low) + ' – ' + fmtPx(a.range && a.range.high) + '
'; - html += '
置信度' - + pct(a.confidence_live != null ? a.confidence_live : a.confidence_confirmed) + '
'; - if (a.next_expected) { - html += '
下一步' - + _wcsEventZh(a.next_expected) + '
'; - } - html += '
'; - if (payload.confirmed_history && payload.confirmed_history.length) { - html += '
已确认历史
'; - payload.confirmed_history.forEach(function(h) { - const ev = (h.events && h.events.length) - ? h.events.map(_wcsEventZh).join('、') - : '—'; - html += '
C' + h.cycle_id + ' ' + _wcsStructureZh(h.structure) + ' · ' + ev + '
'; - }); - html += '
'; - } - html += '
'; - return html; -} - -function renderWyckoffCycleSummary() { - const $el = $('#wyckoffCycleSummary'); - if (!$el.length) return; - if (!currentData) { - $el.hide().empty(); - window.wyckoffCycleSummary = null; - return; - } - const layers = []; - if (_wrLayerTogglesOn('Main') && currentData.wyckoff) { - layers.push({ - key: 'main', - cls: 'wcs-main', - payload: buildWyckoffCycleSummaryPayload( - currentData.wyckoff, - currentData.timeframe || currentData.wyckoff.timeframe || $('#timeframe').val() - ) - }); - } - if (_wrLayerTogglesOn('Element') && currentData.element_wyckoff) { - layers.push({ - key: 'element', - cls: 'wcs-element', - payload: buildWyckoffCycleSummaryPayload( - currentData.element_wyckoff, - currentData.element_timeframe || currentData.element_wyckoff.timeframe || $('#elementTimeframe').val() - ) - }); - } - if (_wrLayerTogglesOn('SubSub') && currentData.sub_sub_wyckoff) { - layers.push({ - key: 'sub_sub', - cls: 'wcs-subsub', - payload: buildWyckoffCycleSummaryPayload( - currentData.sub_sub_wyckoff, - currentData.sub_sub_timeframe || currentData.sub_sub_wyckoff.timeframe || $('#subSubTimeframe').val() - ) - }); - } - const valid = layers.filter(function(L) { return L.payload && L.payload.active; }); - if (!valid.length) { - $el.hide().empty(); - window.wyckoffCycleSummary = null; - return; - } - const bag = {}; - let html = ''; - valid.forEach(function(L) { - bag[L.key] = L.payload; - html += _htmlWyckoffSummaryBlock(L.payload, L.cls); - }); - window.wyckoffCycleSummary = bag; - $el.html(html).show(); -} function loadSymbols() { $.get('/api/symbols', function(data) { diff --git a/web/templates/index.html b/web/templates/index.html index e9d0b69..cc2accc 100644 --- a/web/templates/index.html +++ b/web/templates/index.html @@ -96,81 +96,6 @@ position: relative; z-index: 1; /* 确保图表在数据面板之上 */ } - /* 挂在主图容器内:左下角 = K线主图左下,而非整图(含副图)底边 */ - .wyckoff-cycle-summary { - position: absolute; - left: 8px; - bottom: 28px; /* 略抬高,避开主图时间轴 */ - top: auto; - right: auto; - z-index: 1100; - min-width: 200px; - max-width: 300px; - max-height: calc(100% - 36px); - overflow-y: auto; - padding: 8px 10px; - background: rgba(255, 255, 255, 0.94); - border: 1px solid #d0d7de; - border-radius: 6px; - box-shadow: 0 2px 10px rgba(0,0,0,0.08); - font-size: 12px; - line-height: 1.45; - color: #24292f; - display: none; - pointer-events: auto; - } - .wyckoff-cycle-summary .wcs-block { - padding: 6px 0; - } - .wyckoff-cycle-summary .wcs-block + .wcs-block { - border-top: 1px solid #eaeef2; - margin-top: 6px; - padding-top: 8px; - } - .wyckoff-cycle-summary .wcs-block.wcs-main { border-left: 3px solid #3498db; padding-left: 8px; } - .wyckoff-cycle-summary .wcs-block.wcs-element { border-left: 3px solid #e67e22; padding-left: 8px; } - .wyckoff-cycle-summary .wcs-block.wcs-subsub { border-left: 3px solid #27ae60; padding-left: 8px; } - .wyckoff-cycle-summary .wcs-title { - font-weight: 650; - font-size: 13px; - margin-bottom: 6px; - letter-spacing: 0.02em; - } - .wyckoff-cycle-summary .wcs-row { - display: flex; - justify-content: space-between; - gap: 8px; - margin: 2px 0; - } - .wyckoff-cycle-summary .wcs-k { - color: #656d76; - flex-shrink: 0; - } - .wyckoff-cycle-summary .wcs-v { - text-align: right; - font-variant-numeric: tabular-nums; - } - .wyckoff-cycle-summary .wcs-active { - margin-top: 2px; - padding: 6px 0 4px; - border-top: 1px solid #eaeef2; - } - .wyckoff-cycle-summary .wcs-prev { - margin-top: 6px; - padding-top: 6px; - border-top: 1px dashed #eaeef2; - color: #656d76; - font-size: 11px; - } - .wyckoff-cycle-summary .wcs-badge { - display: inline-block; - padding: 1px 6px; - border-radius: 3px; - background: #ddf4ff; - color: #0969da; - font-weight: 600; - font-size: 11px; - } .chart-options { position: absolute; top: 10px; @@ -1092,22 +1017,6 @@ -
- - -
-
- - -
-
- - -
-
- - -
@@ -1150,22 +1059,6 @@
-
- - -
-
- - -
-
- - -
-
- - -
@@ -1208,22 +1101,6 @@
-
- - -
-
- - -
-
- - -
-
- - -
@@ -1233,7 +1110,6 @@
-