End Game - Personal Finance Simulator for Retired Canadians

There's no guarantee that this tool outputs correct results. Use with caution!

The source code is here.

The instructions for running the code are here.

Scenario File

Overview

The main purpose of this project is:

This tool is focused almost entirely on the retirement phase of life (spending), not on the working phase of life (saving). It can be useful even if you aren't close to retirement, by letting you explore how cash flow works in retirement.

The main steps in using this tool:

An example chart made with Open Office (cash-and-tax-summary.csv):

An example chart made with Google Sheets (cash-flows.csv):

A second example chart made with Google Sheets. This chart shows the scatter in net cash flow when a certain policy for selling shares reacts to stock prices generated using a probability distribution. In this case, the simulation has been run 20 times by the system, with stock prices being different each time. After 2027, the points below the main body of points are years in which stock prices were low and no stocks were sold, resulting in lower cash flow:

The level of detail in this tool is higher than usual. Whereas most tools focus on a high-level rate of return, this tool actually goes through every single day of your retirement, and applies transactions to your accounts. It includes a basic tax return at the end of each year.

Every retirement financial plan should be supported by a robust simulation showing you the details. Personal finance is quite complex. There are many moving parts, and how those parts all work together in your particular case can be found only with a simulator. You can't do these calculations in your head, and you shouldn't rely entirely on rules of thumb, because such rules usually have exceptions.

This tool has:

Limitations

This tool is currently restricted in scope: In spite of these limitations, you may still find it useful.

Features

Features include:

Some items that aren't modeled explicitly can still be effectively modeled by the system. For example, the sale of a primary residence can be modeled as a simple windfall deposited to your bank account, since no tax is paid on the capital gain when you sell your home. From there, the cash can be transferred into an investment account to buy securities.

There are two distinct types of scenarios:

Examples of approximations made by the tool:

Example Scenarios

Example scenarios are included in the source code. They are listed here in rough order of decreasing complexity.

When creating your own scenario, it's always best to start with a working example. Then you gradually change the scenario, step by step, to get closer to your own case. As you make a small edit, you save your changes and test them by running the tool, to see if everything still works. You keep repeating this until you get to your desired final scenario. This is the safest way of proceeding.

Note that the .ini file is the scenario file, and the remaining files in the directory are outputs that result from executing the scenario. The provincial tax return is for PE, Prince Edward Island, simply because that's where the author resides. The tool allows for all other provinces as well.

IDDescription
08.2Fixed 3% growth in stock prices. Liquidate rif-nra-tfsa 6-3% when 65+. Top up TFSA first with RIF, then NRA. PE.
08.1Fixed 3% growth in stock prices. Liquidate rif-nra-tfsa 6-3% when 65+. Top up TFSA first with NRA, then RIF. PE.
07.3GOC large pension 5483/month, no other savings, house unsold, retires at 65. Average CPP 673/month, OAS/GIS. PE. Deterministic.
07.2Govt of Canada pension 2234.00/month, no other savings, house unsold, retires at 65. Average CPP 673/month, OAS/GIS. PE. Deterministic.
07.1Pension 2000 monthly, no other savings, house unsold, retires at 65. Average CPP 673/month, OAS/GIS. PE. Deterministic.
03.1Dividends only, never sell stocks. 3% dividend growth, 3% stock price growth.
02.3Only a RIF with 100k in blue-chip dividends. Half the average CPP. 3% dividend growth, 3% stock price growth.
02.4Only a NRA with 100k in blue-chip dividends. Half the average CPP. 3% dividend growth, 3% stock price growth.
02.2Only a TFSA with 100k in blue-chip dividends. Half the average CPP. 3% dividend growth, 3% stock price growth.
02.1Only a TFSA with 50k in blue-chip dividends. Half the average CPP. 3% dividend growth, 3% stock price growth.
01.4 Worker with no savings, retires at 65. CPP max monthly 1175.75, OAS/GIS. PE. Deterministic.
01.3Worker with no savings, retires at 65. Average CPP 673/month, OAS/GIS, PE. Deterministic.
01.2Worker with no savings, retires at 65. CPP 300/month, OAS/GIS. PE. Deterministic.
01.1Never worked, no savings, retires at 65. OAS/GIS, rent 700. PE. Deterministic.

General Syntax

The scenario file has a specific syntax. That is, the text in the scenario file has to make sense to the code. You have to follow its rules, or an error will result, and the code will stop executing. If you have a syntax error, then an error message will tell you the exact location of the error in the scenario file (using a line number).

It's almost always best to start with an example scenario file that is closest to your situation. Then you gradually modify it, one piece at a time.

Note that:

The on statement

There's a special on syntax for controlling when transactions are applied, and it occurs in various places. Examples:

bank-withdrawal 925.00 on *-01
This means that 925.00 is withdrawn from your bank account on the first of every single month.
on 2027-11-25
This means on that specific day, and no other.
on *-11-25
This means on every November 25th.
on *-03-01,*-06-01,*-09-01,*-12-01
This means on every March 1, June 1, September 1, and December 1 (quarterly dividend payments, for example).
sell 6.0% on *-11-26 | 2027-01-01..2034-12-31
Executed every November 26, in the years 2027 to 2034. When the "|" appears, it adds start-date, end-date, or both. There are 3 variations
# start-date and end-date:
sell 6.0% on *-11-26 | 2027-01-01..2034-12-31  
# only start-date:
sell 6.0% on *-11-26 | 2027-01-01..           
 # only end-date:
sell 6.0% on *-11-26 | ..2034-12-31
An exception to the above is the purchase of a GIC, which always uses a specific date. In this case, the ":" character is used instead of the on statement:
buy-gic rif "Cda Trust" 5700.00 2.5% 1-year : 2024-06-21

Description of Snippets

The following are examples of the various parts that can be found in a scenario file. They appear here in the order that they are allowed to appear in the file.

Some examples have various alternatives. One of the alternatives will be the active one, while the others are all commented out because the line starts with a '#' sign. This lets you quickly change alternatives, by commenting out everything except the one you really want.

Outside of the transactions block, items are usually required. Any optional items are indicated as such below.

syntax-version = v1.0.0
States that this scenario file was created with a given version of the syntax for scenario files (which may change over time). The idea is to ensure that code processing a scenario file knows the exact syntax of the file it's supposed to process. The code is not smart enough to be backwards-compatible with "old" versions of the syntax.

description = "101.6: Fixed 3% growth in stock prices..."
A free-form, high-level description of important aspects of the scenario.

number-of-iterations = 1
Usually 1. You change it to more than 1 when you want to repeat the scenario, from start to finish, multiple times. This only makes sense if the various iterations have different results. For example, when stock prices are generated using randomness, running multiple iterations makes logical sense, to see the general spread in the resulting outputs. When greater than 1, the generated .csv files are completely different.

simulation-start-date = 2022-01-01
simulation-end-date = 2042-12-31
The start and end dates for running the simulation. If multiple iterations are run, then each iteration starts with the start date. Optionally, there can also be a test to see if the person has died at the end of the year. If that is activated, then the simulation will often terminate before the end date is reached. (See below.)

date-of-birth = 1962-05-21
annual-test-for-survival
sex = male
The person's date of birth is used for various calculations. The annual-test-for-survival is optional. If annual-test-for-survival is included, then at the end of each simulated year, a statistical test is performed by the system, to see if the person will survive into the next year. If they fail to survive the test, then they die, and the simulation/iteration is terminated. This feature uses life-tables from Statistics Canada, and those tables are different between males and females.

year-zero-amounts {
  net-income-before-adjustments = 15180.00  # line 23400
  net-income = 15180.00 # line 23600
  oas-income = 0.00
  employment-income = 0.00  
}
The simulation starts in the middle of life, and some items in the tax return need to refer to the previous year. For the first year in the simulation, there is no data for the previous year, so this data fills that gap.

oas {
  monthly-amount-at-65 = 648.67 # as of 2022-06
  start-month = 2027-06  # month of first payment 
  payment-day = 28 # day of the month; never more than 28
  
  clawback-threshold = 81761.00 # line 23400 of tax return
  clawback-percent = 15%
  monthly-reward = 0.7% # for every month delayed past 65
  boost-age = 75 # the age at which OAS is boosted by 10%
  boost-percent = 10%
  start-window-begin = 65 
  start-window-end = 70
  gis-employment-exemption = 5000.00
}
The data used to calculate the OAS (Old Age Security) benefit. The start-month is especially important. The earliest start-month is the month AFTER you turn 65. The latest start-month is the month AFTER you turn 70.

For both OAS and CPP, there are (in a sense) two start-months. One is used to calculate the amount of the payment, and the other (1 month later) is the month you actually receive the first payment.

cpp { 
  # can be used for QPP as well, since they 
  # have nearly the same core logic
  nominal-monthly-amount = 850.00 # if starts at age 65
  start-month = 2022-06           # month of 1st payment 
  payment-day = 28   # day of the month
  
  monthly-reward = 0.7%   # every month delayed past 65
  monthly-penalty = 0.6%  # every month started before 65
  start-window-begin = 60 
  start-nominal = 65
  start-window-end = 70
  survivor-benefit-amount = 1000.00    # optional
  survivor-benefit-start = 2030-02-25  # optional
}
The data used to calculate your CPP benefit. The nominal-monthly-amount and the start-month are especially important. The start-month is often the month AFTER you turn 65. The earliest start-month is the month AFTER you turn 60. The latest start-month is the month AFTER you turn 70. The two survivor items are optional, and come as a pair. In this simplified implementation, an optional survivor benefit is paid only if you are already receiving regular CPP payments. In that case, your CPP payment can be increased by a survivor benefit.

rif-minimum-withdrawals {
 # Applied to the market value of the RIF at 
 # the start of each year.
 # The left-hand side is your age on Jan 1. 
 # For ages outside this range, a formula applies
 71 = 5.28%
 72 = 5.40%
 ...elided for brevity...
 93 = 16.34%
 94 = 18.79%
}
Optional. RIF accounts always have a minimum annual withdrawal. For some ages, a formula applies; for others, this table is used. Reference: here.

lif-maximum-withdrawals {
  # WARNING: should be updated every year.
  # Applied to the market value of the LIF at the 
  # start of each year. No max for PE.
  # AGE, RIF-MIN, CA, MN-QC-NS, AB-BC-ON-NB-NL-SK
  55 2.86% 4.53% 6.40% 6.51%
  56 2.94% 4.58% 6.50% 6.57%
  57 3.03% 4.64% 6.50% 6.63%
  ...elided for brevity...
  93 16.34% 100.00% 20.00% 100.00%
  94 18.79% 100.00% 20.00% 100.00%
  95 20.00% 100.00% 20.00% 100.00%
  # older: just use the last row
}
Optional. LIF accounts always have a maximum annual withdrawal. Reference: link.

federal-tax {
  # Tax return settings.
  # These numbers are for 2021. 
  # Update them each year.
  # Must agree with simulation-start-date: 
  initial-year = 2022 
  personal-amount = 12241.00  # Worksheet for line 30000
  personal-amount-additional = 1387.00
  personal-amount-threshold = 151978.00_216511.00
  age-amount = 7713.00 # Worksheet line 30100
  age-amount-threshold = 38893.00
  pension-income-max = 2000.00 # line 31400
  tax-brackets {
    15.00% : 49020.00    
    20.50% : 98040.00    
    26.00% : 151978.00   
    29.00% : 216511.00   
    33.00% : 100000000.00
  }
  rif-withholding-tax-brackets {
    # these amounts are above the rif's 
    # minimum withdrawal for that year
    10.00% : 5000.00    
    20.00% : 15000.00    
    30.00% : 100000000.00
  }
  standard-retirement-age = 65
  taxable-capital-gain-fraction = 0.5D
  dividend-tax-credit-numer = 6 
  dividend-tax-credit-denom = 11 
}
The federal part of a tax return. The numbers need to be updated yearly. The initial-year must agree with the start-date entered above, near the start of the scenario file.

provincial-tax {
  # Usually there are significant differences 
  # between jurisdictions!
  # Each jurisdiction has its own set of fields.
  # Numbers from 2021 
  jurisdiction = PE 
  tax-brackets {
    9.8% : 31984.00    
    13.8% : 63969.00    
    16.7% : 100000000.00
  }
  personal-amount = 10500.00 # line 58040
  age-amount = 3764.00 # line 58080
  age-amount-threshold = 28019.00
  pension-income-max = 1000.00 # line 58360
  dividend-gross-up-mult = 10.5% # line 61520 worksheet
  low-income-basic = 350.00 # line 63370 
  low-income-age = 250.00 # line 63380
  low-income-threshold = 19000.00 # line 85
  low-income-rate = 5.00%
}
An example of a provincial tax return (for PEI). The numbers have to be reviewed each year. See the input\provincial-tax directory for snippets appropriate to each province. It's important to realize that different provinces have different data in this block. Reference: link.

stocks {
  # Initial data on stocks
  # Not related to an account.
  # All stocks in the sim must be defined here, 
  # even those you don't own at the start.
  "ABC" {   
    price = 56.00
    dividend {
      on *-03-01,*-06-01,*-09-01,*-12-01
      amount = 0.86
      growth = 4.0%
    }
  }
  "XYZ" {
    price = 140.00
    dividend {
     on *-01-28,*-04-28,*-07-28,*-10-28
     amount = 1.61
     growth = 4.0%
    }
  }
}
Optional. Data for all stocks that will be part of the simulation. The ticker symbol is used to identify the stock. The annual growth in the dividend is always modeled here simply as a fixed percentage. In the above examples, the dividends pay quarterly, on the given month-day combinations.

#percent of the gross amount: 
stock-commission = fixed-percent 1.0%     
# some discount brokers have a flat rate:
# stock-commission = fixed-amount 10.50
# requires custom coding:    
# stock-commission = custom-schedule
Optional. How to calculate the commission paid to your investment dealer when trading stocks. The custom-schedule is to let you reflect you investment dealer's actual commission schedule. It requires that you do some coding, by implementing the CustomCommish class.

stock-price-policy {
  on *-11-25 # when stock prices are to be updated
  # gaussian mean = 5.0%  std-dev = 5.0% 
  # range = -5%_10%
  fixed = 3% # no randomness here
  # in order of year; recycles if end is reached:
  # explicit-list = 5%,5%,5%,5%,5%,-7% 
}
Optional. Controls how stock prices change over time. In this example, the prices are updated only on November 25 of each year. The gaussian style means that price increases follow a Gaussian distribution. The range example above means that the percentage price increase is randomly selected between -5% and +10%.

tfsa-room {
  initial-room = 18500.00  
  yearly-limit = 6000.00
}
Optional. Used to calculate the room in your TFSA account (if you have one). The initial-room is your TFSA room as of the start-date of the simulation.

accounts { 
  # Initial account holdings.
  # In this simulation, you can have only up to 
  # 1 account of each account-type.
  # Only the bank account is required. 
  # The others are optional.
  bank {
    cash = 10000.00
    small-balance-limit = 4000.00
  }
  rif {
    convert-rsp-to-rif = 2018-05-01
    cash = 0.00
    stock-positions {
      2000 "ABC"
      800 "XYZ"
      900 "CLU"
    }
  }
  lif {
    convert-lira-to-lif = 2018-05-01
    jurisdiction = ON
    cash = 10.00
    stock-positions {
      50 "ENB"
      100 "CM"
      150 "XEG"
    }
    gic-positions {
     "Canada Trust" 1500.00 2.25% 2-year matures 2023-03-15
    }
  }
  tfsa {
    cash = 0.00
    stock-positions {
      1000 "ABC"
      200 "XYZ"
    }
  }
  nra {
    cash = 283.90
    stock-positions {
      340 "ABC"
      290 "XYZ"
    }
    gic-positions {
     "Home Trust" 7500.00 2.35% 2-year matures 2023-03-15
    }
    stock-book-values {
      33000.00 "XYZ"
      15000.00 "ABC"
    }
  }
}
The initial positions of your investment accounts and bank account. If your bank balance falls below the small-balance-limit, then you are charged fees. The stock-book-values applies only to the nra account (the non-registered account). The convert-rsp-to-rif date deserves special mention. Before this date, the RIF account simply has no minimum annual withdrawal. Other than that, it acts the same. That means no contributions to the RSP can be made before the conversion date in this simulation. In other words, RSPs are modeled here in a limited way. Note the names of the accounts used here: rif, lif, tfsa, and nra (non-registered account).

sequential-liquidation {
  avoid-downturn-years = 2 
  account-sequence = rif,nra,tfsa
  stock-sequence = "clu,xyz,abc"
  sell 6.0% on *-11-26 | 2027-01-01..2034-12-31 
  sell 3.0% on *-11-26 | 2035-01-01.. 
}
Optional. Describes how to liquidate stock positions, in order to generate cash flow. The avoid-downturn-years is a way of avoiding liquidations when the price of the stocks are lower. In the example above, the meaning is if the current price is lower than the prices of the past 2 years, then don't sell. The two sequence items define the order in which to sell stock, from which account, and then which stock in that account. The above example sells 6% of the investments on Nov 26, from 2027 to 2034. Later, from 2035 on, 3% is sold.

annual-tfsa-top-up {
  account-sequence = rif,nra
  stock-sequence = "abc,xyz"
  on *-01-03                
}
Optional. Topping up your TFSA account by transferring in-kind from other accounts. This is done every January 3, in the above example. The two sequence items define the order in which to transfer stock, from which account, and then which stock in that account.

small-paycheck 50.00 on *-15 | 2024-01-01..2026-12-31
small-paycheck 150.00 on *-15 | 2026-01-01..2027-12-31
Optional. Can appear multiple times. Employment earnings. The expectation is that these earnings are modest, since you are retired. However, that isn't enforced explicitly. In the above example, the person gets a raise in 2026 from 50.00 to 150.00. Your paycheck is modeled simplistically, without CPP contributions or EI deductions. There's simply a gross amount that is deposited to your bank account, and that is added to the employment income line on your tax return.

Description of Snippets - Transactions Block

transactions {
  # Transactions in your accounts, 
  # not yet defined implicitly.
  ...
}
Transactions against your accounts that aren't specified elsewhere. Items in the transactions block are different:

bank-deposit 925.00 on *-01 
bank-withdrawal 105.25 on 2027-05-01
A deposit to or withdrawal from your bank account. This line usually represents regular spending.

move-cash from nra to bank 1500.00 on *-01
move-cash from rif to bank on *-06-01,*-12-01
Move cash from one account to another. If you don't specify an amount, then the entire cash balance of the source account is moved. The nra symbol stands for non-registered account. The first example executes on the first of every month, and states a specific amount. The second example executes quarterly, and moves the full cash balance in the source account. Attempts to move cash into the rif account will fail. Moving cash out of the rif account will often resulting in withholding tax. Moving cash into or out of the tfsa account will affect your TFSA room.

spend-bank-balance-above 5000.00 on *-15
A generic way of spending the cash in your bank account. Only the amount above the given base amount is spent. When the money is spent, it simply disappears from the bank account.

sell-stock rif 952 shares "ABC" on 2022-06-20
buy-stock rif 952 shares "ABC" on 2022-06-20
Sell or buy shares in an investment account. In the example, the ticker symbol is ABC.

buy-gic rif "Home Trust" 7500.00 2.5% 1-year : 2022-06-21
Purchase a GIC. There's no corresponding sell. In this simulation, the GIC is always held to maturity in the account that purchased it. Note that the on syntax for the date of execution is not used here. Instead, the ":" symbol is used, and you can only specify the date as yyyy-mm-dd. The purchase of a GIC will, in the background, also trigger two other kinds of transactions: the redemption of the GIC, and, for multi-year GICs, the accrual of interest for your tax return.

transfer-stock-in tfsa 100 shares "ABC" on 2027-05-15 
transfer-stock-out rif 1500.00 "ABC" on 2027-05-15
Transfer stock into or out of an account. You can specify either the number of shares, or a principal amount.

move-stock from rif to tfsa 100 shares "ABC" on 2027-05-15
move-stock from rif to tfsa 7500.00 "ABC" on 2027-05-15
Move stock in kind from one account to another. You can specify either the number of shares, or a principal amount. You can't move stock into the RIF account.

annuity-payment 2000.00 on *-25 | 2027-05-25..
An annuity regularly pays money into your bank account. The above example pays on the 25th of every month, starting on 2027-05-25 and paying until the end of the simulation.

stock-split 2-to-1 "ABC" on 2027-05-12
Perform a stock split. Can be 2-to-1, 3-to-1, 4-to-1, and so on. The price of the stock is reduced accordingly, and stock positions are increased accordingly. Any historical stock data is also adjusted. Performing an occasional stock split can help in the precision of the simulation. Often, you will be liquidating a certain percentage. That translates eventually into a number of shares. If the share price has grown large over time, there's less granularity in the calculated amounts.