/* 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) { if (Array.isArray(data)) { const $select = $('#symbol'); const currentSymbol = $select.val(); // 保存当前选中的值 $select.empty(); data.forEach(function(symbol) { $select.append($('