曾研究
找不到符合的股票 · 按
Enter
改問 AI
AI 解讀中
正在理解你的問題…
AI 已解讀
前往 →
提示:打「比較 2330 和 2454」或「殖利率 5% 以上」按
Enter
今日摘要
自選股
警示
AI 研究問答
研究
市場概況
熱力圖
熱門榜單
儀表板
選股條件
策略
ETF 專區
定期定額
ETF 對比
回測列表
策略回測比較
更新紀錄
問題回報
登出
登入
AllocLab
Asset Research Workspace
✕ 關閉
曾研究
每日看
今日摘要
自選股
警示
AI 研究問答
研究
市場概況
熱力圖
熱門榜單
儀表板
選股條件
策略
ETF 專區
定期定額
ETF 對比
回測
策略回測比較
系統設定
更新紀錄
問題回報
登出
登入
回報問題
✕ 關閉
類型
Bug 回報
資料問題
其他意見
意見內容
✅ 回報已送出,感謝你的反饋!
送出回報
送出中…
✨ 更新日誌
✕ 關閉
必須登入才能使用此功能
→ 登入
Taiwan Market Heatmap
台股熱力圖
視圖
產業
個股
-5%
+5%
依據
成交值
等權重
全市場
/
× 返回
載入中…
領漲
偏弱
window.AllocLab = window.AllocLab || {}; AllocLab.initFancyChart = function (containerId, opts) { const el = document.getElementById(containerId); if (!el || !window.LightweightCharts) return null; const data = (opts && opts.data) || []; if (data.length < 2) return null; const LW = window.LightweightCharts; const mode = opts.mode || 'apple'; const volume = opts.volume || null; const support = Array.isArray(opts.support) ? opts.support : []; const resistance = Array.isArray(opts.resistance) ? opts.resistance : []; const unit = opts.unit || 'NT$'; const rangesRoot = opts.rangesRoot || el.parentElement; const legendEl = opts.legendEl || (el.parentElement && el.parentElement.querySelector('.al-chart__legend-value')); const legendDateEl = opts.legendDateEl || (el.parentElement && el.parentElement.querySelector('.al-chart__legend-date')); const tipEl = opts.tooltipEl || (el.parentElement && el.parentElement.querySelector('.al-chart__tooltip')); const first = data[0].value; const last = data[data.length - 1].value; const isUp = last >= first; const mainColor = isUp ? '#c0392b' : '#2a7a50'; const mainTop = isUp ? 'rgba(192,57,43,0.22)' : 'rgba(42,122,80,0.22)'; const ghostTop = isUp ? 'rgba(192,57,43,0.04)' : 'rgba(42,122,80,0.04)'; const ghostLine = isUp ? 'rgba(192,57,43,0.25)' : 'rgba(42,122,80,0.25)'; const chart = LW.createChart(el, { autoSize: true, layout: { background: { type: 'solid', color: '#FFFFFF' }, textColor: 'rgba(39,48,67,0.50)', fontFamily: 'IBM Plex Mono, ui-monospace, monospace', }, grid: { vertLines: { color: 'rgba(39,48,67,0.04)' }, horzLines: { color: 'rgba(39,48,67,0.04)' }, }, crosshair: { mode: LW.CrosshairMode.Normal, vertLine: { color: 'rgba(39,48,67,0.25)', width: 1, labelBackgroundColor: mainColor }, horzLine: { color: 'rgba(39,48,67,0.15)', width: 1, labelBackgroundColor: mainColor }, }, rightPriceScale: { borderColor: 'rgba(39,48,67,0.10)' }, timeScale: { borderColor: 'rgba(39,48,67,0.10)', timeVisible: true, fixRightEdge: true }, handleScroll: false, handleScale: false, }); // 底圖(ghost)— Apple 模式時整條都畫,但色極淡 const ghost = chart.addAreaSeries({ lineColor: ghostLine, topColor: ghostTop, bottomColor: 'rgba(0,0,0,0)', lineWidth: 2, priceLineVisible: false, lastValueVisible: false, crosshairMarkerVisible: false, }); ghost.setData(data); // 高亮(active)— 游標前鮮明、游標後在 apple 模式下會被 setData 截短 const active = chart.addAreaSeries({ lineColor: mainColor, topColor: mainTop, bottomColor: 'rgba(0,0,0,0)', lineWidth: 2, priceLineVisible: false, lastValueVisible: false, crosshairMarkerRadius: 5, crosshairMarkerBorderColor: '#ffffff', crosshairMarkerBackgroundColor: mainColor, }); active.setData(data); // 成交量 histogram(選配) let volSeries = null; if (volume && volume.length) { volSeries = chart.addHistogramSeries({ priceFormat: { type: 'volume' }, priceScaleId: '', scaleMargins: { top: 0.82, bottom: 0 }, }); volSeries.setData(volume.map(function (v) { return { time: v.time, value: v.value, color: 'rgba(39,48,67,0.18)' }; })); } // 支撐 / 壓力線(AI 或人工標記) // 台灣慣例:壓力(resistance)偏多頂 → 紅;支撐(support)偏空底 → 綠 const priceLines = []; support.forEach(function (p) { priceLines.push(active.createPriceLine({ price: p, color: '#2a7a50', lineWidth: 1, lineStyle: LW.LineStyle.Dashed, axisLabelVisible: true, title: '支撐', })); }); resistance.forEach(function (p) { priceLines.push(active.createPriceLine({ price: p, color: '#c0392b', lineWidth: 1, lineStyle: LW.LineStyle.Dashed, axisLabelVisible: true, title: '壓力', })); }); // Legend & tooltip 格式化 function fmtVal(v) { if (v == null || isNaN(v)) return '—'; return unit + ' ' + Number(v).toLocaleString('zh-TW', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } function setLegend(time, value) { if (legendEl) legendEl.textContent = fmtVal(value); if (legendDateEl) legendDateEl.textContent = time || ''; } setLegend(data[data.length - 1].time, last); // Apple 高亮 + tooltip const byTime = new Map(data.map(function (d, i) { return [d.time, i]; })); chart.subscribeCrosshairMove(function (param) { if (!param.time || !param.point || param.point.x < 0 || param.point.y < 0) { if (mode === 'apple') active.setData(data); setLegend(data[data.length - 1].time, last); if (tipEl) tipEl.style.display = 'none'; return; } const idx = byTime.get(param.time); if (idx == null) return; const d = data[idx]; if (mode === 'apple') active.setData(data.slice(0, idx + 1)); setLegend(d.time, d.value); if (tipEl) { tipEl.style.display = 'block'; tipEl.innerHTML = '
' + d.time + '
' + '
' + fmtVal(d.value) + '
'; const bodyRect = el.getBoundingClientRect(); const parentRect = el.parentElement.getBoundingClientRect(); const xOffset = param.point.x + (bodyRect.left - parentRect.left); const yOffset = param.point.y + (bodyRect.top - parentRect.top); const tw = tipEl.offsetWidth; let left = xOffset + 12; if (left + tw > parentRect.width) left = xOffset - tw - 12; tipEl.style.left = left + 'px'; tipEl.style.top = Math.max(0, yOffset - 32) + 'px'; } }); el.addEventListener('mouseleave', function () { if (mode === 'apple') active.setData(data); setLegend(data[data.length - 1].time, last); if (tipEl) tipEl.style.display = 'none'; }); // Range selector(sibling `.al-chart__ranges` 內 button[data-range]) const RANGE_DAYS = { '1M': 30, '3M': 90, '6M': 180, 'YTD': null, '1Y': 365, '2Y': 730, '5Y': 1825, 'MAX': null }; function applyRange(key) { if (!rangesRoot) return; rangesRoot.querySelectorAll('[data-range]').forEach(function (b) { const active = b.dataset.range === key; b.classList.toggle('is-active', active); b.setAttribute('aria-selected', active ? 'true' : 'false'); }); const lastDate = new Date(data[data.length - 1].time); if (key === 'MAX') { chart.timeScale().fitContent(); return; } let from; if (key === 'YTD') { from = new Date(lastDate.getFullYear(), 0, 1); } else { const days = RANGE_DAYS[key]; if (days == null) { chart.timeScale().fitContent(); return; } from = new Date(lastDate); from.setDate(from.getDate() - days); } const firstDate = new Date(data[0].time); if (from < firstDate) from = firstDate; chart.timeScale().setVisibleRange({ from: from.toISOString().slice(0, 10), to: lastDate.toISOString().slice(0, 10), }); } if (rangesRoot) { rangesRoot.querySelectorAll('[data-range]').forEach(function (btn) { btn.addEventListener('click', function () { applyRange(btn.dataset.range); }); }); const initActive = rangesRoot.querySelector('[data-range].is-active'); if (initActive) applyRange(initActive.dataset.range); } return { chart: chart, active: active, ghost: ghost, vol: volSeries, priceLines: priceLines, setRange: applyRange, /* ── F4 SR Chart:setSupport / setResistance(Option B,2026-04-19)───── accepts either: (a) legacy flat array [price, price, ...] (backward compat) (b) metadata array [{price, scale, source, title?}, ...] scale → lineWidth / lineStyle: short → width 1 / Dashed mid → width 1 / Solid long → width 2 / Solid (default / unknown) → width 1 / Dashed round 2 bug fix: 清除責任移至 clearSR(),caller 在重畫前統一呼叫。 setSupport / setResistance 各自只負責 push,不清除。 否則若 caller 先呼叫 setSupport 後呼叫 setResistance, setResistance 清掉 priceLines 就會把支撐線全刪,只剩壓力線。 ──────────────────────────────────────────────────────────────────────── */ clearSR: function () { // 供 caller 在重畫前統一清除所有 SR price lines priceLines.forEach(function (l) { active.removePriceLine(l); }); priceLines.length = 0; }, setSupport: function (levels) { // 不清除(清除責任已移至 clearSR,避免 setResistance 把 setSupport 線也刪掉) (levels || []).forEach(function (item) { var price, lw, ls, title; if (item !== null && typeof item === 'object') { price = item.price; var scale = item.scale || ''; if (scale === 'long') { lw = 2; ls = LW.LineStyle.Solid; } else if (scale === 'mid') { lw = 1; ls = LW.LineStyle.Solid; } else { lw = 1; ls = LW.LineStyle.Dashed; } title = item.title != null ? item.title : '支撐'; } else { price = item; lw = 1; ls = LW.LineStyle.Dashed; title = '支撐'; } priceLines.push(active.createPriceLine({ price: price, color: '#2a7a50', // hardcoded per handoff §3.3 / SKILL.md — LW Charts JS 內 CSS variable 無效 — CLAUDE.md §6 邊緣案例 PASS lineWidth: lw, lineStyle: ls, axisLabelVisible: true, title: title, })); }); }, setResistance: function (levels) { // 不清除(清除責任已移至 clearSR,避免把 setSupport 剛 push 的支撐線刪掉) (levels || []).forEach(function (item) { var price, lw, ls, title; if (item !== null && typeof item === 'object') { price = item.price; var scale = item.scale || ''; if (scale === 'long') { lw = 2; ls = LW.LineStyle.Solid; } else if (scale === 'mid') { lw = 1; ls = LW.LineStyle.Solid; } else { lw = 1; ls = LW.LineStyle.Dashed; } title = item.title != null ? item.title : '壓力'; } else { price = item; lw = 1; ls = LW.LineStyle.Dashed; title = '壓力'; } priceLines.push(active.createPriceLine({ price: price, color: '#c0392b', // hardcoded per handoff §3.3 / SKILL.md — LW Charts JS 內 CSS variable 無效 — CLAUDE.md §6 邊緣案例 PASS lineWidth: lw, lineStyle: ls, axisLabelVisible: true, title: title, })); }); }, destroy: function () { chart.remove(); }, }; };
本平台使用 Cookie 維持登入狀態,並在您同意後啟用分析功能以改善服務。 詳見
Cookie 政策
及
隱私權政策
。
全部接受
僅必要
自訂
選擇要啟用的 Cookie 類型
必要性 Cookie
(Essential)— 登入、Session,無法關閉
分析 Cookie
(Analytics)— Google Analytics 4,幫助改善服務
行銷 Cookie
(Marketing)— 目前未使用
儲存設定