def test_returns(self, *args, **kwargs): """ Tests Account.returns and Account.returns_history. """ # Account with $1 balance and 100% non-compounded growth. # Should have returns of $1 in its first year: account = self.AccountType(self.owner, *args, balance=1, rate=1.0, nper=1, **kwargs) # pylint: disable=no-member # Pylint is confused by members added by metaclass self.assertEqual(account.returns, Money(1)) # $1 return self.assertEqual(account.returns_history, {self.initial_year: Money(1)})
def test_limit_ordered(self): """ Limit contributions with per-node limit in ordered tree. """ # Limit debt contributions to $100 # (rather than $1000 max. contribution) limits = LimitTuple(max_inflow=Money(100)) limit_node = TransactionNode(self.debt, limits=limits) priority = [self.rrsp, limit_node, self.taxable_account] strategy = TransactionTraversal(priority=priority) # Contribute $300 to the accounts: available = {Decimal(0.5): Money(300)} transactions = strategy(available) # $100 will go to each account: self.assertTransactions(transactions[self.rrsp], Money(100)) self.assertTransactions(transactions[self.debt], Money(100)) self.assertTransactions(transactions[self.taxable_account], Money(100))
def test_next_disc_growth_trans(self, *args, **kwargs): """ Tests next_year with discrete growth and a transaction. """ # The $2 transaction happens at the start of a compounding # period, so behaviour is well-defined. It should grow by a # factor of (1 + r/n)^nt, for n = 12 (monthly) and t = 0.5 account = self.AccountType(self.owner, *args, balance=1, rate=1, nper='M', **kwargs) account.add_transaction(Money(2), when='0.5') next_val = Money((1 + 1 / 12)**(12) + 2 * (1 + 1 / 12)**(12 * 0.5)) account.next_year() self.assertAlmostEqual(account.balance, next_val, 5)
def test_out_empty_all(self): """ Test strategy_weighted with mult. RRSPs, empty all accounts. """ # Try withdrawing more than the accounts have val = sum( sum(account.max_outflows().values()) for account in self.accounts ) - Money(50) available = make_available(Money(val), self.timing) results = self.strategy(available, self.accounts) # Confirm each account has the expected set of transactions: self.assertTransactions(results[self.rrsp], self.rrsp.max_outflows()) self.assertTransactions(results[self.tfsa], self.tfsa.max_outflows()) self.assertTransactions( results[self.taxable_account], self.taxable_account.max_outflows())
def test_add_trans_basic_acct(self): """ Moves $100 from available to an account. """ # Receive cash at start of year: self.available_acct.add_transaction(value=100, when='start') # Move all $100 to account1 right away: self.subforecast.add_transaction(value=100, timing='start', from_account=self.available_acct, to_account=self.account2) # No more money should be available: self.assertEqual(self.available_acct.transactions[Decimal(0)], Money(0)) # A $100 transaction should be added to account2: self.assertEqual(self.account2.transactions[Decimal(0)], Money(100))
def test_living_const_contrib(self): """ Test living expenses based on constant contribution. """ # Contribute $100 and live off the rest: self.strategy = LivingExpensesStrategy( strategy=LivingExpensesStrategy.strategy_const_contribution, base_amount=Money(100)) self.forecast.living_expenses_strategy = self.strategy # It _is_ necessary to record inflows from employment # for this strategy: self.forecast(self.available) # Calculate manually and compare results: living_expenses = self.total_available - Money(100) self.assertEqual(living_expenses, self.forecast.living_expenses)
def test_out_all_empty(self): """ Test strategy_weighted with outflows to empty all accounts. """ # Withdraw more than the accounts have: val = sum( sum(account.max_outflows().values()) for account in self.accounts ) - Money(50) available = make_available(Money(val), self.timing) results = self.strategy(available, self.accounts) # Confirm each account gets the expected total transactions: self.assertTransactions(results[self.rrsp], self.rrsp.max_outflows()) self.assertTransactions(results[self.tfsa], self.tfsa.max_outflows()) self.assertTransactions( results[self.taxable_account], self.taxable_account.max_outflows())
def setUp(self): initial_year = 2000 person = Person(initial_year, 'Testy McTesterson', 1980, retirement_date=2045) self.timing = Timing({Decimal(0.5): 1}) # These accounts have different rates: self.debt_big_high_interest = Debt( person, balance=Money(1000), rate=1, minimum_payment=Money(100), accelerated_payment=Money('Infinity')) self.debt_small_low_interest = Debt( person, balance=Money(100), rate=0, minimum_payment=Money(10), accelerated_payment=Money('Infinity')) self.debt_medium = Debt(person, balance=Money(500), rate=0.5, minimum_payment=Money(50), accelerated_payment=Money('Infinity')) self.debts = { self.debt_big_high_interest, self.debt_medium, self.debt_small_low_interest } self.max_payments = { debt: self.max_payment({debt}) for debt in self.debts } self.min_payments = { debt: self.min_payment({debt}) for debt in self.debts } self.strategy_avalanche = DebtPaymentStrategy( DebtPaymentStrategy.strategy_avalanche) self.strategy_snowball = DebtPaymentStrategy( DebtPaymentStrategy.strategy_snowball) self.excess = Money(10)
def test_next(self, *args, **kwargs): """ Test TFSA.next_year. """ super().test_next(*args, **kwargs) # Set up variables for testing. accruals = self.get_accruals() rand = Random() owner = Person(min(accruals), self.owner.name, 1950, retirement_date=2015, gross_income=self.owner.gross_income, raise_rate={ year: 0 for year in range(min(accruals), max(accruals) + 2) }, tax_treatment=self.owner.tax_treatment) account = self.AccountType(owner, *args, inflation_adjust=self.inflation_adjust, rate=0, balance=0, **kwargs) # For each year, confirm that the balance and contribution room # are updated appropriately transactions = Money(0) for year in accruals: # Add a transaction (either an inflow or outflow) transaction = rand.randint(-account.balance.amount, account.contribution_room.amount) account.add_transaction(transaction) # Confirm that contribution room is the same as accruals, # less any net transactions accrual = sum( [accruals[i] for i in range(min(accruals), year + 1)]) target = Money(accrual) - transactions self.assertEqual(account.contribution_room, target) # Confirm that balance is equal to the sum of transactions # over the previous years (note that this is a no-growth # scenario, since rate=0) self.assertEqual(account.balance, transactions) # Advance the account to next year and repeat tests account.next_year() # Update the running total of transactions, to be referenced # in the next round of tests. transactions += Money(transaction)
def test_max_inflows_neg(self, *args, **kwargs): """ Test max_inflows with negative balance """ # This method should always return Money('Infinity') account = self.AccountType(self.owner, *args, balance=-100, **kwargs) result = account.max_inflows(self.timing) for value in result.values(): self.assertEqual(value, Money('Infinity'))
def test_insufficient_income(self): """ Test a lower net income than the contribution rate. """ # Try to contribute more than net income: method = LivingExpensesStrategy.strategy_const_contribution strategy = LivingExpensesStrategy(method, Money(2001)) # Living expenses are $0 in this case, not -$1: self.assertEqual(strategy(people=self.people), 0)
def test_add_trans_shortfall(self): """ Transaction that must cause a negative balance. """ # Want to withdraw $100 when this amount will not be available # at any point in time: self.available_dict[Decimal(0)] = Money(50) self.available_dict[Decimal(1)] = Money(49) # Try to move $100 in cash to account2 at mid-year: self.subforecast.add_transaction(value=100, timing=0.5, from_account=self.available_dict, to_account=self.account2) # Transaction should occur immediately: self.assertEqual(self.available_dict[Decimal(0.5)], Money(-100)) # A $100 transaction should be added to account2: self.assertEqual(self.account2.transactions[Decimal(0.5)], Money(100))
def test_add_trans_delay_acct(self): """ Transaction that should be shifted to later time. """ # Receive cash mid-year: self.available_acct.add_transaction(value=100, when=0.5) # Try to move $100 in cash to account1 at the start of the year # (i.e. before cash is actually on-hand): self.subforecast.add_transaction(value=100, timing='start', from_account=self.available_acct, to_account=self.account2) # Transactions should be delayed until mid-year: self.assertEqual(self.available_acct.transactions[Decimal(0.5)], Money(0)) # A $100 transaction should be added to account2: self.assertEqual(self.account2.transactions[Decimal(0.5)], Money(100))
def test_gross_withdrawals(self): """ Test total withdrawn from accounts. """ # Set up forecast: self.forecast(self.available) # For default `available`, should withdraw $20,000. self.assertEqual(self.forecast.gross_withdrawals, Money(20000))
def test_init_cont_room_default(self, *args, **kwargs): """ Init TFSA with default contribution_room. """ # TFSAs began in 2009. Confirm that we're correctly determining # future contribution room based on an inflation-adjusted 2009 # amount (a schedule of such accruals is returned by # get_accruals). accruals = self.get_accruals() # Use a person who's at least old enough to qualify for all # available TFSA accruals. owner = Person(self.initial_year, "test", 1950, retirement_date=2015) # For each starting year, confirm that available contribution # room is the sum of past accruals. for year in accruals: account = self.AccountType(owner, *args, inflation_adjust=self.inflation_adjust, initial_year=year, **kwargs) target = Money( sum([accruals[i] for i in range(min(accruals), year + 1)])) self.assertEqual(account.contribution_room, target) # We're starting each TFSA in a different year, which can # confuse the linked-account recordkeeping (later-year # accounts will default to using the contribution room # of the first account). Destroy the link to simplify this. account.max_inflow_link.unregister()
def test_init_federal(self): """ Test TaxCanada.__init__ for federal jurisdiction. """ # There's some type-conversion going on, so test the Decimal- # valued `amount` of the Tax's tax bracket's keys against the # Decimal key object of the Constants tax brackets. tax = TaxCanada(self.inflation_adjustments, self.province) for year in constants.TAX_BRACKETS['Federal']: self.assertEqual( tax.federal_tax.tax_brackets(year), { Money(bracket): value for bracket, value in constants.TAX_BRACKETS['Federal'][year].items()}) self.assertEqual( tax.federal_tax.personal_deduction(year), constants.TAX_PERSONAL_DEDUCTION['Federal'][year]) self.assertEqual( tax.federal_tax.credit_rate(year), constants.TAX_CREDIT_RATE['Federal'][year]) self.assertTrue(callable(tax.federal_tax.inflation_adjust)) # Test that the default timings for CRA refunds/payments have # been set: self.assertEqual( set(tax.payment_timing), {constants.TAX_PAYMENT_TIMING}) self.assertEqual( set(tax.refund_timing), {constants.TAX_REFUND_TIMING})
def test_tax_withheld(self): """ Test tax withheld from accounts. """ # Set up forecast: self.withholding_forecast(self.available) # Total withholdings are $10000 (half of $20,000 withdrawn) self.assertEqual(self.withholding_forecast.tax_withheld, Money(-10000))
def test_min_inflows_pos(self, *args, **kwargs): """ Test min_inflow with positive balance """ # This method should always return $0 account = self.AccountType(self.owner, *args, balance=100, **kwargs) result = account.min_inflows(self.timing) for value in result.values(): self.assertAlmostEqual(value, Money(0), places=4)
def test_init_basic(self, *args, **kwargs): """ Tests Account.__init__ """ # Basic test: All correct values, check for equality and type owner = self.owner balance = Money(0) rate = 1.0 nper = 1 # This is the easiest case to test initial_year = self.initial_year account = self.AccountType(owner, *args, balance=balance, rate=rate, nper=nper, **kwargs) # Test primary attributes # pylint: disable=no-member # Pylint is confused by members added by metaclass self.assertEqual(account.balance_history, {initial_year: balance}) self.assertEqual(account.rate_history, {initial_year: rate}) self.assertEqual(account.transactions_history, {initial_year: {}}) self.assertEqual(account.balance, balance) self.assertEqual(account.rate, rate) self.assertEqual(account.nper, 1) self.assertEqual(account.initial_year, initial_year) self.assertEqual(account.this_year, initial_year) # Check types # pylint: disable=no-member # Pylint is confused by members added by metaclass self.assertTrue(type_check(account.balance_history, {int: Money})) self.assertIsInstance(account.balance, Money) self.assertTrue(type_check(account.rate_history, {int: Decimal})) self.assertIsInstance(account.rate, Decimal) self.assertIsInstance(account.nper, int) self.assertIsInstance(account.initial_year, int)
def test_min_inflows_large_balance(self): """ Test `min_inflows` with balance greater than min. payment. """ self.debt.minimum_payment = 100 self.debt.balance = Money(-1000) result = self.debt.min_inflows(self.timing) # Inflows should be capped at minimum payment: self.assertEqual(sum(result.values()), self.debt.minimum_payment)
def test_account_trans_ordered(self): """ Test account transactions under ordered strategy. """ # Set up forecast: self.forecast.transaction_strategy = TransactionStrategy( strategy=TransactionStrategy.strategy_ordered, weights={ "RRSP": 1, "Account": 2 }) self.forecast(self.available) # We are withdrawing $20,000. We'll withdraw the whole balance # of `rrsp` ($6000), with the rest from `account`: self.assertTransactions(self.forecast.account_transactions[self.rrsp], Money(-6000)) self.assertTransactions( self.forecast.account_transactions[self.account], Money(-14000))
def test_account_trans_weighted(self): """ Test account transactions under weighted strategy. """ # Set up forecast: self.forecast.transaction_strategy = TransactionStrategy( strategy=TransactionStrategy.strategy_weighted, weights={ "RRSP": 3000, "Account": 17000 }) self.forecast(self.available) # We are withdrawing $20,000. We'll withdraw $3000 from # `rrsp`, with the rest from `account`: self.assertTransactions(self.forecast.account_transactions[self.rrsp], Money(-3000)) self.assertTransactions( self.forecast.account_transactions[self.account], Money(-17000))
def test_cont_inf_adjust_basic(self, *args, **kwargs): """ Test inflation-adjust for `contribution_room` accrual max. """ # Start with the last year for which we know the nominal accrual # max already. The next year's accrual max will need to be # estimated via inflation-adjustment: self.set_initial_year(max(constants.RRSP_ACCRUAL_MAX)) # Inflation-adjust the (known) accrual max for the previous year # to get the max for this year. max_accrual = ( constants.RRSP_ACCRUAL_MAX[self.initial_year] * self.inflation_adjust(self.initial_year + 1, self.initial_year)) # Let's have income that's between the initial year's max # accrual and the next year's max accrual: income = Money( (max_accrual + constants.RRSP_ACCRUAL_MAX[self.initial_year]) / 2) / constants.RRSP_ACCRUAL_RATE self.owner.gross_income = income account = self.AccountType( self.owner, *args, rate=0, inflation_adjust=self.inflation_adjust, contribution_room=self.initial_contribution_room, **kwargs) account.next_year() # New contribution room should be simply determined by the # accrual rate set in Constants plus rollover. self.assertEqual( account.contribution_room, self.initial_contribution_room + constants.RRSP_ACCRUAL_RATE * income)
def test_living_const_living(self): """ Test living expenses based on constant living expenses. """ # Live off of $1200/yr: self.strategy = LivingExpensesStrategy( strategy=LivingExpensesStrategy.strategy_const_living_expenses, base_amount=Money(1200)) self.forecast.living_expenses_strategy = self.strategy # It's not necessary to record inflows from employment, # but since this is usually how it'll be called we do # so here: self.forecast(self.available) # Calculate manually and compare results: living_expenses = Money(1200) self.assertEqual(living_expenses, self.forecast.living_expenses)
def test_init_type_conversion(self, *args, **kwargs): """ Test type conversion of __init__ inputs. """ super().test_init_type_conversion(*args, **kwargs) # Try type conversion for inflation_adjustments inflation_adjustments = { '2000': '1', 2001.0: 1.25, Decimal(2002): 1.5, 2003: Decimal('1.75'), 2017.0: Decimal(2.0) } account = self.AccountType(self.owner, *args, contribution_room=500, inflation_adjust=inflation_adjustments, **kwargs) self.assertEqual(account.contributor, self.owner) self.assertEqual(account.contribution_room, Money('500')) self.assertEqual(account.inflation_adjust(2000), Decimal(1)) self.assertEqual(account.inflation_adjust(2001), Decimal(1.25)) self.assertEqual(account.inflation_adjust(2002), Decimal(1.5)) self.assertEqual(account.inflation_adjust(2003), Decimal(1.75)) self.assertEqual(account.inflation_adjust(2017), Decimal(2))
def test_call_money(self): """ Test TaxCanada.__call__ on Money input """ taxable_income = Money(100000) self.assertEqual( self.tax(taxable_income, self.initial_year), self.tax.federal_tax(taxable_income, self.initial_year) + self.tax.provincial_tax(taxable_income, self.initial_year))
def test_capital_gain_real_start(self, *args, **kwargs): """ Test capital gains realized at start of year. """ # Init account with $50 acb. # Balance is $100, of which $50 is capital gains. account = self.AccountType( self.owner, *args, acb=50, balance=100, rate=1, **kwargs) # Add a $100 start-of-year transaction. This should increase # ACB (to $150), but doesn't change capital gains: account.add_transaction(100, 'start') # Withdraw the entire starting balance. account.add_transaction(-200, 'start') # Regardless of the transaction, capital gain is only $50: self.assertEqual(account.capital_gain, Money(50)) # ACB is unchanged, since it's the start of year figure: self.assertEqual(account.acb, Money(50))
def test_capital_gain_real_end(self, *args, **kwargs): """ Test capital gains realized at end of year. """ # Init account with $50 acb. # Balance is $100, of which $50 is capital gains. account = self.AccountType( self.owner, *args, acb=50, balance=100, rate=1, **kwargs) # Add a $100 start-of-year transaction. This should increase # ACB to $150; the end of year balance will be $400, with $250 # in capital gains: account.add_transaction(100, 'start') # Withdraw the entire ending balance. account.add_transaction(-400, 'end') self.assertEqual(account.capital_gain, Money(250)) # ACB is unchanged, since it's the start of year figure: self.assertEqual(account.acb, Money(50))
def test_max_inflows_no_accel(self): """ Test `max_inflows` with zero `accelerated_payment`. """ self.debt.minimum_payment = 100 self.debt.balance = Money(-200) self.debt.accelerated_payment = 0 result = self.debt.max_inflows(self.timing) # Total inflows should be limited to minimum_payment: self.assertEqual(sum(result.values()), self.debt.minimum_payment)
def test_taxable_income_zero(self, *args, **kwargs): """ Test TaxableAccount.taxable_income with no sales. """ # Balance is $100, of which $50 is capital gains. account = self.AccountType( self.owner, *args, acb=50, balance=100, rate=1, **kwargs) # No capital gains are realized yet, so capital_gains=$0 self.assertEqual(account.taxable_income, Money(0))