Пример #1
0
    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret')
Пример #2
0
class TestOrder(unittest.TestCase):
    lc = None
    order = None
    logger = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

        # Use version 2 of browseNotesAj.json
        self.lc.session.post('/session', data={'browseNotesAj': '2'})

        # Start order
        self.order = self.lc.start_order()

    def tearDown(self):
        pass

    def test_add(self):
        self.order.add(123, 50)
        self.assertEqual(len(self.order.loans), 1)
        self.assertEqual(self.order.loans[123], 50)

    def test_update(self):
        self.order.add(123, 50)
        self.assertEqual(self.order.loans[123], 50)

        self.order.add(123, 100)
        self.assertEqual(len(self.order.loans), 1)
        self.assertEqual(self.order.loans[123], 100)

    def test_remove(self):
        self.order.add(123, 50)
        self.order.add(234, 75)

        self.assertEqual(len(self.order.loans), 2)

        self.order.remove(234)

        self.assertEqual(len(self.order.loans), 1)
        self.assertEqual(self.order.loans[123], 50)
        self.assertFalse(234 in self.order.loans)

    def test_multiple_of_25(self):
        self.assertRaises(AssertionError, lambda: self.order.add(123, 0))
        self.assertRaises(AssertionError, lambda: self.order.add(123, 26))
Пример #3
0
    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')
Пример #4
0
    def setUp(self):
        self.filters = Filter()
        self.filters['exclude_existing'] = False
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret')

        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']
Пример #5
0
    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret12')
Пример #6
0
def request_loan_data():
    '''
    Requests list of loans that can be invested in, then makes individual call
    for details of the loans. Results stored in MongoDB database.

    Returns:
    loan_results: Results obtained from initial API request. list.
    loan_details: Individual loan details, obtained with individual API request
    of loan ids obtained in loan_results. list.
    '''
    filter_search = {
        'exclude_existing': False,
        'funding_progress': 0,
        'grades': {
            'All': False,
            'A': True,
            'B': True,
            'C': True,
            'D': True,
            'E': False,
            'F': False,
            'G': False
        },
        'term': {
            'Year3': True,
            'Year5': False
        }
    }

    club = LendingClub()
    filter_search = Filter(filter_search)
    club.authenticate()

    loan_results = club.search(filter_search, start_index=0, limit=1000)
    loan_results = loan_results['loans']
    loan_ids = [loan['loan_id'] for loan in loan_results]

    loan_details = []
    for loan_id in loan_ids:
        print "loan_id", loan_id
        request = club.session.get('/browse/loanDetailAj.action',
                                   query={'loan_id': loan_id})
        loan_details.append(request.json())
        time.sleep(1)

    return loan_results, loan_details
Пример #7
0
    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

        # Use version 3 of browseNotesAj.json
        self.lc.session.post('/session', data={'browseNotesAj': '3'})

        # Start order
        self.order = self.lc.start_order()
Пример #8
0
    def __init__(self, email = None, password = None, filter_ids = None):
        if filter_ids != None:
            self.main_filter_ids = filter_ids

        self.lc = LendingClub(email, password)
        self.lc.authenticate()
        self.saved_filters = self.lc.get_saved_filters()
        self.main_filters = \
            filter(lambda x: x.id in self.main_filter_ids, self.saved_filters)
Пример #9
0
    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
Пример #10
0
    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')
Пример #11
0
    def setUp(self):
        self.filters = Filter()
        self.filters['exclude_existing'] = False
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret12')

        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']
Пример #12
0
def request_loan_data():
    '''
    Requests list of loans that can be invested in, then makes individual call
    for details of the loans. Results stored in MongoDB database.

    Returns:
    loan_results: Results obtained from initial API request. list.
    loan_details: Individual loan details, obtained with individual API request
    of loan ids obtained in loan_results. list.
    '''
    filter_search = {'exclude_existing': False,
                     'funding_progress': 0,
                     'grades': {'All': False,
                                'A': True,
                                'B': True,
                                'C': True,
                                'D': True,
                                'E': False,
                                'F': False,
                                'G': False},
                     'term': {'Year3': True, 'Year5': False}}
                 
    club = LendingClub()
    filter_search = Filter(filter_search)
    club.authenticate()

    loan_results = club.search(filter_search, start_index=0, limit=1000)
    loan_results = loan_results['loans']
    loan_ids = [loan['loan_id'] for loan in loan_results]

    loan_details = []
    for loan_id in loan_ids:
        print "loan_id", loan_id
        request = club.session.get('/browse/loanDetailAj.action', query={'loan_id': loan_id})
        loan_details.append(request.json())
        time.sleep(1)

    return loan_results, loan_details
Пример #13
0
    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
Пример #14
0
    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

        # Use version 3 of browseNotesAj.json
        self.lc.session.post('/session', data={'browseNotesAj': '3'})

        # Start order
        self.order = self.lc.start_order()
Пример #15
0
class TestBatchOrder(unittest.TestCase):
    lc = None
    order = None
    logger = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

        # Use version 3 of browseNotesAj.json
        self.lc.session.post('/session', data={'browseNotesAj': '3'})

        # Start order
        self.order = self.lc.start_order()

    def tearDown(self):
        pass

    def test_add_batch_dict(self):
        """ test_add_batch_dict
        Add a batch of dict loan objects
        """
        self.order.add_batch([{
            'loan_id': 123,
            'invest_amount': 50
        }, {
            'loan_id': 234,
            'invest_amount': 75
        }])

        self.assertEqual(len(self.order.loans), 2)
        self.assertEqual(self.order.loans[123], 50)
        self.assertEqual(self.order.loans[234], 75)

    def test_add_batch_dict_amount(self):
        """ test_add_batch_dict_amount
        Add a batch dict with a batch_amount parameter value to override the individual values
        """
        self.order.add_batch([{
            'loan_id': 123,
            'invest_amount': 50
        }, {
            'loan_id': 234,
            'invest_amount': 75
        }], 100)

        self.assertEqual(len(self.order.loans), 2)
        self.assertEqual(self.order.loans[123], 100)
        self.assertEqual(self.order.loans[234], 100)

    def test_add_batch_list(self):
        """ test_add_batch_list
        Add a batch of IDs from a list, not a dict
        """
        self.order.add_batch([123, 234], 75)

        self.assertEqual(len(self.order.loans), 2)
        self.assertEqual(self.order.loans[123], 75)
        self.assertEqual(self.order.loans[234], 75)

    def test_add_batch_list_no_amount(self):
        """ test_add_batch_list_no_amount
        Send a list of IDs to add_batch, without an amount
        """
        self.assertRaises(AssertionError,
                          lambda: self.order.add_batch([123, 234]))

    def test_add_batch_object(self):
        """ test_add_batch_object
        Pulling loans from the 'loan_fractions' value is no longer supported
        """
        loanDict = {
            'loan_fractions': [{
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }]
        }
        self.assertRaises(AssertionError,
                          lambda: self.order.add_batch(loanDict))

    def test_execute(self):
        self.order.add_batch([{
            'loan_id': 123,
            'invest_amount': 50
        }, {
            'loan_id': 234,
            'invest_amount': 75
        }])

        order_id = self.order.execute()
        self.assertNotEqual(order_id, 0)

    def test_execute_wrong_id(self):
        """ test_execute_wrong_id
        Server returns an ID that doesn't match an ID added to batch (345)
        """
        self.order.add_batch([234, 345], 75)
        self.assertRaises(FilterValidationError, lambda: self.order.execute())

    def test_execute_existing_portfolio(self):
        self.order.add_batch([{
            'loan_id': 123,
            'invest_amount': 50
        }, {
            'loan_id': 234,
            'invest_amount': 75
        }])

        portfolio = 'New Portfolio'
        order_id = self.order.execute(portfolio)
        self.assertNotEqual(order_id, 0)

        # Check portfolio name
        request = self.lc.session.get('/session')
        http_session = request.json()
        self.assertEqual(http_session['new_portfolio'], portfolio)

    def test_execute_new_portfolio(self):
        self.order.add_batch([{
            'loan_id': 123,
            'invest_amount': 50
        }, {
            'loan_id': 234,
            'invest_amount': 75
        }])

        portfolio = 'Existing Portfolio'
        order_id = self.order.execute(portfolio)
        self.assertNotEqual(order_id, 0)

        # Check portfolio name
        request = self.lc.session.get('/session')
        http_session = request.json()
        self.assertEqual(http_session['existing_portfolio'], portfolio)

    def test_double_execute(self):
        """ test_double_execute
        An order can only be executed once
        """
        self.order.add_batch([{
            'loan_id': 123,
            'invest_amount': 50
        }, {
            'loan_id': 234,
            'invest_amount': 75
        }])

        order_id = self.order.execute()
        self.assertNotEqual(order_id, 0)

        self.assertRaises(AssertionError, lambda: self.order.execute())
Пример #16
0
class TestSavedFilters(unittest.TestCase):
    filters = None
    logger = None
    lc = None
    loan_list = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret')

    def tearDown(self):
        pass

    def test_get_all_filters(self):
        filters = SavedFilter.all_filters(self.lc)

        self.assertEqual(len(filters), 2)
        self.assertEqual(filters[0].name, 'Filter 1')

    def test_get_saved_filters(self):
        saved = SavedFilter(self.lc, 1)

        self.assertEqual(saved.name, 'Filter 1')
        self.assertEqual(saved.id, 1)
        self.assertNotEqual(saved.search_string(), None)

    def test_validation_1(self):
        """ test_validation_1
        Filter 1 against filter_validation 1
        """
        saved = SavedFilter(self.lc, 1)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should fail on 'exclude_invested'
        try:
            saved.validate(self.loan_list)
            assert False, 'Test should fail on exclude_existing'
        except FilterValidationError as e:
            print e.criteria
            self.assertTrue(matches('exclude loans', e.criteria))

    def test_validation_2(self):
        """ test_validation_2
        Filter 2 against filter_validation 2
        """
        saved = SavedFilter(self.lc, 2)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 2})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should fail on 'exclude_invested'
        try:
            saved.validate(self.loan_list)
            assert False, 'Test should fail on loan_purpose'
        except FilterValidationError as e:
            print e.criteria
            self.assertTrue(matches('loan purpose', e.criteria))

    def test_validation_2_1(self):
        """ test_validation_2_1
        Filter 2 against filter_validation 1
        """
        saved = SavedFilter(self.lc, 2)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should not fail
        saved.validate(self.loan_list)

    def test_validation_2_3(self):
        """ test_validation_3
        Filter 2 against filter_validation 3
        """
        saved = SavedFilter(self.lc, 2)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 3})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should fail on 'exclude_invested'
        try:
            saved.validate(self.loan_list)
            assert False, 'Test should fail on grade'
        except FilterValidationError as e:
            print e.criteria
            self.assertTrue(matches('grade', e.criteria))
Пример #17
0
class TestLendingClub(unittest.TestCase):
    lc = None
    logger = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

    def tearDown(self):
        pass

    def test_cash_balance(self):
        cash = self.lc.get_cash_balance()
        self.assertEqual(cash, 216.02)

    def test_portfolios(self):
        portfolios = self.lc.get_portfolio_list()
        self.assertEquals(len(portfolios), 2)
        self.assertEquals(portfolios[0]['portfolioName'], 'Existing Portfolio')

    def test_build_portfolio(self):
        portfolio = self.lc.build_portfolio(200, 25, 15, 16)

        self.assertNotEqual(portfolio, False)
        self.assertEqual(portfolio['percentage'], 15.28)

        self.assertTrue('loan_fractions' in portfolio)
        self.assertEqual(len(portfolio['loan_fractions']), 15)

    def test_build_portfolio_session_fail(self):
        """ test_build_portfolio_session_fail"
        If the session isn't saved, fractions shouldn't be found,
        which should make the entire method return False
        """

        # Disable session
        self.lc.session.post('/session/disabled')

        portfolio = self.lc.build_portfolio(200, 25, 15, 16)
        self.assertFalse(portfolio)

    def test_build_portfolio_no_match(self):
        """ test_build_portfolio_no_match"
        Enter a min/max percent that cannot match dummy returned JSON
        """
        portfolio = self.lc.build_portfolio(200, 25, 17.6, 18.5)
        self.assertFalse(portfolio)

    def test_search(self):
        results = self.lc.search()
        self.assertTrue(results is not False)
        self.assertTrue('loans' in results)
        self.assertTrue(len(results['loans']) > 0)
Пример #18
0
class TestFilterValidation(unittest.TestCase):
    filters = None
    logger = None
    lc = None
    loan_list = None

    def setUp(self):
        self.filters = Filter()
        self.filters['exclude_existing'] = False
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret')

        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

    def tearDown(self):
        pass

    def test_validation_defaults(self):
        """ test_validation_defaults
        Default filters should match
        """
        self.assertTrue(self.filters.validate(self.loan_list))

    def test_validation_grade_valid(self):
        self.filters['C'] = True
        self.assertTrue(self.filters.validate(self.loan_list))

    def test_validation_grade_fail(self):
        self.filters['grades']['B'] = True
        self.assertRaises(FilterValidationError,
                          lambda: self.filters.validate(self.loan_list))

    def test_validation_term_36(self):
        """ test_validation_term_36
        Should fail on the 60 month loan, loan_id: 12345
        """
        self.filters['term']['Year3'] = True
        self.filters['term']['Year5'] = False
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 12345)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_term_60(self):
        """ test_validation_term_60
        Should fail on the 36 month loan, loan_id: 23456
        """
        self.filters['term']['Year3'] = False
        self.filters['term']['Year5'] = True
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 23456)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_progress_70(self):
        """ test_validation_progress_70
        Loan 12345 is 91 percent funded
        Loan 23456 is 77 percent funded
        """
        self.filters['funding_progress'] = 70
        self.assertTrue(self.filters.validate(self.loan_list))

    def test_validation_progress_90(self):
        """ test_validation_term_90
        Should fail
        Loan 12345 is 91 percent funded
        Loan 23456 is 77 percent funded
        """
        self.filters['funding_progress'] = 90
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 23456)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_progress_95(self):
        """ test_validation_progress_95
        Should fail
        Loan 12345 is 91 percent funded
        Loan 23456 is 77 percent funded
        """
        self.filters['funding_progress'] = 95
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 12345)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_exclude_existing(self):
        """ test_validation_exclude_existing
        Should fail on loan 23456, which the user is already invested in.
        """
        self.filters['exclude_existing'] = True
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 23456)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)
Пример #19
0
class TestBatchOrder(unittest.TestCase):
    lc = None
    order = None
    logger = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

        # Use version 3 of browseNotesAj.json
        self.lc.session.post('/session', data={'browseNotesAj': '3'})

        # Start order
        self.order = self.lc.start_order()

    def tearDown(self):
        pass

    def test_add_batch_dict(self):
        """ test_add_batch_dict
        Add a batch of dict loan objects
        """
        self.order.add_batch([
            {
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }
        ])

        self.assertEqual(len(self.order.loans), 2)
        self.assertEqual(self.order.loans[123], 50)
        self.assertEqual(self.order.loans[234], 75)

    def test_add_batch_dict_amount(self):
        """ test_add_batch_dict_amount
        Add a batch dict with a batch_amount parameter value to override the individual values
        """
        self.order.add_batch([
            {
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }
        ], 100)

        self.assertEqual(len(self.order.loans), 2)
        self.assertEqual(self.order.loans[123], 100)
        self.assertEqual(self.order.loans[234], 100)

    def test_add_batch_list(self):
        """ test_add_batch_list
        Add a batch of IDs from a list, not a dict
        """
        self.order.add_batch([123, 234], 75)

        self.assertEqual(len(self.order.loans), 2)
        self.assertEqual(self.order.loans[123], 75)
        self.assertEqual(self.order.loans[234], 75)

    def test_add_batch_list_no_amount(self):
        """ test_add_batch_list_no_amount
        Send a list of IDs to add_batch, without an amount
        """
        self.assertRaises(
            AssertionError,
            lambda: self.order.add_batch([123, 234])
        )

    def test_add_batch_object(self):
        """ test_add_batch_object
        Pulling loans from the 'loan_fractions' value is no longer supported
        """
        loanDict = {
            'loan_fractions': [
                {
                    'loan_id': 123,
                    'invest_amount': 50
                }, {
                    'loan_id': 234,
                    'invest_amount': 75
                }
            ]
        }
        self.assertRaises(
            AssertionError,
            lambda: self.order.add_batch(loanDict)
        )

    def test_execute(self):
        self.order.add_batch([
            {
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }
        ])

        order_id = self.order.execute()
        self.assertNotEqual(order_id, 0)

    def test_execute_wrong_id(self):
        """ test_execute_wrong_id
        Server returns an ID that doesn't match an ID added to batch (345)
        """
        self.order.add_batch([234, 345], 75)
        self.assertRaises(
            FilterValidationError,
            lambda: self.order.execute()
        )

    def test_execute_existing_portfolio(self):
        self.order.add_batch([
            {
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }
        ])

        portfolio = 'New Portfolio'
        order_id = self.order.execute(portfolio)
        self.assertNotEqual(order_id, 0)

        # Check portfolio name
        request = self.lc.session.get('/session')
        http_session = request.json()
        self.assertEqual(http_session['new_portfolio'], portfolio)

    def test_execute_new_portfolio(self):
        self.order.add_batch([
            {
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }
        ])

        portfolio = 'Existing Portfolio'
        order_id = self.order.execute(portfolio)
        self.assertNotEqual(order_id, 0)

        # Check portfolio name
        request = self.lc.session.get('/session')
        http_session = request.json()
        self.assertEqual(http_session['existing_portfolio'], portfolio)

    def test_double_execute(self):
        """ test_double_execute
        An order can only be executed once
        """
        self.order.add_batch([
            {
                'loan_id': 123,
                'invest_amount': 50
            }, {
                'loan_id': 234,
                'invest_amount': 75
            }
        ])

        order_id = self.order.execute()
        self.assertNotEqual(order_id, 0)

        self.assertRaises(
            AssertionError,
            lambda: self.order.execute()
        )
Пример #20
0
class Autobuy:

    lc = None
    #main_filter = 'low-risk2'
    saved_filters = None
    main_filters = []
    main_filter_ids = [8641686, 9061019]

    def __init__(self, email = None, password = None, filter_ids = None):
        if filter_ids != None:
            self.main_filter_ids = filter_ids

        self.lc = LendingClub(email, password)
        self.lc.authenticate()
        self.saved_filters = self.lc.get_saved_filters()
        self.main_filters = \
            filter(lambda x: x.id in self.main_filter_ids, self.saved_filters)

    def lc(self):
        self.lc
    
    def run_all(self):
        loan_ids = []
        for f in self.main_filters:
            loan_ids = list(set(loan_ids) | set(self.run(f)))
        
        if len(loan_ids) > 0:
            order = self.lc.start_order()
            order.add_batch(loan_ids, 25)
            order.execute()
            print "%s loans processed" % len(loan_ids)
            return len(loan_ids)
         
        print "No loans processed"
        return False

    def run(self, _filter = None, just_loans = True):
        if _filter is None:
            raise Exception("No filter specified")
        results = self.lc.search(_filter)
        if results[u'totalRecords'] > 30:
            raise Exception("Filter probably broken, returning > 30 results")
        elif results[u'totalRecords'] == 0:
            return []

        if not just_loans:
            order = self.lc.start_order()
        loans = []
        for result in results[u'loans']:
            loan_id = result[u'loan_id']
            additional_details = self.lc.loan_details(loan_id)
            if additional_details[u'currentJobTitle'] == u'n/a':
                continue
            rate = float(re.findall(r"\d+\.\d+|\d+", additional_details[u'rate'])[0]) 
            term = int(additional_details[u'loanLength'])
            if (term == 36 and rate < 0.19) or rate < 0.22:
                continue
            loans.append(loan_id)
        
        if len(loans) > 0:
            if just_loans:
                return loans
            order.add_batch(loans, 25) 
            order.execute()
            return True

        print "No loans found"
        if just_loans:
            return []
        else:
            return True

    def __str__(self):
        print "Autobuyer"
Пример #21
0
class TestOrder(unittest.TestCase):
    lc = None
    order = None
    logger = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

        # Use version 2 of browseNotesAj.json
        self.lc.session.post('/session', data={'browseNotesAj': '2'})

        # Start order
        self.order = self.lc.start_order()

    def tearDown(self):
        pass

    def test_add(self):
        self.order.add(123, 50)
        self.assertEqual(len(self.order.loans), 1)
        self.assertEqual(self.order.loans[123], 50)

    def test_update(self):
        self.order.add(123, 50)
        self.assertEqual(self.order.loans[123], 50)

        self.order.add(123, 100)
        self.assertEqual(len(self.order.loans), 1)
        self.assertEqual(self.order.loans[123], 100)

    def test_remove(self):
        self.order.add(123, 50)
        self.order.add(234, 75)

        self.assertEqual(len(self.order.loans), 2)

        self.order.remove(234)

        self.assertEqual(len(self.order.loans), 1)
        self.assertEqual(self.order.loans[123], 50)
        self.assertFalse(234 in self.order.loans)

    def test_multiple_of_25(self):
        self.assertRaises(
            AssertionError,
            lambda: self.order.add(123, 0)
        )
        self.assertRaises(
            AssertionError,
            lambda: self.order.add(123, 26)
        )
Пример #22
0
class TestSavedFilters(unittest.TestCase):
    filters = None
    logger = None
    lc = None
    loan_list = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret12')

    def tearDown(self):
        pass

    def test_get_all_filters(self):
        filters = SavedFilter.all_filters(self.lc)

        self.assertEqual(len(filters), 2)
        self.assertEqual(filters[0].name, 'Filter 1')

    def test_get_saved_filters(self):
        saved = SavedFilter(self.lc, 1)

        self.assertEqual(saved.name, 'Filter 1')
        self.assertEqual(saved.id, 1)
        self.assertNotEqual(saved.search_string(), None)

    def test_validation_1(self):
        """ test_validation_1
        Filter 1 against filter_validation 1
        """
        saved = SavedFilter(self.lc, 1)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should fail on 'exclude_invested'
        try:
            saved.validate(self.loan_list)
            assert False, 'Test should fail on exclude_existing'
        except FilterValidationError as e:
            print(e.criteria)
            self.assertTrue(matches('exclude loans', e.criteria))

    def test_validation_2(self):
        """ test_validation_2
        Filter 2 against filter_validation 2
        """
        saved = SavedFilter(self.lc, 2)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 2})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should fail on 'exclude_invested'
        try:
            saved.validate(self.loan_list)
            assert False, 'Test should fail on loan_purpose'
        except FilterValidationError as e:
            print(e.criteria)
            self.assertTrue(matches('loan purpose', e.criteria))

    def test_validation_2_1(self):
        """ test_validation_2_1
        Filter 2 against filter_validation 1
        """
        saved = SavedFilter(self.lc, 2)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should not fail
        saved.validate(self.loan_list)

    def test_validation_2_3(self):
        """ test_validation_3
        Filter 2 against filter_validation 3
        """
        saved = SavedFilter(self.lc, 2)

        # Get loan list
        response = self.lc.session.get('/filter_validation', query={'id': 3})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

        # Validate, should fail on 'exclude_invested'
        try:
            saved.validate(self.loan_list)
            assert False, 'Test should fail on grade'
        except FilterValidationError as e:
            print(e.criteria)
            self.assertTrue(matches('grade', e.criteria))
Пример #23
0
class TestFilterValidation(unittest.TestCase):
    filters = None
    logger = None
    lc = None
    loan_list = None

    def setUp(self):
        self.filters = Filter()
        self.filters['exclude_existing'] = False
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)
        self.lc.authenticate('*****@*****.**', 'supersecret12')

        response = self.lc.session.get('/filter_validation', query={'id': 1})
        json_response = response.json()
        self.loan_list = json_response['loanFractions']

    def tearDown(self):
        pass

    def test_validation_defaults(self):
        """ test_validation_defaults
        Default filters should match
        """
        self.assertTrue(self.filters.validate(self.loan_list))

    def test_validation_grade_valid(self):
        self.filters['C'] = True
        self.assertTrue(self.filters.validate(self.loan_list))

    def test_validation_grade_fail(self):
        self.filters['grades']['B'] = True
        self.assertRaises(
            FilterValidationError,
            lambda: self.filters.validate(self.loan_list)
        )

    def test_validation_term_36(self):
        """ test_validation_term_36
        Should fail on the 60 month loan, loan_id: 12345
        """
        self.filters['term']['Year3'] = True
        self.filters['term']['Year5'] = False
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 12345)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_term_60(self):
        """ test_validation_term_60
        Should fail on the 36 month loan, loan_id: 23456
        """
        self.filters['term']['Year3'] = False
        self.filters['term']['Year5'] = True
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 23456)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_progress_70(self):
        """ test_validation_progress_70
        Loan 12345 is 91 percent funded
        Loan 23456 is 77 percent funded
        """
        self.filters['funding_progress'] = 70
        self.assertTrue(self.filters.validate(self.loan_list))

    def test_validation_progress_90(self):
        """ test_validation_term_90
        Should fail
        Loan 12345 is 91 percent funded
        Loan 23456 is 77 percent funded
        """
        self.filters['funding_progress'] = 90
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 23456)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_progress_95(self):
        """ test_validation_progress_95
        Should fail
        Loan 12345 is 91 percent funded
        Loan 23456 is 77 percent funded
        """
        self.filters['funding_progress'] = 95
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 12345)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)

    def test_validation_exclude_existing(self):
        """ test_validation_exclude_existing
        Should fail on loan 23456, which the user is already invested in.
        """
        self.filters['exclude_existing'] = True
        try:
            self.filters.validate(self.loan_list)

        # Check the loan it failed on
        except FilterValidationError as e:
            self.assertEqual(e.loan['loan_id'], 23456)

        # Invalid Exception
        except Exception:
            self.assertTrue(False)
Пример #24
0
class TestLendingClub(unittest.TestCase):
    lc = None
    logger = None

    def setUp(self):
        self.logger = TestLogger()

        self.lc = LendingClub(logger=self.logger)
        self.lc.session.base_url = 'http://127.0.0.1:8000/'
        self.lc.session.set_logger(None)

        self.lc.authenticate('*****@*****.**', 'supersecret')

        # Make sure session is enabled and clear
        self.lc.session.post('/session/enabled')
        self.lc.session.request('delete', '/session')

    def tearDown(self):
        pass

    def test_cash_balance(self):
        cash = self.lc.get_cash_balance()
        self.assertEqual(cash, 216.02)

    def test_portfolios(self):
        portfolios = self.lc.get_portfolio_list()
        self.assertEqual(len(portfolios), 2)
        self.assertEqual(portfolios[0]['portfolioName'], 'Existing Portfolio')

    def test_build_portfolio(self):
        portfolio = self.lc.build_portfolio(200, 25, 15, 16)

        self.assertNotEqual(portfolio, False)
        self.assertEqual(portfolio['percentage'], 15.28)

        self.assertTrue('loan_fractions' in portfolio)
        self.assertEqual(len(portfolio['loan_fractions']), 15)

    def test_build_portfolio_session_fail(self):
        """ test_build_portfolio_session_fail"
        If the session isn't saved, fractions shouldn't be found,
        which should make the entire method return False
        """

        # Disable session
        self.lc.session.post('/session/disabled')

        portfolio = self.lc.build_portfolio(200, 25, 15, 16)
        self.assertFalse(portfolio)

    def test_build_portfolio_no_match(self):
        """ test_build_portfolio_no_match"
        Enter a min/max percent that cannot match dummy returned JSON
        """
        portfolio = self.lc.build_portfolio(200, 25, 17.6, 18.5)
        self.assertFalse(portfolio)

    def test_search(self):
        results = self.lc.search()
        self.assertTrue(results is not False)
        self.assertTrue('loans' in results)
        self.assertTrue(len(results['loans']) > 0)
Пример #25
0
import sys
import unittest
import getpass
from random import choice
from logger import TestLogger

sys.path.insert(0, '.')
sys.path.insert(0, '../')
sys.path.insert(0, '../../')

from lendingclub import LendingClub, Order
from lendingclub.filters import Filter

logger = TestLogger()
lc = LendingClub(logger=logger)


class LiveTests(unittest.TestCase):

    def setUp(self):
        # Clear any existing orders
        lc.session.clear_session_order()

        # Override Order.__place_order so that no orders can be made
        Order._Order__place_order = self.place_order_override

        # Make sure that the override worked
        o = Order(lc)
        self.assertEqual(o._Order__place_order('token'), 12345)
Пример #26
0
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)
Пример #27
0
from random import choice

PY3 = sys.version_info > (3,)

if PY3:
    from builtins import input
else:
    input = raw_input

from logger import TestLogger

from lendingclub import LendingClub, Order
from lendingclub.filters import Filter

logger = TestLogger()
lc = LendingClub(logger=logger)


class LiveTests(unittest.TestCase):
    def setUp(self):
        # Clear any existing orders
        lc.session.clear_session_order()

        # Override _place_order so that no orders can be made
        Order._place_order = self.place_order_override

        # Make sure that the override worked
        o = Order(lc)
        self.assertEqual(o._place_order('token'), 12345)

    def place_order_override(self, token):
Пример #28
0
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)