Документация Engee
Notebook

Кредитный калькулятор

В этой статье рассматривается программная реализация универсального кредитного калькулятора. Основное внимание уделяется внутреннему устройству модуля, ключевым алгоритмам, структурам данных и подходам, обеспечивающим точность, гибкость и удобство интеграции с интерфесом масок кодовых ячеек.

1. Общая архитектура

Модуль построен вокруг центральной функции CreditCalculator, которая выступает фасадом для двух независимых алгоритмов расчёта: аннуитетного и дифференцированного. Все вспомогательные функции инкапсулированы, а возвращаемые данные стандартизированы в виде именованных кортежей (NamedTuple) и таблиц DataFrame из пакета DataFrames.jl.

Основные компоненты:

  • CreditCalculator – диспетчер, выполняющий валидацию входных параметров и выбор метода.
  • calculate_annuity / calculate_differentiated – чистые функции, реализующие финансовые формулы.
  • compare_payment_types – сервисная функция для сравнения двух схем.
  • print_payment_schedule – функция вывода (side‑effect) для форматированного отображения.

Такой дизайн обеспечивает разделение ответственности: вычисления отделены от ввода/вывода, а возвращаемые структуры могут быть использованы для последующего анализа или визуализации.

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

2. Входные параметры и валидация

Функция CreditCalculator использует именованные аргументы с типизацией, что повышает читаемость кода и позволяет задавать значения в любом порядке:

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

Валидация выполняется на входе:

  • Проверка, что сумма, ставка и срок положительны.
  • Проверка, что payment_type принадлежит множеству {"annuity", "differentiated"}.

Это предотвращает некорректные вычисления на раннем этапе и даёт понятные сообщения об ошибках.

3. Алгоритмы расчёта

3.1. Аннуитетные платежи

Аннуитетный коэффициент вычисляется по классической формуле:

image.png

где r = ставка / (100 * n) – месячная процентная ставка, n – срок в месяцах.

Особенности реализации:

  • Ежемесячный платёж рассчитывается как amount * coef.
  • Для каждого месяца вычисляются проценты на остаток и сумма погашения основного долга.
  • Коррекция последнего платежа: из‑за округлений может накопиться погрешность, поэтому на последнем шаге сумма основного долга принудительно устанавливается равной остатку, а платёж пересчитывается. Это гарантирует, что итоговый остаток станет равным нулю.
  • Все суммы округляются до двух знаков после запятой с помощью round(..., digits=2).
  • Данные накапливаются в DataFrame путём последовательных вызовов push!.

3.2. Дифференцированные платежи

Здесь ежемесячная часть основного долга фиксирована:

image.png

Проценты за месяц рассчитываются от текущего остатка. Платёж уменьшается линейно.

Особенности:

  • В отличие от аннуитета, не требуется корректировка последнего платежа, так как остаток обнуляется ровно после последней выплаты (при условии точного деления суммы на целое число месяцев).
  • Для удобства сравнения сохраняются первый и последний платежи.
  • При построении таблицы principal_part округляется только при выводе, но в расчётах используется полная точность, чтобы избежать накопления ошибок.

4. Структуры возвращаемых данных

Оба варианта возвращают NamedTuple с одинаковыми полями, что позволяет писать обобщённый код:

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

Для аннуитета дополнительно есть поле monthly_payment, для дифференцированного – first_payment и last_payment. Такая структура удобна для последующей обработки.

Таблица платежей payments_table содержит колонки:

  • month – номер месяца (Int)
  • date – дата платежа (Date)
  • payment – сумма платежа (Float64)
  • principal – часть, идущая на основной долг (Float64)
  • interest – часть, идущая на проценты (Float64)
  • remaining_debt – остаток долга после платежа (Float64)

Использование DataFrame позволяет применять к результатам все возможности пакета DataFrames.jl (фильтрация, агрегация, объединение) без дополнительных преобразований.

5. Обработка граничных условий

В коде предусмотрены следующие краевые случаи:

  • Нулевые или отрицательные параметры – выбрасывается ошибка.
  • Неверный тип платежа – ошибка с понятным сообщением.
  • Для аннуитета при term = 1 формула аннуитетного коэффициента всё ещё работает, а коррекция последнего платежа гарантирует точность.
  • При дифференцированном расчёте principal_part не округляется, чтобы избежать накопления ошибок при длительных сроках.

6. Тестирование и примеры использования

Представленные в наброске четыре примера служат не только демонстрацией, но и неявными тестами. Рассмотрим, какие аспекты они проверяют.

Пример 1. Аннуитетный кредит

  • Проверяет корректность расчёта аннуитетного коэффициента.
  • Убеждается, что последний платёж скорректирован и остаток обнулён.
  • Демонстрирует округление до копеек.
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

Пример 2. Ипотека (дифференцированные платежи)

  • Проверяет работу с датами (передача start_date и вычисление дат через Dates.Month).
  • Выводит полный график через print_payment_schedule, подтверждая корректность форматирования.
  • Сравнивает первый и последний платежи, чтобы убедиться в линейном убывании.

print_payment_schedule - вывод графика платежей. Функция print_payment_schedule принимает на вход результат CreditCalculator и выводит форматированный отчёт. Её реализация использует строковые манипуляции (rpad, Dates.format) и итерацию по строкам DataFrame. Такой подход отделяет представление от логики расчёта – при необходимости легко заменить вывод на другой (например, в файл или 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

Пример 3. Сравнение типов платежей

  • Проверяет, что функция compare_payment_types возвращает структуру с тремя полями.
  • Сравнивает числовые значения переплат, подтверждая, что аннуитет даёт немного большую переплату при одинаковых параметрах.

compare_payment_types - функция сравнения, она дважды вызывает CreditCalculator с разными payment_type и формирует итоговую таблицу. Таблица создаётся как DataFrame с тремя колонками: показатель, значения для аннуитета и для дифференцированного. Это демонстрирует, как результаты одного модуля могут быть использованы для построения более сложных отчётов.

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(Сумма), rate=Ставка, 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

Пример 4. Анализ данных таблицы платежей

  • Показывает, как извлечь первые пять строк (first).
  • Вычисляет максимум, минимум и сумму процентов – тем самым проверяется, что колонки payment и interest содержат корректные числовые данные.
  • Демонстрирует, что возвращаемый DataFrame полностью функционален и может использоваться для статистического анализа.
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("Первые 5 платежей по ипотеке:")
println(first_payments)
println("\nСтатистика по платежам:")
println("Максимальный платеж: ", maximum(mortgage.payments_table.payment))
println("Минимальный платеж: ", minimum(mortgage.payments_table.payment))
println("Сумма процентов за первый год: ", 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

Вывод

Разработанный модуль демонстрирует эффективное использование возможностей Julia: множественная диспетчеризация через именованные аргументы, типобезопасность, удобные структуры данных (NamedTuple, DataFrame) и функциональную композицию. Такой подход позволяет легко поддерживать код, добавлять новые типы расчётов (например, с учётом досрочного погашения) и интегрировать калькулятор в более крупные финансовые приложения. Тестовые примеры одновременно служат документацией и подтверждением корректности алгоритмов.