Exemplo n.º 1
0
    def tax_person(self, person, year, deduction=Money(0), credit=Money(0)):
        """ Returns tax treatment for an individual person.

        Args:
            person (Person): A person for whom tax liability will be
                determined.
            year (int): The year for which tax treatment is needed.
            deduction (Money): A deduction to be applied against
                the person's income, on top of whatever other deductions
                they are eligible for, including the personal deduction
                for the year and any specific `tax_deduction` attribute
                values of the person and their accounts.
            credit (Money): A credit to be applied against the
                person's tax liability, on top of whatever other credits
                they are eligible for (provided via the `tax_deduction`
                member of `person` and any of their accounts).

        Returns:
            Money: The tax liability of the person.
        """
        taxable_income = person.taxable_income_history[year]
        deduction = (person.tax_deduction_history[year] +
                     self.deduction(person, year) + deduction)
        credit = (person.tax_credit_history[year] + self.credit(person, year) +
                  credit)
        return self.tax_money(taxable_income, year, deduction, credit)
Exemplo n.º 2
0
    def __init__(self,
                 owner=None,
                 balance=0,
                 rate=0,
                 nper=1,
                 default_timing=None,
                 inputs=None,
                 initial_year=None):
        """ Constructor for `Account`.

        This constructor receives only values for the first year.

        Args:
            owner (Person): The owner of the account. Optional.
            balance (Money): The balance for the first year
            rate (Decimal, callable): An object that gives the rate for
                each year, either as a constant value (e.g. a Decimal)
                or as a callable object with a signature of the form
                `rate(year) -> Decimal`.

                If this callable object relies on `Scenario` or other
                objects defined in the `forecaster` package, recommend
                passing an object that stores these objects explicitly
                as attributes (as opposed to a method/function where
                these objects are stored in the context), otherwise
                `Forecaster`'s object-substitution logic will not work.
            nper (int): The number of compounding periods per year.
            default_timing (Timing): The usual schedule for transactions
                to/from this account, used by various methods when no
                `timing` arg is expressly provided. Optional.
            initial_year (int): The first year (e.g. 2000)
        """
        # Use the explicitly-provided initial year if available,
        # otherwise default to the owner's initial year:
        if initial_year is None:
            if not hasattr(owner, 'initial_year'):
                raise TypeError(
                    'Account: owner must have initial_year attribute.')
            else:
                initial_year = owner.initial_year
        super().__init__(initial_year=initial_year, inputs=inputs)

        # Set hidden attributes to support properties that need them to
        # be set in advance:
        self._owner = None
        self._transactions = defaultdict(lambda: Money(0))
        self._rate_callable = None
        self._default_timing = None

        # Set the various property values based on inputs:
        self.owner = owner
        self.balance = Money(balance)
        self.rate_callable = rate
        self.nper = frequency_conv(nper)
        if default_timing is None:
            self.default_timing = Timing()
        else:
            self.default_timing = default_timing
Exemplo n.º 3
0
    def min_outflows(self,
                     timing=None,
                     transaction_limit=None,
                     balance_limit=None,
                     transactions=None,
                     **kwargs):
        """ The minimum outflows that should be withdrawn at `timings`.

        The output transaction values will be proportionate to the
        values of `timings`, which are used as weights.

        For a simple `Account`, this will return `Money(0)` for all
        timings. Subclasses can override it as appropriate.

        Args:
            timing (Timing): A mapping of `{when: weight}` pairs.
                Optional. Uses default_timing if not provided.
            transaction_limit (Money): Total outflows will not exceed
                this amount (not including any outflows already recorded
                against this `Account`). Optional.
            balance_limit (Money): At least this balance, if provided,
                will remain in the account at year-end. Optional.
            transactions (dict[Decimal, Money]): If provided, the result
                of this method will be determined as if the account
                also had these transactions recorded against it.

        Returns:
            dict[float, Money]: A mapping of `{when: value}` pairs where
                `value` indicates the amount that should be withdrawn at
                that time.
        """
        # pylint: disable=unused-argument
        # `kwargs` is provided so that subclasses can expand the
        # argument list without requiring client code to type-check.

        if balance_limit is None:
            # Outflows are limited by the account balance:
            balance_limit = Money(0)

        # Limit transactions to min total outflows, accounting for any
        # existing outflows already recorded as transactions:
        min_total = self.min_outflow_limit - self.outflows(transactions)
        # If a smaller limit is passed in, use that.
        # (Recall that outflows are negative, so we use max, not min)
        if transaction_limit is not None:
            min_total = max(min_total, transaction_limit)
        # Don't allow positive lower bounds:
        min_total = min(min_total, Money(0))

        # Ensure that only negative amounts are returned:
        max_total = Money(0)
        return self.transactions_to_balance(balance_limit,
                                            timing=timing,
                                            max_total=max_total,
                                            min_total=min_total,
                                            transactions=transactions)
Exemplo n.º 4
0
    def tax_money(self,
                  taxable_income,
                  year,
                  deduction=Money(0),
                  credit=Money(0)):
        """ Returns taxes owing on a given amount of taxable income.

        This method does not apply any deductions or credits other than
        what is passed to it. For example, the personal deduction is
        not applied (unless explicitly passed).

        In general, you should be calling `tax_person` if you want
        to have deductions and credits automatically applied.

        Args:
            taxable_income (Money): The amount of income to be taxed,
                assuming it's taxable in the hands of a single person
                with no other income and no deduction or credit other
                than those provided explicitly to this method.
            year (int): The year for which tax treatment is applied.
            deduction (Money): Any deduction from taxable income to be
                applied before determining total tax liability.
                Optional.
            credit (Money): Any tax credit to be applied against tax
                liability; these are applied at the tax credit rate for
                `year`. Optional.

        Returns:
            Money: Total tax liability arising from `taxable_income` in
                `year`, after applying `deduction` and `credit`.
        """
        # Apply deductions:
        taxable_income -= deduction

        # Get the inflation-adjusted tax brackets for this year:
        brackets = self.tax_brackets(year)
        bracket = self.marginal_bracket(taxable_income, year)

        # `accum` gives us the tax owing on lower brackers, so we only
        # have to think about the effect of the marginal rate on any
        # income over the bracket threshold
        marginal_rate = brackets[bracket]
        accum = self.accum(year, bracket)

        # Assess tax owing on marginal bracket:
        gross_tax = accum + (taxable_income - bracket) * marginal_rate
        # Apply tax credts:
        net_tax = gross_tax - credit * self.credit_rate(year)
        # Assume credits are non-refundable:
        return max(net_tax, Money(0))
Exemplo n.º 5
0
    def __init__(self,
                 owner,
                 balance=0,
                 rate=0,
                 nper=1,
                 inputs=None,
                 initial_year=None,
                 default_timing=None,
                 acb=None,
                 **kwargs):
        """ Constructor for `TaxableAccount`.

        See documentation for `Account` for information on args not
        listed below.

        Args:
            acb (Money): The adjusted cost base of the assets in the
                account at the start of `initial_year`.
        """
        # This method does have a lot of arguments, but they're mostly
        # inherited from a superclass. We're stuck with them here.
        # pylint: disable=too-many-arguments

        super().__init__(owner=owner,
                         balance=balance,
                         rate=rate,
                         nper=nper,
                         inputs=inputs,
                         initial_year=initial_year,
                         default_timing=default_timing,
                         **kwargs)

        # If acb wasn't provided, assume there have been no capital
        # gains or losses, so acb = balance.
        self.acb = Money(acb if acb is not None else self.balance)
Exemplo n.º 6
0
 def max_outflow(self, when="end"):
     """ The maximum amount that can be withdrawn at `when`. """
     return max(
         # Withdraw everything (or none if the balance is negative)
         min(-self.balance_at_time(when), Money(0)),
         # But no more than the maximum outflow:
         self.max_outflow_limit)
Exemplo n.º 7
0
 def tax_withheld(self):
     if self.tax_treatment is not None:
         # pylint: disable=not-callable
         # We test that tax_treatment is callable in its setter.
         return self.tax_treatment(self.gross_income, self.this_year)
     else:
         return Money(0)
Exemplo n.º 8
0
    def raise_rate_callable(self, val):
        """ Sets raise_rate_function. """
        # Treat setting the method to None as reverting to the default
        # rate parameter, which is Money(0).
        if val is None:
            self.raise_rate_callable = Money(0)
        # Is raise_rate isn't callable, convert it to a suitable method:
        if not callable(val):  # Make callable if dict or scalar
            if isinstance(val, dict):
                # assume dict of {year: raise} pairs
                def func(year):
                    """ Wraps dict in a function """
                    return val[year]
            else:
                # If we can cast this to Decimal, return a constant rate
                val = Decimal(val)

                def func(_=None):
                    """ Wraps value in a function with an optional arg. """
                    return val

            self._raise_rate_callable = func
        else:
            # If the input is callable, use it without modification.
            self._raise_rate_callable = val
Exemplo n.º 9
0
 def outflows(self, transactions=None):
     """ The sum of all outflows from the account. """
     if transactions is not None:
         result = sum(val for val in transactions.values() if val < 0)
     else:
         result = Money(0)
     result += sum(val for val in self.transactions.values() if val < 0)
     return result
Exemplo n.º 10
0
    def max_inflow_limit(self):
        """ The maximum amount that can be contributed to the account.

        This method uses the same semantics as `max_outflow`, except
        for inflows.
        """
        # For an ordinary Account, there is no limit on contributions.
        return Money('Infinity')
Exemplo n.º 11
0
    def min_outflow_limit(self):
        """ The minimum amount to be withdrawn from the account.

        This method uses the same semantics as `max_outflow`, except
        it provides the minimum.
        """
        # For an ordinary Account, there is no minimum withdrawal
        return Money('0')
Exemplo n.º 12
0
    def min_inflow_limit(self):
        """ The minimum amount to be contributed to the account.

        This method uses the same semantics as `max_outflow`, except
        it provides the minimum for inflows.
        """
        # For an ordinary Account, there is no minimum contribution.
        return Money('0')
Exemplo n.º 13
0
    def tax_people(self, people, year, deduction=None, credit=None):
        """ Total tax liability for a group of people.

        The people do not necessarily need to be spouses or related in
        any way. If a pair of spouses is in `people`, they will be
        passed to `tax_spouses` to be processed together.

        Args:
            people (iterable[Person]): Any number of people.
            year (int): The year for which tax treatment is needed.
            deduction (dict[Person, Money]): A dict of
                `{person: deduction}` pairs. Optional.
            credit (dict[Person, Money]): A dict of `{person: credit}`
                pairs. Optional.

        Returns:
            Money: The total tax liability of the people.
        """
        if deduction is None:
            deduction = {}
        if credit is None:
            credit = {}

        # Base case: If {} is passed, return $0.
        if not people:
            return Money(0)

        # Otherwise, grab someone at random and determine their taxes.
        person = next(iter(people))

        # Treat spouses in a special way; send them to a different
        # method for processing and recurse on the remaining folks.
        # NOTE: This logic (and the logic of Person.spouse) needs to be
        # overridden in subclasses implementing plural marriage.
        if person.spouse is not None and person.spouse in people:
            # Process taxes for the couple and recurse on the remaining
            # people.
            return (self.tax_spouses({person, person.spouse}, year, deduction,
                                     credit) +
                    self.tax_people(people - {person, person.spouse}, year,
                                    deduction, credit))
        # Otherwise, process this person as a single individual and
        # recurse on the remaining folks:
        else:
            # We don't want to override default values for tax_person,
            # so fill a kwargs dict with only explicitly-passed
            # deduction and credit for this person.
            kwargs = {}
            if person in deduction:
                kwargs['deduction'] = deduction[person]
            if person in credit:
                kwargs['credit'] = credit[person]

            # Determine tax owing for this person and then recurse.
            return (
                self.tax_person(person, year, **kwargs) +
                self.tax_people(people - {person}, year, deduction, credit))
Exemplo n.º 14
0
 def max_inflow(self, when="end"):
     """ The maximum amount that can be contributed at `when`. """
     # Max you can contribute is the lesser of: the limit and the
     # remaining balance.
     return min(
         # Repay the whole balance (or none if positive)
         max(-self.balance_at_time(when), Money(0)),
         # But no more than the maximum outflow:
         self.max_inflow_limit)
Exemplo n.º 15
0
    def __init__(
            # Inherited args:
            self,
            owner,
            balance=0,
            rate=0,
            nper=1,
            inputs=None,
            initial_year=None,
            # New args:
            minimum_payment=Money(0),
            accelerated_payment=Money('Infinity'),
            default_timing=None,
            **kwargs):
        """ Constructor for `Debt`. """

        # The superclass has a lot of arguments, so we're sort of stuck
        # with having a lot of arguments here (unless we hide them via
        # *args and **kwargs, but that's against the style guide for
        # this project).
        # pylint: disable=too-many-arguments

        # Declare hidden variables for properties:
        self._minimum_payment = None
        self._accelerated_payment = None

        # Apply generic Account logic:
        super().__init__(owner,
                         balance=balance,
                         rate=rate,
                         nper=nper,
                         inputs=inputs,
                         initial_year=initial_year,
                         default_timing=default_timing,
                         **kwargs)

        # Set up (and type-convert) Debt-specific inputs:
        self.minimum_payment = minimum_payment
        self.accelerated_payment = accelerated_payment

        # Debt must have a negative balance
        if self.balance > 0:
            self.balance = -self.balance
Exemplo n.º 16
0
    def add_brackets(self, brackets, year):
        """ Adds a year of tax brackets to attribute self.tax_brackets.

        Also generates an `accum` dict based on the tax brackets.
        """
        year = int(year)
        # Enforce types for the new brackets (We'll reuse this short
        # name later when building the accum dict for convenience)
        brackets = {Money(key): Decimal(brackets[key]) for key in brackets}
        self._tax_brackets[year] = brackets

        self.add_accum(brackets, year)
Exemplo n.º 17
0
    def _contribution_room_accrual(self, year):
        """ The amount of contribution room accrued in a given year.

        This excludes any rollovers - it's just the statutory accrual.
        """
        # No accrual if the owner is too young to qualify:
        if self.owner.age(year + 1) < constants.TFSA_ELIGIBILITY_AGE:
            return Money(0)

        # If we already have an accrual rate set for this year, use that
        if year in constants.TFSA_ANNUAL_ACCRUAL:
            return Money(constants.TFSA_ANNUAL_ACCRUAL[year])
        # Otherwise, infer the accrual rate by inflation-adjusting the
        # base rate and rounding.
        else:
            return Money(
                round(
                    self._base_accrual * self.inflation_adjust(
                        self._base_accrual_year, year) /
                    constants.TFSA_ACCRUAL_ROUNDING_FACTOR) *
                constants.TFSA_ACCRUAL_ROUNDING_FACTOR
            )
Exemplo n.º 18
0
 def gross_income(self):
     """ The `Person`'s gross income for this year. """
     # No income if retired, otherwise apply the raise rate to last
     # year's income:
     if (self.retirement_date is not None
             and self.retirement_date.year < self.this_year):
         return Money(0)
     else:
         return (
             # Pylint gets confused by attributes added by metaclass.
             # pylint: disable=no-member
             self._gross_income_history[self.this_year - 1] *
             (1 + self.raise_rate))
Exemplo n.º 19
0
    def __init__(self, strategy, base_amount=0, rate=0, inflation_adjust=None):
        """ Constructor for LivingExpensesStrategy. """
        super().__init__(strategy)

        self.base_amount = Money(base_amount)
        self.rate = Decimal(rate)

        # If no inflation_adjustment is specified, create a default
        # value so that methods don't need to test for None
        if inflation_adjust is not None:
            self.inflation_adjust = inflation_adjust
        else:
            self.inflation_adjust = lambda *args, **kwargs: 1
Exemplo n.º 20
0
 def minimum_distribution(self):
     """ A min. amount required by law to be withdrawn based on age. """
     # Minimum withdrawals are required the year after converting to
     # an RRIF. How it is calculated depends on the person's age.
     if self.rrif_conversion_year < self.this_year:
         age = self.contributor.age(self.this_year)
         if age in constants.RRSP_RRIF_WITHDRAWAL_MIN:
             return constants.RRSP_RRIF_WITHDRAWAL_MIN[age] * self.balance
         elif age > max(constants.RRSP_RRIF_WITHDRAWAL_MIN):
             return self.balance * \
                 max(constants.RRSP_RRIF_WITHDRAWAL_MIN.values())
         else:
             return self.balance / (90 - age)
     else:
         return Money(0)
Exemplo n.º 21
0
 def __init__(self, initial_year, default_timing=None):
     """ Initializes an instance of SubForecast. """
     # Invoke Ledger's __init__ or pay the price!
     # NOTE Issue #53 removes this requirement
     super().__init__(initial_year)
     # Use default Timing (i.e. lump sum contributions at the
     # midpoint of the year) if none is explicitly provided:
     if default_timing is None:
         self.default_timing = Timing()
     else:
         self.default_timing = default_timing
     # We store transactions to/from each account so that we can
     # unwind or inspect transactions caused by this subforecast
     # later. So we store it as `{account: {when: value}}`.
     # Since `account` can be a dict (which is non-hashable),
     # we use a custom subclass of defaultdict that allows
     # non-hashable keys.
     self._transactions = TransactionDict(
         lambda: defaultdict(lambda: Money(0)))
     # If the subforecast is called more than once, we
     # may want to do some unwinding. Use this to track
     # whether this subforecast has been called before:
     self._call_invoked = False
     self.total_available = Money(0)
Exemplo n.º 22
0
    def __init__(self, initial_year, people, accounts, transaction_strategy):
        """ Initializes an instance of WithdrawalForecast. """
        # Recall that, as a Ledger object, we need to call the
        # superclass initializer and let it know what the first
        # year is so that `this_year` is usable.
        # NOTE: Issue #53 removes this requirement.
        super().__init__(initial_year)

        # Store input values
        self.people = people
        self.accounts = accounts
        self.transaction_strategy = transaction_strategy

        self.account_transactions = {}
        self.tax_withheld = Money(0)
Exemplo n.º 23
0
    def next_contribution_room(self):
        """ Determines the amount of contribution room for next year.

        Args:
            income (Money): The amount of taxable income for this year
                used to calculate RRSP contribution room.
            year (int): The year in which the income is received.

        Returns:
            The contribution room for the RRSP for the year *after*
            `year`.
        """
        year = self.this_year

        if self.contributor.age(year + 1) > constants.RRSP_RRIF_CONVERSION_AGE:
            # If past the mandatory RRIF conversion age, no
            # contributions are allowed.
            return Money(0)
        else:
            # TODO: Add pension adjustment?

            # Contribution room is determined based on the contributor's
            # gross income for the previous year.
            income = self.contributor.gross_income_history[self.this_year]

            # First, determine how much more contribution room will
            # accrue due to this year's income:
            accrual = income * constants.RRSP_ACCRUAL_RATE
            # Second, compare to the (inflation-adjusted) max accrual
            # for next year:
            max_accrual = extend_inflation_adjusted(constants.RRSP_ACCRUAL_MAX,
                                                    self.inflation_adjust,
                                                    year + 1)
            # Don't forget to add in any rollovers:
            rollover = self.contribution_room - self.inflows()
            return min(accrual, Money(max_accrual)) + rollover
Exemplo n.º 24
0
 def add_accum(self, brackets, year):
     """ Generates an accum dict for the given brackets and year. """
     # For each bracket, the new accumulation is whatever the accum
     # is for the next-lowest bracket (which itself is the
     # accumulation of all lower brackets' tax owing), plus the
     # marginal rate of that bracket applied to the full taxable
     # income within its range.
     prev = min(brackets)  # We need to look at 2 brackets at a time
     self._accum[year] = {prev: Money(0)}  # Accum for lowest bracket
     iterator = sorted(brackets.keys())  # Look at brackets in order
     iterator.remove(prev)  # Lowest bracket is already accounted for.
     for bracket in iterator:
         self._accum[year][bracket] = \
             (bracket - prev) * brackets[prev] + self._accum[year][prev]
         prev = bracket  # Keep track of next-lowest bracket
Exemplo n.º 25
0
    def __init__(self,
                 initial_year,
                 name,
                 birth_date,
                 retirement_date=None,
                 gross_income=0,
                 raise_rate=0,
                 spouse=None,
                 tax_treatment=None,
                 payment_timing=None,
                 inputs=None):
        """ Initializes a `Person` object. """
        super().__init__(initial_year=initial_year, inputs=inputs)

        # For simple, non-property-wrapped attributes, assign directly:
        self.name = name
        if payment_timing is None:
            # Timing is technically mutable, so init it here rather than
            # using "Timing()" as a default value.
            self.payment_timing = Timing()
        else:
            self.payment_timing = Timing(payment_timing)

        # For attributes wrapped by ordinary properties, create hidden
        # attributes and assign to them using the properties:
        self._birth_date = None
        self._retirement_date = None
        self._raise_rate_callable = None
        self._spouse = None
        self._tax_treatment = None
        self._contribution_room = {}
        self._contribution_groups = {}
        self.birth_date = birth_date
        self.retirement_date = retirement_date
        self.raise_rate_callable = raise_rate
        self.spouse = spouse
        self.tax_treatment = tax_treatment

        # Now provide initial-year values for recorded properties:
        # NOTE: Be sure to do type-checking here.
        self.gross_income = Money(gross_income)
        # NOTE: Be sure to set up tax_treatment before calling tax_withheld
        self.net_income = self.gross_income - self.tax_withheld

        # Finally, build an empty set for accounts to add themselves to
        # and a `data` dict for accounts to write unstructed data to.
        self.accounts = set()
        self.data = {}
Exemplo n.º 26
0
    def max_outflow_limit(self):
        """ The maximum amount that can be withdrawn from the account.

        This property provides a scalar value representing the largest
        possible outflow for this account, ignoring timing or the
        current balance. For example, if there is no limit on outflows,
        this returns Money('Infinity'), regardless of the balance.

        In most cases you probably want to determine the maximum
        amount that can actually be withdrawn at the current balance.
        For that, use `max_outflows`.

        Returns:
            The maximum value that can be withdrawn from the account.
        """
        # For an ordinary Account, there is no limit on withdrawals.
        return Money('-Infinity')
Exemplo n.º 27
0
    def tax_spouses(self, people, year, deduction=None, credit=None):
        """ Tax treatment for a pair of spouses.

        This method doesn't provide any special tax treatment to the
        spouses, but it allows subclasses to override its functionality
        to apply special tax treatments.

        This method is also not restricted to two-element inputs,
        although in countries like Canada (which only recognizes two-
        person marriages for tax purposes) this method should only ever
        receive a two-element `people` input. By allowing for arbitrary-
        size inputs, subclasses can extend this functionality to deal
        with countries where plural marriage is recognized by the tax
        system.

        Args:
            people (iterable): Persons with mutual spousal
                relationships. In countries requiring monogamous
                marriages, this input should have exactly two elements.
            year (int): The year for which tax treatment is needed.
            deduction (dict[Person, Money]): A dict of
                `{person: deduction}` pairs. Optional.
            credit (dict[Person, Money]): A dict of `{person: credit}`
                pairs. Optional.

        Returns:
            Money: The tax liability of the spouses.
        """
        # Avoid using {} as a default value in the call signature:
        if deduction is None:
            deduction = {}
        if credit is None:
            credit = {}

        # Add together the tax treatment for each spouse, without doing
        # anything special (this is essentially the same logic as
        # tax_person, but without the check for spouses or recursion)
        tax = Money(0)
        for person in people:
            kwargs = {}
            if person in deduction:
                kwargs['deduction'] = deduction[person]
            if person in credit:
                kwargs['credit'] = credit[person]
            tax += self.tax_person(person, year, **kwargs)
        return tax
Exemplo n.º 28
0
    def next_year(self):
        """ Adds another year to the account.

        This method will call the next_year method for the owner if they
        haven't been advanced to the next year.
        """
        # Ensure that the owner has been brought up to this year
        if self.owner is not None:
            while self.owner.this_year < self.this_year:
                self.owner.next_year()

        # Now increment year via superclass:
        super().next_year()

        # Clear out transactions for the new year:
        # (We assign a new defaultdict because the old dict is
        # stored by the `transactions` recorded_property; invoking
        # `clear` will affect past-year records.)
        self._transactions = defaultdict(lambda: Money(0))
Exemplo n.º 29
0
 def __call__(self,
              *args,
              people=None,
              year=None,
              retirement_year=None,
              **kwargs):
     """ Returns the living expenses for the year. """
     # Collect the accounts owned by `people` into a flat
     # `set[Account]` object:
     if people is not None:
         accounts = set.union(*[person.accounts for person in people])
     else:
         accounts = None
     # Determine how much to spend on living expenses:
     living_expenses = super().__call__(people=people,
                                        year=year,
                                        accounts=accounts,
                                        retirement_year=retirement_year,
                                        *args,
                                        **kwargs)
     # Ensure we return non-negative value:
     return max(living_expenses, Money(0))
Exemplo n.º 30
0
    def __init__(self, *args, rrif_conversion_year=None, **kwargs):
        """ Initializes an RRSP object.

        This class also implements RRIFs (which RRSPs are converted into
        either at a user-defined time or by operation of law). A new
        object is not created when the RRSP converts to an RRIF; rather,
        the object's behaviour changes to limit inflows, require
        minimum withdrawals, and reduce withholding taxes.

        See documentation for `RegisteredAccount` for information on
        args not listed below.

        Args:
            rrif_conversion_year (int): The year in which the `RRSP`
                object's behaviour switches from RRSP rules to RRIF
                rules.
        """
        super().__init__(*args, **kwargs)

        # Although `person` might provide a retirement_age, the RRSP
        # won't necessarily be turned into an RRIF at the retirement
        # date (depending on withdrawal strategy).
        # TODO: Allow RRIF_conversion_year to be passed as an argument?
        # We could use the below convert-at-71 logic if None is passed.
        # TODO: Automatically trigger RRIF conversion after outflow?
        # (Perhaps control this behaviour with an arg?)

        self._rrif_conversion_year = None
        self.rrif_conversion_year = rrif_conversion_year

        # Determine the max contribution room accrual in initial_year:
        self._initial_accrual = extend_inflation_adjusted(
            constants.RRSP_ACCRUAL_MAX, self.inflation_adjust,
            self.initial_year)

        # If no contribution room was provided, set it to $0.
        if self.contribution_room is None:
            self.contribution_room = Money(0)