Engee documentation
Notebook

Credit Calculator

This article discusses the software implementation of a universal credit calculator. The main focus is on the internal structure of the module, key algorithms, data structures, and approaches that ensure accuracy, flexibility, and ease of integration with the interface of code cell masks.

1. General architecture

The module is built around a central function CreditCalculator, which acts as a facade for two independent calculation algorithms: annuity and differential. All auxiliary functions are encapsulated, and the returned data is standardized in the form of named tuples (NamedTuple) and tables DataFrame from the package DataFrames.jl.

Main components:

  • CreditCalculator is a dispatcher that validates input parameters and selects a method.
  • calculate_annuity / calculate_differentiated – pure functions that implement financial formulas.
  • compare_payment_types is a service function for comparing two schemes.
  • print_payment_schedule is an output function (side‑effect) for formatted display.

This design provides a separation of responsibilities: calculations are separated from input/output, and the returned structures can be used for subsequent analysis or visualization.

In [ ]:
include("lib.jl")

2. Input parameters and validation

Function CreditCalculator uses named arguments with typing, which increases the readability of the code and allows you to set values in any order.:

function CreditCalculator(; amount::Float64, rate::Float64, term::Int, 
                          payment_type::String="annuity", 
                          start_date::Date=today(), 
                          loan_type::String="consumer")

Validation is performed at the entrance:

  • Verification that the amount, bid and term are positive.
  • Checking that payment_type belongs to the set {"annuity", "differentiated"}.

This prevents incorrect calculations at an early stage and provides clear error messages.

3. Calculation algorithms

3.1. Annuity payments

The annuity ratio is calculated using the classical formula:

image.png

where r = the rate / (100 * n) is the monthly interest rate, and n is the term in months.

Implementation features:

  • The monthly payment is calculated as amount * coef.
  • For each month, the interest on the balance and the repayment amount of the principal debt are calculated.
  • Correction of the last payment: due to rounding, an error may accumulate, therefore, at the last step, the amount of the principal debt is forcibly set equal to the balance, and the payment is recalculated. This ensures that the final balance will be zero.
  • All amounts are rounded to two decimal places using round(..., digits=2).
  • Data is accumulated in DataFrame by making consecutive calls push!.

3.2. Differentiated payments

Here, the monthly portion of the principal debt is fixed:

image.png

The monthly interest is calculated from the current balance. The payment decreases linearly.

Features:

  • Unlike an annuity, no adjustment of the last payment is required, since the balance is reset exactly after the last payment (provided that the amount is accurately divided by an integer number of months).
  • For the convenience of comparison, the first and last payments are saved.
  • When building the table principal_part It is rounded only during output, but full precision is used in calculations to avoid accumulation of errors.

4. Structures of the returned data

Both options return NamedTuple with the same fields, which allows you to write generalized code.:

(loan_type = ..., payment_type = ..., amount = ..., rate = ..., term = ...,
 total_payment = ..., total_interest = ..., overpayment_percent = ...,
 payments_table = DataFrame(...))

There is an additional field for an annuity monthly_payment, for the differentiated – first_payment and last_payment. This structure is convenient for subsequent processing.

Payment table payments_table contains columns:

  • month – month number (Int)
  • date – date of payment (Date)
  • payment – payment amount (Float64)
  • principal – the part going to the main debt (Float64)
  • interest – the percentage portion (Float64)
  • remaining_debt – debt balance after payment (Float64)

Using DataFrame allows you to apply all the features of the package to the results. DataFrames.jl (filtering, aggregation, merging) without additional transformations.

5. Processing boundary conditions

The following edge cases are provided in the code:

  • Zero or negative parameters – an error is thrown.
  • Incorrect payment type is an error with a clear message.
  • For an annuity at term = 1 The annuity ratio formula is still working, and the correction of the last payment guarantees accuracy.
  • With a differentiated calculation principal_part It is not rounded to avoid accumulating errors over long periods of time.

6. Testing and usage examples

The four examples presented in the sketch serve not only as demonstrations, but also as implicit tests. Let's look at what aspects they check.

Example 1. Annuity loan

  • Checks the correctness of the calculation of the annuity coefficient.
  • Makes sure that the last payment is adjusted and the balance is reset to zero.
  • Demonstrates rounding to pennies.
In [ ]:
Ставка = 9.01 # @param {type:"slider",min:0,max:100,step:0.01}
Срок = 12 # @param {type:"slider",min:1,max:240,step:1}
Сумма = 2151100 # @param {type:"slider",min:100,max:50000000,step:1000}
consumer_loan = CreditCalculator(amount=Float64(Сумма), rate=Ставка, term=Срок, payment_type="annuity", loan_type="consumer")
println(consumer_loan.payments_table)
12×6 DataFrame
 Row │ month  date        payment    principal  interest  remaining_debt
 Int64  Date        Float64    Float64    Float64   Float64
─────┼───────────────────────────────────────────────────────────────────
   1 │     1  2026-03-30  1.88127e5  1.71976e5  16151.2        1.97912e6
   2 │     2  2026-04-30  1.88127e5  1.73267e5  14859.9        1.80586e6
   3 │     3  2026-05-30  1.88127e5  1.74568e5  13559.0        1.63129e6
   4 │     4  2026-06-30  1.88127e5  1.75879e5  12248.3        1.45541e6
   5 │     5  2026-07-30  1.88127e5  1.77199e5  10927.7        1.27821e6
   6 │     6  2026-08-30  1.88127e5  1.7853e5    9597.24       1.09968e6
   7 │     7  2026-09-30  1.88127e5  1.7987e5    8256.78       9.19812e5
   8 │     8  2026-10-30  1.88127e5  1.81221e5   6906.26       7.38592e5
   9 │     9  2026-11-30  1.88127e5  1.82581e5   5545.59       5.5601e5
  10 │    10  2026-12-30  1.88127e5  1.83952e5   4174.71       3.72058e5
  11 │    11  2027-01-30  1.88127e5  1.85333e5   2793.54       1.86725e5
  12 │    12  2027-02-28  1.88127e5  1.86725e5   1401.99       0.0

Example 2. Mortgage (differentiated payments)

  • Checks the work with dates (transfer start_date and calculating dates via Dates.Month).
  • Outputs the full graph via print_payment_schedule, confirming that the formatting is correct.
  • Compares the first and last payments to ensure a linear decrease.

print_payment_schedule - payment schedule output. Function print_payment_schedule accepts the result as input CreditCalculator and outputs a formatted report. Its implementation uses string manipulation (rpad, Dates.format) and iterate through the lines DataFrame. This approach separates the representation from the calculation logic. If necessary, it is easy to replace the output with another one (for example, in a file or HTML).

In [ ]:
start_date = "2026-03-30" # @param {type:"date"}
Ставка = 9.01 # @param {type:"slider",min:0,max:100,step:0.01}
Срок = 12 # @param {type:"slider",min:1,max:240,step:1}
Сумма = 2151100 # @param {type:"slider",min:100,max:50000000,step:1000}
mortgage = CreditCalculator(amount=Float64(Сумма), rate=Ставка, term=Срок, payment_type="differentiated", loan_type="mortgage", start_date=Date(start_date))
print_payment_schedule(mortgage)
График платежей по кредиту:
Тип кредита: mortgage
Тип платежей: differentiated
Сумма: 2.1511e6 руб.
Ставка: 9.01% годовых
Срок: 12 мес.
----------------------------------------------------------------------------------------------------
Первый платеж: 195409.51 руб.
Последний платеж: 180604.26 руб.
Общая сумма выплат: 2.25608264e6 руб.
Переплата: 104982.64 руб. (4.88%)
----------------------------------------------------------------------------------------------------
Месяц | Дата       | Платеж   | Осн.долг | Проценты | Остаток
----------------------------------------------------------------------------------------------------
1     | 30.03.2026| 195409.51| 179258.33| 16151.18 | 1.97184167e6
2     | 30.04.2026| 194063.58| 179258.33| 14805.24 | 1.79258333e6
3     | 30.05.2026| 192717.65| 179258.33| 13459.31 | 1.613325e6
4     | 30.06.2026| 191371.72| 179258.33| 12113.38 | 1.43406667e6
5     | 30.07.2026| 190025.78| 179258.33| 10767.45 | 1.25480833e6
6     | 30.08.2026| 188679.85| 179258.33| 9421.52  | 1.07555e6
7     | 30.09.2026| 187333.92| 179258.33| 8075.59  | 896291.67
8     | 30.10.2026| 185987.99| 179258.33| 6729.66  | 717033.33
9     | 30.11.2026| 184642.06| 179258.33| 5383.73  | 537775.0
10    | 30.12.2026| 183296.13| 179258.33| 4037.79  | 358516.67
11    | 30.01.2027| 181950.2 | 179258.33| 2691.86  | 179258.33
12    | 28.02.2027| 180604.26| 179258.33| 1345.93  | 0.0

Example 3. Comparison of payment types

  • Checks that the function compare_payment_types returns a structure with three fields.
  • Compares the numerical values of overpayments, confirming that the annuity provides a slightly higher overpayment with the same parameters.

compare_payment_types - the comparison function, it calls twice CreditCalculator with different payment_type and generates the final table. The table is created as DataFrame with three columns: indicator, values for annuity and for differentiated. This demonstrates how the results of a single module can be used to build more complex reports.

In [ ]:
Ставка = 9.01 # @param {type:"slider",min:0,max:100,step:0.01}
Срок = 12 # @param {type:"slider",min:1,max:240,step:1}
Сумма = 2151100 # @param {type:"slider",min:100,max:50000000,step:1000}
comparison_result = compare_payment_types(amount=Float64(Amount), rate=Bid, term=Term)
println(comparison_result.comparison)
4×3 DataFrame
 Row │ Показатель                         Аннуитетный            Дифференцированный
 String                             Any                    Any
─────┼─────────────────────────────────────────────────────────────────────────────────
   1 │ Ежемесячный платеж (первый/после…  188126.85 / 188126.85  195409.51 / 180604.26
   2 │ Общая сумма выплат                 2.25752e6              2.25608e6
   3 │ Переплата (абс.)                   1.06422e5              1.04983e5
   4 │ Переплата (%)                      4.95                   4.88

Example 4. Analysis of payment table data

  • Shows how to extract the first five lines (first).
  • Calculates the maximum, minimum, and amount of interest – thereby verifying that the columns payment and interest they contain correct numeric data.
  • Demonstrates that the returned DataFrame It is fully functional and can be used for statistical analysis.
In [ ]:
first_payments = first(mortgage.payments_table, 5)
Out[0]:
5×6 DataFrame
Rowmonthdatepaymentprincipalinterestremaining_debt
Int64DateFloat64Float64Float64Float64
112026-03-301.9541e51.79258e516151.21.97184e6
222026-04-301.94064e51.79258e514805.21.79258e6
332026-05-301.92718e51.79258e513459.31.61332e6
442026-06-301.91372e51.79258e512113.41.43407e6
552026-07-301.90026e51.79258e510767.51.25481e6
In [ ]:
println("The first 5 mortgage payments:")
println(first_payments)
println("\nStatistics on payments:")
println("Maximum payment: ", maximum(mortgage.payments_table.payment))
println("Minimum payment: ", minimum(mortgage.payments_table.payment))
println("The amount of interest for the first year: ", sum(mortgage.payments_table.interest[1:12]))
Первые 5 платежей по ипотеке:
5×6 DataFrame
 Row │ month  date        payment    principal  interest  remaining_debt
 Int64  Date        Float64    Float64    Float64   Float64
─────┼───────────────────────────────────────────────────────────────────
   1 │     1  2026-03-30  1.9541e5   1.79258e5   16151.2       1.97184e6
   2 │     2  2026-04-30  1.94064e5  1.79258e5   14805.2       1.79258e6
   3 │     3  2026-05-30  1.92718e5  1.79258e5   13459.3       1.61332e6
   4 │     4  2026-06-30  1.91372e5  1.79258e5   12113.4       1.43407e6
   5 │     5  2026-07-30  1.90026e5  1.79258e5   10767.5       1.25481e6

Статистика по платежам:
Максимальный платеж: 195409.51
Минимальный платеж: 180604.26
Сумма процентов за первый год: 104982.63999999998

Conclusion

The developed module demonstrates the effective use of Julia's features: multiple dispatching through named arguments, type safety, and convenient data structures (NamedTuple, DataFrame) and a functional composition. This approach makes it easy to maintain the code, add new types of calculations (for example, taking into account early repayment) and integrate the calculator into larger financial applications. The test cases simultaneously serve as documentation and confirmation of the correctness of the algorithms.