eu.crisis_economics.abm.fund.FundTest.java Source code

Java tutorial

Introduction

Here is the source code for eu.crisis_economics.abm.fund.FundTest.java

Source

/*
 * This file is part of CRISIS, an economics simulator.
 * 
 * Copyright (C) 2015 Ross Richardson
 * Copyright (C) 2015 Milan Lovric
 * Copyright (C) 2015 John Kieran Phillips
 * Copyright (C) 2015 Daniel Tang
 *
 * CRISIS is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * CRISIS is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with CRISIS.  If not, see <http://www.gnu.org/licenses/>.
 */
package eu.crisis_economics.abm.fund;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Random;

import org.apache.commons.math3.analysis.UnivariateFunction;
import org.testng.Assert;
import org.testng.annotations.AfterMethod;
import org.testng.annotations.BeforeMethod;
import org.testng.annotations.Test;

import eu.crisis_economics.abm.IAgent;
import eu.crisis_economics.abm.algorithms.portfolio.returns.FundamentalistStockReturnExpectationFunction;
import eu.crisis_economics.abm.algorithms.portfolio.smoothing.NoSmoothingAlgorithm;
import eu.crisis_economics.abm.algorithms.portfolio.weighting.HomogeneousPortfolioWeighting;
import eu.crisis_economics.abm.algorithms.portfolio.weighting.LinearAdaptivePortfolioWeighting;
import eu.crisis_economics.abm.bank.Bank;
import eu.crisis_economics.abm.contracts.Contract;
import eu.crisis_economics.abm.contracts.FixedValueContract;
import eu.crisis_economics.abm.contracts.InsufficientFundsException;
import eu.crisis_economics.abm.contracts.loans.Loan;
import eu.crisis_economics.abm.contracts.stocks.UniqueStockExchange;
import eu.crisis_economics.abm.markets.clearing.ClearingHouse;
import eu.crisis_economics.abm.markets.clearing.heterogeneous.ClearingLoanMarket;
import eu.crisis_economics.abm.markets.clearing.heterogeneous.ClearingStockMarket;
import eu.crisis_economics.abm.markets.clearing.heterogeneous.MockClearingLoanMarket;
import eu.crisis_economics.abm.simulation.EmptySimulation;
import eu.crisis_economics.abm.simulation.Simulation;
import eu.crisis_economics.abm.simulation.injection.AbstractCollectionProvider;

public class FundTest {
    public Simulation sim;
    public Bank myBank;
    public MutualFund myFund;
    public HouseholdStub[] myHouseholds;
    public FirmStub[] myFirms;
    double startingFundDeposits;
    private ClearingHouse clearingHouse;
    static final int N_HOUSEHOLDS = 1000;
    static final int ITERATIONS = 500;
    static final int N_FIRMS = 10;

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    public class MHouseholdStrategy {
        InvestmentAccountHolder iInvestmentAccount;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    @BeforeMethod
    public void setUp() {
        System.out.println("Testing mutual funds..");

        sim = new EmptySimulation(1);
        clearingHouse = new ClearingHouse();
        startingFundDeposits = 0.0;
        myBank = new Bank(1000000.0);
        myFund = new MutualFund(myBank, new LinearAdaptivePortfolioWeighting(),
                new FundamentalistStockReturnExpectationFunction(), clearingHouse, new NoSmoothingAlgorithm());
        myFund.debit(startingFundDeposits);

        int i;
        myHouseholds = new HouseholdStub[N_HOUSEHOLDS];
        for (i = 0; i < N_HOUSEHOLDS; ++i) {
            myHouseholds[i] = new HouseholdStub(myFund, myBank, 200);
        }

        myFirms = new FirmStub[N_FIRMS];
        for (i = 0; i < N_FIRMS; ++i) {
            myFirms[i] = new FirmStub(myBank, 100);
            UniqueStockExchange.Instance.addStock(myFirms[i], 1.0);
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    @Test
    public void runAllFundsTests() throws InsufficientFundsException {
        testInvestmentAccount(myHouseholds[0]);
        testInvestmentAccount(myHouseholds[1]);
        myFund.mInvestmentAccount.setEmissionPrice(4.0);
        testInvestmentAccount(myHouseholds[0]);
        testInvestmentAccount(myHouseholds[1]);
        myFund.mInvestmentAccount.setEmissionPrice(1.0);
        // testSharePriceCalculation();
        testHouseholdFundMarketInteraction();
        testPrivateEquityConstancyAndConservationOfCash();
        testLongTermNetWithdrawal();
        testInvestmentFollowedByCompleteWithdrawal();
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////   
    public void testHouseholdFundMarketInteraction() throws InsufficientFundsException {
        double totalInvestment;
        double[] householdDesiredInvestment = new double[N_HOUSEHOLDS];
        double totalLoanValue;
        int j;
        Random rand = new Random();
        rand.setSeed(12345);
        final MockClearingLoanMarket firstVirtualLoanMarket = new MockClearingLoanMarket(myFund, myBank, "Bonds",
                "3% Coupon Bond", 0.03, clearingHouse),
                secondVirtualLoanMarket = new MockClearingLoanMarket(myFund, myBank, "Bonds", "5% Coupon Bond",
                        0.05, clearingHouse);
        clearingHouse.addMarket(firstVirtualLoanMarket);
        clearingHouse.addMarket(secondVirtualLoanMarket);
        final List<ClearingStockMarket> stocks = new ArrayList<ClearingStockMarket>();

        System.out.println("Testing Household/MutualFund/Market interaction");
        for (j = 0; j < N_FIRMS; ++j) {
            myFund.portfolio.addStock(myFirms[j].getUniqueName());
            myFirms[j].setMarketValue(10.0 * rand.nextDouble());
            myFirms[j].setDividendPerShare(rand.nextDouble() * myFirms[j].getMarketValue());
            final ClearingStockMarket stockMarket = new ClearingStockMarket(myFirms[j], clearingHouse);
            stocks.add(stockMarket);
            clearingHouse.addMarket(stockMarket);
        }

        myFund.setLoansToInvestIn(new AbstractCollectionProvider<ClearingLoanMarket>() {
            @Override
            public Collection<ClearingLoanMarket> get() {
                return clearingHouse.getMarketsOfType(ClearingLoanMarket.class);
            }
        });
        myFund.setStocksToInvestIn(new AbstractCollectionProvider<ClearingStockMarket>() {
            @Override
            public Collection<ClearingStockMarket> get() {
                return clearingHouse.getMarketsOfType(ClearingStockMarket.class);
            }
        });

        for (int i = 0; i < ITERATIONS; ++i) {
            totalInvestment = 0.0;
            for (j = 0; j < N_HOUSEHOLDS; ++j) {
                householdDesiredInvestment[j] = myHouseholds[j].investmentRandomAmount();
                totalInvestment += householdDesiredInvestment[j];
            }
            firstVirtualLoanMarket.expireLoans();
            secondVirtualLoanMarket.expireLoans();
            myFund.preClearingProcessing();
            firstVirtualLoanMarket.process();
            secondVirtualLoanMarket.process();
            for (final ClearingStockMarket market : stocks)
                market.process();
            myFund.postClearingProcessing();
            totalLoanValue = 0.0;
            for (Loan loan : myFund.getAssetLoans()) {
                totalLoanValue += loan.getValue();
            }
            //          cashWeight = myFund.getBalance()/(myFund.marketCap()+myFund.equityCapital());
            if (totalLoanValue < totalInvestment) {
                //              Assert.assertEquals(cashWeight, 
                //                      Math.min(MutualFund.INITIAL_CASH_WEIGHT,
                //                              (totalInvestment-totalLoanValue+myFund.equityCapital())/(totalInvestment+myFund.equityCapital())),
                //                      1e-4);
                for (j = 0; j < N_HOUSEHOLDS; ++j) {
                    Assert.assertEquals(myFund.getBalance(myHouseholds[j]), householdDesiredInvestment[j], 1e-8);
                    Assert.assertEquals(myHouseholds[j].getEquity(), 200.0, 1e-8);
                }
            }
            Assert.assertEquals(myBank.getEquity(), 1000000.0, 1e-6);
        }

        double oldShareCount = myFund.mInvestmentAccount.getNumberOfEmittedShares();
        myFund.mInvestmentAccount.recountShareDistribution();
        double shareCountDrift = myFund.mInvestmentAccount.getNumberOfEmittedShares() - oldShareCount;
        System.out.println("Share count drift = " + shareCountDrift);

        myFund.portfolio.stockWeights().clear();
        System.out.println("Done testing Household/MutualFund/Market interaction");
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////   
    public void testInvestmentAccount(HouseholdStub myHousehold) throws InsufficientFundsException {
        double balance;
        double initialFundBalance;

        System.out.println("Testing InvestmentAccount interface on household's fund");

        myFund.invest(myHousehold, 100.0); // set up InvestmentAccount contract if not already there

        // ---- Zero investment
        initialFundBalance = myFund.getBalance();
        myFund.invest(myHousehold, 0.0);
        balance = myFund.getBalance();
        Assert.assertEquals(balance, initialFundBalance);
        Assert.assertEquals(myFund.getBalance(myHousehold), 100.0, 1.e-10);

        // --- positive investment
        myFund.invest(myHousehold, 100.0);
        balance = myFund.getBalance();
        Assert.assertEquals(balance, initialFundBalance + 100.0, 1.e-10);
        Assert.assertEquals(myFund.getBalance(myHousehold), 200.0, 1.e-10);

        // --- negative investment
        myFund.invest(myHousehold, -100.0);
        balance = myFund.getBalance();
        Assert.assertEquals(balance, initialFundBalance + 100.0, 1.e-10);
        Assert.assertEquals(myFund.getBalance(myHousehold), 200.0, 1.e-10);

        myFund.requestWithdrawal(myHousehold, 50);
        myFund.settleOrderBook(myFund.getBalance() - startingFundDeposits);
        balance = myFund.getBalance();
        Assert.assertEquals(balance, initialFundBalance + 50, 1.e-10);
        Assert.assertEquals(myFund.getBalance(myHousehold), 150.0, 1.e-10);

        // --- 2 orders from same household in a timestep
        myFund.requestWithdrawal(myHousehold, 25);
        myFund.requestWithdrawal(myHousehold, 25);
        myFund.settleOrderBook(myFund.getBalance() - startingFundDeposits);
        balance = myFund.getBalance();
        Assert.assertEquals(balance, initialFundBalance);
        Assert.assertEquals(myFund.getBalance(myHousehold), 100.0, 1.e-10);

        // --- check for proper deletion of orders after settlement
        myFund.settleOrderBook(myFund.getBalance() - startingFundDeposits);
        balance = myFund.getBalance();
        Assert.assertEquals(balance, initialFundBalance);
        Assert.assertEquals(myFund.getBalance(myHousehold), 100.0, 1.e-10);

        // --- negative withdrawal
        myFund.requestWithdrawal(myHousehold, -100.0);
        balance = myFund.getBalance();
        myFund.settleOrderBook(myFund.getBalance() - startingFundDeposits);
        Assert.assertEquals(balance, initialFundBalance);
        Assert.assertEquals(myFund.getBalance(myHousehold), 100.0, 1.e-10);

        myFund.requestWithdrawal(myHousehold, 100);
        myFund.settleOrderBook(myFund.getBalance() - startingFundDeposits);

        System.out.println("Done testing InvestmentAccount");
    }

    /////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////////////////////////////////////////////////////   
    public void testSharePriceCalculation() throws InsufficientFundsException {
        System.out.println("Testing calculation of share price");
        // emission price is 1 initially.
        myFund.invest(myHouseholds[0], 70); // sell 70 shares @ 1 each (initial emission price)
        myFund.invest(myHouseholds[1], 30); // to household #1, + 30 shares to household #2.

        FixedValueContract testEquity = new FixedValueContract();
        testEquity.setValue(200.0);
        myFund.addAsset(testEquity); // assets of the fund = 310.
        myFund.postClearingProcessing();
        Assert.assertEquals(myFund.getEmissionPrice(), 3.0); // (310 - 10) / 100 shares 

        myFund.removeAsset(testEquity); // assets of the fund = 110.
        myFund.postClearingProcessing();
        Assert.assertEquals(myFund.getEmissionPrice(), 1.0); // (110 - 10) / 100 shares

        System.out.println("Done testing share price calculation");
    }

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    /**
      * This test is in two parts.
      * Part 1: a new fund is created with an initial deposit. This deposit is
      *         reserved as private equity. A collection of household stubs are
      *         created with ample starting deposits. For a number of business
      *         cycles, the households all withdraw or contribute random sums to
      *         the fund. The size of these sums is drawn from a Gaussian 
      *         distribution with custom mean and variance. After a fixed number
      *         of business cycles have elapsed, the total amount of cash owned
      *         by each household (be this in fund investments or bank deposits)
      *         is counted. Since the fund has no investment sources and cannot
      *         make a profit, it is expected that no household ever loses or 
      *         gains assets (cash plus fund account value). At every step, it is 
      *         further asserted that the equity of the fund is not significantly
      *         different to the private (constant) equity.
      * Part 2: the fund and all households are destroyed and respawned. Households
      *         begin with ample deposits. The test now continues as in Part 1, except
      *         that the fund deposit account is directly injected with exogenous
      *         profits during each business cycle. The size of these profits is 
      *         drawn from a Gaussian distribution with custom mean and variance.
      *         At the conclusion of Part 2, the amount of cash in the system
      *         is tallied. It is then asserted that the sum of all household deposits
      *         and fund account values is equal to the initial cash in the system 
      *         plus total exogenous profits.
      * Part 3: as Part 2, except the fund now makes exogenous losses as well as
      *         profits. In this test, the fund is deliberately taken to the edge of
      *         its private equity due to market losses.
      */
    public void testPrivateEquityConstancyAndConservationOfCash() {

        /*
         * Test parameters
         */
        final int numberOfBusinessCycles = 1000;
        final double meanFundProfit = 1.e6, fundProfitVariance = 2.e5, meanHouseholdInvestment = 0.,
                householdInvestmentVariances = 1000., householdInitialDeposits = 1.e7, fundInitialDeposits = 0.;
        Random dice = new Random(12345);

        myFund = new MutualFund(myBank, new HomogeneousPortfolioWeighting(),
                new FundamentalistStockReturnExpectationFunction(), clearingHouse, new NoSmoothingAlgorithm()); // Fresh fund

        myFund.debit(fundInitialDeposits);
        final Contract exogenousReturns = myFund.mDepositor.depositAccount;
        Assert.assertEquals(myFund.getEquity(), fundInitialDeposits);
        for (int i = 0; i < myHouseholds.length; ++i) {
            myHouseholds[i] = // Ample household liquidity
                    new HouseholdStub(myFund, myBank, householdInitialDeposits);
            Assert.assertEquals(myHouseholds[i].getTotalAssets(), householdInitialDeposits, 1.e-5);
        }

        /*
         * Part 1
         */

        for (int i = 0; i < numberOfBusinessCycles; ++i) {
            // Random household investments and withdrawals
            for (int j = 0; j < N_HOUSEHOLDS; ++j) {
                final double investment = dice.nextGaussian() * householdInvestmentVariances
                        + meanHouseholdInvestment;
                try {
                    if (investment > 0.)
                        myFund.invest(myHouseholds[j], investment);
                    else if (investment < 0.) {
                        final double maximumWithdrawal = myFund.getBalance(myHouseholds[j]);
                        if (maximumWithdrawal == 0.)
                            continue;
                        myFund.requestWithdrawal(myHouseholds[j], Math.min(-investment, maximumWithdrawal));
                    }
                } catch (final InsufficientFundsException noFunds) {
                    Assert.fail();
                }
            }
            myFund.preClearingProcessing();
            // No profits from fund investments.
            try {
                myFund.postClearingProcessing();
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }
            System.out.printf("MutualFund assets:      %16.10g\n", myFund.getTotalAssets());
            System.out.printf("MutualFund liabilities: %16.10g\n", myFund.getTotalLiabilities());
            System.out.printf("MutualFund equity:      %16.10g\n", myFund.getEquity());

            // Assertions
            final double postClearingEquity = myFund.getEquity();
            Assert.assertEquals(postClearingEquity, fundInitialDeposits, 1.e-5);

            continue;
        }

        // Assert no cash has been created
        System.out.printf("household  deposits [open]  fund investments [open]"
                + "  deposits [close] fund investments [close] sum [close]\n");
        for (int i = 0; i < N_HOUSEHOLDS; ++i) { // Assert no money creation
            final double closingHouseholdDeposits = myHouseholds[i].bankAccount.getBalance(),
                    closingFundAccountValue = myFund.getBalance(myHouseholds[i]);
            System.out.printf("%5d %16.10g %16.10g         %16.10g %16.10g         %16.10g\n", i,
                    householdInitialDeposits, 0., closingHouseholdDeposits, closingFundAccountValue,
                    closingHouseholdDeposits + closingFundAccountValue);
            Assert.assertEquals(closingHouseholdDeposits + closingFundAccountValue, householdInitialDeposits,
                    1.e-6);
        }

        /*
         * Part 2
         */

        // Rerun with exogenous fund profits
        double totalFundProfits = 0.;
        for (int i = 0; i < numberOfBusinessCycles; ++i) {
            // Random household investments and withdrawals
            for (int j = 0; j < N_HOUSEHOLDS; ++j) {
                final double investment = dice.nextGaussian() * householdInvestmentVariances
                        + meanHouseholdInvestment;
                try {
                    if (investment > 0.)
                        myFund.invest(myHouseholds[j], investment);
                    else if (investment < 0.) {
                        final double maximumWithdrawal = myFund.getBalance(myHouseholds[j]);
                        if (maximumWithdrawal == 0.)
                            continue;
                        myFund.requestWithdrawal(myHouseholds[j], Math.min(-investment, maximumWithdrawal));
                    }
                } catch (final InsufficientFundsException noFunds) {
                    Assert.fail();
                }
            }
            myFund.preClearingProcessing();
            final double fundProfits = dice.nextGaussian() * fundProfitVariance + meanFundProfit;
            exogenousReturns.setValue(exogenousReturns.getValue() + fundProfits);
            totalFundProfits += fundProfits;
            try {
                myFund.postClearingProcessing();
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }

            System.out.printf("MutualFund profits:     %16.10g\n", fundProfits);
            System.out.printf("MutualFund assets:      %16.10g\n", myFund.getTotalAssets());
            System.out.printf("MutualFund liabilities: %16.10g\n", myFund.getTotalLiabilities());
            System.out.printf("MutualFund equity:      %16.10g\n", myFund.getEquity());

            System.out.println("Number of shares: " + myFund.mInvestmentAccount.getNumberOfEmittedShares());

            // Assertions
            final double postClearingEquity = myFund.getEquity();
            Assert.assertEquals(postClearingEquity, fundInitialDeposits, 1.e-1);

            continue;
        }

        // Tally the total amount of cash in the system.
        double totalHouseholdProfits = 0.;
        System.out.printf("household  deposits [open]  fund investments [open]"
                + "  deposits [close] fund investments [close] sum [close]\n");
        for (int i = 0; i < N_HOUSEHOLDS; ++i) { // Assert no money creation
            final double closingHouseholdDeposits = myHouseholds[i].bankAccount.getBalance(),
                    closingFundAccountValue = myFund.getBalance(myHouseholds[i]);
            System.out.printf("%5d %16.10g %16.10g         %16.10g %16.10g         %16.10g\n", i,
                    householdInitialDeposits, 0., closingHouseholdDeposits, closingFundAccountValue,
                    closingHouseholdDeposits + closingFundAccountValue);
            totalHouseholdProfits += (closingHouseholdDeposits + closingFundAccountValue)
                    - householdInitialDeposits;
        }

        System.out.printf("Aggregate household profits: %16.10g. MutualFund profits: %16.10g\n",
                totalHouseholdProfits, totalFundProfits);
        Assert.assertEquals(totalHouseholdProfits / Math.pow(10., (int) Math.log10(totalHouseholdProfits)),
                totalFundProfits / Math.pow(10., (int) Math.log10(totalFundProfits)), 1.e-12);

        /*
         * Part 3
         */

        // Rerun with predetermined fund losses.
        UnivariateFunction forcedFundAssetPosition = new UnivariateFunction() {
            @Override
            public double value(double time) {
                return fundInitialDeposits + 1.e-5
                        + householdInvestmentVariances * (1. + Math.sin(2. * Math.PI * time / 50.));
            }
        };
        for (int i = 0; i < numberOfBusinessCycles; ++i) {
            // Random household investments and withdrawals
            for (int j = 0; j < N_HOUSEHOLDS; ++j) {
                final double investment = dice.nextGaussian() * householdInvestmentVariances
                        + meanHouseholdInvestment;
                try {
                    if (investment > 0.) {
                        myFund.invest(myHouseholds[j], investment);
                    } else if (investment < 0.) {
                        final double maximumWithdrawal = myFund.getBalance(myHouseholds[j]);
                        if (maximumWithdrawal == 0.)
                            continue;
                        double withdrawal = Math.min(-investment, maximumWithdrawal);
                        myFund.requestWithdrawal(myHouseholds[j], withdrawal);
                    }
                } catch (final InsufficientFundsException noFunds) {
                    Assert.fail();
                }
            }

            myFund.preClearingProcessing();
            final double profitsNow = forcedFundAssetPosition.value(i) - exogenousReturns.getValue();
            totalFundProfits += profitsNow;
            exogenousReturns.setValue(forcedFundAssetPosition.value(i));
            try {
                myFund.postClearingProcessing();
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }

            System.out.printf("MutualFund profits:     %16.10g\n", profitsNow);
            System.out.printf("MutualFund assets:      %16.10g\n", myFund.getTotalAssets());
            System.out.printf("MutualFund liabilities: %16.10g\n", myFund.getTotalLiabilities());
            System.out.printf("MutualFund equity:      %16.10g\n", myFund.getEquity());

            System.out.println("Number of shares: " + myFund.mInvestmentAccount.getNumberOfEmittedShares());

            // Assertions
            final double postClearingEquity = myFund.getEquity();
            Assert.assertEquals(postClearingEquity, fundInitialDeposits, 1.e-1);

            continue;
        }

        // Tally the total amount of cash in the system.
        totalHouseholdProfits = 0.;
        System.out.printf("household  deposits [open]  fund investments [open]"
                + "  deposits [close] fund investments [close] sum [close]\n");
        for (int i = 0; i < N_HOUSEHOLDS; ++i) { // Assert no money creation
            final double closingHouseholdDeposits = myHouseholds[i].bankAccount.getBalance(),
                    closingFundAccountValue = myFund.getBalance(myHouseholds[i]);
            System.out.printf("%5d %16.10g %16.10g         %16.10g %16.10g         %16.10g\n", i,
                    householdInitialDeposits, 0., closingHouseholdDeposits, closingFundAccountValue,
                    closingHouseholdDeposits + closingFundAccountValue);
            totalHouseholdProfits += (closingHouseholdDeposits + closingFundAccountValue)
                    - householdInitialDeposits;
        }

        System.out.printf("Aggregate household profits: %16.10g. MutualFund profits: %16.10g\n",
                totalHouseholdProfits, totalFundProfits);
        Assert.assertEquals(
                totalHouseholdProfits / Math.pow(10., (int) Math.log10(Math.abs(totalHouseholdProfits))),
                totalFundProfits / Math.pow(10., (int) Math.log10(Math.abs(totalFundProfits))), 1.e-12);

        return;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    public void testLongTermNetWithdrawal() {
        /*
         * Test parameters
         */
        final int numberOfBusinessCycles = 1000;
        final double periodicFundProfit = 1.e7, fundInitialDeposits = 0.,
                householdInitialDeposits = 1.e8 / myHouseholds.length,
                periodicHouseholdWithdrawal = .7 * periodicFundProfit / myHouseholds.length;

        myFund = new MutualFund(myBank, new HomogeneousPortfolioWeighting(),
                new FundamentalistStockReturnExpectationFunction(), clearingHouse, new NoSmoothingAlgorithm()); // Fresh fund

        myFund.debit(fundInitialDeposits);
        final Contract exogenousReturns = myFund.mDepositor.depositAccount;
        for (int i = 0; i < myHouseholds.length; ++i) {
            myHouseholds[i] = new HouseholdStub(myFund, myBank, householdInitialDeposits);
            try {
                myFund.invest(myHouseholds[i], householdInitialDeposits * .9);
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }
        }

        double totalFundProfits = 0.;
        for (int i = 0; i < numberOfBusinessCycles; ++i) {
            for (int j = 0; j < myHouseholds.length; ++j) {
                try {
                    myFund.requestWithdrawal(myHouseholds[j], periodicHouseholdWithdrawal);
                } catch (final InsufficientFundsException unexpectedException) {
                    Assert.fail();
                }
            }
            myFund.preClearingProcessing();
            exogenousReturns.setValue(exogenousReturns.getValue() + periodicFundProfit);
            totalFundProfits += periodicFundProfit;
            try {
                myFund.postClearingProcessing();
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }

            System.out.printf("MutualFund profits:     %16.10g\n", periodicFundProfit);
            System.out.printf("MutualFund assets:      %16.10g\n", myFund.getTotalAssets());
            System.out.printf("MutualFund liabilities: %16.10g\n", myFund.getTotalLiabilities());
            System.out.printf("MutualFund equity:      %16.10g\n", myFund.getEquity());

            System.out.println("Number of shares: " + myFund.mInvestmentAccount.getNumberOfEmittedShares());

            // Assertions
            final double postClearingEquity = myFund.getEquity();
            Assert.assertEquals(postClearingEquity, fundInitialDeposits, 1.e-1);

            continue;
        }

        // Tally the total amount of cash in the system.
        double totalHouseholdProfits = 0.;
        System.out.printf("household  deposits [open]  fund investments [open]"
                + "  deposits [close] fund investments [close] sum [close]\n");
        for (int i = 0; i < N_HOUSEHOLDS; ++i) { // Assert no money creation
            final double closingHouseholdDeposits = myHouseholds[i].bankAccount.getBalance(),
                    closingFundAccountValue = myFund.getBalance(myHouseholds[i]);
            System.out.printf("%5d %16.10g %16.10g         %16.10g %16.10g         %16.10g\n", i,
                    householdInitialDeposits, 0., closingHouseholdDeposits, closingFundAccountValue,
                    closingHouseholdDeposits + closingFundAccountValue);
            totalHouseholdProfits += (closingHouseholdDeposits + closingFundAccountValue)
                    - householdInitialDeposits;
        }

        System.out.printf("Aggregate household profits: %16.10g. MutualFund profits: %16.10g\n",
                totalHouseholdProfits, totalFundProfits);
        Assert.assertEquals(totalHouseholdProfits / Math.pow(10., (int) Math.log10(totalHouseholdProfits)),
                totalFundProfits / Math.pow(10., (int) Math.log10(totalFundProfits)), 1.e-12);
        Assert.assertTrue(myFund.mInvestmentAccount.numberOfEmittedShares > 1.);

        return;
    }

    /**
      * This unit test attempts to induce numerical failures in 
      * fund order book settlement logic and share price formation.
      */
    public void testInvestmentFollowedByCompleteWithdrawal() {
        /*
         * Test parameters
         */
        final int numberOfBusinessCycles = 2000;
        final double periodicFundProfit = 1.e7, fundInitialDeposits = 0., householdInitialDeposits = 1.e9;

        final UnivariateFunction investmentSize = new UnivariateFunction() {
            @Override
            public double value(double time) {
                return householdInitialDeposits * Math.pow(10., -14. * (1. + Math.sin(2. * Math.PI * time / 100.)));
            }
        };

        myFund = new MutualFund(myBank, new HomogeneousPortfolioWeighting(),
                new FundamentalistStockReturnExpectationFunction(), clearingHouse, new NoSmoothingAlgorithm()); // Fresh fund

        myFund.debit(fundInitialDeposits);
        final Contract exogenousReturns = myFund.mDepositor.depositAccount;
        for (int i = 0; i < myHouseholds.length; ++i) {
            myHouseholds[i] = new HouseholdStub(myFund, myBank, householdInitialDeposits);
            try {
                myFund.invest(myHouseholds[i], householdInitialDeposits * .9);
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }
        }

        final double startingSystemCash = myBank.getCashReserveValue();

        for (int i = 0; i < numberOfBusinessCycles; ++i) {
            for (int j = 0; j < myHouseholds.length; ++j) {
                try {
                    if (i % 3 == 0) {
                        // All investors withdraw everything they own.
                        final double accountValue = myFund.getBalance(myHouseholds[j]);
                        myFund.requestWithdrawal(myHouseholds[j], accountValue * (1. - 1.e-15));
                    } else if (i % 3 == 1) {
                        // No action by any participants
                    } else {
                        // All investors invest.
                        myFund.invest(myHouseholds[j], investmentSize.value(i));
                    }
                } catch (final InsufficientFundsException unexpectedException) {
                    //Assert.fail();
                }
            }
            System.out.println("Household investment size: " + investmentSize.value(i));
            myFund.preClearingProcessing();
            exogenousReturns.setValue(exogenousReturns.getValue() + periodicFundProfit);
            try {
                myFund.postClearingProcessing();
            } catch (final InsufficientFundsException unexpectedException) {
                Assert.fail();
            }

            System.out.printf("MutualFund profits:     %16.10g\n", periodicFundProfit);
            System.out.printf("MutualFund assets:      %16.10g\n", myFund.getTotalAssets());
            System.out.printf("MutualFund liabilities: %16.10g\n", myFund.getTotalLiabilities());
            System.out.printf("MutualFund equity:      %16.10g\n", myFund.getEquity());

            Assert.assertTrue(myFund.getTotalAssets() >= startingFundDeposits);

            System.out.println("Number of shares: " + myFund.mInvestmentAccount.getNumberOfEmittedShares());

            continue;
        }

        System.out.printf("Closing bank cash: %16.10g\n", myBank.getCashReserveValue());
        Assert.assertEquals(myBank.getCashReserveValue(), startingSystemCash, 1.e-12 * startingSystemCash);

        return;
    }

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    @AfterMethod
    public void tearDown() {
        System.out.println("Mutual fund tests ok.");
    }

    //////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////////////////////////////////////////////
    /*
     * Manual entry point
     */
    static public void main(String args[]) {
        try {
            FundTest thisTest = new FundTest();
            thisTest.setUp();
            thisTest.runAllFundsTests();
            thisTest.tearDown();
        } catch (final InsufficientFundsException unexpectedException) {
            System.out.println("Unexpected exception raised. Funds tests failed.");
        }
    }

    //////////////////////////////////////////////////////////////////////////////////////
    // Helper functions
    //////////////////////////////////////////////////////////////////////////////////////

    static public void printBalanceSheet(String title, IAgent agent) {
        Contract c;
        String name;
        double val;
        System.out.println(title);
        System.out.println("----------------------------------------------------------");
        for (int n = 0; n < agent.getAssets().size() || n < agent.getLiabilities().size(); ++n) {
            if (n < agent.getAssets().size()) {
                c = agent.getAssets().get(n);
                name = c.getClass().getCanonicalName();
                name = name.substring(name.length() - 20, name.length());
                val = c.getValue();
            } else {
                name = "                ";
                val = 0.0;
            }
            System.out.print(name + " " + val + "\t| ");
            if (n < agent.getLiabilities().size()) {
                c = agent.getLiabilities().get(n);
                name = c.getClass().getCanonicalName();
                name = name.substring(name.length() - 20, name.length());
                val = c.getValue();
            } else {
                name = "                ";
                val = 0.0;
            }
            System.out.println(name + " " + val);
        }
        System.out.println("");
    }

}