From aa2ab65a85b6301da83bd6ff488fdbb795bc6129 Mon Sep 17 00:00:00 2001 From: Rafa Date: Fri, 10 Feb 2017 10:53:45 +0000 Subject: [PATCH 1/8] Added accounting files --- Account.java | 93 ++++++++++++++++ AccountType.java | 9 ++ Collateral.java | 99 +++++++++++++++++ Ledger.java | 268 +++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 469 insertions(+) create mode 100644 Account.java create mode 100644 AccountType.java create mode 100644 Collateral.java create mode 100644 Ledger.java diff --git a/Account.java b/Account.java new file mode 100644 index 0000000..3173f3e --- /dev/null +++ b/Account.java @@ -0,0 +1,93 @@ +package doubleEntry; + +import doubleEntryComponents.Agent; +import doubleEntryComponents.actions.Action; +import doubleEntryComponents.contracts.Contract; + +import java.util.ArrayList; +import java.util.HashSet; + +public class Account { + + public Account(String name, AccountType accountType, Double startingBalance) { + this.name = name; + this.accountType = accountType; + this.total = startingBalance; + this.contractClass = null; + this.contracts = new HashSet<>(); + } + + public Account(String name, AccountType accountType) { + this(name,accountType,0.0); + } + + private double total; + + private Collateral collateralType; + private AccountType accountType; + private String name; + private Class contractClass; + protected HashSet contracts; + + + public void addContract(Contract contract) { + contracts.add(contract); + } + + /** + * A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. + * @param amount the amount to debit + */ + public void debit(double amount) { + if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { + total += amount; + } else { + total -= amount; + } + } + + /** + * A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. + * @param amount the amount to credit + */ + public void credit(double amount) { + if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { + total -= amount; + } else { + total += amount; + } + } + + + public ArrayList getAvailableActions(Agent me) { + ArrayList availableActions = new ArrayList<>(); + for (Contract contract : contracts) { + availableActions.addAll(contract.getAvailableActions(me)); + } + return availableActions; + } + + public void setCollateralType(Collateral collateralType) { + this.collateralType = collateralType; + } + + public AccountType getAccountType() { + return accountType; + } + + public double getTotal() { + return total; + } + + public void setContractClass(Class contractClass) { + this.contractClass = contractClass; + } + + public Class getContractClass() { + return contractClass; + } + + public String getName() { + return name; + } +} diff --git a/AccountType.java b/AccountType.java new file mode 100644 index 0000000..02227aa --- /dev/null +++ b/AccountType.java @@ -0,0 +1,9 @@ +package doubleEntry; + +public enum AccountType { + ASSET, + LIABILITY, + EQUITY, + INCOME, + EXPENSES +} diff --git a/Collateral.java b/Collateral.java new file mode 100644 index 0000000..d15b1b6 --- /dev/null +++ b/Collateral.java @@ -0,0 +1,99 @@ +package doubleEntry; + +/** + * This interface allows the contents of an account to be placed as collateral. + */ +public interface Collateral { + void pledge() throws Exception; + void pledge(double amount) throws Exception; + void unpledge() throws Exception; + void unpledge(double amount) throws Exception; + boolean isEncumbered(); + double getEncumberedAmount(); +} + +class CanBeCollateral implements Collateral { + + CanBeCollateral(double max) { + encumberedAmount=0.0; + this.max = max; + } + + public void pledge() throws Exception { + if (encumberedAmount= amount) { + encumberedAmount += amount; + } else { + throw new Exception("Not enough unencumbered amount."); + } + } + + public void unpledge() throws Exception { + if (encumberedAmount>0) {encumberedAmount=0;} + else { throw new Exception("Already unencumbered.");} + } + + @Override + public void unpledge(double amount) throws Exception { + if (encumberedAmount >= amount) { + encumberedAmount -= amount; + } else { + throw new Exception("Not enough unencumbered amount."); + } + } + + public boolean isEncumbered() { + return (encumberedAmount>0); + } + + public double getEncumberedAmount() { + return encumberedAmount; + } + + public double getMax() { + return max; + } + + public void setMax(double max) { + this.max = max; + } + + private double encumberedAmount; + private double max; + +} + +class CannotBeCollateral implements Collateral { + public void pledge() throws Exception { + error(); + } + + public void pledge(double amount) throws Exception { + error(); + } + + public void unpledge() throws Exception { + error(); + } + + public void unpledge(double amount) throws Exception { + error(); + } + + private void error() throws Exception { + throw new Exception("Error: This item cannot be pledged (or unpledged) as collateral."); + } + + public boolean isEncumbered() { + return false; + } + + public double getEncumberedAmount() { + return 0; + } +} diff --git a/Ledger.java b/Ledger.java new file mode 100644 index 0000000..a36d5f3 --- /dev/null +++ b/Ledger.java @@ -0,0 +1,268 @@ +package doubleEntry; + +import doubleEntryComponents.Agent; +import doubleEntryComponents.Bank; +import doubleEntryComponents.actions.Action; +import doubleEntryComponents.contracts.Asset; +import doubleEntryComponents.contracts.Contract; +import doubleEntryComponents.contracts.Loan; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +/** + * This is the main class implementing double entry accounting. All public operations provided by this class + * are given 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. + * + * A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several. + */ +public class Ledger { + + public Ledger(Bank bank) { + accounts = new HashSet<>(); // A list of accounts + + // Subsets of the list for quicker searching + assets = new HashSet<>(); + liabilities = new HashSet<>(); + equity = new HashSet<>(); + + // A hashmap relating types of contracts to the account they should be sitting in + defaultAssetAccounts = new HashMap<>(); + // Note that separate hashmaps must exist for assets and liabilities since some contracts can be either + defaultLiabilityAccounts = new HashMap<>(); + this.bank = bank; + } + + + private Bank bank; + private HashSet accounts; + private HashSet assets; + private HashSet liabilities; + private HashSet equity; + private HashMap,Account> defaultAssetAccounts; + private HashMap,Account> defaultLiabilityAccounts; + private Account defaultCashAccount; + private Account defaultEquityAccount; + + public double getAssetValue() { + double assetTotal = 0; + for (Account assetAccount : assets) { + assetTotal+=assetAccount.getTotal(); + } + return assetTotal; + } + + + public double getLiabilityValue() { + double liabilityTotal = 0; + for (Account liabilityAccount : liabilities) { + liabilityTotal+=liabilityAccount.getTotal(); + } + return liabilityTotal; + } + + public double getEquityValue() { + double equityTotal = 0; + for (Account equityAccount : equity) { + equityTotal += equityAccount.getTotal(); + } + return equityTotal; + } + + private void addDefaultAssetAccount (Class contractSubclass, Account account) { + //TODO handle repetitions + defaultAssetAccounts.put(contractSubclass,account); + } + + private void addDefaultLiabilityAccount (Class contractSubclass, Account account) { + //TODO handle repetitions + defaultLiabilityAccounts.put(contractSubclass,account); + } + + public Account getAssetAccountFor (Class contractSubclass) { + return defaultAssetAccounts.get(contractSubclass); + } + + public Account getLiabilityAccountFor (Class contractSubclass) { + return defaultLiabilityAccounts.get(contractSubclass); + } + + public Account getDefaultCashAccount() { + return defaultCashAccount; + } + + public void addAccount(Account account) { + accounts.add(account); + switch(account.getAccountType()) { + + case ASSET: + assets.add(account); +// if (account.getContractClass()!=null) { +// addDefaultAssetAccount(account.getContractClass(),account); +// } else if (account instanceof CashAccount) { +// defaultCashAccount = account; +// } + break; + + case LIABILITY: + liabilities.add(account); +// if (account.getContractClass()!=null) { +// addDefaultLiabilityAccount(account.getContractClass(),account); +// } + break; + + case EQUITY: + equity.add(account); + break; + } + + } + + public void addAccount(Account account, Class contractClass) { + addAccount(account); + if (account.getAccountType()==AccountType.ASSET) { + addDefaultAssetAccount(contractClass,account); + } else if (account.getAccountType()==AccountType.LIABILITY) { + addDefaultLiabilityAccount(contractClass,account); + } + } + + public void addCashAccount(Account account) { + addAccount(account); + defaultCashAccount = account; + } + + public void addEquityAccount(Account account) { + addAccount(account); + defaultEquityAccount = account; + } + + /** + * Adding a contract that is an asset means debiting the assets account relevant to that type of contract. + * @param contract an asset contract to add + */ + public void addAsset(Contract contract) { + Account account = getAssetAccountFor(contract.getClass()); + + account.debit(contract.getValue()); + defaultEquityAccount.credit(contract.getValue()); + + account.addContract(contract); + } + + /** + * Adding a contract that is a liability means *crediting* the liabilities account relevant to that type of contract. + * @param contract a liability contract to add + */ + public void addLiability(Contract contract) { + Account account = getLiabilityAccountFor(contract.getClass()); + + defaultEquityAccount.debit(contract.getValue()); + account.credit(contract.getValue()); + + account.addContract(contract); + } + + public void addCash(double amount) { + defaultCashAccount.debit(amount); + defaultEquityAccount.credit(amount); + } + + public void pullFunding(double amount) { + Account loanAccount = getAssetAccountFor(Loan.class); + + defaultCashAccount.debit(amount); + loanAccount.credit(amount); + + } + + public void payLoan(double amount) { + Account loanAccount = getLiabilityAccountFor(Loan.class); + + // todo This can make the agent go into negative cash. Temporarily, we check here. + if (defaultCashAccount.getTotal() < amount) { + System.out.println(bank.getName()+" must raise liquidity immediately."); + bank.raiseLiquidity(amount * (1 - defaultCashAccount.getTotal()/getAssetValue())); + } + + + loanAccount.debit(amount); + defaultCashAccount.credit(amount); + + } + + public void sellAsset(double amount) { + Account assetAccount = getAssetAccountFor(Asset.class); + + defaultCashAccount.debit(amount); + assetAccount.credit(amount); + } + + public ArrayList getAvailableActions(Agent me) { + ArrayList availableActions = new ArrayList<>(); + for (Account account : accounts) { + availableActions.addAll(account.getAvailableActions(me)); + } + return availableActions; + } + + public void updateAssetPrices() { + Account assetAccount = getAssetAccountFor(Asset.class); + + for (Contract contract : assetAccount.contracts) { + if (contract instanceof Asset) { + Asset asset = (Asset) contract; + if (asset.priceFell()) { + devalueAsset(asset.valueLost()); + asset.updatePrice(); + } + } + } + } + + private void devalueAsset(double amount) { + Account assetAccount = getAssetAccountFor(Asset.class); + + defaultEquityAccount.debit(amount); + assetAccount.credit(amount); + } + + public double getCash() { + return defaultCashAccount.getTotal(); + } + + public void printBalanceSheet() { + System.out.println("Asset accounts:"); + System.out.println("---------------"); + for (Account account : assets) { + System.out.println(account.getName()+" -> "+account.getTotal()); + } + System.out.println("TOTAL ASSETS: "+getAssetValue()); + System.out.println(); + + System.out.println("Liability accounts:"); + System.out.println("---------------"); + for (Account account : liabilities) { + System.out.println(account.getName()+" -> "+account.getTotal()); + } + System.out.println("TOTAL LIABILITIES: "+getLiabilityValue()); + System.out.println(); + } + + public void liquidateLoan(double initialValue, double valueFraction) { + Account assetLoanAccount = getAssetAccountFor(Loan.class); + + double valueLost = (1 - valueFraction) * initialValue; + // First, we devalue the loan :( + defaultEquityAccount.debit(valueLost); + assetLoanAccount.credit(valueLost); + + // Then, we liquidate it + defaultCashAccount.debit(initialValue-valueLost); + assetLoanAccount.credit(initialValue-valueLost); + } +} From c3b561b4873237344b4ed037a5c992a61a930ff1 Mon Sep 17 00:00:00 2001 From: Rafa Date: Sun, 26 Feb 2017 00:54:15 +0000 Subject: [PATCH 2/8] One account <=> One Contract Type Made changes to ensure a one-to-one mapping between accounts and contract types --- Account.java | 59 ++++----- AccountType.java | 2 +- Book.java | 315 +++++++++++++++++++++++++++++++++++++++++++++++ BookAPI.java | 27 ++++ 4 files changed, 369 insertions(+), 34 deletions(-) create mode 100644 Book.java create mode 100644 BookAPI.java diff --git a/Account.java b/Account.java index 3173f3e..3a0a6a3 100644 --- a/Account.java +++ b/Account.java @@ -1,36 +1,36 @@ -package doubleEntry; +package accounting; -import doubleEntryComponents.Agent; -import doubleEntryComponents.actions.Action; -import doubleEntryComponents.contracts.Contract; +import agents.Agent; +import actions.Action; +import contracts.Contract; import java.util.ArrayList; import java.util.HashSet; public class Account { - public Account(String name, AccountType accountType, Double startingBalance) { + private Account(String name, AccountType accountType, Double startingBalance) { this.name = name; this.accountType = accountType; - this.total = startingBalance; + this.balance = startingBalance; this.contractClass = null; this.contracts = new HashSet<>(); } - public Account(String name, AccountType accountType) { + Account(String name, AccountType accountType) { this(name,accountType,0.0); } - private double total; + private double balance; - private Collateral collateralType; +// private Collateral collateralType; private AccountType accountType; private String name; private Class contractClass; protected HashSet contracts; - public void addContract(Contract contract) { + void addContract(Contract contract) { contracts.add(contract); } @@ -38,11 +38,11 @@ public void addContract(Contract contract) { * A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. * @param amount the amount to debit */ - public void debit(double amount) { + void debit(double amount) { if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { - total += amount; + balance += amount; } else { - total -= amount; + balance -= amount; } } @@ -50,44 +50,37 @@ public void debit(double amount) { * A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. * @param amount the amount to credit */ - public void credit(double amount) { + void credit(double amount) { if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { - total -= amount; + balance -= amount; } else { - total += amount; + balance += amount; } } - public ArrayList getAvailableActions(Agent me) { + ArrayList getAvailableActions(Agent me) { ArrayList availableActions = new ArrayList<>(); for (Contract contract : contracts) { - availableActions.addAll(contract.getAvailableActions(me)); + ArrayList contractActions = contract.getAvailableActions(me); + if (contractActions != null) availableActions.addAll(contractActions); } return availableActions; } - public void setCollateralType(Collateral collateralType) { - this.collateralType = collateralType; - } +// public void setCollateralType(Collateral collateralType) { +// this.collateralType = collateralType; +// } - public AccountType getAccountType() { + AccountType getAccountType() { return accountType; } - public double getTotal() { - return total; - } - - public void setContractClass(Class contractClass) { - this.contractClass = contractClass; - } - - public Class getContractClass() { - return contractClass; + double getBalance() { + return balance; } - public String getName() { + String getName() { return name; } } diff --git a/AccountType.java b/AccountType.java index 02227aa..40af40d 100644 --- a/AccountType.java +++ b/AccountType.java @@ -1,4 +1,4 @@ -package doubleEntry; +package accounting; public enum AccountType { ASSET, diff --git a/Book.java b/Book.java new file mode 100644 index 0000000..5f0b7a3 --- /dev/null +++ b/Book.java @@ -0,0 +1,315 @@ +package accounting; + +import agents.Agent; +import actions.Action; +import contracts.Asset; +import contracts.Contract; +import contracts.Loan; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; + +/** + * 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 Book 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 Book. + * + * At the moment, a Book 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 Book, whereas complex firms and banks can have several books + * (as in branch banking for example). + * + * @author rafa + */ +public class Book implements BookAPI { + + public Book(Agent owner) { + // A Book is a list of accounts + accounts = new HashSet<>(); + + // Subsets of the list of accounts (for quicker searching) + assetAccounts = new HashSet<>(); + liabilityAccounts = new HashSet<>(); + equityAccounts = 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. + assetAccountsMap = new HashMap<>(); + liabilityAccountsMap = 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 Agent owner; + private HashSet accounts; + private HashSet assetAccounts; + private HashSet liabilityAccounts; + private HashSet equityAccounts; + private HashMap, Account> assetAccountsMap; + private HashMap, Account> liabilityAccountsMap; + 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 assetAccountsMap.get(contractType).getBalance(); + } + + public double getLiabilityValueOf(Class contractType) { + return liabilityAccountsMap.get(contractType).getBalance(); + } + + public double getCash() { + return cashAccount.getBalance(); + } + + + private void addAccount(Account account, Class contractType) { + accounts.add(account); + switch(account.getAccountType()) { + + case ASSET: + assetAccounts.add(account); + assetAccountsMap.put(contractType, account); + break; + + case LIABILITY: + liabilityAccounts.add(account); + liabilityAccountsMap.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 = assetAccountsMap.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) + assetAccount.debit(contract.getValue()); + equityAccount.credit(contract.getValue()); + + // Add the contract to the account's inventory + assetAccount.addContract(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 = liabilityAccountsMap.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) + equityAccount.debit(contract.getValue()); + liabilityAccount.credit(contract.getValue()); + + liabilityAccount.addContract(contract); + } + + public void addCash(double amount) { + + // (dr cash, cr equity) + cashAccount.debit(amount); + equityAccount.credit(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) { + Account loanAccount = assetAccountsMap.get(Loan.class); + + // (dr cash, cr asset ) + cashAccount.debit(amount); + loanAccount.credit(amount); + + } + + /** + * Operation to pay back a liability loan; debit liability and credit cash + * @param amount amount to pay back + */ + public void payLiability(double amount, Class liabilityType) { + Account liabilityAccount = liabilityAccountsMap.get(liabilityType); + + //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 ) + liabilityAccount.debit(amount); + cashAccount.credit(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 = assetAccountsMap.get(assetType); + + // (dr cash, cr asset) + cashAccount.debit(amount); + assetAccount.credit(amount); + } + + /** + * Behavioral stuff; not sure if it should be here + * @param me the owner of the Book + * @return an ArrayList of Actions that are available to me at this moment + */ + public ArrayList getAvailableActions(Agent me) { + ArrayList availableActions = new ArrayList<>(); + for (Account account : accounts) { + availableActions.addAll(account.getAvailableActions(me)); + } + return availableActions; + } + + /** + * Stress-testing specific. + */ + public void updateAssetPrices() { + Account assetAccount = assetAccountsMap.get(Asset.class); + + for (Contract contract : assetAccount.contracts) { + Asset asset = (Asset) contract; + if (asset.priceFell()) { + devalueAsset(asset.valueLost()); + asset.updatePrice(); + } + } + } + + /** + * if an Asset loses value, I must debit equity and credit asset + * @param amount + */ + private void devalueAsset(double amount) { + Account assetAccount = assetAccountsMap.get(Asset.class); + + // (dr equityAccounts, cr assetAccounts) + equityAccount.debit(amount); + assetAccount.credit(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) { + Account assetLoanAccount = assetAccountsMap.get(Loan.class); + + double valueLost = (1 - valueFraction) * initialValue; + + // First, we devalue the loan :( + // (dr equity, cr asset) + equityAccount.debit(valueLost); + assetLoanAccount.credit(valueLost); + + // Then, we liquidate it + // (dr cash, cr asset) + cashAccount.debit(initialValue-valueLost); + assetLoanAccount.credit(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/BookAPI.java b/BookAPI.java new file mode 100644 index 0000000..502654f --- /dev/null +++ b/BookAPI.java @@ -0,0 +1,27 @@ +package accounting; + +import contracts.Contract; + +/** + * Interface for a Book (operations that must be provided at the very least by a Book implementation). + * + * @author rafa + */ +public interface BookAPI { + + 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, Class liabilityType); + + +} From 2ac4b8b9778901de76ccdcb31fe73d17c32c242e Mon Sep 17 00:00:00 2001 From: Rafa Date: Sun, 26 Feb 2017 00:54:46 +0000 Subject: [PATCH 3/8] Delete Ledger.java --- Ledger.java | 268 ---------------------------------------------------- 1 file changed, 268 deletions(-) delete mode 100644 Ledger.java diff --git a/Ledger.java b/Ledger.java deleted file mode 100644 index a36d5f3..0000000 --- a/Ledger.java +++ /dev/null @@ -1,268 +0,0 @@ -package doubleEntry; - -import doubleEntryComponents.Agent; -import doubleEntryComponents.Bank; -import doubleEntryComponents.actions.Action; -import doubleEntryComponents.contracts.Asset; -import doubleEntryComponents.contracts.Contract; -import doubleEntryComponents.contracts.Loan; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -/** - * This is the main class implementing double entry accounting. All public operations provided by this class - * are given 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. - * - * A simple economic agent will usually have a single Ledger, whereas complex firms and banks can have several. - */ -public class Ledger { - - public Ledger(Bank bank) { - accounts = new HashSet<>(); // A list of accounts - - // Subsets of the list for quicker searching - assets = new HashSet<>(); - liabilities = new HashSet<>(); - equity = new HashSet<>(); - - // A hashmap relating types of contracts to the account they should be sitting in - defaultAssetAccounts = new HashMap<>(); - // Note that separate hashmaps must exist for assets and liabilities since some contracts can be either - defaultLiabilityAccounts = new HashMap<>(); - this.bank = bank; - } - - - private Bank bank; - private HashSet accounts; - private HashSet assets; - private HashSet liabilities; - private HashSet equity; - private HashMap,Account> defaultAssetAccounts; - private HashMap,Account> defaultLiabilityAccounts; - private Account defaultCashAccount; - private Account defaultEquityAccount; - - public double getAssetValue() { - double assetTotal = 0; - for (Account assetAccount : assets) { - assetTotal+=assetAccount.getTotal(); - } - return assetTotal; - } - - - public double getLiabilityValue() { - double liabilityTotal = 0; - for (Account liabilityAccount : liabilities) { - liabilityTotal+=liabilityAccount.getTotal(); - } - return liabilityTotal; - } - - public double getEquityValue() { - double equityTotal = 0; - for (Account equityAccount : equity) { - equityTotal += equityAccount.getTotal(); - } - return equityTotal; - } - - private void addDefaultAssetAccount (Class contractSubclass, Account account) { - //TODO handle repetitions - defaultAssetAccounts.put(contractSubclass,account); - } - - private void addDefaultLiabilityAccount (Class contractSubclass, Account account) { - //TODO handle repetitions - defaultLiabilityAccounts.put(contractSubclass,account); - } - - public Account getAssetAccountFor (Class contractSubclass) { - return defaultAssetAccounts.get(contractSubclass); - } - - public Account getLiabilityAccountFor (Class contractSubclass) { - return defaultLiabilityAccounts.get(contractSubclass); - } - - public Account getDefaultCashAccount() { - return defaultCashAccount; - } - - public void addAccount(Account account) { - accounts.add(account); - switch(account.getAccountType()) { - - case ASSET: - assets.add(account); -// if (account.getContractClass()!=null) { -// addDefaultAssetAccount(account.getContractClass(),account); -// } else if (account instanceof CashAccount) { -// defaultCashAccount = account; -// } - break; - - case LIABILITY: - liabilities.add(account); -// if (account.getContractClass()!=null) { -// addDefaultLiabilityAccount(account.getContractClass(),account); -// } - break; - - case EQUITY: - equity.add(account); - break; - } - - } - - public void addAccount(Account account, Class contractClass) { - addAccount(account); - if (account.getAccountType()==AccountType.ASSET) { - addDefaultAssetAccount(contractClass,account); - } else if (account.getAccountType()==AccountType.LIABILITY) { - addDefaultLiabilityAccount(contractClass,account); - } - } - - public void addCashAccount(Account account) { - addAccount(account); - defaultCashAccount = account; - } - - public void addEquityAccount(Account account) { - addAccount(account); - defaultEquityAccount = account; - } - - /** - * Adding a contract that is an asset means debiting the assets account relevant to that type of contract. - * @param contract an asset contract to add - */ - public void addAsset(Contract contract) { - Account account = getAssetAccountFor(contract.getClass()); - - account.debit(contract.getValue()); - defaultEquityAccount.credit(contract.getValue()); - - account.addContract(contract); - } - - /** - * Adding a contract that is a liability means *crediting* the liabilities account relevant to that type of contract. - * @param contract a liability contract to add - */ - public void addLiability(Contract contract) { - Account account = getLiabilityAccountFor(contract.getClass()); - - defaultEquityAccount.debit(contract.getValue()); - account.credit(contract.getValue()); - - account.addContract(contract); - } - - public void addCash(double amount) { - defaultCashAccount.debit(amount); - defaultEquityAccount.credit(amount); - } - - public void pullFunding(double amount) { - Account loanAccount = getAssetAccountFor(Loan.class); - - defaultCashAccount.debit(amount); - loanAccount.credit(amount); - - } - - public void payLoan(double amount) { - Account loanAccount = getLiabilityAccountFor(Loan.class); - - // todo This can make the agent go into negative cash. Temporarily, we check here. - if (defaultCashAccount.getTotal() < amount) { - System.out.println(bank.getName()+" must raise liquidity immediately."); - bank.raiseLiquidity(amount * (1 - defaultCashAccount.getTotal()/getAssetValue())); - } - - - loanAccount.debit(amount); - defaultCashAccount.credit(amount); - - } - - public void sellAsset(double amount) { - Account assetAccount = getAssetAccountFor(Asset.class); - - defaultCashAccount.debit(amount); - assetAccount.credit(amount); - } - - public ArrayList getAvailableActions(Agent me) { - ArrayList availableActions = new ArrayList<>(); - for (Account account : accounts) { - availableActions.addAll(account.getAvailableActions(me)); - } - return availableActions; - } - - public void updateAssetPrices() { - Account assetAccount = getAssetAccountFor(Asset.class); - - for (Contract contract : assetAccount.contracts) { - if (contract instanceof Asset) { - Asset asset = (Asset) contract; - if (asset.priceFell()) { - devalueAsset(asset.valueLost()); - asset.updatePrice(); - } - } - } - } - - private void devalueAsset(double amount) { - Account assetAccount = getAssetAccountFor(Asset.class); - - defaultEquityAccount.debit(amount); - assetAccount.credit(amount); - } - - public double getCash() { - return defaultCashAccount.getTotal(); - } - - public void printBalanceSheet() { - System.out.println("Asset accounts:"); - System.out.println("---------------"); - for (Account account : assets) { - System.out.println(account.getName()+" -> "+account.getTotal()); - } - System.out.println("TOTAL ASSETS: "+getAssetValue()); - System.out.println(); - - System.out.println("Liability accounts:"); - System.out.println("---------------"); - for (Account account : liabilities) { - System.out.println(account.getName()+" -> "+account.getTotal()); - } - System.out.println("TOTAL LIABILITIES: "+getLiabilityValue()); - System.out.println(); - } - - public void liquidateLoan(double initialValue, double valueFraction) { - Account assetLoanAccount = getAssetAccountFor(Loan.class); - - double valueLost = (1 - valueFraction) * initialValue; - // First, we devalue the loan :( - defaultEquityAccount.debit(valueLost); - assetLoanAccount.credit(valueLost); - - // Then, we liquidate it - defaultCashAccount.debit(initialValue-valueLost); - assetLoanAccount.credit(initialValue-valueLost); - } -} From a8299c2a8d5d3f73fbf767b780879c9afa3a8227 Mon Sep 17 00:00:00 2001 From: Rafa Date: Sun, 26 Feb 2017 00:54:54 +0000 Subject: [PATCH 4/8] Delete Collateral.java --- Collateral.java | 99 ------------------------------------------------- 1 file changed, 99 deletions(-) delete mode 100644 Collateral.java diff --git a/Collateral.java b/Collateral.java deleted file mode 100644 index d15b1b6..0000000 --- a/Collateral.java +++ /dev/null @@ -1,99 +0,0 @@ -package doubleEntry; - -/** - * This interface allows the contents of an account to be placed as collateral. - */ -public interface Collateral { - void pledge() throws Exception; - void pledge(double amount) throws Exception; - void unpledge() throws Exception; - void unpledge(double amount) throws Exception; - boolean isEncumbered(); - double getEncumberedAmount(); -} - -class CanBeCollateral implements Collateral { - - CanBeCollateral(double max) { - encumberedAmount=0.0; - this.max = max; - } - - public void pledge() throws Exception { - if (encumberedAmount= amount) { - encumberedAmount += amount; - } else { - throw new Exception("Not enough unencumbered amount."); - } - } - - public void unpledge() throws Exception { - if (encumberedAmount>0) {encumberedAmount=0;} - else { throw new Exception("Already unencumbered.");} - } - - @Override - public void unpledge(double amount) throws Exception { - if (encumberedAmount >= amount) { - encumberedAmount -= amount; - } else { - throw new Exception("Not enough unencumbered amount."); - } - } - - public boolean isEncumbered() { - return (encumberedAmount>0); - } - - public double getEncumberedAmount() { - return encumberedAmount; - } - - public double getMax() { - return max; - } - - public void setMax(double max) { - this.max = max; - } - - private double encumberedAmount; - private double max; - -} - -class CannotBeCollateral implements Collateral { - public void pledge() throws Exception { - error(); - } - - public void pledge(double amount) throws Exception { - error(); - } - - public void unpledge() throws Exception { - error(); - } - - public void unpledge(double amount) throws Exception { - error(); - } - - private void error() throws Exception { - throw new Exception("Error: This item cannot be pledged (or unpledged) as collateral."); - } - - public boolean isEncumbered() { - return false; - } - - public double getEncumberedAmount() { - return 0; - } -} From e78734ea3a83548455a1f9ba61a5b98b352abbae Mon Sep 17 00:00:00 2001 From: Rafa Date: Mon, 27 Feb 2017 14:40:41 +0000 Subject: [PATCH 5/8] Private debit/credit, created doubleEntry function Made a static, legal `doubleEntry(Account debitAccount, Account creditAccount, double amount)` function --- Account.java | 9 +++++++-- Book.java | 31 +++++++++++++------------------ 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/Account.java b/Account.java index 3a0a6a3..25b70fd 100644 --- a/Account.java +++ b/Account.java @@ -23,6 +23,11 @@ private Account(String name, AccountType accountType, Double startingBalance) { private double balance; + public static void doubleEntry(Account debitAccount, Account creditAccount, double amount) { + debitAccount.debit(amount); + creditAccount.credit(amount); + } + // private Collateral collateralType; private AccountType accountType; private String name; @@ -38,7 +43,7 @@ void addContract(Contract contract) { * A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. * @param amount the amount to debit */ - void debit(double amount) { + private void debit(double amount) { if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { balance += amount; } else { @@ -50,7 +55,7 @@ void debit(double amount) { * A Credit is a negative change for ASSET and EXPENSES accounts, and positive for the rest. * @param amount the amount to credit */ - void credit(double amount) { + private void credit(double amount) { if ((accountType==AccountType.ASSET) || (accountType==AccountType.EXPENSES)) { balance -= amount; } else { diff --git a/Book.java b/Book.java index 5f0b7a3..ef8a8f6 100644 --- a/Book.java +++ b/Book.java @@ -137,8 +137,7 @@ public void addAsset(Contract contract) { } // (dr asset, cr equity) - assetAccount.debit(contract.getValue()); - equityAccount.credit(contract.getValue()); + Account.doubleEntry(assetAccount, equityAccount, contract.getValue()); // Add the contract to the account's inventory assetAccount.addContract(contract); @@ -160,8 +159,7 @@ public void addLiability(Contract contract) { } // (dr equity, cr liability) - equityAccount.debit(contract.getValue()); - liabilityAccount.credit(contract.getValue()); + Account.doubleEntry(equityAccount, liabilityAccount, contract.getValue()); liabilityAccount.addContract(contract); } @@ -169,8 +167,8 @@ public void addLiability(Contract contract) { public void addCash(double amount) { // (dr cash, cr equity) - cashAccount.debit(amount); - equityAccount.credit(amount); + Account.doubleEntry(cashAccount, equityAccount, amount); + } @@ -184,8 +182,8 @@ public void pullFunding(double amount) { Account loanAccount = assetAccountsMap.get(Loan.class); // (dr cash, cr asset ) - cashAccount.debit(amount); - loanAccount.credit(amount); + Account.doubleEntry(cashAccount, loanAccount, amount); + } @@ -207,8 +205,8 @@ public void payLiability(double amount, Class liabilityType) } // (dr liability, cr cash ) - liabilityAccount.debit(amount); - cashAccount.credit(amount); + Account.doubleEntry(liabilityAccount, cashAccount, amount); + } @@ -220,8 +218,7 @@ public void sellAsset(double amount, Class assetType) { Account assetAccount = assetAccountsMap.get(assetType); // (dr cash, cr asset) - cashAccount.debit(amount); - assetAccount.credit(amount); + Account.doubleEntry(cashAccount, assetAccount, amount); } /** @@ -260,8 +257,8 @@ private void devalueAsset(double amount) { Account assetAccount = assetAccountsMap.get(Asset.class); // (dr equityAccounts, cr assetAccounts) - equityAccount.debit(amount); - assetAccount.credit(amount); + Account.doubleEntry(equityAccount, assetAccount, amount); + } /** @@ -281,13 +278,11 @@ public void liquidateLoan(double initialValue, double valueFraction) { // First, we devalue the loan :( // (dr equity, cr asset) - equityAccount.debit(valueLost); - assetLoanAccount.credit(valueLost); + Account.doubleEntry(equityAccount, assetLoanAccount, valueLost); // Then, we liquidate it // (dr cash, cr asset) - cashAccount.debit(initialValue-valueLost); - assetLoanAccount.credit(initialValue-valueLost); + Account.doubleEntry(cashAccount, assetLoanAccount, initialValue - valueLost); } public void printBalanceSheet() { From a4119a26d5fada4e5284580c433670a84ba2d22d Mon Sep 17 00:00:00 2001 From: Rafa Date: Tue, 7 Mar 2017 15:02:09 +0000 Subject: [PATCH 6/8] Implemented changes from code review A `Ledger` now has: - a set of `Account`s - two sets of `Contract`s to act as inventory (one for assets and one for liabilities) By splitting the inventory from the accounts, I have avoided issues to do with searching of the inventories etc. The invariants maintained: - each `Account` corresponds to one type of `Contract`, and the sum of the value of the contracts of that type equals the balance of the account. This invariant is not hard-coded but will be. - Assets = Liabilities + Equity (guaranteed by the double entry) --- Account.java | 26 +--- Ledger.java | 340 +++++++++++++++++++++++++++++++++++++++++++++++++ LedgerAPI.java | 27 ++++ 3 files changed, 369 insertions(+), 24 deletions(-) create mode 100644 Ledger.java create mode 100644 LedgerAPI.java diff --git a/Account.java b/Account.java index 25b70fd..57d9c1a 100644 --- a/Account.java +++ b/Account.java @@ -7,14 +7,12 @@ import java.util.ArrayList; import java.util.HashSet; -public class Account { +class Account { private Account(String name, AccountType accountType, Double startingBalance) { this.name = name; this.accountType = accountType; this.balance = startingBalance; - this.contractClass = null; - this.contracts = new HashSet<>(); } Account(String name, AccountType accountType) { @@ -23,7 +21,7 @@ private Account(String name, AccountType accountType, Double startingBalance) { private double balance; - public static void doubleEntry(Account debitAccount, Account creditAccount, double amount) { + static void doubleEntry(Account debitAccount, Account creditAccount, double amount) { debitAccount.debit(amount); creditAccount.credit(amount); } @@ -31,14 +29,8 @@ public static void doubleEntry(Account debitAccount, Account creditAccount, doub // private Collateral collateralType; private AccountType accountType; private String name; - private Class contractClass; - protected HashSet contracts; - void addContract(Contract contract) { - contracts.add(contract); - } - /** * A Debit is a positive change for ASSET and EXPENSES accounts, and negative for the rest. * @param amount the amount to debit @@ -63,20 +55,6 @@ private void credit(double amount) { } } - - ArrayList getAvailableActions(Agent me) { - ArrayList availableActions = new ArrayList<>(); - for (Contract contract : contracts) { - ArrayList contractActions = contract.getAvailableActions(me); - if (contractActions != null) availableActions.addAll(contractActions); - } - return availableActions; - } - -// public void setCollateralType(Collateral collateralType) { -// this.collateralType = collateralType; -// } - AccountType getAccountType() { return accountType; } 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); + + +} From cb9c049c4b8706ab6d4b94f3356b7732348060a8 Mon Sep 17 00:00:00 2001 From: Rafa Date: Tue, 7 Mar 2017 15:24:38 +0000 Subject: [PATCH 7/8] Deleted Book --- Book.java | 310 ------------------------------------------------------ 1 file changed, 310 deletions(-) delete mode 100644 Book.java diff --git a/Book.java b/Book.java deleted file mode 100644 index ef8a8f6..0000000 --- a/Book.java +++ /dev/null @@ -1,310 +0,0 @@ -package accounting; - -import agents.Agent; -import actions.Action; -import contracts.Asset; -import contracts.Contract; -import contracts.Loan; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; - -/** - * 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 Book 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 Book. - * - * At the moment, a Book 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 Book, whereas complex firms and banks can have several books - * (as in branch banking for example). - * - * @author rafa - */ -public class Book implements BookAPI { - - public Book(Agent owner) { - // A Book is a list of accounts - accounts = new HashSet<>(); - - // Subsets of the list of accounts (for quicker searching) - assetAccounts = new HashSet<>(); - liabilityAccounts = new HashSet<>(); - equityAccounts = 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. - assetAccountsMap = new HashMap<>(); - liabilityAccountsMap = 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 Agent owner; - private HashSet accounts; - private HashSet assetAccounts; - private HashSet liabilityAccounts; - private HashSet equityAccounts; - private HashMap, Account> assetAccountsMap; - private HashMap, Account> liabilityAccountsMap; - 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 assetAccountsMap.get(contractType).getBalance(); - } - - public double getLiabilityValueOf(Class contractType) { - return liabilityAccountsMap.get(contractType).getBalance(); - } - - public double getCash() { - return cashAccount.getBalance(); - } - - - private void addAccount(Account account, Class contractType) { - accounts.add(account); - switch(account.getAccountType()) { - - case ASSET: - assetAccounts.add(account); - assetAccountsMap.put(contractType, account); - break; - - case LIABILITY: - liabilityAccounts.add(account); - liabilityAccountsMap.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 = assetAccountsMap.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 the contract to the account's inventory - assetAccount.addContract(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 = liabilityAccountsMap.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()); - - liabilityAccount.addContract(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) { - Account loanAccount = assetAccountsMap.get(Loan.class); - - // (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, Class liabilityType) { - Account liabilityAccount = liabilityAccountsMap.get(liabilityType); - - //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 = assetAccountsMap.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 Book - * @return an ArrayList of Actions that are available to me at this moment - */ - public ArrayList getAvailableActions(Agent me) { - ArrayList availableActions = new ArrayList<>(); - for (Account account : accounts) { - availableActions.addAll(account.getAvailableActions(me)); - } - return availableActions; - } - - /** - * Stress-testing specific. - */ - public void updateAssetPrices() { - Account assetAccount = assetAccountsMap.get(Asset.class); - - for (Contract contract : assetAccount.contracts) { - Asset asset = (Asset) contract; - if (asset.priceFell()) { - devalueAsset(asset.valueLost()); - asset.updatePrice(); - } - } - } - - /** - * if an Asset loses value, I must debit equity and credit asset - * @param amount - */ - private void devalueAsset(double amount) { - Account assetAccount = assetAccountsMap.get(Asset.class); - - // (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) { - Account assetLoanAccount = assetAccountsMap.get(Loan.class); - - 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(); - } - - - -} From d225f1171b5c0afe6d71906fb564804eba2205fd Mon Sep 17 00:00:00 2001 From: Rafa Date: Tue, 7 Mar 2017 15:24:53 +0000 Subject: [PATCH 8/8] Deleted BookAPI --- BookAPI.java | 27 --------------------------- 1 file changed, 27 deletions(-) delete mode 100644 BookAPI.java diff --git a/BookAPI.java b/BookAPI.java deleted file mode 100644 index 502654f..0000000 --- a/BookAPI.java +++ /dev/null @@ -1,27 +0,0 @@ -package accounting; - -import contracts.Contract; - -/** - * Interface for a Book (operations that must be provided at the very least by a Book implementation). - * - * @author rafa - */ -public interface BookAPI { - - 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, Class liabilityType); - - -}