From b00ba5dbb27fa64342c5f804cc42b8eaba1ad6b9 Mon Sep 17 00:00:00 2001 From: josh fernandes Date: Sun, 3 Jan 2021 11:19:48 -0800 Subject: [PATCH] Added ability to withdraw cash to bank account --- examples/withdrawl_funds_to_bank_account.py | 66 +++++++++++++++++++++ robin_stocks/__init__.py | 3 +- robin_stocks/account.py | 25 ++++++++ robin_stocks/urls.py | 2 + setup.py | 2 +- 5 files changed, 96 insertions(+), 2 deletions(-) create mode 100644 examples/withdrawl_funds_to_bank_account.py diff --git a/examples/withdrawl_funds_to_bank_account.py b/examples/withdrawl_funds_to_bank_account.py new file mode 100644 index 0000000..3049553 --- /dev/null +++ b/examples/withdrawl_funds_to_bank_account.py @@ -0,0 +1,66 @@ +import os + +import pyotp +import robin_stocks as r +from dotenv import load_dotenv +''' +This is an example script that will withdrawl money to the bank +account of your choosing. + +NOTE: View the two_factor_log_in.py script to see how automatic +two-factor loggin in works. +''' +### REPLACE ME +amount_to_withdrawl = "REPLACE-ME" +### +# Load environment variables +load_dotenv() +# Login using two-factor code +totp = pyotp.TOTP(os.environ['robin_mfa']).now() +login = r.login(os.environ['robin_username'], + os.environ['robin_password'], store_session=True, mfa_code=totp) +# Get the bank account information +bank_accounts = r.get_linked_bank_accounts() +account_names = r.filter_data(bank_accounts, 'bank_account_nickname') +# set up default variable values for business logic +count = 1 +valid_choice = False +bank_choice = -1 +# Present the choices in a list starting at 1 +# Feel free to delete this whole if loop and just select the ach_relationship url manually +if len(account_names) == 0: + print("you do not have any linked bank accounts. Exiting...") +else: + print("=====\navailable banks\n-----") + for bank in account_names: + print("{0}. {1}".format(count, bank)) + count += 1 + print("{0}. Cancel Withdrawl".format(count)) + print("-----") + # Select a whole integer, if you select an invalid integer, code will prompt you again + while not valid_choice: + try: + bank_choice = input( + "Type in the number of the bank account you want to use below:\n") + bank_choice = int(bank_choice) + if bank_choice > 0 and bank_choice <= count: # allowable limits are 1 to the max number in list + valid_choice = True + else: + raise ValueError + except: + # you could put the print statement outside the try-except block and have this + # except have a simple 'pass' but then the "I'm sorry" statement would ALWAYS + # print instead of printing only on failed entries. This is why I manually + # raise the ValueError above. So that this statement only prints if the + # int() function fails or I decide that the bank_choise is outside + # allowable limits + print("I'm sorry. That's not a valid integer choice, please try again") + +if bank_choice == -1: + pass # exit condition +elif bank_choice == count: + print("you chose to cancel. Exiting...") +else: + ach_relationship = bank_accounts[bank_choice - 1]['url'] + withdrawl = r.withdrawl_funds_to_bank_account(ach_relationship, amount_to_withdrawl) + print(withdrawl) diff --git a/robin_stocks/__init__.py b/robin_stocks/__init__.py index 40b4676..778ac59 100644 --- a/robin_stocks/__init__.py +++ b/robin_stocks/__init__.py @@ -27,7 +27,8 @@ delete_symbols_from_watchlist, \ build_holdings, \ build_user_profile, \ - load_phoenix_account + load_phoenix_account, \ + withdrawl_funds_to_bank_account from .authentication import login, \ logout diff --git a/robin_stocks/account.py b/robin_stocks/account.py index fee4b68..bb4c3d3 100644 --- a/robin_stocks/account.py +++ b/robin_stocks/account.py @@ -1,11 +1,13 @@ """Contains functions for getting information related to the user account.""" import os +from uuid import uuid4 import robin_stocks.helper as helper import robin_stocks.profiles as profiles import robin_stocks.stocks as stocks import robin_stocks.urls as urls + @helper.login_required def load_phoenix_account(info=None): """Returns unified information about your account. @@ -292,6 +294,29 @@ def get_margin_calls(symbol=None): return(data) +@helper.login_required +def withdrawl_funds_to_bank_account(ach_relationship, amount, info=None): + """Submits a post request to withdraw a certain amount of money to a bank account. + + :param ach_relationship: The url of the bank account you want to withdrawl the money to. + :type ach_relationship: str + :param amount: The amount of money you wish to withdrawl. + :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": "withdraw", + "ach_relationship": ach_relationship, + "ref_id": str(uuid4()) + } + data = helper.request_post(url, payload) + return(helper.filter_data(data, info)) + @helper.login_required def get_linked_bank_accounts(info=None): """Returns all linked bank accounts. diff --git a/robin_stocks/urls.py b/robin_stocks/urls.py index 4953910..a6dd96c 100644 --- a/robin_stocks/urls.py +++ b/robin_stocks/urls.py @@ -109,6 +109,8 @@ def dividends(): def documents(): return('https://api.robinhood.com/documents/') +def withdrawl(bank_id): + return("https://api.robinhood.com/ach/relationships/{}/".format(bank_id)) def linked(id=None, unlink=False): if unlink: diff --git a/setup.py b/setup.py index 459c5cb..76b24b1 100644 --- a/setup.py +++ b/setup.py @@ -7,7 +7,7 @@ long_description = f.read() setup(name='robin_stocks', - version='1.5.5', + version='1.6.0', description='A Python wrapper around the Robinhood API', long_description=long_description, long_description_content_type='text/markdown',