diff --git a/internal/calc/calc.go b/internal/calc/calc.go index f590c26..2b8000f 100644 --- a/internal/calc/calc.go +++ b/internal/calc/calc.go @@ -6,7 +6,7 @@ import ( "strings" ) -type DebtRecyclingParameters struct { +type Parameters struct { Salary float64 InitialInvestment float64 AnnualInvestment float64 @@ -20,7 +20,7 @@ type DebtRecyclingParameters struct { ReinvestTaxRefunds bool } -type DebtRecyclingData struct { +type Data struct { DebtRecycled []float64 NonDeductibleInterest []float64 TaxDeductibleInterest []float64 @@ -122,8 +122,8 @@ func CAGR(initialValue, finalValue float64, numYears int) float64 { return math.Pow(finalValue/initialValue, 1/float64(numYears)) - 1 } -func DebtRecycling(params DebtRecyclingParameters) (*DebtRecyclingData, error) { - data := &DebtRecyclingData{} +func DebtRecycling(params Parameters) (*Data, error) { + data := &Data{} // Pre-allocate slices with the correct size data.DebtRecycled = make([]float64, params.NumYears) diff --git a/internal/calc/calc_test.go b/internal/calc/calc_test.go index 7874eb3..6151ea8 100644 --- a/internal/calc/calc_test.go +++ b/internal/calc/calc_test.go @@ -17,13 +17,13 @@ func Test_CAGR(t *testing.T) { func Test_DebtRecycling(t *testing.T) { type drTests struct { - params DebtRecyclingParameters - expected *DebtRecyclingData + params Parameters + expected *Data } cases := []drTests{ { - params: DebtRecyclingParameters{ + params: Parameters{ Salary: 150000, InitialInvestment: 100000, AnnualInvestment: 50000, @@ -36,7 +36,7 @@ func Test_DebtRecycling(t *testing.T) { ReinvestDividends: false, ReinvestTaxRefunds: false, }, - expected: &DebtRecyclingData{ + expected: &Data{ DebtRecycled: []float64{ 100000, 150000, @@ -151,7 +151,7 @@ func Test_DebtRecycling(t *testing.T) { }, }, { - params: DebtRecyclingParameters{ + params: Parameters{ Salary: 150000, InitialInvestment: 100000, AnnualInvestment: 50000, @@ -164,7 +164,7 @@ func Test_DebtRecycling(t *testing.T) { ReinvestDividends: true, ReinvestTaxRefunds: false, }, - expected: &DebtRecyclingData{ + expected: &Data{ PortfolioValue: []float64{ 100000, 164160, @@ -230,121 +230,247 @@ func Test_DebtRecycling(t *testing.T) { TotalValue: 1.3293381914617491e+06, }, }, - // { - // params: DebtRecyclingParameters{ - // Salary: 150000, - // InitialInvestment: 100000, - // AnnualInvestment: 50000, - // MortgageSize: 600000, - // MortgageInterestRate: 0.05, - // DividendReturnRate: 0.02, - // CapitalGrowthRate: 0.08, - // NumYears: 10, - // Country: "au", - // ReinvestDividends: false, - // ReinvestTaxRefunds: true, - // }, - // expected: &DebtRecyclingData{ - // PortfolioValue: []float64{ - // 100000, - // 166247.11052631578, - // 239907.86103614027, - // 321567.7011835439, - // 411857.9836250151, - // 511459.60838165897, - // 621106.9662296256, - // 741592.2063071347, - // 873769.8551086588, - // 1.0185618161322233e+06, - // 1.176962781658853e+06, - // }, - // DebtRecycled: []float64{ - // 104247.1105263158, - // 156360.9816677192, - // 208467.21126451236, - // 260564.86634678775, - // 312652.9860666426, - // 364730.58917743387, - // 416796.68277913897, - // 468850.27229695325, - // 520890.3726148719, - // 572916.0202360519, - // }, - // CumulativeDividends: []float64{ - // 2000, - // 5324.942210526316, - // 10123.09943124912, - // 16554.453454919996, - // 24791.6131274203, - // 35020.80529505348, - // 47442.94461964599, - // 62274.78874578868, - // 79750.18584796187, - // 100121.42217060634, - // }, - // CumulativeTaxRefunds: []float64{ - // 4247.11052631579, - // 10608.092194034987, - // 19075.30345854735, - // 29640.169805335096, - // 42293.15587197774, - // 57023.74504941159, - // 73820.42782855057, - // 92670.70012550385, - // 113561.07274037572, - // 136477.09297642764, - // }, - // NetPosition: []float64{ - // -498000, - // -428427.9472631579, - // -349969.0395326106, - // -261877.84536153614, - // -163350.40324756457, - // -53519.58632328757, - // 68549.91084927157, - // 203866.99505292345, - // 353520.0409566206, - // 518683.2383028297, - // }, - // TotalInvested: 600000, - // TotalValue: 2.0033068141620778e+06, - // }, - // }, - // { - // params: DebtRecyclingParameters{ - // Salary: 150000, - // InitialInvestment: 100000, - // AnnualInvestment: 50000, - // MortgageSize: 600000, - // MortgageInterestRate: 0.05, - // DividendReturnRate: 0.02, - // CapitalGrowthRate: 0.08, - // NumYears: 10, - // Country: "au", - // ReinvestDividends: true, - // ReinvestTaxRefunds: true, - // }, - // expected: &DebtRecyclingData{ - - // blah - //}, - // }, - // { - // params: DebtRecyclingParameters{ - // Salary: 150000, - // InitialInvestment: 100000, - // AnnualInvestment: 50000, - // MortgageSize: 600000, - // MortgageInterestRate: 0.05, - // DividendReturnRate: 0.02, - // CapitalGrowthRate: 0.08, - // NumYears: 10, - // Country: "nz", - // ReinvestDividends: false, - // ReinvestTaxRefunds: false, - // }, - // expected: &DebtRecyclingData{}, - // }, + { + params: Parameters{ + Salary: 150000, + InitialInvestment: 100000, + AnnualInvestment: 50000, + MortgageSize: 600000, + MortgageInterestRate: 0.05, + DividendReturnRate: 0.02, + CapitalGrowthRate: 0.08, + NumYears: 10, + Country: "au", + ReinvestDividends: false, + ReinvestTaxRefunds: true, + }, + expected: &Data{ + PortfolioValue: []float64{ + 100000, + 165402, + 237737.16, + 317560.1328000001, + 405469.9434240001, + 502113.53889792017, + 608189.6220097538, + 724452.7917705342, + 851718.015112177, + 990865.4563211512, + 1.1428456928268434e+06, + }, + DebtRecycled: []float64{ + 103150, + 154725, + 206300, + 257875, + 309450, + 361025, + 412600, + 464175, + 515750, + 567325, + }, + CumulativeDividends: []float64{ + 2000, + 5308.04, + 10062.7832, + 16413.985856, + 24523.38472448, + 34565.65550243841, + 46729.44794263349, + 61218.50377804417, + 78252.86408028772, + 98070.17320671075, + }, + CumulativeTaxRefunds: []float64{ + 3150, + 7875, + 14175, + 22050, + 31500, + 42525, + 55125, + 69300, + 85050, + 102375, + }, + NetPosition: []float64{ + -500000, + -434598, + -362262.83999999997, + -282439.8671999999, + -194530.0565759999, + -97886.46110207983, + 8189.622009753832, + 124452.79177053424, + 251718.015112177, + 390865.4563211512, + }, + TotalInvested: 600000, + TotalValue: 1.343290866033554e+06, + }, + }, + { + params: Parameters{ + Salary: 150000, + InitialInvestment: 100000, + AnnualInvestment: 50000, + MortgageSize: 600000, + MortgageInterestRate: 0.05, + DividendReturnRate: 0.02, + CapitalGrowthRate: 0.08, + NumYears: 10, + Country: "au", + ReinvestDividends: true, + ReinvestTaxRefunds: true, + }, + expected: &Data{ + PortfolioValue: []float64{ + 100000, + 167562, + 243689.2992, + 329252.13199872005, + 425209.14860979, + 532616.3981085448, + 652637.224156373, + 786553.1661306606, + 935775.9678095358, + 1.1018608061389846e+06, + 1.2865208640427056e+06, + }, + DebtRecycled: []float64{ + 105150, + 158076.24, + 211173.785984, + 264460.0426399744, + 317954.1829721958, + 371677.3279621709, + 425652.74448312743, + 479906.0633226132, + 534465.5193561907, + 589362.2161227796, + }, + CumulativeDividends: []float64{ + 2000, + 5351.24, + 10225.025984, + 16810.068623974403, + 25314.2515961702, + 35966.579558341095, + 49019.32404146856, + 64750.38736408177, + 83465.90672027248, + 105503.12284305217, + }, + CumulativeTaxRefunds: []float64{ + 3150, + 7875, + 14175, + 22050, + 31500, + 42525, + 55125, + 69300, + 85050, + 102375, + }, + NetPosition: []float64{ + -500000, + -432438, + -356310.7008, + -270747.86800127995, + -174790.85139020998, + -67383.60189145524, + 52637.22415637295, + 186553.16613066057, + 335775.9678095358, + 501860.8061389846, + }, + TotalInvested: 600000, + TotalValue: 1.494398986885758e+06, + }, + }, + { + params: Parameters{ + Salary: 150000, + InitialInvestment: 100000, + AnnualInvestment: 50000, + MortgageSize: 600000, + MortgageInterestRate: 0.05, + DividendReturnRate: 0.02, + CapitalGrowthRate: 0.08, + NumYears: 10, + Country: "nz", + ReinvestDividends: false, + ReinvestTaxRefunds: false, + }, + // HERE + expected: &Data{ + PortfolioValue: []float64{ + 100000, + 162000, + 228960.00000000003, + 301276.80000000005, + 379378.9440000001, + 463729.25952000014, + 554827.6002816001, + 653213.8083041282, + 759470.9129684585, + 874228.5860059352, + 998166.8728864101, + }, + DebtRecycled: []float64{ + 100000, + 150000, + 200000, + 250000, + 300000, + 350000, + 400000, + 450000, + 500000, + 550000, + }, + CumulativeDividends: []float64{ + 2000, + 5240, + 9819.2, + 15844.736, + 23432.31488, + 32706.900070400006, + 43803.45207603201, + 56867.72824211457, + 72057.14650148374, + 89541.71822160245, + }, + CumulativeTaxRefunds: []float64{ + 3349.9999999999995, + 8374.999999999998, + 15074.999999999996, + 23449.999999999996, + 33499.99999999999, + 45224.99999999999, + 58624.99999999999, + 73699.99999999999, + 90449.99999999999, + 108874.99999999999, + }, + NetPosition: []float64{ + -500000, + -438000, + -371040, + -298723.19999999995, + -220621.05599999992, + -136270.74047999986, + -45172.39971839986, + 53213.80830412824, + 159470.9129684585, + 274228.5860059352, + }, + TotalInvested: 600000, + TotalValue: 1.1965835911080125e+06, + }, + }, } for i, c := range cases { dr, err := DebtRecycling(c.params) @@ -422,7 +548,7 @@ func compareAllFloat64Values( got, want []float64, fieldName string, testIndex int, - params DebtRecyclingParameters, + params Parameters, ) { if len(got) != len(want) { t.Errorf( diff --git a/internal/charts/charts.go b/internal/charts/charts.go index bc8d33f..e358718 100644 --- a/internal/charts/charts.go +++ b/internal/charts/charts.go @@ -94,7 +94,7 @@ func ChartToTemplComponent(chart Renderable) templ.Component { } func Positions( - data *calc.DebtRecyclingData, + data *calc.Data, years int, ctx context.Context, ) (*echarts.Line, error) { @@ -184,7 +184,7 @@ func Positions( return line, nil } -func Income(data *calc.DebtRecyclingData, years int, ctx context.Context) (*echarts.Bar, error) { +func Income(data *calc.Data, years int, ctx context.Context) (*echarts.Bar, error) { bar := echarts.NewBar() styleNonce := middleware.GetInlineStyleNonce(ctx) @@ -259,7 +259,7 @@ func Income(data *calc.DebtRecyclingData, years int, ctx context.Context) (*echa return bar, nil } -func Interest(data *calc.DebtRecyclingData, years int, ctx context.Context) (*echarts.Bar, error) { +func Interest(data *calc.Data, years int, ctx context.Context) (*echarts.Bar, error) { bar := echarts.NewBar() styleNonce := middleware.GetInlineStyleNonce(ctx) diff --git a/internal/handlers/calc.go b/internal/handlers/calc.go index 3616b4f..0df331c 100644 --- a/internal/handlers/calc.go +++ b/internal/handlers/calc.go @@ -1,8 +1,8 @@ package handlers import ( + "fmt" "net/http" - "strconv" "debtrecyclingcalc.com/internal/calc" "debtrecyclingcalc.com/internal/charts" @@ -21,73 +21,12 @@ func CalcHandler(w http.ResponseWriter, r *http.Request) { return } - salary, err := strconv.ParseFloat(r.Form.Get("salary"), 64) + params, err := getFormParams(r) if err != nil { - http.Error(w, "error parsing salary", http.StatusBadRequest) + http.Error(w, err.Error(), http.StatusBadRequest) return } - inititalInvestmentAmount, err := strconv.ParseFloat(r.Form.Get("initial_investment"), 64) - if err != nil { - http.Error(w, "error parsing initial investment amount", http.StatusBadRequest) - return - } - - annualInvestmentAmount, err := strconv.ParseFloat(r.Form.Get("annual_investment"), 64) - if err != nil { - http.Error(w, "error parsing annual investment amount", http.StatusBadRequest) - return - } - - mortgageSize, err := strconv.ParseFloat(r.Form.Get("mortgage_size"), 64) - if err != nil { - http.Error(w, "error parsing mortgage size", http.StatusBadRequest) - return - } - - mortgageInterestRate, err := strconv.ParseFloat(r.Form.Get("mortgage_interest_rate"), 64) - if err != nil { - http.Error(w, "error parsing mortgage interest rate", http.StatusBadRequest) - return - } - - dividendReturnRate, err := strconv.ParseFloat(r.Form.Get("dividend_return_rate"), 64) - if err != nil { - http.Error(w, "error parsing dividend return rate", http.StatusBadRequest) - return - } - - capitalGrowthRate, err := strconv.ParseFloat(r.Form.Get("capital_growth_rate"), 64) - if err != nil { - http.Error(w, "error parsing capital growth rate", http.StatusBadRequest) - return - } - - years, err := strconv.Atoi(r.Form.Get("years")) - if err != nil { - http.Error(w, "error parsing years", http.StatusBadRequest) - return - } - - country := r.Form.Get("country") - - reinvestDividends := r.Form.Get("reinvest_dividends") == "on" - reinvestTaxRefunds := r.Form.Get("reinvest_tax_refunds") == "on" - - params := &calc.DebtRecyclingParameters{ - Salary: salary, - InitialInvestment: inititalInvestmentAmount, - AnnualInvestment: annualInvestmentAmount, - MortgageSize: mortgageSize, - MortgageInterestRate: mortgageInterestRate / 100, - DividendReturnRate: dividendReturnRate / 100, - CapitalGrowthRate: capitalGrowthRate / 100, - NumYears: years, - Country: country, - ReinvestDividends: reinvestDividends, - ReinvestTaxRefunds: reinvestTaxRefunds, - } - // if params is empty respond with error data, err := calc.DebtRecycling(*params) if err != nil { @@ -115,6 +54,31 @@ func CalcHandler(w http.ResponseWriter, r *http.Request) { results := templates.Results(data, params, positionsChart, incomeChart, interestChart) + w.Header(). + Set("HX-Push-Url", fmt.Sprintf("/?salary=%.2f"+ + "&initial_investment=%.2f"+ + "&annual_investment=%.2f"+ + "&mortgage_size=%.2f"+ + "&mortgage_interest_rate=%.2f"+ + "÷nd_return_rate=%.2f"+ + "&capital_growth_rate=%.2f"+ + "&years=%d"+ + "&country=%s"+ + "&reinvest_dividends=%t"+ + "&reinvest_tax_refunds=%t", + params.Salary, + params.InitialInvestment, + params.AnnualInvestment, + params.MortgageSize, + params.MortgageInterestRate*100, + params.DividendReturnRate*100, + params.CapitalGrowthRate*100, + params.NumYears, + params.Country, + params.ReinvestDividends, + params.ReinvestTaxRefunds, + )) + err = results.Render(r.Context(), w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) diff --git a/internal/handlers/index.go b/internal/handlers/index.go index 873c95a..11fc4c8 100644 --- a/internal/handlers/index.go +++ b/internal/handlers/index.go @@ -19,7 +19,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusNotFound) c := templates.NotFound() - err := templates.Layout(c, "Not Fountempl.WithStatus(http.StatusNotFound)d", buildinfo.GitTag, buildinfo.BuildDate). + err := templates.Layout(c, "Not Found", buildinfo.GitTag, buildinfo.BuildDate). Render(r.Context(), w) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -28,20 +28,30 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { return } - params := &calc.DebtRecyclingParameters{ + params := &calc.Parameters{ Salary: 150000, InitialInvestment: 100000, AnnualInvestment: 50000, MortgageSize: 600000, - MortgageInterestRate: 0.05, - DividendReturnRate: 0.02, - CapitalGrowthRate: 0.08, + MortgageInterestRate: 0.0500, + DividendReturnRate: 0.0200, + CapitalGrowthRate: 0.0800, NumYears: 10, Country: "au", ReinvestDividends: true, ReinvestTaxRefunds: true, } + query := r.URL.Query() + var err error + if len(query) != 0 { + params, err = getQueryParams(query) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + } + data, err := calc.DebtRecycling(*params) if err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) @@ -68,7 +78,7 @@ func IndexHandler(w http.ResponseWriter, r *http.Request) { index := templates.Index( templates.Hero(), - templates.Form(), + templates.Form(params), templates.Results(data, params, positionsChart, incomeChart, interestChart), ) diff --git a/internal/handlers/util.go b/internal/handlers/util.go new file mode 100644 index 0000000..a1e44fd --- /dev/null +++ b/internal/handlers/util.go @@ -0,0 +1,146 @@ +package handlers + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + + "debtrecyclingcalc.com/internal/calc" +) + +func getFormParams(r *http.Request) (*calc.Parameters, error) { + parseFloat := func(key string) (float64, error) { + return strconv.ParseFloat(r.Form.Get(key), 64) + } + + parseInt := func(key string) (int, error) { + return strconv.Atoi(r.Form.Get(key)) + } + + salary, err := parseFloat("salary") + if err != nil { + return nil, fmt.Errorf("error parsing salary: %w", err) + } + + initialInvestmentAmount, err := parseFloat("initial_investment") + if err != nil { + return nil, fmt.Errorf("error parsing initial investment amount: %w", err) + } + + annualInvestmentAmount, err := parseFloat("annual_investment") + if err != nil { + return nil, fmt.Errorf("error parsing annual investment amount: %w", err) + } + + mortgageSize, err := parseFloat("mortgage_size") + if err != nil { + return nil, fmt.Errorf("error parsing mortgage size: %w", err) + } + + mortgageInterestRate, err := parseFloat("mortgage_interest_rate") + if err != nil { + return nil, fmt.Errorf("error parsing mortgage interest rate: %w", err) + } + + dividendReturnRate, err := parseFloat("dividend_return_rate") + if err != nil { + return nil, fmt.Errorf("error parsing dividend return rate: %w", err) + } + + capitalGrowthRate, err := parseFloat("capital_growth_rate") + if err != nil { + return nil, fmt.Errorf("error parsing capital growth rate: %w", err) + } + + years, err := parseInt("years") + if err != nil { + return nil, fmt.Errorf("error parsing years: %w", err) + } + + country := r.Form.Get("country") + reinvestDividends := r.Form.Get("reinvest_dividends") == "on" + reinvestTaxRefunds := r.Form.Get("reinvest_tax_refunds") == "on" + + return &calc.Parameters{ + Salary: salary, + InitialInvestment: initialInvestmentAmount, + AnnualInvestment: annualInvestmentAmount, + MortgageSize: mortgageSize, + MortgageInterestRate: mortgageInterestRate / 100, + DividendReturnRate: dividendReturnRate / 100, + CapitalGrowthRate: capitalGrowthRate / 100, + NumYears: years, + Country: country, + ReinvestDividends: reinvestDividends, + ReinvestTaxRefunds: reinvestTaxRefunds, + }, nil +} + +func getQueryParams(query url.Values) (*calc.Parameters, error) { + parseFloat := func(key string) (float64, error) { + return strconv.ParseFloat(query.Get(key), 64) + } + + parseInt := func(key string) (int, error) { + return strconv.Atoi(query.Get(key)) + } + + salary, err := parseFloat("salary") + if err != nil { + return nil, fmt.Errorf("error parsing salary: %w", err) + } + + initialInvestmentAmount, err := parseFloat("initial_investment") + if err != nil { + return nil, fmt.Errorf("error parsing initial investment amount: %w", err) + } + + annualInvestmentAmount, err := parseFloat("annual_investment") + if err != nil { + return nil, fmt.Errorf("error parsing annual investment amount: %w", err) + } + + mortgageSize, err := parseFloat("mortgage_size") + if err != nil { + return nil, fmt.Errorf("error parsing mortgage size: %w", err) + } + + mortgageInterestRate, err := parseFloat("mortgage_interest_rate") + if err != nil { + return nil, fmt.Errorf("error parsing mortgage interest rate: %w", err) + } + + dividendReturnRate, err := parseFloat("dividend_return_rate") + if err != nil { + return nil, fmt.Errorf("error parsing dividend return rate: %w", err) + } + + capitalGrowthRate, err := parseFloat("capital_growth_rate") + if err != nil { + return nil, fmt.Errorf("error parsing capital growth rate: %w", err) + } + + years, err := parseInt("years") + if err != nil { + return nil, fmt.Errorf("error parsing years: %w", err) + } + + country := query.Get("country") + reinvestDividends := query.Get("reinvest_dividends") == "true" + reinvestTaxRefunds := query.Get("reinvest_tax_refunds") == "true" + + return &calc.Parameters{ + Salary: salary, + InitialInvestment: initialInvestmentAmount, + AnnualInvestment: annualInvestmentAmount, + MortgageSize: mortgageSize, + MortgageInterestRate: mortgageInterestRate / 100, + DividendReturnRate: dividendReturnRate / 100, + CapitalGrowthRate: capitalGrowthRate / 100, + NumYears: years, + Country: country, + ReinvestDividends: reinvestDividends, + ReinvestTaxRefunds: reinvestTaxRefunds, + }, nil +} diff --git a/internal/templates/Hero_templ.go b/internal/templates/Hero_templ.go index 20af3f3..9dec81f 100644 --- a/internal/templates/Hero_templ.go +++ b/internal/templates/Hero_templ.go @@ -26,7 +26,7 @@ func Hero() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
♻️

Reduce, Reuse, Recycle!

Calculate your debt recycling scenario by entering your details below.

ℹ️ Information

What is debt recycling?

Debt recycling involves refinancing or redrawing funds from your offset account to invest in income producing assets. The idea is to turn your \"bad debt\" into tax-deductible \"good debt\".

The key requirement for the interest to qualify as deductible is that the funds must be used to earn income.


While this can be a very effective strategy for building wealth, it's important to consider the risks, such as the possibility of losing money if your investments underperform, or worse, go to zero.


Further reading...


What do all of the fields mean?

Descriptions and examples for all fields are provided below.

FieldDescriptionExample
SalaryYour annual salary. This, along with any dividends income will be used to determine your bracket.150000
Initial InvestmentThe initial amount you will pay down, recycle and invest. This is the available cash you have now, e.g. in the bank or in your offset account.100000
Annual InvestmentThe annual amount you will pay down, recycle and invest. This should take into account regular payments as well as any additional payments you expect to make.50000
MortgageThe total size of your mortgage loan.600000
Mortgage Interest RateThe projected annual mortgage interest rate.5
Dividend Return RateThe projected annual dividend return rate.2
Capital Growth RateThe projected annual capital growth rate.8
YearsThe number of years to project the scenario.10
CountrySelect the country you live in. Used to determine your tax bracket alobng with your salary and dividend income.Australia
Reinvest DividendsWhether to reinvest your dividend income via debt recycling.✔️
Reinvest Tax RefundsWhether to reinvest your tax refunds via debt recycling.✔️

DISCLAIMER

This calculator is for illustrative purposes only and is not to be misconstrued as financial advice.


It does not take into account your individual needs, goals and objectives.


The results are simply assumptions based on the information provided and are not guaranteed to be accurate.


In particular, it does not take into account any changes in the market, rates of return, interest rates, tax legislation or any extraordinary occurrences that may impact your results.


It also does not take into account franking credits, or the (hopefully) increasing value of your property and the subsequent increase in available equity.


It is better to be roughly right than precisely wrong.

John Manyard Keynes

") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
♻️

Reduce, Reuse, Recycle!

Calculate your debt recycling scenario by entering your details below.

ℹ️ Information

What is debt recycling?

Debt recycling involves refinancing or redrawing funds from your offset account to invest in income producing assets. The idea is to turn your \"bad debt\" into tax-deductible \"good debt\".

The key requirement for the interest to qualify as deductible is that the funds must be used to earn income.


While this can be a very effective strategy for building wealth, it's important to consider the risks, such as the possibility of losing money if your investments underperform, or worse, go to zero.


Further reading...


What do all of the fields mean?

Descriptions and examples for all fields are provided below.

FieldDescriptionExample
SalaryYour annual salary. This, along with any dividends income will be used to determine your bracket.150000
Initial InvestmentThe initial amount you will pay down, recycle and invest. This is the available cash you have now, e.g. in the bank or in your offset account.100000
Annual InvestmentThe annual amount you will pay down, recycle and invest. This should take into account regular payments as well as any additional payments you expect to make.50000
MortgageThe total size of your mortgage loan.600000
Mortgage Interest RateThe projected annual mortgage interest rate.5
Dividend Return RateThe projected annual dividend return rate.2
Capital Growth RateThe projected annual capital growth rate.8
YearsThe number of years to project the scenario.10
CountrySelect the country you live in. Used to determine your tax bracket according to your combined income from salary and dividends.Australia
Reinvest DividendsWhether to reinvest your dividend income via debt recycling.✔️
Reinvest Tax RefundsWhether to reinvest your tax refunds via debt recycling.✔️

DISCLAIMER

This calculator is for illustrative purposes only and is not to be misconstrued as financial advice.


It does not take into account your individual needs, goals and objectives.


The results are simply assumptions based on the information provided and are not guaranteed to be accurate.


In particular, it does not take into account any changes in the market, rates of return, interest rates, tax legislation or any extraordinary occurrences that may impact your results.


It also does not take into account franking credits, or the (hopefully) increasing value of your property and the subsequent increase in available equity.


It is better to be roughly right than precisely wrong.

John Manyard Keynes

") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/templates/Results.templ b/internal/templates/Results.templ index 0d5ea47..b51ee06 100644 --- a/internal/templates/Results.templ +++ b/internal/templates/Results.templ @@ -6,8 +6,8 @@ import ( ) templ Results( - data *calc.DebtRecyclingData, - params *calc.DebtRecyclingParameters, + data *calc.Data, + params *calc.Parameters, positionsChart *echarts.Line, incomeChart *echarts.Bar, interestChart *echarts.Bar, diff --git a/internal/templates/Results_templ.go b/internal/templates/Results_templ.go index 386960b..faf0947 100644 --- a/internal/templates/Results_templ.go +++ b/internal/templates/Results_templ.go @@ -14,8 +14,8 @@ import ( ) func Results( - data *calc.DebtRecyclingData, - params *calc.DebtRecyclingParameters, + data *calc.Data, + params *calc.Parameters, positionsChart *echarts.Line, incomeChart *echarts.Bar, interestChart *echarts.Bar, diff --git a/internal/templates/cards.templ b/internal/templates/cards.templ index ba87020..0914cc8 100644 --- a/internal/templates/cards.templ +++ b/internal/templates/cards.templ @@ -6,7 +6,7 @@ import ( "debtrecyclingcalc.com/internal/calc" ) -templ cards(data *calc.DebtRecyclingData, years int) { +templ cards(data *calc.Data, years int) {
for year := 0; year < years;year++ { diff --git a/internal/templates/cards_templ.go b/internal/templates/cards_templ.go index dd015da..efc5ad9 100644 --- a/internal/templates/cards_templ.go +++ b/internal/templates/cards_templ.go @@ -14,7 +14,7 @@ import ( "debtrecyclingcalc.com/internal/calc" ) -func cards(data *calc.DebtRecyclingData, years int) templ.Component { +func cards(data *calc.Data, years int) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) diff --git a/internal/templates/form.templ b/internal/templates/form.templ index 7a1b7ec..623da38 100644 --- a/internal/templates/form.templ +++ b/internal/templates/form.templ @@ -1,52 +1,162 @@ package templates -templ Form() { +import ( + "debtrecyclingcalc.com/internal/calc" + "fmt" +) + +templ Form(params *calc.Parameters) {
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- - + +
- +
- +
- +
diff --git a/internal/templates/form_templ.go b/internal/templates/form_templ.go index f8bd0ec..9ecb321 100644 --- a/internal/templates/form_templ.go +++ b/internal/templates/form_templ.go @@ -8,7 +8,12 @@ package templates import "github.com/a-h/templ" import templruntime "github.com/a-h/templ/runtime" -func Form() templ.Component { +import ( + "debtrecyclingcalc.com/internal/calc" + "fmt" +) + +func Form(params *calc.Parameters) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W) @@ -26,7 +31,131 @@ func Form() templ.Component { templ_7745c5c3_Var1 = templ.NopComponent } ctx = templ.ClearChildren(ctx) - _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") + _, templ_7745c5c3_Err = templ_7745c5c3_Buffer.WriteString("
") if templ_7745c5c3_Err != nil { return templ_7745c5c3_Err } diff --git a/internal/templates/metrics.templ b/internal/templates/metrics.templ index 3c39f9a..d1a5247 100644 --- a/internal/templates/metrics.templ +++ b/internal/templates/metrics.templ @@ -31,7 +31,7 @@ func printHumanInt(value int) string { return p.Sprintf("%d", value) } -templ metrics(data *calc.DebtRecyclingData, params *calc.DebtRecyclingParameters) { +templ metrics(data *calc.Data, params *calc.Parameters) {
diff --git a/internal/templates/table_templ.go b/internal/templates/table_templ.go index 8d4c5c2..8f7810c 100644 --- a/internal/templates/table_templ.go +++ b/internal/templates/table_templ.go @@ -14,7 +14,7 @@ import ( "debtrecyclingcalc.com/internal/calc" ) -func table(data *calc.DebtRecyclingData, years int) templ.Component { +func table(data *calc.Data, years int) templ.Component { return templruntime.GeneratedTemplate(func(templ_7745c5c3_Input templruntime.GeneratedComponentInput) (templ_7745c5c3_Err error) { templ_7745c5c3_W, ctx := templ_7745c5c3_Input.Writer, templ_7745c5c3_Input.Context templ_7745c5c3_Buffer, templ_7745c5c3_IsBuffer := templruntime.GetBuffer(templ_7745c5c3_W)