def build_user_profile(): """Builds a dictionary of important information regarding the user account. :returns: Returns a dictionary that has total equity, extended hours equity, cash, and divendend total. """ user = {} portfolios_data = profiles.load_portfolio_profile() accounts_data = profiles.load_account_profile() if portfolios_data: user['equity'] = portfolios_data['equity'] user['extended_hours_equity'] = portfolios_data[ 'extended_hours_equity'] if accounts_data: cash = "{0:.2f}".format( float(accounts_data['cash']) + float(accounts_data['uncleared_deposits'])) user['cash'] = cash user['dividend_total'] = get_total_dividends() return (user)
def get_historical_portfolio(interval=None, span='week', bounds='regular',info=None): interval_check = ['5minute', '10minute', 'hour', 'day', 'week'] span_check = ['day', 'week', 'month', '3month', 'year', '5year', 'all'] bounds_check = ['extended', 'regular', 'trading'] if interval not in interval_check: if interval is None and (bounds != 'regular' and span != 'all'): print ('ERROR: Interval must be None for "all" span "regular" bounds') return ([None]) print( 'ERROR: Interval must be "5minute","10minute","hour","day",or "week"') return([None]) if span not in span_check: print('ERROR: Span must be "day","week","month","3month","year",or "5year"') return([None]) if bounds not in bounds_check: print('ERROR: Bounds must be "extended","regular",or "trading"') return([None]) if (bounds == 'extended' or bounds == 'trading') and span != 'day': print('ERROR: extended and trading bounds can only be used with a span of "day"') return([None]) account = profiles.load_account_profile('account_number') url = urls.portfolis_historicals(account) payload = { 'interval': interval, 'span': span, 'bounds': bounds } data = helper.request_get(url, 'regular', payload) return(helper.filter(data, info))
def order(symbol, quantity, orderType, trigger, side, limitPrice = None, stopPrice = None, timeInForce = 'gtc', extendedHours = False): """A generic order function. All parameters must be supplied. :param symbol: The stock ticker of the stock to sell. :type symbol: str :param quantity: The number of stocks to sell. :type quantity: int :param orderType: Either 'market' or 'limit' :type orderType: str :param trigger: Either 'immediate' or 'stop' :type trigger: str :param side: Either 'buy' or 'sell' :type side: str :param limitPrice: The price to trigger the market order. :type limitPrice: float :param stopPrice: The price to trigger the limit or market order. :type stopPrice: float :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: str :param extendedHours: Premium users only. Allows trading during extended hours. Should be true or false. :type extendedHours: Optional[str] :returns: Dictionary that contains information regarding the purchase or selling of stocks, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() except AttributeError as message: print(message) return None if stopPrice: stopPrice = helper.round_price(stopPrice) if limitPrice: limitPrice = helper.round_price(limitPrice) else: limitPrice = helper.round_price(stocks.get_latest_price(symbol)[0]) payload = { 'account': profiles.load_account_profile(info='url'), 'instrument': stocks.get_instruments_by_symbols(symbol, info='url')[0], 'symbol': symbol, 'price': limitPrice, 'quantity': quantity, 'ref_id': str(uuid4()), 'type': orderType, 'stop_price': stopPrice, 'time_in_force': timeInForce, 'trigger': trigger, 'side': side, 'extended_hours': extendedHours } url = urls.orders() data = helper.request_post(url, payload) return(data)
def order_option_spread(direction, price, symbol, quantity, spread, timeInForce='gfd'): """Submits a limit order for an option spread. i.e. place a debit / credit spread :param direction: credit or debit spread :type direction: str :param price: The limit price to trigger a trade of the option. :type price: float :param symbol: The stock ticker of the stock to trade. :type symbol: str :param quantity: The number of options to trade. :type quantity: int :param spread: A dictionary of spread options with the following keys: \n - expirationDate: The expiration date of the option in 'YYYY-MM-DD' format.\n - strike: The strike price of the option.\n - optionType: This should be 'call' or 'put' :type spread: dict :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: Optional[str] :returns: Dictionary that contains information regarding the selling of options, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() except AttributeError as message: print(message) return None legs = [] for each in spread: optionID = helper.id_for_option(symbol, each['expirationDate'], each['strike'], each['optionType']) legs.append({'position_effect': each['effect'], 'side' : each['action'], 'ratio_quantity': 1, 'option': urls.option_instruments(optionID)}) payload = { 'account': profiles.load_account_profile(info='url'), 'direction': direction, 'time_in_force': timeInForce, 'legs': legs, 'type': 'limit', 'trigger': 'immediate', 'price': price, 'quantity': quantity, 'override_day_trade_checks': False, 'override_dtbp_checks': False, 'ref_id': str(uuid4()), } url = urls.option_orders() data = helper.request_post(url,payload, json=True) return(data)
def order_sell_stop_limit(symbol, quantity, limitPrice, stopPrice, timeInForce='gtc', extendedHours=False): """Submits a stop order to be turned into a limit order once a certain stop price is reached. :param symbol: The stock ticker of the stock to sell. :type symbol: str :param quantity: The number of stocks to sell. :type quantity: int :param limitPrice: The price to trigger the market order. :type limitPrice: float :param stopPrice: The price to trigger the limit order. :type stopPrice: float :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: Optional[str] :param extendedHours: Premium users only. Allows trading during extended hours. Should be true or false. :type extendedHours: Optional[str] :returns: Dictionary that contains information regarding the selling of stocks, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() latestPrice = helper.round_price(stocks.get_latest_price(symbol)[0]) stopPrice = helper.round_price(stopPrice) limitPrice = helper.round_price(limitPrice) except AttributeError as message: print(message) return None if (latestPrice < stopPrice): print('Error: stopPrice must be below the current price.') return (None) payload = { 'account': profiles.load_account_profile(info='url'), 'instrument': stocks.get_instruments_by_symbols(symbol, info='url')[0], 'symbol': symbol, 'price': limitPrice, 'quantity': quantity, 'ref_id': str(uuid4()), 'type': 'limit', 'stop_price': stopPrice, 'time_in_force': timeInForce, 'trigger': 'stop', 'side': 'sell', 'extended_hours': extendedHours } url = urls.orders() data = helper.request_post(url, payload) return (data)
def order_sell_option_limit(positionEffect, price, symbol, quantity, expirationDate, strike, optionType='both', timeInForce='gfd'): """Submits a limit order for an option. i.e. place a short call or a short put. :param positionEffect: Either 'open' for a sell to open effect or 'close' for a sell to close effect. :type positionEffect: str :param price: The limit price to trigger a sell of the option. :type price: float :param symbol: The stock ticker of the stock to trade. :type symbol: str :param quantity: The number of options to sell. :type quantity: int :param expirationDate: The expiration date of the option in 'YYYY-MM-DD' format. :type expirationDate: str :param strike: The strike price of the option. :type strike: float :param optionType: This should be 'call' or 'put' :type optionType: str :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: Optional[str] :returns: Dictionary that contains information regarding the selling of options, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() except AttributeError as message: print(message) return None optionID = helper.id_for_option(symbol, expirationDate, strike, optionType) if (positionEffect == 'close'): direction = 'debit' else: direction = 'credit' payload = { 'account': profiles.load_account_profile(info='url'), 'direction': direction, 'time_in_force': timeInForce, 'legs': [ {'position_effect': positionEffect, 'side' : 'sell', 'ratio_quantity': 1, 'option': urls.option_instruments(optionID) }, ], 'type': 'limit', 'trigger': 'immediate', 'price': price, 'quantity': quantity, 'override_day_trade_checks': False, 'override_dtbp_checks': False, 'ref_id': str(uuid4()), } url = urls.option_orders() data = helper.request_post(url, payload, json=True) return(data)
def build_holdings(): """Builds a dictionary of important information regarding the stocks and positions owned by the user. :returns: Returns a dictionary where the keys are the stock tickers and the value is another dictionary \ that has the stock price, quantity held, equity, percent change, equity change, type, name, id, pe ratio, \ percentage of portfolio, and average buy price. """ positions_data = get_current_positions() portfolios_data = profiles.load_portfolio_profile() accounts_data = profiles.load_account_profile() if not positions_data or not portfolios_data or not accounts_data: return({}) if portfolios_data['extended_hours_equity'] is not None: total_equity = max(float(portfolios_data['equity']),float(portfolios_data['extended_hours_equity'])) else: total_equity = float(portfolios_data['equity']) cash = "{0:.2f}".format(float(accounts_data['cash'])+float(accounts_data['uncleared_deposits'])) holdings = {} for item in positions_data: # It is possible for positions_data to be [None] if not item: continue try: instrument_data = stocks.get_instrument_by_url(item['instrument']) symbol = instrument_data['symbol'] fundamental_data = stocks.get_fundamentals(symbol)[0] price = stocks.get_latest_price(instrument_data['symbol'])[0] quantity = item['quantity'] equity = float(item['quantity'])*float(price) equity_change = (float(quantity)*float(price))-(float(quantity)*float(item['average_buy_price'])) percentage = float(item['quantity'])*float(price)*100/(float(total_equity)-float(cash)) if (float(item['average_buy_price']) == 0.0): percent_change = 0.0 else: percent_change = (float(price)-float(item['average_buy_price']))*100/float(item['average_buy_price']) holdings[symbol]=({'price': price }) holdings[symbol].update({'quantity': quantity}) holdings[symbol].update({'average_buy_price': item['average_buy_price']}) holdings[symbol].update({'equity':"{0:.2f}".format(equity)}) holdings[symbol].update({'percent_change': "{0:.2f}".format(percent_change)}) holdings[symbol].update({'equity_change':"{0:2f}".format(equity_change)}) holdings[symbol].update({'type': instrument_data['type']}) holdings[symbol].update({'name': stocks.get_name_by_symbol(symbol)}) holdings[symbol].update({'id': instrument_data['id']}) holdings[symbol].update({'pe_ratio': fundamental_data['pe_ratio'] }) holdings[symbol].update({'percentage': "{0:.2f}".format(percentage)}) except: pass return(holdings)
def order_option_by_id(option_id, price, quantity, direction='credit', effect='close', side='sell', time_in_force='gfd'): """ :param option_id: :param price: :param quantity: :param direction: :param effect: :param side: :param time_in_force: :return: """ payload = { 'account': profiles.load_account_profile(info='url'), 'direction': direction, 'time_in_force': time_in_force, 'legs': [ { 'position_effect': effect, 'side': side, 'ratio_quantity': 1, 'option': urls.option_instruments(option_id) }, ], 'type': 'limit', 'trigger': 'immediate', 'price': price, 'quantity': quantity, 'override_day_trade_checks': False, 'override_dtbp_checks': False, 'ref_id': str(uuid4()), } url = urls.option_orders() data = helper.request_post(url, payload, json=True) print(data) return data
def get_day_trades(info=None): """Returns recent day trades. :param info: Will filter the results to get a specific value. :type info: Optional[str] :returns: Returns a list of dictionaries of key/value pairs for each day trade. If info parameter is provided, \ a list of strings is returned where the strings are the value of the key that matches info. """ account = profiles.load_account_profile('account_number') url = urls.daytrades(account) data = helper.request_get(url, 'pagination') return(helper.filter(data, info))
def order_buy_stop_limit(symbol,quantity,limitPrice,stopPrice,timeInForce='gtc'): """Submits a stop order to be turned into a limit order once a certain stop price is reached. :param symbol: The stock ticker of the stock to purchase. :type symbol: str :param quantity: The number of stocks to buy. :type quantity: int :param limitPrice: The price to trigger the market order. :type limitPrice: float :param stopPrice: The price to trigger the limit order. :type stopPrice: float :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: Optional[str] :returns: Dictionary that contains information regarding the purchase of stocks, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() latestPrice = float(stocks.get_latest_price(symbol)[0]) stopPrice = float(stopPrice) limitPrice = float(limitPrice) except AttributeError as message: print(message) return None if (latestPrice > stopPrice): print('Error: stopPrice must be above the current price.') return(None) payload = { 'account': profiles.load_account_profile(info='url'), 'instrument': stocks.get_instruments_by_symbols(symbol,info='url')[0], 'symbol': symbol, 'price': limitPrice, 'quantity': quantity, 'ref_id': helper.get_device_token(), 'type': 'limit', 'stop_price': stopPrice, 'time_in_force': timeInForce, 'trigger': 'stop', 'side': 'buy' } url = urls.orders() data = helper.request_post(url,payload) return(data)
def order_buy_market(symbol, quantity, timeInForce='gtc', extendedHours='false'): """Submits a market order to be executed immediately. :param symbol: The stock ticker of the stock to purchase. :type symbol: str :param quantity: The number of stocks to buy. :type quantity: int :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: Optional[str] :param extendedHours: Premium users only. Allows trading during extended hours. Should be true or false. :type extendedHours: str :returns: Dictionary that contains information regarding the purchase of stocks, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() except AttributeError as message: print(message) return None payload = { 'account': profiles.load_account_profile(info='url'), 'instrument': stocks.get_instruments_by_symbols(symbol, info='url')[0], 'symbol': symbol, 'price': float(stocks.get_latest_price(symbol)[0]), 'quantity': quantity, 'ref_id': str(uuid4()), 'type': 'market', 'stop_price': None, 'time_in_force': timeInForce, 'trigger': 'immediate', 'side': 'buy', "extended_hours": extendedHours } url = urls.orders() data = helper.request_post(url, payload) return (data)
def order_sell_limit(symbol,quantity,limitPrice,timeInForce='gtc'): """Submits a limit order to be executed once a certain price is reached. :param symbol: The stock ticker of the stock to sell. :type symbol: str :param quantity: The number of stocks to sell. :type quantity: int :param limitPrice: The price to trigger the sell order. :type limitPrice: float :param timeInForce: Changes how long the order will be in effect for. 'gtc' = good until cancelled. \ 'gfd' = good for the day. 'ioc' = immediate or cancel. 'opg' execute at opening. :type timeInForce: Optional[str] :returns: Dictionary that contains information regarding the selling of stocks, \ such as the order id, the state of order (queued,confired,filled, failed, canceled, etc.), \ the price, and the quantity. """ try: symbol = symbol.upper().strip() limitPrice = helper.round_price(limitPrice) except AttributeError as message: print(message) return None payload = { 'account': profiles.load_account_profile(info='url'), 'instrument': stocks.get_instruments_by_symbols(symbol,info='url')[0], 'symbol': symbol, 'price': limitPrice, 'quantity': quantity, 'ref_id': str(uuid4()), 'type': 'limit', 'stop_price': None, 'time_in_force': timeInForce, 'trigger': 'immediate', 'side': 'sell' } url = urls.orders() data = helper.request_post(url,payload) return(data)
def build_holdings(access_token, with_dividends=False): """Builds a dictionary of important information regarding the stocks and positions owned by the user. :param with_dividends: True if you want to include divident information. :type with_dividends: bool :returns: Returns a dictionary where the keys are the stock tickers and the value is another dictionary \ that has the stock price, quantity held, equity, percent change, equity change, type, name, id, pe ratio, \ percentage of portfolio, and average buy price. """ positions_data = get_open_stock_positions(access_token) portfolios_data = profiles.load_portfolio_profile(access_token) accounts_data = profiles.load_account_profile(access_token) # user wants dividend information in their holdings if with_dividends is True: dividend_data = get_dividends() if not positions_data or not portfolios_data or not accounts_data: return ({}) if portfolios_data['extended_hours_equity'] is not None: total_equity = max(float(portfolios_data['equity']), float(portfolios_data['extended_hours_equity'])) else: total_equity = float(portfolios_data['equity']) cash = "{0:.2f}".format( float(accounts_data['cash']) + float(accounts_data['uncleared_deposits'])) holdings = {} for item in positions_data: # It is possible for positions_data to be [None] if not item: continue try: instrument_data = stocks.get_instrument_by_url(item['instrument'], access_token, info=None) symbol = instrument_data['symbol'] # fundamental_data = stocks.get_fundamentals(symbol, access_token, info=None)[0] price = stocks.get_latest_price(instrument_data['symbol'], access_token=access_token)[0] quantity = item['quantity'] equity = float(item['quantity']) * float(price) equity_change = (float(quantity) * float(price)) - \ (float(quantity) * float(item['average_buy_price'])) percentage = float(item['quantity']) * float(price) * \ 100 / (float(total_equity) - float(cash)) if (float(item['average_buy_price']) == 0.0): percent_change = 0.0 else: percent_change = (float(price) - float( item['average_buy_price'])) * 100 / float( item['average_buy_price']) holdings[symbol] = ({'price': price}) holdings[symbol].update({'quantity': quantity}) holdings[symbol].update( {'average_buy_price': item['average_buy_price']}) holdings[symbol].update({'equity': "{0:.2f}".format(equity)}) holdings[symbol].update( {'percent_change': "{0:.2f}".format(percent_change)}) holdings[symbol].update( {'equity_change': "{0:2f}".format(equity_change)}) holdings[symbol].update( {'percentage': "{0:2f}".format(percentage)}) holdings[symbol].update({'type': instrument_data['type']}) holdings[symbol].update( {'name': stocks.get_name_by_symbol(symbol)}) holdings[symbol].update({'id': instrument_data['id']}) # holdings[symbol].update( # {'percentage': "{0:2f}".format(percentage)}) # holdings[symbol].update({'pe_ratio': fundamental_data['pe_ratio']}) if with_dividends is True: # dividend_data was retrieved earlier holdings[symbol].update( get_dividends_by_instrument(item['instrument'], dividend_data)) except: pass return (holdings)
def rh_pull_portfolio_data(user_id, passwd): r.login(username=user_id, password=passwd) if DbAccess.is_update_needed(): obj = portfolio_summary.objects.all() if not obj: obj = portfolio_summary() obj.timestamp = timezone.now() else: obj = obj[0] obj.portfolio_cash = float(profiles.load_account_profile()['portfolio_cash']) obj.save() # remove current entries stocks_held.objects.all().delete() # get current owned securites and save to db positions_data = r.get_open_stock_positions() for item in positions_data: quantity = float(item['quantity']) if not quantity: continue # check if instrument is present in robinhood_traded_stocks table obj = stocks_held() obj.symbol, obj.name = RhWrapper.rh_pull_symbol_from_instrument_url(item['instrument']) obj.quantity = quantity obj.average_price = float(item['average_buy_price']) obj.latest_price = float(r.get_latest_price(obj.symbol)[0]) obj.open_price = float(r.get_fundamentals(obj.symbol)[0]['open']) try: obj.prev_close_price = float(StockUtils.getStockInfo(obj.symbol)['previousClose']) except Exception as e: logging.error(str(e) + ' encountered when fetching yahoo data for ' + obj.symbol) obj.prev_close_price = obj.open_price obj.save() # remove current entries options_held.objects.all().delete() # get current owned securites and save to db options_position_data = r.get_open_option_positions() for item in options_position_data: quantity = float(item['quantity']) if not quantity: continue obj = options_held() obj.option_id = item['option_id'] contract_into = r.get_option_instrument_data_by_id(obj.option_id) obj.strike_price = float(contract_into['strike_price']) obj.expiration_date = contract_into['expiration_date'] obj.option_type = contract_into['type'] obj.symbol = item['chain_symbol'] obj.trade_value_multiplier = float(item['trade_value_multiplier']) obj.average_price = float(item['average_price']) / obj.trade_value_multiplier obj.quantity = float(item['quantity']) # adjust value for sold calls if obj.average_price < 0: obj.average_price = (-1) * obj.average_price obj.quantity = (-1) * obj.quantity market_data = r.get_option_market_data_by_id(obj.option_id) obj.previous_close_price = float(market_data['previous_close_price']) obj.current_price = float(market_data['adjusted_mark_price']) # print('symbol: %5s strike_price: %7.2f option_type: %4s expiration_date: %12s trade_value_multiplier: %d average_price: %7.2f quantity: %d previous_close_price: %7.2f current_price: %7.2f' % (obj.symbol, obj.strike_price, obj.option_type, obj.expiration_date, obj.trade_value_multiplier, obj.average_price, obj.quantity, obj.previous_close_price, obj.current_price)) obj.save()