Java tutorial
/* * 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(""); } }