From 21a2c383b840f74e8b5b51567ef16ca6e686e0d1 Mon Sep 17 00:00:00 2001 From: Logan Gatlin Date: Mon, 24 Jan 2022 00:07:44 -0600 Subject: [PATCH] Working prototype --- src/__pycache__/trading.cpython-310.pyc | Bin 0 -> 6896 bytes src/main.py | 26 +++ src/trading.py | 224 ++++++++++++++++++++++++ 3 files changed, 250 insertions(+) create mode 100644 src/__pycache__/trading.cpython-310.pyc create mode 100644 src/main.py create mode 100644 src/trading.py diff --git a/src/__pycache__/trading.cpython-310.pyc b/src/__pycache__/trading.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..df4487a7b47d7db33453fc10b6fc779451f07a76 GIT binary patch literal 6896 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 ") + words = _in.split(" ") + if len(words) == 0: + continue + cmd = words.pop(0) + args = [] + kwargs = {} + for w in words: + components = w.split("=") + if len(components) == 1: + args.append(w) + elif len(components) == 2: + kwargs[components[0]] = components[1] + else: + print("Invalid parameter: " + w) + continue + + trader.interpret_command(cmd, *args, **kwargs) + # trader.buy("GOOG", 1) diff --git a/src/trading.py b/src/trading.py new file mode 100644 index 0000000..a27a5b3 --- /dev/null +++ b/src/trading.py @@ -0,0 +1,224 @@ +import mechanicalsoup +import getpass +import bs4 +import selenium + + +def _convert_symbol(dec: str): + mult = 1 + if dec.find("M") != -1: + mult = 1000000 + elif dec.find("B") != -1: + mult = 1000000000 + dec = dec.replace("M", "") + dec = dec.replace("B", "") + dec = dec.replace(",", "") + return float(dec) * mult + + +def _convert_decimal(dec: str): + dec = dec.replace("%", "") + return float(dec) / 100 + + +def _sum_total(stocks: list[dict]): + ret = 0 + for s in stocks: + ret += s["price"] * s["amount"] + return ret + + +def _most_expensive(stocks: list[dict]): + if len(stocks) == 0: + return 0 + largest = 0 + index = 0 + for s in range(len(stocks)): + if stocks[s]["price"] >= largest: + largest = stocks[s]["price"] + index = s + return index + + +def _least_expensive(stocks: list[dict]): + if len(stocks) == 0: + return 0 + smallest = 9999999999 + index = 0 + for s in range(len(stocks)): + if stocks[s]["price"] <= smallest: + smallest = stocks[s]["price"] + index = s + return index + + +class StockAnalyzer(mechanicalsoup.StatefulBrowser): + def __init__(self): + super().__init__(user_agent="Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0") + + def recommend_buy(self, budget): + budget = float(budget) + data = [] + self.open("https://finance.yahoo.com/u/yahoo-finance/watchlists/fiftytwo-wk-low") + table = self.page.find("table", {"class": "cwl-symbols W(100%)"}) + for item in table.find_all("td"): + data.append(item.get_text()) + count = len(data) / 9 + companies = [] + cart = [] + for i in range(int(count)): + start = i * 9 + company = { + "symbol": data[start + 0], + "name": data[start + 1], + "price": float(data[start + 2]), + "change-absolute": float(data[start + 3]), + "change-relative": _convert_decimal(data[start + 4]), + "market-cap": _convert_symbol(data[start + 8]), + "amount": 1 + } + if company["price"] >= budget / 2: + continue + companies.append(company) + prediction = self.get_prediction(company["symbol"]) + if prediction == "up": + cart.append(company) + + total = _sum_total(cart) + while total > budget: + i = _most_expensive(cart) + cart.pop(i) + total = _sum_total(cart) + while total + cart[_least_expensive(cart)]["price"] < budget: + for stock in cart: + if total + stock["price"] <= budget: + stock["amount"] += 1 + total = _sum_total(cart) + return cart + + def get_prediction(self, symbol: str): + status = self.open("https://finance.yahoo.com/quote/" + symbol) + if not status.ok: + print("Invalid symbol: " + symbol) + return "invalid" + value = self.page.find("div", {"Fw(b) Fl(end)--m Fz(s) C($primaryColor"}).get_text() + if value == "Near Fair Value": + return "same" + if value == "Overvalued": + return "down" + if value == "Undervalued": + return "up" + + +class TradingBot(mechanicalsoup.StatefulBrowser): + def __init__(self): + super().__init__(user_agent="Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0") + self.logged_in = False + self.commands = { + "help": self.help, + "quit": quit, + "login": self.login, + "buy": self.buy, + "info": self.info, + "autobuy": self.autobuy + } + self.analyzer = StockAnalyzer() + + def help(self): + print("Unimplemented") + + def interpret_command(self, cmd, *args, **kwargs): + cmd = cmd.lower() + if cmd not in self.commands.keys(): + print("Invalid command " + cmd + ", type help for more info") + return + return self.commands[cmd](*args, **kwargs) + + def login(self, *args, **kwargs): + # Navigate to login page + self.open("https://www.howthemarketworks.com/login") + self.select_form('form[action="/login"]') + # Get login info + _username = args[0] if len(args) > 0 else "" + _password = args[1] if len(args) > 1 else "" + for key in kwargs.keys(): + key = key.lower() + if key == "username" or key == "user": + _username = kwargs[key] + if key == "password" or key == "pass": + _password = kwargs[key] + if _username == "": + _username = input("Username: ") + if _password == "": + _password = getpass.getpass(prompt="Password: ") + # Submit form and get result + self.__setitem__("UserName", _username) + self.__setitem__("Password", _password) + result = self.submit_selected() + self.logged_in = result.ok and self.url == "https://www.howthemarketworks.com/accounting/dashboard" + if self.logged_in: + print("Log-in successful") + else: + print("Log-in failed") + return result + + def buy(self, *args, **kwargs): + if not self.logged_in: + print("Not logged in") + return None + + symbol = args[0] if len(args) > 0 else "" + quantity = args[1] if len(args) > 1 else "" + for key in kwargs.keys(): + key = key.lower() + if key == "sym" or key == "symbol": + symbol = kwargs[key] + if key == "quantity" or key == "q": + quantity = kwargs[key] + + if symbol == "": + symbol = input("Stock Symbol: ") + if quantity == "": + quantity = input("Quantity: ") + + self.open("https://www.howthemarketworks.com/trading/equities") + self.select_form() + self.__setitem__("Symbol", symbol) + self.__setitem__("Quantity", quantity) + result = self.submit_selected() + if result.ok: + print("Successfully bought " + str(quantity) + " shares of " + symbol) + else: + print("Failed to purchase " + symbol + ". Check that the symbol is correct and funds are sufficient") + return result + + def info(self): + if not self.logged_in: + print("Not logged in") + return None + self.open("https://www.howthemarketworks.com/accounting/accountbalance") + self.launch_browser() + + def autobuy(self, budget=1000): + print("Analyzing market, this may take time") + stocks = self.analyzer.recommend_buy(budget) + total = _sum_total(stocks) + symbols = [] + print("The following stocks have been selected:") + for s in stocks: + symbols.append(s["symbol"]) + print(s["name"] + " (" + s["symbol"] + ") ---- " + str(s["amount"]) + " share(s) totalling: $" + str(round(s["amount"] * s["price"], 2))) + print("Purchase will total $" + str(round(total, 2))) + print("Make sure you have sufficient funds before continuing") + decision = input("Finalize purchase? Y/N - ") + if decision.lower() != "y": + print("Cancelled order") + return + for s in stocks: + self.buy(s["symbol"], s["amount"]) + + +if __name__ == "__main__": + ana = StockAnalyzer() + stonks = ana.recommend_buy(999999) + print(stonks)