def test_decimal(self): """ Test compatibility with Decimal inputs. """ # Convert values to Decimal-based self.setUp_decimal() # The test itself is based on test_next initial_year = 2017 gross_income = 100 tax = Tax( {initial_year: { Decimal(0): Decimal(0), Decimal(200): Decimal(0.5), Decimal(1000): Decimal(0.75)}}, inflation_adjust={ 2017: Decimal(1), 2018: Decimal(1), 2019: Decimal(1), 2020: Decimal(1)}, personal_deduction={2017: Decimal(0)}) person = Person( initial_year, 'Name', 2000, raise_rate=Decimal(2.0), retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) # In the first year: $100 gross income, $100 taxable income, # $100 net income. self.assertEqual(person.gross_income, gross_income) self.assertEqual(person.net_income, Decimal(100)) self.assertEqual(person.this_year, initial_year) self.assertEqual(person.taxable_income, Decimal(100)) person.next_year() # 200% raise # In the second year: $300 gross income, $300 taxable income, # $250 net income (as income over $200 is taxed at 50%) self.assertEqual(person.gross_income, Decimal(300)) self.assertEqual(person.net_income, Decimal(250)) self.assertEqual(person.this_year, initial_year + 1) self.assertEqual(person.taxable_income, Decimal(300))
def setUp_decimal(self): """ Sets up variables based on Decimal inputs. """ # pylint: disable=invalid-name # Pylint doesn't like `setUp_decimal`, but it's not our naming # convention, so don't complain to us! # pylint: enable=invalid-name # Different groups of tests use different groups of variables. # We could split this class up into multiple classes (perhaps # one for ordered strategies and one for weighted strategies?), # but for now this project's practice is one test case for each # custom class. # pylint: disable=too-many-instance-attributes initial_year = 2000 person = Person(initial_year, 'Testy McTesterson', 1980, retirement_date=2045, high_precision=Decimal) # Set up some accounts for the tests. self.rrsp = RRSP(person, balance=Decimal(200), rate=Decimal(0), contribution_room=Decimal(200), high_precision=Decimal) self.tfsa = TFSA(person, balance=Decimal(100), rate=Decimal(0), contribution_room=Decimal(100), high_precision=Decimal) self.taxable_account = TaxableAccount(person, balance=Decimal(1000), rate=Decimal(0), high_precision=Decimal) self.accounts = {self.rrsp, self.tfsa, self.taxable_account} # Define a simple timing for transactions: self.timing = {Decimal(0.5): Decimal(1)} # Build strategy for testing (in non-init tests): self.strategy = TransactionStrategy( TransactionStrategy.strategy_ordered, { 'RRSP': 1, 'TFSA': 2, 'TaxableAccount': 3 }, high_precision=Decimal)
def setUp(self): """ Builds stock variables to test with. """ self.initial_year = 2000 # Simple tax treatment: 50% tax rate across the board. tax = Tax(tax_brackets={self.initial_year: {Money(0): Decimal(0.5)}}) # A person who is paid $1000 gross ($500 withheld): timing = Timing(frequency='BW') self.person1 = Person(initial_year=self.initial_year, name="Test 1", birth_date="1 January 1980", retirement_date="31 December 2045", gross_income=Money(1000), tax_treatment=tax, payment_timing=timing) # A person who is paid $500 gross ($250 withheld): self.person2 = Person(initial_year=self.initial_year, name="Test 2", birth_date="1 January 1982", retirement_date="31 December 2047", gross_income=Money(500), tax_treatment=tax, payment_timing=timing) # An account owned by person1 with $100 to withdraw self.account1 = Account(owner=self.person1, balance=Money(100), rate=0) # An account owned by person2 with $200 to withdraw self.account2 = Account(owner=self.person2, balance=Money(100), rate=0) # An account that belongs in some sense to both people # with $50 to withdraw self.account_joint = Account(owner=self.person1, balance=Money(100), rate=0) self.person2.accounts.add(self.account_joint) self.forecast = TaxForecast(initial_year=self.initial_year, people={self.person1, self.person2}, tax_treatment=tax)
def setUp(self): """ Builds stock variables to test with. """ self.initial_year = 2000 self.subforecast = SubForecast(self.initial_year) self.person = Person(initial_year=self.initial_year, name="Test", birth_date="1 January 1980", retirement_date="31 December 2045") # A basic account with 100% interest and no compounding: self.account1 = Account(owner=self.person, balance=100, rate=1, nper=1) # Another account, same as account1: self.account2 = Account(owner=self.person, balance=100, rate=1, nper=1) # Set up a dict and Account for use as `available`: self.available_dict = defaultdict(lambda: 0) self.available_acct = Account(initial_year=self.initial_year, rate=0)
def setUp(self): """ Sets up mutable variables for each test call. """ # Set to default province: self.province = 'BC' self.tax = TaxCanada(self.inflation_adjustments, province='BC') # Set up some people to test on: # Person1 makes $100,000/yr, has a taxable account with $500,000 # taxable income, and an RRSP with $500,000 in taxable income. self.person1 = Person( self.initial_year, "Tester 1", self.initial_year - 20, retirement_date=self.initial_year + 45, gross_income=100000) self.taxable_account1 = TaxableAccount( owner=self.person1, acb=0, balance=Money(1000000), rate=Decimal('0.05'), nper=1) self.taxable_account1.add_transaction(-Money(1000000), when='start') # NOTE: by using an RRSP here, a pension income tax credit will # be applied by TaxCanadaJurisdiction. Be aware of this if you # want to test this output against a generic Tax object with # Canadian brackets. self.rrsp = RRSP( self.person1, inflation_adjust=self.inflation_adjustments, contribution_room=0, balance=Money(500000), rate=Decimal('0.05'), nper=1) self.rrsp.add_transaction(-Money(500000), when='start') # Person2 makes $50,000/yr and has a taxable account with # $5000 taxable income. self.person2 = Person( self.initial_year, "Tester 2", self.initial_year - 18, retirement_date=self.initial_year + 47, gross_income=50000) self.taxable_account2 = TaxableAccount( owner=self.person2, acb=0, balance=Money(10000), rate=Decimal('0.05'), nper=1) self.taxable_account2.add_transaction(-Money(10000), when='start')
def setUp(self): """ Builds stock variables to test with. """ self.initial_year = 2000 # Simple tax treatment: 50% tax rate across the board. tax = Tax(tax_brackets={self.initial_year: {Money(0): Decimal(0.5)}}) # A person who is paid $200 gross ($100 net) every 2 weeks: timing = Timing(frequency='BW') self.person1 = Person(initial_year=self.initial_year, name="Test 1", birth_date="1 January 1980", retirement_date="31 December 2045", gross_income=Money(5200), tax_treatment=tax, payment_timing=timing) # A person who is paid $100 gross ($50 net) every 2 weeks: self.person2 = Person(initial_year=self.initial_year, name="Test 2", birth_date="1 January 1982", retirement_date="31 December 2047", gross_income=Money(2600), tax_treatment=tax, payment_timing=timing) self.forecast = IncomeForecast(initial_year=self.initial_year, people={self.person1, self.person2})
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 setUp_decimal(self): """ Builds stock variables based on Decimal inputs. """ # pylint: disable=invalid-name # Pylint doesn't like `setUp_decimal`, but it's not our naming # convention, so don't complain to us! # pylint: enable=invalid-name self.initial_year = 2000 # Simple tax treatment: 50% tax rate across the board. tax = Tax(tax_brackets={self.initial_year: { Decimal(0): Decimal(0.5) }}, high_precision=Decimal) # Accounts need an owner: timing = Timing(frequency='BW', high_precision=Decimal) self.person = Person(initial_year=self.initial_year, name="Test", birth_date="1 January 1980", retirement_date="31 December 2045", gross_income=Decimal(5200), tax_treatment=tax, payment_timing=timing, high_precision=Decimal) # We want at least two accounts which are contributed to # in different orders depending on the strategy. self.account = Account(owner=self.person, high_precision=Decimal) self.rrsp = canada.accounts.RRSP(owner=self.person, contribution_room=Decimal(1000), high_precision=Decimal) # Track money available for use by the forecast: self.available = defaultdict(lambda: Decimal(0)) for i in range(26): # biweekly inflows from employment self.available[Decimal(0.5 + i) / 26] = Decimal(150) for i in range(12): # monthly living expenses and reductions: self.available[Decimal(i) / 12] -= Decimal(75) # The result: $3000 available self.total_available = sum(self.available.values()) # Now we can set up the big-ticket items: # Use an ordered strategy by default: self.strategy = TransactionTraversal([self.account, self.rrsp], high_precision=Decimal) self.forecast = SavingForecast( initial_year=self.initial_year, retirement_accounts={self.account, self.rrsp}, debt_accounts=set(), transaction_strategy=self.strategy, high_precision=Decimal)
def test_add_account(self): """ Test Person after being added as an Account owner. """ person1 = self.owner person2 = Person(self.initial_year, "Spouse", self.initial_year - 20, retirement_date=self.retirement_date, gross_income=Money(50000), spouse=person1, tax_treatment=self.tax_treatment) # Add an account and confirm that the Person passed as owner is # updated. account1 = Account(owner=person1) account2 = Account(owner=person1) self.assertEqual(person1.accounts, {account1, account2}) self.assertEqual(person2.accounts, set())
def setUp(self): """ Sets up variables for testing. """ self.initial_year = 2000 self.person = Person(self.initial_year, "Test", "1 January 1980", retirement_date="1 January 2030") # An RRSP with a $1000 balance and $100 in contribution room: self.rrsp = RRSP(initial_year=self.initial_year, owner=self.person, balance=1000, contribution_room=100) # Another RRSP, linked to the first one: # (For testing LinkedLimitAccount nodes) self.rrsp2 = RRSP(initial_year=self.initial_year, owner=self.person) # A TFSA with a $100 balance and $1000 in contribution room: self.tfsa = TFSA(initial_year=self.initial_year, owner=self.person, balance=100, contribution_room=1000) # Another TFSA, linked to the first one: self.tfsa2 = TFSA(initial_year=self.initial_year, owner=self.person) # A taxable account with $0 balance (and no contribution limit) self.taxable_account = TaxableAccount(initial_year=self.initial_year, owner=self.person, balance=0) # A $100 debt with no interest and a $10 min. payment: self.debt = Debt(initial_year=self.initial_year, owner=self.person, balance=100, minimum_payment=10) # Contribute to RRSP, then TFSA, then taxable self.priority_ordered = [self.rrsp, self.tfsa, self.taxable_account] # Contribute 50% to RRSP, 25% to TFSA, and 25% to taxable self.priority_weighted = { self.rrsp: 0.5, self.tfsa: 0.25, self.taxable_account: 0.25 } # Contribute to RRSP and TFSA (50-50) until both are full, with # the remainder to taxable accounts. self.priority_nested = [{ self.rrsp: 0.5, self.tfsa: 0.5 }, self.taxable_account]
def setUp_decimal(self): """ Sets up variables based on Decimal inputs. """ # We use caps because this is a type. # pylint: disable=invalid-name self.AccountType = Account # pylint: enable=invalid-name # It's important to synchronize the initial years of related # objects, so store it here: self.initial_year = 2000 # Every init requires an owner, so store that here: self.scenario = Scenario(inflation=Decimal(0), stock_return=Decimal(1), bond_return=Decimal(0.5), other_return=Decimal(0), management_fees=Decimal(0.03125), initial_year=self.initial_year, num_years=100) self.allocation_strategy = AllocationStrategy( strategy=AllocationStrategy.strategy_n_minus_age, min_equity=Decimal(0.5), max_equity=Decimal(0.5), target=Decimal(0.5), standard_retirement_age=65, risk_transition_period=20, adjust_for_retirement_plan=False) self.owner = Person( self.initial_year, "test", 2000, raise_rate={year: Decimal(1) for year in range(2000, 2066)}, retirement_date=2065) # We'll also need a timing value for various tests. # Use two inflows, at the start and end, evenly weighted: self.timing = {Decimal(0): Decimal(1), Decimal(1): Decimal(1)} # Inheriting classes should assign to self.account with an # instance of an appropriate subclass of Account. self.account = Account(self.owner, balance=Decimal(100), rate=Decimal(1.0), high_precision=Decimal)
def test_tax_deduction(self): """ Test Person.tax_deduction. """ initial_year = 2017 gross_income = 300 tax = Tax({initial_year: { 0: 0, 200: 0.5, 1000: 0.75 }}, { 2017: 1, 2018: 1, 2019: 1, 2020: 1 }, {2017: 0}) person = Person(self.initial_year, 'Name', 2000, retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) self.assertEqual(person.tax_deduction, Money(0))
def test_init_optional(self): """ Tests Person.__init__ with optional args. """ # Now confirm that we can pass gross_income, spouse, # tax_treatment, and initial_year gross_income = 100000 person1 = Person( self.initial_year, self.name, self.birth_date, retirement_date=self.retirement_date, gross_income=gross_income, spouse=None, tax_treatment=self.tax_treatment) self.assertEqual(person1.gross_income, gross_income) self.assertEqual( # pylint: disable=no-member # Pylint is confused by members added by metaclass person1.gross_income_history, {self.initial_year: gross_income} ) self.assertEqual(person1.tax_treatment, self.tax_treatment) self.assertEqual(person1.initial_year, self.initial_year) self.assertIsNone(person1.spouse) self.assertEqual(person1.accounts, set())
def setUp(self): """ Builds stock variables to test with. """ self.initial_year = 2000 # Simple tax treatment: 50% tax rate across the board. tax = Tax(tax_brackets={self.initial_year: {0: 0.5}}) # Accounts need an owner: timing = Timing(frequency='BW') self.person = Person(initial_year=self.initial_year, name="Test", birth_date="1 January 1980", retirement_date="31 December 2045", gross_income=5200, tax_treatment=tax, payment_timing=timing) # We want at least two accounts which are contributed to # in different orders depending on the strategy. self.account = Account(owner=self.person) self.rrsp = canada.accounts.RRSP(owner=self.person, contribution_room=1000) # Track money available for use by the forecast: self.available = defaultdict(lambda: 0) for i in range(26): # biweekly inflows from employment self.available[(0.5 + i) / 26] = 150 for i in range(12): # monthly living expenses and reductions: self.available[i / 12] -= 75 # The result: $3000 available self.total_available = sum(self.available.values()) # Now we can set up the big-ticket items: # Use an ordered strategy by default: self.strategy = TransactionTraversal([self.account, self.rrsp]) self.forecast = SavingForecast( initial_year=self.initial_year, retirement_accounts={self.account, self.rrsp}, debt_accounts=set(), transaction_strategy=self.strategy)
def setUp(self): """ Sets up variables for testing. """ # Vars for building accounts: initial_year = 2000 person = Person( initial_year, 'Testy McTesterson', 1980, retirement_date=2045) # Set up some accounts for the tests. self.rrsp = RRSP( person, balance=Money(200), rate=0, contribution_room=Money(200)) self.tfsa = TFSA( person, balance=Money(100), rate=0, contribution_room=Money(100)) self.taxable_account = TaxableAccount( person, balance=Money(1000), rate=0) self.accounts = {self.rrsp, self.tfsa, self.taxable_account} # Define a simple timing for transactions: self.timing = {Decimal(0.5): 1} self.max_outflow = sum( sum(account.max_outflows(timing=self.timing).values()) for account in self.accounts) self.max_inflows = sum( sum(account.max_inflows(timing=self.timing).values()) for account in self.accounts) # Build strategy for testing (in non-init tests): self.weights = { 'RRSP': Decimal('0.4'), 'TFSA': Decimal('0.3'), 'TaxableAccount': Decimal('0.3') } self.strategy = TransactionStrategy( TransactionStrategy.strategy_weighted, self.weights)
def test_init_inputs(self): """ Test Person.__init__ with inputs arg. """ initial_year = 2017 gross_income = 500 inputs = { 'gross_income': { initial_year: 1000, initial_year + 2: 0 } } person = Person( initial_year, 'Name', 2000, retirement_date=2065, gross_income=gross_income, raise_rate=1, inputs=inputs) # We've gross income for the first and third years; the second # year should be set programmatically based on a 100% raise. self.assertEqual(person.gross_income, 1000) person.next_year() self.assertEqual(person.gross_income, 2000) person.next_year() self.assertEqual(person.gross_income, 0)
def test_init_invalid_order(self): """ Test Person.__init__ with retirement_date before birth_date. """ with self.assertRaises(ValueError): Person( self.initial_year, self.name, self.birth_date, retirement_date=self.birth_date - relativedelta(days=1))
class TestPersonMethods(unittest.TestCase): """ A test suite for the `Person` class. """ # This class has instance attributes corresponding to those of # Person, so we're sort of stuck with this number of attributes. # pylint: disable=too-many-instance-attributes def setUp(self): """ Sets up default vaules for testing """ self.initial_year = 2020 self.name = "Testy McTesterson" self.birth_date = datetime(2000, 2, 1) # 1 February 2000 self.retirement_date = datetime(2065, 6, 26) # 26 June 2065 self.gross_income = 100000 # $100000 self.raise_rate = 1 # 100% self.tax_treatment = Tax( {self.initial_year: { 0: 0.1, 1000: 0.2, 100000: 0.3} }, inflation_adjust={ year: 1 + (year - self.initial_year / 16) for year in range(self.initial_year, self.initial_year + 100) }, personal_deduction={self.initial_year: 100}, credit_rate={self.initial_year: 0.15}) self.spouse = Person( initial_year=self.initial_year, name="Spouse", birth_date=1998, retirement_date=2063, gross_income=50000, raise_rate=self.raise_rate, spouse=None, tax_treatment=self.tax_treatment) self.owner = Person( initial_year=self.initial_year, name=self.name, birth_date=self.birth_date, retirement_date=self.retirement_date, gross_income=self.gross_income, raise_rate=self.raise_rate, spouse=self.spouse, tax_treatment=self.tax_treatment) def setUp_decimal(self): """ Sets up default vaules using Decimal inputs """ self.initial_year = 2020 self.name = "Testy McTesterson" self.birth_date = datetime(2000, 2, 1) # 1 February 2000 self.retirement_date = datetime(2065, 6, 26) # 26 June 2065 self.gross_income = Decimal(100000) # $100000 self.raise_rate = Decimal(1) # 100% self.tax_treatment = Tax( {self.initial_year: { Decimal(0): Decimal('0.1'), Decimal(1000): Decimal('0.2'), Decimal(100000): Decimal('0.3')} }, inflation_adjust={ year: Decimal(1 + (year - self.initial_year) / 16) for year in range(self.initial_year, self.initial_year + 100) }, personal_deduction={self.initial_year: Decimal(100)}, credit_rate={self.initial_year: Decimal('0.15')}) self.spouse = Person( initial_year=self.initial_year, name="Spouse", birth_date=1998, retirement_date=2063, gross_income=Decimal(50000), raise_rate=self.raise_rate, spouse=None, tax_treatment=self.tax_treatment) self.owner = Person( initial_year=self.initial_year, name=self.name, birth_date=self.birth_date, retirement_date=self.retirement_date, gross_income=self.gross_income, raise_rate=self.raise_rate, spouse=self.spouse, tax_treatment=self.tax_treatment) def test_init_basic(self): """ Tests Person.__init__ with properly-types mandatory args. """ person = Person( self.initial_year, self.name, self.birth_date, retirement_date=self.retirement_date) self.assertEqual(person.name, self.name) self.assertEqual(person.birth_date, self.birth_date) self.assertEqual(person.retirement_date, self.retirement_date) self.assertIsInstance(person.name, str) self.assertIsInstance(person.birth_date, datetime) self.assertIsInstance(person.retirement_date, datetime) self.assertIsNone(person.spouse) self.assertIsNone(person.tax_treatment) def test_input_type_conv(self): """ Test Person.__init__ with args that need type conversion. """ # Should work with strings instead of dates birth_date_str = "1 January 2000" birth_date = datetime(2000, 1, 1) person = Person( self.initial_year, self.name, birth_date_str, retirement_date=self.retirement_date) self.assertEqual(person.birth_date, birth_date) self.assertIsInstance(person.birth_date, datetime) # Should work with non-str/non-datetime values as well birth_date = 2000 retirement_date = birth_date + 65 person = Person( self.initial_year, self.name, birth_date, retirement_date=retirement_date) self.assertEqual(person.birth_date.year, datetime(2000, 1, 1).year) self.assertEqual(person.birth_date.year + 65, person.retirement_date.year) self.assertEqual(person.birth_date.month, person.retirement_date.month) self.assertEqual(person.birth_date.day, person.retirement_date.day) # Let's mix different types of non-datetime inputs. Should work. birth_date = "3 February 2001" retirement_date = 2002 person = Person( self.initial_year, self.name, birth_date, retirement_date=retirement_date) birth_date = datetime(2001, 2, 3) self.assertEqual(person.birth_date, birth_date) self.assertEqual(person.retirement_date.year, retirement_date) self.assertEqual(person.birth_date.month, person.retirement_date.month) self.assertEqual(person.birth_date.day, person.retirement_date.day) # Let's mix datetime and non-datetime inputs. Should work. birth_date = "3 February 2001" retirement_date = datetime(2002, 1, 1) person = Person( self.initial_year, self.name, birth_date, retirement_date=retirement_date) birth_date = datetime(2001, 2, 3) self.assertEqual(person.birth_date, birth_date) self.assertEqual(person.retirement_date.year, retirement_date.year) self.assertEqual(person.birth_date.month, birth_date.month) self.assertEqual(person.retirement_date.month, retirement_date.month) self.assertEqual(person.birth_date.day, birth_date.day) self.assertEqual(person.retirement_date.day, retirement_date.day) def test_init_invalid_order(self): """ Test Person.__init__ with retirement_date before birth_date. """ with self.assertRaises(ValueError): Person( self.initial_year, self.name, self.birth_date, retirement_date=self.birth_date - relativedelta(days=1)) def test_init_invalid_birth_date(self): """ Test Person.__init__ with invalid birth_date string. """ # Whether this raises a ValueError or TypeError is an # implementation detail delegated to `datetime` with self.assertRaises((ValueError, TypeError)): Person( self.initial_year, self.name, 'invalid', retirement_date=self.retirement_date) def test_init_invalid_retire_date(self): """ Test Person.__init__ with invalid retirement_date string. """ # Whether this raises a ValueError or TypeError is an # implementation detail delegated to `datetime` with self.assertRaises((ValueError, TypeError)): Person( self.initial_year, self.name, self.birth_date, retirement_date='invalid') def test_init_optional(self): """ Tests Person.__init__ with optional args. """ # Now confirm that we can pass gross_income, spouse, # tax_treatment, and initial_year gross_income = 100000 person1 = Person( self.initial_year, self.name, self.birth_date, retirement_date=self.retirement_date, gross_income=gross_income, spouse=None, tax_treatment=self.tax_treatment) self.assertEqual(person1.gross_income, gross_income) self.assertEqual( # pylint: disable=no-member # Pylint is confused by members added by metaclass person1.gross_income_history, {self.initial_year: gross_income} ) self.assertEqual(person1.tax_treatment, self.tax_treatment) self.assertEqual(person1.initial_year, self.initial_year) self.assertIsNone(person1.spouse) self.assertEqual(person1.accounts, set()) def test_init_spouse(self): """ Test Person.__init__ with a spouse parameter. """ # Add a spouse and confirm that both Person objects are updated person1 = self.owner person2 = Person( self.initial_year, "Spouse", self.initial_year - 20, retirement_date=self.retirement_date, gross_income=50000, spouse=person1, tax_treatment=self.tax_treatment) self.assertEqual(person1.spouse, person2) self.assertEqual(person2.spouse, person1) def test_add_account(self): """ Test Person after being added as an Account owner. """ person1 = self.owner person2 = Person( self.initial_year, "Spouse", self.initial_year - 20, retirement_date=self.retirement_date, gross_income=50000, spouse=person1, tax_treatment=self.tax_treatment) # Add an account and confirm that the Person passed as owner is # updated. account1 = Account(owner=person1) account2 = Account(owner=person1) self.assertEqual(person1.accounts, {account1, account2}) self.assertEqual(person2.accounts, set()) def test_age(self): """ Tests person.age """ # Test output for person's 20th birthday: date = self.birth_date + relativedelta(years=20) self.assertEqual(self.owner.age(date), 20) self.assertIsInstance(self.owner.age(date), int) # Test output for day before person's 20th birthday: date = self.birth_date + relativedelta(years=20, days=-1) self.assertEqual(self.owner.age(date), 19) # Test output for day after person's 20th birthday: date = date + relativedelta(days=2) self.assertEqual(self.owner.age(date), 20) # NOTE: The following tests for negative ages, and should # probably be left undefined (i.e. implementation-specific) # Test output for day before person's birth date = self.birth_date - relativedelta(days=1) self.assertEqual(self.owner.age(date), -1) # Test output for one year before person's birth date = self.birth_date - relativedelta(years=1) self.assertEqual(self.owner.age(date), -1) # Test output for one year and a day before person's birth date = self.birth_date - relativedelta(years=1, days=1) self.assertEqual(self.owner.age(date), -2) # Repeat the above, but with strings date = str(self.birth_date + relativedelta(years=20)) self.assertEqual(self.owner.age(date), 20) date = str(self.birth_date + relativedelta(years=20) - relativedelta(days=1)) self.assertEqual(self.owner.age(date), 19) date = str(self.birth_date + relativedelta(years=20, day=1)) self.assertEqual(self.owner.age(date), 20) date = str(self.birth_date - relativedelta(days=1)) self.assertEqual(self.owner.age(date), -1) # Finally, test ints as input date = self.birth_date.year + 20 self.assertEqual(self.owner.age(date), 20) date = self.birth_date.year - 1 self.assertEqual(self.owner.age(date), -1) def test_retirement_age(self): """ Tests person.retirement_age """ # Test that the retirement age for stock person is accurate delta = relativedelta(self.owner.retirement_date, self.owner.birth_date) self.assertEqual(self.owner.retirement_age, delta.years) self.assertIsInstance(self.owner.retirement_age, int) # Test retiring on 65th birthday retirement_date = self.birth_date + relativedelta(years=65) person = Person( self.initial_year, self.name, self.birth_date, retirement_date=retirement_date) self.assertEqual(person.retirement_age, 65) # Test retiring on day after 65th birthday retirement_date = self.birth_date + relativedelta(years=65, day=1) person = Person( self.initial_year, self.name, self.birth_date, retirement_date=retirement_date) self.assertEqual(person.retirement_age, 65) # Test retiring on day before 65th birthday retirement_date = self.birth_date + relativedelta(years=65) - \ relativedelta(days=1) person = Person( self.initial_year, self.name, self.birth_date, retirement_date=retirement_date) self.assertEqual(person.retirement_age, 64) # Test person with no known retirement date # NOTE: This is not currently implemented # person = Person(self.name, self.birth_date) # self.assertIsNone(person.retirement_age) def test_next(self): """ Test next_year to confirm that properties are advanced. """ initial_year = 2017 gross_income = 100 tax = Tax( {initial_year: {0: 0, 200: 0.5, 1000: 0.75}}, inflation_adjust={2017: 1, 2018: 1, 2019: 1, 2020: 1}, personal_deduction={2017: 0}) person = Person( initial_year, 'Name', 2000, raise_rate=2.0, retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) self.assertEqual(person.gross_income, gross_income) self.assertEqual(person.net_income, 100) self.assertEqual(person.this_year, initial_year) person.next_year() # 200% raise - gross income is now $300 self.assertEqual(person.gross_income, 300) self.assertEqual(person.net_income, 250) self.assertEqual(person.this_year, initial_year + 1) def test_decimal(self): """ Test compatibility with Decimal inputs. """ # Convert values to Decimal-based self.setUp_decimal() # The test itself is based on test_next initial_year = 2017 gross_income = 100 tax = Tax( {initial_year: { Decimal(0): Decimal(0), Decimal(200): Decimal(0.5), Decimal(1000): Decimal(0.75)}}, inflation_adjust={ 2017: Decimal(1), 2018: Decimal(1), 2019: Decimal(1), 2020: Decimal(1)}, personal_deduction={2017: Decimal(0)}) person = Person( initial_year, 'Name', 2000, raise_rate=Decimal(2.0), retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) # In the first year: $100 gross income, $100 taxable income, # $100 net income. self.assertEqual(person.gross_income, gross_income) self.assertEqual(person.net_income, Decimal(100)) self.assertEqual(person.this_year, initial_year) self.assertEqual(person.taxable_income, Decimal(100)) person.next_year() # 200% raise # In the second year: $300 gross income, $300 taxable income, # $250 net income (as income over $200 is taxed at 50%) self.assertEqual(person.gross_income, Decimal(300)) self.assertEqual(person.net_income, Decimal(250)) self.assertEqual(person.this_year, initial_year + 1) self.assertEqual(person.taxable_income, Decimal(300)) def test_taxable_income(self): """ Test Person.taxable_income. """ initial_year = 2017 gross_income = 100 tax = Tax({initial_year: {0: 0, 200: 0.5, 1000: 0.75}}, {2017: 1, 2018: 1, 2019: 1, 2020: 1}, {2017: 0}) person = Person( initial_year, 'Name', 2000, retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) self.assertEqual(person.taxable_income, gross_income) def test_tax_withheld(self): """ Test Person.tax_withheld. """ initial_year = 2017 gross_income = 300 tax = Tax( {initial_year: {0: 0, 200: 0.5, 1000: 0.75}}, inflation_adjust={2017: 1, 2018: 1, 2019: 1, 2020: 1}, personal_deduction={2017: 0}) person = Person( self.initial_year, 'Name', 2000, retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) self.assertEqual(person.tax_withheld, 50) def test_tax_credit(self): """ Test Person.tax_credit. """ initial_year = 2017 gross_income = 300 tax = Tax({initial_year: {0: 0, 200: 0.5, 1000: 0.75}}, {2017: 1, 2018: 1, 2019: 1, 2020: 1}, {2017: 0}) person = Person( self.initial_year, 'Name', 2000, retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) self.assertEqual(person.tax_credit, 0) def test_tax_deduction(self): """ Test Person.tax_deduction. """ initial_year = 2017 gross_income = 300 tax = Tax({initial_year: {0: 0, 200: 0.5, 1000: 0.75}}, {2017: 1, 2018: 1, 2019: 1, 2020: 1}, {2017: 0}) person = Person( self.initial_year, 'Name', 2000, retirement_date=self.retirement_date, gross_income=gross_income, tax_treatment=tax) self.assertEqual(person.tax_deduction, 0) def test_init_inputs(self): """ Test Person.__init__ with inputs arg. """ initial_year = 2017 gross_income = 500 inputs = { 'gross_income': { initial_year: 1000, initial_year + 2: 0 } } person = Person( initial_year, 'Name', 2000, retirement_date=2065, gross_income=gross_income, raise_rate=1, inputs=inputs) # We've gross income for the first and third years; the second # year should be set programmatically based on a 100% raise. self.assertEqual(person.gross_income, 1000) person.next_year() self.assertEqual(person.gross_income, 2000) person.next_year() self.assertEqual(person.gross_income, 0)
def setUp_decimal(self): """ Builds default strategies/persons/etc. with Decimal inputs. """ # pylint: disable=invalid-name # This name is based on `setUp`, which doesn't follow Pylint's rules # pylint: enable=invalid-name # Use a default settings object: # (This is conditional so that subclasses can assign their own # settings object before calling super().setUp()) if not hasattr(self, 'settings'): self.settings = Settings() # To simplify tests, modify Settings so that forecasts are # just 2 years with easy-to-predict contributions ($1000/yr) self.settings.num_years = 2 self.settings.living_expenses_strategy = ( LivingExpensesStrategy.strategy_const_contribution) self.settings.living_expenses_base_amount = Decimal(1000) # Allow subclasses to use subclasses of Forecaster by assigning # to forecaster_type if not hasattr(self, 'forecaster_type'): self.forecaster_type = Forecaster # Build default `SubForecast` inputs based on `settings`: self.initial_year = self.settings.initial_year self.scenario = Scenario( inflation=Decimal(self.settings.inflation), stock_return=Decimal(self.settings.stock_return), bond_return=Decimal(self.settings.bond_return), other_return=Decimal(self.settings.other_return), management_fees=Decimal(self.settings.management_fees), initial_year=self.settings.initial_year, num_years=self.settings.num_years) self.living_expenses_strategy = LivingExpensesStrategy( strategy=self.settings.living_expenses_strategy, base_amount=Decimal(self.settings.living_expenses_base_amount), rate=Decimal(self.settings.living_expenses_rate), inflation_adjust=self.scenario.inflation_adjust) self.saving_strategy = TransactionStrategy( strategy=self.settings.saving_strategy, weights={ year: Decimal(val) for (year, val) in self.settings.saving_weights.items() }) self.withdrawal_strategy = TransactionStrategy( strategy=self.settings.withdrawal_strategy, weights={ year: Decimal(val) for (year, val) in self.settings.withdrawal_weights.items() }) self.allocation_strategy = AllocationStrategy( strategy=self.settings.allocation_strategy, min_equity=Decimal(self.settings.allocation_min_equity), max_equity=Decimal(self.settings.allocation_max_equity), target=Decimal(self.settings.allocation_target), standard_retirement_age=( self.settings.allocation_std_retirement_age), risk_transition_period=self.settings.allocation_risk_trans_period, adjust_for_retirement_plan=( self.settings.allocation_adjust_retirement)) self.debt_payment_strategy = DebtPaymentStrategy( strategy=self.settings.debt_payment_strategy, high_precision=Decimal) self.tax_treatment = Tax( tax_brackets={ year: { Decimal(lower): Decimal(upper) } for (year, vals) in self.settings.tax_brackets.items() for (lower, upper) in vals.items() }, personal_deduction={ year: Decimal(val) for (year, val) in self.settings.tax_personal_deduction.items() }, credit_rate={ year: Decimal(val) for (year, val) in self.settings.tax_credit_rate.items() }, inflation_adjust=self.scenario.inflation_adjust, high_precision=Decimal) # Now build some Ledger objects to test against: # A person making $10,000/yr self.person = Person(initial_year=self.initial_year, name="Test 1", birth_date="1 January 1980", retirement_date="31 December 2040", gross_income=Decimal(10000), raise_rate=Decimal(0), spouse=None, tax_treatment=self.tax_treatment, high_precision=Decimal) # An account with $1000 in it (and no interest) self.account = Account(owner=self.person, balance=Decimal(1000), high_precision=Decimal) # A debt with a $100 balance (and no interest) self.debt = Debt(owner=self.person, balance=Decimal(100), high_precision=Decimal) # Init a Forecaster object here for convenience: self.forecaster = self.forecaster_type(settings=self.settings, high_precision=Decimal)
def setUp(self): """ Builds default strategies, persons, etc. """ # Use a default settings object: # (This is conditional so that subclasses can assign their own # settings object before calling super().setUp()) if not hasattr(self, 'settings'): self.settings = Settings() # To simplify tests, modify Settings so that forecasts are # just 2 years with easy-to-predict contributions ($1000/yr) self.settings.num_years = 2 self.settings.living_expenses_strategy = ( LivingExpensesStrategy.strategy_const_contribution) self.settings.living_expenses_base_amount = 1000 # Allow subclasses to use subclasses of Forecaster by assigning # to forecaster_type if not hasattr(self, 'forecaster_type'): self.forecaster_type = Forecaster # Build default `SubForecast` inputs based on `settings`: self.initial_year = self.settings.initial_year self.scenario = Scenario(inflation=self.settings.inflation, stock_return=self.settings.stock_return, bond_return=self.settings.bond_return, other_return=self.settings.other_return, management_fees=self.settings.management_fees, initial_year=self.settings.initial_year, num_years=self.settings.num_years) self.living_expenses_strategy = LivingExpensesStrategy( strategy=self.settings.living_expenses_strategy, base_amount=self.settings.living_expenses_base_amount, rate=self.settings.living_expenses_rate, inflation_adjust=self.scenario.inflation_adjust) self.saving_strategy = TransactionStrategy( strategy=self.settings.saving_strategy, weights=self.settings.saving_weights) self.withdrawal_strategy = TransactionStrategy( strategy=self.settings.withdrawal_strategy, weights=self.settings.withdrawal_weights) self.allocation_strategy = AllocationStrategy( strategy=self.settings.allocation_strategy, min_equity=self.settings.allocation_min_equity, max_equity=self.settings.allocation_max_equity, target=self.settings.allocation_target, standard_retirement_age=( self.settings.allocation_std_retirement_age), risk_transition_period=self.settings.allocation_risk_trans_period, adjust_for_retirement_plan=( self.settings.allocation_adjust_retirement)) self.debt_payment_strategy = DebtPaymentStrategy( strategy=self.settings.debt_payment_strategy) self.tax_treatment = Tax( tax_brackets=self.settings.tax_brackets, personal_deduction=self.settings.tax_personal_deduction, credit_rate=self.settings.tax_credit_rate, inflation_adjust=self.scenario.inflation_adjust) # Now build some Ledger objects to test against: # A person making $10,000/yr self.person = Person(initial_year=self.initial_year, name="Test 1", birth_date="1 January 1980", retirement_date="31 December 2040", gross_income=10000, raise_rate=0, spouse=None, tax_treatment=self.tax_treatment) # An account with $1000 in it (and no interest) self.account = Account(owner=self.person, balance=1000) # A debt with a $100 balance (and no interest) self.debt = Debt(owner=self.person, balance=100) # Init a Forecaster object here for convenience: self.forecaster = self.forecaster_type(settings=self.settings)
def setUp_decimal(self): # pylint: disable=invalid-name """ Sets up mutable variables for each test call. """ # Set up constants: self.initial_year = 2000 self.constants = constants.ConstantsCanada(high_precision=Decimal) # Modify constants to make math easier: # Build some brackets with nice round numbers: self.constants.TAX_BRACKETS = { 'Federal': { self.initial_year: { Decimal(0): Decimal('0.1'), Decimal(100): Decimal('0.2'), Decimal(10000): Decimal('0.3') } }, 'BC': { self.initial_year: { Decimal(0): Decimal('0.25'), Decimal(1000): Decimal('0.5'), Decimal(100000): Decimal('0.75') } } } self.constants.TAX_PERSONAL_DEDUCTION = { 'Federal': { self.initial_year: Decimal('100') }, 'BC': { self.initial_year: Decimal('1000') } } self.constants.TAX_CREDIT_RATE = { 'Federal': { self.initial_year: Decimal('0.1') }, 'BC': { self.initial_year: Decimal('0.25') } } self.constants.TAX_PENSION_CREDIT = { 'Federal': { self.initial_year: Decimal('100') }, 'BC': { self.initial_year: Decimal('1000') } } # It's convenient (and accurate!) to use the same values # for the spousal amount and the personal deduction: self.constants.TAX_SPOUSAL_AMOUNT = ( self.constants.TAX_PERSONAL_DEDUCTION) # Build 100 years of inflation adjustments. growth_factor = Decimal(32) year_range = range(self.initial_year, self.initial_year + 100) self.inflation_adjustments = { year: 1 + (year - self.initial_year) / growth_factor for year in year_range } # Set to default province: self.province = 'BC' self.tax = TaxCanada(self.inflation_adjustments, province='BC', constants=self.constants) # Set up some people to test on: # Person1 makes $100,000/yr, has a taxable account with $500,000 # taxable income, and an RRSP with $500,000 in taxable income. self.person1 = Person(self.initial_year, "Tester 1", self.initial_year - 20, retirement_date=self.initial_year + 45, gross_income=100000) self.taxable_account1 = TaxableAccount(owner=self.person1, acb=0, balance=Decimal(1000000), rate=Decimal('0.05'), nper=1) self.taxable_account1.add_transaction(-Decimal(1000000), when='start') # NOTE: by using an RRSP here, a pension income tax credit will # be applied by TaxCanadaJurisdiction. Be aware of this if you # want to test this output against a generic Tax object with # Canadian brackets. self.rrsp = RRSP(self.person1, inflation_adjust=self.inflation_adjustments, contribution_room=0, balance=Decimal(500000), rate=Decimal('0.05'), nper=1, constants=self.constants) self.rrsp.add_transaction(-Decimal(500000), when='start') # Person2 makes $50,000/yr and has a taxable account with # $5000 taxable income. self.person2 = Person(self.initial_year, "Tester 2", self.initial_year - 18, retirement_date=self.initial_year + 47, gross_income=50000) self.taxable_account2 = TaxableAccount(owner=self.person2, acb=0, balance=Decimal(10000), rate=Decimal('0.05'), nper=1) self.taxable_account2.add_transaction(-Decimal(10000), when='start')
def setUp(self): """ Builds stock variables to test with. """ self.initial_year = 2000 # We will occasionally need to swap out subforecasts when # we want them to have no effect (e.g. no withdrawals because # we're not yet retired). Use null_forecast for that: self.null_forecast = DummyForecast(self.initial_year) # Paid $100 at the start of each month self.income_forecast_dummy = DummyForecast( self.initial_year, {when / 12: 100 for when in range(12)}) self.income_forecast_dummy.people = None # Spend $70 on living expenses at the start of each month self.living_expenses_forecast_dummy = DummyForecast( self.initial_year, {when / 12: -70 for when in range(12)}) # Contribute the balance ($30/mo, $360/yr): self.saving_forecast_dummy = DummyForecast( self.initial_year, {when + 1 / 12: -30 for when in range(12)}) # Withdraw $300 at the start and middle of the year: self.withdrawal_forecast_dummy = DummyForecast(self.initial_year, { 0: 300, 0.5: 300 }) # Refund for $100 next year: self.tax_forecast_dummy = DummyForecast(self.initial_year) self.tax_forecast_dummy.tax_adjustment = 100 self.tax_forecast_dummy.tax_refund_timing = Timing('start') # Also build a real ContributionForecast so that we can # test cash flows into accounts according to the overall # Forecast: # Simple tax rate: 50% on all income: tax = Tax(tax_brackets={self.initial_year: {0: 0.5}}) # One person, to own the account: timing = Timing(frequency='BW') self.person = Person(initial_year=self.initial_year, name="Test", birth_date="1 January 1980", retirement_date="31 December 2045", gross_income=5200, tax_treatment=tax, payment_timing=timing) # An account for savings to go to: self.account = Account(owner=self.person) # A strategy is required, but since there's only # one account the result will always be the same: self.strategy = TransactionTraversal(priority=[self.account]) self.saving_forecast = SavingForecast( initial_year=self.initial_year, retirement_accounts={self.account}, debt_accounts=set(), transaction_strategy=self.strategy) # Now assign `people`, `accounts`, and `debts` attrs to # appropriate subforecasts so that Forecast can retrieve # them: self.income_forecast_dummy.people = {self.person} self.saving_forecast_dummy.debt_accounts = set() self.withdrawal_forecast_dummy.accounts = {self.account} # Also add these to the null forecast, since it could be # substituted for any of the above dummy forecasts: self.null_forecast.people = self.income_forecast_dummy.people self.null_forecast.accounts = self.withdrawal_forecast_dummy.accounts self.null_forecast.debt_accounts = ( self.saving_forecast_dummy.debt_accounts) # Forecast depends on SubForecasts having certain properties, # so add those here: self.income_forecast_dummy.net_income = (sum( self.income_forecast_dummy.transactions.values())) self.living_expenses_forecast_dummy.living_expenses = (sum( self.living_expenses_forecast_dummy.transactions.values())) self.withdrawal_forecast_dummy.gross_withdrawals = (sum( self.withdrawal_forecast_dummy.transactions.values())) self.tax_forecast_dummy.tax_owing = 600 # Add the same properties to the null forecast, since it # could be substituted for any of the above: self.null_forecast.net_income = self.income_forecast_dummy.net_income self.null_forecast.living_expenses = ( self.living_expenses_forecast_dummy.living_expenses) self.null_forecast.gross_withdrawals = ( self.withdrawal_forecast_dummy.gross_withdrawals) self.null_forecast.tax_owing = self.tax_forecast_dummy.tax_owing # Finally, we need a Scenario to build a Forecast. # This is the simplest possible: 1 year, no growth. self.scenario = Scenario(self.initial_year, num_years=1)
def setUp_decimal(self): """ Builds stock variables to test with. """ # pylint: disable=invalid-name # Pylint doesn't like `setUp_decimal`, but it's not our naming # convention, so don't complain to us! # pylint: enable=invalid-name self.initial_year = 2000 # Simple tax treatment: 50% tax rate across the board. tax = Tax(tax_brackets={ self.initial_year: {Decimal(0): Decimal(0.5)}}, high_precision=Decimal) # Accounts need an owner: timing = Timing(frequency='BW',high_precision=Decimal) self.person = Person( initial_year=self.initial_year, name="Test", birth_date="1 January 1980", retirement_date="31 December 1999", # last year gross_income=Decimal(5200), tax_treatment=tax, payment_timing=timing, high_precision=Decimal) # We want at least two accounts which are withdrawn from # in different orders depending on the strategy. self.account = Account( owner=self.person, balance=Decimal(60000), # $60,000 <- BIGGER! high_precision=Decimal) self.rrsp = canada.accounts.RRSP( owner=self.person, contribution_room=Decimal(1000), balance=Decimal(6000), # $6,000 high_precision=Decimal) # Assume there are $2000 in inflows and $22,000 in outflows, # for a net need of $20,000: self.available = { Decimal(0.25): Decimal(1000), Decimal(0.5): Decimal(-11000), Decimal(0.75): Decimal(1000), Decimal(1): Decimal(-11000) } # Now we can set up the big-ticket items: self.strategy = TransactionStrategy( strategy=TransactionStrategy.strategy_ordered, weights={"RRSP": Decimal(1), "Account": Decimal(2)}) self.forecast = WithdrawalForecast( initial_year=self.initial_year, people={self.person}, accounts={self.account, self.rrsp}, transaction_strategy=self.strategy, high_precision=Decimal) # Set up another forecast for testing withholding behaviour: self.withholding_account = WithholdingAccount( owner=self.person, balance=Decimal(100000), high_precision=Decimal) self.withholding_strategy = TransactionStrategy( strategy=TransactionStrategy.strategy_ordered, weights={"WithholdingAccount": Decimal(1)}, high_precision=Decimal) self.withholding_forecast = WithdrawalForecast( initial_year=self.initial_year, people={self.person}, accounts={self.withholding_account}, transaction_strategy=self.withholding_strategy, high_precision=Decimal)
def setUp(self): self.initial_year = 2000 # Build 100 years of inflation adjustments with steadily growing # adjustment factors. First, pick a nice number (ideally a power # of 2 to avoid float precision issues); inflation_adjustment # will grow by adding the inverse (1/n) of this number annually growth_factor = 32 year_range = range(self.initial_year, self.initial_year + 100) self.inflation_adjustments = { year: 1 + (year - self.initial_year) / growth_factor for year in year_range } # For convenience, store the year where inflation has doubled # the nominal value of money self.double_year = self.initial_year + growth_factor # Build some brackets with nice round numbers: self.tax_brackets = {self.initial_year: {0: 0.1, 100: 0.2, 10000: 0.3}} # For convenience, build a sorted, type-converted array of # each of the tax bracket thresholds: self.brackets = sorted({ key: self.tax_brackets[self.initial_year][key] for key in self.tax_brackets[self.initial_year].keys() }) # For convenience in testing, build an accum dict that # corresponds to the tax brackets above. self.accum = {self.initial_year: {0: 0, 100: 10, 10000: 1990}} self.personal_deduction = {self.initial_year: 100} self.credit_rate = {self.initial_year: 0.1} self.tax = Tax(self.tax_brackets, inflation_adjust=self.inflation_adjustments, personal_deduction=self.personal_deduction, credit_rate=self.credit_rate) # Set up a simple person with no account-derived taxable income self.person = Person(self.initial_year, "Tester", self.initial_year - 25, retirement_date=self.initial_year + 40, gross_income=0) # Set up two people, spouses, on which to do more complex tests self.person1 = Person(self.initial_year, "Tester 1", self.initial_year - 20, retirement_date=self.initial_year + 45, gross_income=100000) self.person2 = Person(self.initial_year, "Tester 2", self.initial_year - 22, retirement_date=self.initial_year + 43, gross_income=50000) # Give the first person two accounts, one taxable and one # tax-deferred. Withdraw the entirety from the taxable account, # so that we don't need to worry about tax on unrealized growth: self.taxable_account1 = TaxableAccount(owner=self.person1, acb=0, balance=50000, rate=0.05, nper=1) self.taxable_account1.add_transaction(-50000, when='start') self.rrsp = RRSP(owner=self.person1, inflation_adjust=self.inflation_adjustments, contribution_room=0, balance=10000, rate=0.05, nper=1) # Employment income is fully taxable, and only half of capital # gains (the income from the taxable account) is taxable: self.person1_taxable_income = (self.person1.gross_income + self.taxable_account1.balance / 2) # Give the second person two accounts, one taxable and one # non-taxable. Withdraw the entirety from the taxable account, # so that we don't need to worry about tax on unrealized growth, # and withdraw a bit from the non-taxable account (which should # have no effect on taxable income): self.taxable_account2 = TaxableAccount(owner=self.person2, acb=0, balance=20000, rate=0.05, nper=1) self.taxable_account2.add_transaction(-20000, when='start') self.tfsa = TFSA(owner=self.person2, balance=50000, rate=0.05, nper=1) self.tfsa.add_transaction(-20000, when='start') # Employment income is fully taxable, and only half of capital # gains (the income from the taxable account) is taxable: self.person2_taxable_income = (self.person2.gross_income + self.taxable_account2.balance / 2)