Lending Model

Last updated on 26th March 2024

Introduction

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.

FinancialAgent

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.

Balance sheet

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.

Income statement

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

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;
  }
}

Agents default if they enter a state of negative equity

You need to initialise borrowers with cash or only request loans once they have had a positive income flow. Requesting a loan reduces equity on account of the interest paid on loans. If an agent reaches a state of negative equity, they default and play no further role in the simulation. If you would like to change this behaviour simply override the `FinancialAgent#handleDefault` method

Borrower#requestLoan

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)));
  });
}
There are a limited number of configuration options available. For example, the toolkit currently only supports uncollateralized loans. The option has been left in to display how the API will function once the toolkit has matured.

Borrower#payLoans

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

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.

Lender#issueLoan

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.

LendingModel

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.

Creating a loan transaction market

Loan transaction markets represent the lender, borrowers and their underlying connections. They are created by calling LendingModel#generateLoanTopology.

Processing income statements

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.

Example

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.

Classes required

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> {

}

Generating the LoanTransactions

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();
}

No links?

You may be wondering how these agents are connected if we do not specify links. They are created with a specific link which is used when you request a loan. You are free to connect these groups with additional links to other agents but requesting a loan will always use this hidden market link so you do not have to worry about it.

Requesting Loans

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.

Create an action and call requestLoan

static Action<Household> requestLoan() {
  return Action.create(Household.class, h ->  h.requestLoan());
}

Configure the loan

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)));
      });
}

Custom line items (optional)

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);
}

Implementing the lender

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);
}

Running the model

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.