Cookie 政策
生效日期:2026-XX-XX(上架日確認前為草稿)
1. 什麼是 Cookie
Cookie 是網站在您的裝置上儲存的小型文字檔案,用於記住您的偏好設定、維持登入狀態
及分析服務使用狀況。本平台也使用瀏覽器 localStorage 儲存 JWT 登入 token 及
Cookie 同意狀態。
2. 必要性 Cookie(Essential)
這些 Cookie 是本平台正常運作所必須,無法 opt-out。
| 名稱 |
用途 |
保留期 |
access_token(localStorage) |
JWT 登入 token,維持登入狀態 |
瀏覽器關閉或手動登出 |
cookie_consent(localStorage) |
記錄 Cookie 同意狀態,避免重複顯示 banner |
12 個月或手動重設 |
| CSRF token(Session) |
防止跨站請求偽造攻擊 |
Session 結束 |
3. 分析 Cookie(Analytics)
這些 Cookie 幫助本平台了解用戶如何使用服務,以便持續改善產品。
需要您的同意(opt-in)才會啟用。
| 服務 |
用途 |
保留期 |
| Google Analytics 4(GA4) |
頁面瀏覽、事件追蹤(匿名化 IP) |
最長 24 個月 |
GA4 於 X4 功能 ship 後啟用。本平台使用 GA4 匿名化 IP 設定,不傳送可識別個人的資料。
4. 功能性 Cookie(Functional)
這些 Cookie 支援特定選用功能,僅在您主動啟用相關功能後才會設定。
| 名稱 |
用途 |
觸發條件 |
tg_bind_state(localStorage) |
記錄 Telegram 推播綁定狀態 |
用戶主動啟用 Telegram 推播 |
5. 第三方 Cookie 清單
-
Google Analytics 4:設定
_ga、_ga_* 等 Cookie。
詳見 Google 隱私權政策。
-
Sentry:如未來啟用 Session Replay,可能設定效能監控 Cookie。
目前僅記錄錯誤事件,不設 Cookie。
6. 如何管理或撤回同意
-
前往 帳號設定,可重新設定 Cookie 同意選項
- 透過瀏覽器設定清除 Cookie 及 localStorage(清除後登入狀態將失效)
-
在大多數瀏覽器中,您可透過「設定」→「隱私和安全」→「Cookie 和其他網站資料」管理 Cookie
停用必要性 Cookie 可能導致登入功能無法正常運作。
如有任何疑問,請聯絡
support@alloclab.tw。
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 =
'