Modelling Personal Finance in Python
This is for people who want to understand how to make choices in personal finance and know how to program. Normally, it's easy enough to use rules of thumb, and to just follow advice / well trodden paths in finance - but if you're inclined you can model things, and actually predict where you would be in a few years, dependent on choices!
This post is not financial advice - do not make any real world decisions based on your models without consulting someone who knows what they're doing and can check what you've done. I am not responsible for any decisions you make or any errors in this code.
Most of the time you can predict how a single investment or debt will behave using Excel, but if you want to see how various choices could combine, it quickly becomes difficult to control, and so I decided to make python classes that behave as accounts, mortgages etc so I could model the questions I had.
First I created an account class, that I can use to model savings and investments. It should be fairly self explanatory, it allows transferring between accounts, and has a few extra methods important in evaluating for any goal, the ROI (return on investment) and passive income. Most people's goal is not simply to amass as much money as possible for no reason. Passive income is important, as the sum of passive income from all your assets is what you could potentially live off (although it's more complex, see Safe Withdrawal Rates).
# Simple class to model an account with transactions and interest class Account: def __init__(self, balance, apr, name): self.balance = balance self.apr = apr self.name = name def applyMonthlyInterest(self): self.balance = self.balance + self.passive_income() def deposit(self, amount): self.balance += amount def withdraw(self, amount): self.balance -= amount def transfer(self, amount, to): self.withdraw(amount) to.deposit(amount) def passive_income(self): return (self.balance * self.apr / 1200) def roi(self): return self.apr
This class is about as simple as you get for the basic purpose - I subclass it for various accounts which have withdrawal / purchase fees (e.g. funds) and would do also if my accounts had any other weird features (e.g. monthly charges etc). The idea is that with the class format you can lay out the rules for how the account should behave, and you can now run simulations, by iterating through months / years and checking balances.
Say that you want to decide whether to pay off a student loan as a priority or not, you can run a few simulations and edit the behaviour to see what the outcome would be.
# Simulation 1 - pay off student loan with 1000 from wage, # switch to Investment ISA after done n_years = 15 account1 = Account(-30000, 1.5, 'Student Loan') account2 = Account(0, 5, 'Investment ISA') accounts = [account1, account2] for y in range(n_years): for m in range(12): save = 1000 loan_left = 0 - account1.balance into_student_loan = save if save < loan_left else loan_left into_isa = save - into_student_loan account1.deposit(into_student_loan) account2.deposit(into_isa) for account in accounts: account.applyMonthlyInterest() print('Simulation 1') for account in accounts: print(account.name, account.value()) print('Total:', sum(map(lambda x: x.value(), accounts))) # Simulation 2 - pay of interest only on student loan, # and put rest of monthly savings in ISA account1 = Account(-30000, 1.5, 'Student Loan') account2 = Account(0, 5, 'Investment ISA') accounts = [account1, account2] for y in range(n_years): for m in range(12): save = 1000 interest_to_pay = 0 - (account1.balance + 30000) account1.deposit(interest_to_pay) account2.deposit(save - interest_to_pay) for account in accounts: account.applyMonthlyInterest() print('Simulation 2') for account in accounts: print(account.name, account.value()) print('Total:',sum(map(lambda x: x.value(), accounts)))
After 15 years, simulation 1 gives you a final balance of 20,7623, whereas simulation 2 gives you a final balance of 22,8379. However this account doesn't take into account the fact that I might not make 5% returns - I am not a financial advisor so I will not give you any advice there! There are other things to take into account, tax is a big one; I calculate my tax bill after carefully researching all the rules for allowances etc, I just add them to the simulation. Inflation is another one, I take it into account in my simulations - certain things inflate - costs, and the price of property - whereas balances do not - which is important when evaluating whether property is an investment. Obviously inflation rates are not guaranteed, so I run the simulation in best and worst case scenarios...
The other thing you can do asides from running a month by month strategy of putting cash here or there, is to have triggers at certain points - such as when you are saving for a house. Calculating everything by hand would be a nightmare. Here is a hypothetical situation, one is buying a house as soon as you have a 5% deposit, or waiting a certain amount of time to get a larger deposit and a better value mortgage. Here is a very simple class to deal with a house.
# Simple class to manage a house (rental or mortgage) class House(Account): def __init__(self, balance, apr, name, monthlyExpenditure, borrowedAmount, investedAmount, yearsLeft): self.balance = balance self.apr = apr self.name = name self.monthlyExpenditure = monthlyExpenditure self.borrowedAmount = borrowedAmount self.investedAmount = investedAmount self.yearsLeft = yearsLeft def monthlyMortgageToPay(self): interest = self.borrowedAmount * (self.apr / 1200) if (self.yearsLeft > 0): repay = (self.borrowedAmount / self.yearsLeft) / 12 return interest + repay else: return interest def doMonthlyUpdate(self): self.balance += self.passive_income() if (self.yearsLeft > 0): repay = (self.borrowedAmount / self.yearsLeft) / 12 self.borrowedAmount -= repay self.investedAmount += repay self.yearsLeft -= (1/12) def value(self): return self.investedAmount + self.balance def passive_income(self): return 0 - self.monthlyExpenditure -self.monthlyMortgageToPay() def roi(self): return 1200 * self.passive_income() / self.investedAmount
In these models, we start off with a rental property (no loan but huge outgoing monthly expense) and wait for a trigger to purchase a property with a mortgage. I put the simulation in it's own function so I can call it with different parameters.
# Run a simple simulation of saving, and once reaching a threshold, purchasing # a house, to see how well I would do over 5 years def purchase_simulation(deposit_percentage, mortgage_rate, mortgage_cost): # Starting situation with cash savings and rental outgoing. cashISA = Account(16000,1.5,'Cash ISA') house = House(0,0,'Rental flat',1250,0,0,0) accounts = [cashISA] # Calculate price of house transaction - rough UK estimate # for properties under 250000 house_price = 200000 stamp_duty = (0.02 * (house_price - 125000)) house_purchase_cost = 4000 + stamp_duty + mortgage_cost deposit = house_price * deposit_percentage / 100 saving_threshold = house_purchase_cost + deposit print('Saving threshold:',saving_threshold) bought_house = False n_years = 5 for y in range(n_years): for m in range(12): # 1500 from wage, which is spent on living costs / savings. save = 1500 cashISA.deposit(save) for account in accounts: account.applyMonthlyInterest() # Use the cash ISA to balance the house account house.doMonthlyUpdate() house.transfer(house.balance, cashISA) # Purchase the house immediately after saving enough if not bought_house and cashISA.balance >= saving_threshold: cashISA.withdraw(saving_threshold) house = House(0,mortgage_rate,'House',100,house_price - deposit, deposit, 25) bought_house = True print('Bought house year:', y + (m/12)) print('Total assets:', sum(map(lambda x: x.balance, accounts)) + house.investedAmount) # One simulation is based on saving a 5% deposit print('Simulation - 5% deposit') purchase_simulation(5, 2.97, 1000) # The other simulation is based on saving a 10% deposit, which gets you a better rate print('Simulation - 10% deposit') purchase_simulation(10, 2.24, 1000)
I find that in the second simulation, not only does it take three more years to save, but that I am actually way worse off after 5 years:
Simulation - 5% deposit Saving threshold: 16500.0 Bought house year: 0.08333333333333333 Total assets: 67365.21932369476 Simulation - 10% deposit Saving threshold: 26500.0 Bought house year: 3.0833333333333335 Total assets: 43921.627199638795
I think I can say in this case that buying the house sooner is the better strategy.
My recommendation if you decide to do your own models: Use verbose code - you do not want to make any mistakes - spell everything out and double check everything!