Welcome to cred!
What is cred?
cred is a python package for modeling commercial real estate debt. It is designed to enable quickly building and analyzing cash flows for common debt structures while still retaining the flexibility to easily customize debt terms and performance scenarios.
Homepage: https://github.com/jordanhitchcock/cred
Documentation: https://cred.readthedocs.io/en/latest/
Tutorials: Beginner's Guide to Building a Loan with cred
Installation
pip install cred
Quickstart
The main interface for creating cash flows for loans with regular interest periods is PeriodicBorrowing. The FixedRateBorrowing subclass is a convenient class representation of fixed rate debt. The example below builds a borrowing object for a 1 year loan with monthly interest payments at 5.0%.
from datetime import date
from cred import FixedRateBorrowing, Monthly
loan = FixedRateBorrowing(
start_date=date(2020, 1, 1),
end_date=date(2021, 1, 1),
freq=Monthly(months=1),
initial_principal=10_000_000,
coupon=0.05
)
loan.schedule()
Other convenience arguments than can be defined at initialization include the amortization method (default is interest only) and day count convention (default is actual / 360). See the documentation for additional detail.
Calling borrowing.schedule() returns a pandas.DataFrame with the scheduled cash flows:
index start_date end_date payment_date bop_principal interest_rate interest_payment principal_payment payment eop_principal 0 2020-01-01 2020-02-01 2020-02-01 10000000 0.05 43055.555556 0 4.305556e+04 10000000 1 2020-02-01 2020-03-01 2020-03-01 10000000 0.05 40277.777778 0 4.027778e+04 10000000 2 2020-03-01 2020-04-01 2020-04-01 10000000 0.05 43055.555556 0 4.305556e+04 10000000 3 2020-04-01 2020-05-01 2020-05-01 10000000 0.05 41666.666667 0 4.166667e+04 10000000 4 2020-05-01 2020-06-01 2020-06-01 10000000 0.05 43055.555556 0 4.305556e+04 10000000 5 2020-06-01 2020-07-01 2020-07-01 10000000 0.05 41666.666667 0 4.166667e+04 10000000 6 2020-07-01 2020-08-01 2020-08-01 10000000 0.05 43055.555556 0 4.305556e+04 10000000 7 2020-08-01 2020-09-01 2020-09-01 10000000 0.05 43055.555556 0 4.305556e+04 10000000 8 2020-09-01 2020-10-01 2020-10-01 10000000 0.05 41666.666667 0 4.166667e+04 10000000 9 2020-10-01 2020-11-01 2020-11-01 10000000 0.05 43055.555556 0 4.305556e+04 10000000 10 2020-11-01 2020-12-01 2020-12-01 10000000 0.05 41666.666667 0 4.166667e+04 10000000 11 2020-12-01 2021-01-01 2021-01-01 10000000 0.05 43055.555556 10000000 1.004306e+07 0
Floating Rate Example
Floating rate borrowing objects require subclassing PeriodicBorrowing and implementing the interest_rate method. The interest_rate method is called as part of the loop that calculates loan values for each period. It receives an InterestPeriod object which contains index, start_date, and end_date attributes for the period.
The example below generates random index resets between 1.5% and 2.0% and adds a 2.0% margin to calculate total interest.
import random
from cred import PeriodicBorrowing
class MyFloatingRateLoanType(PeriodicBorrowing):
def interest_rate(self, period):
return random.uniform(0.015, 0.02) + 0.02
floating_loan = MyFloatingRateLoanType(
start_date=date(2020, 1, 1),
end_date=date(2021, 1, 1),
freq=Monthly(months=1),
initial_principal=10_000_000
)
floating_loan.schedule()
Output:
index start_date end_date payment_date bop_principal interest_rate interest_payment principal_payment payment eop_principal 0 2020-01-01 2020-02-01 2020-02-01 10000000 0.039226 33778.233665 0 3.377823e+04 10000000 1 2020-02-01 2020-03-01 2020-03-01 10000000 0.036212 29170.599256 0 2.917060e+04 10000000 2 2020-03-01 2020-04-01 2020-04-01 10000000 0.039830 34298.387753 0 3.429839e+04 10000000 3 2020-04-01 2020-05-01 2020-05-01 10000000 0.037286 31072.075651 0 3.107208e+04 10000000 4 2020-05-01 2020-06-01 2020-06-01 10000000 0.038355 33027.953727 0 3.302795e+04 10000000 5 2020-06-01 2020-07-01 2020-07-01 10000000 0.036090 30074.908731 0 3.007491e+04 10000000 # ...
Custom implementations of other cash flow and data fields can similarly be modified by subclassing and overriding the applicable method.
Adding Custom Fields to the Borrowing Schedule
In addition to modifying current schedule columns, new fields can easily be added to the schedule as well. The example below adds two new columns:
- NOI: Net operating income for each month ($60,000 per month, growing monthly at an annual rate of 3.0%)
- DSCR: The debt service coverage ratio for each month based on a constant 6.44% debt service multiple (approximately the debt multiple for a 30 year amortizing loan with 5% interest)
set_period_values is the main method inside schedule that sets period values. Since the two new methods are called after the super class sets its period values, the new columns will be appended to the right side of the schedule.
class MyCustomLoanType(MyFloatingRateLoanType):
def noi(self, period):
return 60000 * (1 + 0.03 / 12 * period.index)
def dscr(self, period):
return period.noi / period.interest_payment
def set_period_values(self, period):
super().set_period_values(period)
period.add_display_field(self.noi(period), 'noi')
period.add_display_field(self.dscr(period), 'dscr')
custom_loan = MyCustomLoanType(
start_date=date(2020, 1, 1),
end_date=date(2021, 1, 1),
freq=Monthly(months=1),
initial_principal=10_000_000
)
custom_loan.schedule()
Result (scroll all the way to the right):
index start_date end_date payment_date bop_principal interest_rate interest_payment principal_payment payment eop_principal noi dscr 0 2020-01-01 2020-02-01 2020-02-01 10000000 0.036185 31159.351494 0 3.115935e+04 10000000 60000.0 1.925586 1 2020-02-01 2020-03-01 2020-03-01 10000000 0.035363 28486.801992 0 2.848680e+04 10000000 60150.0 2.111504 2 2020-03-01 2020-04-01 2020-04-01 10000000 0.035551 30613.195658 0 3.061320e+04 10000000 60300.0 1.969739 3 2020-04-01 2020-05-01 2020-05-01 10000000 0.037290 31075.189753 0 3.107519e+04 10000000 60450.0 1.945282 4 2020-05-01 2020-06-01 2020-06-01 10000000 0.037907 32642.384490 0 3.264238e+04 10000000 60600.0 1.856482 5 2020-06-01 2020-07-01 2020-07-01 10000000 0.037355 31129.007229 0 3.112901e+04 10000000 60750.0 1.951556 # ...
Accessing Period Values
In addition to accessing the entire loan schedule through the schedule method, values for individual periods can be accessed through the borrowing.period method. This method takes the zero-based index of the target period and returns the schedule values for the period as a dictionary.
self.period is the recommended way to recursively pull in values from previous periods when setting period values. For example, after the initial period the beginning-of-period principal (bop_principal) balance is equal to the previous period's ending value. The implementation for the bop_principal method is:
def bop_principal(self, period): if period.index == 0: return self.initial_principal return self.period(period.index - 1).eop_principal
Note
Always reference the current period with the period argument and not through self.period as doing so will cause infinite recursion problems.
Accessing values from previous periods provides a simple and intuitive way to implement recursive calculations, for example capitalizing interest expense for a construction loan.
Period Value Caching
Certain debt assumptions may change during project evaluation or may be unknown prior to building the cash flows. The clearest example is interest rates which change second by second.
In order to avoid accidentally using stale values, Borrowing objects do not store schedule values. They are recalculated any time schedule or period is called. This means that it is safe to update borrowing attributes, and any attribute changes will be reflected in subsequent calls.
Recalculating values for every period could hamper performance if many recursive look-ups exist, however the schedule method is smart and caches previous period values during execution of the method.
Additionally, borrowings have a context manager that will enable period caching on entry and purge cached values on exit.