A comprehensive Ruby library for generating loan amortization schedules with advanced features including grace periods, interest-only payments, federal bank holidays, and multiple interest calculation methods.
- Multiple payment frequencies: Daily or weekly
- Flexible loan terms: 6, 9, 12, 15, or 18 months
- Interest calculation methods: Simple (accrued daily) or precomputed (spread evenly through payments)
- Grace periods: With automatic interest capitalization
- Interest-only periods: Configure initial interest-only payments
- Fee handling: Origination fees and additional fees with multiple treatment options
- Bank day calculations: Automatically skip weekends and US federal holidays
- Multiple output formats: Console display or CSV export
gem install holidays
gem install rspec # for running testsJust require the file in your project:
require_relative 'amortization_engine'require_relative 'amortization_engine'
schedule = AmortizationSchedule.new(
start_date: "2025-11-15",
principal: 100000.00,
term_months: 12,
annual_rate: 17.75,
frequency: :daily
)
# Display in console
schedule.generate
# Generate CSV file
schedule.generate(output: :csv, csv_path: "schedule.csv")schedule = AmortizationSchedule.new(
start_date: "2025-11-15",
principal: 100000.00,
term_months: 12,
annual_rate: 17.75,
frequency: :daily,
origination_fee: 10000.00, # Added to principal
additional_fee: 2500.00, # Processing fee
additional_fee_treatment: :distributed, # Options: :distributed, :add_to_principal, :separate_payment
bank_days_only: true, # Skip weekends & holidays
interest_only_periods: 10, # First 10 payments are interest-only
grace_period_days: 3, # 3-day grace period
interest_method: :simple # Options: :simple, :precomputed
)
schedule.generate| Parameter | Type | Required | Options | Description |
|---|---|---|---|---|
start_date |
String/Date | Yes | YYYY-MM-DD | Loan start date |
principal |
Float | Yes | - | Loan principal amount |
term_months |
Integer | Yes | 6, 9, 12, 15, 18 | Loan term in months |
annual_rate |
Float | Yes | - | Annual interest rate (%) |
frequency |
Symbol | Yes | :daily, :weekly | Payment frequency |
origination_fee |
Float | No | Default: 0 | Fee added to principal |
additional_fee |
Float | No | Default: 0 | Additional processing fee |
additional_fee_treatment |
Symbol | No | :distributed, :add_to_principal, :separate_payment | How to handle additional fee |
bank_days_only |
Boolean | No | Default: false | Skip weekends and holidays |
interest_only_periods |
Integer | No | Default: 0 | Number of interest-only payments |
grace_period_days |
Integer | No | Default: 0 | Days before first payment |
interest_method |
Symbol | No | :simple, :precomputed | Interest calculation method |
Generate the amortization schedule.
Parameters:
output::consoleor:csvcsv_path: Required if output is:csv
Returns: nil (outputs to console or file)
The fee is spread evenly across all payments.
additional_fee_treatment: :distributedThe fee is added to the loan principal upfront.
additional_fee_treatment: :add_to_principalThe fee is collected as a separate first payment.
additional_fee_treatment: :separate_paymentInterest accrues daily on the remaining balance. As you pay down the principal, interest payments decrease.
Best for: Traditional amortizing loans
interest_method: :simpleTotal interest is calculated upfront and divided equally across all payments. Interest per payment stays constant.
Best for: Fixed payment structures
interest_method: :precomputedWhen bank_days_only: true, the schedule automatically skips:
- Saturday
- Sunday
- New Year's Day
- Martin Luther King Jr. Birthday
- Presidents' Day (Washington's Birthday)
- Memorial Day
- Juneteenth National Independence Day
- Independence Day
- Labor Day
- Columbus Day
- Veterans Day
- Thanksgiving Day
- Christmas Day
Observed dates handled automatically:
- Holidays on Saturday → observed Friday
- Holidays on Sunday → observed Monday
Run the engine directly for an interactive experience:
ruby amortization_engine.rbChoose from:
- Run default examples (demonstrates simple vs precomputed interest)
- Enter parameters manually (interactive prompts)
- Quit
Run the comprehensive test suite:
rspec amortization_spec.rbTest Coverage:
- 29 test cases
- 100+ assertions
- Covers all features and edge cases
schedule = AmortizationSchedule.new(
start_date: "2025-01-15",
principal: 50000.00,
term_months: 6,
annual_rate: 12.0,
frequency: :daily
)
schedule.generateschedule = AmortizationSchedule.new(
start_date: "2025-01-15",
principal: 50000.00,
term_months: 12,
annual_rate: 15.0,
frequency: :daily,
grace_period_days: 5 # Interest capitalizes during grace period
)
schedule.generateschedule = AmortizationSchedule.new(
start_date: "2025-01-15",
principal: 100000.00,
term_months: 18,
annual_rate: 10.0,
frequency: :weekly,
interest_only_periods: 8, # First 8 weeks interest-only
bank_days_only: true # Skip weekends and holidays
)
schedule.generateschedule = AmortizationSchedule.new(
start_date: "2025-01-15",
principal: 100000.00,
term_months: 12,
annual_rate: 17.75,
frequency: :daily,
origination_fee: 10000.00,
additional_fee: 2500.00,
additional_fee_treatment: :distributed,
bank_days_only: true,
interest_only_periods: 10,
grace_period_days: 3,
interest_method: :precomputed
)
schedule.generate(output: :csv, csv_path: "complex_loan.csv")Access schedule data directly for custom processing:
schedule = AmortizationSchedule.new(
start_date: "2025-01-15",
principal: 50000.00,
term_months: 12,
annual_rate: 10.0,
frequency: :daily
)
# Get raw schedule data
schedule_data = schedule.send(:generate_schedule_data)
# Process each payment
schedule_data.each do |payment|
puts "Payment #{payment[:payment_number]}"
puts " Date: #{payment[:date]}"
puts " Principal: $#{'%.2f' % payment[:principal_payment]}"
puts " Interest: $#{'%.2f' % payment[:interest_payment]}"
puts " Balance: $#{'%.2f' % payment[:principal_balance]}"
end
# Calculate totals
total_interest = schedule_data.sum { |row| row[:interest_payment] || 0 }
puts "Total interest: $#{'%.2f' % total_interest}"Formatted table with:
- Payment number
- Payment date
- Days in period
- Principal payment
- Interest payment
- Additional fee payment
- Total payment
- Principal balance
- Accrued interest
- Total balance
- Payment type
Same data as console in spreadsheet-compatible format, suitable for:
- Excel/Google Sheets
- Further data analysis
- Financial reporting
- Record keeping
- Ruby 2.7 or higher
holidaysgem (~> 8.0)
This library depends on the holidays gem for accurate US federal holiday detection. The gem is actively maintained and includes automatic weekend observation rules.
To update holiday data:
gem update holidaysThis project is licensed under the MIT License - see the LICENSE file for details.
Contributions are welcome! Here's how you can help:
Please open an issue with:
- A clear description of the problem
- Steps to reproduce
- Expected vs actual behavior
- Your Ruby version and OS
Open an issue describing:
- The feature you'd like to see
- Why it would be useful
- How it might work
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Add tests for new functionality
- Ensure all tests pass (
rspec amortization_spec.rb) - Commit your changes (
git commit -m 'Add amazing feature') - Push to your branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow Ruby community style guidelines
- Write clear, descriptive variable names
- Add comments for complex logic
- Keep methods focused and concise
- All new features must include RSpec tests
- Ensure all existing tests pass
- Aim for high test coverage
Feel free to open an issue for any questions or discussions.
For issues or questions:
- Run the test suite to verify installation:
rspec amortization_spec.rb - Check the examples above
- Review the API reference
- Initial release
- Support for daily and weekly payment frequencies
- Simple and precomputed interest methods
- Grace period calculations
- Interest-only periods
- Bank holiday awareness
- Multiple fee treatment options
- CSV and console output
- Comprehensive RSpec test suite