Last updated on 16th July 2024
The simudyne.core.abm.finkit.balance.sheet.lending
module allows us to quickly and succinctly implement models that concern themselves with lenders, borrowers and their underlying balance sheets. This is achieved through a set of new APIs and visualisation features on the console. The API sits on top of core-abm
allowing you to implement financial models that can be extended to include interactions between any number of different agents.
Let's take a whistle-stop tour of the key concepts involved in implementing a balance sheet model.
The toolkit exposes two new FinancialAgents
, Borrowers
and Lenders
that you are free to extend. Both these agent types have a balance sheet and an income statement.
The balance sheet keeps track of the assets, liabilities and equity of the agents in our models. On creation, each agent is initialised with a balance sheet object. Initially, it is created with default categories of assets, liabilities and equity as well as a separate cash category under assets.
In addition to default assets and liabilities, you can also register you own line items. line items add a further layer of granularity to assets and liabilities. For example, a loan may be a mortgage or a personal loan. We will discuss how to create and register custom line items as well as how they are used below.
When you create your own FinancialAgent
you will need to implement the following methods.
@Override
public double debits() {
return 0;
}
@Override
public double credits() {
return 0;
}
@Override
public double initialiseWithCash() {
return 0;
}
These afford us the notion of an income statement. At each tick the net result of credits minus debits is calculated and added to the cash item on the balance sheet, this is done for you by the toolkit. You are free to return variables held on the agents or call your own methods that return doubles. This way the income statement of your financial agents can change over time. Note as well that FinancialAgent#initialiseWithCash
is called at setup and allows you to initialise your agents with cash.
Borrowers can be used to represent households, people, or any other agent you wish to request loans. They expose two methods which, allow you to request and pay loans without worrying about the underlying message passing or link manipulation. They can also be initialised with cash which appears as an asset on their balance sheet, and they have the notion of income gained through defining debits and credits.
To create a borrower with this functionality simply extend the Borrower class. You will need to override the functionality associated with income statements which we will discuss in detail later on.
Creating an agent with borrower functionality.
public class Household extends Borrower<GlobalState> {
@Override
public double debits() {
return 0;
}
@Override
public double credits() {
return 0;
}
@Override
public double initialiseWithCash() {
return 0;
}
}
You can request a loan by creating an Action
and calling Borrower#requestLoan
. This method takes a LoanApplication
object as an argument and sends the application along their MarketLink
to the lender. The loan application allows you to construct the loan fluidly. If you are using an IDE the chained methods should auto-complete and the code will not compile until a valid loan has been sent.
Loan request API example.
public static Action<Household> requestLoan() {
return Action.create(Household.class, h -> {
h.requestLoan(Borrower.newLoan()
.ofType(LoanType.UNCOLLATERALIZED)
.withPrinciple(10)
.withTerm(12)
.withInterest(Borrower.interest()
.fixedOrVariable(FixedOrVariable.FIXED)
.interestType(InterestType.SIMPLE)
.withAPR(2)));
});
}
Loans are paid off by calling Borrower#payLoans on an action you define. This method updates the assets and liabilities of the borrower and sends a payment message to the lender of the loan, automatically updating the lender's balance sheets also. Once a loan is paid off the loan is then removed from both lender and borrower.
Paying loans off.
public static Action<Household> payLoansOff() {
return Action.create(Household.class, Borrower::payLoans)
}
Lenders are the other side of the loan transaction system. They represent anyone you want to model as a provider of loans, such as banks. They expose a single functionality which is the issuing of loans while again keeping track of loans and payments for you.
To issue loans create an Action
and call Lender#issueLoan, this methods expects loan requests sent from borrowers previously in the sequence.
Issuing a loan
public static Action<Bank> issue() {
return Action.create(Bank.class, Lender::issueLoan);
}
Once a Lender
has issued a loan it adds a Loan
link to the requesting borrower. The borrower receives notification of the loan and automatically generates a Repayment
link back to the lender. These links are not for you to worry about and are used by the SDK to automate the payment process during the loan lifetime.
The LendingModel
is an extension to the AgentBasedModel
. All models wanting to run with lenders and borrowers must use LendingModel
. Running the simulation in a LendingModel
takes care of calculating the income statement at each tick and checks to see if agents have defaulted. It also provides a number of helper methods for creating topologies of borrowers and lenders.
Loan transaction markets represent the lender, borrowers and their underlying connections. They are created by calling LendingModel#generateLoanTopology
.
Borrowers and lenders have the notion of an income statement, comprised of credits and debits. At the start of every step, before your logic is executed financial agents update their balance sheets with their income. Two instances will cause an agent to stop. Firstly, if their equity drops below zero. Secondly, if the cash in their balance sheet falls below zero, in these cases, the agent will stop participating in the model and handle defaulting. If a borrower defaults all outstanding loans are written off, this has a knock on effect on the lender where a reduction in equity is the result of projected interest profits being written off. When banks default they simply stop and borrower loans are unaffected.
Let’s create a simple model to simulate different borrowers interacting with a single bank. For simplicity, and to get an intuitive idea of how the balance sheet works, we will limit ourselves to a single bank with a small business and a household acting as borrowers.
Let's start by generating the relevant classes; we need two classes
extending Borrower
one for our small business and one for the household. We will need a single Lender
for our bank. We also need a LendingModel
to run everything inside.
We will go ahead and define the credits, debits and initialisation cash directly in the return of our methods, of course in your models these could be functions or even comprised of messages from other agents.
When you are done we should have the following code.
Class representing a small business SmallBusiness.java
import simudyne.core.abm.GlobalState;
import simudyne.core.abm.finkit.balance.sheet.lending.Borrower;
public class SmallBusiness extends Borrower<GlobalState> {
@Override
public double debits() {
return 90;
}
@Override
public double credits() {
return 100;
}
@Override
public double initialiseWithCash() {
return 1000;
}
}
Class representing a household Household.java.
import simudyne.core.abm.GlobalState;
import simudyne.core.abm.finkit.balance.sheet.lending.Borrower;
public class Household extends Borrower<GlobalState> {
@Override
public double debits() {
return 8;
}
@Override
public double credits() {
return 10;
}
@Override
public double initialiseWithCash() {
return 10;
}
}
Class representing a bank Bank.java.
public class Bank extends Lender<GlobalState> {
@Override
public double debits() {
return 0;
}
@Override
public double credits() {
return 0;
}
@Override
public double initialiseWithCash() {
return 0;
}
}
The class our models will run in LendingModel.java.
import simudyne.core.abm.GlobalState;
import simudyne.core.abm.finkit.balance.sheet.lending.LendingModel;
public class LoanTransactionModel extends LendingModel<GlobalState> {
}
We need to create our LoanTransactions
. If you have used the SDK previously, these are simply Groups
of lenders and borrowers connected by a link from the lenders to the borrower. However, the API wraps this extrapolates this logic. Simply call generateLoanTopology
in the setup
passing in the class of the borrower, you can then chain the withBorrowers
passing in different borrower types and finally call build to generate a loan topology.
The class our models will run in LendingModel.java.
@Override
public void setup() {
generateLoanTopology(Bank.class)
.withBorrowers(Household.class, 1)
.withBorrowers(SmallBusiness.class, 1)
.build();
super.setup();
}
Our SmallBusiness
and Household
now need to request loans of varying size. All borrowers expose a requestLoan
method thart can be used inside an action to allow borrowers to request loans.
static Action<Household> requestLoan() {
return Action.create(Household.class, h -> h.requestLoan());
}
Request loan takes a LoanApplication
object. To create a new loan use the method newLoan
. This will set up a chain of method calls that need to be executed in order to fully configure the loan. Currently you can configure the following options.
Type
: This represents if the loan is collateralised or not. Currently there is only functionality to have uncolateralised loans but soon this will be expanded.
Principle
: This is the principle value of the loan before interest.
Term
: This is the number of months the loan will last in months.
Interest
: Is the interest on the loan. It has a further configuration and is initialised by calling interest()
.
Fixed
or Variable
: Currently there is only the functionality to have fixed interest loans but soon it will be possible for lenders to change loan prices.
Simple
Compound
or Amortised
: Currently all interest is simple, again this will be expanded upon.
APR
: This represents the amount of interest being charged on the loan.
After chaining the relevant methods our household loan could look like this. It is an uncolateralised loan with a principle of 10. The loan will last 12 months with simple interest charged at 2 percent.
static Action<Household> requestLoan() {
return Action.create(
Household.class,
h -> {
h.requestLoan(
Borrower.newLoan()
.ofType(LoanType.UNCOLLATERALIZED)
.withPrinciple(10)
.withTerm(12)
.withInterest(
Borrower.interest()
.fixedOrVariable(FixedOrVariable.FIXED)
.interestType(InterestType.SIMPLE)
.withAPR(2)));
});
}
This will send a message to our bank where it can be processed. Similarly our SmallBusiness
can requests loan in the same way.
static Action<SmallBusiness> requestLoan() {
return Action.create(
SmallBusiness.class,
h -> {
h.requestLoan(
Borrower.newLoan()
.ofType(LoanType.UNCOLLATERALIZED)
.withPrinciple(1000)
.withTerm(24)
.withInterest(
Borrower.interest()
.fixedOrVariable(FixedOrVariable.FIXED)
.interestType(InterestType.SIMPLE)
.withAPR(2)));
});
}
At the moment the bank will receive almost exactly the same loan but in reality, a small business loan would appear separately from mortgages on some balance sheets. If you wish to distinguish between loan types create an enum that implements LineItem
we can pass these to our newLoan which will in turn differentiate our loan types on the balance sheet.
Custom line items LineItems.java.
import simudyne.core.abm.finkit.balance.sheet.lending.loan.LineItem;
public enum LineItems implements LineItem {
MORTGAGE{
@Override
public String getName() {
return "mortgage";
}
},
BUSINESS_LOAN {
@Override
public String getName() {
return "business_loan";
}
}
}
These are then passed to the new loan like so
static Action<Household> requestLoan() {
return Action.create(
Household.class,
h -> {
h.requestLoan(
newLoan(LineItems.MORTGAGE)
.ofType(LoanType.UNCOLLATERALIZED)
.withPrinciple(1000)
.withTerm(24)
.withInterest(
interest()
.fixedOrVariable(FixedOrVariable.FIXED)
.interestType(InterestType.SIMPLE)
.withAPR(2)));
});
}
In order for the borrower to know about the line item they must also register them. This can be done in a block in the borrower class.
{
registerLineItems(LineItems.MORTGAGE);
}
There is a method exposed to all Lenders
that gets all loan applications received and accepts them. At the moment all loans are accepted but, in the future, you will be able to predicate if a loan is accepted or not. To accept loans, add the following action to your lender class.
public static Action<Bank> issue() {
return Action.create(Bank.class, Lender::issueLoan);
}
If you implemented your own line items above you will also need to register all the line items each of the borrowers are using.
{
registerLineItems(LineItems.MORTGAGE, LineItems.BUSINESS_LOAN);
}
Override the step method of the LendingModel
. Our household requests a loan at tick 6 and our small business requests one at tick 9. The lender must issue the loan in the same sequence. We must also call the payLoans method at each tick.
@Override
public void step() {
super.step();
run(Action.create(Household.class, Borrower::payLoans));
run(Action.create(SmallBusiness.class, SmallBusiness::payLoans));
if (getContext().getTick() == 6) {
run(Household.requestLoan(), Bank.issue());
}
if (getContext().getTick() == 9) {
run(SmallBusiness.requestLoan(), Bank.issue());
}
}
Once you are done you can see the aggregate behaviour of your agents assets, liabilities and equity.