From 5cb77562347a6b4955ce6071f1846a8c040aaea9 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Tue, 20 Jun 2017 07:19:39 +0300 Subject: [PATCH 1/7] Started work on simplifying the configuration file. --- pom.xml | 5 + .../java/configuration/GovernmentConfig.java | 25 + .../configuration/HousingMarketConfig.java | 65 ++ src/main/java/configuration/ModelConfig.java | 35 ++ src/main/java/housing/Government.java | 125 ++-- src/main/java/housing/Model.java | 572 +++++++++--------- src/main/resources/model.conf | 318 ++++++++++ 7 files changed, 802 insertions(+), 343 deletions(-) create mode 100644 src/main/java/configuration/GovernmentConfig.java create mode 100644 src/main/java/configuration/HousingMarketConfig.java create mode 100644 src/main/java/configuration/ModelConfig.java create mode 100644 src/main/resources/model.conf diff --git a/pom.xml b/pom.xml index 4236b41d..4498d407 100644 --- a/pom.xml +++ b/pom.xml @@ -24,6 +24,11 @@ mason 19 + + com.typesafe + config + 1.3.1 + org.apache.commons commons-math3 diff --git a/src/main/java/configuration/GovernmentConfig.java b/src/main/java/configuration/GovernmentConfig.java new file mode 100644 index 00000000..f38940f0 --- /dev/null +++ b/src/main/java/configuration/GovernmentConfig.java @@ -0,0 +1,25 @@ +package configuration; + + +import com.typesafe.config.Config; + + +public class GovernmentConfig { + + private double personalAllowanceLimit; + private double incomeSupport; + + GovernmentConfig (Config config) { + personalAllowanceLimit = config.getDouble("personal-allowance-limit"); + incomeSupport = config.getDouble("income-support"); + } + + public double getPersonalAllowanceLimit() { + return personalAllowanceLimit; + } + + public double getIncomeSupport() { + return incomeSupport; + } + +} diff --git a/src/main/java/configuration/HousingMarketConfig.java b/src/main/java/configuration/HousingMarketConfig.java new file mode 100644 index 00000000..83bcd5ed --- /dev/null +++ b/src/main/java/configuration/HousingMarketConfig.java @@ -0,0 +1,65 @@ +package configuration; + + +import com.typesafe.config.Config; + + +/** Class encapsulating all of the housing market parameters. + * + * @author davidrpugh + */ +public class HousingMarketConfig { + + private int daysUnderOffer; + private double bidUp; + private double marketAveragePriceDecay; + private double initialHousePriceIndex; + private double medianHousePriceIndex; + private double shapeHousePriceIndex; + private int averageTenancyLength; + private double rentGrossYield; + + HousingMarketConfig(Config config) { + daysUnderOffer = config.getInt("days-under-offer"); + bidUp = config.getDouble("bid-up"); + marketAveragePriceDecay = config.getDouble("market-average-price-decay"); + initialHousePriceIndex = config.getDouble("initial-house-price-index"); + medianHousePriceIndex = config.getDouble("median-house-price-index"); + shapeHousePriceIndex = config.getDouble("shape-house-price-index"); + averageTenancyLength = config.getInt("average-tenancy-length"); + rentGrossYield = config.getDouble("rent-gross-yield"); + } + + public int getDaysUnderOffer() { + return daysUnderOffer; + } + + public double getBidUp() { + return bidUp; + } + + public double getMarketAveragePriceDecay() { + return marketAveragePriceDecay; + } + + public double getInitialHousePriceIndex() { + return initialHousePriceIndex; + } + + public double getMedianHousePriceIndex() { + return medianHousePriceIndex; + } + + public double getShapeHousePriceIndex() { + return shapeHousePriceIndex; + } + + public int getAverageTenancyLength() { + return averageTenancyLength; + } + + public double getRentGrossYield() { + return rentGrossYield; + } + +} diff --git a/src/main/java/configuration/ModelConfig.java b/src/main/java/configuration/ModelConfig.java new file mode 100644 index 00000000..b606d890 --- /dev/null +++ b/src/main/java/configuration/ModelConfig.java @@ -0,0 +1,35 @@ +package configuration; + + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + + +/** Class encapsulating all of the model parameters. + * + * @author davidrpugh + */ +public class ModelConfig { + + private HousingMarketConfig housingMarketConfig; + private GovernmentConfig governmentConfig; + + public ModelConfig(String filename) { + Config config = ConfigFactory.load(filename); + housingMarketConfig = new HousingMarketConfig(config.getConfig("simulation.housing-market")); + governmentConfig = new GovernmentConfig(config.getConfig("simulation.governmentConfig")); + } + + /** Housing Market Configuration + * + * @return a `HousingMarketConfig` object encapsulating the housing market parameters. + */ + public HousingMarketConfig getHousingMarketConfig() { + return housingMarketConfig; + } + + public GovernmentConfig getGovernmentConfig() { + return governmentConfig; + } + +} diff --git a/src/main/java/housing/Government.java b/src/main/java/housing/Government.java index 51b47abc..dad945e7 100644 --- a/src/main/java/housing/Government.java +++ b/src/main/java/housing/Government.java @@ -1,5 +1,7 @@ package housing; +import configuration.GovernmentConfig; + /***************************************** * This class represents the government. * This is the class where taxation policy should be encoded. @@ -9,63 +11,70 @@ ****************************************/ public class Government { - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field + private GovernmentConfig config; + + /** Create an instance of the `Government` class. + * + * @param config a `GovernmentConfig` object encapsulating the behavioral parameters. + */ + public Government(GovernmentConfig config) { + this.config = config; + } - /****************************************** - * Calculates the income tax due in one year for a given - * gross annual income. Doesn't account for married couple's allowance. - * - * @param grossIncome The gross, annual income in pounds. - * @return The annual income tax due in pounds. - ******************************************/ - public double incomeTaxDue(double grossIncome) { - double tax = bandedPercentage(grossIncome, data.Government.tax.bands, data.Government.tax.rates); - if(grossIncome > config.GOVERNMENT_PERSONAL_ALLOWANCE_LIMIT) { - //double personalAllowance = Math.max((grossIncome - config.GOVERNMENT_PERSONAL_ALLOWANCE_LIMIT)/2.0,0.0); - double personalAllowance = Math.max( - data.Government.tax.bands[0]-(grossIncome-config.GOVERNMENT_PERSONAL_ALLOWANCE_LIMIT)/2.0, - 0.0); - tax += (data.Government.tax.bands[0]-personalAllowance)*data.Government.tax.rates[0]; // TODO: what does this do? - } - return(tax); - } - - /*********************************** - * Calculate the class 1 National Insurance Contributions due on a - * given annual income (under PAYE). - * - * @param grossIncome Gross annual income in pounds - * @return Annual class 1 NICs due. - **********************************/ - public double class1NICsDue(double grossIncome) { - return(bandedPercentage(grossIncome, data.Government.nationalInsurance.bands, data.Government.nationalInsurance.rates)); - } - - /********************************** - * Calculate a "banded percentage" on a value. - * A "banded percentage" is a way of calculating a non-linear - * function, f(x), widely used by HMRC. The domain of - * values of f(x) is split into bands: from 0 to x1, from x1 to x2 - * etc. Each band is associated with a percentage p1, p2 etc. - * The final value of f(x) is the sum of the percentages of each band. - * So, for example, if x lies somewhere between x1 and x2, f(x) would be - * p1x1 + p2(x-x1) - * - * @param taxableIncome the value to apply the banded percentage to. - * @param bands an array holding the upper limit of each band - * @param rates an array holding the percentage applicable to each band - * @return The banded percentage of 'taxableIncome' - ***********************************/ - protected double bandedPercentage(double taxableIncome, Double [] bands, Double [] rates) { - int i = 0; - double lastRate = 0.0; - double tax = 0.0; - - while(i < bands.length && taxableIncome > bands[i]) { - tax += (taxableIncome - bands[i])*(rates[i] - lastRate); - lastRate = rates[i]; - ++i; - } - return(tax); - } + /****************************************** + * Calculates the income tax due in one year for a given + * gross annual income. Doesn't account for married couple's allowance. + * + * @param grossIncome The gross, annual income in pounds. + * @return The annual income tax due in pounds. + ******************************************/ + public double incomeTaxDue(double grossIncome) { + double tax = bandedPercentage(grossIncome, data.Government.tax.bands, data.Government.tax.rates); + if(grossIncome > config.getPersonalAllowanceLimit()) { + //double personalAllowance = Math.max((grossIncome - config.GOVERNMENT_PERSONAL_ALLOWANCE_LIMIT)/2.0,0.0); + double personalAllowance = Math.max( + data.Government.tax.bands[0]-(grossIncome-config.getPersonalAllowanceLimit())/2.0, 0.0); + tax += (data.Government.tax.bands[0]-personalAllowance)*data.Government.tax.rates[0]; // TODO: what does this do? + } + return(tax); + } + + /*********************************** + * Calculate the class 1 National Insurance Contributions due on a + * given annual income (under PAYE). + * + * @param grossIncome Gross annual income in pounds + * @return Annual class 1 NICs due. + **********************************/ + public double class1NICsDue(double grossIncome) { + return(bandedPercentage(grossIncome, data.Government.nationalInsurance.bands, data.Government.nationalInsurance.rates)); + } + + /********************************** + * Calculate a "banded percentage" on a value. + * A "banded percentage" is a way of calculating a non-linear + * function, f(x), widely used by HMRC. The domain of + * values of f(x) is split into bands: from 0 to x1, from x1 to x2 + * etc. Each band is associated with a percentage p1, p2 etc. + * The final value of f(x) is the sum of the percentages of each band. + * So, for example, if x lies somewhere between x1 and x2, f(x) would be + * p1x1 + p2(x-x1) + * + * @param taxableIncome the value to apply the banded percentage to. + * @param bands an array holding the upper limit of each band + * @param rates an array holding the percentage applicable to each band + * @return The banded percentage of 'taxableIncome' + ***********************************/ + private double bandedPercentage(double taxableIncome, Double [] bands, Double [] rates) { + int i = 0; + double lastRate = 0.0; + double tax = 0.0; + + while(i < bands.length && taxableIncome > bands[i]) { + tax += (taxableIncome - bands[i])*(rates[i] - lastRate); + lastRate = rates[i]; + ++i; + } + return(tax); + } } diff --git a/src/main/java/housing/Model.java b/src/main/java/housing/Model.java index 426d4925..a20cfb2a 100644 --- a/src/main/java/housing/Model.java +++ b/src/main/java/housing/Model.java @@ -11,6 +11,7 @@ import collectors.HousingMarketStats; import collectors.MicroDataRecorder; import collectors.Recorder; +import configuration.ModelConfig; import org.apache.commons.math3.random.RandomGenerator; import ec.util.MersenneTwisterFast; @@ -28,298 +29,299 @@ @SuppressWarnings("serial") public class Model extends SimState implements Steppable { - //////////////////////////////////////////////////////////////////////// - - /* - * ATTENTION: Seed for random number generation is set by calling the program with argument "-seed ", - * where must be a positive integer. In the absence of this argument, seed is set from machine time. - */ - - public static Config config; - - //////////////////////////////////////////////////////////////////////// - - public static void main(String[] args) { - //doLoop(ModelNoGUI.class, args); - doLoop(Model.class,args); - System.exit(0); //Stop the program when finished. - } - - public Model(long seed) { - super(seed); - rand = new MersenneTwister(seed); - config = new Config("src/main/resources/config.properties"); - //System.exit(0); - - government = new Government(); - demographics = new Demographics(); - recorder = new collectors.Recorder(); - transactionRecorder = new collectors.MicroDataRecorder(); - - centralBank = new CentralBank(); - mBank = new Bank(); - mConstruction = new Construction(); - mHouseholds = new ArrayList(config.TARGET_POPULATION*2); - housingMarket = mHousingMarket = new HouseSaleMarket(); // Variables of housingMarket are initialised (including HPI) - rentalMarket = mRentalMarket = new HouseRentalMarket(); // Variables of rentalMarket are initialised (including HPI) - mCollectors = new collectors.Collectors(); - nSimulation = 0; - - setupStatics(); - init(); // Variables of both housingMarket and rentalMarket are initialised again (including HPI) - } - - @Override - public void awakeFromCheckpoint() { - super.awakeFromCheckpoint(); - setupStatics(); - } - - protected void setupStatics() { -// centralBank = mCentralBank; - bank = mBank; - construction = mConstruction; - households = mHouseholds; - housingMarket = mHousingMarket; - rentalMarket = mRentalMarket; - collectors = mCollectors; - root = this; - setRecordCoreIndicators(config.recordCoreIndicators); - setRecordMicroData(config.recordMicroData); - } - - public void init() { - construction.init(); - housingMarket.init(); - rentalMarket.init(); - bank.init(); - households.clear(); - collectors.init(); - t = 0; - if(!monteCarloCheckpoint.equals("")) { //changed this from != "" - File f = new File(monteCarloCheckpoint); - readFromCheckpoint(f); - } - } - - /** - * This method is called before the simulation starts. It schedules this - * object to be stepped at each timestep and initialises the agents. - */ - public void start() { - super.start(); - scheduleRepeat = schedule.scheduleRepeating(this); - - if(!monteCarloCheckpoint.equals("")) { //changed from != "" - File f = new File(monteCarloCheckpoint); - readFromCheckpoint(f); - } -// recorder.start(); - } - - public void stop() { - scheduleRepeat.stop(); - } - - /** - * This is the main time-step of the whole simulation. Everything starts - * here. - */ - public void step(SimState simulationStateNow) { - if (schedule.getTime() >= config.N_STEPS*config.N_SIMS) simulationStateNow.kill(); - if(t >= config.N_STEPS) { - // start new simulation - nSimulation += 1; - if (nSimulation >= config.N_SIMS) { - // this was the last simulation, clean up - if(config.recordCoreIndicators) recorder.finish(); - if(config.recordMicroData) transactionRecorder.finish(); - simulationStateNow.kill(); - return; - } - if(config.recordCoreIndicators) recorder.endOfSim(); - if(config.recordMicroData) transactionRecorder.endOfSim(); - init(); // Variables of both housingMarket and rentalMarket are initialised again (including HPI) - } - - /* - * Steps model and stores ownership and rental markets bid and offer prices, and their averages, into their - * respective variables - */ - modelStep(); - - if (t>=config.TIME_TO_START_RECORDING) { - // Finds values of variables and records them to their respective files - if(config.recordCoreIndicators) recorder.step(); - } - - collectors.step(); - } - - public void modelStep() { - demographics.step(); - construction.step(); - - for(Household h : households) h.step(); + //////////////////////////////////////////////////////////////////////// + + /* + * ATTENTION: Seed for random number generation is set by calling the program with argument "-seed ", + * where must be a positive integer. In the absence of this argument, seed is set from machine time. + */ + + public static ModelConfig config; + + //////////////////////////////////////////////////////////////////////// + + public static void main(String[] args) { + //doLoop(ModelNoGUI.class, args); + doLoop(Model.class,args); + System.exit(0); //Stop the program when finished. + } + + public Model(long seed) { + super(seed); + rand = new MersenneTwister(seed); + + config = new ModelConfig("model.conf"); + + + government = new Government(config.getGovernmentConfig()); + demographics = new Demographics(); + recorder = new collectors.Recorder(); + transactionRecorder = new collectors.MicroDataRecorder(); + + centralBank = new CentralBank(); + mBank = new Bank(); + mConstruction = new Construction(); + mHouseholds = new ArrayList(config.TARGET_POPULATION*2); + housingMarket = mHousingMarket = new HouseSaleMarket(); // Variables of housingMarket are initialised (including HPI) + rentalMarket = mRentalMarket = new HouseRentalMarket(); // Variables of rentalMarket are initialised (including HPI) + mCollectors = new collectors.Collectors(); + nSimulation = 0; + + setupStatics(); + init(); // Variables of both housingMarket and rentalMarket are initialised again (including HPI) + } + + @Override + public void awakeFromCheckpoint() { + super.awakeFromCheckpoint(); + setupStatics(); + } + + protected void setupStatics() { +// centralBank = mCentralBank; + bank = mBank; + construction = mConstruction; + households = mHouseholds; + housingMarket = mHousingMarket; + rentalMarket = mRentalMarket; + collectors = mCollectors; + root = this; + setRecordCoreIndicators(config.recordCoreIndicators); + setRecordMicroData(config.recordMicroData); + } + + public void init() { + construction.init(); + housingMarket.init(); + rentalMarket.init(); + bank.init(); + households.clear(); + collectors.init(); + t = 0; + if(!monteCarloCheckpoint.equals("")) { //changed this from != "" + File f = new File(monteCarloCheckpoint); + readFromCheckpoint(f); + } + } + + /** + * This method is called before the simulation starts. It schedules this + * object to be stepped at each timestep and initialises the agents. + */ + public void start() { + super.start(); + scheduleRepeat = schedule.scheduleRepeating(this); + + if(!monteCarloCheckpoint.equals("")) { //changed from != "" + File f = new File(monteCarloCheckpoint); + readFromCheckpoint(f); + } +// recorder.start(); + } + + public void stop() { + scheduleRepeat.stop(); + } + + /** + * This is the main time-step of the whole simulation. Everything starts + * here. + */ + public void step(SimState simulationStateNow) { + if (schedule.getTime() >= config.N_STEPS*config.N_SIMS) simulationStateNow.kill(); + if(t >= config.N_STEPS) { + // start new simulation + nSimulation += 1; + if (nSimulation >= config.N_SIMS) { + // this was the last simulation, clean up + if(config.recordCoreIndicators) recorder.finish(); + if(config.recordMicroData) transactionRecorder.finish(); + simulationStateNow.kill(); + return; + } + if(config.recordCoreIndicators) recorder.endOfSim(); + if(config.recordMicroData) transactionRecorder.endOfSim(); + init(); // Variables of both housingMarket and rentalMarket are initialised again (including HPI) + } + + /* + * Steps model and stores ownership and rental markets bid and offer prices, and their averages, into their + * respective variables + */ + modelStep(); + + if (t>=config.TIME_TO_START_RECORDING) { + // Finds values of variables and records them to their respective files + if(config.recordCoreIndicators) recorder.step(); + } + + collectors.step(); + } + + public void modelStep() { + demographics.step(); + construction.step(); + + for(Household h : households) h.step(); // Stores ownership market bid and offer prices, and their averages, into their respective variables - collectors.housingMarketStats.record(); + collectors.housingMarketStats.record(); // Clears market and updates the HPI - housingMarket.clearMarket(); + housingMarket.clearMarket(); // Stores rental market bid and offer prices, and their averages, into their respective variables - collectors.rentalMarketStats.record(); - rentalMarket.clearMarket(); - bank.step(); - centralBank.step(getCoreIndicators()); - t += 1; - } - - - /** - * Cleans up after a simulation ends. - */ - public void finish() { - super.finish(); - if(config.recordCoreIndicators) recorder.finish(); - if(config.recordMicroData) transactionRecorder.finish(); - } - - /** - * @return simulated time in months - */ - static public int getTime() { - return(Model.root.t); - } - - static public int getMonth() { - return(Model.root.t%12 + 1); - } - - public Stoppable scheduleRepeat; - - // non-statics for serialization - public ArrayList mHouseholds; - public Bank mBank; -// public CentralBank mCentralBank; - public Construction mConstruction; - public HouseSaleMarket mHousingMarket; - public HouseRentalMarket mRentalMarket; - public collectors.Collectors mCollectors; - - public static CentralBank centralBank; - public static Bank bank; - public static Government government; - public static Construction construction; - public static HouseSaleMarket housingMarket; - public static HouseRentalMarket rentalMarket; - public static ArrayList households; - public static Demographics demographics; - public static MersenneTwister rand; - public static Model root; - - public static collectors.Collectors collectors; // = new Collectors(); - public static Recorder recorder; // records info to file - public static MicroDataRecorder transactionRecorder; - - public static int nSimulation; // number of simulations run - public int t; // time (months) -// public static LogNormalDistribution grossFinancialWealth; // household wealth in bank balances and investments - - /** - * proxy class to allow us to work with apache.commons distributions - */ - public static class MersenneTwister extends MersenneTwisterFast implements RandomGenerator { - public MersenneTwister(long seed) {super(seed);} - public void setSeed(int arg0) { - super.setSeed((long)arg0); - } - } - - //////////////////////////////////////////////////////////////////////// - // Getters/setters for MASON console - //////////////////////////////////////////////////////////////////////// - - public CreditSupply getCreditSupply() { - return collectors.creditSupply; - } - - public collectors.HousingMarketStats getHousingMarketStats() { - return collectors.housingMarketStats; - } - - public HousingMarketStats getRentalMarketStats() { - return collectors.rentalMarketStats; - } - - public CoreIndicators getCoreIndicators() { - return collectors.coreIndicators; - } - - public HouseholdStats getHouseholdStats() { - return collectors.householdStats; - } - - String monteCarloCheckpoint = ""; - - public String getMonteCarloCheckpoint() { - return monteCarloCheckpoint; - } - - public void setMonteCarloCheckpoint(String monteCarloCheckpoint) { - this.monteCarloCheckpoint = monteCarloCheckpoint; - } - -// Deprecated getters/setters! New access to parameters is through instances of the Config class -// public static int getN_STEPS() { -// return N_STEPS; -// } + collectors.rentalMarketStats.record(); + rentalMarket.clearMarket(); + bank.step(); + centralBank.step(getCoreIndicators()); + t += 1; + } + + + /** + * Cleans up after a simulation ends. + */ + public void finish() { + super.finish(); + if(config.recordCoreIndicators) recorder.finish(); + if(config.recordMicroData) transactionRecorder.finish(); + } + + /** + * @return simulated time in months + */ + static public int getTime() { + return(Model.root.t); + } + + static public int getMonth() { + return(Model.root.t%12 + 1); + } + + public Stoppable scheduleRepeat; + + // non-statics for serialization + public ArrayList mHouseholds; + public Bank mBank; +// public CentralBank mCentralBank; + public Construction mConstruction; + public HouseSaleMarket mHousingMarket; + public HouseRentalMarket mRentalMarket; + public collectors.Collectors mCollectors; + + public static CentralBank centralBank; + public static Bank bank; + public static Government government; + public static Construction construction; + public static HouseSaleMarket housingMarket; + public static HouseRentalMarket rentalMarket; + public static ArrayList households; + public static Demographics demographics; + public static MersenneTwister rand; + public static Model root; + + public static collectors.Collectors collectors; // = new Collectors(); + public static Recorder recorder; // records info to file + public static MicroDataRecorder transactionRecorder; + + public static int nSimulation; // number of simulations run + public int t; // time (months) +// public static LogNormalDistribution grossFinancialWealth; // household wealth in bank balances and investments + + /** + * proxy class to allow us to work with apache.commons distributions + */ + public static class MersenneTwister extends MersenneTwisterFast implements RandomGenerator { + public MersenneTwister(long seed) {super(seed);} + public void setSeed(int arg0) { + super.setSeed((long)arg0); + } + } + + //////////////////////////////////////////////////////////////////////// + // Getters/setters for MASON console + //////////////////////////////////////////////////////////////////////// + + public CreditSupply getCreditSupply() { + return collectors.creditSupply; + } + + public collectors.HousingMarketStats getHousingMarketStats() { + return collectors.housingMarketStats; + } + + public HousingMarketStats getRentalMarketStats() { + return collectors.rentalMarketStats; + } + + public CoreIndicators getCoreIndicators() { + return collectors.coreIndicators; + } + + public HouseholdStats getHouseholdStats() { + return collectors.householdStats; + } + + String monteCarloCheckpoint = ""; + + public String getMonteCarloCheckpoint() { + return monteCarloCheckpoint; + } + + public void setMonteCarloCheckpoint(String monteCarloCheckpoint) { + this.monteCarloCheckpoint = monteCarloCheckpoint; + } + +// Deprecated getters/setters! New access to parameters is through instances of the Config class +// public static int getN_STEPS() { +// return N_STEPS; +// } // -// public static void setN_STEPS(int n_STEPS) { -// N_STEPS = n_STEPS; -// } -// public String nameN_STEPS() {return("Number of timesteps");} +// public static void setN_STEPS(int n_STEPS) { +// N_STEPS = n_STEPS; +// } +// public String nameN_STEPS() {return("Number of timesteps");} // -// public static int getN_SIMS() { -// return N_SIMS; -// } +// public static int getN_SIMS() { +// return N_SIMS; +// } // -// public static void setN_SIMS(int n_SIMS) { -// N_SIMS = n_SIMS; -// } -// public String nameN_SIMS() {return("Number of monte-carlo runs");} +// public static void setN_SIMS(int n_SIMS) { +// N_SIMS = n_SIMS; +// } +// public String nameN_SIMS() {return("Number of monte-carlo runs");} // -// public boolean isRecordCoreIndicators() { -// return recordCoreIndicators; -// } - - public void setRecordCoreIndicators(boolean recordCoreIndicators) { - this.config.recordCoreIndicators = recordCoreIndicators; - if(recordCoreIndicators) { - collectors.coreIndicators.setActive(true); - collectors.creditSupply.setActive(true); - collectors.householdStats.setActive(true); - collectors.housingMarketStats.setActive(true); - collectors.rentalMarketStats.setActive(true); - try { - recorder.start(); - } catch (FileNotFoundException | UnsupportedEncodingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } -// else { -// recorder.finish(); -// } - } - public String nameRecordCoreIndicators() {return("Record core indicators");} - - public boolean isRecordMicroData() { - return transactionRecorder.isActive(); - } - - public void setRecordMicroData(boolean record) { - transactionRecorder.setActive(record); - } - public String nameRecordMicroData() {return("Record micro data");} +// public boolean isRecordCoreIndicators() { +// return recordCoreIndicators; +// } + + public void setRecordCoreIndicators(boolean recordCoreIndicators) { + this.config.recordCoreIndicators = recordCoreIndicators; + if(recordCoreIndicators) { + collectors.coreIndicators.setActive(true); + collectors.creditSupply.setActive(true); + collectors.householdStats.setActive(true); + collectors.housingMarketStats.setActive(true); + collectors.rentalMarketStats.setActive(true); + try { + recorder.start(); + } catch (FileNotFoundException | UnsupportedEncodingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } +// else { +// recorder.finish(); +// } + } + public String nameRecordCoreIndicators() {return("Record core indicators");} + + public boolean isRecordMicroData() { + return transactionRecorder.isActive(); + } + + public void setRecordMicroData(boolean record) { + transactionRecorder.setActive(record); + } + public String nameRecordMicroData() {return("Record micro data");} } diff --git a/src/main/resources/model.conf b/src/main/resources/model.conf new file mode 100644 index 00000000..28ce3a1f --- /dev/null +++ b/src/main/resources/model.conf @@ -0,0 +1,318 @@ +simulation { + ################################################## + ################ General comments ################ + ################################################## + + # Seed for random number generation is set by calling the program with argument + # "-seed ", where must be a positive integer. In the + # absence of this argument, seed is set from machine time + # todo seed should be specified here in the configuration file! + ################################################## + ######## General model control parameters ######## + ################################################## + + # Simulation duration in time steps (int) + N_STEPS = 10000 + # Time steps before recording statistics, initialisation time (int) + TIME_TO_START_RECORDING = 0 + # Number of simulations to run (int) + N_SIMS = 1 + # True to write time series for each core indicator (boolean) + recordCoreIndicators = true + # True to write micro data for each transaction made (boolean) + recordMicroData = false + + ################################################## + ################ House parameters ################ + ################################################## + + # Number of quality bands for houses (int) + N_QUALITY = 48 + + ################################################## + ########### Housing market parameters ############ + ################################################## + market { + + # Time, in days, that a house remains under offer (int) + days-under-offer = 7 + + # Smallest proportional increase in price that can cause a gazump (double) + bid-up = 1.0075 + + # Decay constant for the exponential moving average of sale prices (double) + market-average-price-decay = 0.25 + + # Initial housing price index, HPI (double) + # TODO: Reference for this value and justification for this discounting are needed! (the parameter is declared, but no source nor justification is given) + initial-hpi = 0.8 + + # Median house price (double) + # TODO: Replace by the 2011 value + hpi-median = 195000.0 + + # Shape parameter for the log-normal distribution of housing prices, taken from the ONS (2013) house price index data tables, table 34 (double) + # TODO: Replace by the 2011 value, compare with Land Registry Paid Price data and decide whether to use real distribution + hpi-shape = 0.555 + + # Average number of months a tenant will stay in a rented house. Source: ARLA - Members survey of the Private Rented Sector Q4 2013 + # TODO: Replace with 2011 value + average-tenacy-length = 18 + + # Profit margin for buy-to-let investors (double) + # Yield on rent had average 6% between 2009/01 and 2015/01, minimum in 2009/10 maximum in 2012/04 peak-to-peak amplitude of 0.4%. Source: Bank of England, unpublished analysis based on Zoopla/Land Registry matching (Philippe Bracke) + rent-gross-yield = 0.05 + + } + + ################################################## + ############# Demographic parameters ############# + ################################################## + demographics { + # Target number of households (int) + target-population = 10000 + # TODO: Unclear parameter related to the creation of the population (boolean) + spin-up = false + # Future birth rate (births per year per capita), calibrated with flux of FTBs, Council of Mortgage Lenders Regulated Mortgage Survey, 2015 (double) + future-birth-rate = 0.018 + } + ################################################## + ############## Household parameters ############## + ################################################## + households { + # True to have a buy-to-let sector (boolean) + BTL_ENABLED = true + # Monthly percentage growth of financial investments (double) + RETURN_ON_FINANCIAL_WEALTH = 0.002 + # Average number of months a tenant will stay in a rented house (int) + # Source: ARLA - Members survey of the Private Rented Sector Q4 2013 + TENANCY_LENGTH_AVERAGE = 18 + # Standard deviation of the noise in determining the tenancy length (int) + TENANCY_LENGTH_EPSILON = 6 + + ################################################## + ######### Household behaviour parameters ######### + ################################################## + behavior { + ############# Buy-To-Let parameters ############## + # Prior probability of being (wanting to be) a BTL investor (double) + # TODO: Shouldn't this be 4% according to the article? + P_INVESTOR = 0.16 + # Minimum income percentile for a household to be a BTL investor (double) + MIN_INVESTOR_PERCENTILE = 0.5 + # Weight that fundamentalists put on cap gain (double) + FUNDAMENTALIST_CAP_GAIN_COEFF = 0.5 + # Weight that trend-followers put on cap gain (double) + TREND_CAP_GAIN_COEFF = 0.9 + # Probability that a BTL investor is a fundamentalist versus a trend-follower (double) + P_FUNDAMENTALIST = 0.5 + # Chooses between two possible equations for BTL investors to make their buy/sell decisions (boolean) + BTL_YIELD_SCALING = false + + ################ Rent parameters ################# + # Desired proportion of income to be spent on rent (double) + DESIRED_RENT_INCOME_FRACTION = 0.33 + # Annual psychological cost of renting (double) + # TODO: This value comes from 1.1/12.0... Where does that come from? + PSYCHOLOGICAL_COST_OF_RENTING = 0.0916666666667 + # Sensitivity parameter of the decision between buying and renting (double) + # TODO: This value comes from 1.0/3500.0... Where does that come from? + SENSITIVITY_RENT_OR_PURCHASE = 0.000285714285714 + + ############### General parameters ############### + # If the ratio between the buyer's bank balance and the house price is above this, + # payment will be made fully in cash (double) + BANK_BALANCE_FOR_CASH_DOWNPAYMENT = 2.0 + # Dampening or multiplier factor, depending on its value being <1 or >1, for the current trend when computing expectations as in + # HPI(t+DT) = HPI(t) + FACTOR*DT*dHPI/dt (double) + # TODO: According to John Muellbauer, this is a dampening factor (<1). Find a reference for this! + HPA_EXPECTATION_FACTOR = 0.5 + # Number of years of the HPI record to check when computing the annual HPA, i.e., how much backward looking households are (int) + HPA_YEARS_TO_CHECK = 1 + # Average period, in years, for which owner-occupiers hold their houses (double) + # British housing survey 2008 + HOLD_PERIOD = 11.0 + + ######### Sale price reduction parameters ######## + # This subsection was calibrated against Zoopla data at the BoE + # Monthly probability of reducing the price of a house on the market (double) + # This value comes from 1.0-0.945 + P_SALE_PRICE_REDUCE = 0.055 + # Mean percentage reduction for prices of houses on the market (double) + REDUCTION_MU = 1.603 + # Standard deviation of percentage reductions for prices of houses on the market (double) + REDUCTION_SIGMA = 0.617 + + ############# Comsumption parameters ############# + # Fraction of the monthly budget allocated for consumption, being the monthly + # budget equal to the bank balance minus the minimum desired bank balance (double) + CONSUMPTION_FRACTION = 0.5 + # Fraction of Government support representing the amount necessarily spent monthly by + # all households as essential consumption (double) + ESSENTIAL_CONSUMPTION_FRACTION = 0.8 + + ######### Initial sale price parameters ########## + # Initial markup over average price of same quality houses (double) + # TODO: Note says that, according to BoE calibration, this should be around 0.2. Check and solve this! + SALE_MARKUP = 0.04 + # Weight of the days-on-market effect (double) + SALE_WEIGHT_DAYS_ON_MARKET = 0.011 + # Standard deviation of the noise (double) + SALE_EPSILON = 0.05 + + ##### Buyer's desired expenditure parameters ##### + # Scale, number of annual salaries the buyer is willing to spend for buying a house (double) + # TODO: This has been macro-calibrated against owner-occupier LTI and LTV ration, core indicators average 1987-2006. Find sources! + BUY_SCALE = 4.5 + # Weight given to house price appreciation when deciding how much to spend for buying a house (double) + BUY_WEIGHT_HPA = 0.08 + # Standard deviation of the noise (double) + BUY_EPSILON = 0.14 + + ############ Demanded rent parameters ############ + # Markup over average rent demanded for houses of the same quality (double) + RENT_MARKUP = 0.00 + # Number of months on the market in an equilibrium situation (double) + RENT_EQ_MONTHS_ON_MARKET = 6.0 + # Standard deviation of the noise (double) + RENT_EPSILON = 0.05 + # Maximum period of time BTL investors are ready to wait to get back their investment through rents, + # this determines the minimum rent they are ready to accept (double) + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: This parameter and its associated mechanism are not declared in the article! Need to declare and reference! + RENT_MAX_AMORTIZATION_PERIOD = 20.833333333 + # Percentage reduction of demanded rent for every month the property is in the market, not rented (double) + RENT_REDUCTION = 0.05 + + ############# Downpayment parameters ############# + # Minimum income percentile to consider any downpayment, below this level, downpayment is set to 0 (double) + # TODO: Calibrated against PSD data, need clearer reference or disclose distribution! + DOWNPAYMENT_MIN_INCOME = 0.3 + # TODO: Both functional form and parameters are micro-calibrated against BoE data. Need reference or disclose distribution! + # Scale parameter for the log-normal distribution of downpayments by first-time-buyers (double) + DOWNPAYMENT_FTB_SCALE = 10.30 + # Shape parameter for the log-normal distribution of downpayments by first-time-buyers (double) + DOWNPAYMENT_FTB_SHAPE = 0.9093 + # Scale parameter for the log-normal distribution of downpayments by owner-occupiers (double) + DOWNPAYMENT_OO_SCALE = 11.155 + # Shape parameter for the log-normal distribution of downpayments by owner-occupiers (double) + DOWNPAYMENT_OO_SHAPE = 0.7538 + # Average downpayment, as percentage of house price, by but-to-let investors (double) + # TODO: Said to be calibrated to match LTV ratios, but no reference is given. Need reference! + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: Functional form slightly different to the one presented in the article + DOWNPAYMENT_BTL_MEAN = 0.3 + # Standard deviation of the noise (double) + DOWNPAYMENT_BTL_EPSILON = 0.1 + + ######## Desired bank balance parameters ######### + # Micro-calibrated to match the log-normal relationship between wealth and income from the Wealth and Assets Survey + # (double) + DESIRED_BANK_BALANCE_ALPHA = -32.0013877 + # (double) + DESIRED_BANK_BALANCE_BETA = 4.07 + # Standard deviation of a noise, it states a propensity to save (double) + DESIRED_BANK_BALANCE_EPSILON = 0.1 + + ########## Selling decision parameters ########### + # Weight of houses per capita effect + DECISION_TO_SELL_ALPHA = 4.0 + # Weight of interest rate effect + DECISION_TO_SELL_BETA = 5.0 + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: fudge parameter, explicitly explained otherwise in the article + DECISION_TO_SELL_HPC = 0.05 + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: fudge parameter, explicitly explained otherwise in the article + DECISION_TO_SELL_INTEREST = 0.03 + + ######### BTL buy/sell choice parameters ######### + # Shape parameter, or intensity of choice on effective yield (double) + BTL_CHOICE_INTENSITY = 50.0 + # Minimun bank balance, as a percentage of the desired bank balance, to buy new properties (double) + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: This parameter and its associated mechanism are not declared in the article! Need to declare and reference! + BTL_CHOICE_MIN_BANK_BALANCE = 0.75 + } + + } + + ################################################## + ################ Bank parameters ################# + ################################################## + # TODO: We need references or justification for all these values! + banks { + # Mortgage duration in years (int) + MORTGAGE_DURATION_YEARS = 25 + # Bank initial base-rate, which remains currently unchanged (double) + BANK_INITIAL_BASE_RATE = 0.005 + # Bank's target supply of credit per household per month (double) + BANK_CREDIT_SUPPLY_TARGET = 380 + } + ################################################## + ############# Central bank parameters ############ + ################################################## + # TODO: We need references or justification for all these values! Also, need to clarify meaning of "when not regulated" + central-bank { + # Maximum LTV ratio that the bank would allow for first-time-buyers when not regulated (double) + CENTRAL_BANK_MAX_FTB_LTV = 0.95 + # Maximum LTV ratio that the bank would allow for owner-occupiers when not regulated (double) + CENTRAL_BANK_MAX_OO_LTV = 0.9 + # Maximum LTV ratio that the bank would allow for BTL investors when not regulated (double) + CENTRAL_BANK_MAX_BTL_LTV = 0.8 + # Maximum fraction of mortgages that the bank can give over the LTV ratio limit (double) + CENTRAL_BANK_FRACTION_OVER_MAX_LTV = 0.0 + # Maximum LTI ratio that the bank would allow for first-time-buyers when not regulated (double) + CENTRAL_BANK_MAX_FTB_LTI = 6.0 + # Maximum LTI ratio that the bank would allow for owner-occupiers when not regulated (double) + CENTRAL_BANK_MAX_OO_LTI = 6.0 + # Maximum fraction of mortgages that the bank can give over the LTI ratio limit (double) + CENTRAL_BANK_FRACTION_OVER_MAX_LTI = 0.15 + # Maximum fraction of the household's income to be spent on mortgage repayments under stressed conditions (double) + CENTRAL_BANK_AFFORDABILITY_COEFF = 0.5 + # Interest rate under stressed condition for BTL investors when calculating interest coverage ratios, ICR (double) + CENTRAL_BANK_BTL_STRESSED_INTEREST = 0.05 + # Interest coverage ratio (ICR) limit imposed by the central bank (double) + CENTRAL_BANK_MAX_ICR = 1.25 + } + ################################################## + ############ Construction parameters ############# + ################################################## + # TODO: We need references or justification for all these values! + + # Target ratio of houses per household (double) + CONSTRUCTION_HOUSES_PER_HOUSEHOLD = 0.82 + + ################################################## + ############# Government parameters ############## + ################################################## + # TODO: We need references or justification for all these values! + government { + # Maximum personal allowance (double) + personal-allowance-limit = 100000.0 + # Minimum monthly earnings for a married couple from income support (double) + income-support = 492.7 + } + ################################################## + ############## Collectors parameters ############# + ################################################## + + # Approximate number of households in UK, used to scale up results for core indicators (double) + # TODO: Reference needed + UK_HOUSEHOLDS = 26.5e6 + # Whether to record mortgage statistics (boolean) + MORTGAGE_DIAGNOSTICS_ACTIVE = true + + ################################################## + ################# Data addresses ################# + ################################################## + + ############ Government data addresses ########### + # TODO: We need clearer references for the values contained in these files! Also, current values are for 2013/2014, replace for 2011! + DATA_TAX_RATES = "src/main/resources/TaxRates.csv" + DATA_NATIONAL_INSURANCE_RATES = "src/main/resources/NationalInsuranceRates.csv" + + ############ Lifecycle data addresses ############ + DATA_INCOME_GIVEN_AGE = "src/main/resources/IncomeGivenAge.csv" + + ########### Demographics data addresses ########## + # Target probability density of age of representative person in the household at time t=0, calibrated against LCFS (2012) + DATA_AGE_MARGINAL_PDF = "src/main/resources/AgeMarginalPDFstatic.csv" + DATA_HOUSEHOLD_AGE_AT_BIRTH_PDF = "src/main/resources/HouseholdAgeAtBirthPDF.csv" + DATA_DEATH_PROB_GIVEN_AGE = "src/main/resources/DeathProbGivenAge.csv" +} \ No newline at end of file From 2e7043fe393a3c400734e1130b3bcd3b836a9c60 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Tue, 20 Jun 2017 21:12:42 +0300 Subject: [PATCH 2/7] Created a CentralBankConfig class. --- .../java/configuration/CentralBankConfig.java | 117 ++++++++++++++++ .../java/configuration/GovernmentConfig.java | 4 + src/main/java/configuration/ModelConfig.java | 16 ++- src/main/java/housing/CentralBank.java | 125 ++++++++---------- src/main/resources/model.conf | 31 +++-- 5 files changed, 212 insertions(+), 81 deletions(-) create mode 100644 src/main/java/configuration/CentralBankConfig.java diff --git a/src/main/java/configuration/CentralBankConfig.java b/src/main/java/configuration/CentralBankConfig.java new file mode 100644 index 00000000..80d7360d --- /dev/null +++ b/src/main/java/configuration/CentralBankConfig.java @@ -0,0 +1,117 @@ +package configuration; + + +import com.typesafe.config.Config; + + +/** + * + * @author davidrpugh + */ +public class CentralBankConfig { + + private double maxFirstTimeBuyerLTV; + private double maxOwnerOccupiersLTV; + private double maxBuyToLetLTV; + private double fractionOverMaxLTV; + private double maxFirstTimeBuyersLTI; + private double maxOwnerOccupiersLTI; + private double fractionOverMaxLTI; + private double affordabilityCoefficient; + private double buyToLetStressedInterest; + private double maxInterestCoverageRatio; + + CentralBankConfig(Config config) { + maxFirstTimeBuyerLTV = config.getDouble("max-first-time-buyers-ltv"); + maxOwnerOccupiersLTV = config.getDouble("max-owner-occupiers-ltv"); + maxBuyToLetLTV = config.getDouble("max-buy-to-let-ltv"); + fractionOverMaxLTV = config.getDouble("fraction-over-max-ltv"); + maxFirstTimeBuyersLTI = config.getDouble("max-first-time-buyers-lti"); + maxOwnerOccupiersLTI = config.getDouble("max-owner-occupiers-lti"); + fractionOverMaxLTI = config.getDouble("fraction=over-max-lti"); + affordabilityCoefficient = config.getDouble("affordability-coefficient"); + buyToLetStressedInterest = config.getDouble("buy-to-let-stressed-interest-ratio"); + maxInterestCoverageRatio = config.getDouble("max-interest-coverage-ratio"); + } + + /** Maximum LTV ratio that the bank would allow for first-time-buyers when not regulated. + * + * @return + */ + public double getMaxFirstTimeBuyerLTV() { + return maxFirstTimeBuyerLTV; + } + + /** Maximum LTV ratio that the bank would allow for owner-occupiers when not regulated. + * + * @return + */ + public double getMaxOwnerOccupiersLTV() { + return maxOwnerOccupiersLTV; + } + + /** Maximum LTV ratio that the bank would allow for BTL investors when not regulated. + * + * @return + */ + public double getMaxBuyToLetLTV() { + return maxBuyToLetLTV; + } + + /** Maximum fraction of mortgages that the bank can give over the LTV ratio limit. + * + * @return + */ + public double getFractionOverMaxLTV() { + return fractionOverMaxLTV; + } + + /** Maximum LTI ratio that the bank would allow for first-time-buyers when not regulated. + * + * @return + */ + public double getMaxFirstTimeBuyersLTI() { + return maxFirstTimeBuyersLTI; + } + + /** Maximum LTI ratio that the bank would allow for owner-occupiers when not regulated. + * + * @return + */ + public double getMaxOwnerOccupiersLTI() { + return maxOwnerOccupiersLTI; + } + + /** Maximum fraction of mortgages that the bank can give over the LTI ratio limit. + * + * @return + */ + public double getFractionOverMaxLTI() { + return fractionOverMaxLTI; + } + + /** Maximum fraction of the household's income to be spent on mortgage repayments under stressed conditions. + * + * @return + */ + public double getAffordabilityCoefficient() { + return affordabilityCoefficient; + } + + /** Interest rate under stressed condition for BTL investors when calculating interest coverage ratios, ICR. + * + * @return + */ + public double getBuyToLetStressedInterest() { + return buyToLetStressedInterest; + } + + /** Interest coverage ratio (ICR) limit imposed by the central bank. + * + * @return + */ + public double getMaxInterestCoverageRatio() { + return maxInterestCoverageRatio; + } + +} diff --git a/src/main/java/configuration/GovernmentConfig.java b/src/main/java/configuration/GovernmentConfig.java index f38940f0..ea180280 100644 --- a/src/main/java/configuration/GovernmentConfig.java +++ b/src/main/java/configuration/GovernmentConfig.java @@ -4,6 +4,10 @@ import com.typesafe.config.Config; +/** + * + * @author davidrpugh + */ public class GovernmentConfig { private double personalAllowanceLimit; diff --git a/src/main/java/configuration/ModelConfig.java b/src/main/java/configuration/ModelConfig.java index b606d890..f6f9deed 100644 --- a/src/main/java/configuration/ModelConfig.java +++ b/src/main/java/configuration/ModelConfig.java @@ -13,11 +13,13 @@ public class ModelConfig { private HousingMarketConfig housingMarketConfig; private GovernmentConfig governmentConfig; + private CentralBankConfig centralBankConfig; public ModelConfig(String filename) { Config config = ConfigFactory.load(filename); housingMarketConfig = new HousingMarketConfig(config.getConfig("simulation.housing-market")); - governmentConfig = new GovernmentConfig(config.getConfig("simulation.governmentConfig")); + governmentConfig = new GovernmentConfig(config.getConfig("simulation.government")); + centralBankConfig = new CentralBankConfig(config.getConfig("simulation.central-bank")); } /** Housing Market Configuration @@ -28,8 +30,20 @@ public HousingMarketConfig getHousingMarketConfig() { return housingMarketConfig; } + /** Government Configuration + * + * @return a `GovernmentConfig` object encapsulating the government parameters. + */ public GovernmentConfig getGovernmentConfig() { return governmentConfig; } + /** Central Bank Configuration + * + * @return a `CentralBankConfig` object encapsulating the central bank parameters. + */ + public CentralBankConfig getCentralBankConfig() { + return centralBankConfig; + } + } diff --git a/src/main/java/housing/CentralBank.java b/src/main/java/housing/CentralBank.java index a56ad5bc..a68a42b4 100644 --- a/src/main/java/housing/CentralBank.java +++ b/src/main/java/housing/CentralBank.java @@ -1,82 +1,67 @@ package housing; +import configuration.CentralBankConfig; + import java.io.Serializable; public class CentralBank implements Serializable { - private static final long serialVersionUID = -2857716547766065142L; - - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - - public CentralBank() { - // Setup initial values - firstTimeBuyerLTVLimit = config.CENTRAL_BANK_MAX_FTB_LTV; - ownerOccupierLTVLimit= config.CENTRAL_BANK_MAX_OO_LTV; - buyToLetLTVLimit = config.CENTRAL_BANK_MAX_BTL_LTV; - - firstTimeBuyerLTILimit = config.CENTRAL_BANK_MAX_FTB_LTI; - ownerOccupierLTILimit = config.CENTRAL_BANK_MAX_OO_LTI; - - proportionOverLTVLimit = config.CENTRAL_BANK_FRACTION_OVER_MAX_LTV; - proportionOverLTILimit = config.CENTRAL_BANK_FRACTION_OVER_MAX_LTI; + + private static final long serialVersionUID = -2857716547766065142L; + private CentralBankConfig config; - interestCoverRatioLimit = config.CENTRAL_BANK_MAX_ICR; - } - - /*** - * This method implements the policy strategy of the Central Bank. - * @param coreIndicators The current value of the core indicators - */ - public void step(collectors.CoreIndicators coreIndicators) { - /** Use this method to express the policy strategy of the central bank by - * setting the value of the various limits in response to the current - * value of the core indicators. - * - * Example policy: if house price growth is greater than 0.001 then FTB LTV limit is 0.75 - * otherwise (if house price growth is less than or equal to 0.001) - * FTB LTV limit is 0.95 - * - * Example code: - * - * if(coreIndicators.getHousePriceGrowth() > 0.001) { - * firstTimeBuyerLTVLimit = 0.75; - * } else { - * firstTimeBuyerLTVLimit = 0.95; - * } - */ + public CentralBank(CentralBankConfig config) { + this.config = config; + } + + /*** + * This method implements the policy strategy of the Central Bank. + * @param coreIndicators The current value of the core indicators + */ + public void step(collectors.CoreIndicators coreIndicators) { + /** Use this method to express the policy strategy of the central bank by + * setting the value of the various limits in response to the current + * value of the core indicators. + * + * Example policy: if house price growth is greater than 0.001 then FTB LTV limit is 0.75 + * otherwise (if house price growth is less than or equal to 0.001) + * FTB LTV limit is 0.95 + * + * Example code: + * + * if(coreIndicators.getHousePriceGrowth() > 0.001) { + * firstTimeBuyerLTVLimit = 0.75; + * } else { + * firstTimeBuyerLTVLimit = 0.95; + * } + */ - // Include the policy strategy code here: + // Include the policy strategy code here: - } - - public double loanToIncomeRegulation(boolean firstTimeBuyer) { - if(firstTimeBuyer) { - return(firstTimeBuyerLTILimit); - } - return(ownerOccupierLTILimit); - } + } + + public double loanToIncomeRegulation(boolean firstTimeBuyer) { + if(firstTimeBuyer) { + return config.getMaxFirstTimeBuyersLTI(); + } else { + return config.getMaxOwnerOccupiersLTI(); + } + } - public double loanToValueRegulation(boolean firstTimeBuyer, boolean isHome) { - if(isHome) { - if(firstTimeBuyer) { - return(firstTimeBuyerLTVLimit); - } - return(ownerOccupierLTVLimit); - } - return(buyToLetLTVLimit); - } - - public double interestCoverageRatioRegulation() { - return(interestCoverRatioLimit); - } + public double loanToValueRegulation(boolean firstTimeBuyer, boolean isHome) { + if(isHome) { + if (firstTimeBuyer) { + return config.getMaxFirstTimeBuyerLTV(); + } else { + return config.getMaxOwnerOccupiersLTV(); + } + } else { + return config.getMaxBuyToLetLTV(); + } + } + + public double interestCoverageRatioRegulation() { + return config.getMaxInterestCoverageRatio(); + } - public double ownerOccupierLTILimit; // LTI upper limit for owner-occupiers - public double ownerOccupierLTVLimit; // LTV upper limit for owner-occupiers - public double buyToLetLTILimit; // LTI upper limit for Buy-to-let investors - public double buyToLetLTVLimit; // LTV upper limit for Buy-to-let investors - public double firstTimeBuyerLTILimit; // LTI upper limit for first-time buyers - public double firstTimeBuyerLTVLimit; // LTV upper limit for first-time buyers - public double proportionOverLTILimit; // proportion of mortgages that are allowed to be above the respective LTI limit - public double proportionOverLTVLimit; // proportion of mortgages that are allowed to be above the respective LTV limit - public double interestCoverRatioLimit; } diff --git a/src/main/resources/model.conf b/src/main/resources/model.conf index 28ce3a1f..9082d0fc 100644 --- a/src/main/resources/model.conf +++ b/src/main/resources/model.conf @@ -250,26 +250,37 @@ simulation { # TODO: We need references or justification for all these values! Also, need to clarify meaning of "when not regulated" central-bank { # Maximum LTV ratio that the bank would allow for first-time-buyers when not regulated (double) - CENTRAL_BANK_MAX_FTB_LTV = 0.95 + max-first-time-buyer-ltv = 0.95 + # Maximum LTV ratio that the bank would allow for owner-occupiers when not regulated (double) - CENTRAL_BANK_MAX_OO_LTV = 0.9 + max-owner-occupiers-ltv = 0.9 + # Maximum LTV ratio that the bank would allow for BTL investors when not regulated (double) - CENTRAL_BANK_MAX_BTL_LTV = 0.8 + max-buy-to-let-ltv = 0.8 + # Maximum fraction of mortgages that the bank can give over the LTV ratio limit (double) - CENTRAL_BANK_FRACTION_OVER_MAX_LTV = 0.0 + fraction-over-max-ltv = 0.0 + # Maximum LTI ratio that the bank would allow for first-time-buyers when not regulated (double) - CENTRAL_BANK_MAX_FTB_LTI = 6.0 + max-first-time-buyers-lti = 6.0 + # Maximum LTI ratio that the bank would allow for owner-occupiers when not regulated (double) - CENTRAL_BANK_MAX_OO_LTI = 6.0 + max-owner-occupiers-lti = 6.0 + # Maximum fraction of mortgages that the bank can give over the LTI ratio limit (double) - CENTRAL_BANK_FRACTION_OVER_MAX_LTI = 0.15 + fraction-over-max-lti = 0.15 + # Maximum fraction of the household's income to be spent on mortgage repayments under stressed conditions (double) - CENTRAL_BANK_AFFORDABILITY_COEFF = 0.5 + affordability-coeff = 0.5 + # Interest rate under stressed condition for BTL investors when calculating interest coverage ratios, ICR (double) - CENTRAL_BANK_BTL_STRESSED_INTEREST = 0.05 + buy-to-let-stressed-interest = 0.05 + # Interest coverage ratio (ICR) limit imposed by the central bank (double) - CENTRAL_BANK_MAX_ICR = 1.25 + max-interest-coverage-ratio = 1.25 + } + ################################################## ############ Construction parameters ############# ################################################## From f70e32108cf0c65f722a76a04137879ec7190d89 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Thu, 22 Jun 2017 06:14:06 +0300 Subject: [PATCH 3/7] Added a BuyToLetConfig class. --- src/main/java/collectors/CreditSupply.java | 211 +++++++++--------- .../java/configuration/BuyToLetConfig.java | 60 +++++ .../HouseholdBehaviorConfig.java | 11 + src/main/java/housing/Bank.java | 2 - src/main/java/housing/Config.java | 13 -- src/main/java/housing/House.java | 2 - src/main/java/housing/HouseSaleMarket.java | 4 +- src/main/java/housing/Household.java | 2 - src/main/java/housing/HouseholdBehaviour.java | 2 - src/main/java/housing/Lifecycle.java | 2 - src/main/java/housing/Model.java | 2 +- src/main/resources/model.conf | 37 +-- 12 files changed, 202 insertions(+), 146 deletions(-) create mode 100644 src/main/java/configuration/BuyToLetConfig.java create mode 100644 src/main/java/configuration/HouseholdBehaviorConfig.java diff --git a/src/main/java/collectors/CreditSupply.java b/src/main/java/collectors/CreditSupply.java index fd007e70..a2ccd978 100644 --- a/src/main/java/collectors/CreditSupply.java +++ b/src/main/java/collectors/CreditSupply.java @@ -8,31 +8,30 @@ import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; public class CreditSupply extends CollectorBase { - private static final long serialVersionUID = 1630707025974306844L; - - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - - public CreditSupply() { - mortgageCounter = 0; - ftbCounter = 0; - btlCounter = 0; - // TODO: This limit in the number of events taken into account to build statistics is not explained in the paper (affects oo_lti, oo_ltv, btl_ltv, btl_icr, downpayments) - setArchiveLength(10000); - } + private static final long serialVersionUID = 1630707025974306844L; + + + public CreditSupply() { + mortgageCounter = 0; + ftbCounter = 0; + btlCounter = 0; + // TODO: This limit in the number of events taken into account to build statistics is not explained in the paper (affects oo_lti, oo_ltv, btl_ltv, btl_icr, downpayments) + setArchiveLength(10000); + } - /*** - * collect information for this timestep - */ - public void step() { + /*** + * collect information for this timestep + */ + public void step() { double oldTotalCredit = totalOOCredit + totalBTLCredit; totalOOCredit = 0.0; totalBTLCredit = 0.0; for(MortgageAgreement m : Model.bank.mortgages) { - if(m.isBuyToLet) { - totalBTLCredit += m.principal; - } else { - totalOOCredit += m.principal; - } + if(m.isBuyToLet) { + totalBTLCredit += m.principal; + } else { + totalOOCredit += m.principal; + } } netCreditGrowth = (totalOOCredit + totalBTLCredit - oldTotalCredit)/oldTotalCredit; nApprovedMortgages = mortgageCounter; @@ -41,55 +40,55 @@ public void step() { mortgageCounter = 0; ftbCounter = 0; btlCounter = 0; - } + } - /*** - * record information for a newly issued mortgage - * @param h - * @param approval - */ - public void recordLoan(Household h, MortgageAgreement approval, House house) { - double housePrice; - if(config.isMortgageDiagnosticsActive()) { - housePrice = approval.principal + approval.downPayment; - affordability = config.derivedParams.getAffordabilityDecay()*affordability + (1.0-config.derivedParams.getAffordabilityDecay())*approval.monthlyPayment/(h.monthlyEmploymentIncome); - if(approval.principal > 1.0) { - if(approval.isBuyToLet) { - btl_ltv.addValue(100.0*approval.principal/housePrice); -// double icr = Model.rentalMarket.getAverageSalePrice(house.getQuality())*12.0/(approval.principal*Model.bank.getBtLStressedMortgageInterestRate()); - double icr = Model.rentalMarket.averageSoldGrossYield*approval.purchasePrice/(approval.principal*config.getCentralBankBTLStressedInterest()); - btl_icr.addValue(icr); - } else { - oo_ltv.addValue(100.0*approval.principal/housePrice); - oo_lti.addValue(approval.principal/h.annualEmploymentIncome()); - } - downpayments.addValue(approval.downPayment); - } -// approved_mortgages[0][approved_mortgages_index] = approval.principal/(h.annualEmploymentIncome()); -// approved_mortgages[1][approved_mortgages_index] = approval.downPayment/(h.annualEmploymentIncome()); -// approved_mortgages_index += 1; -// if(approved_mortgages_index == ARCHIVE_LEN) approved_mortgages_index = 0; - mortgageCounter += 1; - if(approval.isFirstTimeBuyer) ftbCounter += 1; - if(approval.isBuyToLet) btlCounter += 1; - } - } - + /*** + * record information for a newly issued mortgage + * @param h + * @param approval + */ + public void recordLoan(Household h, MortgageAgreement approval, House house) { + double housePrice; + if(config.isMortgageDiagnosticsActive()) { + housePrice = approval.principal + approval.downPayment; + affordability = config.derivedParams.getAffordabilityDecay()*affordability + (1.0-config.derivedParams.getAffordabilityDecay())*approval.monthlyPayment/(h.monthlyEmploymentIncome); + if(approval.principal > 1.0) { + if(approval.isBuyToLet) { + btl_ltv.addValue(100.0*approval.principal/housePrice); +// double icr = Model.rentalMarket.getAverageSalePrice(house.getQuality())*12.0/(approval.principal*Model.bank.getBtLStressedMortgageInterestRate()); + double icr = Model.rentalMarket.averageSoldGrossYield*approval.purchasePrice/(approval.principal*config.getCentralBankBTLStressedInterest()); + btl_icr.addValue(icr); + } else { + oo_ltv.addValue(100.0*approval.principal/housePrice); + oo_lti.addValue(approval.principal/h.annualEmploymentIncome()); + } + downpayments.addValue(approval.downPayment); + } +// approved_mortgages[0][approved_mortgages_index] = approval.principal/(h.annualEmploymentIncome()); +// approved_mortgages[1][approved_mortgages_index] = approval.downPayment/(h.annualEmploymentIncome()); +// approved_mortgages_index += 1; +// if(approved_mortgages_index == ARCHIVE_LEN) approved_mortgages_index = 0; + mortgageCounter += 1; + if(approval.isFirstTimeBuyer) ftbCounter += 1; + if(approval.isBuyToLet) btlCounter += 1; + } + } + - // ---- Mason stuff - // ---------------- - public String desLTI() {return("Loan to Income constraint on mortgages");} - public String desTHETA_FTB() {return("Loan to Value haircut for first time buyers");} - public String desTHETA_HOME() {return("Loan to Value haircut for homeowners");} - public String desTHETA_BTL() {return("Loan to Value haircut for buy-to-let investors");} - public String desN_PAYMENTS() {return("Number of monthly repayments in a mortgage");} - public double getBaseRate() { - return Model.bank.getBaseRate(); - } - public void setBaseRate(double rate) { - Model.bank.setBaseRate(rate); - } - + // ---- Mason stuff + // ---------------- + public String desLTI() {return("Loan to Income constraint on mortgages");} + public String desTHETA_FTB() {return("Loan to Value haircut for first time buyers");} + public String desTHETA_HOME() {return("Loan to Value haircut for homeowners");} + public String desTHETA_BTL() {return("Loan to Value haircut for buy-to-let investors");} + public String desN_PAYMENTS() {return("Number of monthly repayments in a mortgage");} + public double getBaseRate() { + return Model.bank.getBaseRate(); + } + public void setBaseRate(double rate) { + Model.bank.setBaseRate(rate); + } + public double [] getOOLTVDistribution() {return(oo_ltv.getValues());} public double [] getOOLTIDistribution() {return(oo_lti.getValues());} public double [] getBTLLTVDistribution() {return(btl_ltv.getValues());} @@ -115,53 +114,53 @@ public void setSaveBTLICRDistribution(boolean doSave) throws FileNotFoundExcepti public int getNRegisteredMortgages() { - return(Model.bank.mortgages.size()); + return(Model.bank.mortgages.size()); } - public int getArchiveLength() { - return archiveLength; - } - - public void writeDistributionToFile(double [] vals, String filename) throws FileNotFoundException, UnsupportedEncodingException { + public int getArchiveLength() { + return archiveLength; + } + + public void writeDistributionToFile(double [] vals, String filename) throws FileNotFoundException, UnsupportedEncodingException { PrintWriter dist = new PrintWriter(filename, "UTF-8"); if(vals.length > 0) { - dist.print(vals[0]); - for(int i=1; i, Serializable { private static final long serialVersionUID = 4538336934216907799L; - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - // static public class Config { // public static int N_QUALITY = 48; // number of quality bands // } diff --git a/src/main/java/housing/HouseSaleMarket.java b/src/main/java/housing/HouseSaleMarket.java index 5bb7e556..09917f9b 100644 --- a/src/main/java/housing/HouseSaleMarket.java +++ b/src/main/java/housing/HouseSaleMarket.java @@ -14,9 +14,7 @@ *****************************************************/ public class HouseSaleMarket extends HousingMarket { private static final long serialVersionUID = -2878118108039744432L; - - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - + public HouseSaleMarket() { offersPY = new PriorityQueue2D<>(new HousingMarketRecord.PYComparator()); } diff --git a/src/main/java/housing/Household.java b/src/main/java/housing/Household.java index a8655f2d..f0e34c25 100644 --- a/src/main/java/housing/Household.java +++ b/src/main/java/housing/Household.java @@ -25,8 +25,6 @@ public class Household implements IHouseOwner, Serializable { private static final long serialVersionUID = -5042897399316333745L; - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - public static int bankruptcies = 0; // TODO: Unused variable... counts bankruptcies, but it's never used! /******************************************************** diff --git a/src/main/java/housing/HouseholdBehaviour.java b/src/main/java/housing/HouseholdBehaviour.java index d1393bbe..b433ce87 100644 --- a/src/main/java/housing/HouseholdBehaviour.java +++ b/src/main/java/housing/HouseholdBehaviour.java @@ -14,8 +14,6 @@ public class HouseholdBehaviour implements Serializable {// implements IHouseholdBehaviour { private static final long serialVersionUID = -7785886649432814279L; - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // public final double DOWNPAYMENT_FRACTION = 0.75 + 0.0025*rand.nextGaussian(); // Fraction of bank-balance household would like to spend on mortgage downpayments // public final double INTENSITY_OF_CHOICE = 10.0; diff --git a/src/main/java/housing/Lifecycle.java b/src/main/java/housing/Lifecycle.java index 3e49d2e5..146517bc 100644 --- a/src/main/java/housing/Lifecycle.java +++ b/src/main/java/housing/Lifecycle.java @@ -7,8 +7,6 @@ public class Lifecycle implements Serializable { private static final long serialVersionUID = -2455155016204679970L; - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - public Lifecycle(double iage) { age = iage; incomePercentile = rand.nextDouble(); diff --git a/src/main/java/housing/Model.java b/src/main/java/housing/Model.java index a20cfb2a..ed4599f4 100644 --- a/src/main/java/housing/Model.java +++ b/src/main/java/housing/Model.java @@ -58,7 +58,7 @@ public Model(long seed) { recorder = new collectors.Recorder(); transactionRecorder = new collectors.MicroDataRecorder(); - centralBank = new CentralBank(); + centralBank = new CentralBank(config.getCentralBankConfig()); mBank = new Bank(); mConstruction = new Construction(); mHouseholds = new ArrayList(config.TARGET_POPULATION*2); diff --git a/src/main/resources/model.conf b/src/main/resources/model.conf index 9082d0fc..eb6f60ce 100644 --- a/src/main/resources/model.conf +++ b/src/main/resources/model.conf @@ -94,20 +94,30 @@ simulation { ######### Household behaviour parameters ######### ################################################## behavior { + ############# Buy-To-Let parameters ############## - # Prior probability of being (wanting to be) a BTL investor (double) - # TODO: Shouldn't this be 4% according to the article? - P_INVESTOR = 0.16 - # Minimum income percentile for a household to be a BTL investor (double) - MIN_INVESTOR_PERCENTILE = 0.5 - # Weight that fundamentalists put on cap gain (double) - FUNDAMENTALIST_CAP_GAIN_COEFF = 0.5 - # Weight that trend-followers put on cap gain (double) - TREND_CAP_GAIN_COEFF = 0.9 - # Probability that a BTL investor is a fundamentalist versus a trend-follower (double) - P_FUNDAMENTALIST = 0.5 - # Chooses between two possible equations for BTL investors to make their buy/sell decisions (boolean) - BTL_YIELD_SCALING = false + buy-to-let { + + # Prior probability of being (wanting to be) a BTL investor (double) + # TODO: Shouldn't this be 4% according to the article? + probability-investor = 0.16 + + # Minimum income percentile for a household to be a BTL investor (double) + min-income-percentile = 0.5 + + # Weight that fundamentalists put on cap gain (double) + fundamentalist-capital-gain-coefficient = 0.5 + + # Weight that trend-followers put on cap gain (double) + trend-followers-capital-gain-coefficient = 0.9 + + # Probability that a BTL investor is a fundamentalist versus a trend-follower (double) + probability-fundamentalist = 0.5 + + # Chooses between two possible equations for BTL investors to make their buy/sell decisions (boolean) + buy-to-let-yield-scaling = false + + } ################ Rent parameters ################# # Desired proportion of income to be spent on rent (double) @@ -249,6 +259,7 @@ simulation { ################################################## # TODO: We need references or justification for all these values! Also, need to clarify meaning of "when not regulated" central-bank { + # Maximum LTV ratio that the bank would allow for first-time-buyers when not regulated (double) max-first-time-buyer-ltv = 0.95 From d4e325b37ba71cb9e85cb412ff6784ed2bce4417 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Thu, 22 Jun 2017 07:24:52 +0300 Subject: [PATCH 4/7] Started work to link up new config for household behavior --- .../HouseholdBehaviorConfig.java | 10 +- .../java/configuration/HouseholdConfig.java | 18 + src/main/java/configuration/ModelConfig.java | 2 + src/main/java/housing/HouseSaleMarket.java | 2 +- src/main/java/housing/HouseholdBehaviour.java | 896 +++++++++--------- 5 files changed, 479 insertions(+), 449 deletions(-) create mode 100644 src/main/java/configuration/HouseholdConfig.java diff --git a/src/main/java/configuration/HouseholdBehaviorConfig.java b/src/main/java/configuration/HouseholdBehaviorConfig.java index bb058c29..4eac903b 100644 --- a/src/main/java/configuration/HouseholdBehaviorConfig.java +++ b/src/main/java/configuration/HouseholdBehaviorConfig.java @@ -1,11 +1,19 @@ package configuration; +import com.typesafe.config.Config; + + public class HouseholdBehaviorConfig { + private BuyToLetConfig buyToLetConfig; - HouseholdBehaviorConfig() { + HouseholdBehaviorConfig(Config config) { + buyToLetConfig = new BuyToLetConfig(config.getConfig("buy-to-let")); + } + public BuyToLetConfig getBuyToLetConfig() { + return buyToLetConfig; } } diff --git a/src/main/java/configuration/HouseholdConfig.java b/src/main/java/configuration/HouseholdConfig.java new file mode 100644 index 00000000..41ee55d1 --- /dev/null +++ b/src/main/java/configuration/HouseholdConfig.java @@ -0,0 +1,18 @@ +package configuration; + + +import com.typesafe.config.Config; + +public class HouseholdConfig { + + private HouseholdBehaviorConfig behaviorConfig; + + HouseholdConfig(Config config) { + behaviorConfig = new HouseholdBehaviorConfig(config.getConfig("behavior")); + } + + public HouseholdBehaviorConfig getBehaviorConfig() { + return behaviorConfig; + } + +} diff --git a/src/main/java/configuration/ModelConfig.java b/src/main/java/configuration/ModelConfig.java index f6f9deed..d14ef78d 100644 --- a/src/main/java/configuration/ModelConfig.java +++ b/src/main/java/configuration/ModelConfig.java @@ -14,12 +14,14 @@ public class ModelConfig { private HousingMarketConfig housingMarketConfig; private GovernmentConfig governmentConfig; private CentralBankConfig centralBankConfig; + private HouseholdBehaviorConfig householdBehaviorConfig; public ModelConfig(String filename) { Config config = ConfigFactory.load(filename); housingMarketConfig = new HousingMarketConfig(config.getConfig("simulation.housing-market")); governmentConfig = new GovernmentConfig(config.getConfig("simulation.government")); centralBankConfig = new CentralBankConfig(config.getConfig("simulation.central-bank")); + householdConfig = new HouseholdBehaviorConfig(config.getConfig("simulation.households")) } /** Housing Market Configuration diff --git a/src/main/java/housing/HouseSaleMarket.java b/src/main/java/housing/HouseSaleMarket.java index 09917f9b..2b9e34b7 100644 --- a/src/main/java/housing/HouseSaleMarket.java +++ b/src/main/java/housing/HouseSaleMarket.java @@ -14,7 +14,7 @@ *****************************************************/ public class HouseSaleMarket extends HousingMarket { private static final long serialVersionUID = -2878118108039744432L; - + public HouseSaleMarket() { offersPY = new PriorityQueue2D<>(new HousingMarketRecord.PYComparator()); } diff --git a/src/main/java/housing/HouseholdBehaviour.java b/src/main/java/housing/HouseholdBehaviour.java index b433ce87..890c9608 100644 --- a/src/main/java/housing/HouseholdBehaviour.java +++ b/src/main/java/housing/HouseholdBehaviour.java @@ -2,6 +2,7 @@ import java.io.Serializable; +import configuration.HouseholdBehaviorConfig; import org.apache.commons.math3.distribution.LogNormalDistribution; //import ec.util.MersenneTwisterFast; @@ -12,17 +13,17 @@ * @author daniel */ public class HouseholdBehaviour implements Serializable {// implements IHouseholdBehaviour { - private static final long serialVersionUID = -7785886649432814279L; + private static final long serialVersionUID = -7785886649432814279L; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// -// public final double DOWNPAYMENT_FRACTION = 0.75 + 0.0025*rand.nextGaussian(); // Fraction of bank-balance household would like to spend on mortgage downpayments -// public final double INTENSITY_OF_CHOICE = 10.0; +// public final double DOWNPAYMENT_FRACTION = 0.75 + 0.0025*rand.nextGaussian(); // Fraction of bank-balance household would like to spend on mortgage downpayments +// public final double INTENSITY_OF_CHOICE = 10.0; - private Model.MersenneTwister rand = Model.rand; // Passes the Model's random number generator to a private field - public boolean BTLInvestor; - public double propensityToSave; - public double desiredBalance; - public double BtLCapGainCoeff; // Sensitivity of BtL investors to capital gain, 0.0 cares only about rental yield, 1.0 cares only about cap gain + private Model.MersenneTwister rand = Model.rand; // Passes the Model's random number generator to a private field + public boolean BTLInvestor; + public double propensityToSave; + public double desiredBalance; + public double BtLCapGainCoeff; // Sensitivity of BtL investors to capital gain, 0.0 cares only about rental yield, 1.0 cares only about cap gain // Size distributions for downpayments of first-time-buyers and owner-occupiers public LogNormalDistribution FTB_DOWNPAYMENT = new LogNormalDistribution(rand, config.DOWNPAYMENT_FTB_SCALE, @@ -30,234 +31,235 @@ public class HouseholdBehaviour implements Serializable {// implements IHousehol public LogNormalDistribution OO_DOWNPAYMENT = new LogNormalDistribution(rand, config.DOWNPAYMENT_OO_SCALE, config.DOWNPAYMENT_OO_SHAPE); - public double sigma(double x) { // the Logistic function, sometimes called sigma function, 1/1+e^(-x) - return 1.0/(1.0+Math.exp(-1.0*x)); - } - - /*************************************** - * Constructor: initialise the behavioural variables for a new household: propensity to save, and - * if the income percentile is above a minimum, decide whether to give the household - * the BTL investor 'gene', and if so, decide whether they will be a fundamentalist or trend follower investor - * - * @param incomePercentile the fixed income percentile for the household (assumed constant over a lifetime), - * used to determine whether the household can be a BTL investor + public double sigma(double x) { // the Logistic function, sometimes called sigma function, 1/1+e^(-x) + return 1.0/(1.0+Math.exp(-1.0*x)); + } + + /*************************************** + * Constructor: initialise the behavioural variables for a new household: propensity to save, and + * if the income percentile is above a minimum, decide whether to give the household + * the BTL investor 'gene', and if so, decide whether they will be a fundamentalist or trend follower investor + * + * @param incomePercentile the fixed income percentile for the household (assumed constant over a lifetime), + * used to determine whether the household can be a BTL investor ***************************************************/ - public HouseholdBehaviour(double incomePercentile) { - // Propensity to save is computed here so that it is constant for a given agent + public HouseholdBehaviour(HouseholdBehaviorConfig config, double incomePercentile) { + + // Propensity to save is computed here so that it is constant for a given agent propensityToSave = config.DESIRED_BANK_BALANCE_EPSILON*rand.nextGaussian(); - BtLCapGainCoeff = 0.0; - if(config.BTL_ENABLED) { - if(incomePercentile > config.MIN_INVESTOR_PERCENTILE && - rand.nextDouble() < config.getPInvestor()/config.MIN_INVESTOR_PERCENTILE) { - BTLInvestor = true;//(data.Households.buyToLetDistribution.inverseCumulativeProbability(rand.nextDouble())+0.5); - double type = rand.nextDouble(); - if(type < config.P_FUNDAMENTALIST) { - BtLCapGainCoeff = config.FUNDAMENTALIST_CAP_GAIN_COEFF; - } else { - BtLCapGainCoeff = config.TREND_CAP_GAIN_COEFF; - } - } else { - BTLInvestor = false; - } - } else { - BTLInvestor = false; - } - desiredBalance = -1.0; - } - /////////////////////////////////////////////////////////////////////////////////////////////// - // Owner-Ocupier behaviour - /////////////////////////////////////////////////////////////////////////////////////////////// - - /******************************** - * How much a household consumes (optional, non-essential consumption) - * Consumption rule made to fit ONS wealth in Great Britain data. - * - * @param me Household - * @return Non-essential consumption for the month - ********************************/ - public double desiredConsumptionB(Household me) {//double monthlyIncome, double bankBalance) { - return(config.CONSUMPTION_FRACTION*Math.max(me.getBankBalance() - desiredBankBalance(me), 0.0)); - } - - /******************************** - * @param me Household - * @return Minimum bank balance a household would like to have at the end of the month (like a minimum safety net). - * Used to determine non-essential consumption. + BtLCapGainCoeff = 0.0; + if(config.BTL_ENABLED) { + if(incomePercentile > config.getBuyToLetConfig().getMinIncomePercentile() && + rand.nextDouble() < config.getBuyToLetConfig().getProbabilityInvestor() / config.getBuyToLetConfig().getMinIncomePercentile()) { + BTLInvestor = true;//(data.Households.buyToLetDistribution.inverseCumulativeProbability(rand.nextDouble())+0.5); + double type = rand.nextDouble(); + if(type < config.getBuyToLetConfig().getProbabilityFundamentalist()) { + BtLCapGainCoeff = config.getBuyToLetConfig().getFundamentalistCapitalGainCoefficient(); + } else { + BtLCapGainCoeff = config.getBuyToLetConfig().getTrendFollowersCapitalGainCoefficient(); + } + } else { + BTLInvestor = false; + } + } else { + BTLInvestor = false; + } + desiredBalance = -1.0; + } + /////////////////////////////////////////////////////////////////////////////////////////////// + // Owner-Ocupier behaviour + /////////////////////////////////////////////////////////////////////////////////////////////// + + /******************************** + * How much a household consumes (optional, non-essential consumption) + * Consumption rule made to fit ONS wealth in Great Britain data. + * + * @param me Household + * @return Non-essential consumption for the month + ********************************/ + public double desiredConsumptionB(Household me) {//double monthlyIncome, double bankBalance) { + return(config.CONSUMPTION_FRACTION*Math.max(me.getBankBalance() - desiredBankBalance(me), 0.0)); + } + + /******************************** + * @param me Household + * @return Minimum bank balance a household would like to have at the end of the month (like a minimum safety net). + * Used to determine non-essential consumption. *********************************/ - public double desiredBankBalance(Household me) { + public double desiredBankBalance(Household me) { // TODO: why only if desired bank balance is set to -1? (does this get calculated only once? why?) - if(desiredBalance == -1.0) { -// desiredBalance = 3.0*Math.exp(4.07*Math.log(me.getMonthlyPreTaxIncome()*12.0)-33.1 - propensityToSave); - double lnDesiredBalance = config.DESIRED_BANK_BALANCE_ALPHA + if(desiredBalance == -1.0) { +// desiredBalance = 3.0*Math.exp(4.07*Math.log(me.getMonthlyPreTaxIncome()*12.0)-33.1 - propensityToSave); + double lnDesiredBalance = config.DESIRED_BANK_BALANCE_ALPHA + config.DESIRED_BANK_BALANCE_BETA * Math.log(me.getMonthlyPreTaxIncome()*config.constants.MONTHS_IN_YEAR) + propensityToSave; - desiredBalance = Math.exp(lnDesiredBalance); - // TODO: What is this next rule? Not declared in the article! Check if 0.3 should be included as a parameter - if(me.lifecycle.incomePercentile < 0.3 && !isPropertyInvestor()) desiredBalance = 1.0; - } - return(desiredBalance); - } - - /*************************** - * @param me Household - * @param monthlyIncome Mohthly income - * @return desired purchase price after having decided to buy a house - ****************************/ - public double desiredPurchasePrice(Household me, double monthlyIncome) { - return(config.BUY_SCALE*config.constants.MONTHS_IN_YEAR*monthlyIncome - *Math.exp(config.BUY_EPSILON*rand.nextGaussian()) - /(1.0 - config.BUY_WEIGHT_HPA*HPAExpectation())); - -// PurchasePlan plan = findBestPurchase(me); -// double housePrice = Model.housingMarket.getAverageSalePrice(plan.quality);//behaviour.desiredPurchasePrice(getMonthlyPreTaxIncome(), houseMarket.housePriceAppreciation()); -// return(1.01*housePrice*Math.exp(0.05*rand.nextGaussian())); - } - - /******************************** - * @param pbar average sale price of houses of the same quality - * @param d average number of days on the market before sale // TODO: Is this average or for this property - * @param principal amount of principal left on any mortgage on this house - * @return initial sale price of a house - ********************************/ - public double initialSalePrice(double pbar, double d, double principal) { - // TODO: During the first month, the third term is actually introducing an extra markup. Solve! - double exponent = config.SALE_MARKUP + Math.log(pbar) + desiredBalance = Math.exp(lnDesiredBalance); + // TODO: What is this next rule? Not declared in the article! Check if 0.3 should be included as a parameter + if(me.lifecycle.incomePercentile < 0.3 && !isPropertyInvestor()) desiredBalance = 1.0; + } + return(desiredBalance); + } + + /*************************** + * @param me Household + * @param monthlyIncome Mohthly income + * @return desired purchase price after having decided to buy a house + ****************************/ + public double desiredPurchasePrice(Household me, double monthlyIncome) { + return(config.BUY_SCALE*config.constants.MONTHS_IN_YEAR*monthlyIncome + *Math.exp(config.BUY_EPSILON*rand.nextGaussian()) + /(1.0 - config.BUY_WEIGHT_HPA*HPAExpectation())); + +// PurchasePlan plan = findBestPurchase(me); +// double housePrice = Model.housingMarket.getAverageSalePrice(plan.quality);//behaviour.desiredPurchasePrice(getMonthlyPreTaxIncome(), houseMarket.housePriceAppreciation()); +// return(1.01*housePrice*Math.exp(0.05*rand.nextGaussian())); + } + + /******************************** + * @param pbar average sale price of houses of the same quality + * @param d average number of days on the market before sale // TODO: Is this average or for this property + * @param principal amount of principal left on any mortgage on this house + * @return initial sale price of a house + ********************************/ + public double initialSalePrice(double pbar, double d, double principal) { + // TODO: During the first month, the third term is actually introducing an extra markup. Solve! + double exponent = config.SALE_MARKUP + Math.log(pbar) - config.SALE_WEIGHT_DAYS_ON_MARKET*Math.log((d + 1.0)/(config.constants.DAYS_IN_MONTH + 1.0)) + config.SALE_EPSILON*rand.nextGaussian(); - return(Math.max(Math.exp(exponent), principal)); - } - - - /** - * @return Does an owner-occupier decide to sell house? - */ - public boolean decideToSellHome(Household me) { - // TODO: need to add expenditure - if(isPropertyInvestor()) return(false); - return(rand.nextDouble() < config.derivedParams.MONTHLY_P_SELL*(1.0 + return(Math.max(Math.exp(exponent), principal)); + } + + + /** + * @return Does an owner-occupier decide to sell house? + */ + public boolean decideToSellHome(Household me) { + // TODO: need to add expenditure + if(isPropertyInvestor()) return(false); + return(rand.nextDouble() < config.derivedParams.MONTHLY_P_SELL*(1.0 + config.DECISION_TO_SELL_ALPHA*(config.DECISION_TO_SELL_HPC - Model.housingMarket.offersPQ.size()*1.0/Model.households.size())) + config.DECISION_TO_SELL_BETA*(config.DECISION_TO_SELL_INTEREST - Model.bank.getMortgageInterestRate())); - // reference - //int potentialQualityChange = Model.housingMarket.maxQualityGivenPrice(Model.bank.getMaxMortgage(me,true))- me.home.getQuality(); - //double p_move = data.Households.P_FORCEDTOMOVE + (data.Households.P_SELL-data.Households.P_FORCEDTOMOVE)/(1.0+Math.exp(5.0-2.0*potentialQualityChange)); - - /* - - // calc purchase price - PurchasePlan plan = findBestPurchase(me); - if(plan.quality < 0) return(false); // can't afford new home anyway - int currentQuality = me.home.getQuality(); - double currentUtility;// = utilityOfHome(me,me.home.getQuality()) - me.mortgageFor(me.home).nextPayment()/me.getMonthlyPreTaxIncome(); -// currentUtility = utilityOfHome(me,currentQuality) +(Model.housingMarket.getAverageSalePrice(currentQuality)*HPAExpectation()/12.0 - me.mortgageFor(me.home).nextPayment())/me.getMonthlyPreTaxIncome(); - double currentLeftForConsumption = 1.0 - (me.mortgageFor(me.home).nextPayment() - Model.housingMarket.getAverageSalePrice(currentQuality)*HPAExpectation()/12.0)/me.monthlyEmploymentIncome; -// currentUtility = (currentQuality-me.desiredQuality)/House.Config.N_QUALITY + qualityOfLiving(currentLeftForConsumption); - currentUtility = utilityOfHome(me, currentQuality) + qualityOfLiving(currentLeftForConsumption); -// System.out.println("Move utility = "+(plan.utility- currentUtility)); - - double p_move = data.Households.P_FORCEDTOMOVE; - p_move += 2.0*(data.Households.P_SELL-data.Households.P_FORCEDTOMOVE)/(1.0+Math.exp(4.0-INTENSITY_OF_CHOICE*(plan.utility - currentUtility))); - p_move *= 1.0 - data.HouseSaleMarket.SEASONAL_VOL_ADJ*Math.cos((2.0*3.141/12.0)*Model.getMonth()); - // System.out.println("Move utility = "+INTENSITY_OF_CHOICE*(plan.utility- currentUtility)+" "+p_move); - return(rand.nextDouble() < p_move); - */ - } - - /** - * - * @param me the household - * @param housePrice the price of the house + // reference + //int potentialQualityChange = Model.housingMarket.maxQualityGivenPrice(Model.bank.getMaxMortgage(me,true))- me.home.getQuality(); + //double p_move = data.Households.P_FORCEDTOMOVE + (data.Households.P_SELL-data.Households.P_FORCEDTOMOVE)/(1.0+Math.exp(5.0-2.0*potentialQualityChange)); + + /* + + // calc purchase price + PurchasePlan plan = findBestPurchase(me); + if(plan.quality < 0) return(false); // can't afford new home anyway + int currentQuality = me.home.getQuality(); + double currentUtility;// = utilityOfHome(me,me.home.getQuality()) - me.mortgageFor(me.home).nextPayment()/me.getMonthlyPreTaxIncome(); +// currentUtility = utilityOfHome(me,currentQuality) +(Model.housingMarket.getAverageSalePrice(currentQuality)*HPAExpectation()/12.0 - me.mortgageFor(me.home).nextPayment())/me.getMonthlyPreTaxIncome(); + double currentLeftForConsumption = 1.0 - (me.mortgageFor(me.home).nextPayment() - Model.housingMarket.getAverageSalePrice(currentQuality)*HPAExpectation()/12.0)/me.monthlyEmploymentIncome; +// currentUtility = (currentQuality-me.desiredQuality)/House.Config.N_QUALITY + qualityOfLiving(currentLeftForConsumption); + currentUtility = utilityOfHome(me, currentQuality) + qualityOfLiving(currentLeftForConsumption); +// System.out.println("Move utility = "+(plan.utility- currentUtility)); + + double p_move = data.Households.P_FORCEDTOMOVE; + p_move += 2.0*(data.Households.P_SELL-data.Households.P_FORCEDTOMOVE)/(1.0+Math.exp(4.0-INTENSITY_OF_CHOICE*(plan.utility - currentUtility))); + p_move *= 1.0 - data.HouseSaleMarket.SEASONAL_VOL_ADJ*Math.cos((2.0*3.141/12.0)*Model.getMonth()); + // System.out.println("Move utility = "+INTENSITY_OF_CHOICE*(plan.utility- currentUtility)+" "+p_move); + return(rand.nextDouble() < p_move); + */ + } + + /** + * + * @param me the household + * @param housePrice the price of the house * @return the downpayment */ - public double downPayment(Household me, double housePrice) { -// return(me.getBankBalance() - (1.0 - DOWNPAYMENT_FRACTION)*desiredBankBalance(me)); - if(me.getBankBalance() > housePrice*config.BANK_BALANCE_FOR_CASH_DOWNPAYMENT) { // calibrated against mortgage approval/housing transaction ratio, core indicators average 1987-2006 - return(housePrice); - } - double downpayment; - if(me.isFirstTimeBuyer()) { - downpayment = Model.housingMarket.housePriceIndex*FTB_DOWNPAYMENT.inverseCumulativeProbability(Math.max(0.0, + public double downPayment(Household me, double housePrice) { +// return(me.getBankBalance() - (1.0 - DOWNPAYMENT_FRACTION)*desiredBankBalance(me)); + if(me.getBankBalance() > housePrice*config.BANK_BALANCE_FOR_CASH_DOWNPAYMENT) { // calibrated against mortgage approval/housing transaction ratio, core indicators average 1987-2006 + return(housePrice); + } + double downpayment; + if(me.isFirstTimeBuyer()) { + downpayment = Model.housingMarket.housePriceIndex*FTB_DOWNPAYMENT.inverseCumulativeProbability(Math.max(0.0, (me.lifecycle.incomePercentile - config.DOWNPAYMENT_MIN_INCOME)/(1 - config.DOWNPAYMENT_MIN_INCOME))); - } else if(isPropertyInvestor()) { - downpayment = housePrice*(Math.max(0.0, - config.DOWNPAYMENT_BTL_MEAN + config.DOWNPAYMENT_BTL_EPSILON*rand.nextGaussian())); // calibrated... - //downpayment = housePrice*(Math.max(0.0, 0.26+0.08*rand.nextGaussian())); // calibrated... - } else { - downpayment = Model.housingMarket.housePriceIndex*OO_DOWNPAYMENT.inverseCumulativeProbability(Math.max(0.0, + } else if(isPropertyInvestor()) { + downpayment = housePrice*(Math.max(0.0, + config.DOWNPAYMENT_BTL_MEAN + config.DOWNPAYMENT_BTL_EPSILON*rand.nextGaussian())); // calibrated... + //downpayment = housePrice*(Math.max(0.0, 0.26+0.08*rand.nextGaussian())); // calibrated... + } else { + downpayment = Model.housingMarket.housePriceIndex*OO_DOWNPAYMENT.inverseCumulativeProbability(Math.max(0.0, (me.lifecycle.incomePercentile - config.DOWNPAYMENT_MIN_INCOME)/(1 - config.DOWNPAYMENT_MIN_INCOME))); - } - if(downpayment > me.getBankBalance()) downpayment = me.getBankBalance(); - return(downpayment); -// return(Model.housingMarket.housePriceIndex*OO_DOWNPAYMENT.inverseCumulativeProbability(me.lifecycle.incomePercentile)); - } - - - /******************************************************** - * Decide how much to drop the list-price of a house if - * it has been on the market for (another) month and hasn't - * sold. Calibrated against Zoopla dataset in Bank of England - * - * @param sale The HouseSaleRecord of the house that is on the market. - ********************************************************/ - public double rethinkHouseSalePrice(HouseSaleRecord sale) { -// return(sale.getPrice() *0.95); - - if(rand.nextDouble() < config.P_SALE_PRICE_REDUCE) { - double logReduction = config.REDUCTION_MU+(rand.nextGaussian()*config.REDUCTION_SIGMA); -// System.out.println(1.0-Math.exp(logReduction)/100.0); - return(sale.getPrice() * (1.0-Math.exp(logReduction)/100.0)); -// return(sale.getPrice() * (1.0-data.Households.REDUCTION_MU/100.0) + rand.nextGaussian()*data.Households.REDUCTION_SIGMA/100.0); - } - return(sale.getPrice()); - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Renter behaviour - /////////////////////////////////////////////////////////////////////////////////////////////// - - /*** renters or OO after selling home decide whether to rent or buy - * N.B. even though the HH may not decide to rent a house of the - * same quality as they would buy, the cash value of the difference in quality - * is assumed to be the difference in rental price between the two qualities. - * @return true if we should buy a house, false if we should rent - */ - public boolean rentOrPurchaseDecision(Household me) { - if(isPropertyInvestor()) return(true); - - double purchasePrice = Math.min(desiredPurchasePrice(me, me.monthlyEmploymentIncome), + } + if(downpayment > me.getBankBalance()) downpayment = me.getBankBalance(); + return(downpayment); +// return(Model.housingMarket.housePriceIndex*OO_DOWNPAYMENT.inverseCumulativeProbability(me.lifecycle.incomePercentile)); + } + + + /******************************************************** + * Decide how much to drop the list-price of a house if + * it has been on the market for (another) month and hasn't + * sold. Calibrated against Zoopla dataset in Bank of England + * + * @param sale The HouseSaleRecord of the house that is on the market. + ********************************************************/ + public double rethinkHouseSalePrice(HouseSaleRecord sale) { +// return(sale.getPrice() *0.95); + + if(rand.nextDouble() < config.P_SALE_PRICE_REDUCE) { + double logReduction = config.REDUCTION_MU+(rand.nextGaussian()*config.REDUCTION_SIGMA); +// System.out.println(1.0-Math.exp(logReduction)/100.0); + return(sale.getPrice() * (1.0-Math.exp(logReduction)/100.0)); +// return(sale.getPrice() * (1.0-data.Households.REDUCTION_MU/100.0) + rand.nextGaussian()*data.Households.REDUCTION_SIGMA/100.0); + } + return(sale.getPrice()); + } + + /////////////////////////////////////////////////////////////////////////////////////////////// + // Renter behaviour + /////////////////////////////////////////////////////////////////////////////////////////////// + + /*** renters or OO after selling home decide whether to rent or buy + * N.B. even though the HH may not decide to rent a house of the + * same quality as they would buy, the cash value of the difference in quality + * is assumed to be the difference in rental price between the two qualities. + * @return true if we should buy a house, false if we should rent + */ + public boolean rentOrPurchaseDecision(Household me) { + if(isPropertyInvestor()) return(true); + + double purchasePrice = Math.min(desiredPurchasePrice(me, me.monthlyEmploymentIncome), Model.bank.getMaxMortgage(me, true)); - MortgageAgreement mortgageApproval = Model.bank.requestApproval(me, purchasePrice, + MortgageAgreement mortgageApproval = Model.bank.requestApproval(me, purchasePrice, downPayment(me,purchasePrice), true); - int newHouseQuality = Model.housingMarket.maxQualityGivenPrice(purchasePrice); -// int rentalQuality = Model.rentalMarket.maxQualityGivenPrice(desiredRent(me, me.monthlyEmploymentIncome)); -// if(rentalQuality > newHouseQuality+House.Config.N_QUALITY/8) return(false); // better quality to rent - if(newHouseQuality < 0) return(false); // can't afford a house anyway - double costOfHouse = mortgageApproval.monthlyPayment*config.constants.MONTHS_IN_YEAR - purchasePrice*HPAExpectation(); - double costOfRent = Model.rentalMarket.getAverageSalePrice(newHouseQuality)*config.constants.MONTHS_IN_YEAR; -// System.out.println(FTB_K*(costOfRent + COST_OF_RENTING - costOfHouse)); - //return(rand.nextDouble() < 1.0/(1.0 + Math.exp(-FTB_K*(costOfRent*(1.0+COST_OF_RENTING) - costOfHouse)))); - return(rand.nextDouble() < sigma(config.SENSITIVITY_RENT_OR_PURCHASE*(costOfRent*(1.0 + int newHouseQuality = Model.housingMarket.maxQualityGivenPrice(purchasePrice); +// int rentalQuality = Model.rentalMarket.maxQualityGivenPrice(desiredRent(me, me.monthlyEmploymentIncome)); +// if(rentalQuality > newHouseQuality+House.Config.N_QUALITY/8) return(false); // better quality to rent + if(newHouseQuality < 0) return(false); // can't afford a house anyway + double costOfHouse = mortgageApproval.monthlyPayment*config.constants.MONTHS_IN_YEAR - purchasePrice*HPAExpectation(); + double costOfRent = Model.rentalMarket.getAverageSalePrice(newHouseQuality)*config.constants.MONTHS_IN_YEAR; +// System.out.println(FTB_K*(costOfRent + COST_OF_RENTING - costOfHouse)); + //return(rand.nextDouble() < 1.0/(1.0 + Math.exp(-FTB_K*(costOfRent*(1.0+COST_OF_RENTING) - costOfHouse)))); + return(rand.nextDouble() < sigma(config.SENSITIVITY_RENT_OR_PURCHASE*(costOfRent*(1.0 + config.PSYCHOLOGICAL_COST_OF_RENTING) - costOfHouse))); - /* - - PurchasePlan purchase = findBestPurchase(me); - if(purchase.quality == 0 && purchase.utility <-10.0) return(false); // can't afford to buy anyway - int rentQuality = findBestRentalQuality(me); - - if(me.isFirstTimeBuyer()) { - COST_OF_RENTING = 0.001; - } else { - COST_OF_RENTING = 0.01; - } - - double pBuy = 1.0/(1.0 + Math.exp(-INTENSITY_OF_CHOICE*(COST_OF_RENTING + purchase.utility - utilityOfRenting(me, rentQuality)))); -// System.out.println(utilityOfRenting(me, rentQuality) + " : "+purchase.utility+" : "+INTENSITY_OF_CHOICE*(COST_OF_RENTING+purchase.utility-utilityOfRenting(me, rentQuality))+" ... "+pBuy); - return(rand.nextDouble() < pBuy); - */ - - } + /* + + PurchasePlan purchase = findBestPurchase(me); + if(purchase.quality == 0 && purchase.utility <-10.0) return(false); // can't afford to buy anyway + int rentQuality = findBestRentalQuality(me); + + if(me.isFirstTimeBuyer()) { + COST_OF_RENTING = 0.001; + } else { + COST_OF_RENTING = 0.01; + } + + double pBuy = 1.0/(1.0 + Math.exp(-INTENSITY_OF_CHOICE*(COST_OF_RENTING + purchase.utility - utilityOfRenting(me, rentQuality)))); +// System.out.println(utilityOfRenting(me, rentQuality) + " : "+purchase.utility+" : "+INTENSITY_OF_CHOICE*(COST_OF_RENTING+purchase.utility-utilityOfRenting(me, rentQuality))+" ... "+pBuy); + return(rand.nextDouble() < pBuy); + */ + + } public boolean rentOrPurchaseDecision(Household me, double desiredPurchasePrice) { if(isPropertyInvestor()) return(true); @@ -266,266 +268,266 @@ public boolean rentOrPurchaseDecision(Household me, double desiredPurchasePrice) MortgageAgreement mortgageApproval = Model.bank.requestApproval(me, purchasePrice, downPayment(me,purchasePrice), true); int newHouseQuality = Model.housingMarket.maxQualityGivenPrice(purchasePrice); -// int rentalQuality = Model.rentalMarket.maxQualityGivenPrice(desiredRent(me, me.monthlyEmploymentIncome)); -// if(rentalQuality > newHouseQuality+House.Config.N_QUALITY/8) return(false); // better quality to rent +// int rentalQuality = Model.rentalMarket.maxQualityGivenPrice(desiredRent(me, me.monthlyEmploymentIncome)); +// if(rentalQuality > newHouseQuality+House.Config.N_QUALITY/8) return(false); // better quality to rent if(newHouseQuality < 0) return(false); // can't afford a house anyway double costOfHouse = mortgageApproval.monthlyPayment*config.constants.MONTHS_IN_YEAR - purchasePrice*HPAExpectation(); double costOfRent = Model.rentalMarket.getAverageSalePrice(newHouseQuality)*config.constants.MONTHS_IN_YEAR; -// System.out.println(FTB_K*(costOfRent + COST_OF_RENTING - costOfHouse)); +// System.out.println(FTB_K*(costOfRent + COST_OF_RENTING - costOfHouse)); //return(rand.nextDouble() < 1.0/(1.0 + Math.exp(-FTB_K*(costOfRent*(1.0+COST_OF_RENTING) - costOfHouse)))); return(rand.nextDouble() < sigma(config.SENSITIVITY_RENT_OR_PURCHASE*(costOfRent*(1.0 + config.PSYCHOLOGICAL_COST_OF_RENTING) - costOfHouse))); - /* + /* - PurchasePlan purchase = findBestPurchase(me); - if(purchase.quality == 0 && purchase.utility <-10.0) return(false); // can't afford to buy anyway - int rentQuality = findBestRentalQuality(me); + PurchasePlan purchase = findBestPurchase(me); + if(purchase.quality == 0 && purchase.utility <-10.0) return(false); // can't afford to buy anyway + int rentQuality = findBestRentalQuality(me); - if(me.isFirstTimeBuyer()) { - COST_OF_RENTING = 0.001; - } else { - COST_OF_RENTING = 0.01; - } + if(me.isFirstTimeBuyer()) { + COST_OF_RENTING = 0.001; + } else { + COST_OF_RENTING = 0.01; + } - double pBuy = 1.0/(1.0 + Math.exp(-INTENSITY_OF_CHOICE*(COST_OF_RENTING + purchase.utility - utilityOfRenting(me, rentQuality)))); -// System.out.println(utilityOfRenting(me, rentQuality) + " : "+purchase.utility+" : "+INTENSITY_OF_CHOICE*(COST_OF_RENTING+purchase.utility-utilityOfRenting(me, rentQuality))+" ... "+pBuy); - return(rand.nextDouble() < pBuy); - */ + double pBuy = 1.0/(1.0 + Math.exp(-INTENSITY_OF_CHOICE*(COST_OF_RENTING + purchase.utility - utilityOfRenting(me, rentQuality)))); +// System.out.println(utilityOfRenting(me, rentQuality) + " : "+purchase.utility+" : "+INTENSITY_OF_CHOICE*(COST_OF_RENTING+purchase.utility-utilityOfRenting(me, rentQuality))+" ... "+pBuy); + return(rand.nextDouble() < pBuy); + */ + + } + /******************************************************** + * Decide how much to bid on the rental market + * Source: Zoopla rental prices 2008-2009 (at Bank of England) + ********************************************************/ + public double desiredRent(Household me, double monthlyIncome) { + return(monthlyIncome * config.DESIRED_RENT_INCOME_FRACTION); + +// int quality = findBestRentalQuality(me); +// return(1.01*Model.rentalMarket.getAverageSalePrice(quality)*Math.exp(0.1*rand.nextGaussian())); + + /* + // Zoopla calibrated values + double annualIncome = monthlyIncome*12.0; // this should be net annual income, not gross + double rent; + if(annualIncome < 12000.0) { + rent = 386.0; + } else { + rent = 11.72*Math.pow(annualIncome, 0.372); + } + rent *= Math.exp(rand.nextGaussian()*0.0826); + return(rent); + */ } - /******************************************************** - * Decide how much to bid on the rental market - * Source: Zoopla rental prices 2008-2009 (at Bank of England) - ********************************************************/ - public double desiredRent(Household me, double monthlyIncome) { - return(monthlyIncome * config.DESIRED_RENT_INCOME_FRACTION); - -// int quality = findBestRentalQuality(me); -// return(1.01*Model.rentalMarket.getAverageSalePrice(quality)*Math.exp(0.1*rand.nextGaussian())); - - /* - // Zoopla calibrated values - double annualIncome = monthlyIncome*12.0; // this should be net annual income, not gross - double rent; - if(annualIncome < 12000.0) { - rent = 386.0; - } else { - rent = 11.72*Math.pow(annualIncome, 0.372); - } - rent *= Math.exp(rand.nextGaussian()*0.0826); - return(rent); - */ - } - - /////////////////////////////////////////////////////////////////////////////////////////////// - // Property investor behaviour - /////////////////////////////////////////////////////////////////////////////////////////////// - - /** - * Decide whether to sell an investment property. - * - * @param h The house in question - * @param me The investor - * @return Does an investor decide to sell a buy-to-let property (per month) - */ - public boolean decideToSellInvestmentProperty(House h, Household me) { - if(me.nInvestmentProperties() < 2) return(false); // Always keep at least one property - - double effectiveYield; - - // sell if not selling on rental market at interest coverage ratio of 1.0 (?) - if(!h.isOnRentalMarket()) return(false); // don't sell while occupied by tenant - MortgageAgreement mortgage = me.mortgageFor(h); - //if(mortgage == null) { - if(h.owner!=me){ - System.out.println("Strange: deciding to sell investment property that I don't own"); - return(false); - } - // TODO: add transaction costs to expected capital gain -// double icr = (h.rentalRecord.getPrice()-mortgage.nextPayment())/h.rentalRecord.getPrice(); - double marketPrice = Model.housingMarket.getAverageSalePrice(h.getQuality()); - // TODO: Why to call this "equity"? It is called "downpayment" in the article! + /////////////////////////////////////////////////////////////////////////////////////////////// + // Property investor behaviour + /////////////////////////////////////////////////////////////////////////////////////////////// + + /** + * Decide whether to sell an investment property. + * + * @param h The house in question + * @param me The investor + * @return Does an investor decide to sell a buy-to-let property (per month) + */ + public boolean decideToSellInvestmentProperty(House h, Household me) { + if(me.nInvestmentProperties() < 2) return(false); // Always keep at least one property + + double effectiveYield; + + // sell if not selling on rental market at interest coverage ratio of 1.0 (?) + if(!h.isOnRentalMarket()) return(false); // don't sell while occupied by tenant + MortgageAgreement mortgage = me.mortgageFor(h); + //if(mortgage == null) { + if(h.owner!=me){ + System.out.println("Strange: deciding to sell investment property that I don't own"); + return(false); + } + // TODO: add transaction costs to expected capital gain +// double icr = (h.rentalRecord.getPrice()-mortgage.nextPayment())/h.rentalRecord.getPrice(); + double marketPrice = Model.housingMarket.getAverageSalePrice(h.getQuality()); + // TODO: Why to call this "equity"? It is called "downpayment" in the article! double equity = Math.max(0.01, marketPrice - mortgage.principal); // Dummy security parameter to avoid dividing by zero - double leverage = marketPrice/equity; - double rentalYield = h.rentalRecord.getPrice()*config.constants.MONTHS_IN_YEAR/marketPrice; - double mortgageRate = mortgage.nextPayment()*config.constants.MONTHS_IN_YEAR/equity; - if(config.BTL_YIELD_SCALING) { - effectiveYield = leverage*((1.0 - BtLCapGainCoeff)*rentalYield + double leverage = marketPrice/equity; + double rentalYield = h.rentalRecord.getPrice()*config.constants.MONTHS_IN_YEAR/marketPrice; + double mortgageRate = mortgage.nextPayment()*config.constants.MONTHS_IN_YEAR/equity; + if(config.BTL_YIELD_SCALING) { + effectiveYield = leverage*((1.0 - BtLCapGainCoeff)*rentalYield + BtLCapGainCoeff*(Model.rentalMarket.longTermAverageGrossYield + HPAExpectation())) - mortgageRate; - } else { - effectiveYield = leverage*(rentalYield + BtLCapGainCoeff*HPAExpectation()) - mortgageRate; - } - double pKeep = Math.pow(sigma(config.BTL_CHOICE_INTENSITY*effectiveYield), + } else { + effectiveYield = leverage*(rentalYield + BtLCapGainCoeff*HPAExpectation()) - mortgageRate; + } + double pKeep = Math.pow(sigma(config.BTL_CHOICE_INTENSITY*effectiveYield), 1.0/config.constants.MONTHS_IN_YEAR); - return(rand.nextDouble() < (1.0 - pKeep)); - } - - - /** - * How much rent does an investor decide to charge on a buy-to-let house? - * @param rbar average rent for house of this quality - * @param d average days on market - * @param h house being offered for rent - */ - public double buyToLetRent(double rbar, double d, House h) { - // TODO: What? Where does this equation come from? - final double beta = config.RENT_MARKUP/Math.log(config.RENT_EQ_MONTHS_ON_MARKET); // Weight of days-on-market effect - - double exponent = config.RENT_MARKUP + Math.log(rbar) + return(rand.nextDouble() < (1.0 - pKeep)); + } + + + /** + * How much rent does an investor decide to charge on a buy-to-let house? + * @param rbar average rent for house of this quality + * @param d average days on market + * @param h house being offered for rent + */ + public double buyToLetRent(double rbar, double d, House h) { + // TODO: What? Where does this equation come from? + final double beta = config.RENT_MARKUP/Math.log(config.RENT_EQ_MONTHS_ON_MARKET); // Weight of days-on-market effect + + double exponent = config.RENT_MARKUP + Math.log(rbar) - beta*Math.log((d + 1.0)/(config.constants.DAYS_IN_MONTH + 1)) + config.RENT_EPSILON*rand.nextGaussian(); - double result = Math.exp(exponent); + double result = Math.exp(exponent); // TODO: The following contains a fudge (config.RENT_MAX_AMORTIZATION_PERIOD) to keep rental yield up - double minAcceptable = Model.housingMarket.getAverageSalePrice(h.getQuality()) + double minAcceptable = Model.housingMarket.getAverageSalePrice(h.getQuality()) /(config.RENT_MAX_AMORTIZATION_PERIOD*config.constants.MONTHS_IN_YEAR); - if(result < minAcceptable) result = minAcceptable; - return(result); + if(result < minAcceptable) result = minAcceptable; + return(result); - } + } - /** - * Update the demanded rent for a property - * - * @param sale the HouseSaleRecord of the property for rent - * @return the new rent + /** + * Update the demanded rent for a property + * + * @param sale the HouseSaleRecord of the property for rent + * @return the new rent */ - public double rethinkBuyToLetRent(HouseSaleRecord sale) { - return((1.0 - config.RENT_REDUCTION)*sale.getPrice()); -// if(rand.nextDouble() > 0.944) { -// double logReduction = Math.min(4.6, 1.603+(rand.nextGaussian()*0.6173)); -// return(sale.getPrice() * (1.0-0.01*Math.exp(logReduction))); -// } -// return(sale.getPrice()); - } - - /*** - * Monthly opportunity of buying a new BTL property. - * - * Investor households with no investment properties always attempt to buy one. Households with at least - * one investment property will calculate the expected yield of a new property based on two contributions: - * capital gain and rental yield (with their corresponding weights which depend on the type of investor). - * - * @param me household - * @return true if decision to buy - */ - public boolean decideToBuyBuyToLet(Household me) { - if(me.nInvestmentProperties() < 1) { // If I don't have any BTL properties, I always decide to buy one!! - return(true); - } - double effectiveYield; - - if(!isPropertyInvestor()) return false; - // TODO: This mechanism and its parameter are not declared in the article! Any reference for the value of the parameter? - if(me.getBankBalance() < desiredBankBalance(me)*config.BTL_CHOICE_MIN_BANK_BALANCE) { - return(false); - } - // --- calculate expected yield on zero quality house - double maxPrice = Model.bank.getMaxMortgage(me, false); - if(maxPrice < Model.housingMarket.getAverageSalePrice(0)) return false; - - MortgageAgreement m = Model.bank.requestApproval(me, maxPrice, 0.0, false); // maximise leverage with min downpayment - - double leverage = m.purchasePrice/m.downPayment; - double rentalYield = Model.rentalMarket.averageSoldGrossYield; - double mortgageRate = m.monthlyPayment*config.constants.MONTHS_IN_YEAR/m.downPayment; - if(config.BTL_YIELD_SCALING) { - effectiveYield = leverage*((1.0 - BtLCapGainCoeff)*rentalYield + public double rethinkBuyToLetRent(HouseSaleRecord sale) { + return((1.0 - config.RENT_REDUCTION)*sale.getPrice()); +// if(rand.nextDouble() > 0.944) { +// double logReduction = Math.min(4.6, 1.603+(rand.nextGaussian()*0.6173)); +// return(sale.getPrice() * (1.0-0.01*Math.exp(logReduction))); +// } +// return(sale.getPrice()); + } + + /*** + * Monthly opportunity of buying a new BTL property. + * + * Investor households with no investment properties always attempt to buy one. Households with at least + * one investment property will calculate the expected yield of a new property based on two contributions: + * capital gain and rental yield (with their corresponding weights which depend on the type of investor). + * + * @param me household + * @return true if decision to buy + */ + public boolean decideToBuyBuyToLet(Household me) { + if(me.nInvestmentProperties() < 1) { // If I don't have any BTL properties, I always decide to buy one!! + return(true); + } + double effectiveYield; + + if(!isPropertyInvestor()) return false; + // TODO: This mechanism and its parameter are not declared in the article! Any reference for the value of the parameter? + if(me.getBankBalance() < desiredBankBalance(me)*config.BTL_CHOICE_MIN_BANK_BALANCE) { + return(false); + } + // --- calculate expected yield on zero quality house + double maxPrice = Model.bank.getMaxMortgage(me, false); + if(maxPrice < Model.housingMarket.getAverageSalePrice(0)) return false; + + MortgageAgreement m = Model.bank.requestApproval(me, maxPrice, 0.0, false); // maximise leverage with min downpayment + + double leverage = m.purchasePrice/m.downPayment; + double rentalYield = Model.rentalMarket.averageSoldGrossYield; + double mortgageRate = m.monthlyPayment*config.constants.MONTHS_IN_YEAR/m.downPayment; + if(config.BTL_YIELD_SCALING) { + effectiveYield = leverage*((1.0 - BtLCapGainCoeff)*rentalYield + BtLCapGainCoeff*(Model.rentalMarket.longTermAverageGrossYield + HPAExpectation())) - mortgageRate; - } else { - effectiveYield = leverage*(rentalYield + BtLCapGainCoeff*HPAExpectation()) - mortgageRate; - } - //double pDontBuy = Math.pow(1.0/(1.0 + Math.exp(INTENSITY*effectiveYield)),AGGREGATE_RATE); - //return(rand.nextDouble() < (1.0-pDontBuy)); - return (rand.nextDouble() < Math.pow(sigma(config.BTL_CHOICE_INTENSITY*effectiveYield), + } else { + effectiveYield = leverage*(rentalYield + BtLCapGainCoeff*HPAExpectation()) - mortgageRate; + } + //double pDontBuy = Math.pow(1.0/(1.0 + Math.exp(INTENSITY*effectiveYield)),AGGREGATE_RATE); + //return(rand.nextDouble() < (1.0-pDontBuy)); + return (rand.nextDouble() < Math.pow(sigma(config.BTL_CHOICE_INTENSITY*effectiveYield), 1.0/config.constants.MONTHS_IN_YEAR)); - } - - public double btlPurchaseBid(Household me) { - return(Math.min(Model.bank.getMaxMortgage(me, false), + } + + public double btlPurchaseBid(Household me) { + return(Math.min(Model.bank.getMaxMortgage(me, false), 1.1*Model.housingMarket.getAverageSalePrice(config.N_QUALITY-1))); - } + } + + public boolean isPropertyInvestor() { + return(BTLInvestor); + } - public boolean isPropertyInvestor() { - return(BTLInvestor); - } + public boolean setPropertyInvestor(boolean isInvestor) { + return(BTLInvestor = isInvestor); + } - public boolean setPropertyInvestor(boolean isInvestor) { - return(BTLInvestor = isInvestor); - } +// public int nDesiredBTLProperties() { +// return desiredBTLProperties; +// } + + /*** @returns expectation value of HPI in one year's time divided by today's HPI*/ + public double HPAExpectation() { + // Dampening or multiplier factor, depending on its value being <1 or >1, for the current trend of HPA when + // computing expectations as in HPI(t+DT) = HPI(t) + FACTOR*DT*dHPI/dt (double) + return(Model.housingMarket.housePriceAppreciation(config.HPA_YEARS_TO_CHECK)*config.HPA_EXPECTATION_FACTOR); + } + + /* + public double utilityOfRenting(Household me, int q) { + double leftForConsumption = 1.0 - Model.rentalMarket.getAverageSalePrice(q)/me.monthlyEmploymentIncome; + return(utilityOfHome(me,q) + qualityOfLiving(leftForConsumption)); + } -// public int nDesiredBTLProperties() { -// return desiredBTLProperties; -// } - /*** @returns expectation value of HPI in one year's time divided by today's HPI*/ - public double HPAExpectation() { - // Dampening or multiplier factor, depending on its value being <1 or >1, for the current trend of HPA when - // computing expectations as in HPI(t+DT) = HPI(t) + FACTOR*DT*dHPI/dt (double) - return(Model.housingMarket.housePriceAppreciation(config.HPA_YEARS_TO_CHECK)*config.HPA_EXPECTATION_FACTOR); + public int findBestRentalQuality(Household me) { + int bestQ = 0; + double bestU = utilityOfRenting(me,0); + double u; + for(int q = 0; q bestU) { + bestU = u; + bestQ = q; + } + } + return(bestQ); + } + + public double utilityOfPurchase(Household me, int q, MortgageAgreement mortgage) { + double price = Model.housingMarket.getAverageSalePrice(q); + if(price > mortgage.purchasePrice) return(-10.0); + double principal = price - mortgage.downPayment; + double leftForConsumption = 1.0 - (principal*Model.bank.monthlyPaymentFactor(true) - price*HPAExpectation()/12.0)/me.monthlyEmploymentIncome; + return(utilityOfHome(me,q) + qualityOfLiving(leftForConsumption)); + + } + + public PurchasePlan findBestPurchase(Household me) { + PurchasePlan result = new PurchasePlan(); + MortgageAgreement maxMortgage = Model.bank.requestApproval(me, Model.bank.getMaxMortgage(me, true), downPayment(me) + me.getHomeEquity(), true); + + result.quality = 0; + result.utility = utilityOfPurchase(me,0,maxMortgage); + double u; + for(int q = 1; q result.utility) { + result.utility = u; + result.quality = q; + } + } + return(result); + + } + + public double utilityOfHome(Household me, int q) { + final double rmo = 0.3; // reference housing spend + final double k = 0.5; // flexibility of spend on hpi change + final double c = k*rmo/(1.0+rmo*(k-1.0)); // 0.0968 + final double lambda = (rmo-c)/(1-rmo); // 0.290 + double Pref = (0.05/12.0)*Model.housingMarket.referencePrice(q)/me.monthlyEmploymentIncome; + if(Pref < c) return(-10.0); + return(lambda*Math.log(Pref-c)); + } + + public double qualityOfLiving(double consumptionFraction) { + return(Math.log(consumptionFraction)); } - - /* - public double utilityOfRenting(Household me, int q) { - double leftForConsumption = 1.0 - Model.rentalMarket.getAverageSalePrice(q)/me.monthlyEmploymentIncome; - return(utilityOfHome(me,q) + qualityOfLiving(leftForConsumption)); - } - - - public int findBestRentalQuality(Household me) { - int bestQ = 0; - double bestU = utilityOfRenting(me,0); - double u; - for(int q = 0; q bestU) { - bestU = u; - bestQ = q; - } - } - return(bestQ); - } - - public double utilityOfPurchase(Household me, int q, MortgageAgreement mortgage) { - double price = Model.housingMarket.getAverageSalePrice(q); - if(price > mortgage.purchasePrice) return(-10.0); - double principal = price - mortgage.downPayment; - double leftForConsumption = 1.0 - (principal*Model.bank.monthlyPaymentFactor(true) - price*HPAExpectation()/12.0)/me.monthlyEmploymentIncome; - return(utilityOfHome(me,q) + qualityOfLiving(leftForConsumption)); - - } - - public PurchasePlan findBestPurchase(Household me) { - PurchasePlan result = new PurchasePlan(); - MortgageAgreement maxMortgage = Model.bank.requestApproval(me, Model.bank.getMaxMortgage(me, true), downPayment(me) + me.getHomeEquity(), true); - - result.quality = 0; - result.utility = utilityOfPurchase(me,0,maxMortgage); - double u; - for(int q = 1; q result.utility) { - result.utility = u; - result.quality = q; - } - } - return(result); - - } - - public double utilityOfHome(Household me, int q) { - final double rmo = 0.3; // reference housing spend - final double k = 0.5; // flexibility of spend on hpi change - final double c = k*rmo/(1.0+rmo*(k-1.0)); // 0.0968 - final double lambda = (rmo-c)/(1-rmo); // 0.290 - double Pref = (0.05/12.0)*Model.housingMarket.referencePrice(q)/me.monthlyEmploymentIncome; - if(Pref < c) return(-10.0); - return(lambda*Math.log(Pref-c)); - } - - public double qualityOfLiving(double consumptionFraction) { - return(Math.log(consumptionFraction)); - } - */ + */ } From 4991e7f926e347f7ebce985a06b7dfd59d86018a Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Thu, 22 Jun 2017 21:39:25 +0300 Subject: [PATCH 5/7] Added Bank config object. --- src/main/java/configuration/BankConfig.java | 34 ++ src/main/java/configuration/ModelConfig.java | 17 +- src/main/java/housing/Bank.java | 574 ++++++++++--------- src/main/resources/model.conf | 92 +-- 4 files changed, 390 insertions(+), 327 deletions(-) create mode 100644 src/main/java/configuration/BankConfig.java diff --git a/src/main/java/configuration/BankConfig.java b/src/main/java/configuration/BankConfig.java new file mode 100644 index 00000000..b2c730c6 --- /dev/null +++ b/src/main/java/configuration/BankConfig.java @@ -0,0 +1,34 @@ +package configuration; + + +import com.typesafe.config.Config; + + +public class BankConfig { + + private int mortgageDurationYears; + private double initialBaseRate; + private double creditSupplyTarget; + + BankConfig(Config config) { + mortgageDurationYears = config.getInt("mortgage-duration-years"); + initialBaseRate = config.getDouble("initial=base-rate"); + creditSupplyTarget = config.getDouble("credit-supply-target"); + } + + /** Mortgage duration in years. */ + public int getMortgageDurationYears() { + return mortgageDurationYears; + } + + /** Bank initial base-rate, which remains currently unchanged. */ + public double getInitialBaseRate() { + return initialBaseRate; + } + + /** Bank's target supply of credit per household per month. */ + public double getCreditSupplyTarget() { + return creditSupplyTarget; + } + +} diff --git a/src/main/java/configuration/ModelConfig.java b/src/main/java/configuration/ModelConfig.java index d14ef78d..8e541374 100644 --- a/src/main/java/configuration/ModelConfig.java +++ b/src/main/java/configuration/ModelConfig.java @@ -3,6 +3,7 @@ import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; +import housing.Bank; /** Class encapsulating all of the model parameters. @@ -15,13 +16,15 @@ public class ModelConfig { private GovernmentConfig governmentConfig; private CentralBankConfig centralBankConfig; private HouseholdBehaviorConfig householdBehaviorConfig; + private BankConfig bankConfig; public ModelConfig(String filename) { Config config = ConfigFactory.load(filename); housingMarketConfig = new HousingMarketConfig(config.getConfig("simulation.housing-market")); governmentConfig = new GovernmentConfig(config.getConfig("simulation.government")); centralBankConfig = new CentralBankConfig(config.getConfig("simulation.central-bank")); - householdConfig = new HouseholdBehaviorConfig(config.getConfig("simulation.households")) + householdBehaviorConfig = new HouseholdBehaviorConfig(config.getConfig("simulation.households")); + bankConfig = new BankConfig(config.getConfig("simulation.bank")); } /** Housing Market Configuration @@ -48,4 +51,16 @@ public CentralBankConfig getCentralBankConfig() { return centralBankConfig; } + /** Household Behavior Configuration. + * + * @return A `HouseholdBehaviorConfig` object encapsulating the household behavior parameters. + */ + public HouseholdBehaviorConfig getHouseholdBehaviorConfig() { return householdBehaviorConfig; } + + /** Bank Configuration. + * + * @return A `BankConfig` object encapsulating the bank parameters. + */ + public BankConfig getBankConfig() { return bankConfig; } + } diff --git a/src/main/java/housing/Bank.java b/src/main/java/housing/Bank.java index f285e687..7766708d 100644 --- a/src/main/java/housing/Bank.java +++ b/src/main/java/housing/Bank.java @@ -1,5 +1,7 @@ package housing; +import configuration.BankConfig; + import java.io.Serializable; import java.util.HashSet; @@ -13,310 +15,312 @@ * *************************************************/ public class Bank implements Serializable { - private static final long serialVersionUID = -8301089924358306706L; + private static final long serialVersionUID = -8301089924358306706L; -// public double INTEREST_MARGIN = 0.03; // Interest rate rise in affordability stress test (http://www.bankofengland.co.uk/financialstability/Pages/fpc/intereststress.aspx) - - /******************************** - * Constructor. This just sets up a few - * pre-computed values. - ********************************/ - public Bank() { - mortgages = new HashSet<>(); - init(); - } - - public void init() { - mortgages.clear(); - baseRate = config.BANK_INITIAL_BASE_RATE; - // TODO: Is this (dDemand_dInterest) a parameter? Shouldn't it depend somehow on other variables of the model? - dDemand_dInterest = 10*1e10; +// public double INTEREST_MARGIN = 0.03; // Interest rate rise in affordability stress test (http://www.bankofengland.co.uk/financialstability/Pages/fpc/intereststress.aspx) + private BankConfig config; + + /******************************** + * Constructor. This just sets up a few + * pre-computed values. + ********************************/ + public Bank(BankConfig config) { + this.config = config; + mortgages = new HashSet<>(); + init(); + } + + public void init() { + mortgages.clear(); + baseRate = config.getInitialBaseRate(); + // TODO: Is this (dDemand_dInterest) a parameter? Shouldn't it depend somehow on other variables of the model? + dDemand_dInterest = 10*1e10; // TODO: Is this (0.02) a parameter? Does it affect results in any significant way or is it just a dummy initialisation? setMortgageInterestRate(0.02); - resetMonthlyCounters(); + resetMonthlyCounters(); + } + + /*** + * This is where the bank gets to do its monthly calculations + */ + public void step() { + supplyTarget = config.getCreditSupplyTarget() * Model.households.size(); + setMortgageInterestRate(recalcInterestRate()); + resetMonthlyCounters(); + } + + /*** + * Resets all the various monthly diagnostic measures ready for the next month + */ + public void resetMonthlyCounters() { + lastMonthsSupplyVal = supplyVal; + demand = 0.0; + supplyVal = 0.0; + nLoans = 0; + nOverLTICapLoans = 0; + nOverLTVCapLoans = 0; + } + + /*** + * Calculates the next months mortgage interest based on this months + * rate and the resulting demand. + * + * Assumes a linear relationship between interest rate and demand, + * and aims to halve the difference between current demand + * and target supply + */ + public double recalcInterestRate() { + double rate = getMortgageInterestRate() + 0.5*(supplyVal - supplyTarget)/dDemand_dInterest; +// System.out.println(supplyVal/Model.households.size()); + if(rate < baseRate) rate = baseRate; + return(rate); } - - /*** - * This is where the bank gets to do its monthly calculations - */ - public void step() { - supplyTarget = config.BANK_CREDIT_SUPPLY_TARGET*Model.households.size(); - setMortgageInterestRate(recalcInterestRate()); - resetMonthlyCounters(); - } - - /*** - * Resets all the various monthly diagnostic measures ready for the next month - */ - public void resetMonthlyCounters() { - lastMonthsSupplyVal = supplyVal; - demand = 0.0; - supplyVal = 0.0; - nLoans = 0; - nOverLTICapLoans = 0; - nOverLTVCapLoans = 0; - } - - /*** - * Calculates the next months mortgage interest based on this months - * rate and the resulting demand. - * - * Assumes a linear relationship between interest rate and demand, - * and aims to halve the difference between current demand - * and target supply - */ - public double recalcInterestRate() { - double rate = getMortgageInterestRate() + 0.5*(supplyVal - supplyTarget)/dDemand_dInterest; -// System.out.println(supplyVal/Model.households.size()); - if(rate < baseRate) rate = baseRate; - return(rate); - } - - /****************************** - * Get the interest rate on mortgages. - * @return The interest rate on mortgages. - *****************************/ - public double getMortgageInterestRate() { - return(baseRate + interestSpread); - } - + + /****************************** + * Get the interest rate on mortgages. + * @return The interest rate on mortgages. + *****************************/ + public double getMortgageInterestRate() { + return(baseRate + interestSpread); + } + - /****************************** - * Get the interest rate on mortgages. - * @return The interest rate on mortgages. - *****************************/ - public void setMortgageInterestRate(double rate) { - interestSpread = rate - baseRate; - recalculateK(); - } - - public double getBaseRate() { - return baseRate; - } + /****************************** + * Get the interest rate on mortgages. + * @return The interest rate on mortgages. + *****************************/ + public void setMortgageInterestRate(double rate) { + interestSpread = rate - baseRate; + recalculateK(); + } + + public double getBaseRate() { + return baseRate; + } - public void setBaseRate(double baseRate) { - this.baseRate = baseRate; - recalculateK(); - } + public void setBaseRate(double baseRate) { + this.baseRate = baseRate; + recalculateK(); + } - protected void recalculateK() { - double r = getMortgageInterestRate()/config.constants.MONTHS_IN_YEAR; - k = r/(1.0 - Math.pow(1.0+r, -config.derivedParams.N_PAYMENTS)); - } + protected void recalculateK() { + double r = getMortgageInterestRate()/config.constants.MONTHS_IN_YEAR; + k = r/(1.0 - Math.pow(1.0+r, -config.derivedParams.N_PAYMENTS)); + } - /******************************* - * Get the monthly payment on a mortgage as a fraction of the mortgage principle. - * @return The monthly payment fraction. - *******************************/ - public double monthlyPaymentFactor(boolean isHome) { - if(isHome) { - return(k); // Pay off in N_PAYMENTS - } else { - return(getMortgageInterestRate()/config.constants.MONTHS_IN_YEAR); // interest only - } - } + /******************************* + * Get the monthly payment on a mortgage as a fraction of the mortgage principle. + * @return The monthly payment fraction. + *******************************/ + public double monthlyPaymentFactor(boolean isHome) { + if(isHome) { + return(k); // Pay off in N_PAYMENTS + } else { + return(getMortgageInterestRate()/config.constants.MONTHS_IN_YEAR); // interest only + } + } - /* - public double stressedMonthlyPaymentFactor(boolean isHome) { - if(isHome) { - return(k); // Fixed rate for OO - } else { - return((getMortgageInterestRate()+INTEREST_MARGIN)/12.0); // interest only - } - } + /* + public double stressedMonthlyPaymentFactor(boolean isHome) { + if(isHome) { + return(k); // Fixed rate for OO + } else { + return((getMortgageInterestRate()+INTEREST_MARGIN)/12.0); // interest only + } + } */ - /***************************** - * Use this to arrange a Mortgage and get a MortgageApproval object. - * - * @param h The household that is requesting the mortgage. - * @param housePrice The price of the house that 'h' wants to buy - * @param isHome true if 'h' plans to live in the house. - * @return The MortgageApproval object, or NULL if the mortgage is declined - ****************************/ - public MortgageAgreement requestLoan(Household h, double housePrice, double desiredDownPayment, boolean isHome, House house) { - MortgageAgreement approval = requestApproval(h, housePrice, desiredDownPayment, isHome); - if(approval == null) return(null); - // --- if all's well, go ahead and arrange mortgage - supplyVal += approval.principal; - if(approval.principal > 0.0) { - mortgages.add(approval); - Model.collectors.creditSupply.recordLoan(h, approval, house); - ++nLoans; - if(isHome) { - if(approval.principal/h.annualEmploymentIncome() > Model.centralBank.loanToIncomeRegulation(h.isFirstTimeBuyer())) { - ++nOverLTICapLoans; - } - if(approval.principal/(approval.principal + approval.downPayment) > Model.centralBank.loanToValueRegulation(h.isFirstTimeBuyer(),isHome)) { - ++nOverLTVCapLoans; - } - } - } - return(approval); - } - - - public void endMortgageContract(MortgageAgreement mortgage) { - mortgages.remove(mortgage); - } + /***************************** + * Use this to arrange a Mortgage and get a MortgageApproval object. + * + * @param h The household that is requesting the mortgage. + * @param housePrice The price of the house that 'h' wants to buy + * @param isHome true if 'h' plans to live in the house. + * @return The MortgageApproval object, or NULL if the mortgage is declined + ****************************/ + public MortgageAgreement requestLoan(Household h, double housePrice, double desiredDownPayment, boolean isHome, House house) { + MortgageAgreement approval = requestApproval(h, housePrice, desiredDownPayment, isHome); + if(approval == null) return(null); + // --- if all's well, go ahead and arrange mortgage + supplyVal += approval.principal; + if(approval.principal > 0.0) { + mortgages.add(approval); + Model.collectors.creditSupply.recordLoan(h, approval, house); + ++nLoans; + if(isHome) { + if(approval.principal/h.annualEmploymentIncome() > Model.centralBank.loanToIncomeRegulation(h.isFirstTimeBuyer())) { + ++nOverLTICapLoans; + } + if(approval.principal/(approval.principal + approval.downPayment) > Model.centralBank.loanToValueRegulation(h.isFirstTimeBuyer(),isHome)) { + ++nOverLTVCapLoans; + } + } + } + return(approval); + } + + + public void endMortgageContract(MortgageAgreement mortgage) { + mortgages.remove(mortgage); + } - /******** - * Use this to request a mortgage approval but not actually sign a mortgage contract. - * This is useful if you want to inspect the details of the mortgage contract before - * deciding whether to actually go ahead and sign. - * - * @param h The household that is requesting the approval. - * @param housePrice The price of the house that 'h' wants to buy - * @param isHome does 'h' plan to live in the house? - * @return A MortgageApproval object, or NULL if the mortgage is declined - */ - public MortgageAgreement requestApproval(Household h, double housePrice, double desiredDownPayment, boolean isHome) { - MortgageAgreement approval = new MortgageAgreement(h, !isHome); - double r = getMortgageInterestRate()/config.constants.MONTHS_IN_YEAR; // monthly interest rate - double lti_principal, affordable_principal, icr_principal; - double liquidWealth = h.getBankBalance(); - - if(isHome) liquidWealth += h.getHomeEquity(); + /******** + * Use this to request a mortgage approval but not actually sign a mortgage contract. + * This is useful if you want to inspect the details of the mortgage contract before + * deciding whether to actually go ahead and sign. + * + * @param h The household that is requesting the approval. + * @param housePrice The price of the house that 'h' wants to buy + * @param isHome does 'h' plan to live in the house? + * @return A MortgageApproval object, or NULL if the mortgage is declined + */ + public MortgageAgreement requestApproval(Household h, double housePrice, double desiredDownPayment, boolean isHome) { + MortgageAgreement approval = new MortgageAgreement(h, !isHome); + double r = getMortgageInterestRate()/config.constants.MONTHS_IN_YEAR; // monthly interest rate + double lti_principal, affordable_principal, icr_principal; + double liquidWealth = h.getBankBalance(); + + if(isHome) liquidWealth += h.getHomeEquity(); - // --- LTV constraint - approval.principal = housePrice*loanToValue(h.isFirstTimeBuyer(), isHome); + // --- LTV constraint + approval.principal = housePrice*loanToValue(h.isFirstTimeBuyer(), isHome); - if(isHome) { - // --- affordability constraint TODO: affordability for BtL? - affordable_principal = Math.max(0.0,config.CENTRAL_BANK_AFFORDABILITY_COEFF*h.getMonthlyPostTaxIncome())/monthlyPaymentFactor(isHome); - approval.principal = Math.min(approval.principal, affordable_principal); + if(isHome) { + // --- affordability constraint TODO: affordability for BtL? + affordable_principal = Math.max(0.0,config.CENTRAL_BANK_AFFORDABILITY_COEFF*h.getMonthlyPostTaxIncome())/monthlyPaymentFactor(isHome); + approval.principal = Math.min(approval.principal, affordable_principal); - // --- lti constraint - lti_principal = h.annualEmploymentIncome() * loanToIncome(h.isFirstTimeBuyer()); - approval.principal = Math.min(approval.principal, lti_principal); - } else { - // --- BtL ICR constraint - icr_principal = Model.rentalMarket.averageSoldGrossYield*housePrice/(interestCoverageRatio()*config.CENTRAL_BANK_BTL_STRESSED_INTEREST); - approval.principal = Math.min(approval.principal, icr_principal); - // System.out.println(icr_principal/housePrice); - } - - approval.downPayment = housePrice - approval.principal; - - if(liquidWealth < approval.downPayment) { - System.out.println("Failed down-payment constraint: bank balance = "+liquidWealth+" Downpayment = "+approval.downPayment); - System.out.println("isHome = "+isHome+" isFirstTimeBuyer = "+h.isFirstTimeBuyer()); - approval.downPayment = liquidWealth; -// return(null); - } - // --- allow larger downpayments - if(desiredDownPayment < 0.0) desiredDownPayment = 0.0; - if(desiredDownPayment > liquidWealth) desiredDownPayment = liquidWealth; - if(desiredDownPayment > housePrice) desiredDownPayment = housePrice; - if(desiredDownPayment > approval.downPayment) { - approval.downPayment = desiredDownPayment; - approval.principal = housePrice - desiredDownPayment; - } - - approval.monthlyPayment = approval.principal*monthlyPaymentFactor(isHome); - approval.nPayments = config.derivedParams.N_PAYMENTS; - approval.monthlyInterestRate = r; -// approval.isFirstTimeBuyer = h.isFirstTimeBuyer(); - approval.purchasePrice = approval.principal + approval.downPayment; - return(approval); - } + // --- lti constraint + lti_principal = h.annualEmploymentIncome() * loanToIncome(h.isFirstTimeBuyer()); + approval.principal = Math.min(approval.principal, lti_principal); + } else { + // --- BtL ICR constraint + icr_principal = Model.rentalMarket.averageSoldGrossYield*housePrice/(interestCoverageRatio()*config.CENTRAL_BANK_BTL_STRESSED_INTEREST); + approval.principal = Math.min(approval.principal, icr_principal); + // System.out.println(icr_principal/housePrice); + } + + approval.downPayment = housePrice - approval.principal; + + if(liquidWealth < approval.downPayment) { + System.out.println("Failed down-payment constraint: bank balance = "+liquidWealth+" Downpayment = "+approval.downPayment); + System.out.println("isHome = "+isHome+" isFirstTimeBuyer = "+h.isFirstTimeBuyer()); + approval.downPayment = liquidWealth; +// return(null); + } + // --- allow larger downpayments + if(desiredDownPayment < 0.0) desiredDownPayment = 0.0; + if(desiredDownPayment > liquidWealth) desiredDownPayment = liquidWealth; + if(desiredDownPayment > housePrice) desiredDownPayment = housePrice; + if(desiredDownPayment > approval.downPayment) { + approval.downPayment = desiredDownPayment; + approval.principal = housePrice - desiredDownPayment; + } + + approval.monthlyPayment = approval.principal*monthlyPaymentFactor(isHome); + approval.nPayments = config.derivedParams.N_PAYMENTS; + approval.monthlyInterestRate = r; +// approval.isFirstTimeBuyer = h.isFirstTimeBuyer(); + approval.purchasePrice = approval.principal + approval.downPayment; + return(approval); + } - /***************************************** - * Find the maximum mortgage that this mortgage-lender will approve - * to a household. - * - * @param h household who is applying for the mortgage - * @param isHome true if 'h' plans to live in the house - * @return The maximum value of house that this mortgage-lender is willing - * to approve a mortgage for. - ****************************************/ - public double getMaxMortgage(Household h, boolean isHome) { - double max; - double pdi_max; // disposable income constraint - double lti_max; // loan to income constraint - double icr_max; // interest rate coverage - double liquidWealth = h.getBankBalance(); + /***************************************** + * Find the maximum mortgage that this mortgage-lender will approve + * to a household. + * + * @param h household who is applying for the mortgage + * @param isHome true if 'h' plans to live in the house + * @return The maximum value of house that this mortgage-lender is willing + * to approve a mortgage for. + ****************************************/ + public double getMaxMortgage(Household h, boolean isHome) { + double max; + double pdi_max; // disposable income constraint + double lti_max; // loan to income constraint + double icr_max; // interest rate coverage + double liquidWealth = h.getBankBalance(); - if(isHome) { - liquidWealth += h.getHomeEquity(); // assume h will sell current home - } - - max = liquidWealth/(1.0 - loanToValue(h.isFirstTimeBuyer(), isHome)); // LTV constraint + if(isHome) { + liquidWealth += h.getHomeEquity(); // assume h will sell current home + } + + max = liquidWealth/(1.0 - loanToValue(h.isFirstTimeBuyer(), isHome)); // LTV constraint - if(isHome) { // no LTI for BtL investors -// lti_max = h.getMonthlyPreTaxIncome()*12.0* loanToIncome(h.isFirstTimeBuyer())/loanToValue(h.isFirstTimeBuyer(),isHome); - pdi_max = liquidWealth + Math.max(0.0,config.CENTRAL_BANK_AFFORDABILITY_COEFF*h.getMonthlyPostTaxIncome())/monthlyPaymentFactor(isHome); - max = Math.min(max, pdi_max); - lti_max = h.annualEmploymentIncome()* loanToIncome(h.isFirstTimeBuyer()) + liquidWealth; - max = Math.min(max, lti_max); - } else { - icr_max = Model.rentalMarket.averageSoldGrossYield/(interestCoverageRatio()*config.CENTRAL_BANK_BTL_STRESSED_INTEREST); - if(icr_max < 1.0) { - icr_max = liquidWealth/(1.0 - icr_max); - max = Math.min(max, icr_max); - } - } - - max = Math.floor(max*100.0)/100.0; // round down to nearest penny - return(max); - } + if(isHome) { // no LTI for BtL investors +// lti_max = h.getMonthlyPreTaxIncome()*12.0* loanToIncome(h.isFirstTimeBuyer())/loanToValue(h.isFirstTimeBuyer(),isHome); + pdi_max = liquidWealth + Math.max(0.0,config.CENTRAL_BANK_AFFORDABILITY_COEFF*h.getMonthlyPostTaxIncome())/monthlyPaymentFactor(isHome); + max = Math.min(max, pdi_max); + lti_max = h.annualEmploymentIncome()* loanToIncome(h.isFirstTimeBuyer()) + liquidWealth; + max = Math.min(max, lti_max); + } else { + icr_max = Model.rentalMarket.averageSoldGrossYield/(interestCoverageRatio()*config.CENTRAL_BANK_BTL_STRESSED_INTEREST); + if(icr_max < 1.0) { + icr_max = liquidWealth/(1.0 - icr_max); + max = Math.min(max, icr_max); + } + } + + max = Math.floor(max*100.0)/100.0; // round down to nearest penny + return(max); + } - /********************************************** - * Get the Loan-To-Value ratio applicable to a given household. - * - * @param firstTimeBuyer true if the household is a first time buyer - * @param isHome true if the household plans to live in the house - * @return The loan-to-value ratio applicable to the given household. - *********************************************/ - public double loanToValue(boolean firstTimeBuyer, boolean isHome) { - double limit; - if(isHome) { - limit = config.CENTRAL_BANK_MAX_OO_LTV; - } else { - limit = config.CENTRAL_BANK_MAX_BTL_LTV; - } - if((nOverLTVCapLoans+1.0)/(nLoans + 1.0) > Model.centralBank.proportionOverLTVLimit) { - limit = Math.min(limit, Model.centralBank.loanToValueRegulation(firstTimeBuyer, isHome)); - } - return(limit); - } + /********************************************** + * Get the Loan-To-Value ratio applicable to a given household. + * + * @param firstTimeBuyer true if the household is a first time buyer + * @param isHome true if the household plans to live in the house + * @return The loan-to-value ratio applicable to the given household. + *********************************************/ + public double loanToValue(boolean firstTimeBuyer, boolean isHome) { + double limit; + if(isHome) { + limit = config.CENTRAL_BANK_MAX_OO_LTV; + } else { + limit = config.CENTRAL_BANK_MAX_BTL_LTV; + } + if((nOverLTVCapLoans+1.0)/(nLoans + 1.0) > Model.centralBank.proportionOverLTVLimit) { + limit = Math.min(limit, Model.centralBank.loanToValueRegulation(firstTimeBuyer, isHome)); + } + return(limit); + } - /********************************************** - * Get the Loan-To-Income ratio applicable to a given household. - * - * @param firstTimeBuyer true if the household is a first time buyer - * @return The loan-to-income ratio applicable to the given household. - *********************************************/ - public double loanToIncome(boolean firstTimeBuyer) { - double limit; - limit = config.CENTRAL_BANK_MAX_OO_LTI; - if((nOverLTICapLoans+1.0)/(nLoans + 1.0) > Model.centralBank.proportionOverLTILimit) { - limit = Math.min(limit, Model.centralBank.loanToIncomeRegulation(firstTimeBuyer)); - } - return(limit); - } - - public double interestCoverageRatio() { - return(Model.centralBank.interestCoverageRatioRegulation()); - } + /********************************************** + * Get the Loan-To-Income ratio applicable to a given household. + * + * @param firstTimeBuyer true if the household is a first time buyer + * @return The loan-to-income ratio applicable to the given household. + *********************************************/ + public double loanToIncome(boolean firstTimeBuyer) { + double limit; + limit = config.CENTRAL_BANK_MAX_OO_LTI; + if((nOverLTICapLoans+1.0)/(nLoans + 1.0) > Model.centralBank.proportionOverLTILimit) { + limit = Math.min(limit, Model.centralBank.loanToIncomeRegulation(firstTimeBuyer)); + } + return(limit); + } + + public double interestCoverageRatio() { + return(Model.centralBank.interestCoverageRatioRegulation()); + } - // TODO: Remove (no needed anymore) -// public double getBtLStressedMortgageInterestRate() { -// return(config.CENTRAL_BANK_BTL_STRESSED_INTEREST); -// } - - public HashSet mortgages; // all unpaid mortgage contracts supplied by the bank - public double k; // principal to monthly payment factor - public double interestSpread; // current mortgage interest spread above base rate (monthly rate*12) - public double baseRate; - // --- supply strategy stuff - public double supplyTarget; // target supply of mortgage lending (pounds) - public double demand; // monthly demand for mortgage loans (pounds) - public double supplyVal; // monthly supply of mortgage loans (pounds) - public double lastMonthsSupplyVal; - public double dDemand_dInterest; // rate of change of demand with interest rate (pounds) - public int nOverLTICapLoans; // number of (non-BTL) loans above LTI cap this step - public int nOverLTVCapLoans; // number of (non-BTL) loans above LTV cap this step - public int nLoans; // total number of non-BTL loans this step - + // TODO: Remove (no needed anymore) +// public double getBtLStressedMortgageInterestRate() { +// return(config.CENTRAL_BANK_BTL_STRESSED_INTEREST); +// } + + public HashSet mortgages; // all unpaid mortgage contracts supplied by the bank + public double k; // principal to monthly payment factor + public double interestSpread; // current mortgage interest spread above base rate (monthly rate*12) + public double baseRate; + // --- supply strategy stuff + public double supplyTarget; // target supply of mortgage lending (pounds) + public double demand; // monthly demand for mortgage loans (pounds) + public double supplyVal; // monthly supply of mortgage loans (pounds) + public double lastMonthsSupplyVal; + public double dDemand_dInterest; // rate of change of demand with interest rate (pounds) + public int nOverLTICapLoans; // number of (non-BTL) loans above LTI cap this step + public int nOverLTVCapLoans; // number of (non-BTL) loans above LTV cap this step + public int nLoans; // total number of non-BTL loans this step + } diff --git a/src/main/resources/model.conf b/src/main/resources/model.conf index eb6f60ce..e8febadf 100644 --- a/src/main/resources/model.conf +++ b/src/main/resources/model.conf @@ -196,64 +196,74 @@ simulation { ############# Downpayment parameters ############# # Minimum income percentile to consider any downpayment, below this level, downpayment is set to 0 (double) # TODO: Calibrated against PSD data, need clearer reference or disclose distribution! - DOWNPAYMENT_MIN_INCOME = 0.3 - # TODO: Both functional form and parameters are micro-calibrated against BoE data. Need reference or disclose distribution! - # Scale parameter for the log-normal distribution of downpayments by first-time-buyers (double) - DOWNPAYMENT_FTB_SCALE = 10.30 - # Shape parameter for the log-normal distribution of downpayments by first-time-buyers (double) - DOWNPAYMENT_FTB_SHAPE = 0.9093 - # Scale parameter for the log-normal distribution of downpayments by owner-occupiers (double) - DOWNPAYMENT_OO_SCALE = 11.155 - # Shape parameter for the log-normal distribution of downpayments by owner-occupiers (double) - DOWNPAYMENT_OO_SHAPE = 0.7538 - # Average downpayment, as percentage of house price, by but-to-let investors (double) - # TODO: Said to be calibrated to match LTV ratios, but no reference is given. Need reference! - # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: Functional form slightly different to the one presented in the article - DOWNPAYMENT_BTL_MEAN = 0.3 - # Standard deviation of the noise (double) - DOWNPAYMENT_BTL_EPSILON = 0.1 - + downpayment { + DOWNPAYMENT_MIN_INCOME = 0.3 + # TODO: Both functional form and parameters are micro-calibrated against BoE data. Need reference or disclose distribution! + # Scale parameter for the log-normal distribution of downpayments by first-time-buyers (double) + DOWNPAYMENT_FTB_SCALE = 10.30 + # Shape parameter for the log-normal distribution of downpayments by first-time-buyers (double) + DOWNPAYMENT_FTB_SHAPE = 0.9093 + # Scale parameter for the log-normal distribution of downpayments by owner-occupiers (double) + DOWNPAYMENT_OO_SCALE = 11.155 + # Shape parameter for the log-normal distribution of downpayments by owner-occupiers (double) + DOWNPAYMENT_OO_SHAPE = 0.7538 + # Average downpayment, as percentage of house price, by but-to-let investors (double) + # TODO: Said to be calibrated to match LTV ratios, but no reference is given. Need reference! + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: Functional form slightly different to the one presented in the article + DOWNPAYMENT_BTL_MEAN = 0.3 + # Standard deviation of the noise (double) + DOWNPAYMENT_BTL_EPSILON = 0.1 + } ######## Desired bank balance parameters ######### # Micro-calibrated to match the log-normal relationship between wealth and income from the Wealth and Assets Survey # (double) - DESIRED_BANK_BALANCE_ALPHA = -32.0013877 - # (double) - DESIRED_BANK_BALANCE_BETA = 4.07 - # Standard deviation of a noise, it states a propensity to save (double) - DESIRED_BANK_BALANCE_EPSILON = 0.1 + desired-bank-balance { + + DESIRED_BANK_BALANCE_ALPHA = -32.0013877 + # (double) + DESIRED_BANK_BALANCE_BETA = 4.07 + # Standard deviation of a noise, it states a propensity to save (double) + DESIRED_BANK_BALANCE_EPSILON = 0.1 + } ########## Selling decision parameters ########### # Weight of houses per capita effect - DECISION_TO_SELL_ALPHA = 4.0 - # Weight of interest rate effect - DECISION_TO_SELL_BETA = 5.0 - # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: fudge parameter, explicitly explained otherwise in the article - DECISION_TO_SELL_HPC = 0.05 - # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: fudge parameter, explicitly explained otherwise in the article - DECISION_TO_SELL_INTEREST = 0.03 - - ######### BTL buy/sell choice parameters ######### - # Shape parameter, or intensity of choice on effective yield (double) - BTL_CHOICE_INTENSITY = 50.0 - # Minimun bank balance, as a percentage of the desired bank balance, to buy new properties (double) - # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: This parameter and its associated mechanism are not declared in the article! Need to declare and reference! - BTL_CHOICE_MIN_BANK_BALANCE = 0.75 - } + selling { + DECISION_TO_SELL_ALPHA = 4.0 + # Weight of interest rate effect + DECISION_TO_SELL_BETA = 5.0 + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: fudge parameter, explicitly explained otherwise in the article + DECISION_TO_SELL_HPC = 0.05 + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: fudge parameter, explicitly explained otherwise in the article + DECISION_TO_SELL_INTEREST = 0.03 + + ######### BTL buy/sell choice parameters ######### + # Shape parameter, or intensity of choice on effective yield (double) + BTL_CHOICE_INTENSITY = 50.0 + # Minimun bank balance, as a percentage of the desired bank balance, to buy new properties (double) + # TODO: @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ Attention: This parameter and its associated mechanism are not declared in the article! Need to declare and reference! + BTL_CHOICE_MIN_BANK_BALANCE = 0.75 + } } ################################################## ################ Bank parameters ################# ################################################## # TODO: We need references or justification for all these values! - banks { + bank { + # Mortgage duration in years (int) - MORTGAGE_DURATION_YEARS = 25 + mortgage-duration-years = 25 + # Bank initial base-rate, which remains currently unchanged (double) - BANK_INITIAL_BASE_RATE = 0.005 + initial-base-rate = 0.005 + # Bank's target supply of credit per household per month (double) - BANK_CREDIT_SUPPLY_TARGET = 380 + credit-supply-target = 380 + } + ################################################## ############# Central bank parameters ############ ################################################## From 5352ef56fe3c377fa55acfaa60abdeef87a9a55b Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Fri, 23 Jun 2017 10:16:45 +0300 Subject: [PATCH 6/7] More config work. --- .../HouseholdBehaviorConfig.java | 30 +++++++++++++++++++ .../java/configuration/HouseholdConfig.java | 5 ++++ src/main/java/housing/HouseholdBehaviour.java | 11 ++++--- src/main/resources/model.conf | 14 +++++---- 4 files changed, 50 insertions(+), 10 deletions(-) diff --git a/src/main/java/configuration/HouseholdBehaviorConfig.java b/src/main/java/configuration/HouseholdBehaviorConfig.java index 4eac903b..99136230 100644 --- a/src/main/java/configuration/HouseholdBehaviorConfig.java +++ b/src/main/java/configuration/HouseholdBehaviorConfig.java @@ -7,13 +7,43 @@ public class HouseholdBehaviorConfig { private BuyToLetConfig buyToLetConfig; + private DesiredBankBalanceConfig desiredBankBalanceConfig; HouseholdBehaviorConfig(Config config) { + buyToLetConfig = new BuyToLetConfig(config.getConfig("buy-to-let")); + desiredBankBalanceConfig = new DesiredBankBalanceConfig(config.getConfig("desired-bank-balance")); } public BuyToLetConfig getBuyToLetConfig() { return buyToLetConfig; } + public DesiredBankBalanceConfig getDesiredBankBalanceConfig() { return desiredBankBalanceConfig; } + + + /** Class used to configure a household's desired bank balances decision rule. + * + * @author davidrpugh + */ + public static class DesiredBankBalanceConfig { + + private double alpha; + private double beta; + private double epsilon; + + DesiredBankBalanceConfig (Config config) { + alpha = config.getDouble("alpha"); + beta = config.getDouble("beta"); + epsilon = config.getDouble("epsilon"); + } + + public double getAlpha() { return alpha; } + + public double getBeta() { return beta; } + + public double getEpsilon() { return epsilon; } + + } + } diff --git a/src/main/java/configuration/HouseholdConfig.java b/src/main/java/configuration/HouseholdConfig.java index 41ee55d1..a698ce74 100644 --- a/src/main/java/configuration/HouseholdConfig.java +++ b/src/main/java/configuration/HouseholdConfig.java @@ -3,6 +3,11 @@ import com.typesafe.config.Config; + +/** Class encapsulating all of the household parameters. + * + * @author davidrpugh + */ public class HouseholdConfig { private HouseholdBehaviorConfig behaviorConfig; diff --git a/src/main/java/housing/HouseholdBehaviour.java b/src/main/java/housing/HouseholdBehaviour.java index 890c9608..4b3e2d59 100644 --- a/src/main/java/housing/HouseholdBehaviour.java +++ b/src/main/java/housing/HouseholdBehaviour.java @@ -15,6 +15,7 @@ public class HouseholdBehaviour implements Serializable {// implements IHouseholdBehaviour { private static final long serialVersionUID = -7785886649432814279L; + private HouseholdBehaviorConfig config; //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // public final double DOWNPAYMENT_FRACTION = 0.75 + 0.0025*rand.nextGaussian(); // Fraction of bank-balance household would like to spend on mortgage downpayments // public final double INTENSITY_OF_CHOICE = 10.0; @@ -44,9 +45,10 @@ public double sigma(double x) { // the Logistic function, sometimes called sigma * used to determine whether the household can be a BTL investor ***************************************************/ public HouseholdBehaviour(HouseholdBehaviorConfig config, double incomePercentile) { - + this.config = config; + // Propensity to save is computed here so that it is constant for a given agent - propensityToSave = config.DESIRED_BANK_BALANCE_EPSILON*rand.nextGaussian(); + propensityToSave = config.getDesiredBankBalanceConfig().getEpsilon()*rand.nextGaussian(); BtLCapGainCoeff = 0.0; if(config.BTL_ENABLED) { if(incomePercentile > config.getBuyToLetConfig().getMinIncomePercentile() && @@ -66,6 +68,7 @@ public HouseholdBehaviour(HouseholdBehaviorConfig config, double incomePercentil } desiredBalance = -1.0; } + /////////////////////////////////////////////////////////////////////////////////////////////// // Owner-Ocupier behaviour /////////////////////////////////////////////////////////////////////////////////////////////// @@ -90,8 +93,8 @@ public double desiredBankBalance(Household me) { // TODO: why only if desired bank balance is set to -1? (does this get calculated only once? why?) if(desiredBalance == -1.0) { // desiredBalance = 3.0*Math.exp(4.07*Math.log(me.getMonthlyPreTaxIncome()*12.0)-33.1 - propensityToSave); - double lnDesiredBalance = config.DESIRED_BANK_BALANCE_ALPHA - + config.DESIRED_BANK_BALANCE_BETA + double lnDesiredBalance = config.getDesiredBankBalanceConfig().getAlpha() + + config.getDesiredBankBalanceConfig().getBeta() * Math.log(me.getMonthlyPreTaxIncome()*config.constants.MONTHS_IN_YEAR) + propensityToSave; desiredBalance = Math.exp(lnDesiredBalance); // TODO: What is this next rule? Not declared in the article! Check if 0.3 should be included as a parameter diff --git a/src/main/resources/model.conf b/src/main/resources/model.conf index e8febadf..bd3c8e54 100644 --- a/src/main/resources/model.conf +++ b/src/main/resources/model.conf @@ -197,6 +197,7 @@ simulation { # Minimum income percentile to consider any downpayment, below this level, downpayment is set to 0 (double) # TODO: Calibrated against PSD data, need clearer reference or disclose distribution! downpayment { + DOWNPAYMENT_MIN_INCOME = 0.3 # TODO: Both functional form and parameters are micro-calibrated against BoE data. Need reference or disclose distribution! # Scale parameter for the log-normal distribution of downpayments by first-time-buyers (double) @@ -216,19 +217,20 @@ simulation { } ######## Desired bank balance parameters ######### # Micro-calibrated to match the log-normal relationship between wealth and income from the Wealth and Assets Survey - # (double) desired-bank-balance { - DESIRED_BANK_BALANCE_ALPHA = -32.0013877 - # (double) - DESIRED_BANK_BALANCE_BETA = 4.07 - # Standard deviation of a noise, it states a propensity to save (double) - DESIRED_BANK_BALANCE_EPSILON = 0.1 + alpha = -32.0013877 + + beta = 4.07 + + epsilon = 0.1 + } ########## Selling decision parameters ########### # Weight of houses per capita effect selling { + DECISION_TO_SELL_ALPHA = 4.0 # Weight of interest rate effect DECISION_TO_SELL_BETA = 5.0 From b4559eab3cb69a8c5383c604ffb5a7b48ffa06ab Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Fri, 23 Jun 2017 17:53:27 +0300 Subject: [PATCH 7/7] Added config for consumption decision rule. --- .../HouseholdBehaviorConfig.java | 21 ++++++++++++++++++- src/main/java/housing/HouseholdBehaviour.java | 2 +- src/main/resources/model.conf | 16 ++++++++------ 3 files changed, 31 insertions(+), 8 deletions(-) diff --git a/src/main/java/configuration/HouseholdBehaviorConfig.java b/src/main/java/configuration/HouseholdBehaviorConfig.java index 99136230..788db9af 100644 --- a/src/main/java/configuration/HouseholdBehaviorConfig.java +++ b/src/main/java/configuration/HouseholdBehaviorConfig.java @@ -8,11 +8,12 @@ public class HouseholdBehaviorConfig { private BuyToLetConfig buyToLetConfig; private DesiredBankBalanceConfig desiredBankBalanceConfig; + private ConsumptionDecisionRuleConfig consumptionDecisionRuleConfig; HouseholdBehaviorConfig(Config config) { - buyToLetConfig = new BuyToLetConfig(config.getConfig("buy-to-let")); desiredBankBalanceConfig = new DesiredBankBalanceConfig(config.getConfig("desired-bank-balance")); + consumptionDecisionRuleConfig = new ConsumptionDecisionRuleConfig(config.getConfig("consumption-decision-rule")); } public BuyToLetConfig getBuyToLetConfig() { @@ -21,6 +22,7 @@ public BuyToLetConfig getBuyToLetConfig() { public DesiredBankBalanceConfig getDesiredBankBalanceConfig() { return desiredBankBalanceConfig; } + public ConsumptionDecisionRuleConfig getConsumptionDecisionRuleConfig() { return consumptionDecisionRuleConfig; } /** Class used to configure a household's desired bank balances decision rule. * @@ -46,4 +48,21 @@ public static class DesiredBankBalanceConfig { } + + public static class ConsumptionDecisionRuleConfig { + + private double consumptionFraction; + private double essentialConsumptionFraction; + + ConsumptionDecisionRuleConfig(Config config) { + consumptionFraction = config.getDouble("consumption-fraction"); + essentialConsumptionFraction = config.getDouble("essential-consumption-fraction"); + } + + public double getConsumptionFraction() { return consumptionFraction; } + + public double getEssentialConsumptionFraction() { return essentialConsumptionFraction; } + + } + } diff --git a/src/main/java/housing/HouseholdBehaviour.java b/src/main/java/housing/HouseholdBehaviour.java index 4b3e2d59..6fc76c1c 100644 --- a/src/main/java/housing/HouseholdBehaviour.java +++ b/src/main/java/housing/HouseholdBehaviour.java @@ -81,7 +81,7 @@ public HouseholdBehaviour(HouseholdBehaviorConfig config, double incomePercentil * @return Non-essential consumption for the month ********************************/ public double desiredConsumptionB(Household me) {//double monthlyIncome, double bankBalance) { - return(config.CONSUMPTION_FRACTION*Math.max(me.getBankBalance() - desiredBankBalance(me), 0.0)); + return(config.getConsumptionDecisionRuleConfig().getConsumptionFraction()*Math.max(me.getBankBalance() - desiredBankBalance(me), 0.0)); } /******************************** diff --git a/src/main/resources/model.conf b/src/main/resources/model.conf index bd3c8e54..0d38b9f5 100644 --- a/src/main/resources/model.conf +++ b/src/main/resources/model.conf @@ -154,13 +154,17 @@ simulation { REDUCTION_SIGMA = 0.617 ############# Comsumption parameters ############# - # Fraction of the monthly budget allocated for consumption, being the monthly - # budget equal to the bank balance minus the minimum desired bank balance (double) - CONSUMPTION_FRACTION = 0.5 - # Fraction of Government support representing the amount necessarily spent monthly by - # all households as essential consumption (double) - ESSENTIAL_CONSUMPTION_FRACTION = 0.8 + consumption-decision-rule { + # Fraction of the monthly budget allocated for consumption, being the monthly + # budget equal to the bank balance minus the minimum desired bank balance (double) + consumption-fraction = 0.5 + + # Fraction of Government support representing the amount necessarily spent monthly by + # all households as essential consumption (double) + essential-consumption-fraction = 0.8 + + } ######### Initial sale price parameters ########## # Initial markup over average price of same quality houses (double) # TODO: Note says that, according to BoE calibration, this should be around 0.2. Check and solve this!