Robinhood QOL Trading

A Robinhood quality of life script

// ==UserScript==
// @name        Robinhood QOL Trading
// @namespace   Trading
// @match       https://robinhood.com/legend/layout/*
// @grant       none
// @version     1.4
// @license     MIT
// @author      AdSam
// @description A Robinhood quality of life script
// ==/UserScript==

const MOD_KEY = "ctrl";

const Hotkeys = {
    BUY: "b",
    SELL: "s",
    TRAILING_STOP: "l",
    SET_SHARES: "z",
    SET_AMOUNT: "x",
};

const Actions = {
    [Hotkeys.BUY]: async () => await buy(shares),
    [Hotkeys.SELL]: async () => await sell(shares),
    [Hotkeys.TRAILING_STOP]: async () => await trailing_stop(shares, amount),
    [Hotkeys.SET_SHARES]: () => {
        shares = input("Enter number of shares:", shares);
    },
    [Hotkeys.SET_AMOUNT]: () => {
        amount = input("Enter trailing amount:");
    },
};

const TIMEOUT = 5;

function findElement(selector, index, timeout = 0) {
    let iter = 0;

    return new Promise((resolve, reject) => {
        const interval = setInterval(() => {
            if (timeout != 0 && iter >= timeout * 1000) {
                reject(new Error("Timeout"));
            }

            const elements = document.querySelectorAll(selector);

            if (elements[index]) {
                clearInterval(interval);
                resolve(elements[index]);
            }

            iter++;
        }, 1);
    });
}

function input(message, _default) {
    userInput = prompt(message);

    if (userInput != null) {
        userInput = userInput
            .split("")
            .filter((char) => !isNaN(char) || char === ".")
            .join("");

        return parseFloat(userInput || _default);
    } else {
        return parseFloat(_default);
    }
}

async function buy(shares) {
    let menuBtn = await findElement(
        '[data-testid="legend_buy_button"]',
        0,
        TIMEOUT
    );
    menuBtn.click();

    let dropdown = await findElement('[role="combobox"]', 0, TIMEOUT);
    dropdown.click();

    let market = await findElement('[role="option"]', 1, TIMEOUT);
    market.click();

    let quantity = await findElement('[name="quantity"]', 0, TIMEOUT);
    quantity.focus();

    document.execCommand("selectAll");
    document.execCommand("insertText", false, shares.toString());

    while (quantity.value != shares.toString()) {}

    let buyBtn = await findElement('[type="submit"]', 0, TIMEOUT);
    buyBtn.click();
}

async function sell(shares) {
    let menuBtn = await findElement(
        '[data-testid="legend_sell_button"]',
        0,
        TIMEOUT
    );
    menuBtn.click();

    let dropdown = await findElement('[role="combobox"]', 0, TIMEOUT);
    dropdown.click();

    let market = await findElement('[role="option"]', 1, TIMEOUT);
    market.click();

    let quantity = await findElement('[name="quantity"]', 0, TIMEOUT);
    quantity.focus();

    document.execCommand("selectAll");
    document.execCommand("insertText", false, shares.toString());

    while (quantity.value != shares.toString()) {}

    let sellBtn = await findElement('[type="submit"]', 0, TIMEOUT);
    sellBtn.click();
}

async function trailing_stop(shares, amount) {
    let menuBtn = await findElement(
        '[data-testid="legend_sell_button"]',
        0,
        TIMEOUT
    );
    menuBtn.click();

    let dropdown = await findElement('[role="combobox"]', 0, TIMEOUT);
    dropdown.click();

    let ts = await findElement('[role="option"]', 4, TIMEOUT);
    ts.click();

    let quantity = await findElement('[name="quantity"]', 0, TIMEOUT);
    quantity.focus();

    document.execCommand("selectAll");
    document.execCommand("insertText", false, shares.toString());

    let trail = await findElement('[role="combobox"]', 1, TIMEOUT);
    trail.click();

    let a = await findElement('[role="option"]', 8, TIMEOUT);
    a.click();

    let ta = await findElement('[name="trailAmount"]', 1, TIMEOUT);
    ta.focus();

    document.execCommand("selectAll");
    document.execCommand("insertText", false, amount.toString());

    let tif = await findElement('[role="combobox"]', 2, TIMEOUT);
    tif.click();

    let gtc = await findElement('[role="option"]', 6, TIMEOUT);
    gtc.click();

    while (
        quantity.value != shares.toString() ||
        !ta.value.includes(amount.toString())
    ) {}

    await new Promise((r) => setTimeout(r, 100));

    let sellBtn = await findElement('[type="submit"]', 0, TIMEOUT);
    sellBtn.click();
}

let shares = 0;
let amount = 0;

let debounce = false;

document.addEventListener("keydown", async (event) => {
    if (!debounce) {
        debounce = true;

        try {
            if (
                event[MOD_KEY.toLowerCase() + "Key"] &&
                Actions[event.key.toLowerCase()]
            ) {
                event.preventDefault();
                await Actions[event.key]();
            }
        } catch (e) {
            console.warn(e);
        }

        debounce = false;
    }
});