def test_reverse_mortgage(self): cf = ReverseMortgage(home_value=200000, value_date=self.today, start_date=self.retirement, end_date=self.death) # Make sure we can use the calculator as of today self.assertEqual(cf.on(self.today), 0) # Make sure before start_date is zero self.assertEqual(cf.on(self.retirement - relativedelta(days=1)), 0) # Make sure it starts on start date, and was inflated correctly ret_val = cf.on(self.retirement) payments = months_between(self.retirement, self.death) self.assertAlmostEqual( ret_val, 200000 * 1.001**months_between(self.today, self.retirement) / payments * 0.9, 4) # Make sure we can get end date and it is the same payment self.assertAlmostEqual(cf.on(self.death), ret_val, 4) # Make sure after end date it returns zero self.assertEqual(cf.on(self.death + relativedelta(days=1)), 0) # Make sure backwards dates raise a value error with self.assertRaises(ValueError): cf.on(self.retirement) # Make sure a reset allows previous dates again, and we get the same result cf.reset() self.assertEqual(ret_val, cf.on(self.retirement)) time_series = self.get_cash_flow(cf) self.assertTrue(isinstance(time_series, pd.DataFrame))
def test_inflated_cash_flow(self): cf = InflatedCashFlow(amount=116, today=self.today, start_date=self.retirement, end_date=self.dob + relativedelta(years=85)) # Make sure we can use the calculator as of today self.assertEqual(cf.on(self.today), 0) # Make sure before start_date is zero self.assertEqual(cf.on(self.retirement - relativedelta(days=1)), 0) # Make sure it starts on start date, and was inflated correctly ret_val = cf.on(self.retirement) self.assertAlmostEqual( ret_val, 116 * 1.001**months_between(self.today, self.retirement), 4) # Make sure we can get end date and it is inflated correctly self.assertAlmostEqual( cf.on(self.dob + relativedelta(years=85)), 116 * 1.001** months_between(self.today, self.dob + relativedelta(years=85)), 4) # Make sure after end date it returns zero self.assertEqual(cf.on(self.dob + relativedelta(years=85, days=1)), 0) # Make sure backwards dates raise a value error with self.assertRaises(ValueError): cf.on(self.retirement) # Make sure a reset allows previous dates again, and we get the same result cf.reset() self.assertEqual(ret_val, cf.on(self.retirement)) time_series = self.get_cash_flow(cf) self.assertTrue(isinstance(time_series, pd.DataFrame))
def test_tax_paid_account(self): ac = TaxPaidAccount(name="Test Account", today=self.today, opening_balance=50000, growth=0.05, retirement_date=self.retirement, end_date=self.death, contributions=400) # Make sure we can use the calculator as of today, but attempted withdrawals fail until retirement date self.assertEqual(ac.balance(self.today), 50000) self.assertEqual(ac.withdraw(self.today, 1000), 0) self.assertEqual(ac.balance(self.today), 50000) # Make sure withdrawal before start_date is zero, but balance is inflated correctly r_minus_1 = self.retirement - relativedelta(days=1) def get_pred(months): pred = 50000 * (((1.05**(1 / 12)) + 0.001)**months) for m in range(1, months + 1): pred += (400 * (1.001**m)) * ((1.05**(1 / 12)) + 0.001)**(months - m) return pred predicted = get_pred(months_between(self.today, r_minus_1)) self.assertAlmostEqual(ac.balance(r_minus_1), predicted, 6) self.assertEqual(ac.withdraw(r_minus_1, 1000), 0) # Make sure the withdrawal didn't actually withdraw. self.assertAlmostEqual(ac.balance(r_minus_1), predicted, 6) # Make sure withdrawal can happen on start date, and withdrawal actually happens. predicted = get_pred(months_between(self.today, self.retirement)) self.assertAlmostEqual(ac.balance(self.retirement), predicted, 6) self.assertEqual(ac.withdraw(self.retirement, 1000), 1000) self.assertAlmostEqual(ac.balance(self.retirement), predicted - 1000, 6) # Make sure we can get end date self.assertEqual(ac.withdraw(self.death, 1000), 1000) # Make sure after end date it returns zero self.assertEqual(ac.withdraw(self.death + relativedelta(days=1), 1000), 0) # Make sure backwards dates raise a value error with self.assertRaises(ValueError): ac.balance(self.retirement) # Make sure a reset allows previous dates again, and we get the same result ac.reset() self.assertAlmostEqual(ac.balance(self.retirement), predicted, 6) time_series = self.get_balances(ac) self.assertTrue(isinstance(time_series, pd.DataFrame))
def test_employment_income(self): cf = EmploymentIncome(income=150000, growth=0.01, today=self.today, end_date=self.retirement) # Make sure we can use the calculator as of today self.assertEqual(cf.on(self.today), 150000) # Make sure we can get it on the end date and it is inflated correctly predicted = 150000 * ( (1 + (0.01 / 12))**months_between(self.today, self.retirement)) self.assertAlmostEqual(cf.on(self.retirement), predicted, 4) # Make sure after end date it returns zero self.assertEqual(cf.on(self.death + relativedelta(days=1)), 0) # Make sure backwards dates raise a value error with self.assertRaises(ValueError): cf.on(self.retirement) # Make sure a reset allows previous dates again, and we get the same result cf.reset() self.assertAlmostEqual(predicted, cf.on(self.retirement), 4) time_series = self.get_cash_flow(cf) self.assertTrue(isinstance(time_series, pd.DataFrame))
def __init__(self, home_value: float, value_date: datetime.date, start_date: datetime.date, end_date: datetime.date): """ Initialises a reverse mortgage calculator :param home_value: :param value_date: The date the home was valued. Must be <= start_date :param start_date: The day the reverse mortgage should start paying (Usually the retirement date) :param end_date: The day the reverse mortgage should end (Usually the death of the last family member) """ self.start_date = start_date self.end_date = end_date home_value_at_start_date = home_value * ( 1 + Inflation.between(value_date, start_date)) self.monthly_payment = home_value_at_start_date * 0.9 / months_between( start_date, end_date)
def cumulative(cls): """ :return: A dictionary from (year, month) => cumulative total inflation (1-based) from beginning of records till that time """ data = getattr(cls, '_cum_data', None) if not data: data = cache.get(redis.Keys.INFLATION) if not data: data = {} vals = list(cls.objects.all().values_list('year', 'month', 'value')) if vals: f_d = date(vals[0][0], vals[0][1], 1) l_d = date(vals[-1][0], vals[-1][1], 1) if (months_between(f_d, l_d) + 1) > len(vals): raise Exception("Holes exist in the inflation forecast figures, cannot proceed.") isum = 1 # Add the entry for the start of the series. data[((f_d - timedelta(days=1)).month, (f_d - timedelta(days=1)).year)] = isum for val in vals: isum *= 1 + val[2] data[(val[0], val[1])] = isum cache.set(redis.Keys.INFLATION, data, timeout=60 * 60 * 24) cls._cum_data = data return data