{"id":2125,"date":"2026-03-15T16:53:07","date_gmt":"2026-03-15T16:53:07","guid":{"rendered":"https:\/\/tetsu811.com\/?page_id=2125"},"modified":"2026-03-16T16:17:40","modified_gmt":"2026-03-16T16:17:40","slug":"portfolio","status":"publish","type":"page","link":"https:\/\/tetsu811.com\/ja\/portfolio\/","title":{"rendered":"Tetsu \u7684\u6301\u80a1\u660e\u7d30\uff08\u7d14\u5206\u4eab\uff09"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"2125\" class=\"elementor elementor-2125\">\n\t\t\t\t<div class=\"elementor-element elementor-element-20b7363 e-flex e-con-boxed e-con e-parent\" data-id=\"20b7363\" data-element_type=\"container\" data-e-type=\"container\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-4805de9 elementor-widget elementor-widget-html\" data-id=\"4805de9\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t\t<script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n<script src=\"https:\/\/unpkg.com\/lucide@latest\"><\/script>\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/echarts@5.5.0\/dist\/echarts.min.js\"><\/script>\n<link href=\"https:\/\/fonts.googleapis.com\/css2?family=Noto+Sans+TC:wght@300;400;500;700;900&display=swap\" rel=\"stylesheet\">\n\n<style>\n    .tetsu-portfolio-wrapper {\n        font-family: 'Noto Sans TC', sans-serif;\n        background-color: #050505;\n        color: #ffffff;\n    }\n    .tech-gradient-text {\n        background: linear-gradient(135deg, #3b82f6 0%, #06b6d4 100%);\n        -webkit-background-clip: text;\n        -webkit-text-fill-color: transparent;\n    }\n    .hero-glow {\n        background: radial-gradient(circle at center, rgba(59, 130, 246, 0.15) 0%, rgba(0, 0, 0, 0) 70%);\n    }\n    .glass-table {\n        border-collapse: separate;\n        border-spacing: 0;\n        width: 100%;\n    }\n    .glass-table th {\n        background: rgba(255, 255, 255, 0.03);\n        border-bottom: 1px solid rgba(255, 255, 255, 0.1);\n        color: #9ca3af;\n        font-weight: 600;\n        text-transform: uppercase;\n        font-size: 0.75rem;\n        letter-spacing: 0.05em;\n    }\n    .glass-table td {\n        border-bottom: 1px solid rgba(255, 255, 255, 0.05);\n        transition: background-color 0.2s;\n    }\n    .glass-table tbody tr:hover td {\n        background: rgba(59, 130, 246, 0.05);\n    }\n    .grid-bg {\n        background-image: linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),\n        linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);\n        background-size: 30px 30px;\n    }\n    .table-container::-webkit-scrollbar {\n        height: 6px;\n        width: 6px;\n    }\n    .table-container::-webkit-scrollbar-track {\n        background: #111;\n        border-radius: 3px;\n    }\n    .table-container::-webkit-scrollbar-thumb {\n        background: #333;\n        border-radius: 3px;\n    }\n    #loadingOverlay {\n        backdrop-filter: blur(5px);\n        z-index: 50;\n    }\n\n    \/* \u624b\u6a5f\u7248\u512a\u5316\uff1a\u5361\u7247\u5f0f\u8868\u683c\u8a2d\u8a08 *\/\n    @media (max-width: 768px) {\n        .glass-table, .glass-table tbody, .glass-table tr, .glass-table td {\n            display: block;\n            width: 100%;\n        }\n        .glass-table thead {\n            display: none; \/* \u96b1\u85cf\u50b3\u7d71\u8868\u982d *\/\n        }\n        .glass-table tr {\n            margin-bottom: 1rem;\n            background: rgba(255, 255, 255, 0.02);\n            border: 1px solid rgba(255, 255, 255, 0.05);\n            border-radius: 0.75rem;\n            padding: 0.5rem 0;\n        }\n        .glass-table td {\n            display: flex;\n            justify-content: space-between;\n            align-items: center;\n            border-bottom: 1px solid rgba(255, 255, 255, 0.05);\n            padding: 0.75rem 1.25rem;\n            text-align: right;\n            white-space: normal;\n        }\n        .glass-table td:last-child {\n            border-bottom: none;\n        }\n        .glass-table td::before {\n            content: attr(data-label);\n            font-size: 0.75rem;\n            color: #9ca3af;\n            text-transform: uppercase;\n            font-weight: 600;\n            margin-right: 1rem;\n            text-align: left;\n            white-space: nowrap;\n        }\n        .glass-table td > div {\n            text-align: right; \/* \u78ba\u4fdd\u5167\u90e8\u5143\u7d20\u9760\u53f3\u5c0d\u9f4a *\/\n        }\n    }\n<\/style>\n\n<div class=\"tetsu-portfolio-wrapper antialiased selection:bg-blue-500 selection:text-white relative w-full\" style=\"padding-top: 120px; min-height: 100vh;\">\n\n    <div id=\"loadingOverlay\" class=\"absolute inset-0 bg-black\/80 flex flex-col items-center justify-center transition-opacity duration-500\">\n        <i data-lucide=\"loader-2\" class=\"w-12 h-12 text-blue-500 animate-spin mb-4\"><\/i>\n        <p class=\"text-blue-400 font-medium tracking-widest animate-pulse\">\u6b63\u5728\u540c\u6b65 Google Sheet \u96f2\u7aef\u8cc7\u6599...<\/p>\n    <\/div>\n\n    <section class=\"relative pt-12 pb-12 overflow-hidden grid-bg border-b border-white\/5\">\n        <div class=\"absolute inset-0 hero-glow pointer-events-none\"><\/div>\n        <div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 relative z-10\">\n            <div class=\"inline-flex items-center px-4 py-2 rounded-full border border-blue-500\/30 bg-blue-500\/10 text-blue-400 text-sm font-medium mb-6\">\n                <span class=\"flex h-2 w-2 rounded-full bg-blue-400 mr-2 animate-pulse\"><\/span>\n                \u5be6\u6230\u7e3e\u6548\u900f\u660e\u516c\u958b\n            <\/div>\n            <h1 class=\"text-4xl md:text-5xl font-black tracking-tight mb-4\">\n                \u5168\u7403\u5e02\u5834 <span class=\"tech-gradient-text\">\u8cc7\u7522\u914d\u7f6e\u8207\u6301\u80a1\u8ffd\u8e64<\/span>\n            <\/h1>\n            <p class=\"text-xl text-gray-400 max-w-2xl\">\n                \u6db5\u84cb\u53f0\u80a1\u3001\u7f8e\u80a1\u8207\u65e5\u80a1\u90e8\u4f4d\uff0c\u4ee5\u91cf\u5316+\u8cea\u5316\u6578\u64da\u7684\u65b9\u5f0f\u57f7\u884c\u64cd\u76e4<br>\n\u8cc7\u6599\u4e0d\u4e00\u5b9a\u6bcf\u65e5\u66f4\u65b0\uff0c\u723d\u7684\u6642\u5019\u624d\u66f4\u65b0<br>\n\u6c92\u6709\u8cc7\u6599\u7684\u8cc7\u6599\u5c31\u662f\u9577\u671f\u5b58\u80a1\uff0c\u57fa\u672c\u4e0a\u5831\u916c\u90fd\u8d85\u904e10\u500d<br>\n            <\/p>\n        <\/div>\n    <\/section>\n\n    <section class=\"py-12 bg-[#050505]\">\n        <div class=\"max-w-7xl mx-auto px-4 sm:px-6 lg:px-8\">\n            \n            <div class=\"grid lg:grid-cols-3 gap-8 mb-16\">\n                \n                <div class=\"lg:col-span-2 bg-gray-900\/50 rounded-2xl border border-gray-800 p-6 shadow-2xl relative\">\n                    <h2 class=\"text-xl font-bold mb-2 flex items-center\">\n                        <i data-lucide=\"pie-chart\" class=\"w-5 h-5 mr-2 text-blue-500\"><\/i>\n                        \u7576\u524d\u6301\u5009\u6bd4\u4f8b\u914d\u7f6e (\u65ed\u65e5\u5716)\n                    <\/h2>\n                    <p class=\"text-sm text-gray-500 mb-4\">\u7531\u5167\u800c\u5916\uff1a\u5e02\u5834\u5206\u4f48 \u2794 \u7522\u696d\u677f\u584a \u2794 \u5177\u9ad4\u6a19\u7684<\/p>\n                    <div id=\"sunburstChart\" class=\"w-full h-[400px]\"><\/div>\n                <\/div>\n\n                <div class=\"flex flex-col gap-6\">\n                    <div class=\"bg-gradient-to-br from-blue-900\/20 to-transparent rounded-2xl border border-blue-500\/30 p-6 shadow-lg\">\n                        <div class=\"text-sm text-gray-400 mb-1\">\u6295\u8cc7\u7d44\u5408\u72c0\u614b<\/div>\n                        <div class=\"text-2xl font-bold text-white mb-4\">\u96f2\u7aef\u540c\u6b65\u5b8c\u6210<\/div>\n                        \n                        <div class=\"space-y-4 mt-6\">\n                            <div class=\"flex justify-between items-center border-b border-white\/5 pb-2\">\n                                <span class=\"text-gray-400\">\u76ee\u524d\u6d3b\u8e8d\u6301\u80a1\u6578<\/span>\n                                <span class=\"font-bold text-blue-400\" id=\"summaryActive\">0 \u6a94<\/span>\n                            <\/div>\n                            <div class=\"flex justify-between items-center border-b border-white\/5 pb-2\">\n                                <span class=\"text-gray-400\">\u5df2\u51fa\u6e05\u7372\u5229\u6a19\u7684<\/span>\n                                <span class=\"font-bold text-emerald-400\" id=\"summaryClosed\">0 \u6a94<\/span>\n                            <\/div>\n                            <div class=\"flex justify-between items-center\">\n                                <span class=\"text-gray-400\">\u6700\u5f8c\u66f4\u65b0\u6642\u9593<\/span>\n                                <span class=\"text-xs text-gray-500\" id=\"lastUpdated\">-<\/span>\n                            <\/div>\n                        <\/div>\n                    <\/div>\n\n                    <div class=\"bg-gray-900\/50 rounded-2xl border border-gray-800 p-5 flex-1 flex flex-col relative shadow-lg\">\n                        <h3 class=\"text-sm font-bold text-gray-300 mb-2 flex items-center\">\n                            <i data-lucide=\"trending-up\" class=\"w-4 h-4 mr-2 text-blue-500\"><\/i>\n                            \u7d2f\u7a4d\u640d\u76ca\u8da8\u52e2 (\u5df2\u5be6\u73fe + \u672a\u5be6\u73fe)\n                        <\/h3>\n                        <div id=\"pnlChart\" class=\"w-full flex-1 min-h-[180px]\"><\/div>\n                    <\/div>\n                <\/div>\n            <\/div>\n\n            <div class=\"mb-8 border-b border-gray-800\">\n                <nav class=\"flex space-x-8\" aria-label=\"Tabs\" id=\"marketTabs\"><\/nav>\n            <\/div>\n\n            <!-- \u6539\u70ba\u4e0a\u4e0b\u6392\u7248 (flex-col) \u4e26\u4e14\u52a0\u5927\u9593\u8ddd -->\n            <div class=\"flex flex-col gap-12\">\n                <!-- \u76ee\u524d\u6301\u80a1 Table -->\n                <div class=\"bg-gray-900\/40 rounded-2xl border border-gray-800 overflow-hidden flex flex-col\">\n                    <div class=\"p-5 border-b border-gray-800 bg-gray-900\/80 flex justify-between items-center\">\n                        <h3 class=\"text-lg font-bold flex items-center\">\n                            <i data-lucide=\"briefcase\" class=\"w-5 h-5 mr-2 text-cyan-400\"><\/i>\n                            \u76ee\u524d\u6301\u80a1 (Active Positions)\n                        <\/h3>\n                        <span class=\"px-2 py-1 bg-cyan-500\/10 text-cyan-400 text-xs rounded border border-cyan-500\/20\" id=\"activeCount\">0<\/span>\n                    <\/div>\n                    <!-- \u4f7f\u7528 max-h \u9650\u5236\u6700\u5927\u9ad8\u5ea6\uff0c\u5167\u5bb9\u904e\u591a\u6642\u53ef\u5167\u90e8\u6efe\u52d5\uff0c\u907f\u514d\u6490\u7206\u6574\u500b\u7db2\u9801 -->\n                    <div class=\"table-container overflow-x-hidden md:overflow-x-auto flex-1 max-h-[600px] overflow-y-auto p-4 md:p-0\">\n                        <table class=\"glass-table text-left whitespace-nowrap w-full\">\n                            <thead class=\"sticky top-0 z-10 hidden md:table-header-group\">\n                                <tr>\n                                    <th class=\"px-6 py-4\">\u6a19\u7684 (Ticker)<\/th>\n                                    <th class=\"px-6 py-4\">\u5e02\u5834<\/th>\n                                    <th class=\"px-6 py-4\">\u8cb7\u9032\u65e5\u671f<\/th>\n                                    <th class=\"px-6 py-4 text-right\">\u4f54\u6bd4<\/th>\n                                    <th class=\"px-6 py-4 text-right\">\u76ee\u524d\u7e3e\u6548<\/th>\n                                <\/tr>\n                            <\/thead>\n                            <tbody id=\"activeTableBody\"><\/tbody>\n                        <\/table>\n                    <\/div>\n                <\/div>\n\n                <!-- \u5df2\u51fa\u6e05 Table -->\n                <div class=\"bg-gray-900\/40 rounded-2xl border border-gray-800 overflow-hidden flex flex-col\">\n                    <div class=\"p-5 border-b border-gray-800 bg-gray-900\/80 flex justify-between items-center\">\n                        <h3 class=\"text-lg font-bold flex items-center\">\n                            <i data-lucide=\"history\" class=\"w-5 h-5 mr-2 text-gray-400\"><\/i>\n                            \u5df2\u51fa\u6e05 (Closed Positions)\n                        <\/h3>\n                        <span class=\"px-2 py-1 bg-gray-800 text-gray-400 text-xs rounded border border-gray-700\" id=\"closedCount\">0<\/span>\n                    <\/div>\n                    <!-- \u4f7f\u7528 max-h \u9650\u5236\u6700\u5927\u9ad8\u5ea6 -->\n                    <div class=\"table-container overflow-x-hidden md:overflow-x-auto flex-1 max-h-[600px] overflow-y-auto p-4 md:p-0\">\n                        <table class=\"glass-table text-left whitespace-nowrap w-full\">\n                            <thead class=\"sticky top-0 z-10 hidden md:table-header-group\">\n                                <tr>\n                                    <th class=\"px-6 py-4\">\u6a19\u7684 (Ticker)<\/th>\n                                    <th class=\"px-6 py-4\">\u8cb7\u9032\u65e5\u671f<\/th>\n                                    <th class=\"px-6 py-4\">\u8ce3\u51fa\u65e5\u671f<\/th>\n                                    <th class=\"px-6 py-4 text-right\">\u6700\u7d42\u7e3e\u6548<\/th>\n                                <\/tr>\n                            <\/thead>\n                            <tbody id=\"closedTableBody\"><\/tbody>\n                        <\/table>\n                    <\/div>\n                <\/div>\n            <\/div>\n        <\/div>\n    <\/section>\n<\/div>\n\n<script>\n    let appData = [];\n    let currentFilter = '\u5168\u90e8 (All)';\n    const API_URL = 'https:\/\/script.google.com\/macros\/s\/AKfycbzH6TnlFcZ7aIjSuH-pN8BXbEmsLUw7Xvv53Nk7rjkS5UNERA3L8y1KPmBwA_6EWUXT\/exec';\n\n    document.addEventListener('DOMContentLoaded', () => {\n         if (typeof lucide !== 'undefined') lucide.createIcons();\n         document.getElementById('lastUpdated').innerText = new Date().toLocaleDateString('zh-TW');\n         fetchAndRenderData();\n    });\n\n    async function fetchAndRenderData() {\n        try {\n            const response = await fetch(API_URL);\n            if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);\n            \n            const rawData = await response.json();\n            \n            appData = rawData.map(row => {\n                const cleanRow = {};\n                for (let key in row) {\n                    if (key) {\n                        const cleanKey = key.replace(\/^\\uFEFF\/, '').trim().toLowerCase();\n                        cleanRow[cleanKey] = row[key];\n                    }\n                }\n\n                const getValue = (keys, defaultValue) => {\n                    for (let k of keys) {\n                        if (cleanRow[k] !== undefined && cleanRow[k] !== '') return String(cleanRow[k]).trim();\n                    }\n                    return defaultValue;\n                };\n\n                const allocRaw = getValue(['\u4f54\u6bd4', 'allocation'], '0');\n                const returnRaw = getValue(['\u7e3e\u6548', 'return'], '0');\n\n                return {\n                    market: getValue(['\u5e02\u5834', 'market'], '\u672a\u5206\u985e'),\n                    sector: getValue(['\u677f\u584a', '\u7522\u696d', 'sector'], '\u672a\u5206\u985e'),\n                    ticker: getValue(['\u6a19\u7684', '\u4ee3\u865f', 'ticker'], '-'),\n                    name: getValue(['\u540d\u7a31', 'name'], '-'),\n                    status: getValue(['\u72c0\u614b', 'status'], '\u6301\u6709\u4e2d'),\n                    alloc: parseFloat(String(allocRaw).replace(\/[%$,]\/g, '')),\n                    buyDate: getValue(['\u8cb7\u9032\u65e5\u671f', 'buy date'], '-'),\n                    sellDate: getValue(['\u8ce3\u51fa\u65e5\u671f', 'sell date'], '-'),\n                    return: parseFloat(String(returnRaw).replace(\/[%$,]\/g, ''))\n                };\n            }).filter(item => item.ticker !== '-');\n\n            document.getElementById('loadingOverlay').style.opacity = '0';\n            setTimeout(() => {\n                document.getElementById('loadingOverlay').style.display = 'none';\n            }, 500);\n\n            initDashboard();\n            \n        } catch (error) {\n            document.getElementById('loadingOverlay').innerHTML = `\n                <i data-lucide=\"alert-circle\" class=\"w-12 h-12 text-red-500 mb-4\"><\/i>\n                <p class=\"text-red-400 font-medium\">\u8cc7\u6599\u540c\u6b65\u5931\u6557\uff0c\u8acb\u78ba\u8a8d API \u72c0\u614b\u3002<\/p>\n            `;\n            if (typeof lucide !== 'undefined') lucide.createIcons();\n        }\n    }\n\n    function initDashboard() {\n        renderTabs();\n        renderTables();\n        initSunburstChart();\n        initPnlChart();\n    }\n\n    function renderTabs() {\n        const uniqueMarkets = [...new Set(appData.map(item => item.market))].filter(m => m !== '\u672a\u5206\u985e');\n        const markets = ['\u5168\u90e8 (All)', ...uniqueMarkets];\n        const tabContainer = document.getElementById('marketTabs');\n        tabContainer.innerHTML = '';\n\n        markets.forEach(market => {\n            const isActive = market === currentFilter;\n            const btn = document.createElement('button');\n            btn.className = `whitespace-nowrap py-4 px-1 border-b-2 font-medium text-sm transition-colors ${\n                isActive ? 'border-blue-500 text-blue-400' : 'border-transparent text-gray-400 hover:text-gray-300 hover:border-gray-700'\n            }`;\n            btn.innerText = market;\n            btn.onclick = () => {\n                currentFilter = market;\n                renderTabs(); \n                renderTables(); \n            };\n            tabContainer.appendChild(btn);\n        });\n    }\n\n    function formatReturn(val) {\n        if (isNaN(val)) return `<span class=\"text-gray-400\">-<\/span>`;\n        if (val > 0) return `<span class=\"text-emerald-400 font-medium\">+${val.toFixed(2)}%<\/span>`;\n        if (val < 0) return `<span class=\"text-rose-400 font-medium\">${val.toFixed(2)}%<\/span>`;\n        return `<span class=\"text-gray-400\">0.00%<\/span>`;\n    }\n\n    function renderTables() {\n        const activeTable = document.getElementById('activeTableBody');\n        const closedTable = document.getElementById('closedTableBody');\n        activeTable.innerHTML = '';\n        closedTable.innerHTML = '';\n        let activeCount = 0;\n        let closedCount = 0;\n\n        appData.forEach(item => {\n            if (currentFilter !== '\u5168\u90e8 (All)' && item.market !== currentFilter) return;\n\n            if (item.status === '\u6301\u6709\u4e2d' || item.status === 'Active') {\n                activeCount++;\n                activeTable.innerHTML += `\n                    <tr>\n                        <td class=\"px-6 py-4 md:table-cell\" data-label=\"\u6a19\u7684 (Ticker)\">\n                            <div class=\"font-bold text-white\">${item.ticker}<\/div>\n                            <div class=\"text-xs text-gray-500\">${item.name}<\/div>\n                        <\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-sm text-gray-300\" data-label=\"\u5e02\u5834\">${item.market}<\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-sm text-gray-400\" data-label=\"\u8cb7\u9032\u65e5\u671f\">${item.buyDate}<\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-right text-sm text-gray-300\" data-label=\"\u4f54\u6bd4\">${isNaN(item.alloc) ? 0 : item.alloc}%<\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-right\" data-label=\"\u76ee\u524d\u7e3e\u6548\">${formatReturn(item.return)}<\/td>\n                    <\/tr>\n                `;\n            } else if (item.status === '\u5df2\u51fa\u6e05' || item.status === 'Closed') {\n                closedCount++;\n                closedTable.innerHTML += `\n                    <tr>\n                        <td class=\"px-6 py-4 md:table-cell\" data-label=\"\u6a19\u7684 (Ticker)\">\n                            <div class=\"font-bold text-gray-400 line-through\">${item.ticker}<\/div>\n                            <div class=\"text-xs text-gray-600\">${item.name}<\/div>\n                        <\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-sm text-gray-500\" data-label=\"\u8cb7\u9032\u65e5\u671f\">${item.buyDate}<\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-sm text-gray-400\" data-label=\"\u8ce3\u51fa\u65e5\u671f\">${item.sellDate}<\/td>\n                        <td class=\"px-6 py-4 md:table-cell text-right\" data-label=\"\u6700\u7d42\u7e3e\u6548\">${formatReturn(item.return)}<\/td>\n                    <\/tr>\n                `;\n            }\n        });\n\n        document.getElementById('activeCount').innerText = activeCount;\n        document.getElementById('closedCount').innerText = closedCount;\n        document.getElementById('summaryActive').innerText = `${activeCount} \u6a94`;\n        document.getElementById('summaryClosed').innerText = `${closedCount} \u6a94`;\n\n        if (activeCount === 0) activeTable.innerHTML = `<tr><td colspan=\"5\" class=\"px-6 py-8 text-center text-gray-600\">\u7121\u7b26\u5408\u7684\u6301\u80a1\u8cc7\u6599<\/td><\/tr>`;\n        if (closedCount === 0) closedTable.innerHTML = `<tr><td colspan=\"4\" class=\"px-6 py-8 text-center text-gray-600\">\u7121\u7b26\u5408\u7684\u51fa\u6e05\u8cc7\u6599<\/td><\/tr>`;\n    }\n\n    function initSunburstChart() {\n        const chartDom = document.getElementById('sunburstChart');\n        if (echarts.getInstanceByDom(chartDom)) echarts.dispose(chartDom);\n        const myChart = echarts.init(chartDom);\n\n        const treeData = [];\n        const activeHoldings = appData.filter(d => (d.status === '\u6301\u6709\u4e2d' || d.status === 'Active') && d.alloc > 0);\n        const marketGroups = {};\n        \n        activeHoldings.forEach(item => {\n            if (!marketGroups[item.market]) marketGroups[item.market] = {};\n            if (!marketGroups[item.market][item.sector]) marketGroups[item.market][item.sector] = [];\n            marketGroups[item.market][item.sector].push(item);\n        });\n\n        const marketColors = {\n            '\u7f8e\u80a1': '#3b82f6',\n            '\u53f0\u80a1': '#06b6d4',\n            '\u65e5\u80a1': '#8b5cf6',\n            'US': '#3b82f6',\n            'TW': '#06b6d4',\n            'JP': '#8b5cf6'\n        };\n\n        for (const market in marketGroups) {\n            const marketNode = {\n                name: market,\n                itemStyle: { color: marketColors[market] || '#64748b' },\n                children: []\n            };\n\n            for (const sector in marketGroups[market]) {\n                const sectorNode = { name: sector, children: [] };\n                marketGroups[market][sector].forEach(stock => {\n                    sectorNode.children.push({\n                        name: stock.ticker,\n                        value: stock.alloc,\n                        label: { formatter: '{b}\\n{c}%' } \n                    });\n                });\n                marketNode.children.push(sectorNode);\n            }\n            treeData.push(marketNode);\n        }\n\n        const option = {\n            tooltip: {\n                trigger: 'item',\n                backgroundColor: 'rgba(0,0,0,0.8)',\n                borderColor: '#333',\n                textStyle: { color: '#fff' },\n                formatter: function (info) {\n                    const value = info.value;\n                    const name = info.name;\n                    return value ? `${name} : ${value}%` : name;\n                }\n            },\n            series: {\n                type: 'sunburst',\n                data: treeData,\n                radius: [0, '80%'], \/\/ \u4fee\u6539\u6b64\u8655\uff1a\u5c07\u534a\u5f91\u7e2e\u5c0f\u70ba 80% \u4ee5\u7559\u51fa\u908a\u8ddd\u7d66\u6587\u5b57\n                sort: null,\n                emphasis: { focus: 'ancestor' },\n                itemStyle: { borderRadius: 4, borderWidth: 2, borderColor: '#050505' },\n                label: { color: '#ffffff', textBorderColor: '#000', textBorderWidth: 2 },\n                levels: [\n                    {}, \n                    { r0: '10%', r: '30%', itemStyle: { borderWidth: 2 }, label: { rotate: 'tangential', fontSize: 14, fontWeight: 'bold' } }, \/\/ \u76f8\u5c0d\u61c9\u8abf\u6574\u5167\u5c64\u6bd4\u4f8b\n                    { r0: '30%', r: '60%', label: { align: 'right' }, itemStyle: { opacity: 0.8 } },\n                    { r0: '60%', r: '80%', label: { position: 'outside', padding: 3, silent: false, color: '#9ca3af' }, itemStyle: { opacity: 0.6 } }\n                ]\n            }\n        };\n\n        myChart.setOption(option);\n        window.addEventListener('resize', () => myChart.resize());\n    }\n\n    function initPnlChart() {\n        const chartDom = document.getElementById('pnlChart');\n        if (!chartDom) return;\n        if (echarts.getInstanceByDom(chartDom)) echarts.dispose(chartDom);\n        const myChart = echarts.init(chartDom);\n\n        let earliestDate = new Date();\n        appData.forEach(item => {\n            if (item.buyDate && item.buyDate !== '-') {\n                const d = new Date(item.buyDate);\n                if (!isNaN(d.getTime()) && d < earliestDate) earliestDate = d;\n            }\n        });\n\n        const today = new Date();\n        if (earliestDate >= today || isNaN(earliestDate.getTime())) {\n            earliestDate = new Date();\n            earliestDate.setMonth(earliestDate.getMonth() - 6);\n        }\n\n        const timeline = [];\n        let curr = new Date(earliestDate);\n        curr.setDate(1);\n        while (curr <= today) {\n            timeline.push(new Date(curr));\n            curr.setMonth(curr.getMonth() + 1);\n        }\n        timeline.push(new Date(today));\n\n        const dataPoints = [];\n        const seenDates = new Set();\n\n        timeline.forEach(t => {\n            let totalPnL = 0;\n            appData.forEach(item => {\n                if (item.buyDate === '-' || isNaN(item.return)) return;\n                const buyD = new Date(item.buyDate);\n                if (isNaN(buyD.getTime()) || buyD > t) return;\n\n                const msPerDay = 1000 * 60 * 60 * 24;\n\n                if (item.status === '\u5df2\u51fa\u6e05' || item.status === 'Closed') {\n                    let sellD = today;\n                    if (item.sellDate && item.sellDate !== '-') sellD = new Date(item.sellDate);\n\n                    if (t >= sellD) {\n                        totalPnL += item.return;\n                    } else {\n                        const totalDays = Math.max((sellD - buyD) \/ msPerDay, 1);\n                        const elapsedDays = (t - buyD) \/ msPerDay;\n                        totalPnL += item.return * (elapsedDays \/ totalDays);\n                    }\n                } else {\n                    const totalDays = Math.max((today - buyD) \/ msPerDay, 1);\n                    const elapsedDays = (t - buyD) \/ msPerDay;\n                    totalPnL += item.return * (elapsedDays \/ totalDays);\n                }\n            });\n\n            const dateStr = `${t.getFullYear()}-${String(t.getMonth() + 1).padStart(2, '0')}`;\n            if (!seenDates.has(dateStr) || t.getTime() === today.getTime()) {\n                let finalDateStr = dateStr;\n                if (t.getTime() === today.getTime()) finalDateStr = '\u4eca\u65e5';\n                seenDates.add(finalDateStr);\n                dataPoints.push([finalDateStr, parseFloat(totalPnL.toFixed(2))]);\n            }\n        });\n\n        const option = {\n            tooltip: {\n                trigger: 'axis',\n                backgroundColor: 'rgba(0,0,0,0.8)',\n                borderColor: '#333',\n                textStyle: { color: '#fff', fontSize: 12 },\n                formatter: function (params) {\n                    const val = params[0].value[1];\n                    const color = val >= 0 ? '#34d399' : '#f87171';\n                    const sign = val > 0 ? '+' : '';\n                    return `${params[0].value[0]}<br\/>\u7d2f\u7a4d\u7e3d\u7e3e\u6548: <span style=\"color:${color};font-weight:bold;\">${sign}${val}%<\/span>`;\n                }\n            },\n            grid: { top: 10, bottom: 20, left: 45, right: 15 },\n            xAxis: {\n                type: 'category',\n                boundaryGap: false,\n                axisLine: { lineStyle: { color: '#374151' } },\n                axisLabel: { color: '#9ca3af', fontSize: 10 }\n            },\n            yAxis: {\n                type: 'value',\n                splitLine: { lineStyle: { color: '#1f2937', type: 'dashed' } },\n                axisLabel: { color: '#9ca3af', fontSize: 10, formatter: '{value}%' }\n            },\n            series: [{\n                data: dataPoints,\n                type: 'line',\n                smooth: true,\n                symbol: 'circle',\n                symbolSize: 4,\n                showSymbol: false,\n                lineStyle: { color: '#3b82f6', width: 3 },\n                areaStyle: {\n                    color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [\n                        { offset: 0, color: 'rgba(59, 130, 246, 0.4)' },\n                        { offset: 1, color: 'rgba(59, 130, 246, 0.0)' }\n                    ])\n                }\n            }]\n        };\n\n        myChart.setOption(option);\n        window.addEventListener('resize', () => myChart.resize());\n    }\n<\/script>\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>\u6b63\u5728\u540c\u6b65 Google Sheet \u96f2\u7aef\u8cc7\u6599&#8230; \u5be6\u6230\u7e3e\u6548\u900f\u660e\u516c\u958b \u5168\u7403\u5e02\u5834 \u8cc7\u7522\u914d\u7f6e\u8207\u6301\u80a1\u8ffd\u8e64 \u6db5\u84cb\u53f0\u80a1\u3001\u7f8e\u80a1\u8207\u65e5\u80a1\u90e8\u4f4d\uff0c\u4ee5\u91cf\u5316+\u8cea\u5316\u6578\u64da\u7684\u65b9\u5f0f\u57f7\u884c\u64cd\u76e4 \u8cc7\u6599\u4e0d\u4e00\u5b9a\u6bcf\u65e5\u66f4\u65b0\uff0c\u723d\u7684\u6642\u5019\u624d\u66f4\u65b0 \u6c92\u6709\u8cc7\u6599\u7684\u8cc7\u6599\u5c31\u662f [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"_gspb_post_css":"","footnotes":""},"class_list":["post-2125","page","type-page","status-publish","hentry"],"blocksy_meta":[],"_links":{"self":[{"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/pages\/2125","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/comments?post=2125"}],"version-history":[{"count":13,"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/pages\/2125\/revisions"}],"predecessor-version":[{"id":2144,"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/pages\/2125\/revisions\/2144"}],"wp:attachment":[{"href":"https:\/\/tetsu811.com\/ja\/wp-json\/wp\/v2\/media?parent=2125"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}