C++ library and CLI for monthly loan repayment schedules. It supports annuity, differentiated and interest-only payments, three day-count conventions, fixed fees and early repayments on scheduled dates.
An independent project using synthetic examples. No employer code or customer data.
Money stores signed 64-bit minor units. Decimal input is parsed exactly and output always uses two decimal places.
Interest is calculated with integer arithmetic and rounded half up once per period:
interest_minor = round_half_up(opening_minor × rate_parts × day_count / (1e9 × year_basis))
The annuity installment is the smallest whole-cent payment that repays the loan within the term under that rounding rule. Binary search simulates the same integer calculation used for the output rows. The payment must also exceed the highest initial period interest by one cent, so principal decreases even for very small loans. The last installment can be smaller; there is no balloon caused by rounding down a formula result.
The engine keeps these invariants:
opening[n + 1] == closing[n]
final principal == 0
initial principal == Σ principal payments + Σ extra repayments
Payment dates preserve an end-of-month start. For example, a schedule beginning on 2024-01-31 continues on 2024-02-29, 2024-03-31, and so on.
- positive principal with exact two-decimal storage;
- annual interest rate expressed as a decimal percent;
- 1–1200 monthly periods;
- annuity, differentiated or interest-only repayment;
- Actual/365, Actual/360 and 30E/360 day count (selected as
30_360in JSON); - fixed fee per payment;
- one or more partial repayments on payment dates.
Example: examples/annuity-with-prepayment.json.
{
"principal": "250000.00",
"annual_interest_rate_percent": "14.50",
"term_months": 24,
"start_date": "2026-09-15",
"repayment_strategy": "annuity",
"day_count": "actual_365",
"fee_per_payment": "0.00",
"events": [
{
"type": "partial_repayment",
"date": "2027-03-15",
"amount": "30000.00"
}
]
}Requirements: CMake 3.24+, a C++23 compiler and Git. CMake fetches pinned versions of nlohmann/json and Catch2 during configuration.
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel
ctest --test-dir build --output-on-failureJSON output:
./build/loanplan calculate examples/annuity-with-prepayment.jsonCSV output:
./build/loanplan calculate examples/annuity-with-prepayment.json --format csvInvalid money, dates, rates and repayment events fail with a non-zero exit status instead of producing a partial schedule.
- exact money parsing and overflow;
- leap-year and end-of-month dates;
- zero-rate and tiny-principal schedules;
- all three current repayment strategies;
- final-payment rounding correction;
- partial early repayment;
- fees kept separate from principal;
- day-count differences;
- invalid event dates;
- balance continuity and full-repayment invariants.
GitHub Actions builds with GCC and Clang. A separate job runs the test suite under AddressSanitizer and UndefinedBehaviorSanitizer.
Payments are monthly; dates must stay within years 0001–9999. Rate input accepts up to nine decimal places of percent and rounds half up to one part per billion of the annual fraction. Monetary calculations use checked 128-bit intermediates and return signed 64-bit minor units.
Payment holidays, rate changes, balloon schedules, non-payment-date events, currency-specific decimal scales and business-day calendars are not implemented. Early repayments keep the annuity installment and shorten the term; differentiated schedules redistribute the remaining principal over the remaining dates. An early repayment larger than the remaining balance is capped, and no fees accrue after payoff.