示例#1
0
 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))
示例#2
0
    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)
示例#3
0
    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)
示例#4
0
 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)
示例#5
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')
示例#6
0
 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})
示例#7
0
    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)
示例#8
0
    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)
示例#9
0
    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)
示例#10
0
 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())
示例#11
0
 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]
示例#12
0
    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)
示例#13
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, Money(0))
示例#14
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())
示例#15
0
    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)
示例#16
0
    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)
示例#17
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)
示例#18
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))
示例#19
0
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)
示例#20
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)
示例#21
0
    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)
示例#22
0
    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')
示例#23
0
    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)
示例#24
0
    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)
示例#25
0
    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)