class RobinhoodExporter: """ Exports metadata about a Robinhood user's held securities and portfolio. """ def __init__(self, mfa_code, username=None, password=None): username = os.environ['rh_user'] if not username else username password = os.environ['rh_pass'] if not password else password try: self.rh = Robinhood() if not self.rh.login(username, password, mfa_code): raise LoginException("Invalid login credentials.") except: raise LoginException("Invalid login credentials.") def _securities_to_quotes(self, securities): return self.rh.get_url(quote_endpoint(securities))['results'] def _stocks_from_securities(self, securities): for security, quote in zip(securities, self._securities_to_quotes(securities)): yield Stock.from_security_and_quote(security, quote) def export_portfolio(self, export_func=None): securities = self.rh.securities_owned() stocks = list(self._stocks_from_securities(securities['results'])) total_equity = self.rh.equity() portfolio = Portfolio(total_equity, stocks) if export_func: export_func(portfolio) return portfolio
def RDataPull(BankInfo): from Robinhood import Robinhood Account = BankInfo["Usr"] PWord = BankInfo["Pass"] trader = Robinhood() trader.login(username=Account, password=PWord) cash = trader.get_account()['cash'] equity = trader.equity() invested = float(equity) - float(cash) dsecowned = trader.securities_owned()['results'] RData = {"cash": cash, "invested": invested} positions = {} for position in dsecowned: id = position['instrument'].split('/')[4] if float(position['quantity']) > 0: positions[trader.instrument(id)['symbol']] = position['quantity'] RData["positions"] = positions return RData
class RobinhoodShell(cmd.Cmd): intro = 'Welcome to the Robinhood shell. Type help or ? to list commands.\n' prompt = '> ' # API Object trader = None # Cache file used to store instrument cache instruments_cache_file = 'instruments.data' # Maps Symbol to Instrument URL instruments_cache = {} # Maps URL to Symbol instruments_reverse_cache = {} # Cache file used to store instrument cache watchlist_file = 'watchlist.data' # Auth file auth_file = 'auth.data' # List of stocks in watchlist watchlist = [] def _save_auth_data(self): auth_data = {} auth_data['device_token'] = self.trader.device_token auth_data['auth_token'] = self.trader.auth_token auth_data['refresh_token'] = self.trader.refresh_token open(self.auth_file, 'w').write(json.dumps(auth_data)) def __init__(self): cmd.Cmd.__init__(self) self.trader = Robinhood() # Robinhood now uses 2FA # The workflow we use is as follows # If we find auth token in auth.data - try to see if it still works # If yes, continue # If no, try to refresh the token using refresh token # If it still fails, we need to relogin with 2FA try: data = open(self.auth_file).read() auth_data = json.loads(data) if 'auth_token' in auth_data: self.trader.device_token = auth_data['device_token'] self.trader.auth_token = auth_data['auth_token'] self.trader.refresh_token = auth_data['refresh_token'] self.trader.headers['Authorization'] = 'Bearer ' + self.trader.auth_token try: self.trader.user() except: del self.trader.headers['Authorization'] self.trader.relogin_oauth2() self._save_auth_data() except: challenge_type = 'email' if CHALLENGE_TYPE == 'sms': challenge_type = 'sms' self.trader.login(username = USERNAME, password = PASSWORD, challenge_type = challenge_type) self._save_auth_data() try: data = open(self.instruments_cache_file).read() self.instruments_cache = json.loads(data) for k in self.instruments_cache: self.instruments_reverse_cache[self.instruments_cache[k]] = k except: pass try: data = open(self.watchlist_file).read() self.watchlist = json.loads(data) except: pass # nytime = parser.parse('2018-06-15T23:14:15Z').astimezone(to_zone) # from dateutil import parser # ----- basic commands ----- def do_l(self, arg): 'Lists current portfolio' t = Terminal() portfolio = self.trader.portfolios() if portfolio['extended_hours_equity']: equity = float(portfolio['extended_hours_equity']) else: equity = float(portfolio['equity']) eq = '%.2f' % equity previous_close = float(portfolio['adjusted_equity_previous_close']) change = equity - previous_close change_pct = '%.2f' % (change/previous_close * 100.0) # format change = f"{change:.2f}" # colorize change_pct = color_data(change_pct) change = color_data(change) account_details = self.trader.get_account() if 'margin_balances' in account_details: buying_power = account_details['margin_balances']['unallocated_margin_cash'] account_table = SingleTable([['Portfolio Value','Change','Buying Power'],[eq, change+' ('+change_pct+'%)', buying_power]],'Account') print((account_table.table)) # Load Stocks positions = self.trader.securities_owned() instruments = [position['instrument'] for position in positions['results']] symbols = [self.get_symbol(position['instrument']) for position in positions['results']] market_data = self.trader.get_stock_marketdata(instruments) table_data = [] table_data.append(["Symbol", "Last", "Shares", "Equity", "Avg Cost", "Return" , "Day", "EquityChange", "Day %"]) i = 0 for position in positions['results']: quantity = int(float(position['quantity'])) symbol = self.get_symbol(position['instrument']) price = market_data[i]['last_trade_price'] total_equity = float(price) * quantity buy_price = float(position['average_buy_price']) p_l = f"{total_equity - (buy_price * quantity):.2f}" total_equity = f"{total_equity:.2f}" buy_price = f"{buy_price:.2f}" day_change = f"{float(market_data[i]['last_trade_price']) - float(market_data[i]['previous_close']):.2f}" day_change_q_val = f"{float(quantity) * float(day_change):.2f}" day_change_pct = f"{float(day_change) / float(market_data[i]['previous_close']) * 100:.2f}" price = f"{float(price):.2f}" table_data.append([ symbol, price, quantity, total_equity, buy_price, color_data(p_l), color_data(day_change), color_data(day_change_q_val), color_data(day_change_pct) ]) i += 1 table = SingleTable(table_data,'Portfolio') table.inner_row_border = True table.justify_columns = {0: 'center' } print((table.table)) def do_lo(self, arg): 'Lists current options portfolio' # Load Options options_t_data=[] option_positions = self.trader.options_owned() options_table = SingleTable(options_t_data,'Options') options_table.inner_row_border = True options_table.justify_columns = {0: 'center' } options_t_data.append(["Symbol","Type","Experation","Strike", "Price", "QTY", "Equity", "Cost", "Total Return","Today"]) for op in option_positions: quantity = float(op['quantity']) if quantity == 0: continue cost = float(op['average_price']) if op['type'] == 'short': quantity = -quantity cost = -cost instrument = op['option'] option_data = self.trader.session.get(instrument).json() # skip expired -- verify when it changes state day of or, after market close on expieration if option_data['state'] == "expired": continue expiration_date = option_data['expiration_date'] strike = float(option_data['strike_price']) type = option_data['type'] symbol = op['chain_symbol'] option_type = str(type).upper() expiration = expiration_date strike_price = '$'+str(strike) info = self.trader.get_option_marketdata(instrument) last_price = float(info['adjusted_mark_price']) total_equity = (100 * last_price) * quantity change = total_equity - (float(cost) * quantity) change_pct = '{:04.2f}'.format(change / float(cost) * 100) day_change = float(info['adjusted_mark_price']) - float(info['previous_close_price']) day_pct = '{:04.2f}'.format((day_change / float(info['previous_close_price']) ) * 100) # format after calc day_change = f"{day_change:.3f}" options_t_data.append([ symbol,option_type, expiration, strike_price , last_price, quantity, total_equity, cost, color_data(change) +' ('+ color_data(change_pct) +'%)', color_data(day_change) +' ('+ color_data(day_pct) +'%)' ]) print((options_table.table)) def do_w(self, arg): 'Show watchlist w \nAdd to watchlist w a <symbol> \nRemove from watchlist w r <symbols>' parts = re.split('\W+',arg.upper()) if len(parts) >= 2: if parts[0] == 'A': for p in parts[1:]: if p not in self.watchlist: self.watchlist.append(p.strip()) if parts[0] == 'R': self.watchlist = [r for r in self.watchlist if r not in parts[1:]] print("Done") else: watch_t_data=[] watch_table = SingleTable(watch_t_data,'Watch List') watch_table.inner_row_border = True watch_table.justify_columns = {0: 'center', 1: 'center', 2: 'center', 3:'center',4: 'center'} watch_t_data.append(["Symbol","Ask Price", "Open", "Today", "%"]) if len(self.watchlist) > 0: instruments = [self.get_instrument(s)['url'] for s in self.watchlist] raw_data = self.trader.get_stock_marketdata(instruments) quotes_data = {} for quote in raw_data: day_change = float(quote['last_trade_price']) - float(quote['previous_close']) day_change_pct = '{:05.2f}'.format(( day_change / float(quote['previous_close']) ) * 100) watch_t_data.append([ quote['symbol'], '{:05.2f}'.format(float(quote['last_trade_price'])), '{:05.2f}'.format(float(quote['previous_close'])), color_data(day_change), color_data(day_change_pct) ]) print((watch_table.table)) else: print("Watchlist empty!") def do_b(self, arg): 'Buy stock b <symbol> <quantity> <price>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0].upper() quantity = parts[1] if len(parts) == 3: price = float(parts[2]) else: price = 0.0 stock_instrument = self.get_instrument(symbol) if not stock_instrument['url']: print("Stock not found") return res = self.trader.place_buy_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print("Error executing order") try: data = res.json() if 'detail' in data: print(data['detail']) except: pass else: print("Done") else: print("Bad Order") def do_s(self, arg): 'Sell stock s <symbol> <quantity> <?price>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0].upper() quantity = parts[1] if len(parts) == 3: price = float(parts[2]) else: price = 0.0 stock_instrument = self.get_instrument(symbol) if not stock_instrument['url']: print("Stock not found") return res = self.trader.place_sell_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print("Error executing order") try: data = res.json() if 'detail' in data: print(data['detail']) except: pass else: print("Done") else: print("Bad Order") def do_sl(self, arg): 'Setup stop loss on stock sl <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0].upper() quantity = parts[1] price = float(parts[2]) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_stop_loss_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print("Error executing order") try: data = res.json() if 'detail' in data: print(data['detail']) except: pass else: print("Done") else: print("Bad Order") def do_o(self, arg): 'List open orders' open_orders = self.trader.get_open_orders() if open_orders: open_t_data=[] open_table = SingleTable(open_t_data,'open List') open_table.inner_row_border = True open_table.justify_columns = {0: 'center', 1: 'center', 2: 'center', 3:'center',4: 'center'} open_t_data.append( ["index", "symbol", "price", "quantity", "type", "id"]) index = 1 for order in open_orders: if order['trigger'] == 'stop': order_price = order['stop_price'] order_type = "stop loss" else: order_price = order['price'] order_type = order['side']+" "+order['type'] open_t_data.append([ index, self.get_symbol(order['instrument']), order_price, int(float(order['quantity'])), order_type, order['id'], ]) index += 1 print((open_table.table)) else: print("No Open Orders") def do_c(self, arg): 'Cancel open order c <index> or c <id>' order_id = arg.strip() order_index = -1 try: order_index = int(order_id) except: pass if order_index > 0: order_index = order_index - 1 open_orders = self.trader.get_open_orders() if order_index < len(open_orders): order_id = open_orders[order_index]['id'] else: print("Bad index") return try: self.trader.cancel_order(order_id) print("Done") except Exception as e: print("Error executing cancel") print(e) def do_ca(self, arg): 'Cancel all open orders' open_orders = self.trader.get_open_orders() for order in open_orders: try: self.trader.cancel_order(order['id']) except Exception as e: pass print("Done") def do_news(self,arg,show_num=5): if len(arg) == 0: print("Missing symbol") else: news_data = self.trader.get_news(arg.upper()) if news_data['count'] == 0: print("No News available") return for x in range(0,show_num): news_box(news_data['results'][x]['source'], news_data['results'][x]['published_at'], news_data['results'][x]['summary'], news_data['results'][x]['title'],news_data['results'][x]['url']) def do_mp(self, arg): 'Buy as many shares possible by defined max dollar amount: mp <symbol> <max_spend> <?price_limit>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0].upper() #quantity = parts[1] spend = parts[1] if len(parts) == 3: print("Parts: 3") price_limit = float(parts[2]) else: price_limit = 0.0 try: cur_data = self.trader.quote_data(symbol) last_price = cur_data['last_trade_price'] except: print("Invalid Ticker?") pass return # quote['last_trade_price'] quantity = int(math.floor(float(spend) / float(last_price))) print(("\nBuying %s\n Max Spend: %s\nQTY: %s\n Current Price: %s\nMax Price: %s\n" % (symbol,spend, quantity, last_price, price_limit))) # stock_instrument = self.trader.instruments(symbol)[0] # res = self.trader.place_buy_order(stock_instrument, quantity, price) # if not (res.status_code == 200 or res.status_code == 201): # print "Error executing order" # try: # data = res.json() # if 'detail' in data: # print data['detail'] # except: # pass # else: # print "Done" # else: # print "Bad Order" def do_q(self, arg): 'Get detailed quote for stock: q <symbol(s)>' symbols = re.split('\W+',arg.upper()) if len(arg) == 0: print("Missing symbol(s)") else: instruments = [self.get_instrument(s)['url'] for s in symbols] raw_data = self.trader.get_stock_marketdata(instruments) quotes_data = {} quote_t_data=[] quote_table = SingleTable(quote_t_data,'Quote List') quote_table.inner_row_border = True quote_table.justify_columns = {0: 'center', 1: 'center', 2: 'center', 3:'center',4: 'center'} quote_t_data.append(["Symbol", "Current Price", "Open","Change", "Ask","Bid"]) for quote in raw_data: if not quote: continue day_change = float(quote['last_trade_price']) - float(quote['previous_close']) day_change_pct = ( day_change / float(quote['previous_close']) ) * 100 ask_price = '{:05.2f}'.format(float(quote['ask_price'])) ask_size = quote['ask_size'] bid_price = '{:05.2f}'.format(float(quote['bid_price'])) bid_size = quote['bid_size'] quote_t_data.append([ quote['symbol'], '{:05.2f}'.format(float(quote['last_trade_price'])), '{:05.2f}'.format(float(quote['previous_close'])), color_data(day_change)+' ('+color_data('{:05.2f}'.format(day_change_pct))+'%)', str(ask_price)+' x '+str(ask_size), str(bid_price)+' x '+str(bid_size) ]) print((quote_table.table)) def do_qq(self, arg): 'Get quote for stock q <symbol> or option q <symbol> <call/put> <strike> <(optional) YYYY-mm-dd>' arg = arg.strip().split() try: symbol = arg[0].upper() except: print("Please check arguments again. Format: ") print("Stock: q <symbol>") print("Option: q <symbol> <call/put> <strike> <(optional) YYYY-mm-dd>") type = strike = expiry = None if len(arg) > 1: try: type = arg[1] strike = arg[2] except Exception as e: print("Please check arguments again. Format: ") print("q <symbol> <call/put> <strike> <(optional) YYYY-mm-dd>") try: expiry = arg[3] except: expiry = None arg_dict = {'symbol': symbol, 'type': type, 'expiration_dates': expiry, 'strike_price': strike, 'state': 'active', 'tradability': 'tradable'}; quotes = self.trader.get_option_quote(arg_dict); qquote_t_data=[] qquote_table = SingleTable(qquote_t_data,'Quote List') qquote_table.inner_row_border = True qquote_table.justify_columns = {0: 'center', 1: 'center'} qquote_t_data.append(['expiry', 'price']) for row in quotes: qquote_t_data.append(row) print((qquote_table.table)) else: try: self.trader.print_quote(symbol) except: print("Error getting quote for:", symbol) def do_bye(self, arg): open(self.instruments_cache_file, 'w').write(json.dumps(self.instruments_cache)) open(self.watchlist_file, 'w').write(json.dumps(self.watchlist)) self._save_auth_data() return True # ------ utils -------- def get_symbol(self, url): if not url in self.instruments_reverse_cache: self.add_instrument_from_url(url) return self.instruments_reverse_cache[url] def get_instrument(self, symbol): if not symbol in self.instruments_cache: instruments = self.trader.instruments(symbol) for instrument in instruments: self.add_instrument(instrument['url'], instrument['symbol']) url = '' if symbol in self.instruments_cache: url = self.instruments_cache[symbol] return { 'symbol': symbol, 'url': url } def add_instrument_from_url(self, url): data = self.trader.get_url(url) if 'symbol' in data: symbol = data['symbol'] else: types = { 'call': 'C', 'put': 'P'} symbol = data['chain_symbol'] + ' ' + data['expiration_date'] + ' ' + ''.join(types[data['type']].split('-')) + ' ' + str(float(data['strike_price'])) self.add_instrument(url, symbol) def add_instrument(self, url, symbol): self.instruments_cache[symbol] = url self.instruments_reverse_cache[url] = symbol
class Robinhood_AA (object): def __init__(self): self.start_time = datetime.now() self.my_trader = Robinhood() self.my_trader.login(username=usrnm, password=pssw) self.equity = self.my_trader.equity() self.owned_securities = self.my_trader.securities_owned() self.available_funds = self.my_trader.equity() - sum([x[1]*x[4] for x in self.my_trader.securities_owned()]) self.buy_limit = 120 self.stocks = [] self.ld = 10 ## sell no matter what self.sd = 5 ## sell if positive self.file_path = home_path self.conn = sqlite3.connect(self.file_path+'log.db') self.conn.execute('drop table if exists history') self.conn.execute('''create table history (id int primary key not null, date text not null, activity text not null, stock text not null, gain text not null);''') self.idCount = 1 print('Deleting old files...') # delete old files os.chdir(self.file_path) for f in glob.glob("*.png"): os.remove(f) def today_stocks_to_buy(self): F = FGS('Low Cost', 100, {'minPrice':7,'maxPrice':35}, 'lowcost', self.file_path) stocks = F.find_good_stocks() self.stocks = [x[0] for x in stocks] print(self.stocks) def update_info(self): self.equity = self.my_trader.equity() self.owned_securities = self.my_trader.securities_owned() self.available_funds = self.my_trader.equity() - sum([x[1]*x[4] for x in self.my_trader.securities_owned()]) def current_stock_info(self): self.open_log() try: table = [] for s in self.owned_securities: datetime_format = datetime.strptime(s[2], '%Y-%m-%dT%H:%M:%S.%fZ') duration = datetime.now() - datetime_format table.append([s[0], round((s[4]/s[3]-1) * 100, 2), duration > timedelta(days=5), duration > timedelta(days=10)]) table = sorted(table, key = lambda x: x[1]) self.log.write(tabulate(table, headers = ['Stock', 'Total Gain/Loss', '> {} days', '> {} days'.format(self.sd, self.ld)])) self.log.write('\n') except Exception as e: self.log.write('Can\'t print table: unexpected error {}.\n'.format(e)) self.close_log() def buy_stock(self, stock = None): self.open_log() self.log.write('\nBuying:\n') try: if stock == None: self.log.write('Nothing bought: no stock specified.\n') elif self.available_funds > self.buy_limit: last_price = float(self.my_trader.last_trade_price(stock)) number_to_buy = int(self.buy_limit/last_price) self.my_trader.place_buy_order(self.my_trader.instruments(stock)[0], int(self.buy_limit/last_price)) self.log.write('Buying {} stocks of {} at {} for a total of {}.\n'.format(number_to_buy, stock, last_price, last_price * number_to_buy)) self.conn.execute('insert into history (id,date,activity,stock,gain) values (?, ?, ?, ?, ?)', (self.idCount, datetime.now(), 'BUY', stock, 'N/A')) self.idCount += 1 else: self.log.write('Nothing bought: insufficient funds to buy stock. Need at least {} dollars.\n'.format(self.buy_limit)) except Exception as e: self.log.write('Nothing bought: {}.\n'.format(e)) self.close_log() def auto_buy_stocks(self): self.open_log() self.log.write('\nBuying:\n') try: if self.available_funds < self.buy_limit: self.log.write('Nothing bought: insufficient funds to buy stocks. Need at least {0} dollars.\n'.format(self.buy_limit)) else: for i in range(int(self.available_funds / self.buy_limit)): last_price = float(self.my_trader.last_trade_price(self.stocks[i])) number_to_buy = int(self.buy_limit/last_price) self.my_trader.place_buy_order(self.my_trader.instruments(self.stocks[i])[0], int(self.buy_limit/last_price)) self.log.write('Buying {} stocks of {} at {} for a total of {}.\n'.format(number_to_buy, self.stocks[i], last_price, last_price * number_to_buy)) self.conn.execute('insert into history (id,date,activity,stock,gain) values (?, ?, ?, ?, ?)', (self.idCount, datetime.now(), 'BUY', self.stocks[i], 'N/A')) self.idCount += 1 time.sleep(10) #except Exception as e: except BufferError: self.log.write('Nothing bought: {}.\n'.format(e)) self.close_log() def auto_sell_stocks(self): self.open_log() self.log.write('\nSelling:\n') try: for s in self.owned_securities: datetime_format = datetime.strptime(s[2], '%Y-%m-%dT%H:%M:%S.%fZ') duration = datetime.now() - datetime_format gain = round((s[4]/s[3]-1) * 100, 2) try: if duration > timedelta(days=self.ld): self.my_trader.place_sell_order(self.my_trader.instruments(s[0])[0], s[1]) self.log.write('Selling {}: More than {} days :|\n'.format(s[0], self.ld)) self.conn.execute('insert into history (id,date,activity,stock,gain) values (?, ?, ?, ?, ?)', (self.idCount, datetime.now(), 'SELL', s[0], gain)) self.idCount += 1 elif duration > timedelta(days=self.sd) and gain > 0: self.my_trader.place_sell_order(self.my_trader.instruments(s[0])[0], s[1]) self.log.write('Selling {}: More than {} days and positive :)\n'.format(s[0], self.sd)) self.conn.execute('insert into history (id,date,activity,stock,gain) values (?, ?, ?, ?, ?)', (self.idCount, datetime.now(), 'SELL', s[0], gain)) self.idCount += 1 elif gain > 5: self.my_trader.place_sell_order(self.my_trader.instruments(s[0])[0], s[1]) self.log.write('Selling {}: More than 5% :)\n'.format(s[0])) self.conn.execute('insert into history (id,date,activity,stock,gain) values (?, ?, ?, ?, ?)', (self.idCount, datetime.now(), 'SELL', s[0], gain)) self.idCount += 1 elif gain < -5: self.my_trader.place_sell_order(self.my_trader.instruments(s[0])[0], s[1]) self.log.write('Selling {}: Less than -5% :(\n'.format(s[0])) self.conn.execute('insert into history (id,date,activity,stock,gain) values (?, ?, ?, ?, ?)', (self.idCount, datetime.now(), 'SELL', s[0], gain)) self.idCount += 1 except Exception as e: self.log.write('Couldn\'t sell {}: {}.\n'.format(s[0], e)) except Exception as e: self.log.write('Nothing sold: {}.\n'.format(e)) self.close_log() def new_log(self): # creates new log if different day if os.path.isfile('{}log{}.txt'.format(self.file_path,datetime.now().strftime('%y%m%d'))): pass else: self.log = open('{}log{}.txt'.format(self.file_path,datetime.now().strftime('%y%m%d')),'w') self.log.close() def open_log(self): self.log = open('{}log{}.txt'.format(self.file_path,datetime.now().strftime('%y%m%d')),'a') self.log.write('\n\nRobinhood Status: {}\n\n'.format(datetime.now())) def close_log(self): try: self.log.write('\nTook {} seconds.'.format(datetime.now() - self.start_time)) self.log.close() except Exception as e: print('Couldn\'t close log: {}'.format(e)) def printDB(self, db='log.db'): try: self.log.write('\nDatabase:') cur = sqlite3.connect(db).cursor() self.log.write(' '+str([header[0] for header in cur.execute("select * from history").description][1:])) for entry in cur.fetchall(): self.log.write(entry) cur.close() except Exception as e: print('Couldn\'t print database: {}'.format(e)) def send_email_AA(self): sendEmail(self.file_path, 'Robinhood Operation', ['*****@*****.**'], ['blacklist.txt',self.file_path+'readme.txt',self.file_path+'log.db', self.file_path+'.DS_Store', self.file_path+'log.db-journal'])
#Print multiple symbols #my_trader.print_quotes(stocks=["BBRY", "FB", "MSFT"]) #View all data for a given stock ie. Ask price and size, bid price and size, previous close, adjusted previous close, etc. #quote_info = my_trader.quote_data("GEVO") #print(quote_info) #Place a buy order (uses market bid price) #buy_order = my_trader.place_buy_order(stock_instrument, 1) #Place a sell order #sell_order = my_trader.place_sell_order(stock_instrument, 1) portfolio = my_trader.portfolios() positions = my_trader.positions() securities = my_trader.securities_owned() while True: exchange_closed = is_time_between(time(17, 00), time(9, 30)) print exchange_closed if exchange_closed: print("Exhange not Open. Passing.") pass else: for key in securities['results']: instrument = my_trader.get_url( key['instrument']) #get symbol of stock name = instrument['symbol']
class RobinhoodShell(cmd.Cmd): intro = 'Welcome to the Robinhood shell. Type help or ? to list commands.\n' prompt = '> ' # API Object trader = None # Cache file used to store instrument cache instruments_cache_file = 'instruments.data' # Maps Symbol to Instrument URL instruments_cache = {} # Maps URL to Symbol instruments_reverse_cache = {} def __init__(self): cmd.Cmd.__init__(self) self.trader = Robinhood() self.trader.login(username=USERNAME, password=PASSWORD) try: data = open('instruments.data').read() self.instruments_cache = json.loads(data) for k in self.instruments_cache: self.instruments_reverse_cache[self.instruments_cache[k]] = k except: pass # ----- basic commands ----- def do_l(self, arg): 'Lists current portfolio' portfolio = self.trader.portfolios() print 'Equity Value:', portfolio['equity'] account_details = self.trader.get_account() if 'margin_balances' in account_details: print 'Buying Power:', account_details['margin_balances'][ 'unallocated_margin_cash'] positions = self.trader.securities_owned() symbols = [] for position in positions['results']: symbol = self.get_symbol(position['instrument']) symbols.append(symbol) raw_data = self.trader.quotes_data(symbols) quotes_data = {} for quote in raw_data: quotes_data[quote['symbol']] = quote['last_trade_price'] table = BeautifulTable() table.column_headers = [ "symbol", "current price", "quantity", "total equity" ] for position in positions['results']: quantity = int(float(position['quantity'])) symbol = self.get_symbol(position['instrument']) price = quotes_data[symbol] total_equity = float(price) * quantity table.append_row([symbol, price, quantity, total_equity]) print(table) def do_b(self, arg): 'Buy stock b <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0] quantity = parts[1] price = float(parts[2]) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_buy_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_s(self, arg): 'Sell stock s <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0] quantity = parts[1] price = float(parts[2]) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_sell_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_o(self, arg): 'List open orders' open_orders = self.trader.get_open_orders() if open_orders: table = BeautifulTable() table.column_headers = [ "symbol", "price", "quantity", "type", "id" ] for order in open_orders: table.append_row([ self.get_symbol(order['instrument']), order['price'], int(float(order['quantity'])), order['side'], order['id'], ]) print(table) else: print "No Open Orders" def do_c(self, arg): 'Cancel open order c <id>' order_id = arg.strip() try: self.trader.cancel_order(order_id) print "Done" except Exception as e: print "Error executing cancel" print e def do_q(self, arg): 'Get quote for stock q <symbol>' symbol = arg.strip() try: self.trader.print_quote(symbol) except: print "Error getting quote for:", symbol def do_bye(self, arg): open(self.instruments_cache_file, 'w').write(json.dumps(self.instruments_cache)) return True # ------ utils -------- def get_symbol(self, url): if not url in self.instruments_reverse_cache: self.add_instrument_from_url(url) return self.instruments_reverse_cache[url] def add_instrument_from_url(self, url): data = self.trader.get_url(url) symbol = data['symbol'] self.instruments_cache[symbol] = url self.instruments_reverse_cache[url] = symbol
class TKshell(cmd.Cmd): intro = 'Welcome to the Robinhood shell. Type help or ? to list commands.\n' prompt = '> ' # API Object trader = None # Cache file used to store instrument cache instruments_cache_file = 'instruments.data' # Maps Symbol to Instrument URL instruments_cache = {} # Maps URL to Symbol instruments_reverse_cache = {} def __init__(self): cmd.Cmd.__init__(self) self.trader = Robinhood() self.trader.login_prompt() try: data = open('instruments.data').read() self.instruments_cache = json.loads(data) for k in self.instruments_cache: self.instruments_reverse_cache[self.instruments_cache[k]] = k except: pass # ----- basic commands ----- def do_l(self, arg): 'Lists current portfolio' portfolio = self.trader.portfolios() print('Equity Value:', portfolio['equity']) account_details = self.trader.get_account() if 'margin_balances' in account_details: print( 'Buying Power:', account_details['margin_balances']['unallocated_margin_cash']) positions = self.trader.securities_owned() symbols = '' buy_price_data = {} for position in positions['results']: symbol = self.get_symbol(position['instrument']) buy_price_data[symbol] = position['average_buy_price'] symbols += symbol + ',' symbols = symbols[:-1] raw_data = self.trader.quote_data(symbols) quotes_data = {} for quote in raw_data: quotes_data[quote['symbol']] = quote['last_trade_price'] d = [] for position in positions['results']: quantity = int(float(position['quantity'])) symbol = self.get_symbol(position['instrument']) price = quotes_data[symbol] total_equity = float(price) * quantity buy_price = float(buy_price_data[symbol]) p_l = total_equity - buy_price * quantity d.append([symbol, price, quantity, total_equity, buy_price, p_l]) column_headers = [ "symbol", "current price", "quantity", "total equity", "cost basis", "p/l" ] table = tabulate(d, headers=column_headers) print(table) def do_q(self, arg): 'Get quote for stock q <symbol>' symbols = arg.strip() headers = [ 'symbol', 'price', 'bid_size', 'bid_price', 'ask_size', 'ask_price' ] try: data = self.trader.get_quote_list( symbols, 'symbol,last_trade_price,bid_size,bid_price,ask_size,ask_price' ) print(tabulate(data, headers=headers)) except: print("Error getting quote for:", symbols) def do_b(self, arg): 'Buy stock b <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0] quantity = parts[1] price = round(float(parts[2]), 2) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_buy_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print("Error executing order") try: data = res.json() if 'detail' in data: print(data['detail']) except: pass else: print("Done") else: print("Bad Order") def do_qb(self, arg): ''' Quick Buy a quick buy is a limit buy which price is set at previous close + 15% * previous candle body :param arg: <symbol> <quantity> :return: order detail ''' parts = arg.split() if len(parts) == 2: symbol = parts[0] quantity = parts[1] d = g_ts.get_intraday(symbol=symbol, interval='5min')[0][-2:-1] o = float(d['open']) c = float(d['close']) price = round(min(o, c) + 0.15 * abs(o - c), 2) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_buy_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print("Error executing order") try: data = res.json() if 'detail' in data: print(data['detail']) except: pass else: print("Done") else: print("Bad Order") def do_s(self, arg): 'Sell stock s <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0] quantity = parts[1] price = float(parts[2]) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_sell_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print("Error executing order") try: data = res.json() if 'detail' in data: print(data['detail']) except: pass else: print("Done") else: print("Bad Order") def do_o(self, arg): 'List open orders' open_orders = self.trader.order_history()['results'][:10] if open_orders: column_headers = [ "index", "symbol", "state", "price", "quantity", "type", "id" ] global g_openorders, g_liststates g_openorders = [] count = 1 for order in open_orders: if order['state'] in g_liststates: g_openorders.append([ count, self.get_symbol(order['instrument']), order['state'], order['price'], int(float(order['quantity'])), order['side'], order['id'] ]) count += 1 g_openorders.reverse() table = tabulate(g_openorders, headers=column_headers) print(table) else: print("No Open Orders") def do_c(self, arg): ''' Cancel an open order by index return in list orders :param arg: :return: ''' order_index = int(arg.strip()) try: self.trader.cancel_order(g_openorders[-order_index][-1]) except Exception as e: print("Error executing cancel\n", e) print(g_openorders[-order_index], "has been cancelled.") def do_u(self, arg): ''' refresh the watch list, combining positions owned with symbol in wl.json :param arg: :return: ''' positions = self.trader.securities_owned() global g_watchlist g_watchlist = [] for position in positions['results']: g_watchlist.append(self.trader.quote_data(position['instrument'])) # TODO maybe add pre-calculated price level in the json file as well with open('wl.json', 'r') as f: d = json.load(f) for each in d: g_watchlist.append(each) print("Watchlist updated") print(tabulate(d)) def do_r(self, arg): ''' rate :param arg: :return: ''' def do_bye(self, arg): open(self.instruments_cache_file, 'w').write(json.dumps(self.instruments_cache)) return True # ------ utils -------- def get_symbol(self, url): if not url in self.instruments_reverse_cache: self.add_instrument_from_url(url) return self.instruments_reverse_cache[url] def add_instrument_from_url(self, url): data = self.trader.get_url(url) symbol = data['symbol'] self.instruments_cache[symbol] = url self.instruments_reverse_cache[url] = symbol
class RobinhoodShell(cmd.Cmd): intro = 'Welcome to the Robinhood shell. Type help or ? to list commands.\n' prompt = '> ' # API Object trader = None # Cache file used to store instrument cache instruments_cache_file = 'instruments.data' # Maps Symbol to Instrument URL instruments_cache = {} # Maps URL to Symbol instruments_reverse_cache = {} # Cache file used to store instrument cache watchlist_file = 'watchlist.data' # List of stocks in watchlist watchlist = [] def __init__(self): cmd.Cmd.__init__(self) self.trader = Robinhood() self.trader.login(username=USERNAME, password=PASSWORD) try: data = open(self.instruments_cache_file).read() self.instruments_cache = json.loads(data) for k in self.instruments_cache: self.instruments_reverse_cache[self.instruments_cache[k]] = k except: pass try: data = open(self.watchlist_file).read() self.watchlist = json.loads(data) except: pass # nytime = parser.parse('2018-06-15T23:14:15Z').astimezone(to_zone) # from dateutil import parser # ----- basic commands ----- def do_l(self, arg): 'Lists current portfolio' portfolio = self.trader.portfolios() if portfolio['extended_hours_equity']: equity = float(portfolio['extended_hours_equity']) else: equity = float(portfolio['equity']) print 'Equity Value: %.2f' % equity previous_close = float(portfolio['adjusted_equity_previous_close']) change = equity - previous_close print '%s%.2f Today (%.2f%%)' % (('+' if change > 0 else ''), change, change / previous_close * 100.0) account_details = self.trader.get_account() if 'margin_balances' in account_details: print 'Buying Power:', account_details['margin_balances'][ 'unallocated_margin_cash'] # Load Stocks positions = self.trader.securities_owned() symbols = [ self.get_symbol(position['instrument']) for position in positions['results'] ] quotes_data = {} if len(symbols) > 0: raw_data = self.trader.quotes_data(symbols) for quote in raw_data: if quote['last_extended_hours_trade_price']: price = quote['last_extended_hours_trade_price'] else: price = quote['last_trade_price'] quotes_data[quote['symbol']] = price table = BeautifulTable() table.column_headers = [ "symbol", "current price", "quantity", "total equity", "cost basis", "p/l" ] for position in positions['results']: quantity = int(float(position['quantity'])) symbol = self.get_symbol(position['instrument']) price = quotes_data[symbol] total_equity = float(price) * quantity buy_price = float(position['average_buy_price']) p_l = total_equity - buy_price * quantity table.append_row( [symbol, price, quantity, total_equity, buy_price, p_l]) print "Stocks:" print(table) # Load Options option_positions = self.trader.options_owned() table = BeautifulTable() table.column_headers = [ "option", "price", "quantity", "equity", "cost basis", "p/l" ] for op in option_positions: quantity = float(op['quantity']) if quantity == 0: continue cost = float(op['average_price']) if op['type'] == 'short': quantity = -quantity cost = -cost instrument = op['option'] option_data = self.trader.session.get(instrument).json() expiration_date = option_data['expiration_date'] strike = float(option_data['strike_price']) type = option_data['type'] symbol = op[ 'chain_symbol'] + ' ' + expiration_date + ' ' + type + ' $' + str( strike) info = self.trader.get_option_marketdata(instrument) last_price = float(info['adjusted_mark_price']) total_equity = (100 * last_price) * quantity change = total_equity - (float(cost) * quantity) table.append_row( [symbol, last_price, quantity, total_equity, cost, change]) print "Options:" print(table) def do_w(self, arg): 'Show watchlist w \nAdd to watchlist w a <symbol> \nRemove from watchlist w r <symbol>' parts = arg.split() if len(parts) == 2: if parts[0] == 'a': self.watchlist.append(parts[1].strip()) if parts[0] == 'r': self.watchlist = [ r for r in self.watchlist if not r == parts[1].strip() ] print "Done" else: table = BeautifulTable() table.column_headers = ["symbol", "current price"] if len(self.watchlist) > 0: raw_data = self.trader.quotes_data(self.watchlist) quotes_data = {} for quote in raw_data: table.append_row( [quote['symbol'], quote['last_trade_price']]) print(table) else: print "Watchlist empty!" def do_b(self, arg): 'Buy stock b <symbol> <quantity> <price>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0] quantity = parts[1] if len(parts) == 3: price = float(parts[2]) else: price = 0.0 stock_instrument = self.get_instrument(symbol) if not stock_instrument['url']: print "Stock not found" return res = self.trader.place_buy_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_s(self, arg): 'Sell stock s <symbol> <quantity> <?price>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0] quantity = parts[1] if len(parts) == 3: price = float(parts[2]) else: price = 0.0 stock_instrument = self.get_instrument(symbol) if not stock_instrument['url']: print "Stock not found" return res = self.trader.place_sell_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_sl(self, arg): 'Setup stop loss on stock sl <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0] quantity = parts[1] price = float(parts[2]) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_stop_loss_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_o(self, arg): 'List open orders' open_orders = self.trader.get_open_orders() if open_orders: table = BeautifulTable() table.column_headers = [ "index", "symbol", "price", "quantity", "type", "id" ] index = 1 for order in open_orders: if order['trigger'] == 'stop': order_price = order['stop_price'] order_type = "stop loss" else: order_price = order['price'] order_type = order['side'] + " " + order['type'] table.append_row([ index, self.get_symbol(order['instrument']), order_price, int(float(order['quantity'])), order_type, order['id'], ]) index += 1 print(table) else: print "No Open Orders" def do_c(self, arg): 'Cancel open order c <index> or c <id>' order_id = arg.strip() order_index = -1 try: order_index = int(order_id) except: pass if order_index > 0: order_index = order_index - 1 open_orders = self.trader.get_open_orders() if order_index < len(open_orders): order_id = open_orders[order_index]['id'] else: print "Bad index" return try: self.trader.cancel_order(order_id) print "Done" except Exception as e: print "Error executing cancel" print e def do_ca(self, arg): 'Cancel all open orders' open_orders = self.trader.get_open_orders() for order in open_orders: try: self.trader.cancel_order(order['id']) except Exception as e: pass print "Done" def do_q(self, arg): 'Get quote for stock q <symbol> or option q <symbol> <call/put> <strike> <(optional) YYYY-mm-dd>' arg = arg.strip().split() try: symbol = arg[0] except: print "Please check arguments again. Format: " print "Stock: q <symbol>" print "Option: q <symbol> <call/put> <strike> <(optional) YYYY-mm-dd>" type = strike = expiry = None if len(arg) > 1: try: type = arg[1] strike = arg[2] except Exception as e: print "Please check arguments again. Format: " print "q <symbol> <call/put> <strike> <(optional) YYYY-mm-dd>" try: expiry = arg[3] except: expiry = None arg_dict = { 'symbol': symbol, 'type': type, 'expiration_dates': expiry, 'strike_price': strike, 'state': 'active', 'tradability': 'tradable' } quotes = self.trader.get_option_quote(arg_dict) table = BeautifulTable() table.column_headers = ['expiry', 'price'] for row in quotes: table.append_row(row) print table else: try: self.trader.print_quote(symbol) except: print "Error getting quote for:", symbol def do_bye(self, arg): open(self.instruments_cache_file, 'w').write(json.dumps(self.instruments_cache)) open(self.watchlist_file, 'w').write(json.dumps(self.watchlist)) return True # ------ utils -------- def get_symbol(self, url): if not url in self.instruments_reverse_cache: self.add_instrument_from_url(url) return self.instruments_reverse_cache[url] def get_instrument(self, symbol): if not symbol in self.instruments_cache: instruments = self.trader.instruments(symbol) for instrument in instruments: self.add_instrument(instrument['url'], instrument['symbol']) url = '' if symbol in self.instruments_cache: url = self.instruments_cache[symbol] return {'symbol': symbol, 'url': url} def add_instrument_from_url(self, url): data = self.trader.get_url(url) if 'symbol' in data: symbol = data['symbol'] else: types = {'call': 'C', 'put': 'P'} symbol = data['chain_symbol'] + ' ' + data[ 'expiration_date'] + ' ' + ''.join( types[data['type']].split('-')) + ' ' + str( float(data['strike_price'])) self.add_instrument(url, symbol) def add_instrument(self, url, symbol): self.instruments_cache[symbol] = url self.instruments_reverse_cache[url] = symbol
class Trader: def __init__(self): cfg = self.get_config() self.user_name = cfg.get('robinhood').get('user') self.password = cfg.get('robinhood').get('password') self.client = Robinhood() self.client.login(username=self.user_name, password=self.password) self.stock_list = self.get_stock_list(cfg.get('stock')) self.stock_owned() def get_stock_list(self, stock_dict): stock_list = [] for stock in stock_dict: instrument = self.client.instrument(stock)[0].get('url') instrument_id = self.client.instrument(stock)[0].get('id') stock_details = stock_dict.get(stock) stock_details['instrument'] = instrument stock_details['instrument_id'] = instrument_id stock_details['symobl'] = stock stock_list.append(stock_details) return stock_list def stock_owned(self): stocks = self.client.securities_owned() for stock in stocks.get('results'): for s in self.stock_list: if s.get('instrument') == stock.get('instrument'): s['amount_owned'] = float(stock.get('quantity')) s['purchase_price'] = float( stock.get('pending_average_buy_price')) s['sell_price'] = float(s['purchase_price']) + float( s.get('target-profit')) break else: s['amount_owned'] = 0 s['purchase_price'] = 0 if not stock.get('purchase_price'): s['sell_price'] = float(s.get('median-price')) + float( s.get('target-profit')) s['purchase_price'] = float(s.get('median-price')) + float( s.get('price-target')) def create_buy_order(self, stock): qty = stock.get('position') - stock.get('amount_owned') if qty > 0: stock['order'] = self.client.place_limit_buy_order( None, stock.get('symobl'), 'GFD', stock.get('purchase_price'), qty).json() def create_sell_order(self, stock): qty = stock.get('amount_owned') if stock.get('can_sell_today'): self.client.place_limit_sell_order(None, stock.get('symobl'), 'GFD', stock.get('sell_price'), qty).json() else: print("this might trigger day trading sorry no order created") def should_i_stop_trading(self): for stock in self.stock_list: if stock.get('can_sell_today'): print(stock) return False return True def create_market_position(self): for stock in self.stock_list: if stock.get('amount_owned') == 0: #prevent day tradding stock['can_sell_today'] = False self.create_buy_order(stock) stock['amount_owned'] = stock.get('position') else: self.create_sell_order(stock) def check_if_sell_order_confirmed(self): for stock in self.stock_list: order = stock.get('order') if order and order.get('side') == 'sell': order_state = self.client.order_history( order.get('id')).get('state') if order_state == "filled": stock['amount_owned'] = 0 self.create_market_position() def get_config(self): with open("config.yaml", 'r') as ymlfile: cfg = yaml.load(ymlfile) return cfg
class Query: # __init__:Void # param email:String => Email of the Robinhood user. # param password:String => Password for the Robinhood user. def __init__(self, email, password): self.trader = Robinhood() self.trader.login(username=email, password=password) self.email = email self.password = password ## ## # Getters # ## ## # get_fundamentals_by_criteria:[String] # param price_range:(float, float) => High and low prices for the queried fundamentals. # returns List of symbols that fit the given criteria. def get_fundamentals_by_criteria(self, price_range=(0.00, sys.maxsize), tags=None): all_symbols = [] if tags is not None and tags is not []: if isinstance(tags, Enum): try: all_symbols = self.get_by_tag(tag) except Exception as e: pass else: for tag in tags: try: all_symbols += self.get_by_tag(tag) except Exception as e: pass else: all_symbols = [ instrument['symbol'] for instrument in self.trader.instruments_all() ] queried_fundamentals = [] for symbol in all_symbols: try: fundamentals = self.get_fundamentals(symbol) if fundamentals is not None and 'low' in fundamentals and 'high' in fundamentals and float( fundamentals['low'] or -1) >= price_range[0] and float( fundamentals['high'] or sys.maxsize + 1) <= price_range[1]: fundamentals['symbol'] = symbol queried_fundamentals.append(fundamentals) except Exception as e: continue return queried_fundamentals # get_symbols_by_criteria:[String] # param price_range:(float, float) => High and low prices for the queried symbols. # returns List of symbols that fit the given criteria. def get_symbols_by_criteria(self, price_range=(0.00, sys.maxsize), tags=None): queried_fundamentals = self.get_fundamentals_by_criteria( price_range, tags) queried_symbols = [ fundamentals['symbol'] for fundamentals in queried_fundamentals ] return queried_symbols # get_current_price:[String:String] # param symbol:String => String symbol of the instrument to return. # returns Float value of the current price of the stock with the given symbol. def get_current_price(self, symbol): return float(self.trader.quote_data(symbol)['last_trade_price']) # get_quote:[String:String] # param symbol:String => String symbol of the instrument to return. # returns Quote data for the instrument with the given symbol. def get_quote(self, symbol): return self.trader.quote_data(symbol) # get_quotes:[[String:String]] # param symbol:[String] => List of string symbols of the instrument to return. # returns Quote data for the instruments with the given symbols. def get_quotes(self, symbols): return self.trader.quotes_data(symbols) # get_instrument:[String:String] # param symbol:String => String symbol of the instrument. # returns The instrument with the given symbol. def get_instrument(self, symbol): return self.trader.instruments(symbol)[0] or None # stock_from_instrument_url:Dict[String:String] # param url:String => URL of instrument. # returns Stock dictionary from the url of the instrument. def stock_from_instrument_url(self, url): return self.trader.stock_from_instrument_url(url) # get_history:[[String:String]] # param symbol:String => String symbol of the instrument. # param interval:Span => Time in between each value. (default: DAY) # param span:Span => Range for the data to be returned. (default: YEAR) # param bounds:Span => The bounds to be included. (default: REGULAR) # returns Historical quote data for the instruments with the given symbols on a 5-minute, weekly interval. def get_history(self, symbol, interval=Span.DAY, span=Span.YEAR, bounds=Bounds.REGULAR): return self.trader.get_historical_quotes(symbol, interval.value, span.value, bounds.value) # get_news:[[String:String]] # param symbol:String => String symbol of the instrument. # returns News for the instrument with the given symbol. def get_news(self, symbol): return self.trader.get_news(symbol) # get_fundamentals:Dict[String:String] # param symbol:String => String symbol of the instrument. # returns Fundamentals for the instrument with the given symbol. def get_fundamentals(self, symbol): return self.trader.get_fundamentals(symbol) # get_fundamentals:[String:String] # param symbol:String => String symbol of the instrument. # param dates:Date => List of datetime.date objects. # param type:Option => Option.CALL or Option.PUT # returns Options for the given symbol within the listed dates for the given type. def get_options(self, symbol, dates, type): return self.trader.get_options( symbol, list(map(lambda date: date.isoFormat(), dates)), type.value) # get_market_data:[String:String] # param optionId:String => Option ID for the option to return. # returns Options for the given ID. def get_market_data(self, optionId): return self.trader.get_option_market_data(optionId) # get_by_tag:[String:String] # param tag:Tag => Type of tag to return the quotes by. # returns Quotes for the given tag. def get_by_tag(self, tag): return self.trader.get_tickers_by_tag(tag.value) # get_current_bid_price:Float # param symbol:String => String symbol of the quote. # returns The current bid price of the stock, as a float. def get_current_bid_price(self, symbol): return float(self.trader.get_quote(symbol)['bid_price']) or 0.0 ## ## # User Methods # ## ## # user_portfolio:[String:String] # returns Portfolio model for the logged in user. def user_portfolio(self): quotes = [] user_portfolio = self.user_stock_portfolio() for data in user_portfolio: symbol = data['symbol'] count = float(data['quantity']) quotes.append(Quote(symbol, count)) return Portfolio(self, quotes, 'User Portfolio') # user_stock_portfolio:[String:String] # TODO: Better documentation. # returns Stock perfolio for the user. def user_stock_portfolio(self): positions = self.trader.positions()['results'] or [] return list( map( lambda position: Utility.merge_dicts( position, self.trader.session.get(position['instrument'], timeout=15) .json()), positions)) # user_portfolio:[String:String] # returns Positions for the logged in user. def user_positions(self): return self.trader.positions() # user_dividends:[String:String] # returns Dividends for the logged in user. def user_dividends(self): return self.trader.dividends() # user_securities:[String:String] # returns Securities for the logged in user. def user_securities(self): return self.trader.securities_owned() # user_equity:[String:String] # returns Equity for the logged in user. def user_equity(self): return self.trader.equity() # user_equity_prev:[String:String] # returns Equity upon the previous close for the logged in user. def user_equity_prev(self): return self.trader.equity_previous_close() # user_equity_adj_prev:[String:String] # returns Adjusted equity upon the previous close for the logged in user. def user_equity_adj_prev(self): return self.trader.adjusted_equity_previous_close() # user_equity_ext_hours:[String:String] # returns Extended hours equity for the logged in user. def user_equity_ext_hours(self): return self.trader.extended_hours_equity() # user_equity_last_core:[String:String] # returns Last core equity for the logged in user. def user_equity_last_core(self): return self.trader.last_core_equity() # user_excess_margin:[String:String] # returns Excess margin for the logged in user. def user_excess_margin(self): return self.trader.excess_margin() # user_market_value:[String:String] # returns Market value for the logged in user. def user_market_value(self): return self.trader.market_value() # user_market_value_ext_hours:[String:String] # returns Extended hours market value for the logged in user. def user_market_value_ext_hours(self): return self.trader.extended_hours_market_value() # user_market_value_last_core:[String:String] # returns Last core market value for the logged in user. def user_market_value_last_core(self): return self.trader.last_core_market_value() # user_order_history:[String:String] # param orderId:String => The order ID to return the order for. # returns A specified order executed by the logged in user. def user_order(self, orderId): return self.trader.order_history(orderId) # user_orders:[[String:String]] # returns The order history for the logged in user. def user_orders(self): return self.trader.order_history(None) # user_open_orders:[[String:String]] # returns The open orders for the user def user_open_orders(self): orders = self.trader.order_history(None)['results'] open_orders = [] for order in orders: if order['state'] == 'queued': open_orders.append(order) return open_orders # user_account:[[String:String]] # returns The user's account. def user_account(self): return self.trader.get_account() # user_buying_power:float # returns The user's buying power. def user_buying_power(self): return float(self.trader.get_account()['buying_power'] or 0.0) ## ## # Execution Methods # ## ## # exec_buy:[String:String] # param symbol:String => String symbol of the instrument. # param quantity:Number => Number of shares to execute buy for. # param stop:Number? => Sets a stop price on the buy, if not None. # param limit:Number? => Sets a limit price on the buy, if not None. # param time:GoodFor? => Defines the expiration for a limited buy. # returns The order response. def exec_buy(self, symbol, quantity, stop=None, limit=None, time=None): if time is None: time = GoodFor.GOOD_TIL_CANCELED if limit is not None: if stop is not None: return self.trader.place_stop_limit_buy_order( None, symbol, time.value, stop, quantity) return self.trader.place_limit_buy_order(None, symbol, time.value, limit, quantity) elif stop is not None: return self.trader.place_stop_loss_buy_order( None, symbol, time.value, stop, quantity) return self.trader.place_market_buy_order(None, symbol, time.value, quantity) # exec_sell:[String:String] # param symbol:String => String symbol of the instrument. # param quantity:Number => Number of shares to execute sell for. # param stop:Number? => Sets a stop price on the sell, if not None. # param limit:Number? => Sets a limit price on the sell, if not None. # param time:GoodFor? => Defines the expiration for a limited buy. # returns The order response. def exec_sell(self, symbol, quantity, stop=None, limit=None, time=None): if time is None: time = GoodFor.GOOD_TIL_CANCELED if limit is not None: if stop is not None: return self.trader.place_stop_limit_sell_order( None, symbol, time.value, stop, quantity) return self.trader.place_limit_sell_order(None, symbol, time.value, limit, quantity) elif stop is not None: return self.trader.place_stop_loss_sell_order( None, symbol, time.value, stop, quantity) return self.trader.place_market_sell_order(None, symbol, time.value, quantity) # exec_cancel:[String:String] # param order_id:String => ID of the order to cancel. # returns The canceled order response. def exec_cancel(self, order_id): return self.trader.cancel_order(order_id) # exec_cancel_open_orders:[String] # returns A list of string IDs for the cancelled orders. def exec_cancel_open_orders(self): orders = self.trader.order_history(None)['results'] cancelled_order_ids = [] for order in orders: if order['state'] == 'queued': self.trader.cancel_order(order['id']) cancelled_order_ids.append(order['id']) return cancelled_order_ids
trader = Robinhood() logged_in = trader.login(username=USERNAME, password=getpass.getpass()) stock_instrument = None while True: quote_info = trader.quote_data(STOCK_NAME) stock_instrument = trader.instruments(STOCK_NAME)[0] print("################################") print("Time: {}".format(time.ctime())) print("################################") print("Name: {}".format(stock_instrument['name'])) print("Symbol: {}".format(STOCK_NAME)) print("Price: ${}".format(quote_info['last_trade_price'])) instrument_url = stock_instrument['url'] owned = trader.securities_owned()['results'] print("################################") for sec in owned: if sec['instrument'] == instrument_url: price = sec['average_buy_price'] quantity = sec['quantity'] print("Ave. Price: ${}".format(price)) print("Shares: {}".format(quantity)) print("Total Value: ${}".format( round(float(price) * float(quantity)), 2)) print("################################\n") def sellIf(instrument, current_price, sell_price, quantity): if current_price >= sell_price: return trader.place_sell_order(instrument, quantity)
class RobinhoodAccount: def __init__(self, username: str, password: str): self.username = username self.password = password self.robinhood = Robinhood() self.loggedin = False def __enter__(self): if self.loggedin: self.logout() self.login() def __exit__(self, *args): self.logout() def login(self): self.loggedin = self.robinhood.login(self.username, self.password) return self.loggedin def logout(self): self.robinhood.logout() def get_positions(self): positions = {} for pos in self.robinhood.securities_owned()['results']: symbol = RobinhoodUtility.instrument_2_symbol(pos['instrument']) pos['symbol'] = symbol positions[symbol] = pos return positions def get_open_orders(self, symbol: str) -> []: open_orders = [] for order in self.robinhood.order_history()['results']: if RobinhoodUtility.is_order_open(order): if symbol == RobinhoodUtility.instrument_2_symbol( order['instrument']): open_orders.append(order) return open_orders def cancel_all_orders(self, symbol: str): for order in self.get_open_orders(symbol): self.cancel_order(order) def cancel_order(self, order): res = self.robinhood.session.post( 'https://api.robinhood.com/orders/%s/cancel/' % order['id']) res.raise_for_status() def place_limit_sell_order(self, position, limit_price: float, time_in_force: str = 'GFD', shares: int = None): self.robinhood.place_limit_sell_order( instrument_URL=position['instrument'], symbol=position['symbol'], time_in_force=time_in_force, price=limit_price, quantity=shares or int(float(position['quantity']))) def place_stop_limit_sell_order(self, position, limit_price: float, stop_price: float = None, time_in_force: str = 'GFD', shares: int = None): self.robinhood.place_stop_limit_sell_order( instrument_URL=position['instrument'], symbol=position['symbol'], time_in_force=time_in_force, price=limit_price, stop_price=stop_price or limit_price, quantity=shares or int(float(position['quantity'])))
class RobinhoodShell(cmd.Cmd): intro = 'Welcome to the Robinhood shell. Type help or ? to list commands.\n' prompt = '> ' # API Object trader = None # Cache file used to store instrument cache instruments_cache_file = 'instruments.data' # Maps Symbol to Instrument URL instruments_cache = {} # Maps URL to Symbol instruments_reverse_cache = {} # Cache file used to store instrument cache watchlist_file = 'watchlist.data' # List of stocks in watchlist watchlist = [] def __init__(self): cmd.Cmd.__init__(self) self.trader = Robinhood() self.trader.login(username=USERNAME, password=PASSWORD) try: data = open(self.instruments_cache_file).read() self.instruments_cache = json.loads(data) for k in self.instruments_cache: self.instruments_reverse_cache[self.instruments_cache[k]] = k except: pass try: data = open(self.watchlist_file).read() self.watchlist = json.loads(data) except: pass # ----- basic commands ----- def do_l(self, arg): 'Lists current portfolio' portfolio = self.trader.portfolios() print 'Equity Value:', portfolio['equity'] account_details = self.trader.get_account() if 'margin_balances' in account_details: print 'Buying Power:', account_details['margin_balances']['unallocated_margin_cash'] positions = self.trader.securities_owned() symbols = [] buy_price_data = {} for position in positions['results']: symbol = self.get_symbol(position['instrument']) buy_price_data[symbol] = position['average_buy_price'] symbols.append(symbol) quotes_data = {} if len(symbols) > 0: raw_data = self.trader.quotes_data(symbols) for quote in raw_data: quotes_data[quote['symbol']] = quote['last_trade_price'] table = BeautifulTable() table.column_headers = ["symbol", "current price", "quantity", "total equity", "cost basis", "p/l"] for position in positions['results']: quantity = int(float(position['quantity'])) symbol = self.get_symbol(position['instrument']) price = quotes_data[symbol] total_equity = float(price) * quantity buy_price = float(buy_price_data[symbol]) p_l = total_equity - buy_price * quantity table.append_row([symbol, price, quantity, total_equity, buy_price, p_l]) print(table) def do_w(self, arg): 'Show watchlist w \nAdd to watchlist w a <symbol> \nRemove from watchlist w r <symbol>' parts = arg.split() if len(parts) == 2: if parts[0] == 'a': self.watchlist.append(parts[1].strip()) if parts[0] == 'r': self.watchlist = [r for r in self.watchlist if not r == parts[1].strip()] print "Done" else: table = BeautifulTable() table.column_headers = ["symbol", "current price"] if len(self.watchlist) > 0: raw_data = self.trader.quotes_data(self.watchlist) quotes_data = {} for quote in raw_data: table.append_row([quote['symbol'], quote['last_trade_price']]) print(table) else: print "Watchlist empty!" def do_b(self, arg): 'Buy stock b <symbol> <quantity> <price>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0] quantity = parts[1] if len(parts) == 3: price = float(parts[2]) else: price = 0.0 stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_buy_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_s(self, arg): 'Sell stock s <symbol> <quantity> <?price>' parts = arg.split() if len(parts) >= 2 and len(parts) <= 3: symbol = parts[0] quantity = parts[1] if len(parts) == 3: price = float(parts[2]) else: price = 0.0 stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_sell_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_sl(self, arg): 'Setup stop loss on stock sl <symbol> <quantity> <price>' parts = arg.split() if len(parts) == 3: symbol = parts[0] quantity = parts[1] price = float(parts[2]) stock_instrument = self.trader.instruments(symbol)[0] res = self.trader.place_stop_loss_order(stock_instrument, quantity, price) if not (res.status_code == 200 or res.status_code == 201): print "Error executing order" try: data = res.json() if 'detail' in data: print data['detail'] except: pass else: print "Done" else: print "Bad Order" def do_o(self, arg): 'List open orders' open_orders = self.trader.get_open_orders() if open_orders: table = BeautifulTable() table.column_headers = ["index", "symbol", "price", "quantity", "type", "id"] index = 1 for order in open_orders: table.append_row([ index, self.get_symbol(order['instrument']), order['price'], int(float(order['quantity'])), order['side'], order['id'], ]) index += 1 print(table) else: print "No Open Orders" def do_c(self, arg): 'Cancel open order c <index> or c <id>' order_id = arg.strip() order_index = -1 try: order_index = int(order_id) except: pass if order_index > 0: order_index = order_index - 1 open_orders = self.trader.get_open_orders() if order_index < len(open_orders): order_id = open_orders[order_index]['id'] else: print "Bad index" return try: self.trader.cancel_order(order_id) print "Done" except Exception as e: print "Error executing cancel" print e def do_ca(self, arg): 'Cancel all open orders' open_orders = self.trader.get_open_orders() for order in open_orders: try: self.trader.cancel_order(order['id']) except Exception as e: pass print "Done" def do_q(self, arg): 'Get quote for stock q <symbol>' symbol = arg.strip() try: self.trader.print_quote(symbol) except: print "Error getting quote for:", symbol def do_bye(self, arg): open(self.instruments_cache_file, 'w').write(json.dumps(self.instruments_cache)) open(self.watchlist_file, 'w').write(json.dumps(self.watchlist)) return True # ------ utils -------- def get_symbol(self, url): if not url in self.instruments_reverse_cache: self.add_instrument_from_url(url) return self.instruments_reverse_cache[url] def add_instrument_from_url(self, url): data = self.trader.get_url(url) symbol = data['symbol'] self.instruments_cache[symbol] = url self.instruments_reverse_cache[url] = symbol
class PortfolioMgr: def __init__(self, robin_un=None, robin_pd=None, name=None, load_from=None): """ Manager for multiple portfolios in the same account robin_un (str): username of robinhood account robin_pd (str): password of robinhood account name (str): name of the manager load_from (str): path to the saving file """ assert robin_un is not None assert robin_pd is not None assert name is not None self.trader = Robinhood() self.trader.login(robin_un, robin_pd) self.name = name self.converter = SIconverter(trader=self.trader) self.unassigned_bp = float(self.trader.get_account()['margin_balances'] ['unallocated_margin_cash']) self.unassigned_shares = { self.converter(d['instrument']): int(float(d['quantity'])) for d in self.trader.securities_owned()['results'] } self.portfolios = {} self.regisiter = {} self.working_now = True self.working_cv = Condition() self.check_work_v = True self.check_work_cv = Condition() self.threads = [] self.log = [] def add_portfolio(self, name=None, ini_bp=0, load=None, cancel_count=np.inf): """ create a portfolio with the manager name (str): name of the portfolio ini_bp (float): initial buying power for this portfolio load (str): path to the saving file of this portfolio cancel_count (int|np.inf) used for specific trading algorithm, order will be canceled if it can not be executed for cancel_count times """ assert name is not None assert ini_bp < self.unassigned_bp self.portfolios[name] = Portfolio(trader=self.trader, name=name, iniFund=ini_bp, cancel_count=cancel_count, converter=self.converter) self.unassigned_bp -= ini_bp def update_allocatable_buying_power(self): """ update the unassigned_bp variable. should never be used unless the user transfers money between bank and robinhood account. """ allocated = 0 for k, p in self.portfolios.items(): allocated += p.bp self.unassigned_bp = self.get_bp_owned() - allocated def update_allocatable_shares(self): """ update the unassigned_shares variable. should never be used unless the user sell/buy shares without using this mgr """ owned_shares = self.get_securities_owned() for k, p in self.portfolios.items(): p.portfolio_record_lock.acquire() for scode in p.portfolio_record.index: try: owned_shares[scode] -= p.portfolio_record.loc[scode][ "SHARES"] except: pass p.portfolio_record_lock.release() self.unassigned_shares = owned_shares def get_securities_owned(self): """ get shares owned by the user """ return { self.converter(d['instrument']): int(float(d['quantity'])) for d in self.trader.securities_owned()['results'] } def get_bp_owned(self): """ get buying power owned by the user """ return float(self.trader.get_account()['margin_balances'] ['unallocated_margin_cash']) def add_bp_to(self, name, amount): """ add buying power from mgr to portfolio name (str): the name of the portfolio amount (float): money in USD to be added """ assert name in self.portfolios self.update_allocatable_buying_power() assert self.unassigned_bp > amount self.portfolios[name].bp += amount self.unassigned_bp -= amount self.portfolios[name].add_trading_record("None", "None", amount, 1, "add bp") def add_shares_to(self, name, scode, amount): """ add shares from mgr to portfolio name (str): the name op the portfolio scode (str): symbol of the stock amount (int): number of shares """ assert name in self.portfolios self.update_allocatable_shares() amount = int(amount) assert self.unassigned_shares[scode] >= amount self.portfolios[name].add_shares_from_pool(scode=scode, n=amount) self.unassigned_shares[scode] -= amount self.portfolios[name].add_trading_record("None", scode, "None", amount, "add share") def add_multi_shares_to(self, name, **sa): for s, a in sa.items(): self.add_shares_to(name, s, a) def draw_bp_from(self, name, amount): """ draw bp from a portfolio to mgr name (str): name of the portfolio amount (float): money in USD to be draw """ assert name in self.portfolios assert self.portfolios[name].bp >= amount self.portfolios[name].bp -= amount self.unassigned_bp += amount self.portfolios[name].add_trading_record("None", "None", amount, 1, "draw bp") def draw_shares_from(self, name, scode, amount): """ draw shares from a portfolio to mgr name (str): name of the portfolio scode (str): symbol of the stock amount (int): number of shares """ assert name in self.portfolios amount = int(amount) assert self.portfolios[name].shares_owned(scode) >= amount self.portfolios[name].portfolio_record_lock.acquire() self.portfolios[name].portfolio_record.loc[scode]["SHARES"] -= amount if self.portfolios[name].portfolio_record.loc[scode]["SHARES"] == 0: self.portfolios[name].portfolio_record = self.portfolios[ name].portfolio_record.drop(scode) self.portfolios[name].portfolio_record_lock.release() self.unassigned_shares[scode] += amount self.portfolios[name].add_trading_record("None", scode, "None", amount, "draw share") def transfer_bp(self, from_name, to_name, amount): """ transfer buying power between two portfolios from_name (str): name of the portfolio to send the buying power to_name (str): name of the portfolio to receive the buying power """ assert from_name in self.portfolios assert to_name in self.portfolios self.portfolios[from_name].transfer_buying_power( self.portfolios[to_name], amount) def transfer_shares(self, from_name, to_name, scode, amount): """ transfer shares between two portfolios from_name (str): name of the portfolio to send shares to_name (str): name of the portfolio to receive shares """ assert from_name in self.portfolios assert to_name in self.portfolios self.portfolios[from_name].transfer_shares(self.portfolios[to_name], scode, amount) def schedule(self, algo=None, method=None, portfolio_name=None, freq=None, misc=None): """ schedule an algorithm with this portfolio """ assert portfolio_name not in self.regisiter assert algo is not None assert method is not None assert freq is not None p = self.portfolios[portfolio_name] if "cancel_count" in misc: p.cancel_count = misc["cancel_count"] def worker(): p.confirm_order(loop=True) while self.working_now: try: algo.__getattribute__(method)(self, pname=portfolio_name, args={ "call_from_mgr": True }, misc=misc) except AssertionError: p.unlock_all() p.log_lock.acquire() p.log.append("{}: Error Operation During Trading".format( Portfolio.get_time())) p.log_lock.release() self.working_cv.acquire() self.working_cv.wait(freq * 60) self.working_cv.release() p.log_lock.acquire() p.log.append("{}: {} worker stopped".format( Portfolio.get_time(), method)) p.log_lock.release() p.unlock_all() p.stop_confirm() p.cancel_all_orders_in_queue() self.regisiter[portfolio_name][0] = 'STOPED' self.regisiter[portfolio_name] = ["PENDING", worker] if self.working_now: self.regisiter[portfolio_name] = ["STARTED", worker] t = Thread(target=worker) self.threads.append(t) t.start() p.log_lock.acquire() p.log.append("{}: {} worker started".format( Portfolio.get_time(), method)) p.log_lock.release() def cnow(self): """ begin to check the market hour, once every 900 secs """ def worker(): while self.check_work_v: check_work() self.check_work_cv.acquire() self.log.append("{}: check work cv waiting now".format( Portfolio.get_time())) self.check_work_cv.wait(900) self.log.append("{}: check work cv escaped") self.check_work_cv.release() def dnow(self): """ stop checking the market hour """ self.check_work_v = False self.log.append("{}: try down".format(Portfolio.get_time())) self.check_work_cv.acquire() self.check_work_cv.notify() self.check_work_cv.release() self.log.append("{}: down scuu".format(Portfolio.get_time())) def check_work(self): """ check the market hour, is market is open, all panding or stoped algorithm will start """ if not len(self.portfolios): self.working_now = False self.working_cv.acquire() self.working_cv.notifyAll() self.working_cv.release() if list(self.portfolios.values())[0].is_market_open(): self.working_now = True for key in self.regisiter: s, w = self.regisiter[key] if s != 'STARTED': t = Thread(target=w) self.threads.append(t) t.start() self.regisiter[key][0] = 'STARTED' else: self.working_now = False self.working_cv.acquire() self.working_cv.notifyAll() self.working_cv.release() def quit(self): """ quit this s**t """ self.working_now = False self.check_work_v = False self.check_work_cv.acquire() self.check_work_cv.notify() self.check_work_cv.release() self.working_cv.acquire() self.working_cv.notify() self.working_cv.release() while len(self.threads): self.threads.pop().join() for p in list(self.portfolios.values()): p.quit() def save(self, sav=None): """ save """ if sav is None: sav = self.name sav = sav + '/' for k, p in self.portfolios.items(): p.save(root_name=sav) def load(self, sav=None): """ load """ if sav is None: sav = self.name sav = sav + '/' for k, p in self.portfolios.items(): p.load(root_name=sav)