def __init__(self, verbose=False): """ Create an AutoInvestor instance - Set verbose to True if you want to see debugging logs """ self.verbose = verbose self.logger = util.create_logger(verbose) self.app_dir = util.get_app_directory() self.lc = LendingClub() # Set logger on lc if self.verbose: self.lc.set_logger(self.logger) # Create settings object self.settings = Settings(investor=self, settings_dir=self.app_dir, logger=self.logger, verbose=self.verbose) self.settings.investor = self # create a link back to this instance
class AutoInvestor: """ Regularly check a LendingClub account for available cash and reinvest it automatically. """ lc = None authed = False verbose = False auto_execute = True settings = None loop = False app_dir = None # The file that the summary from the last investment is saved to last_investment_file = 'last_investment.json' def __init__(self, verbose=False, auto_execute=True): """ Create an AutoInvestor instance - Set verbose to True if you want to see debugging logs """ self.verbose = verbose self.auto_execute = auto_execute self.logger = util.create_logger(verbose) self.app_dir = util.get_app_directory() self.lc = LendingClub() # Set logger on lc if self.verbose: self.lc.set_logger(self.logger) # Create settings object self.settings = Settings(investor=self, settings_dir=self.app_dir, logger=self.logger, verbose=self.verbose) self.settings.investor = self # create a link back to this instance def version(self): """ Return the version number of the Lending Club Investor tool """ return util.get_version(); def welcome_screen(self): print "\n///--------------------------- $$$ ---------------------------\\\\\\" print '| Welcome to the unofficial Lending Club investment tool |' print " ---------------------------------------------------------------- \n" def get_auth(self): print 'To start, we need to log you into Lending Club (your password will never be saved)\n' while True: self.settings.get_auth_settings() print '\nAuthenticating...' try: return self.authenticate() except Exception as e: print '\nLogin failed: {0}'.format(str(e.value)) print "Please try again\n" def setup(self): """ Setup the investor to run """ if self.verbose: print 'VERBOSE OUTPUT IS ON\n' if not self.authed: self.get_auth() self.settings.select_profile() print 'You have ${0} in your account, free to invest\n'.format(self.lc.get_cash_balance()) # Investment settings print 'Now let\'s define what you want to do' # Use the settings from last time if self.settings.profile_loaded is not False: summary = self.settings.show_summary() if summary is False: # there was an error with saved settings print '\nThere was an error with your saved settings. Please go through the prompts again.\n' self.settings.get_investment_settings() if util.prompt_yn('Would you like to use these settings?', 'y'): self.settings.save() # to save the email that was just entered else: self.settings.get_investment_settings() else: self.settings.get_investment_settings() # All ready to start running print '\nThat\'s all we need. Now, as long as this is running, your account will be checked every {0} minutes and invested if enough funds are available.\n'.format(self.settings['frequency']) def authenticate(self): """ Attempt to authenticate the user with the email/pass from the Settings object. This is just a wrapper for LendingClub.authenticate() Returns True or raises an exceptions """ self.authed = self.lc.authenticate(self.settings.auth['email'], self.settings.auth['pass']) return self.authed def run(self): """ Alias for investment_loop. This is used by python-runner """ self.investment_loop() def run_once(self): """ Try to invest, based on your settings, and then end the program. """ self.loop = False # Make sure the site is available attempts = 0 while not self.lc.is_site_available(): attempts += 1 if attempts % 5 == 0: self.logger.warn('LendingClub is not responding. Trying again in 10 seconds...') sleep(10) # Invest self.attempt_to_invest() def stop(self): """ Called when the investment loop should end. If the loop is currently attempting to invest cash, this will not be canceled. """ self.loop = False self.logger.info("Stopping investor...") def get_order_summary(self, portfolio): """ Log a summary of the investment portfolio which was ordered """ summary = 'Investment portfolio summary: {0} loan notes ('.format(portfolio['numberOfLoans']) breakdown = [] for grade in ['a', 'aa', 'b', 'c', 'd', 'e', 'f', 'g']: if portfolio[grade] > 0.0: percent = int(round(portfolio[grade])) breakdown.append('{0}:{1}%'.format(grade.upper(), percent)) if len(breakdown) > 0: summary += ', '.join(breakdown) summary += ')' return summary def attempt_to_invest(self): """ Attempt an investment if there is enough available cash and matching investment option Returns true if money was invested """ # Authenticate try: self.authenticate() self.logger.info('Authenticated') except Exception as e: self.logger.error('Could not authenticate: {0}'.format(e.value)) return False # Try to invest self.logger.info('Checking for funds to invest...') try: # Get current cash balance cash = self.lc.get_investable_balance() if cash > 0 and cash >= self.settings['min_cash']: # Invest self.logger.info(" $ $ $ $ $ $ $ $ $ $") # Create break in logs try: # Refresh saved filter filters = self.settings['filters'] if type(filters) is SavedFilter: filters.reload() # Find investment portfolio, starting will all your cash, # down to the minimum you're willing to invest # No more than 10 searches i = 0 portfolio = False decrement = None while portfolio is False and cash >= self.settings['min_cash'] and i < 10: i += 1 # Try to find a portfolio try: self.logger.info('Searching for a portfolio for ${0}'.format(cash)) portfolio = self.lc.build_portfolio(cash, max_per_note=self.settings['max_per_note'], min_percent=self.settings['min_percent'], max_percent=self.settings['max_percent'], filters=filters, do_not_clear_staging=True) except LendingClubError as e: pass # Try a lower amount of cash to invest if not portfolio: self.logger.info('Could not find any matching portfolios for ${0}'.format(cash)) # Create decrement value that will search up to 5 more times if decrement is None: delta = cash - self.settings['min_cash'] if delta < 25: break elif delta <= 100: decrement = 25 else: decrement = delta / 4 # Just to be safe, shouldn't decrement in $10 increments if decrement < 10: break # We are at our lowest if cash <= self.settings['min_cash']: break # New amount to search for cash -= decrement if cash < self.settings['min_cash']: cash = self.settings['min_cash'] else: cash = util.nearest_25(cash) if portfolio: # Invest assign_to = self.settings['portfolio'] order = self.lc.start_order() order.add_batch(portfolio['loan_fractions']) if self.auto_execute: self.logger.info('Auto investing ${0} at {1}%...'.format(cash, portfolio['percentage'])) sleep(5) # last chance to cancel order._Order__already_staged = True # Don't try this at home kids order._Order__i_know_what_im_doing = True # Seriously, don't do it order_id = order.execute(portfolio_name=assign_to) else: self.logger.info('Order staged but not completed, please to go LendingClub website to complete the order. (see the "--no-auto-execute" command flag)') return False # Success! Show summary and save the order summary = self.get_order_summary(portfolio) self.logger.info(summary) self.logger.info('Done\n') self.save_last_investment(cash, portfolio, order_id, portfolio_name=assign_to) else: self.logger.warning('No investment portfolios matched your filters at this time -- Trying again in {2} minutes'.format(self.settings['min_percent'], self.settings['max_percent'], self.settings['frequency'])) except Exception as e: self.logger.error('Failed trying to invest: {0}'.format(str(e))) else: self.logger.info('Only ${0} available for investing (of your ${1} balance)'.format(cash, self.lc.get_cash_balance())) return False except Exception as e: self.logger.error(str(e)) return False def save_last_investment(self, cash, portfolio, order_id, portfolio_name=None): """" Save a log of the last investment to the last_investment file """ try: last_invested = { 'timestamp': int(time.time()), 'order_id': order_id, 'portfolio': portfolio_name, 'cash': cash, 'investment': portfolio } # Convert to JSON json_out = json.dumps(last_invested) self.logger.debug('Saving last investment file with JSON: {0}'.format(json_out)) # Save file_path = os.path.join(self.app_dir, self.last_investment_file) f = open(file_path, 'w') f.write(json_out) f.close() except Exception as e: self.logger.warning('Couldn\'t save the investment summary to file (this warning can be ignored). {0}'.format(str(e))) def get_last_investment(self): """ Return the last investment summary that has been saved to the last_investment file """ try: file_path = os.path.join(self.app_dir, self.last_investment_file) if os.path.exists(file_path): # Read file f = open(file_path, 'r') json_str = f.read() f.close() # Convert to dictionary and return return json.loads(json_str) except Exception as e: self.logger.warning('Couldn\'t read the last investment file. {0}'.format(str(e))) return None def investment_loop(self): """ Start the investment loop Check the account every so often (default is every 60 minutes) for funds to invest The frequency is defined by the 'frequency' value in the ~/.lcinvestor/settings.yaml file """ self.loop = True frequency = self.settings.user_settings['frequency'] while self.loop: # Make sure the site is available (network could be reconnecting after sleep) attempts = 0 while not self.lc.is_site_available() and self.loop: attempts += 1 if attempts % 5 == 0: self.logger.warn('LendingClub is not responding. Trying again in 10 seconds...') sleep(10) # Invest self.attempt_to_invest() pause.minutes(frequency)
class AutoInvestor: """ Regularly check a LendingClub account for available cash and reinvest it automatically. """ lc = None authed = False verbose = False auto_execute = True settings = None loop = False app_dir = None # The file that the summary from the last investment is saved to last_investment_file = 'last_investment.json' def __init__(self, verbose=False, auto_execute=True): """ Create an AutoInvestor instance - Set verbose to True if you want to see debugging logs """ self.verbose = verbose self.auto_execute = auto_execute self.logger = util.create_logger(verbose) self.app_dir = util.get_app_directory() self.lc = LendingClub() # Set logger on lc if self.verbose: self.lc.set_logger(self.logger) # Create settings object self.settings = Settings(investor=self, settings_dir=self.app_dir, logger=self.logger, verbose=self.verbose) self.settings.investor = self # create a link back to this instance def version(self): """ Return the version number of the Lending Club Investor tool """ return util.get_version() def welcome_screen(self): print( "\n///--------------------------- $$$ ---------------------------\\\\\\" ) print( '| Welcome to the unofficial Lending Club investment tool |' ) print( " ---------------------------------------------------------------- \n" ) def get_auth(self): print( 'To start, we need to log you into Lending Club (your password will never be saved)\n' ) while True: self.settings.get_auth_settings() print('\nAuthenticating...') try: return self.authenticate() except Exception as e: print('\nLogin failed: {0}'.format(str(e.value))) print("Please try again\n") def setup(self): """ Setup the investor to run """ if self.verbose: print('VERBOSE OUTPUT IS ON\n') if not self.authed: self.get_auth() self.settings.select_profile() print('You have ${0} in your account, free to invest\n'.format( self.lc.get_cash_balance())) # Investment settings print('Now let\'s define what you want to do') # Use the settings from last time if self.settings.profile_loaded is not False: summary = self.settings.show_summary() if summary is False: # there was an error with saved settings print( '\nThere was an error with your saved settings. Please go through the prompts again.\n' ) self.settings.get_investment_settings() if util.prompt_yn('Would you like to use these settings?', 'y'): self.settings.save() # to save the email that was just entered else: self.settings.get_investment_settings() else: self.settings.get_investment_settings() # All ready to start running print( '\nThat\'s all we need. Now, as long as this is running, your account will be checked every {0} minutes and invested if enough funds are available.\n' .format(self.settings['frequency'])) def authenticate(self): """ Attempt to authenticate the user with the email/pass from the Settings object. This is just a wrapper for LendingClub.authenticate() Returns True or raises an exceptions """ self.authed = self.lc.authenticate(self.settings.auth['email'], self.settings.auth['pass']) return self.authed def run(self): """ Alias for investment_loop. This is used by python-runner """ self.investment_loop() def run_once(self): """ Try to invest, based on your settings, and then end the program. """ self.loop = False # Make sure the site is available attempts = 0 while not self.lc.is_site_available(): attempts += 1 if attempts % 5 == 0: self.logger.warn( 'LendingClub is not responding. Trying again in 10 seconds...' ) sleep(10) # Invest self.attempt_to_invest() def stop(self): """ Called when the investment loop should end. If the loop is currently attempting to invest cash, this will not be canceled. """ self.loop = False self.logger.info("Stopping investor...") def get_order_summary(self, portfolio): """ Log a summary of the investment portfolio which was ordered """ summary = 'Investment portfolio summary: {0} loan notes ('.format( portfolio['numberOfLoans']) breakdown = [] for grade in ['a', 'aa', 'b', 'c', 'd', 'e', 'f', 'g']: if portfolio[grade] > 0.0: percent = int(round(portfolio[grade])) breakdown.append('{0}:{1}%'.format(grade.upper(), percent)) if len(breakdown) > 0: summary += ', '.join(breakdown) summary += ')' return summary def attempt_to_invest(self): """ Attempt an investment if there is enough available cash and matching investment option Returns true if money was invested """ # Authenticate try: self.authenticate() self.logger.info('Authenticated') except Exception as e: self.logger.error('Could not authenticate: {0}'.format(e.value)) return False # Try to invest self.logger.info('Checking for funds to invest...') try: # Get current cash balance cash = self.lc.get_investable_balance() if cash > 0 and cash >= self.settings['min_cash']: # Invest self.logger.info( " $ $ $ $ $ $ $ $ $ $") # Create break in logs try: # Refresh saved filter filters = self.settings['filters'] if type(filters) is SavedFilter: filters.reload() # Find investment portfolio, starting will all your cash, # down to the minimum you're willing to invest # No more than 10 searches i = 0 portfolio = False decrement = None while portfolio is False and cash >= self.settings[ 'min_cash'] and i < 10: i += 1 # Try to find a portfolio try: self.logger.info( 'Searching for a portfolio for ${0}'.format( cash)) portfolio = self.lc.build_portfolio( cash, max_per_note=self.settings['max_per_note'], min_percent=self.settings['min_percent'], max_percent=self.settings['max_percent'], filters=filters, do_not_clear_staging=True) except LendingClubError as e: pass # Try a lower amount of cash to invest if not portfolio: self.logger.info( 'Could not find any matching portfolios for ${0}' .format(cash)) # Create decrement value that will search up to 5 more times if decrement is None: delta = cash - self.settings['min_cash'] if delta < 25: break elif delta <= 100: decrement = 25 else: decrement = delta / 4 # Just to be safe, shouldn't decrement in $10 increments if decrement < 10: break # We are at our lowest if cash <= self.settings['min_cash']: break # New amount to search for cash -= decrement if cash < self.settings['min_cash']: cash = self.settings['min_cash'] else: cash = util.nearest_25(cash) if portfolio: # Invest assign_to = self.settings['portfolio'] order = self.lc.start_order() order.add_batch(portfolio['loan_fractions']) if self.auto_execute: self.logger.info( 'Auto investing ${0} at {1}%...'.format( cash, portfolio['percentage'])) sleep(5) # last chance to cancel order._Order__already_staged = True # Don't try this at home kids order._Order__i_know_what_im_doing = True # Seriously, don't do it order_id = order.execute(portfolio_name=assign_to) else: self.logger.info( 'Order staged but not completed, please to go LendingClub website to complete the order. (see the "--no-auto-execute" command flag)' ) return False # Success! Show summary and save the order summary = self.get_order_summary(portfolio) self.logger.info(summary) self.logger.info('Done\n') self.save_last_investment(cash, portfolio, order_id, portfolio_name=assign_to) else: self.logger.warning( 'No investment portfolios matched your filters at this time -- Trying again in {2} minutes' .format(self.settings['min_percent'], self.settings['max_percent'], self.settings['frequency'])) except Exception as e: self.logger.exception( 'Failed trying to invest: {0}'.format(str(e))) else: self.logger.info( 'Only ${0} available for investing (of your ${1} balance)'. format(cash, self.lc.get_cash_balance())) return False except Exception as e: self.logger.error(str(e)) return False def save_last_investment(self, cash, portfolio, order_id, portfolio_name=None): """" Save a log of the last investment to the last_investment file """ try: last_invested = { 'timestamp': int(time.time()), 'order_id': order_id, 'portfolio': portfolio_name, 'cash': cash, 'investment': portfolio } # Convert to JSON json_out = json.dumps(last_invested) self.logger.debug( 'Saving last investment file with JSON: {0}'.format(json_out)) # Save file_path = os.path.join(self.app_dir, self.last_investment_file) f = open(file_path, 'w') f.write(json_out) f.close() except Exception as e: self.logger.warning( 'Couldn\'t save the investment summary to file (this warning can be ignored). {0}' .format(str(e))) def get_last_investment(self): """ Return the last investment summary that has been saved to the last_investment file """ try: file_path = os.path.join(self.app_dir, self.last_investment_file) if os.path.exists(file_path): # Read file f = open(file_path, 'r') json_str = f.read() f.close() # Convert to dictionary and return return json.loads(json_str) except Exception as e: self.logger.warning( 'Couldn\'t read the last investment file. {0}'.format(str(e))) return None def investment_loop(self): """ Start the investment loop Check the account every so often (default is every 60 minutes) for funds to invest The frequency is defined by the 'frequency' value in the ~/.lcinvestor/settings.yaml file """ self.loop = True frequency = self.settings.user_settings['frequency'] while self.loop: # Make sure the site is available (network could be reconnecting after sleep) attempts = 0 while not self.lc.is_site_available() and self.loop: attempts += 1 if attempts % 5 == 0: self.logger.warn( 'LendingClub is not responding. Trying again in 10 seconds...' ) sleep(10) # Invest self.attempt_to_invest() pause.minutes(frequency)
import sys import os sys.path.insert(0, '.') sys.path.insert(0, '../') sys.path.insert(0, '../../') import lcinvestor from lcinvestor.settings import Settings from lcinvestor import util """ Setup """ base_dir = os.path.dirname(os.path.realpath(__file__)) app_dir = os.path.join(base_dir, '.folio_picker_test') settings = Settings(settings_dir=app_dir) investor = lcinvestor.AutoInvestor(settings=settings, verbose=True) investor.lc.get_portfolio_list = lambda names_only: ['apple', 'bar', 'foo'] """ With default option """ print('\nWith a default option') while True: chosen = settings.portfolio_picker('The default') print('You chose: {0}\n'.format(chosen)) if not util.prompt_yn('Again?', 'n'): break """ No default option """
sys.path.insert(0, '.') sys.path.insert(0, '../') sys.path.insert(0, '../../') import lcinvestor from lcinvestor.settings import Settings from lcinvestor import util """ Setup """ base_dir = os.path.dirname(os.path.realpath(__file__)) app_dir = os.path.join(base_dir, '.folio_picker_test') settings = Settings(settings_dir=app_dir) investor = lcinvestor.AutoInvestor(settings=settings, verbose=True) investor.lc.get_portfolio_list = lambda names_only: ['apple', 'bar', 'foo'] """ With default option """ print '\nWith a default option' while True: chosen = settings.portfolio_picker('The default') print 'You chose: {0}\n'.format(chosen) if not util.prompt_yn('Again?', 'n'): break """