Variational PnL

Adds Total PnL to the Variational Portfolio page & header

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==UserScript==
// @name         Variational PnL
// @namespace    variational-tools
// @version      0.1
// @description  Adds Total PnL to the Variational Portfolio page & header
// @author       inco
// @match        https://omni.variational.io/*
// @grant        none
// @license MIT 
// ==/UserScript==

(function() {

    const dataUpdateInterval = 30000; // How often to retrieve new data
    const portfolioPageCheckInterval = 1000; // How often to check whether we're on the portfolio page

    // ---------- HTML elements ----------
    const PnLBox = document.createElement('div');
    // flex flex-col sm:flex-col gap-0.5
    PnLBox.classList.add('flex', 'flex-col', 'sm:flex-col', 'gap-0.5')
    PnLBox.innerHTML = `
    <span class="text-blackwhite/50 truncate">Total PnL</span>
    <div class="flex items-center gap-1">
        <span class="inline-block tabular-nums text-left text-red transition ease-in-out duration-300" id="pnl-value-header">N/A</span>
    </div>
    `;

    const statsBar = document.createElement('div');
    statsBar.classList.add('relative', 'flex', 'items-center', 'justify-around', 'px-8', 'gap-3', 'h-16', 'bg-darkblue-400', 'rounded-sm', 'm-[2px]');

    statsBar.innerHTML = `
    <!--Num Transfers-->
    <div class="flex flex-col items-start text-xs text-center gap-0.5 whitespace-nowrap" style="width:100px">
        <div role="button" tabindex="0" class="text-blackwhite/50" style="color: #f7f5efc2">Num Orders</div>
        <div slot="value" class="transition ease-in-out duration-300">
            <span class="" id="num-transfers">N/A</span>
        </div>
    </div>

    <!--PnL-->
    <div class="flex flex-col items-start text-xs text-center gap-0.5 whitespace-nowrap" style="width:100px">
        <div role="button" tabindex="0" class="text-blackwhite/50" style="color: #f7f5efc2">Total Realized PnL</div>
        <div slot="value" class="transition ease-in-out duration-300">
            <span class="text-red" id="pnl-value">N/A</span>
        </div>
    </div>

    <!--Funding-->
    <div class="flex flex-col items-start text-xs text-center gap-0.5 whitespace-nowrap" style="width:100px">
        <div role="button" tabindex="0" class="text-blackwhite/50" style="color: #f7f5efc2">Total Funding</div>
        <div slot="value" class="transition ease-in-out duration-300">
            <span class="" id="funding-value">N/A</span>
        </div>
    </div>

    <!--Refunds-->
    <div class="flex flex-col items-start text-xs text-center gap-0.5 whitespace-nowrap" style="width:100px">
        <div role="button" tabindex="0" class="text-blackwhite/50" style="color: #f7f5efc2">Num Refunds</div>
        <div slot="value" class="transition ease-in-out duration-300">
            <span class="" id="num-refunds">N/A</span>
        </div>
    </div>

    <!--Refund Value-->
    <div class="flex flex-col items-start text-xs text-center gap-0.5 whitespace-nowrap" style="width:100px">
        <div role="button" tabindex="0" class="text-blackwhite/50" style="color: #f7f5efc2">Total Refund Value</div>
        <div slot="value" class="transition ease-in-out duration-300">
            <span class="" id="refund-value">N/A</span>
        </div>
    </div>
    `;

    const checkInterval = setInterval(() => {

        
        const pnlBoxInjectionTarget = document.querySelector('[data-testid="portfolio-summary"]');
        if (pnlBoxInjectionTarget) {
            pnlBoxInjectionTarget.prepend(PnLBox);
            console.log('PnL box added.');
            clearInterval(checkInterval); // Stop the interval once the element is found
        }
    }, 500); // Check every 500 milliseconds until found

    let statsBarAdded = false;

    setInterval(() => {
        if( !statsBarAdded && window.location.pathname === "/portfolio" ) {
            const statsBarInjectionTarget = document.querySelector('.relative.flex.flex-col.w-full.px-2.my-12');
            if (statsBarInjectionTarget) {
                //Select second child
                statsBarInjectionTarget.insertBefore(statsBar, statsBarInjectionTarget.children[2]);
                console.log('Stats bar added.');
                statsBarAdded = true;
            }
        } else if (statsBarAdded && window.location.pathname !== "/portfolio") {
            statsBarAdded = false;
        }
    }, portfolioPageCheckInterval); // Check every 1 second

    const $ = (sel) => statsBar.querySelector(sel);

    const el = {
        numTransfers: $('#num-transfers'),
        pnlValue: $('#pnl-value'),
        fundingValue: $('#funding-value'),
        numRefunds: $('#num-refunds'),
        refundValue: $('#refund-value'),
    }
    
    const pnlValueHeader = PnLBox.querySelector('#pnl-value-header');
    

    async function getTransfers(offset) {
    return await fetch(`https://omni.variational.io/api/transfers?limit=100&offset=${offset}&order_by=created_at&order=desc`, {
        headers: {
        "accept": "*/*",
        "accept-language": "en-US,en;q=0.6",
        "content-type": "application/json",
        },
        method: "GET",
        mode: "cors",


        credentials: "include"
    })
        .then(r => r.json())
        .then(r => {
            return r.result;
        })
    }

    // Queries paginated API endpoint, calculates PnL
    async function getPnL() {
        let offset = 0;
        let count = 0;
        let PnL = 0;
        let funding = 0;
        let refund = 0;
        let refundCount = 0;
        // Get pnl
        while (true) {
            const transactions = await getTransfers(offset);
            //console.log(transactions)
            if (transactions.length === 0) break;
            
            transactions.forEach(t => {
                if (t.transfer_type === "realized_pnl") {
                    PnL += parseFloat(t.qty);
                }
                if (t.transfer_type === "funding") {
                    funding += parseFloat(t.qty);
                }
                if (t.transfer_type === "reward") {
                    refund += parseFloat(t.qty);
                    refundCount += 1;
                }
            });
            offset += 100;
            count += transactions.length;
        }


        el.numTransfers.textContent = `${count}`;
        //console.log(`Total PnL over ${count} transfers: \$${PnL}`);
        el.pnlValue.textContent = `$${PnL.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;
        el.fundingValue.textContent = `$${funding.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;
        el.numRefunds.textContent = `${refundCount}`;
        el.refundValue.textContent = `$${refund.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;

        pnlValueHeader.textContent = `$${PnL.toFixed(2).replace(/\B(?=(\d{3})+(?!\d))/g, ',')}`;

        if( PnL > 0 ) {
            el.pnlValue.classList.replace('text-red', 'text-green');
            pnlValueHeader.classList.replace('text-red', 'text-green');
        } else {
            el.pnlValue.classList.replace('text-green', 'text-red');
            pnlValueHeader.classList.replace('text-green', 'text-red');
        }
        if( funding > 0 ) {
            el.fundingValue.classList.replace('text-red', 'text-green');
        } else {
            el.fundingValue.classList.replace('text-green', 'text-red');
        }
    }

    getPnL();
    // Update data every 30 seconds
    setInterval(getPnL, dataUpdateInterval);

})();