Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Double Entry Accounting #4

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open

Conversation

rafabap
Copy link

@rafabap rafabap commented Feb 26, 2017

Hybrid accounting system that uses inventories and double-entry accounting.

The Double-entry is simplified so that it uses only stock accounts (ASSETS, LIABILITIES, EQUITY) and ignores flow accounts (INCOME, EXPENSES). Each account has an inventory of contracts.

Each account can hold only one type of contract in its inventory.

The Agents interact with their accounts via a Book, that provides a series of operations given in BookAPI.

Made changes to ensure a one-to-one mapping between accounts and contract types
@davidrpugh
Copy link
Member

@rafabap Let's start with the Book interface. First, I do not understand the terminology. A Book seems to be a collection of Account (or Contract) objects in which case why not call this a BalanceSheet? Second if a Book is just a collection of contracts then I think we should only allow adding/removing of contracts from the Book as well as valuing assets liabilities and equity. Logic for updating the value of a particular contract should be pushed elsewhere.

Excuse the Scala, but something like the following...

abstract class Contract {
  
  /** agent for whom the contract is a liability */
  def issuer: Agent

  /** agent for whom the contract is an asset */
  def counterparty: Agent

 /** Objective value of a contract */
  def faceValue: Double

} 


/** Immutable storage container for a particular agent's contracts. */
class BalanceSheet[+C <: Contract] private(agent: UUID, contracts: GenSet[C] ) {

  /** Returns a new BalanceSheet with an additional contract .*/
  def add[C1 >: C](contract: C1): BalanceSheet[C1] = {
    new BalanceSheet(agent, contracts + contract)  
  }

  /** Returns a new BalanceSheet after removing a particular contact. */
  def remove(contract: C): BalanceSheet[C] = {
    new BalanceSheet(agent, contracts - contract)
  } 

  /** Collection of contracts that are assets. */
  def assets: GenSet[C] = {
    contracts.filter(contract => contract.counterparty == agent)
  }

  /** Collection of contracts that are liabilities. */
  def liabilities: GenSet[C] = {
    contracts.filter(contract => contract.issuer == agent)
  }

  /** The value of equity is just the difference between the value of assets and the value of liabilities. 
     * @note the value of equity is subjective and depends on the value function `v` used to value the 
     * underlying contracts.
     */
  def equity(v: (Contract) => Double): Double = {
    assets.map(v).sum - liabilities.map(v).sum
  }

  /** Leverage ratio */
  def leverage(v: (Contract) => Double): Double = {
    assets.map(v).sum / equity(v)
  }
  
}

@davidrpugh
Copy link
Member

@rafabap I am also confused about the difference between Contract and an Account.

@davidrpugh
Copy link
Member

davidrpugh commented Feb 26, 2017

My most general comment is that I find the terminology very confusing. I generally do not think of Asset, Liability and Equity as being types of Accounts. But might just be me.

I guess you could have something like...

class DepositAccount private(issuer: UUID, counterparty: UUID, initialDeposit: Double) extends Contract {
  val faceValue = initialDeposit

  def debit(value: Double): DepositAccount = {
    new DepositAccount(issuer, counterparty, initialDeposit - value)
  } 

  def credit(value: Double): DepositAccount = {
    new DepositAccount(issuer, counterparty, initialDeposit + value)
  }

}

val agent: UUID = ???
val bank: UUID = ???

val deposit = DepositContract(bank, agent, 1e6)

in the above

@davidrpugh
Copy link
Member

@rafabap I just realized that Book used to be called Ledger! I much prefer Ledger as it makes good use of standard accounting terminology (which has been picked up by the blockchain community which I think is useful).

@davidrpugh
Copy link
Member

@rafabap Some specific comments about the Account class.

  • Is the account name supposed to uniquely identify the account? If so, then perhaps better to use UUID then String for the type.
  • Why use an Enum to model different account types? Is the Enum values just to allow you to switch behavior depending on account type?
  • Where does the Action type imported from? Is this a Java type? Or has this ours?

@davidrpugh
Copy link
Member

@rafabap Perhaps my lack of comfort with your terminology stems from my being used to Perry Mehrling's terminology. I am reading through the Double-Entry Book-Keeping Wikipedia entry to try and familiarize myself with your terms. Is this the best source?

@rafabap
Copy link
Author

rafabap commented Feb 26, 2017

@davidrpugh sorry for the confusion! Just a few pints to clarify:

  1. double-entry
    The Wikipedia article is one of the best resources I've found. The problem is that although the rules are consistent across sources, the terminology isn't!
    I'm happy to go back to using Ledger rather than Book.

The types of Account only matter when defining the sign of debits and credits.

  1. double entry vs Inventories

I see our accounting system as a hybrid composed of two parts. The first is the double entry rules, which requires separate accounts implementing their debits and credits. The second is the inventory: a way to keep lists of contracts.

My solution is to combine the two by starting with a standard double-entry ledger havjng a few accounts, and then rather than having a single inventory for the agent, I split it in smaller inventories, each associated to one account. In addition, for clarity, I force that each account includes only one type of contract in its inventory.

Account.java Outdated

private double balance;

// private Collateral collateralType;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented variables, unless it is a discussion point whether or not to add them.

Account.java Outdated
protected HashSet<Contract> contracts;


void addContract(Contract contract) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it intentional that these functions scope are the Java default? It's likely that's best, but I didn't know if some, like addContract, should be public?

Account.java Outdated
return availableActions;
}

// public void setCollateralType(Collateral collateralType) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove?

String getName() {
return name;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a removeContract functionality?

String getName() {
return name;
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the user be able to pull a list of the existing contracts in the contract hashSet?

Book.java Outdated
}

// (dr asset, cr equity)
assetAccount.debit(contract.getValue());
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do contracts have a 'getValue()'? Perhaps they should, if they don't. We should discuss contract valuation and how it relates to this system.

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure if valuation shouldn't be external to the contract, e.g. by means of a function that accepts a contract and returns a value. If it is internal to the contract, then it will be difficult to have different types of valuation. Also, if valuation is done in a more functional way, it will become easier to plug together more complex types of valuation. But that's probably a separate discussion.

Book.java Outdated
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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We should probably decide how we're going to deal with errors in the library.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@prauwolf Agreed. I don't know much about the Java conventions regarding checked vs unchecked exceptions, etc. I think we need some kind of LiquidityException that will be thrown in the event that an agent does not have liquid resources available to make required payments. Ideally this would also be expressed in the return type of the method (which would force the caller of the method to explicitly deal with the possibility that an exception would be returned.

Book.java Outdated
* If I've sold an asset, debit cash and credit asset
* @param amount the *value* of the asset
*/
public void sellAsset(double amount, Class<? extends Contract> assetType) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As implemented, what class should be calling 'sellAsset'?

Book.java Outdated
* @param me the owner of the Book
* @return an ArrayList of Actions that are available to me at this moment
*/
public ArrayList<Action> getAvailableActions(Agent me) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss actions in the code review. I really like the idea. Perhaps we should have some kind of interface which defines a list of actions, since we'd probably use them in a variety of contexts.

Book.java Outdated

for (Contract contract : assetAccount.contracts) {
Asset asset = (Asset) contract;
if (asset.priceFell()) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you explain this in a bit more detail in the review?

Book.java Outdated
assetAccounts = new HashSet<>();
liabilityAccounts = new HashSet<>();
equityAccounts = new HashSet<>();

Copy link

@bherd bherd Feb 27, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it strictly necessary to have different hash sets holding the same account objects? Although (I think) Java stores values by reference, I fear there might be situations where, for some reason, the account object gets copied and thus ends up being 'forked'.

Would it be better to use just one single data structure accounts and use clever access functions to get and set the type of content that is relevant? For example, to return all accounts from accounts in a lazy way (i.e. as a stream), you could define the following function (sorry, haven't checked if it compiles):

Stream<Object> getAccounts() {
  return accounts.entrySet().stream.map(Map.Entry::getValue);
}

To return all accounts of a particular type, you could define the following function:

Stream<Object> getAccountsOfType(AccountType t) {
  return accounts.entrySet().stream().filter(entry -> entry.getKey() == t).map(Map.Entry::getValue);
}

In order to turn the streams into lists, you could also define a general function:

List<?> toList(Stream<Object> s) {
  return s.collect(Collectors.toList());
}

The same could, of course be done in the opposite direction, i.e. to set objects in the map. You could then get rid of the other data structures and only keep a single source of truth.

As I said, I haven't checked whether the snippets compile, so please take them with a pinch of salt :)

Book.java Outdated
// 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<>();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

General question: is thread safety an issue at this point in time? If yes, then this would require some sort of synchronisation, because HashMaps and HashSets are (as far as I'm aware) not thread-safe.

@davidrpugh
Copy link
Member

@rafabap and @bherd Based on our discussion today we decided to move forward with a message passing solution for handling interaction between Ledger / Book and Transactions, etc. I think we should take a hard look at Akka. Existence of widely used Java and Scala APIs is nice and using the Actor model as our concurrency solution will help constrain our design space in general...

Made a static, legal `doubleEntry(Account debitAccount, Account creditAccount, double amount)` function
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)
@rafabap
Copy link
Author

rafabap commented Mar 7, 2017

Hi all, I have implemented most of the comments received so far and during the code review. The concurrent implementation of the Ledger is left for a future date.

I have followed David's advice and decided to split the inventories from the Accounts. Therefore, a Ledger has, on the one hand, a set of accounts; and on the other hand, an inventory of contracts (the inventory is split into assets and liabilities for convenience).

Searching etc. of the inventory is done using functional calls as suggested by Ben.

The invariants provided by the framework are:

  • Assets = Liabilities + Equity, as guaranteed by the double entry
  • For each type of Contract, there exists an Account associated with it. The invariant is that the sum of the value of all contracts of that type is equal to the balance of the account. This invariant is not hard-coded yet.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants