From 3ec585794693dffa740c55565b30ee07dc397b39 Mon Sep 17 00:00:00 2001 From: Logan Laptop Date: Tue, 25 Jan 2022 20:33:45 -0600 Subject: [PATCH] Beta 2.0 Working --- .idea/.gitignore | 3 + .idea/StocksGame.iml | 8 + .idea/inspectionProfiles/Project_Default.xml | 12 ++ .../inspectionProfiles/profiles_settings.xml | 6 + .idea/misc.xml | 4 + .idea/modules.xml | 8 + .idea/vcs.xml | 6 + src/__pycache__/picker.cpython-38.pyc | Bin 0 -> 3038 bytes src/__pycache__/stock.cpython-38.pyc | Bin 0 -> 3679 bytes src/__pycache__/trading.cpython-38.pyc | Bin 0 -> 3966 bytes src/main.py | 19 +- src/picker.py | 79 ++++++++ src/stock.py | 119 ++++++++++++ src/trading.py | 181 ++++-------------- 14 files changed, 284 insertions(+), 161 deletions(-) create mode 100644 .idea/.gitignore create mode 100644 .idea/StocksGame.iml create mode 100644 .idea/inspectionProfiles/Project_Default.xml create mode 100644 .idea/inspectionProfiles/profiles_settings.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 src/__pycache__/picker.cpython-38.pyc create mode 100644 src/__pycache__/stock.cpython-38.pyc create mode 100644 src/__pycache__/trading.cpython-38.pyc create mode 100644 src/picker.py create mode 100644 src/stock.py 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 0000000000000000000000000000000000000000..d6b0299a22f3b3ccea1b63439a4217ff366ef1a1 GIT binary patch literal 3038 zcmbtWPjA~s7N6liQGYCZ-C(mti!QgDre+ICaW+Yjr0BMF) zTvu=Zm&7fEe%23*mje$U!!6zeK@r6niuA9Q;gJITm+% zz5O&NdBVDfWKa~{q!{%kJ)?KdM|x8tlR*Yq3vk|-hjLnUro&EFOv``0a+E>ZAC&+6 zLt#dRkR3uPqviH11W06hz&c_~67l=%k(`mm&5#2Id(q;&Ik`Q@vp zGa^~mNyxY(G-gDC7to@04H$ikbr2FAqsN$HYJHC}U=j*$@06c^UX*E;k>1VjwP54h zG^fQ>1Urv{TiwmO0X*NjaW{CX1wXiRVbQyOZSxxZZwA{bXZyv|o*Bfd zF%gW%q|b60TFMoZG2_aQ<1|lY90R}|!Lt1y0qAj@WJHKK{t^8@>t#isQ?LmG(vXln1 z<=hEWEuC4EP1A$&>h^SFFATP`4VKfe(-{TZ<%S4@t&J;Vo{k7V+$yqyM~;BaELV3J z;lVaZdGHO%CM;?^JYXCqm@o<~QcUxx`E^d`CTlVdJ>?W*mMg0mDi>nrQaNKNv2x(} zQ|0%Wj3s+2LzmZJOF1wjQl5}RPK2tljEw*d$~BX4O`tQ;)NXD4@|ZK4!Wk>_HL!^Q z1j23X;TCrA8umf@=Td9>F5g_P`*N72n(G-gVoc3{0!7!n!lzJOs4PAK4y6{gQ(arD zkD$KR36S(DzK6bBhsr|jD@RvZzwzWf=~ebIrtTZ)$d$EYbmU22)+_fIAL};;N42U( z-Am}muk6aF-evSUpaitqT=VBzeXcd;T2pJZ23a;Lvg0b;Y=TgGap3Q;;Zn( zHCyo4Kqz0=HwKnb9vK4yD8Ho5cE)n911Oi19B^O*B=}#z5Y>TeV?#$_!+=3KS(*>| z>)P9)L=xqsl8qqUVgjJ3Lo*qZJY_=R6zD<{E~DDacz6*jm-?LS#isGZ^M>;_L>3z$ z5N_agz(yMo(*o=OZmb5_Ui?{j?SD?Ah0C`uAm>+tF5yS-lID#$ek3r4<_4$`1_W7} zIlyKa8(`<$4hW+5DKvc;b1saz3uAyBi1qS4Z(+>4FjiX_t6doL7smYavHB@8^ViRx z8?sqJ?ixXr1^8*~z;+}g&(pjgWQ6ybkipIC!IZIKkQG8O(d~B2zx}3<>1yDzUH*0M zqnmDE3dDf5OmEUb-dc7brl|(IUwpFlu9@WT^YN{R-#ob2*}NTamJzuWGLmQm|SB>GFlrlHjX(1hyyFacZu@$ zOk(^hqtR+sb*T zPgs`4@xqEU>2Tcz_=_VZG{0zqpZ{4cyS?sTHXfm?+9Q?>fG-k~iDEL2fHC0HXGTV= myU=mi{v`X9!$%C`=ADJ<%vPGF<9|3?;YXSZg(g7QKKmCH&p!JA literal 0 HcmV?d00001 diff --git a/src/__pycache__/stock.cpython-38.pyc b/src/__pycache__/stock.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d15eb009c6ce17d24f81a165ecf66e9ea363d3c GIT binary patch literal 3679 zcma)9&2Jk;6rb5IuN^0G(=<)mB83aod^8mx6``u4qNqI-b7Dl=Vn7?Fjn0t5`9BIu^T9iCs%cWxW);wz8Fj z)={qV(7LLke6*grs%mQKrQEHkYpSl6(eu@cTE)AnYN!inYidnhM7yNc)g`obwV^Jf zT~?cF3+;-!g8jUeS080b)OpxWvq+XUMtuv<7kJW7Aoj&r1V&2kTP$ss4ojD%$Fict zBXK5MXMXObFM3asZtnEMUYxrF-HzgXH983U`*9FHNt5m{n<;eM4YT&MIIs0W-HEdx z3J19t_L5;g%WDH2t9F#Nlm3~^oiy(5oe7OVh`+Dz93;JXr~ubFOjH|AjccAie6q%L-_SY z5FNx(hbE*nL0Fq|c8@k@t}Rc0&AkQqD3iwGM4SpKG6^@sz2`>Q`Xb)y3nbW*H*L#} z2s;~?lhPa^eK}g4?SkLV{X9jMT*A@)2puJl*pnynR2<8D;`?hy)|j_4wzeqpHnr>G zmN=4!jWM=&X0;|*D4I_0Y67}m$H@4UoY=JWGMe1Yk}T}zvc2F-y@|J1@TBB?AsyL} zSLU|O3m-?c*Yk_^Oi|-~H;}<&sU+`Dbi5;bZ0mbtE4_#PiW6zxoysGJTsn4Ku_ZDa zT3B){ZwXu627Nt(bq?ldj+uWf_wmjG6R_T8y-h2odYAQHR>6Fyr1x0w^L)3Y_gP6$n@D$K&X5q7)D zkAWHvy6q^;;uOq29K`x<%v=CKUJaEBn6tL=HoXQRrg zLld`1ED(ejC4Ja^6R)PJ*pQ>fY~$v2L@_zPs88WxOpnnqR4|qusW&s=`&gRT5{j{R zd5SUmPO0yf`d+DDDfRtQzgp_o&ijlXt2L^BJ<-1I%`q&6+9I>J#J)eJU`@f%ACj04wkC+(#luG-Vfom5>Hez>*aAs&G)#ak zLjM^CM8kM2DryCAw+Xl)Fp$vL>bQD+Y#$kFhUUmRTqio#m$3ZIE_%2&H_Shnoj*Db5zXf)cIS+=lZ z4rfH4Mdf^jfyq`>OQ5Uc=nqWbrd$J_pnVE&^!!x8FU{%#rIAxp4R{ZMVu*I3}n949( zZ6(`=#1(!XTws+tdPJd-} zY>g2WfVrR`d&p%t)^I6kPTiDS!-4UPZ5+S_k32Iy*l-r%fZiU(3x?*6P8<&c9Y;y8 z7o)hUgsdj=pkof%y}?$GuFNpExTMvTkLhof4)gSHMG)k*Am}A(*rk3w2o8r~*Syhm z#5V^;K7`THN>d5b?~&km5?Pt>aug{T`co3Luacm1uBL+nJt=(&8?NDYe6QB1IsUR= e_p7)YJPZ5ew)6%5ARm|g+h)Ia=v#l{5&r_gj?|(6 literal 0 HcmV?d00001 diff --git a/src/__pycache__/trading.cpython-38.pyc b/src/__pycache__/trading.cpython-38.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f406a9b99671812f44b2c82fd91a99a8061d09da GIT binary patch literal 3966 zcmai1&5zs073Yu?MQK01j^jE_)2Zt=mQk;qAaQ|p+BA0Hphc{@wS%B+1D4Aft+=8{ zc{s{u32rZIG^YUh1A5p!CVvvIy)>7i=k$>N-jG^vK8jG{<9u@Fz4^W0dwg$gt#9G_ z{Qdt#f4g8=|D?+K%SGikxQjnR2urZk3YsSi80vOv&z!(v);pGPg!|MIuC(^uV<+fH zPjsfX8cFBa3c9j;;tTJT1wGl9y<^7ku1D6O`vu->jRveQih&To7ww9WDOH zmTLt)Y+$SIKUR@Qvc0>xj+t4b5mfeYJGhG-2yGo(`)0jQX!}lOg|>#=cX4-cd$_y8 z{RhgSuY1Q9o_%3qmCmSq@E|XfG>wL@Z{OnA-%m1;9~6A_nBU%h|7}h0lJFsFKh9^f zC=-RUO*Lvy=rdNkg-pkI_QEhuqoN4I&#W(R3@7|RBBO9X~ z@9d zo@hNUc80@)gM;l!exN6E7OAP!2f3OS+fbh%0igW=+b;X>0lOIxvBS&$LqG?k#101y zcypD{=DNHD!_WR4#X8BeUtMkHUi~2OY2pYI1AjTKy#4)4M{%4lG7T>uil~_E=8+QR zg?ID4n@PrtMI6hb7%$Rtt!)}dNh-ylU%UBSW_2gY<_leS_oSXzSRtnqPFvC;4D7ijSBfhu?6U` zrI%>CaseZ^h1CrWemw`P9n`7ct{elizoOnjy;F76Uxat$px&kVnJaw2iQv|2fd_yY z(0QVN20$?M82~-8S~&)c04pHMp!a*)!SQ1F)p(r53A7*dpTWQGjMF^Q>W9FcdV$1`NW4Vi$0RsJ?G#!K zx(%~>%|e0yq&<(CX~Z5_x%QW5p?*SBzfQju#Fh{t^D9D_FTsT-}WLP7c|`B-XYwZY`__*s~BJ2`Tp}h zM_g@jH%g-{mSB-HMlog#*3)Q_#glMXA+eB3tz(IAT7*1*gu5_VUu%&*D+#U+)- z{;{D0TJOo*Sh;7$Ip^C&|Hu>UsEhq0h8_8pf3kjRiB;(IFR^yyp?w|gUCiDv`v$3? z-m7};IOkKQh%PUj(dBu~9zrva+tkLWcL9Nor6%WfsJv5T%^Cmy{Z;qWW!9(c$x8+_ zTO&n8UOsnsArOa5V$!B|;HPU%;!Ld6gk9t?6L{t$DOD8i~UVjX?9p`D9AGG`x zPYXVYK9qb{N<=~v)}+`eZGOG%4){%6ynMc4itEJyl%S)O$Zv;V`+~Bs^3ua4MI0c# zZ3ggb>KgT3c}s}zrpuZgs-M!3m%ql6hH+YUG30tilI81$fr|x*Y*hFPc%xfzfz~fRe=+lVE!ICgs>5MM@k4 zZ`T}X;DOO_9Eun#=r){J)T{{lChUZilLp;3o~Ro%$`>+D3S>y%^mB68_iz_)Kv>{2 zlPo}j|B!ZWvK!3)%4RQq<=fArjapZYq;czJ+$QTYZkJo$f8I(^e zVqvmqLCHBZS@pgCr9St&rib=0i;@f<5O3byum&r^`b;9IWl0>RMZTCT%Bccxp2Sle z4S_n?c%&mOkrCc)$&{8*x2em)2avjHX5hdw9GJn&v(RHoCjIx{Xf*yc*`y#oHpk#> JWfWlg?0@ri^2z`J literal 0 HcmV?d00001 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__":