# calc.py from datetime import datetime, timedelta from defines import END_YEAR # GLOBAL CONSTANTS LEASE = 0 # Dates that don't change car_balloon_date = datetime(2026, 11, 15) new_fin_year_25 = datetime(2025, 7, 1) new_fin_year_26 = datetime(2026, 7, 1) end_date = datetime(END_YEAR, 4, 15) school_fees_date = datetime(2025, 12, 5) mich_present_date = datetime(2026,10,15) first_pay_date = datetime(2025,1,8) def bill_amount_today(finance, day, bill_data, bt_id_name, total ): amt=0 day_str = day.strftime("%Y-%m-%d") for b in bill_data: # there may be more than one bill on this day, keep add amount and keep going in loop if b['bill_date'] == day_str: amt += b['amount'] if b['amount'] > 1000: n=bt_id_name[ b['bill_type'] ] print( f"bill_amt_today {n} for {day_str} has amt={amt}" ) add_annotation(finance, day, total-amt, amt, f"Pay {n}" ) # bills are desc order so if the bill is before the day we are after then stop looking if b['bill_date'] < day_str: return amt #failsafe, doubt this even can occur with bills older than today return amt def add_annotation(finance, dt, total, delta, text): # dont add an annotation for small changes (jic) tm = dt.timestamp() * 1000 if delta > 0: text += f": ${int(abs(delta))}" else: text += f": -${int(abs(delta))}" finance['annotations'].append( { 'label': text, 'x': tm, 'y': total } ) return def calculate_savings_depletion(finance, bill_data, bill_type): # Extract all the financial data from the database D_Salary = finance['D_Salary'] D_Num_fortnights_pay = finance['D_Num_fortnights_pay'] School_Fees = finance['School_Fees'] Car_loan_via_pay = finance['Car_loan_via_pay'] Car_loan = finance['Car_loan'] Car_balloon = finance['Car_balloon'] Car_buyout = finance['Car_buyout'] Living_Expenses = finance['Living_Expenses'] Savings = finance['Savings'] Interest_Rate = finance['Interest_Rate'] Inflation = finance['Inflation'] Mich_present = finance['Mich_present'] Overseas_trip = finance['Overseas_trip'] Mark_reno = finance['Mark_reno'] D_leave_owed_in_days = finance['D_leave_owed_in_days'] Sell_shares = finance['Sell_shares'] D_TLS_shares = finance['D_TLS_shares'] M_TLS_shares = finance['M_TLS_shares'] D_CBA_shares = finance['D_CBA_shares'] TLS_price = finance['TLS_price'] CBA_price = finance['CBA_price'] Ioniq6_future = finance['Ioniq6_future'] ### COMPLEX tax implications with my leave I have not taken. It will be taxed in the year I 'quit' ### # leave in days, 10 business days to a fortnight, # paid before tax I earn $7830.42 / fortnight. Tax on that will be at 37% or $4933.16 after tax # if we could stretch this to July 2026, then would be more (due to less tax) bus_days_in_fortnight=10 # this is what I now earn before-tax (and I *THINK* vehicle allowance won't be paid X 12 weeks) pre_tax_D_earning = 8143.65 # whenever I leave, I get 12 weeks (or 60 business days) + whatever leave they owe me payout = ((60+D_leave_owed_in_days)/bus_days_in_fortnight) * pre_tax_D_earning # just use redundancy calc... payout = 83115.84 print( f"leave payout gross={payout}" ) # as the leave is just on top of my existing earnings and if in 2024 fin year, just take tax at 37% for the extra leave amount # hardcoded 6 represents the 12 weeks or 6 fornights of pay owed to me when I give notice or they sack me D_leave_after_tax = payout * (1-0.37) # However, if I quit in the next fin year - tax for 2025 will be: $4,288 plus 30c for each $1 over $45,000 # (assuming the 7830.42 * ~90/bus_days_in_fortnight = ~ $64k - > 45k and < $135k bracket is 30%) # Given, I probably can't stop Deakin doing PAYG deductions, I won't get # the tax back until the end of the financial year, so work out the # amount of tax I will get back info: tax_diff_D_leave tax_on_leave = (payout - 45000)*.37 + 4288 D_leave_after_tax_new_fin_year = payout - tax_on_leave # just use redunancy calc... D_leave_after_tax_new_fin_year = 56518.77 tax_diff_D_leave = payout - D_leave_after_tax_new_fin_year print( f"tax_diff_D_leave: {tax_diff_D_leave}") ### leave / tax items finished ### # convenience vars to make it easier to read conditional leave tax/payment logic below D_has_quit = False D_quit_year = 0 claim_tax_on_leave = False # Constants for interest calculations annual_interest_rate = Interest_Rate / 100.0 daily_interest_rate = annual_interest_rate / 365 # main loop range -- start from now, and simulate till D is 60 (April 2031) current_date = datetime.today() # TODO: need to refactor Living_Expenses to exclude bills total=0 yr=str(current_date.year) for b in bill_data: if yr in b['bill_date']: total += b['amount'] print( f"this yr={current_date.year} - total={total}" ) Living_Expenses -= total print( f"LE is now={Living_Expenses}" ) # Calculate daily living expenses daily_living_expenses = Living_Expenses / 365 # Start the calculation current_savings = Savings depletion_date = None savings_per_fortnight = [] # significant dates - but who knows when? :) overseas_trip_date = datetime.strptime( finance['Overseas_trip_date'], "%Y-%m-%d") mark_reno_date = datetime.strptime( finance['Mark_reno_date'], "%Y-%m-%d") car_buyout_date = datetime.strptime( finance['Car_buyout_date'], "%Y-%m-%d") # to force deakin pay cycles to match reality, we work from the 8th of Jan as our "day-zero" so we are paid on the 8/1/25, 22/1/25, etc. days_count = ( current_date - datetime(2025,1,1) ).days # Track the fortnight, and monthly interest fortnight_income = 0 monthly_interest = 0 # Create an empty dict to store annotations to display in the GUI # (key is date, text is for larger spend items by hand) finance['annotations']=[] #quick convenience lookup of bill types name for annotations. bt_id_name = {row["id"]: row["name"] for row in bill_type} while current_date <= end_date: #paid on 8th or 22nd of Jan (so 8th day of fortnight) is_fortnight = (days_count % 14 == 7) is_end_of_month = (current_date.day == 1) # Subtract daily living expenses current_savings -= daily_living_expenses # if we have a bill for today, pay for it current_savings -= bill_amount_today( finance, current_date, bill_data, bt_id_name, current_savings ) # Calculate daily interest but apply at the end of the month monthly_interest += current_savings * daily_interest_rate # Apply fortnightly salary and handle car loan deduction if is_fortnight: if D_Num_fortnights_pay > 0: fortnight_income += D_Salary D_Num_fortnights_pay -= 1 # keep paying car off fortnightly until the end of the car loan (while I am still working) - once I quit, pay reverts to monthly on the 15th if not D_has_quit and current_date < car_balloon_date: current_savings -= Car_loan_via_pay print( f"{current_date}: making car loan pay as pre-tax lease: ${Car_loan_via_pay}" ) if D_Num_fortnights_pay == 0 and D_leave_after_tax > 0: D_has_quit = True D_quit_date = current_date D_quit_year = current_date.year # okay, if we leave before Jun 30th 2024, then I pay full tax, otherwise I get 'extra', but have to await end of next fin year if current_date > new_fin_year_25: claim_tax_on_leave = True print(f"{current_date}: D has resigned in new year- get paid out my 12 weeks + remaining leave and lose some to tax - ${D_leave_after_tax_new_fin_year}" ) current_savings += D_leave_after_tax_new_fin_year add_annotation(finance, current_date, current_savings, D_leave_after_tax_new_fin_year, "D quit" ) else: claim_tax_on_leave = False print(f"{current_date}: D has resigned - get paid out my 12 weeks + remaining leave and lose some to tax - ${D_leave_after_tax}" ) current_savings += D_leave_after_tax add_annotation(finance, current_date, current_savings, D_leave_after_tax, "D quit" ) D_leave_after_tax = 0 # its end of 'next' fin year, if tax_diff > 0, then ddp quit after new tax year and gets back the overpaid tax if current_date > new_fin_year_26 and claim_tax_on_leave: current_savings += tax_diff_D_leave print( f"I quit last fin year, so now its 1st July {current_date.year}, get tax back of {tax_diff_D_leave}" ) add_annotation(finance, current_date, current_savings, tax_diff_D_leave, "D quit - tax back" ) # can only claim the tax back once :) claim_tax_on_leave=False if fortnight_income: print(f"{current_date}: salary paid by Deakin - adding: {fortnight_income}" ) current_savings += fortnight_income fortnight_income = 0 # reset for next fortnight savings_per_fortnight.append((current_date.strftime("%Y-%m-%d"), round(current_savings, 2))) # if I have quit, then car lease payments are made on the 15th of the month for full Car_loan if D_has_quit and current_date.day == 15: if Ioniq6_future == LEASE and current_date <= car_balloon_date: current_savings -= Car_loan print( f"{current_date}: making car loan pay (after quitting): ${Car_loan}" ) elif Ioniq6_future != LEASE and current_date <= car_buyout_date: current_savings -= Car_loan print( f"{current_date}: making car loan pay (after quitting): ${Car_loan}" ) if is_end_of_month: current_savings += monthly_interest #print(f"{current_date}: interest paid - ${monthly_interest}") monthly_interest = 0 # monthly increase living expenses by a monthly inflation multiplier Living_Expenses += (Inflation/100.0)/12 * Living_Expenses daily_living_expenses = Living_Expenses / 365 #print(f"{current_date}: Living Exp inceased - ${Living_Expenses}") if current_date.date() == school_fees_date.date(): current_savings -= School_Fees add_annotation(finance, current_date, current_savings, -School_Fees, "School Fees") if Ioniq6_future == LEASE and current_date.date() == car_balloon_date.date(): current_savings -= Car_balloon add_annotation(finance, current_date, current_savings, -Car_balloon, "car balloon") print(f"{current_date}: car balloon - ${Car_balloon}" ) if Ioniq6_future != LEASE and current_date.date() == car_buyout_date.date(): current_savings -= Car_buyout add_annotation(finance, current_date, current_savings, -Car_buyout, "car buyout") print(f"{current_date}: car buyout - ${Car_buyout}" ) if current_date.date() == mich_present_date.date(): current_savings -= Mich_present add_annotation(finance, current_date, current_savings, -Mich_present, "Mich's present" ) if current_date.date() == mark_reno_date.date(): current_savings -= Mark_reno add_annotation(finance, current_date, current_savings, -Mark_reno, "Mark/reno" ) if current_savings < 0: depletion_date = current_date break # twice a year, CBA has a dividend of $2-2.5 DRP gives back around 20ish shares twice a year, estimate this... # and on average they exceed interest rate, but lets assume at least int.rate increase (remember its twice a year, so /2) if current_date.day == 1 and (current_date.month == 4 or current_date.month == 10): CBA_price = CBA_price+ CBA_price * (Interest_Rate/2)/100 drp = int( (2.25*D_CBA_shares/CBA_price) ) print( f"DRP {current_date} - adding {drp} CBA shares" ) D_CBA_shares += int( (2.25*D_CBA_shares/CBA_price) ) # if selling shares, and its 1st of July... # BUT not if D quits before end of financial year - as I won't be able to sell CBA shares for no cap gains # so wait until the following year if current_date.month == 7 and current_date.day == 1 and D_has_quit and Sell_shares>0 and (current_date.year > D_quit_year or current_date.year == D_quit_year and claim_tax_on_leave == False): # 2024 Govt. value tax_threshold = 18200 # cap-gains is 50% of profit (lazy profit calc here, just assume its all profit) can_sell = 2*tax_threshold actual_sell = 0 # sell off TLS first - and they are way under the limit, so just sell them all in one hit if D_TLS_shares > 0: actual_sell += TLS_price*D_TLS_shares D_TLS_shares = 0 if M_TLS_shares > 0: actual_sell += TLS_price*M_TLS_shares M_TLS_shares = 0 while actual_sell + CBA_price < can_sell and D_CBA_shares > 0: actual_sell += CBA_price D_CBA_shares -= 1 Sell_shares -= 1 current_savings += actual_sell add_annotation(finance, current_date, current_savings, actual_sell, "Sell shares" ) current_date += timedelta(days=1) days_count += 1 finance['CBA']=D_CBA_shares finance['TLS']=D_TLS_shares+M_TLS_shares return depletion_date, savings_per_fortnight, current_savings ################################################################################ # work out the date D quits and when we own the car, so we can then use it to # handle future bills ################################################################################ def calc_key_dates( finance ): key_dates={} now = datetime.today() # this will be 0 to 13 days - how far into this fortnights pay cycle are we now days_in_pay_fortnight= ( now - first_pay_date ).days % 14 # add 1 less fortnight than we continue to work, then add rest of pay cycle (14-days_in_pay_fortnight) key_dates['D_quit_date'] = (now+timedelta(weeks=2*(finance['D_Num_fortnights_pay']-1))+timedelta(days=(14-days_in_pay_fortnight))).strftime('%Y-%m-%d') # use lease date if finance['Ioniq6_future'] == LEASE: key_dates['D_hyundai_owned'] = car_balloon_date.strftime('%Y-%m-%d') # use buyout date else: key_dates['D_hyundai_owned'] = finance['Car_buyout_date'] print( f"kd={key_dates}" ) return key_dates