Pionex 網格利潤顯示

加強網格利潤顯功能

// ==UserScript==
// @name         Pionex 網格利潤顯示
// @namespace    http://tampermonkey.net/
// @version      2024-12-14
// @description  加強網格利潤顯功能
// @author       Rick
// @match        https://www.pionex.com/*/orders/bot/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// @license MIT
// ==/UserScript==

(function() {
    'use strict';

    const profitItems = [
        { label: '網格年化報酬率', className: 'annualized-profit' },
        { label: '日均網格利潤 (USDT)', className: 'daily-profit' },
        { label: '累計格利潤 (USDT)', className: 'total-profit' },
    ];

    function extractDateFromRuntime(runtimeText) {
        const dateMatch = runtimeText.match(/(\d{4}\/\d{2}\/\d{2} \d{2}:\d{2}:\d{2})/);
        if (dateMatch) {
            return new Date(dateMatch[1]);
        }
        return null;
    }

    function parseRuntimeToSeconds(runtimeText) {
        const durationMatch = runtimeText.match(/(?:(\d+)日 )?(?:(\d+)時 )?(?:(\d+)分 )?(?:(\d+)秒)/) ||
              runtimeText.match(/Lasting (?:(\d+)d )?(?:(\d+)h )?(?:(\d+)m )?(?:(\d+)s)/);
        let totalSeconds = 0;
        if (durationMatch) {
            const days = parseInt(durationMatch[1] || 0, 10);
            const hours = parseInt(durationMatch[2] || 0, 10);
            const minutes = parseInt(durationMatch[3] || 0, 10);
            const seconds = parseInt(durationMatch[4] || 0, 10);
            totalSeconds += days * 86400; // 一天有 86400 秒
            totalSeconds += hours * 3600; // 一小時有 3600 秒
            totalSeconds += minutes * 60; // 一分鐘有 60 秒
            totalSeconds += seconds;
        }
        return totalSeconds;
    }

    function appendGrid(item) {
        const profitTextElement = item.querySelector('span.mr-4px.text-increase')
        if (!profitTextElement) {
            return;
        }

        const grid = item.querySelector('div.grid');
        if (!grid) {
            return;
        }

        profitItems.forEach(({ label, value, className }) => {
            let profitDiv = grid.querySelector(`.${className}`);
            if (!profitDiv) {
                const profitDiv = document.createElement('div');
                profitDiv.className = `flex flex-col gap-2px text-increase ${className}`;
                profitDiv.innerHTML = `
                <div class="text-secondary text-sm font-r">${label}</div>
        <div class="text-base font-b">--</div>
        `;
                grid.prepend(profitDiv); // 插入到最前面
            }
        });

    }

    function updateGrid(item, {investmentAmount, totalProfitAmount, runtimeInDays}) {
        const grid = item.querySelector('div.grid');

        if (!grid || !investmentAmount || !runtimeInDays) {
            return;
        }

        const totalProfitRate = totalProfitAmount / investmentAmount;
        const dailyProfitAmount = totalProfitAmount / runtimeInDays;
        const dailyProfitRate = dailyProfitAmount / investmentAmount;
        const annualizedProfitAmount = dailyProfitAmount * 365;
        const annualizedProfitRate = dailyProfitRate * 365;

        const values = {
            'annualized-profit': `${(100 * annualizedProfitRate).toFixed(2)}%`,
            'daily-profit': `+${dailyProfitAmount.toFixed(4)} (${(100 * dailyProfitRate).toFixed(2)}%)`,
            'total-profit': `+${totalProfitAmount.toFixed(4)} (${(100 * totalProfitRate).toFixed(2)}%)`,
        }

        profitItems.forEach(({ label, className }) => {
            const value = values[className]
            let profitDiv = grid.querySelector(`.${className}`);
            const valueDiv = profitDiv.querySelector('.text-base.font-b');
            if (valueDiv) {
                valueDiv.innerText = value;
            }
        });
    }



    function findTotalProfitElement (item) {
        const profitTextElement = item.querySelector('span.mr-4px.text-increase')
        if (!profitTextElement) {
            return;
        }
        const profitText = profitTextElement.textContent.trim().replace('+','')
        const tooltipElements = document.querySelectorAll('div.flex.justify-between.text-base.gap-12px>div.text-accent-sub')
        const currentProfitElementArray = Array.from(tooltipElements).filter(element => element.firstChild.textContent.trim() === '總網格利潤' || element.firstChild.textContent.trim() === '當前網格利潤').map(element => element.nextSibling);
        const currentProrfitElement = currentProfitElementArray.find(element => profitText.includes(element.textContent.trim().split(' ')[0]))
        if (!currentProrfitElement) return;
        return currentProrfitElement.parentElement.parentElement.lastChild.lastChild;
    }

    function updateItem(item) {
        const rect = item.getBoundingClientRect()
        if (rect.y > window.innerHeight || rect.y < 0) return;
        const investmentAmountElement = item.querySelector('div.rounded-6px.rounded-tr-none>div.text-lg.font-sb')
        if (!investmentAmountElement) return;
        const investmentAmount = Number(investmentAmountElement.textContent.trim());
        const runtimeElement = item.querySelector(
            'div.text-sm.text-secondary.font-r'
        );
        const runtimeText = runtimeElement ? runtimeElement.textContent.trim() : null;
        if (!runtimeText) {
            return;
        }

        const runtime = parseRuntimeToSeconds(runtimeText);
        const runtimeInDays = runtime / (60 * 60 * 24);

        const totalProfitElement = findTotalProfitElement(item);
        if (totalProfitElement) {
            const totalProfitAmount = Number(totalProfitElement.textContent.split(' ')[0]);

            updateGrid(item, {investmentAmount, totalProfitAmount, runtimeInDays})
            return
        }

        const hoverSvg = item.querySelector('svg > path[fill="#E1E2E5"], svg > path[fill="#27282A"]');
        if (hoverSvg) {
            hoverSvg.dispatchEvent(new MouseEvent('mouseover', {
                bubbles: true,
                cancelable: true,
            }));

            // 等待 tooltip 顯示後,再抓取利潤數據
            setTimeout(() => {
                const totalProfitElement = findTotalProfitElement(item);
                hoverSvg.dispatchEvent(new MouseEvent('mouseout', {
                    bubbles: true,
                    cancelable: true,
                }));
                if (!totalProfitElement) {
                    return;
                }
                const totalProfitAmount = Number(totalProfitElement.textContent.split(' ')[0])
                updateGrid(item, {investmentAmount, totalProfitAmount, runtimeInDays})
            }, 100);
        }
    }

    const observedElements = new Set();
    const intersectingElements = new Set();
    const intersectionObserver = new IntersectionObserver((entries) => {
        entries.forEach(entry => {
            if (entry.isIntersecting) {
                intersectingElements.add(entry.target)
            } else {
                intersectingElements.delete(entry.target)
            }
        });
    }, {
        root: null,
        threshold: 0
    });

    function updateIntersectionObserverItems() {
        const elements = document.querySelectorAll('div[data-index]');
        elements.forEach(element => {
            if (!observedElements.has(element)) {
                intersectionObserver.observe(element);
                observedElements.add(element);
            }
        });
    }

    function update() {
        updateIntersectionObserverItems();
        intersectingElements.forEach(element => {
            appendGrid(element);
            updateItem(element);
        })
    }

    update();
    setInterval(update, 500);
})();