diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..26d3352
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/StocksGame.iml b/.idea/StocksGame.iml
new file mode 100644
index 0000000..d0876a7
--- /dev/null
+++ b/.idea/StocksGame.iml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..f281ec3
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..d1e22ec
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..f21374d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/__pycache__/picker.cpython-38.pyc b/src/__pycache__/picker.cpython-38.pyc
new file mode 100644
index 0000000..d6b0299
Binary files /dev/null and b/src/__pycache__/picker.cpython-38.pyc differ
diff --git a/src/__pycache__/stock.cpython-38.pyc b/src/__pycache__/stock.cpython-38.pyc
new file mode 100644
index 0000000..2d15eb0
Binary files /dev/null and b/src/__pycache__/stock.cpython-38.pyc differ
diff --git a/src/__pycache__/trading.cpython-38.pyc b/src/__pycache__/trading.cpython-38.pyc
new file mode 100644
index 0000000..f406a9b
Binary files /dev/null and b/src/__pycache__/trading.cpython-38.pyc differ
diff --git a/src/main.py b/src/main.py
index 6087b9b..2e51336 100644
--- a/src/main.py
+++ b/src/main.py
@@ -6,21 +6,4 @@ if __name__ == "__main__":
trader = TradingBot()
while True:
_in = input("> ")
- 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)
+ trader.interpret_command(_in.lower())
diff --git a/src/picker.py b/src/picker.py
new file mode 100644
index 0000000..2aeca9d
--- /dev/null
+++ b/src/picker.py
@@ -0,0 +1,79 @@
+import mechanicalsoup
+from stock import StockList
+
+
+class StockAnalyzer(mechanicalsoup.StatefulBrowser):
+ yahoo_52wk_low = "https://finance.yahoo.com/u/yahoo-finance/watchlists/fiftytwo-wk-low"
+ yahoo_52wk_high = "https://finance.yahoo.com/u/yahoo-finance/watchlists/fiftytwo-wk-high"
+ yahoo_most_added = "https://finance.yahoo.com/u/yahoo-finance/watchlists/most-added"
+ yahoo_active_spacs = "https://finance.yahoo.com/u/yahoo-finance/watchlists/most-active-spacs"
+ yahoo_covid = "https://finance.yahoo.com/u/trea/watchlists/the-fight-against-covid19"
+ yahoo_small_cap = "https://finance.yahoo.com/u/yahoo-finance/watchlists/most-active-small-cap-stocks"
+
+ 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 get_prediction(self, symbol: str):
+ status = self.open("https://finance.yahoo.com/quote/" + symbol)
+ if not status.ok:
+ print("Invalid symbol: " + symbol)
+ return "invalid"
+ element = self.page.find("div", {"Fw(b) Fl(end)--m Fz(s) C($primaryColor"})
+ if element is None:
+ return "same"
+ value = element.get_text()
+ if value is None:
+ return "same"
+ if value == "Near Fair Value":
+ return "same"
+ if value == "Overvalued":
+ return "down"
+ if value == "Undervalued":
+ return "up"
+
+ def grab_yahoo_watchlist(self, link):
+ self.open(link)
+ table = self.page.find("table", {"class": "cwl-symbols W(100%)"})
+ data = []
+ for item in table.find_all("td"):
+ data.append(item.get_text())
+ count = len(data) / 9
+ companies = StockList()
+ for i in range(int(count)):
+ start = i * 9
+ # WHY TF ARE THERE 2 SYMBOLS FOR GOOGLE?????
+ if data[start + 1] == "GOOGL":
+ data[start + 1] = "GOOG"
+ company = [
+ data[start + 0],
+ data[start + 1],
+ data[start + 2],
+ data[start + 3],
+ data[start + 4],
+ data[start + 8],
+ 1,
+ self.get_prediction(data[start + 0])
+ ]
+ companies.add_stock(company)
+ return companies
+
+ def recommend_buy(self, budget):
+ print("Scanning largest 52 week losses...")
+ companies = self.grab_yahoo_watchlist(self.yahoo_52wk_low)
+ print("Scanning largest 52 week gains...")
+ companies.concat(self.grab_yahoo_watchlist(self.yahoo_52wk_high), False)
+ print("Scanning most watched stocks...")
+ companies.concat(self.grab_yahoo_watchlist(self.yahoo_most_added), False)
+ print("Scanning most active SPAC's...")
+ companies.concat(self.grab_yahoo_watchlist(self.yahoo_active_spacs), False)
+ print("Scanning COVID-19 related stocks...")
+ companies.concat(self.grab_yahoo_watchlist(self.yahoo_covid), False)
+ print("Scanning small market cap stocks...")
+ companies.concat(self.grab_yahoo_watchlist(self.yahoo_small_cap), False)
+ print("Sorting results...")
+ companies.keep_recommended()
+ print("Budgeting...")
+ companies.budget(int(budget))
+ return companies
+
+
diff --git a/src/stock.py b/src/stock.py
new file mode 100644
index 0000000..f9876e9
--- /dev/null
+++ b/src/stock.py
@@ -0,0 +1,119 @@
+class StockList:
+ def __init__(self):
+ self.symbol = []
+ self.name = []
+ self.price = []
+ self.change_absolute = []
+ self.change_relative = []
+ self.market_cap = []
+ self.amount = []
+ self.prediction = []
+
+ def sanity_check(self):
+ # debug
+ assert len(self.symbol) == len(self.name)
+ assert len(self.name) == len(self.price)
+ assert len(self.price) == len(self.change_absolute)
+ assert len(self.change_absolute) == len(self.change_relative)
+ assert len(self.change_relative) == len(self.market_cap)
+ assert len(self.market_cap) == len(self.amount)
+ assert len(self.amount) == len(self.prediction)
+
+ def count(self):
+ self.sanity_check()
+ return len(self.symbol)
+
+ def total(self):
+ total = 0
+ for i in range(self.count()):
+ total += self.price[i] * self.amount[i]
+ return total
+
+ def add_stock(self, data, allow_duplicates=True):
+ dupe = data[0] in self.symbol
+ if dupe and not allow_duplicates:
+ index = self.symbol.index(data[0])
+ self.amount[index] += int(data[6])
+ return
+ self.symbol += [str(data[0])]
+ self.name += [str(data[1])]
+ self.price += [float(data[2])]
+ self.change_absolute += [str(data[3])]
+ self.change_relative += [str(data[4])]
+ self.market_cap += [str(data[5])]
+ self.amount += [int(data[6])]
+ self.prediction += [str(data[7])]
+ self.sanity_check()
+
+ def concat(self, stocks, allow_duplicates=True):
+ for i in range(stocks.count()):
+ self.add_stock(stocks.get_stock(i), allow_duplicates)
+
+ def remove_stock(self, index):
+ if self.count() <= index:
+ print("Out of range")
+ return
+ self.symbol.pop(index)
+ self.name.pop(index)
+ self.price.pop(index)
+ self.change_absolute.pop(index)
+ self.change_relative.pop(index)
+ self.market_cap.pop(index)
+ self.amount.pop(index)
+ self.prediction.pop(index)
+
+ def get_stock(self, index):
+ return [
+ self.symbol[index],
+ self.name[index],
+ self.price[index],
+ self.change_absolute[index],
+ self.change_relative[index],
+ self.market_cap[index],
+ self.amount[index],
+ self.prediction[index]
+ ]
+
+ def most_expensive(self):
+ index = 0
+ highest = 0
+ for i in range(self.count()):
+ if self.price[i] >= highest:
+ index = i
+ highest = self.price[i]
+ return index
+
+ def least_expensive(self):
+ smallest = 9999999999
+ index = 0
+ for s in range(self.count()):
+ if self.price[s] <= smallest:
+ smallest = self.price[s]
+ index = s
+ return index
+
+ def budget(self, budget):
+ total = self.total()
+ while total > budget:
+ i = self.most_expensive()
+ self.remove_stock(i)
+ total = self.total()
+ print(self.name)
+ if self.count() == 0:
+ print("No stocks found")
+ return
+ while total + self.price[self.least_expensive()] < budget:
+ for stock in range(self.count()):
+ if total + self.price[stock] <= budget:
+ self.amount[stock] += 1
+ total = self.total()
+
+ def keep_recommended(self):
+ i = 0
+ size = self.count()
+ while i < size:
+ while i < size and self.prediction[i] != "up":
+ self.remove_stock(i)
+ size = self.count()
+ i += 1
+
diff --git a/src/trading.py b/src/trading.py
index a27a5b3..1bc4cc2 100644
--- a/src/trading.py
+++ b/src/trading.py
@@ -1,113 +1,6 @@
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"
+from picker import StockAnalyzer
class TradingBot(mechanicalsoup.StatefulBrowser):
@@ -115,7 +8,6 @@ class TradingBot(mechanicalsoup.StatefulBrowser):
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,
@@ -124,37 +16,26 @@ class TradingBot(mechanicalsoup.StatefulBrowser):
}
self.analyzer = StockAnalyzer()
- def help(self):
- print("Unimplemented")
-
- def interpret_command(self, cmd, *args, **kwargs):
- cmd = cmd.lower()
+ def interpret_command(self, cmd):
if cmd not in self.commands.keys():
print("Invalid command " + cmd + ", type help for more info")
return
- return self.commands[cmd](*args, **kwargs)
+ return self.commands[cmd]()
- def login(self, *args, **kwargs):
+ def login(self, _username="", _password=""):
# 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.select_form('form[action="/login"]')
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")
@@ -162,32 +43,34 @@ class TradingBot(mechanicalsoup.StatefulBrowser):
print("Log-in failed")
return result
- def buy(self, *args, **kwargs):
+ def buy(self, symbol="", quantity="", t="", stop=0):
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]
+ stop = float(stop)
if symbol == "":
symbol = input("Stock Symbol: ")
if quantity == "":
quantity = input("Quantity: ")
+ if t == "":
+ trailing = input("Make order trailing stop? Y/N - ")
+ if trailing:
+ t = "Trailing Stop %"
+ stop = input("What percent of price to stop? __/100 - ")
+ else:
+ t = "Market"
self.open("https://www.howthemarketworks.com/trading/equities")
self.select_form()
self.__setitem__("Symbol", symbol)
self.__setitem__("Quantity", quantity)
+ self.__setitem__("OrderType", t)
+ if t == "Trailing Stop %":
+ self.__setitem__("Price", stop)
result = self.submit_selected()
if result.ok:
- print("Successfully bought " + str(quantity) + " shares of " + symbol)
+ print("Successfully bought " + str(quantity) + " shares of " + symbol + " with type " + t)
else:
print("Failed to purchase " + symbol + ". Check that the symbol is correct and funds are sufficient")
return result
@@ -199,23 +82,35 @@ class TradingBot(mechanicalsoup.StatefulBrowser):
self.open("https://www.howthemarketworks.com/accounting/accountbalance")
self.launch_browser()
- def autobuy(self, budget=1000):
+ def autobuy(self, budget=0):
+ if budget == 0:
+ budget = input("Budget for autobuy: ")
print("Analyzing market, this may take time")
stocks = self.analyzer.recommend_buy(budget)
- total = _sum_total(stocks)
+ total = stocks.total()
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)))
+ for s in range(stocks.count()):
+ symbols.append(stocks.symbol[s])
+ print(stocks.name[s] + " (" + stocks.symbol[s] + ") ---- " + str(stocks.amount[s]) +
+ " share(s) totalling: $" + str(round(stocks.amount[s] * stocks.price[s], 2)))
print("Purchase will total $" + str(round(total, 2)))
+ order_type = "Market"
+ percent = 0
+ trailing = input("Add trailing stop? Y/N - ")
+ if trailing.lower() == "y":
+ order_type = "Trailing Stop %"
+ percent = input("What percent to put the stop? __/100 - ")
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"])
+ for s in range(stocks.count()):
+ if order_type == "Market":
+ self.buy(stocks.symbol[s], str(stocks.amount[s]), order_type)
+ else:
+ self.buy(stocks.symbol[s], str(stocks.amount[s]), order_type, percent)
if __name__ == "__main__":