diff --git a/Account.java b/Account.java new file mode 100644 index 0000000..57d9c1a --- /dev/null +++ b/Account.java @@ -0,0 +1,69 @@ +package accounting; + +import agents.Agent; +import actions.Action; +import contracts.Contract; + +import java.util.ArrayList; +import java.util.HashSet; + +class Account { + + private Account(String name, AccountType accountType, Double startingBalance) { + this.name = name; + this.accountType = accountType; + this.balance = startingBalance; + } + + Account(String name, AccountType accountType) { + this(name,accountType,0.0); + } + + private double balance; + + static void doubleEntry(Account debitAccount, Account creditAccount, double amount) { + debitAccount.debit(amount); + creditAccount.credit(amount); + } + +// private Collateral collateralType; + private AccountType accountType; + private String name; + + + /** + * A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. + * @param amount the amount to debit + */ + private void debit(double amount) { + if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { + balance += amount; + } else { + balance -= amount; + } + } + + /** + * A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. + * @param amount the amount to credit + */ + private void credit(double amount) { + if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { + balance -= amount; + } else { + balance += amount; + } + } + + AccountType getAccountType() { + return accountType; + } + + double getBalance() { + return balance; + } + + String getName() { + return name; + } +} diff --git a/AccountType.java b/AccountType.java new file mode 100644 index 0000000..40af40d --- /dev/null +++ b/AccountType.java @@ -0,0 +1,9 @@ +package accounting; + +public enum AccountType { + ASSET, + LIABILITY, + EQUITY, + INCOME, + EXPENSES +} diff --git a/Ledger.java b/Ledger.java new file mode 100644 index 0000000..0fc704c --- /dev/null +++ b/Ledger.java @@ -0,0 +1,340 @@ +package accounting; + +import agents.Agent; +import actions.Action; +import contracts.Asset; +import contracts.Contract; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.stream.Collectors; + +/** + * This is the main class implementing double entry accounting. All public operations provided by this class + * are performed as a double entry operation, i.e. a pair of (dr, cr) operations. + * + * A Ledger contains a set of accounts, and is the interface between an agent and its accounts. Agents cannot + * directly interact with accounts other than via a Ledger. + * + * At the moment, a Ledger contains an account for each type of contract, plus an equity account and a cash account. + * + * A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several books + * (as in branch banking for example). + * + * @author rafa + */ +public class Ledger implements LedgerAPI { + + public Ledger(Agent owner) { + // A Ledger is a list of accounts (for quicker searching) + assetAccounts = new HashSet<>(); + liabilityAccounts = new HashSet<>(); + equityAccounts = new HashSet<>(); + + allAssets = new HashSet<>(); + allLiabilities = new HashSet<>(); + + // Each Account includes an inventory to hold one type of contract. + // These hashmaps are used to access the correct account for a given type of contract. + // Note that separate hashmaps are needed for asset accounts and liability accounts: the same contract + // type (such as Loan) can sometimes be an asset and sometimes a liability. + contractsToAssetAccounts = new HashMap<>(); + contractsToLiabilityAccounts = new HashMap<>(); + + // Not sure whether I should be passing the owner... + this.owner = owner; + + // A book is initially created with a cash account and an equityAccounts account (it's the simplest possible book) + cashAccount = new Account("cash", AccountType.ASSET); + equityAccount = new Account("equityAccounts", AccountType.EQUITY); + addAccount(cashAccount, null); + addAccount(equityAccount, null); + } + + + private HashSet allAssets; + private HashSet allLiabilities; + private Agent owner; + private HashSet assetAccounts; + private HashSet liabilityAccounts; + private HashSet equityAccounts; + private HashMap, Account> contractsToAssetAccounts; + private HashMap, Account> contractsToLiabilityAccounts; + private Account cashAccount; + private Account equityAccount; + + public double getAssetValue() { + double assetTotal = 0; + for (Account assetAccount : assetAccounts) { + assetTotal+=assetAccount.getBalance(); + } + return assetTotal; + } + + public double getLiabilityValue() { + double liabilityTotal = 0; + for (Account liabilityAccount : liabilityAccounts) { + liabilityTotal+=liabilityAccount.getBalance(); + } + return liabilityTotal; + } + + public double getEquityValue() { + double equityTotal = 0; + for (Account equityAccount : equityAccounts) { + equityTotal += equityAccount.getBalance(); + } + return equityTotal; + } + + public double getAssetValueOf(Class contractType) { +// return contractsToAssetAccounts.get(contractType).getBalance(); + return allAssets.stream() + .filter(contractType::isInstance) + .mapToDouble(Contract::getValue) + .sum(); + } + + public double getLiabilityValueOf(Class contractType) { +// return contractsToLiabilityAccounts.get(contractType).getBalance(); + return allLiabilities.stream() + .filter(contractType::isInstance) + .mapToDouble(Contract::getValue) + .sum(); + + } + + public HashSet getAssetsOfType(Class contractType) { + return allAssets.stream() + .filter(contractType::isInstance) + .collect(Collectors.toCollection(HashSet::new)); + } + + public HashSet getLiabilitiesOfType(Class contractType) { + return allLiabilities.stream() + .filter(contractType::isInstance) + .collect(Collectors.toCollection(HashSet::new)); + + } + + public double getCash() { + return cashAccount.getBalance(); + } + + + private void addAccount(Account account, Class contractType) { + switch(account.getAccountType()) { + + case ASSET: + assetAccounts.add(account); + contractsToAssetAccounts.put(contractType, account); + break; + + case LIABILITY: + liabilityAccounts.add(account); + contractsToLiabilityAccounts.put(contractType, account); + break; + + case EQUITY: + equityAccounts.add(account); + + // Not sure what to do with INCOME, EXPENSES + } + + } + + /** + * Adding an asset means debiting the account relevant to that type of contract + * and crediting equity. + * @param contract an Asset contract to add + */ + public void addAsset(Contract contract) { + Account assetAccount = contractsToAssetAccounts.get(contract.getClass()); + + if (assetAccount==null) { + // If there doesn't exist an Account to hold this type of contract, we create it + assetAccount = new Account(contract.getClass().getName(), AccountType.ASSET); + addAccount(assetAccount, contract.getClass()); + } + + // (dr asset, cr equity) + Account.doubleEntry(assetAccount, equityAccount, contract.getValue()); + + // Add to the general inventory? + allAssets.add(contract); + } + + /** + * Adding a liability means debiting equity and crediting the account + * relevant to that type of contract. + * @param contract a Liability contract to add + */ + public void addLiability(Contract contract) { + Account liabilityAccount = contractsToLiabilityAccounts.get(contract.getClass()); + + if (liabilityAccount==null) { + // If there doesn't exist an Account to hold this type of contract, we create it + liabilityAccount = new Account(contract.getClass().getName(), AccountType.LIABILITY); + addAccount(liabilityAccount, contract.getClass()); + } + + // (dr equity, cr liability) + Account.doubleEntry(equityAccount, liabilityAccount, contract.getValue()); + + // Add to the general inventory? + allLiabilities.add(contract); + } + + public void addCash(double amount) { + // (dr cash, cr equity) + Account.doubleEntry(cashAccount, equityAccount, amount); + + } + + + /** + * Operation to cancel a Loan to someone (i.e. cash in a Loan in the Assets side). + * + * I'm using this for simplicity but note that this is equivalent to selling an asset. + * @param amount the amount of loan that is cancelled + */ + public void pullFunding(double amount, Contract loan) { + Account loanAccount = contractsToAssetAccounts.get(loan.getClass()); + + // (dr cash, cr asset ) + Account.doubleEntry(cashAccount, loanAccount, amount); + } + + /** + * Operation to pay back a liability loan; debit liability and credit cash + * @param amount amount to pay back + */ + public void payLiability(double amount, Contract loan) { + Account liabilityAccount = contractsToLiabilityAccounts.get(loan.getClass()); + + //Todo: What do we do if we can't pay??!! At the moment I'm calling my owner to raise liquidity + if (cashAccount.getBalance() < amount) { + System.out.println(); + System.out.println("***"); + System.out.println(owner.getName()+" must raise liquidity immediately."); + owner.raiseLiquidity(amount * (1 - cashAccount.getBalance()/getAssetValue())); + System.out.println("***"); + System.out.println(); + } + + // (dr liability, cr cash ) + Account.doubleEntry(liabilityAccount, cashAccount, amount); + + + } + + /** + * If I've sold an asset, debit cash and credit asset + * @param amount the *value* of the asset + */ + public void sellAsset(double amount, Class assetType) { + Account assetAccount = contractsToAssetAccounts.get(assetType); + + // (dr cash, cr asset) + Account.doubleEntry(cashAccount, assetAccount, amount); + } + + /** + * Behavioral stuff; not sure if it should be here + * @param me the owner of the Ledger + * @return an ArrayList of Actions that are available to me at this moment + */ + public ArrayList getAvailableActions(Agent me) { + ArrayList availableActions = new ArrayList<>(); + for (Contract contract : allAssets) { + if (contract.getAvailableActions(me)!=null) { + availableActions.addAll(contract.getAvailableActions(me)); + } + } + + for (Contract contract : allLiabilities) { + if (contract.getAvailableActions(me)!= null) { + availableActions.addAll(contract.getAvailableActions(me)); + } + } + + return availableActions; + } + + /** + * Stress-testing specific. + */ + public void updateAssetPrices() { + List allAssets = this.allAssets.stream() + .filter(contract -> contract instanceof Asset) + .collect(Collectors.toList()); + + for (Contract contract : allAssets) { + Asset asset = (Asset) contract; + if (asset.priceFell()) { + devalueAsset(asset.valueLost(), asset); + asset.updatePrice(); + } + } + } + + /** + * if an Asset loses value, I must debit equity and credit asset + * @param amount + */ + private void devalueAsset(double amount, Contract asset) { + Account assetAccount = contractsToAssetAccounts.get(asset.getClass()); + + // (dr equityAccounts, cr assetAccounts) + Account.doubleEntry(equityAccount, assetAccount, amount); + + } + + /** + * This mimics the default on a loan. If I lend money to someone and they default on me, at the moment + * I assume that I lose a 'valueFraction' of its value. There are two double-entry operations: + * + * First I take a hit on equity for the lost value of the loan (dr equity, cr asset) + * Then I cash in the loan (dr cash, cr asset) + * + * @param initialValue the original value of the loan + * @param valueFraction the fraction of the loan that will be lost due to the default + */ + public void liquidateLoan(double initialValue, double valueFraction, Contract loan) { + Account assetLoanAccount = contractsToAssetAccounts.get(loan.getClass()); + + double valueLost = (1 - valueFraction) * initialValue; + + // First, we devalue the loan :( + // (dr equity, cr asset) + Account.doubleEntry(equityAccount, assetLoanAccount, valueLost); + + // Then, we liquidate it + // (dr cash, cr asset) + Account.doubleEntry(cashAccount, assetLoanAccount, initialValue - valueLost); + } + + public void printBalanceSheet() { + System.out.println("Asset accounts:"); + System.out.println("---------------"); + for (Account account : assetAccounts) { + System.out.println(account.getName()+" -> "+ String.format( "%.2f", account.getBalance())); + } + System.out.println("TOTAL ASSETS: "+ String.format( "%.2f", getAssetValue())); + System.out.println(); + + System.out.println("Liability accounts:"); + System.out.println("---------------"); + for (Account account : liabilityAccounts) { + System.out.println(account.getName()+" -> "+ String.format( "%.2f", account.getBalance())); + } + System.out.println("TOTAL LIABILITIES: "+ String.format( "%.2f", getLiabilityValue())); + System.out.println(); + System.out.println("TOTAL EQUITY: "+String.format("%.2f", getEquityValue())); + System.out.println(); + } + + +} diff --git a/LedgerAPI.java b/LedgerAPI.java new file mode 100644 index 0000000..ea3388a --- /dev/null +++ b/LedgerAPI.java @@ -0,0 +1,27 @@ +package accounting; + +import contracts.Contract; + +/** + * Interface for a Ledger (operations that must be provided at the very least by a Ledger implementation). + * + * @author rafa + */ +public interface LedgerAPI { + + double getAssetValue(); + double getLiabilityValue(); + double getEquityValue(); + double getAssetValueOf(Class contractType); + double getLiabilityValueOf(Class contractType); + + void addAsset(Contract contract); + void addLiability(Contract contract); + void addCash(double amount); + + + void sellAsset(double amount, Class assetType); + void payLiability(double amount, Contract liability); + + +}