def login2(data, payload, url, pickle_path, device_token, auth_key, store_session=True): if data: if 'mfa_required' in data: mfa_token = auth_key payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) if (res.status_code != 200): raise Exception( "That MFA code was not correct. Please type in another MFA code." ) data = res.json() elif 'challenge' in data: challenge_id = data['challenge']['id'] sms_code = auth_key res = respond_to_challenge(challenge_id, sms_code) if 'challenge' in res and res['challenge'][ 'remaining_attempts'] > 0: raise Exception( 'That code was not correct. {0} tries remaining. Please type in another code: ' .format(res['challenge']['remaining_attempts'])) helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "logged in with brand new authentication code." if store_session: with open(pickle_path, 'wb') as f: pickle.dump( { 'token_type': data['token_type'], 'access_token': data['access_token'], 'refresh_token': data['refresh_token'], 'device_token': device_token }, f) else: raise Exception(data['detail']) else: raise Exception( 'Error: Trouble connecting to robinhood API. Check internet connection.' ) return True, (data)
def cancel_all_open_option_orders(): """Cancels all open orders. :returns: The list of orders that were cancelled. """ items = get_all_open_option_orders() for item in items: cancel_url = item.get('cancel_url') helper.request_post(cancel_url) print('All Orders Cancelled') return items
def login(username, password, expiresIn=86400, scope='internal'): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :returns: A dictionary with log in information. """ if not username or not password: raise Exception('login must be called with a non-empty username and ' 'password') url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username } data = helper.request_post(url, payload) token = 'Bearer {}'.format(data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) return (data)
def delete_symbols_from_watchlist(inputSymbols, name="My First List"): """Deletes multiple stock tickers from a watchlist. :param inputSymbols: May be a single stock ticker or a list of stock tickers. :type inputSymbols: str or list :param name: The name of the watchlist to delete data from. :type name: Optional[str] :returns: Returns result of the delete request. """ symbols = helper.inputs_to_set(inputSymbols) ids = stocks.get_instruments_by_symbols(symbols, info='id') data = [] #Get id of requested watchlist all_watchlists = get_all_watchlists() watchlist_id = '' for wl in all_watchlists['results']: if wl['display_name'] == name: watchlist_id = wl['id'] for id in ids: payload = { watchlist_id: [{ "object_type": "instrument", "object_id": id, "operation": "delete" }] } url = urls.watchlists(name, True) data.append(helper.request_post(url, payload, json=True)) return (data)
def generate_challenge(user: str, passwd: str, device_token: str, sendviasms: bool) -> str: """ Generate a 2FA challenge ID by logging into Robinhood with the user/pass. This is a stripped down version of the robin_stocks.authentication module. """ if sendviasms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() #Client ID appears to be a hardcoded magic number from client requests? #Something to watch out for. Could be related to User-Agent & app version. payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': 86400, 'grant_type': 'password', 'password': passwd, 'scope': 'internal', 'username': user, 'challenge_type': challenge_type, 'device_token': device_token } #Initial query to create a challenge request. data = helper.request_post(url, payload) try: return data['challenge']['id'] except KeyError: return ""
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 cancel_all_open_orders(): """Cancels all open orders. :returns: The list of orders that were cancelled. """ url = urls.orders() items = helper.request_get(url, 'pagination') items = [item['id'] for item in items if item['cancel'] is not None] for item in items: cancel_url = urls.cancel(item) helper.request_post(cancel_url) print('All Orders Cancelled') return (items)
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 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_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 order_buy_crypto_by_price(symbol, amountInDollars, priceType='ask_price', timeInForce='gtc'): """Submits a market order for a crypto by specifying the amount in dollars that you want to trade. Good for share fractions up to 8 decimal places. :param symbol: The crypto ticker of the crypto to trade. :type symbol: str :param amountInDollars: The amount in dollars of the crypto you want to buy. :type amountInDollars: float :param priceType: The type of price to get. Can be 'ask_price', 'bid_price', or 'mark_price' :type priceType: 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 crypto_info = crypto.get_crypto_info(symbol) price = round( float( crypto.get_crypto_quote_from_id(crypto_info['id'], info=priceType)), 8) # turn the money amount into decimal number of shares try: shares = round(amountInDollars / float(price), 8) except: shares = 0 payload = { 'mimeType': 'application/json', 'account_id': crypto.load_crypto_profile(info="id"), 'currency_pair_id': crypto_info['id'], 'price': price, 'quantity': shares, 'ref_id': str(uuid4()), 'side': 'buy', 'time_in_force': timeInForce, 'type': 'market' } url = urls.order_crypto() data = helper.request_post(url, payload, json=True) return (data)
def unlink_bank_account(id): """Unlinks a bank account. :param id: The bank id. :type id: str :returns: Information returned from post request. """ url = urls.linked(id, True) data = helper.request_post(url) return(data)
def _post_to_login(username, password, challenge_type='sms'): payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': TOKEN_DURATION, 'grant_type': 'password', 'password': password, 'scope': 'internal', 'username': username, 'challenge_type': challenge_type, 'device_token': '3559a85e-0726-eec9-e83b-6c6803d6ddad' } # TODO: make device-specific. return helper.request_post(urls.login_url(), payload)
def cancel_option_order(orderID): """Cancels a specific option order. :param orderID: The ID associated with the order. Can be found using get_all_orders(info=None) or get_all_orders(info=None). :type orderID: str :returns: Returns the order information for the order that was cancelled. """ url = urls.option_cancel(orderID) data = helper.request_post(url) if data: print('Order ' + orderID + ' cancelled') return (data)
def refresh_token(refresh_token=None): url = "https://api.robinhood.com/oauth2/token/" payload = { "grant_type": "refresh_token", "refresh_token": refresh_token, "scope": "internal", "client_id": "c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS", "expires_in": 734000, } helper.update_session('Authorization', None) data = helper.request_post(url, payload) return data
def respond_to_challenge(challenge_id, sms_code): """This functino will post to the challenge url. :param challenge_id: The challenge id. :type challenge_id: str :param sms_code: The sms code. :type sms_code: str :returns: The response from requests. """ url = urls.challenge_url(challenge_id) payload = {'response': sms_code} return (helper.request_post(url, payload))
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 post_symbols_to_watchlist(inputSymbols, name='Default'): """Posts multiple stock tickers to a watchlist. :param inputSymbols: May be a single stock ticker or a list of stock tickers. :type inputSymbols: str or list :param name: The name of the watchlist to post data to. :type name: Optional[str] :returns: Returns result of the post request. """ symbols = helper.inputs_to_set(inputSymbols) payload = {'symbols': ','.join(symbols)} url = urls.watchlists(name, True) data = helper.request_post(url, payload) return (data)
def post_symbols_to_watchlist(*inputSymbols, name='Default'): """Posts multiple stock tickers to a watchlist. :param inputSymbols: This is a variable length parameter that represents a stock ticker. \ May be several tickers seperated by commas or a list of tickers. :type inputSymbols: str or list :param name: The name of the watchlist to post data to. :type name: Optional[str] :returns: Returns result of the post request. """ symbols = helper.inputs_to_set(inputSymbols) payload = {'symbols': ','.join(symbols)} url = urls.watchlists(name, True) data = helper.request_post(url, payload) return (data)
def order_sell_crypto_limit(symbol, quantity, price, timeInForce='gtc'): """Submits a limit order for a crypto by specifying the decimal amount of shares to sell. Good for share fractions up to 8 decimal places. :param symbol: The crypto ticker of the crypto to trade. :type symbol: str :param quantity: The decimal amount of shares to sell. :type quantity: float :param price: The limit price to set for the crypto. :type price: 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 crypto, \ 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 crypto_info = crypto.get_crypto_info(symbol) if crypto_info['display_only']: print( "WARNING: The dictionary returned by crypto.get_crypto_info() for this crypto has key 'display_only' set to True. May not be able to trade this crypto." ) payload = { 'account_id': crypto.load_crypto_profile(info="id"), 'currency_pair_id': crypto_info['id'], 'price': price, 'quantity': quantity, 'ref_id': str(uuid4()), 'side': 'sell', 'time_in_force': timeInForce, 'type': 'limit' } url = urls.order_crypto() data = helper.request_post(url, payload, json=True) 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_crypto_by_quantity(symbol, quantity, priceType='ask_price', timeInForce='gtc'): """Submits a market order for a crypto by specifying the decimal amount of shares to buy. Good for share fractions up to 8 decimal places. :param symbol: The crypto ticker of the crypto to trade. :type symbol: str :param quantity: The decimal amount of shares to sell. :type quantity: float :param priceType: The type of price to get. Can be 'ask_price', 'bid_price', or 'mark_price' :type priceType: 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. """ crypto_info = crypto.get_crypto_info(symbol) price = round( float( crypto.get_crypto_quote_from_id(crypto_info['id'], info=priceType)), 8) print("pice is ", price) payload = { 'account_id': crypto.load_crypto_profile(info="id"), 'currency_pair_id': crypto_info['id'], 'price': price, 'quantity': quantity, 'ref_id': str(uuid4()), 'side': 'sell', 'time_in_force': timeInForce, 'type': 'market' } url = urls.order_crypto() data = helper.request_post(url, payload, json=True) 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 base_login(username,password,device_token,expiresIn=86400,scope='internal',by_sms=True): """This function will try to log the user in and will return the response data. It may contain a challenge (sms) or the access token. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param device_token: The device_token you should re-use (can be saved with "robin_stocks.get_new_device_token()"). :type device_token: str :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :param by_sms: Specifies whether to send an email(False) or an sms(True) :type by_sms: Optional[boolean] :returns: A dictionary with response information. """ if not username or not password: raise Exception('login must be called with a non-empty username and ' 'password') if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } data = helper.request_post(url,payload) return(data)
def process_login(username=None, password=None, device_token=None, mfa_code=None, challenge_id=None): expires_in = 86400 scope = 'internal' challenge_type = 'sms' url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expires_in, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } if challenge_id != None: helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) return data
def deposit_funds_to_robinhood_account(ach_relationship, amount, info=None): """Submits a post request to deposit a certain amount of money from a bank account to Robinhood. :param ach_relationship: The url of the bank account you want to deposit the money from. :type ach_relationship: str :param amount: The amount of money you wish to deposit. :type amount: float :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 the transaction. """ url = urls.banktransfers() payload = { "amount": amount, "direction": "deposit", "ach_relationship": ach_relationship, "ref_id": str(uuid4()) } data = helper.request_post(url, payload) return (helper.filter_data(data, info))
def login(username, password, expiresIn=86400, scope='internal', by_sms=True): device_token = authentication.generate_device_token() # Challenge type is used if not logging in with two-factor authentication. if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } data = helper.request_post(url, payload) payload['login_response'] = data return payload
def login(username, password, expiresIn=86400, scope='internal'): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. :param name: The username. :type name: str :param password: The password. :type state: str :returns: A dictionary with log in information. """ url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username } data = helper.request_post(url, payload) token = 'Bearer {}'.format(data['access_token']) helper.update_session('Authorization', token) return (data)
def login(username, password, expiresIn=86400, scope='internal', by_sms=True, store_session=True): """This function will effectivly log the user into robinhood by getting an authentication token and saving it to the session header. By default, it will store the authentication token in a pickle file and load that value on subsequent logins. :param username: The username for your robinhood account. Usually your email. :type username: str :param password: The password for your robinhood account. :type password: str :param expiresIn: The time until your login session expires. This is in seconds. :type expiresIn: Optional[int] :param scope: Specifies the scope of the authentication. :type scope: Optional[str] :param by_sms: Specifies whether to send an email(False) or an sms(True) :type by_sms: Optional[boolean] :param store_session: Specifies whether to save the log in authorization for future log ins. :type store_session: Optional[boolean] :returns: A dictionary with log in information. The 'access_token' keyword contains the access token, and the 'detail' keyword \ contains information on whether the access token was generated or loaded from pickle file. """ device_token = generate_device_token() dir_path = os.path.dirname(os.path.realpath(__file__)) pickle_path = os.path.join(dir_path, "data.pickle") # Challenge type is used if not logging in with two-factor authentication. if by_sms: challenge_type = "sms" else: challenge_type = "email" url = urls.login_url() payload = { 'client_id': 'c82SH0WZOsabOXGP2sxqcj34FxkvfnWRZBKlBjFS', 'expires_in': expiresIn, 'grant_type': 'password', 'password': password, 'scope': scope, 'username': username, 'challenge_type': challenge_type, 'device_token': device_token } # If authentication has been stored in pickle file then load it. Stops login server from being pinged so much. if os.path.isfile(pickle_path): # If store_session has been set to false then delete the pickle file, otherwise try to load it. # Loading pickle file will fail if the acess_token has expired. if store_session: try: with open(pickle_path, 'rb') as f: pickle_data = pickle.load(f) access_token = pickle_data['access_token'] token_type = pickle_data['token_type'] refresh_token = pickle_data['refresh_token'] # Set device_token to be the original device token when first logged in. pickle_device_token = pickle_data['device_token'] payload['device_token'] = pickle_device_token # Set login status to True in order to try and get account info. helper.set_login_state(True) helper.update_session( 'Authorization', '{0} {1}'.format(token_type, access_token)) # Try to load account profile to check that authorization token is still valid. res = helper.request_get(urls.portfolio_profile(), 'regular', payload, jsonify_data=False) # Raises exception is response code is not 200. res.raise_for_status() return ({ 'access_token': access_token, 'token_type': token_type, 'expires_in': expiresIn, 'scope': scope, 'detail': 'logged in using authentication in data.pickle', 'backup_code': None, 'refresh_token': refresh_token }) except: print( "ERROR: There was an issue loading pickle file. Authentication may be expired - logging in normally." ) helper.set_login_state(False) helper.update_session('Authorization', None) else: os.remove(pickle_path) # Try to log in normally. data = helper.request_post(url, payload) # Handle case where mfa or challenge is required. if 'mfa_required' in data: mfa_token = input("Please type in the MFA code: ") payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) while (res.status_code != 200): mfa_token = input( "That MFA code was not correct. Please type in another MFA code: " ) payload['mfa_code'] = mfa_token res = helper.request_post(url, payload, jsonify_data=False) data = res.json() elif 'challenge' in data: challenge_id = data['challenge']['id'] sms_code = input('Enter Robinhood code for validation: ') res = respond_to_challenge(challenge_id, sms_code) while 'challenge' in res and res['challenge']['remaining_attempts'] > 0: sms_code = input( 'That code was not correct. {0} tries remaining. Please type in another code: ' .format(res['challenge']['remaining_attempts'])) res = respond_to_challenge(challenge_id, sms_code) helper.update_session('X-ROBINHOOD-CHALLENGE-RESPONSE-ID', challenge_id) data = helper.request_post(url, payload) # Update Session data with authorization or raise exception with the information present in data. if 'access_token' in data: token = '{0} {1}'.format(data['token_type'], data['access_token']) helper.update_session('Authorization', token) helper.set_login_state(True) data['detail'] = "logged in with brand new authentication code." if store_session: with open(pickle_path, 'wb') as f: pickle.dump( { 'token_type': data['token_type'], 'access_token': data['access_token'], 'refresh_token': data['refresh_token'], 'device_token': device_token }, f) else: raise Exception(data['detail']) return (data)