javascript: (function () {
  if (!/^https:\/\/(?:dev\.)?next\.lude\.site\/rotation\/(?:convertible-bond|etf|stock)(?:[/?#].*)?$/.test(location.href)) return;

  const BUILD_TAG = 'monthly-heatmap-upload-20260419-v010';
  const PANEL_ID = 'lude-monthly-heatmap-panel';
  const BUTTON_ID = 'lude-monthly-heatmap-button';
  const STYLE_ID = 'lude-monthly-heatmap-style';
  const SELECT_ID = 'lude-monthly-heatmap-select';
  const TAB_LABELS = {
    monthly: '月度收益图',
    annual: '年度分析'
  };
  const FIELD_LABELS = {
    strategy: '策略月度收益',
    benchmark: '基准月度收益',
    excess: '超额月度收益'
  };
  const THEME = {
    primary: '#2563eb',
    primaryLight: '#dbeafe',
    primarySoft: '#eff6ff',
    primaryBorder: '#93c5fd',
    primaryText: '#1d4ed8',
    primaryBarPositive: 'rgba(211,47,47,0.85)',
    primaryBarNegative: 'rgba(39,174,96,0.85)',
    primaryBarText: '#334155',
    drawdownGreen: '#16a34a'
  };

  if (window.__LUDE_MONTHLY_HEATMAP_UPLOAD__ === BUILD_TAG) {
    return;
  }
  window.__LUDE_MONTHLY_HEATMAP_UPLOAD__ = BUILD_TAG;

  const state = {
    monthlyReturns: [],
    yearlyMetrics: [],
    benchKey: '',
    selectedField: 'strategy',
    activeTab: 'monthly',
    lastSignature: '',
    panelOpen: false,
    top: '',
    left: '',
    bottom: '80px',
    right: '20px',
    width: ''
  };

  function showToast(message, isError = false) {
    const toast = document.createElement('div');
    toast.style.cssText = `
      position: fixed;
      bottom: 20px;
      right: 20px;
      background: ${isError ? '#e74c3c' : '#323232'};
      color: #fff;
      padding: 12px 20px;
      border-radius: 6px;
      font-size: 14px;
      z-index: 999999;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
      transition: opacity 0.3s;
      font-family: sans-serif;
    `;
    toast.innerText = message;
    document.body.appendChild(toast);
    setTimeout(() => {
      toast.style.opacity = '0';
      setTimeout(() => toast.remove(), 300);
    }, 2500);
  }

  function ensureStyle() {
    if (document.getElementById(STYLE_ID)) return;
    const style = document.createElement('style');
    style.id = STYLE_ID;
    style.innerHTML = `
      #${BUTTON_ID} {
        position: fixed;
        right: 20px;
        top: 50%;
        transform: translateY(-50%);
        z-index: 999998;
        border: none;
        background: linear-gradient(135deg, #2563eb 0%, #60a5fa 100%);
        color: #fff;
        border-radius: 999px;
        padding: 10px 14px;
        font-size: 13px;
        font-weight: 600;
        cursor: pointer;
        box-shadow: 0 10px 24px rgba(37, 99, 235, 0.28);
      }
      #${PANEL_ID} {
        position: fixed;
        background: #fff;
        border: 1px solid #bfdbfe;
        border-radius: 8px;
        box-shadow: 0 10px 28px rgba(37, 99, 235, 0.16);
        z-index: 999999;
        font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
        color: #334155;
        display: flex;
        flex-direction: column;
        max-height: 80vh;
        max-width: calc(100vw - 24px);
        box-sizing: border-box;
        overflow: hidden;
        resize: horizontal;
      }
      #${PANEL_ID} * {
        box-sizing: border-box;
      }
    `;
    document.head.appendChild(style);
  }

  function ensureButton() {
    ensureStyle();
    let button = document.getElementById(BUTTON_ID);
    if (!button) {
      button = document.createElement('button');
      button.id = BUTTON_ID;
      button.textContent = '月度收益图';
      button.addEventListener('click', () => {
        const panel = document.getElementById(PANEL_ID);
        if (panel) {
          capturePanelFrame(panel);
          panel.remove();
          state.panelOpen = false;
        } else {
          renderPanel();
        }
      });
      document.body.appendChild(button);
    }
    return button;
  }

  function makeDraggable(element, handle) {
    let prevX = 0;
    let prevY = 0;
    let startX = 0;
    let startY = 0;

    handle.onmousedown = onMouseDown;

    function onMouseDown(event) {
      if (event.target.closest('button') || event.target.closest('select')) return;
      event.preventDefault();
      startX = event.clientX;
      startY = event.clientY;

      const rect = element.getBoundingClientRect();
      element.style.left = rect.left + 'px';
      element.style.top = rect.top + 'px';
      element.style.bottom = 'auto';
      element.style.right = 'auto';

      document.onmouseup = stopDragging;
      document.onmousemove = drag;
    }

    function drag(event) {
      event.preventDefault();
      prevX = startX - event.clientX;
      prevY = startY - event.clientY;
      startX = event.clientX;
      startY = event.clientY;
      element.style.top = element.offsetTop - prevY + 'px';
      element.style.left = element.offsetLeft - prevX + 'px';
    }

    function stopDragging() {
      capturePanelFrame(element);
      document.onmouseup = null;
      document.onmousemove = null;
    }
  }

  function capturePanelFrame(element) {
    const rect = element.getBoundingClientRect();
    state.width = `${Math.round(rect.width)}px`;
    if (element.style.top || element.style.left) {
      state.top = element.style.top || `${Math.round(rect.top)}px`;
      state.left = element.style.left || `${Math.round(rect.left)}px`;
      state.bottom = 'auto';
      state.right = 'auto';
      return;
    }

    state.top = '';
    state.left = '';
    state.bottom = element.style.bottom || state.bottom;
    state.right = element.style.right || state.right;
  }

  function extractTimelinesFromBacktestText(text) {
    let timelines = null;

    try {
      const parsed = JSON.parse(text);
      timelines = parsed?.timelines || parsed?.data?.timelines || parsed?.backtest?.timelines || null;
      if (timelines?.length) return timelines;
    } catch (error) {}

    try {
      const arrayMatch = text.match(/timelines=(\[[\s\S]*?\])(?=\n|$)/);
      if (arrayMatch?.[1]) {
        timelines = JSON.parse(arrayMatch[1]);
        if (timelines?.length) return timelines;
      }
    } catch (error) {}

    const lines = text.split('\n');
    for (const line of lines) {
      if (!line.includes('timelines')) continue;
      const cleanLine = line.replace(/^data:\s*/, '').trim();
      const jsonMatch = cleanLine.match(/\{[\s\S]*\}/);
      if (!jsonMatch) continue;
      try {
        const parsed = JSON.parse(jsonMatch[0]);
        timelines = parsed?.timelines || parsed?.data?.timelines || parsed?.backtest?.timelines || null;
        if (timelines?.length) return timelines;
      } catch (error) {}
    }

    return null;
  }

  function detectBenchKey(sample) {
    if (!sample) return 'benchmark_equity';
    for (const key of Object.keys(sample)) {
      const lower = key.toLowerCase();
      if (lower.includes('benchmark') || lower.includes('bench') || lower.includes('hs300')) {
        return key;
      }
    }
    return 'benchmark_equity';
  }

  function detectBenchReturnKey(sample, benchKey) {
    if (!sample) return 'time_return_benchmark';
    const keys = Object.keys(sample);
    const lowerBenchKey = String(benchKey || '').toLowerCase();
    const preferredCandidates = [
      'time_return_benchmark',
      benchKey.replace(/equity/ig, 'time_return'),
      benchKey.replace(/benchmark_equity/ig, 'time_return_benchmark'),
      benchKey.replace(/equity_benchmark/ig, 'time_return_benchmark')
    ].filter(Boolean);

    for (const candidate of preferredCandidates) {
      if (keys.includes(candidate)) return candidate;
    }

    const benchTokens = lowerBenchKey.split(/[^a-z0-9]+/).filter((token) => token && token !== 'equity');
    for (const key of keys) {
      const lower = key.toLowerCase();
      if (!lower.includes('time_return')) continue;
      if (!benchTokens.length || benchTokens.every((token) => lower.includes(token))) {
        return key;
      }
    }

    for (const key of keys) {
      const lower = key.toLowerCase();
      if (lower.includes('time_return') && (lower.includes('benchmark') || lower.includes('bench') || lower.includes('hs300'))) {
        return key;
      }
    }

    return 'time_return_benchmark';
  }

  function getNumeric(value, fallback = 0) {
    const num = Number(value);
    return Number.isFinite(num) ? num : fallback;
  }

  function formatPercent(value, digits = 2) {
    return `${(value * 100).toFixed(digits)}%`;
  }

  function buildMonthlyBuckets(timelines) {
    const buckets = [];
    let currentBucket = null;

    timelines.forEach((item) => {
      const month = String(item?.trade_date || '').slice(0, 7);
      if (!month) return;

      if (!currentBucket || currentBucket.month !== month) {
        currentBucket = { month, start: item, end: item, points: [item] };
        buckets.push(currentBucket);
        return;
      }

      currentBucket.end = item;
      currentBucket.points.push(item);
    });

    return buckets;
  }

  function buildYearlyBuckets(timelines) {
    const buckets = [];
    let currentBucket = null;

    timelines.forEach((item) => {
      const year = String(item?.trade_date || '').slice(0, 4);
      if (!year) return;

      if (!currentBucket || currentBucket.year !== year) {
        currentBucket = { year, points: [item] };
        buckets.push(currentBucket);
        return;
      }

      currentBucket.points.push(item);
    });

    return buckets;
  }

  function getTradeDate(value) {
    return String(value || '').split(' ')[0];
  }

  function calculatePeriodMaxDrawdown(points) {
    let peak = -Infinity;
    let maxDrawdown = 0;

    points.forEach((point) => {
      const equity = getNumeric(point?.equity, NaN);
      if (!Number.isFinite(equity)) return;
      if (equity > peak) peak = equity;
      if (peak > 0) {
        const drawdown = (peak - equity) / peak;
        if (drawdown > maxDrawdown) maxDrawdown = drawdown;
      }
    });

    return maxDrawdown;
  }

  function calculateMonthlyReturns(timelines, benchReturnKey) {
    return buildMonthlyBuckets(timelines).map(({ month, start, end, points }) => {
      const strategy = compoundReturn(points, 'time_return');
      const benchmark = compoundReturn(points, benchReturnKey);
      return {
        month,
        strategy,
        benchmark,
        excess: strategy - benchmark,
        firstDate: getTradeDate(start?.trade_date),
        lastDate: getTradeDate(end?.trade_date)
      };
    });
  }

  function compoundReturn(points, fieldKey) {
    return points.reduce((acc, point) => acc * (1 + getNumeric(point?.[fieldKey], 0)), 1) - 1;
  }

  function calculateYearlyMetrics(timelines, benchReturnKey) {
    return buildYearlyBuckets(timelines).map(({ year, points }) => {
      const first = points[0];
      const last = points[points.length - 1];
      const strategy = compoundReturn(points, 'time_return');
      const benchmark = compoundReturn(points, benchReturnKey);
      return {
        year,
        strategy,
        benchmark,
        excess: strategy - benchmark,
        strategyMaxDrawdown: calculatePeriodMaxDrawdown(points),
        firstDate: getTradeDate(first?.trade_date),
        lastDate: getTradeDate(last?.trade_date)
      };
    });
  }

  function buildMonthlyHeatmapMatrix(monthlyReturns, field) {
    const years = [...new Set(monthlyReturns.map((item) => item.month.slice(0, 4)))];
    const valueMap = new Map(monthlyReturns.map((item) => [item.month, item[field]]));
    const rows = Array.from({ length: 12 }, (_, index) => {
      const month = index + 1;
      const monthKey = String(month).padStart(2, '0');
      return {
        label: `${month}月`,
        cells: years.map((year) => {
          const key = `${year}-${monthKey}`;
          return { key, value: valueMap.has(key) ? valueMap.get(key) : null };
        })
      };
    });
    const maxAbs = Math.max(...monthlyReturns.map((item) => Math.abs(item[field])), 0.0001);
    return { years, rows, maxAbs };
  }

  function getHeatColor(value, maxAbs) {
    if (value == null) return '#f5f5f5';
    if (value === 0) return '#fff7cc';
    const ratio = Math.min(Math.abs(value) / maxAbs, 1);
    const alpha = 0.16 + ratio * 0.74;
    return value > 0 ? `rgba(211, 47, 47, ${alpha.toFixed(3)})` : `rgba(39, 174, 96, ${alpha.toFixed(3)})`;
  }

  function getHeatTextColor(value, maxAbs) {
    if (value == null) return '#bbb';
    return Math.abs(value) / maxAbs > 0.58 ? '#fff' : '#2d3436';
  }

  function getFieldLabel(field) {
    return FIELD_LABELS[field] || FIELD_LABELS.strategy;
  }

  function getAnalysisYearCount() {
    const monthlyYearCount = [...new Set(state.monthlyReturns.map((item) => item.month.slice(0, 4)))].length;
    return Math.max(monthlyYearCount, state.yearlyMetrics.length, 1);
  }

  function getTargetPanelWidth(activeTab) {
    const yearCount = getAnalysisYearCount();
    const maxWidth = Math.max(360, window.innerWidth - 24);
    const minWidth = Math.min(activeTab === 'annual' ? 680 : 480, maxWidth);
    const desired = activeTab === 'annual' ? 320 + yearCount * 96 : 220 + yearCount * 104;
    return Math.max(minWidth, Math.min(maxWidth, desired));
  }

  function buildTopTabsHTML(activeTab) {
    return `
      <div style="display:flex;gap:8px;flex-wrap:wrap;">
        ${Object.entries(TAB_LABELS).map(([tabKey, label]) => {
          const active = activeTab === tabKey;
          return `<button data-panel-tab="${tabKey}" style="border:1px solid ${active ? THEME.primaryBorder : '#dbe3f0'}; background:${active ? THEME.primarySoft : '#fff'}; color:${active ? THEME.primaryText : '#5b6b83'}; border-radius:999px; padding:6px 12px; font-size:12px; font-weight:${active ? '600' : '500'}; cursor:pointer;">${label}</button>`;
        }).join('')}
      </div>
    `;
  }

  function buildSingleHeatmapHTML(monthlyReturns, field, title) {
    const { years, rows, maxAbs } = buildMonthlyHeatmapMatrix(monthlyReturns, field);
    if (!years.length) {
      return '<div style="font-size:12px;color:#999;background:#fafafa;border:1px dashed #e5e7eb;border-radius:6px;padding:16px;text-align:center;">暂无月度收益数据</div>';
    }

    const header = years.map((year) => `<th style="padding:8px 10px;font-weight:600;color:#636e72;text-align:center;white-space:nowrap;min-width:96px;">${year}年</th>`).join('');
    const body = rows.map((row) => {
      const cells = row.cells.map((cell) => {
        const text = cell.value == null ? '' : formatPercent(cell.value);
        return `<td style="padding:0;border:1px solid #fff;background:${getHeatColor(cell.value, maxAbs)};min-width:96px;height:44px;text-align:center;color:${getHeatTextColor(cell.value, maxAbs)};font-weight:500;">${text}</td>`;
      }).join('');
      return `<tr><th style="padding:8px 10px;color:#636e72;font-weight:600;text-align:right;white-space:nowrap;background:#fafafa;position:sticky;left:0;z-index:1;min-width:64px;">${row.label}</th>${cells}</tr>`;
    }).join('');

    return `
      <div data-role="heatmap-card" style="border:1px solid #dbeafe;border-radius:6px;padding:12px;background:#fff;">
        <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px;">
          <div style="font-size:13px;font-weight:600;color:${THEME.primaryText};">${title}</div>
        </div>
        <div data-role="heatmap-scroll" style="overflow-x:auto;overflow-y:hidden;padding-bottom:6px;">
          <table style="border-collapse:separate;border-spacing:0;font-size:12px;width:max-content;min-width:100%;">
            <thead>
              <tr>
                <th style="padding:8px 10px;background:#fafafa;position:sticky;left:0;z-index:2;min-width:64px;"></th>
                ${header}
              </tr>
            </thead>
            <tbody>${body}</tbody>
          </table>
        </div>
      </div>
    `;
  }

  function buildMonthlyReturnsChartHTML(monthlyReturns, field) {
    if (!monthlyReturns.length) {
      return '<div style="font-size:12px;color:#999;background:#fafafa;border:1px dashed #e5e7eb;border-radius:6px;padding:16px;text-align:center;">暂无月度收益数据</div>';
    }

    const selectedField = FIELD_LABELS[field] ? field : 'strategy';
    const title = getFieldLabel(selectedField);
    return `
      <div style="display:flex;flex-direction:column;gap:12px;">
        <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;padding:10px 12px;background:${THEME.primarySoft};border:1px solid ${THEME.primaryBorder};border-radius:6px;">
          <div>
            <div style="font-size:12px;color:${THEME.primaryText};font-weight:600;">热力图视图切换</div>
          </div>
          <label style="display:flex;align-items:center;gap:8px;font-size:12px;color:${THEME.primaryText};">
            <span>当前视图</span>
            <select id="${SELECT_ID}" style="height:32px;padding:0 10px;border:1px solid ${THEME.primaryBorder};border-radius:6px;background:#fff;color:${THEME.primaryText};font-size:12px;outline:none;cursor:pointer;">
              <option value="strategy" ${selectedField === 'strategy' ? 'selected' : ''}>策略月度收益</option>
              <option value="benchmark" ${selectedField === 'benchmark' ? 'selected' : ''}>基准月度收益</option>
              <option value="excess" ${selectedField === 'excess' ? 'selected' : ''}>超额月度收益</option>
            </select>
          </label>
        </div>
        ${buildSingleHeatmapHTML(monthlyReturns, selectedField, title)}
      </div>
    `;
  }

  function buildAnnualReturnChartHTML(yearlyMetrics) {
    if (!yearlyMetrics.length) {
      return '<div style="font-size:12px;color:#999;background:#fafafa;border:1px dashed #e5e7eb;border-radius:6px;padding:16px;text-align:center;">暂无年度收益数据</div>';
    }

    const maxAbs = Math.max(...yearlyMetrics.map((item) => Math.abs(item.strategy)), 0.01);
    const svgWidth = Math.max(460, yearlyMetrics.length * 92 + 70);
    const svgHeight = 240;
    const baseline = 118;
    const topPadding = 30;
    const bottomPadding = 34;
    const barAreaHeight = baseline - topPadding;
    const negativeAreaHeight = svgHeight - baseline - bottomPadding;
    const slotWidth = (svgWidth - 60) / yearlyMetrics.length;
    const barWidth = Math.min(32, Math.max(20, slotWidth * 0.48));
    const bars = yearlyMetrics.map((item, index) => {
      const x = 44 + index * slotWidth + (slotWidth - barWidth) / 2;
      const value = item.strategy;
      const positive = value >= 0;
      const scale = positive ? barAreaHeight / maxAbs : negativeAreaHeight / maxAbs;
      const barHeight = Math.max(Math.abs(value) * scale, Math.abs(value) > 0 ? 4 : 0);
      const y = positive ? baseline - barHeight : baseline;
      const labelY = positive ? Math.max(14, y - 6) : Math.min(svgHeight - 8, y + barHeight + 14);
      const label = formatPercent(value);
      const fill = positive ? THEME.primaryBarPositive : THEME.primaryBarNegative;
      return `
        <rect x="${x.toFixed(1)}" y="${y.toFixed(1)}" width="${barWidth.toFixed(1)}" height="${barHeight.toFixed(1)}" rx="4" fill="${fill}" />
        <text x="${(x + barWidth / 2).toFixed(1)}" y="${labelY.toFixed(1)}" text-anchor="middle" font-size="11" fill="${THEME.primaryBarText}" font-weight="600">${label}</text>
        <text x="${(x + barWidth / 2).toFixed(1)}" y="${svgHeight - 10}" text-anchor="middle" font-size="11" fill="#666">${item.year}</text>
      `;
    }).join('');

    return `
      <div style="border:1px solid #dbeafe;border-radius:6px;padding:12px;background:#fff;">
        <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px;">
          <div style="font-size:13px;font-weight:600;color:${THEME.primaryText};">年度收益柱状图（策略）</div>
        </div>
        <div style="overflow-x:auto;">
          <svg xmlns="http://www.w3.org/2000/svg" width="${svgWidth}" height="${svgHeight}" style="display:block;">
            <line x1="24" y1="${baseline}" x2="${svgWidth - 16}" y2="${baseline}" stroke="#d9d9d9" stroke-width="1" />
            <text x="24" y="20" font-size="10" fill="#999">策略年度收益</text>
            ${bars}
          </svg>
        </div>
      </div>
    `;
  }

  function buildAnnualTableHTML(yearlyMetrics) {
    if (!yearlyMetrics.length) {
      return '<div style="font-size:12px;color:#999;background:#fafafa;border:1px dashed #e5e7eb;border-radius:6px;padding:16px;text-align:center;">暂无年度分析表格</div>';
    }

    const rows = yearlyMetrics.map((item) => {
      const positiveColor = (value) => value >= 0 ? '#c62828' : '#2e7d32';
      return `
        <tr style="border-bottom:1px solid #eff6ff;">
          <td style="padding:8px 10px;font-weight:600;color:#334155;white-space:nowrap;">${item.year}年</td>
          <td style="padding:8px 10px;color:${positiveColor(item.strategy)};font-weight:600;white-space:nowrap;">${formatPercent(item.strategy)}</td>
          <td style="padding:8px 10px;color:${THEME.drawdownGreen};font-weight:600;white-space:nowrap;">-${formatPercent(item.strategyMaxDrawdown)}</td>
          <td style="padding:8px 10px;color:${positiveColor(item.benchmark)};font-weight:600;white-space:nowrap;">${formatPercent(item.benchmark)}</td>
          <td style="padding:8px 10px;color:${positiveColor(item.excess)};font-weight:600;white-space:nowrap;">${formatPercent(item.excess)}</td>
        </tr>
      `;
    }).join('');

    return `
      <div style="border:1px solid #dbeafe;border-radius:6px;padding:12px;background:#fff;">
        <div style="display:flex;align-items:center;justify-content:space-between;gap:12px;flex-wrap:wrap;margin-bottom:10px;">
          <div style="font-size:13px;font-weight:600;color:${THEME.primaryText};">年度分析明细表</div>
        </div>
        <div style="overflow-x:auto;">
          <table style="width:100%;min-width:680px;border-collapse:collapse;font-size:12px;text-align:left;">
            <thead style="background:${THEME.primarySoft};border-bottom:1px solid ${THEME.primaryBorder};">
              <tr>
                <th style="padding:8px 10px;font-weight:600;white-space:nowrap;color:${THEME.primaryText};">年份</th>
                <th style="padding:8px 10px;font-weight:600;white-space:nowrap;color:${THEME.primaryText};">策略年度收益</th>
                <th style="padding:8px 10px;font-weight:600;white-space:nowrap;color:${THEME.primaryText};">策略年度最大回撤</th>
                <th style="padding:8px 10px;font-weight:600;white-space:nowrap;color:${THEME.primaryText};">基准年度收益</th>
                <th style="padding:8px 10px;font-weight:600;white-space:nowrap;color:${THEME.primaryText};">超额年度收益</th>
              </tr>
            </thead>
            <tbody>${rows}</tbody>
          </table>
        </div>
      </div>
    `;
  }

  function buildAnnualAnalysisHTML(yearlyMetrics) {
    return `
      <div style="display:flex;flex-direction:column;gap:12px;">
        ${buildAnnualReturnChartHTML(yearlyMetrics)}
        ${buildAnnualTableHTML(yearlyMetrics)}
      </div>
    `;
  }

  function getPanelTitle() {
    return state.activeTab === 'annual' ? '年度分析' : getFieldLabel(state.selectedField);
  }

  function buildPanelContentHTML() {
    return `
      <div style="display:flex;flex-direction:column;gap:12px;">
        ${buildTopTabsHTML(state.activeTab)}
        ${state.activeTab === 'annual'
          ? buildAnnualAnalysisHTML(state.yearlyMetrics)
          : buildMonthlyReturnsChartHTML(state.monthlyReturns, state.selectedField)}
      </div>
    `;
  }

  function getPanelMinWidth(activeTab) {
    return `${Math.min(activeTab === 'annual' ? 680 : 480, Math.max(360, window.innerWidth - 24))}px`;
  }

  function removeExistingPanel() {
    const existingPanel = document.getElementById(PANEL_ID);
    if (!existingPanel) return;
    capturePanelFrame(existingPanel);
    existingPanel.remove();
  }

  function createPanelShell() {
    const panel = document.createElement('div');
    panel.id = PANEL_ID;
    panel.style.top = state.top;
    panel.style.left = state.left;
    panel.style.bottom = state.bottom;
    panel.style.right = state.right;
    panel.style.minWidth = getPanelMinWidth(state.activeTab);
    panel.style.width = state.width || `${getTargetPanelWidth(state.activeTab)}px`;
    return panel;
  }

  function createPanelHeader() {
    const header = document.createElement('div');
    header.style.cssText = `background:${THEME.primarySoft};padding:10px 15px;border-bottom:1px solid ${THEME.primaryBorder};cursor:move;display:flex;justify-content:space-between;align-items:center;user-select:none;border-radius:8px 8px 0 0;gap:12px;`;
    header.innerHTML = `
      <div>
        <h3 style="margin:0;font-size:14px;font-weight:600;color:${THEME.primaryText};">📈 ${getPanelTitle()}</h3>
        <div style="margin-top:2px;font-size:11px;color:#64748b;">benchKey: ${state.benchKey || '-'}</div>
      </div>
      <button id="close-monthly-heatmap-panel" style="background:none;border:none;font-size:18px;cursor:pointer;color:#64748b;padding:0 4px;line-height:1;margin-top:-2px;transition:color 0.2s;flex:0 0 auto;">×</button>
    `;
    return header;
  }

  function createPanelContent() {
    const content = document.createElement('div');
    content.style.cssText = 'padding:12px;overflow:auto;';
    content.innerHTML = buildPanelContentHTML();
    return content;
  }

  function bindCloseButton(panel) {
    const closeButton = panel.querySelector('#close-monthly-heatmap-panel');
    if (!closeButton) return;

    closeButton.addEventListener('click', (event) => {
      event.stopPropagation();
      capturePanelFrame(panel);
      panel.remove();
      state.panelOpen = false;
    });
    closeButton.addEventListener('mouseover', (event) => {
      event.target.style.color = THEME.primaryText;
    });
    closeButton.addEventListener('mouseout', (event) => {
      event.target.style.color = '#999';
    });
  }

  function bindTabButtons(panel) {
    panel.querySelectorAll('[data-panel-tab]').forEach((button) => {
      button.addEventListener('click', () => {
        state.activeTab = button.getAttribute('data-panel-tab') || 'monthly';
        renderPanel();
      });
    });
  }

  function bindFieldSelect(panel) {
    const select = panel.querySelector(`#${SELECT_ID}`);
    if (!select) return;

    select.addEventListener('change', (event) => {
      state.selectedField = event.target.value;
      renderPanel();
    });
  }

  function bindPanelFrameCapture(panel) {
    panel.addEventListener('mouseup', () => {
      capturePanelFrame(panel);
    });
  }

  function renderPanel() {
    if (!state.monthlyReturns.length) {
      showToast('请先运行一次回测后再打开月度收益图', true);
      return;
    }

    removeExistingPanel();

    const panel = createPanelShell();
    const header = createPanelHeader();
    const content = createPanelContent();

    panel.appendChild(header);
    panel.appendChild(content);
    document.body.appendChild(panel);

    bindCloseButton(panel);
    bindTabButtons(panel);
    bindFieldSelect(panel);
    bindPanelFrameCapture(panel);
    makeDraggable(panel, header);
    state.panelOpen = true;
  }

  function buildAnalysisState(timelines) {
    const benchKey = detectBenchKey(timelines[0]);
    const benchReturnKey = detectBenchReturnKey(timelines[0], benchKey);
    const firstDate = getTradeDate(timelines[0]?.trade_date);
    const lastDate = getTradeDate(timelines[timelines.length - 1]?.trade_date);

    return {
      benchKey,
      monthlyReturns: calculateMonthlyReturns(timelines, benchReturnKey),
      yearlyMetrics: calculateYearlyMetrics(timelines, benchReturnKey),
      signature: `${timelines.length}|${firstDate}|${lastDate}|${benchKey}|${benchReturnKey}`
    };
  }

  function updateFromTimelines(timelines) {
    if (!timelines?.length) return;

    const analysisState = buildAnalysisState(timelines);
    state.benchKey = analysisState.benchKey;
    state.monthlyReturns = analysisState.monthlyReturns;
    state.yearlyMetrics = analysisState.yearlyMetrics;

    ensureButton();

    if (analysisState.signature === state.lastSignature) return;
    state.lastSignature = analysisState.signature;
    renderPanel();
    showToast('✅ 收益分析面板刷新完成');
  }

  function isBacktestUrl(reqUrl) {
    return String(reqUrl || '').includes('backtest');
  }

  function handleBacktestResponseText(text) {
    const timelines = extractTimelinesFromBacktestText(text);
    if (!timelines?.length) return;
    updateFromTimelines(timelines);
  }

  const originalFetch = window.fetch;
  window.fetch = async function (...args) {
    let reqUrl = '';

    if (args[0] instanceof Request) {
      reqUrl = args[0].url;
    } else {
      reqUrl = args[0] || '';
    }

    const response = await originalFetch.apply(this, args);

    if (isBacktestUrl(reqUrl)) {
      response.clone().text().then(handleBacktestResponseText).catch(console.error);
    }

    return response;
  };

  const OriginalXHR = window.XMLHttpRequest;
  window.XMLHttpRequest = function () {
    const xhr = new OriginalXHR();
    let reqUrl = '';

    const originalOpen = xhr.open;
    xhr.open = function (method, url, ...rest) {
      reqUrl = typeof url === 'string' ? url : String(url);
      return originalOpen.apply(this, [method, url, ...rest]);
    };

    const originalSend = xhr.send;
    xhr.send = function () {
      this.addEventListener('load', function () {
        if (!isBacktestUrl(reqUrl)) return;
        handleBacktestResponseText(this.responseText);
      });

      return originalSend.apply(this, arguments);
    };

    return xhr;
  };

  ensureButton();
  showToast('🚀 收益分析脚本已挂载，运行一次回测即可自动展示');
})();