InvestNow Return % Calculator

Calculate and display Return % for each fund on InvestNow

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         InvestNow Return % Calculator
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Calculate and display Return % for each fund on InvestNow
// @author       You
// @match        https://secure.investnow.co.nz/feature/client/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const CONFIG = {
        columnHeaders: {
            marketValue: ['Market Value', 'Market Value (NZD)', 'Current Value', 'NZD Value'],
            nzdValue: ['NZD Value', 'NZD Value (NZD)'],
            unrealisedGain: ['Unrealised Gain', 'Unrealised Gain (NZD)', 'Unrealised', 'Unrealised'],
            totalReturn: ['Total Return', 'Total Return (NZD)', 'Total Return (NZD)', 'Total Return (NZD)']
        },
        returnColumnHeader: 'Return %',
        observerConfig: {
            childList: true,
            subtree: true
        },
        retryDelay: 1000,
        maxRetries: 10
    };

    // Global storage for calculated Return % values
    const returnPercentages = new Map(); // key: fund name, value: return percentage

    // Global storage for NZD values from Valuation table
    const marketValues = new Map(); // key: fund name, value: NZD value

    // Flag to prevent multiple expansions
    let hasExpandedRows = false;

    // Extract NZD values from Valuation (Holdings) table
    function extractMarketValues(isRecursive = false) {
        console.log('RL - InvestNow Return % Calculator: Extracting NZD values from Valuation table...');

        // Only clear if we haven't extracted anything yet, or if we're on the Valuation tab
        const tables = document.querySelectorAll('table');
        let hasValuationTable = false;

        tables.forEach((table, index) => {
            const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());
            const hasNzdValue = CONFIG.columnHeaders.nzdValue.some(header =>
                headers.some(h => h.includes(header))
            );

            // Valuation table has NZD Value but typically doesn't have Unrealised Gain
            // Returns table has Unrealised Gain but doesn't have NZD Value
            if (hasNzdValue && !headers.some(h => h.includes('Unrealised'))) {
                hasValuationTable = true;
                console.log(`RL - InvestNow Return % Calculator: Table ${index + 1} identified as Valuation table (has NZD Value, no Unrealised)`);
            }
        });

        // Only clear and re-extract if we're on the Valuation tab
        if (hasValuationTable) {
            console.log('RL - InvestNow Return % Calculator: On Valuation tab, clearing and re-extracting NZD values...');
            marketValues.clear();
        } else {
            console.log(`RL - InvestNow Return % Calculator: On Returns tab, keeping existing ${marketValues.size} NZD values`);
            if (marketValues.size > 0) {
                console.log(`RL - InvestNow Return % Calculator: Current NZD values:`, Array.from(marketValues.entries()));
            }
            return; // Keep existing values
        }

        console.log(`RL - InvestNow Return % Calculator: Found ${tables.length} tables to search for NZD values`);

        // First, check if we need to expand rows in any Valuation table
        let needsExpansion = false;
        tables.forEach((table, index) => {
            const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());
            const hasNzdValue = CONFIG.columnHeaders.nzdValue.some(header =>
                headers.some(h => h.includes(header))
            );

            if (hasNzdValue) {
                console.log(`RL - InvestNow Return % Calculator: Found Valuation table ${index + 1}, checking for collapsed rows...`);

                // Check if we need to expand rows for this table
                const expandButtons = table.querySelectorAll('button[aria-label="Row Expanded"]');
                // Check if there are any collapsed rows (chevronright icon) that need expanding
                const collapsedButtons = Array.from(expandButtons).filter(btn =>
                    btn.querySelector('chevronrighticon')
                );

                if (collapsedButtons.length > 0) {
                    console.log(`RL - InvestNow Return % Calculator: Found Valuation table with ${collapsedButtons.length} collapsed rows, expanding...`);
                    needsExpansion = true;
                }
            }
        });

        if (needsExpansion) {
            console.log(`RL - InvestNow Return % Calculator: Expanding Valuation table rows before extracting NZD values...`);
            hasExpandedRows = false; // Reset flag to allow expansion
            expandAllRows();

            // Wait a moment for expansion to complete, then re-extract
            setTimeout(() => {
                console.log(`RL - InvestNow Return % Calculator: Re-extracting NZD values after expansion...`);
                extractMarketValues(true); // Recursive call to extract after expansion
            }, 1000);
            return;
        }

        tables.forEach((table, index) => {
            const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());
            console.log(`RL - InvestNow Return % Calculator: Table ${index + 1} headers:`, headers);

            // Look for Holdings/Valuation table (has NZD Value column)
            const hasNzdValue = CONFIG.columnHeaders.nzdValue.some(header =>
                headers.some(h => h.includes(header))
            );
            const hasMarketValue = CONFIG.columnHeaders.marketValue.some(header =>
                headers.some(h => h.includes(header))
            );
            const hasUnrealisedGain = CONFIG.columnHeaders.unrealisedGain.some(header =>
                headers.some(h => h.includes(header))
            );

            console.log(`RL - InvestNow Return % Calculator: Table ${index + 1} - NZDValue: ${hasNzdValue}, MarketValue: ${hasMarketValue}, UnrealisedGain: ${hasUnrealisedGain}`);

            if (hasNzdValue) {
                console.log(`RL - InvestNow Return % Calculator: Found Valuation table ${index + 1}`);

                const nzdValueIndex = findColumnIndex(headers, CONFIG.columnHeaders.nzdValue);
                console.log(`RL - InvestNow Return % Calculator: NZD Value column index: ${nzdValueIndex}`);

                const rows = table.querySelectorAll('tbody tr, tr');
                console.log(`RL - InvestNow Return % Calculator: Found ${rows.length} rows in Valuation table`);

                rows.forEach((row, rowIndex) => {
                    // Debug: Log what we're seeing in each row
                    const rowHtml = row.innerHTML.substring(0, 200);
                    const hasTh = !!row.querySelector('th');
                    const hasToggler = !!row.querySelector('p-treetabletoggler');
                    console.log(`RL - InvestNow Return % Calculator: Row ${rowIndex + 1} debug - hasTh: ${hasTh}, hasToggler: ${hasToggler}, HTML: ${rowHtml}`);

                    // Only skip actual header rows (rows with th elements)
                    // Don't skip rows with togglers as they might contain actual data after expansion
                    if (row.querySelector('th')) {
                        console.log(`RL - InvestNow Return % Calculator: Skipping header row ${rowIndex + 1}`);
                        return;
                    }

                    // Check if this is a category sub-header (has toggler but no actual fund data)
                    const nameCell = row.querySelector('td:first-child');
                    if (nameCell && hasToggler) {
                        const nameContent = getCellContent(nameCell);
                        // Skip if it looks like a category header (no specific fund name)
                        if (nameContent && (nameContent.includes('Fixed Interest') || nameContent.includes('Equities') || nameContent.includes('Cash'))) {
                            console.log(`RL - InvestNow Return % Calculator: Skipping category subheader row ${rowIndex + 1}: "${nameContent}"`);
                            return;
                        }
                    }

                    const cells = row.querySelectorAll('td');
                    if (cells.length <= nzdValueIndex) {
                        console.log(`RL - InvestNow Return % Calculator: Row ${rowIndex + 1} has insufficient cells (${cells.length} <= ${nzdValueIndex})`);
                        return;
                    }

                    // Get fund name - reuse the nameCell we already got
                    let fundName = `Fund_${rowIndex}`;
                    if (nameCell) {
                        const nameContent = getCellContent(nameCell);
                        if (nameContent && nameContent.trim() && !nameContent.includes('%') && !parseCurrency(nameContent)) {
                            fundName = nameContent.trim();
                        }
                    }

                    // Extract NZD Value
                    const nzdValueContent = getCellContent(cells[nzdValueIndex]);
                    const nzdValue = parseCurrency(nzdValueContent);

                    console.log(`RL - InvestNow Return % Calculator: Row ${rowIndex + 1} - Fund: "${fundName}", NZD Value: "${nzdValueContent}" -> ${nzdValue}`);

                    if (nzdValue !== null && nzdValue > 0) {
                        marketValues.set(fundName, nzdValue);
                        console.log(`RL - InvestNow Return % Calculator: Stored NZD value for "${fundName}": ${nzdValue}`);
                    }
                });
            }
        });

        console.log(`RL - InvestNow Return % Calculator: Extracted ${marketValues.size} NZD values`);
        console.log(`RL - InvestNow Return % Calculator: NZD values map:`, Array.from(marketValues.entries()));
    }

    // Auto-expand all rows function
    function expandAllRows() {
        if (hasExpandedRows) {
            console.log('RL - InvestNow Return % Calculator: Rows already expanded, skipping...');
            return;
        }

        console.log('RL - InvestNow Return % Calculator: Expanding all rows...');
        const expandButtons = document.querySelectorAll('button[aria-label="Row Expanded"]');

        // Filter to only get collapsed rows (chevronright icon)
        const collapsedButtons = Array.from(expandButtons).filter(btn =>
            btn.querySelector('chevronrighticon')
        );

        if (collapsedButtons.length === 0) {
            console.log('RL - InvestNow Return % Calculator: No collapsed rows found to expand');
            return;
        }

        console.log(`RL - InvestNow Return % Calculator: Found ${collapsedButtons.length} collapsed rows to expand`);
        hasExpandedRows = true; // Set flag to prevent re-expansion

        // Add a small delay between clicks to avoid overwhelming the UI
        collapsedButtons.forEach((button, index) => {
            if (button) {
                setTimeout(() => {
                    console.log(`RL - InvestNow Return % Calculator: Expanding collapsed row ${index + 1}`);
                    button.click();
                }, index * 100); // 100ms delay between each click
            }
        });

        // Wait longer for all expansions to complete, then process again
        setTimeout(() => {
            console.log('RL - InvestNow Return % Calculator: Processing after row expansion...');
            // Find and process only the Returns table
            const tables = document.querySelectorAll('table');
            console.log(`RL - InvestNow Return % Calculator: Found ${tables.length} tables after expansion`);

            tables.forEach((table, index) => {
                console.log(`RL - InvestNow Return % Calculator: Checking table ${index + 1} after expansion`);
                if (shouldProcessTable(table)) {
                    console.log(`RL - InvestNow Return % Calculator: Processing Returns table ${index + 1}`);
                    processTable(table);
                }
            });
        }, 2000); // Wait 2 seconds for all expansions
    }

    // Main function to process tables
    function processTables() {
        console.log('RL - InvestNow Return % Calculator: processTables() called');

        // First, extract market values from Valuation table
        extractMarketValues();

        const tables = document.querySelectorAll('table');
        console.log(`RL - InvestNow Return % Calculator: Found ${tables.length} tables on page`);
        let processedCount = 0;

        // Look for Returns tables specifically
        let returnsTableFound = false;

        tables.forEach((table, index) => {
            console.log(`RL - InvestNow Return % Calculator: Examining table ${index + 1}`);
            if (shouldProcessTable(table)) {
                console.log(`RL - InvestNow Return % Calculator: Table ${index + 1} should be processed`);
                returnsTableFound = true;

                // Check if we need to expand rows for this table
                const expandButtons = table.querySelectorAll('button[aria-label="Row Expanded"]');
                // Check if there are any collapsed rows (chevronright icon) that need expanding
                const collapsedButtons = Array.from(expandButtons).filter(btn =>
                    btn.querySelector('chevronrighticon')
                );

                if (collapsedButtons.length > 0) {
                    console.log(`RL - InvestNow Return % Calculator: Found Returns table with ${collapsedButtons.length} collapsed rows, expanding...`);
                    hasExpandedRows = false; // Reset flag to allow expansion
                    expandAllRows();
                    return false; // Return false for now, will be called again after expansion
                }

                if (processTable(table)) {
                    processedCount++;
                    console.log(`RL - InvestNow Return % Calculator: Successfully processed table ${index + 1}`);
                } else {
                    console.log(`RL - InvestNow Return % Calculator: Failed to process table ${index + 1}`);
                }
            } else {
                console.log(`RL - InvestNow Return % Calculator: Table ${index + 1} does not contain required columns`);
            }
        });

        // If no Returns table found but we see expand buttons, try expanding
        if (!returnsTableFound && !hasExpandedRows) {
            const expandButtons = document.querySelectorAll('button[aria-label="Row Expanded"]');
            if (expandButtons.length > 0) {
                console.log('RL - InvestNow Return % Calculator: No Returns table found but expandable rows exist, expanding to reveal Returns data...');
                expandAllRows();
                return false;
            }
        }

        console.log(`RL - InvestNow Return % Calculator: Processed ${processedCount} tables successfully`);
        return processedCount > 0;
    }

    // Check if table contains relevant financial data
    function shouldProcessTable(table) {
        const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());
        console.log(`RL - InvestNow Return % Calculator: Table headers found: [${headers.join(', ')}]`);

        const hasMarketValue = CONFIG.columnHeaders.marketValue.some(header =>
            headers.some(h => h.includes(header))
        );
        const hasUnrealisedGain = CONFIG.columnHeaders.unrealisedGain.some(header =>
            headers.some(h => h.includes(header))
        );
        const hasTotalReturn = CONFIG.columnHeaders.totalReturn.some(header =>
            headers.some(h => h.includes(header))
        );

        // Check for Return table type (has Unrealised and Total Return but no Market Value)
        const isReturnTable = hasUnrealisedGain && hasTotalReturn && !hasMarketValue;
        // Check for Holdings table type (has Market Value and Unrealised)
        const isHoldingsTable = hasMarketValue && hasUnrealisedGain;

        console.log(`RL - InvestNow Return % Calculator: Column check - MarketValue: ${hasMarketValue}, UnrealisedGain: ${hasUnrealisedGain}, TotalReturn: ${hasTotalReturn}`);
        console.log(`RL - InvestNow Return % Calculator: Table type - ReturnTable: ${isReturnTable}, HoldingsTable: ${isHoldingsTable}`);

        return isReturnTable || isHoldingsTable;
    }

    // Process a single table
    function processTable(table) {
        console.log('RL - InvestNow Return % Calculator: processTable() called');
        const headers = Array.from(table.querySelectorAll('th')).map(th => th.textContent.trim());

        // Find column indices
        const marketValueIndex = findColumnIndex(headers, CONFIG.columnHeaders.marketValue);
        const unrealisedGainIndex = findColumnIndex(headers, CONFIG.columnHeaders.unrealisedGain);
        const totalReturnIndex = findColumnIndex(headers, CONFIG.columnHeaders.totalReturn);

        // Determine table type
        const hasMarketValue = marketValueIndex !== -1;
        const hasUnrealisedGain = unrealisedGainIndex !== -1;
        const hasTotalReturn = totalReturnIndex !== -1;
        const isReturnTable = hasUnrealisedGain && hasTotalReturn && !hasMarketValue;
        const isHoldingsTable = hasMarketValue && hasUnrealisedGain;

        console.log(`RL - InvestNow Return % Calculator: Column indices - MarketValue: ${marketValueIndex}, UnrealisedGain: ${unrealisedGainIndex}, TotalReturn: ${totalReturnIndex}`);
        console.log(`RL - InvestNow Return % Calculator: Processing as ${isReturnTable ? 'Return' : 'Holdings'} table`);

        if (!isReturnTable && !isHoldingsTable) {
            console.log('RL - InvestNow Return % Calculator: Table does not match expected patterns');
            return false;
        }

        // Add Return % header if not exists
        if (!headers.includes(CONFIG.returnColumnHeader)) {
            console.log('RL - InvestNow Return % Calculator: Adding Return % header');
            const headerRow = table.querySelector('tr');
            if (headerRow) {
                const th = document.createElement('th');
                th.textContent = CONFIG.returnColumnHeader;
                th.style.textAlign = 'right';
                th.style.fontWeight = 'bold';
                headerRow.appendChild(th);
            }
        } else {
            console.log('RL - InvestNow Return % Calculator: Return % header already exists');
        }

        // Process each data row
        const rows = table.querySelectorAll('tbody tr, tr');
        console.log(`RL - InvestNow Return % Calculator: Found ${rows.length} rows to process`);
        let hasProcessedRows = false;

        rows.forEach((row, index) => {
            // Skip header rows and category sub-headers (rows with expand buttons)
            if (row.querySelector('th') || row.querySelector('p-treetabletoggler')) {
                console.log(`RL - InvestNow Return % Calculator: Skipping header/subheader row ${index + 1}`);
                return;
            }

            const cells = row.querySelectorAll('td');
            const requiredColumns = isReturnTable ?
                Math.max(unrealisedGainIndex, totalReturnIndex) :
                Math.max(marketValueIndex, unrealisedGainIndex, totalReturnIndex);

            if (cells.length <= requiredColumns) {
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} has insufficient cells (${cells.length})`);
                return;
            }

            // Get fund name for storage key - specifically use the first column (Name column)
            let fundName = `Fund_${index}`;
            const nameCell = cells[0]; // First column should be the fund name
            if (nameCell) {
                const nameContent = getCellContent(nameCell);
                if (nameContent && nameContent.trim() && !nameContent.includes('%') && !parseCurrency(nameContent)) {
                    fundName = nameContent.trim();
                }
            }
            console.log(`RL - InvestNow Return % Calculator: Processing fund: "${fundName}"`);

            let marketValue, unrealisedGain, totalReturn;

            if (isReturnTable) {
                // For Return table: calculate market value from total return and unrealised gain
                // totalReturn = marketValue - costBase, where costBase = marketValue - unrealisedGain
                // So: totalReturn = marketValue - (marketValue - unrealisedGain) = unrealisedGain
                // This means the "Total Return" column might actually be something different
                // Let's use the formula: returnPercentage = (totalReturn / (marketValue - unrealisedGain)) * 100
                // But we need to infer market value differently

                // Get cell content more thoroughly - check for nested elements
                const unrealisedCell = cells[unrealisedGainIndex];
                const totalReturnCell = cells[totalReturnIndex];

                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Return table) - Unrealised cell content: "${unrealisedCell?.textContent}", innerHTML: "${unrealisedCell?.innerHTML}"`);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Return table) - TotalReturn cell content: "${totalReturnCell?.textContent}", innerHTML: "${totalReturnCell?.innerHTML}"`);

                const unrealisedContent = getCellContent(unrealisedCell);
                const totalReturnContent = getCellContent(totalReturnCell);

                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Return table) - Extracted Unrealised content: "${unrealisedContent}"`);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Return table) - Extracted TotalReturn content: "${totalReturnContent}"`);

                unrealisedGain = parseCurrency(unrealisedContent);
                totalReturn = parseCurrency(totalReturnContent);

                // For Return table, we'll need to calculate market value differently
                // Let's assume we can derive it from other data or use a different approach
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Return table) - UnrealisedGain: ${unrealisedGain}, TotalReturn: ${totalReturn}`);

                // For now, let's try to calculate return percentage directly if we have the data
                if (unrealisedGain !== null && totalReturn !== null) {
                    // If totalReturn represents the actual return, and unrealisedGain is the unrealized portion
                    // We might need to make assumptions or look for additional data
                    marketValue = null; // We'll handle this specially
                }
            } else {
                // For Holdings table: use original logic
                const marketValueCell = cells[marketValueIndex];
                const unrealisedCell = cells[unrealisedGainIndex];
                const totalReturnCell = cells[totalReturnIndex];

                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) - MarketValue cell: "${marketValueCell?.textContent}"`);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) - Unrealised cell: "${unrealisedCell?.textContent}"`);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) - TotalReturn cell: "${totalReturnCell?.textContent}"`);

                const marketValueContent = getCellContent(marketValueCell);
                const unrealisedContent = getCellContent(unrealisedCell);
                const totalReturnContent = getCellContent(totalReturnCell);

                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) - Extracted MarketValue content: "${marketValueContent}"`);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) - Extracted Unrealised content: "${unrealisedContent}"`);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) - Extracted TotalReturn content: "${totalReturnContent}"`);

                marketValue = parseCurrency(marketValueContent);
                unrealisedGain = parseCurrency(unrealisedContent);
                totalReturn = parseCurrency(totalReturnContent);
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} (Holdings table) values - MarketValue: ${marketValue}, UnrealisedGain: ${unrealisedGain}, TotalReturn: ${totalReturn}`);
            }

            if ((isReturnTable && unrealisedGain !== null && totalReturn !== null) ||
                (!isReturnTable && marketValue !== null && unrealisedGain !== null && totalReturn !== null)) {

                let returnPercentage = null;

                if (isReturnTable) {
                    // For Return table: Use market value from Valuation table with correct formula
                    // costBase = marketValue - unrealisedGain
                    // returnPercentage = (totalReturn / costBase) * 100

                    const marketValueFromValuation = marketValues.get(fundName);
                    console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} looking for market value for "${fundName}": ${marketValueFromValuation}`);

                    if (marketValueFromValuation !== null && marketValueFromValuation !== undefined) {
                        const costBase = marketValueFromValuation - unrealisedGain;
                        console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} calculated costBase: ${marketValueFromValuation} - ${unrealisedGain} = ${costBase}`);

                        if (costBase !== 0 && costBase > 0) {
                            returnPercentage = (totalReturn / costBase) * 100;
                            console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} calculated return percentage: ${totalReturn} / ${costBase} * 100 = ${returnPercentage}%`);
                        } else {
                            console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} costBase is ${costBase}, cannot calculate percentage`);
                        }
                    } else {
                        console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} could not find market value for "${fundName}" in Valuation table`);
                    }
                } else {
                    // Original Holdings table logic
                    const costBase = marketValue - unrealisedGain;
                    console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} calculated costBase: ${costBase}`);

                    if (costBase !== 0) {
                        returnPercentage = (totalReturn / costBase) * 100;
                        console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} calculated returnPercentage: ${returnPercentage}%`);
                    } else {
                        console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} costBase is zero, cannot calculate percentage`);
                    }
                }

                // Find or create the Return % column
                let returnColumnIndex = findColumnIndex(headers, [CONFIG.returnColumnHeader]);
                let returnCell;

                if (returnColumnIndex !== -1 && returnColumnIndex < cells.length) {
                    // Return % column exists, use it
                    returnCell = cells[returnColumnIndex];
                    console.log(`RL - InvestNow Return % Calculator: Updating existing Return % cell for row ${index + 1}`);
                } else {
                    // Return % column doesn't exist or doesn't have enough cells, create it
                    console.log(`RL - InvestNow Return % Calculator: Creating new Return % cell for row ${index + 1}`);
                    returnCell = document.createElement('td');
                    returnCell.style.textAlign = 'right';
                    returnCell.style.fontWeight = 'bold';
                    row.appendChild(returnCell);
                }

                if (returnPercentage !== null && !isNaN(returnPercentage)) {
                    // Store the calculated value
                    returnPercentages.set(fundName, returnPercentage);
                    console.log(`RL - InvestNow Return % Calculator: Stored return percentage for "${fundName}": ${returnPercentage}%`);

                    returnCell.textContent = formatPercentage(returnPercentage);
                    returnCell.style.textAlign = 'right';
                    returnCell.style.fontWeight = 'bold';
                    returnCell.style.color = returnPercentage >= 0 ? '#28a745' : '#dc3545';
                    returnCell.setAttribute('data-return-percentage', 'true'); // Mark our element
                    console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} Return % set to: ${returnCell.textContent}`);
                    hasProcessedRows = true;
                } else {
                    // Check if we have a stored value for this fund
                    const storedPercentage = returnPercentages.get(fundName);
                    if (storedPercentage !== undefined) {
                        console.log(`RL - InvestNow Return % Calculator: Restoring stored return percentage for "${fundName}": ${storedPercentage}%`);
                        returnCell.textContent = formatPercentage(storedPercentage);
                        returnCell.style.textAlign = 'right';
                        returnCell.style.fontWeight = 'bold';
                        returnCell.style.color = storedPercentage >= 0 ? '#28a745' : '#dc3545';
                        returnCell.setAttribute('data-return-percentage', 'true'); // Mark our element
                        console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} Return % restored to: ${returnCell.textContent}`);
                        hasProcessedRows = true;
                    } else {
                        returnCell.textContent = 'N/A';
                        returnCell.style.textAlign = 'right';
                        returnCell.style.color = '#6c757d';
                        returnCell.setAttribute('data-return-percentage', 'true'); // Mark our element
                        console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} Return % set to N/A`);
                    }
                }

                hasProcessedRows = true;
            } else {
                console.log(`RL - InvestNow Return % Calculator: Row ${index + 1} has null values, skipping`);
            }
        });

        console.log(`RL - InvestNow Return % Calculator: Table processing complete, hasProcessedRows: ${hasProcessedRows}`);
        return hasProcessedRows;
    }

    // Find column index by header text
    function findColumnIndex(headers, possibleHeaders) {
        console.log(`RL - InvestNow Return % Calculator: findColumnIndex() searching for headers: [${possibleHeaders.join(', ')}] in [${headers.join(', ')}]`);
        for (const header of possibleHeaders) {
            const index = headers.findIndex(h => h.includes(header));
            if (index !== -1) {
                console.log(`RL - InvestNow Return % Calculator: Found header "${header}" at index ${index}`);
                return index;
            }
        }
        console.log(`RL - InvestNow Return % Calculator: No matching header found for [${possibleHeaders.join(', ')}]`);
        return -1;
    }

    // Parse currency value from text
    function parseCurrency(text) {
        console.log(`RL - InvestNow Return % Calculator: parseCurrency() parsing: "${text}"`);
        if (!text) {
            console.log('RL - InvestNow Return % Calculator: parseCurrency() - empty text, returning null');
            return null;
        }

        // Clean the text more thoroughly
        const cleanText = text.toString().replace(/[$,\s]/g, '').trim();
        console.log(`RL - InvestNow Return % Calculator: parseCurrency() cleaned text: "${cleanText}"`);

        // If still empty after cleaning, return null
        if (!cleanText || cleanText === '') {
            console.log('RL - InvestNow Return % Calculator: parseCurrency() - still empty after cleaning');
            return null;
        }

        // Try multiple regex patterns
        const patterns = [
            /-?\d+\.?\d*/,  // Standard decimal
            /-?\d+,?\d*/,   // Comma as decimal separator
            /-?\d+/,        // Integer only
        ];

        let result = null;
        for (const pattern of patterns) {
            const match = cleanText.match(pattern);
            if (match && match[0]) {
                const parsed = parseFloat(match[0].replace(',', '.'));
                if (!isNaN(parsed)) {
                    result = parsed;
                    break;
                }
            }
        }

        console.log(`RL - InvestNow Return % Calculator: parseCurrency() result: ${result}`);
        return result;
    }

    // Enhanced cell content extraction for Angular apps
    function getCellContent(cell) {
        if (!cell) return null;

        // Try multiple methods to get the actual content
        const methods = [
            () => cell.textContent?.trim(),
            () => cell.innerText?.trim(),
            () => {
                // Look for nested divs with content
                const nestedDivs = cell.querySelectorAll('div');
                for (const div of nestedDivs) {
                    const content = div.textContent?.trim();
                    if (content && content !== '') {
                        return content;
                    }
                }
                return null;
            },
            () => {
                // Try to get value from Angular data attributes
                const ngContent = cell.getAttribute('ng-reflect-model') ||
                                 cell.getAttribute('ng-value') ||
                                 cell.getAttribute('value');
                return ngContent?.trim();
            }
        ];

        for (const method of methods) {
            try {
                const content = method();
                if (content && content !== '' && content !== ' ') {
                    console.log(`RL - InvestNow Return % Calculator: Found content using method: ${content}`);
                    return content;
                }
            } catch (e) {
                console.log('RL - InvestNow Return % Calculator: Method failed:', e.message);
            }
        }

        return null;
    }

    // Format percentage with 2 decimal places
    function formatPercentage(value) {
        const formatted = `${value.toFixed(2)}%`;
        console.log(`RL - InvestNow Return % Calculator: formatPercentage() ${value} -> ${formatted}`);
        return formatted;
    }

    // Wait for content to load
    function waitForContent(callback, retries = 0) {
        console.log(`RL - InvestNow Return % Calculator: waitForContent() called, retries: ${retries}/${CONFIG.maxRetries}`);
        console.log(`RL - InvestNow Return % Calculator: Document readyState: ${document.readyState}`);
        console.log(`RL - InvestNow Return % Calculator: Document body exists: ${!!document.body}`);
        console.log(`RL - InvestNow Return % Calculator: Current URL: ${window.location.href}`);

        if (document.readyState === 'complete' && document.body) {
            // Check if tables exist
            const tables = document.querySelectorAll('table');
            console.log(`RL - InvestNow Return % Calculator: Document ready, found ${tables.length} tables`);

            // Log all elements to help debug
            const allElements = document.querySelectorAll('*');
            console.log(`RL - InvestNow Return % Calculator: Total elements on page: ${allElements.length}`);

            // Look for any table-like elements
            const tableElements = document.querySelectorAll('table, .table, [role="table"]');
            console.log(`RL - InvestNow Return % Calculator: Found ${tableElements.length} table-like elements`);

            if (tables.length > 0) {
                console.log('RL - InvestNow Return % Calculator: Tables found, executing callback');
                callback();
                return;
            } else {
                console.log('RL - InvestNow Return % Calculator: No tables found, checking page content...');
                // Log some page content to debug
                const bodyText = document.body?.innerText?.substring(0, 500) || 'No body text';
                console.log(`RL - InvestNow Return % Calculator: Page content preview: ${bodyText}`);

                // Check if we're on dashboard and need to wait for navigation
                if (window.location.href.includes('/dashboard')) {
                    console.log('RL - InvestNow Return % Calculator: On dashboard page - will wait for navigation to Holdings/Returns');
                }
            }
        }

        if (retries < CONFIG.maxRetries) {
            console.log(`RL - InvestNow Return % Calculator: Retrying in ${CONFIG.retryDelay}ms...`);
            setTimeout(() => waitForContent(callback, retries + 1), CONFIG.retryDelay);
        } else {
            console.log('RL - InvestNow Return % Calculator: Max retries reached, giving up');
            console.log('RL - InvestNow Return % Calculator: Final page state:');
            console.log(`RL - InvestNow Return % Calculator: - URL: ${window.location.href}`);
            console.log(`RL - InvestNow Return % Calculator: - Title: ${document.title}`);
            console.log(`RL - InvestNow Return % Calculator: - ReadyState: ${document.readyState}`);

            // Even if no tables found initially, still set up observer and click handlers
            // This will catch navigation to Holdings/Returns pages
            console.log('RL - InvestNow Return % Calculator: Setting up observers anyway for navigation detection');
            setupAfterInit();
        }
    }

    // Set up observers and event handlers after initialization
    function setupAfterInit() {
        console.log('RL - InvestNow Return % Calculator: Setting up post-initialization observers');

        // Set up observer for dynamic content changes
        const observer = setupObserver();

        // Also reprocess on tab changes or clicks
        document.addEventListener('click', (e) => {
            // Check if click is on a tab or navigation element
            const target = e.target;
            if (target.matches('[role="tab"], .tab, .nav-link, button') ||
                target.closest('[role="tab"], .tab, .nav-link, button')) {
                console.log('RL - InvestNow Return % Calculator: Click detected on tab/navigation, triggering reprocess');
                setTimeout(processTables, 1000);
            }
        });

        // Periodic reprocessing for safety
        setInterval(() => {
            console.log('RL - InvestNow Return % Calculator: Periodic reprocess triggered');
            processTables();
        }, 5000);

        console.log('RL - InvestNow Return % Calculator: Post-initialization setup complete');
    }

    // Set up MutationObserver to detect content changes
    function setupObserver() {
        console.log('RL - InvestNow Return % Calculator: Setting up MutationObserver');
        const observer = new MutationObserver((mutations) => {
            let shouldReprocess = false;

            mutations.forEach((mutation) => {
                if (mutation.type === 'childList') {
                    // Check if new tables were added or if existing tables changed
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeType === Node.ELEMENT_NODE) {
                            if (node.tagName === 'TABLE' || node.querySelector('table')) {
                                console.log('RL - InvestNow Return % Calculator: MutationObserver detected table changes');
                                shouldReprocess = true;
                            }
                        }
                    });
                }
            });

            if (shouldReprocess) {
                console.log('RL - InvestNow Return % Calculator: MutationObserver triggering reprocess');
                setTimeout(processTables, 500);
            }
        });

        observer.observe(document.body, CONFIG.observerConfig);
        console.log('RL - InvestNow Return % Calculator: MutationObserver setup complete');
        return observer;
    }

    // Initialize the script
    function init() {
        console.log('RL - InvestNow Return % Calculator: Initializing...');

        // Wait for initial content
        waitForContent(() => {
            console.log('RL - InvestNow Return % Calculator: Processing initial tables...');
            processTables();

            // Set up post-initialization observers
            setupAfterInit();

            console.log('RL - InvestNow Return % Calculator: Initialization complete');
        });
    }

    // Start the script
    if (document.readyState === 'loading') {
        console.log('RL - InvestNow Return % Calculator: Document still loading, waiting for DOMContentLoaded');
        document.addEventListener('DOMContentLoaded', init);
    } else {
        console.log('RL - InvestNow Return % Calculator: Document already loaded, initializing immediately');
        init();
    }

})();