Robinhood Order Calculator

Adds a button to robinhood.com that calculates the amount of shares you can buy and initiates the order automatically.

// ==UserScript==
// @name         Robinhood Order Calculator
// @namespace    http://tampermonkey.net/
// @version      1.0
// @description  Adds a button to robinhood.com that calculates the amount of shares you can buy and initiates the order automatically.
// @author       Game Abuse Studios
// @match        https://robinhood.com/*
// @license MIT
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // --- Injected CSS for the new button style ---
    GM_addStyle(`
        @keyframes scanline {
            0% { transform: translateY(-100%); }
            100% { transform: translateY(100%); }
        }

        .tech-btn {
            /* Gradient Border Technique - BORDER THINNER */
            border: 1px solid transparent;
            /* UPDATED: Inside of button uses #1E2124 */
            background: linear-gradient(#1E2124, #1E2124) padding-box,
                        linear-gradient(to right, royalblue, blueviolet) border-box;

            /* Set to a more standard 'normal' font */
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
            font-weight: bold;
            box-shadow: 0 2px 5px rgba(0,0,0,0.4);
            transition: all 0.2s ease-in-out;
            cursor: pointer;
            position: relative;
            overflow: hidden;
        }

        /* NEW: Style for the text span to create the gradient effect */
        .tech-btn span {
            background: linear-gradient(to right, royalblue, blueviolet);
            -webkit-background-clip: text;
            background-clip: text;
            color: transparent;
            transition: all 0.2s ease-in-out;
        }

        .tech-btn:hover {
            /* Brighter gradient on hover for the border */
            /* UPDATED: Inside of button on hover uses #1E2124 */
            background: linear-gradient(#1E2124, #1E2124) padding-box,
                        linear-gradient(to right, dodgerblue, mediumorchid) border-box;
            transform: scale(1.03);
        }

        /* NEW: Update the text span's gradient on hover */
        .tech-btn:hover span {
             background: linear-gradient(to right, dodgerblue, mediumorchid);
            -webkit-background-clip: text;
            background-clip: text;
        }

        /* Scan line animation on hover */
        .tech-btn::after {
            content: '';
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: linear-gradient(to bottom, transparent 0%, rgba(138, 43, 226, 0.1) 50%, transparent 100%);
            opacity: 0;
            transform: translateY(-100%);
            transition: opacity 0.2s ease;
        }

        .tech-btn:hover::after {
            opacity: 1;
            animation: scanline 0.6s linear;
        }
    `);


    console.log("Robinhood Tampermonkey script is running!");

    /**
     * Finds and parses a numerical value from an element's text content.
     * @param {string} textLabel The text label to find (e.g., "buying power available").
     * @returns {number|null} The parsed numerical value or null.
     */
    function findAndParseValueByLabel(textLabel) {
        const potentialElements = document.querySelectorAll('span, div');
        let value = null;
        potentialElements.forEach(element => {
            const textContent = element.textContent.trim();
            if (textContent.includes(textLabel)) {
                // Assuming the value is in a sibling or a nested span
                const valueText = (element.previousElementSibling && element.previousElementSibling.textContent) || textContent;
                const match = valueText.match(/\$([\d,]+\.?\d*)/);
                if (match && match[1]) {
                    value = parseFloat(match[1].replace(/,/g, ''));
                }
            }
        });
        return value;
    }

    // Main function to perform the calculation and button click
    async function runScript() {
        console.log("Starting script...");

        try {
            console.log("Step 1: Attempting to find buying power and market price.");

            const buyingPower = findAndParseValueByLabel("buying power available");
            const stockPrice = findAndParseValueByLabel("Market Price");

            if (buyingPower === null || stockPrice === null) {
                console.error("Robinhood Script: Could not find one or both of the required values.");
                return;
            }

            console.log(`Robinhood Script: Final stock price found: $${stockPrice}`);
            console.log(`Robinhood Script: Final buying power found: $${buyingPower}`);

            // Subtract 7% from buying power as per Robinhood's message
            const adjustedBuyingPower = buyingPower * 0.93;

            // Calculate the number of shares to buy
            const sharesToBuy = Math.floor(adjustedBuyingPower / stockPrice);

            if (sharesToBuy <= 0) {
                console.log("Robinhood Script: Cannot buy at least 1 share with available funds.");
                return;
            }

            console.log(`Robinhood Script: Shares to buy: ${sharesToBuy}`);

            // Input the calculated value into the text box by directly interacting with React state
            const sharesSelector = '[data-testid="OrderFormRows-Shares"]';
            const sharesInput = document.querySelector(sharesSelector);

            if (!sharesInput) {
                console.log("Robinhood Script: Could not find the shares input field.");
                return;
            }

            console.log("Robinhood Script: Found the shares input field. Now setting value via React state.");

            const reactKey = Object.keys(sharesInput).find(key => key.startsWith('__react'));

            if (reactKey) {
                const reactProps = sharesInput[reactKey].pendingProps;

                // Create a synthetic event to trigger React's onChange handler
                const syntheticEvent = {
                    target: {
                        value: sharesToBuy.toString()
                    }
                };

                // Call the original onChange function to trigger the update
                if (reactProps && typeof reactProps.onChange === 'function') {
                    reactProps.onChange(syntheticEvent);
                    console.log(`Robinhood Script: Share value successfully set to ${sharesToBuy}.`);
                } else {
                    console.error("Robinhood Script: React onChange function not found.");
                }

                // Also set the value directly on the DOM element for good measure
                sharesInput.value = sharesToBuy.toString();
            } else {
                console.error("Robinhood Script: Could not find the React internal key on the element.");
            }
        } catch (error) {
            console.error("Robinhood Script: An error occurred during execution:", error);
        }
    }

    // Add the button when the document is ready
    function addFloatingButton() {
        // Find the parent of the buttons
        const referenceSelector = 'div[class*="_1oJVwyMO-j1ETeFAdtVHEW"]';
        const referenceElement = document.querySelector(referenceSelector);

        if (document.getElementById('auto-set-value-btn') || !referenceElement) {
            return;
        }

        const wrapperDiv = document.createElement('div');
        wrapperDiv.style.cssText = `
            display: flex;
            justify-content: center;
            align-items: flex-start;
            width: 100%;
            margin-bottom: 12px;
        `;

        const button = document.createElement('button');
        button.id = 'auto-set-value-btn';

        // **UPDATED**: Wrap the text in a <span> for the gradient style to target
        button.innerHTML = '<span>Calculate And Order</span>';

        // Apply the 'tech-btn' class for the new look and feel.
        button.className = 'tech-btn';

        // Keep only the necessary inline styles for layout (size, position, etc.).
        button.style.cssText = `
            border-radius: 24px;
            padding: 0;
            font-size: 19px; /* FONT SMALLER */
            width: 280.4px;
            height: 44px;
            margin-top: -10px;
            display: flex;
            align-items: center;
            justify-content: center;
        `;

        button.addEventListener('click', runScript);
        wrapperDiv.appendChild(button);
        referenceElement.appendChild(wrapperDiv);
        console.log("Robinhood Tampermonkey script: 'Auto Calculate & Set Shares' button added as a child of the specified element.");
    }

    // Use a MutationObserver to watch for when the order form is added to the page
    const observer = new MutationObserver(() => {
        if (document.querySelector('[data-testid="OrderFormRows-Shares"]')) {
            addFloatingButton();
        }
    });

    observer.observe(document.body, {
        childList: true,
        subtree: true
    });

    // Also try to add the button on initial page load if the element is already there
    if (document.querySelector('[data-testid="OrderFormRows-Shares"]')) {
        addFloatingButton();
    }
})();