From c3b8950cd91e4e896adbed4f73632fe2e88e7d42 Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Wed, 18 Apr 2018 06:05:34 +0300 Subject: [PATCH 1/2] moved all housing market related files into separate package. --- src/main/java/collectors/HousingMarketStats.java | 6 +++++- src/main/java/collectors/MicroDataRecorder.java | 4 ++++ src/main/java/collectors/RentalMarketStats.java | 2 ++ src/main/java/housing/Construction.java | 1 + src/main/java/housing/House.java | 4 +++- src/main/java/housing/Household.java | 1 + src/main/java/housing/HouseholdBehaviour.java | 1 + src/main/java/housing/IHouseOwner.java | 2 ++ src/main/java/housing/Model.java | 2 ++ src/main/java/{housing => markets}/BTLBuyerRecord.java | 5 ++++- src/main/java/{housing => markets}/HouseBuyerRecord.java | 4 +++- src/main/java/{housing => markets}/HouseRentalMarket.java | 5 ++++- src/main/java/{housing => markets}/HouseSaleMarket.java | 8 +++++--- src/main/java/{housing => markets}/HouseSaleRecord.java | 7 +++++-- src/main/java/{housing => markets}/HousingMarket.java | 7 ++++--- .../java/{housing => markets}/HousingMarketRecord.java | 2 +- 16 files changed, 47 insertions(+), 14 deletions(-) rename src/main/java/{housing => markets}/BTLBuyerRecord.java (73%) rename src/main/java/{housing => markets}/HouseBuyerRecord.java (96%) rename src/main/java/{housing => markets}/HouseRentalMarket.java (95%) rename src/main/java/{housing => markets}/HouseSaleMarket.java (95%) rename src/main/java/{housing => markets}/HouseSaleRecord.java (97%) rename src/main/java/{housing => markets}/HousingMarket.java (98%) rename src/main/java/{housing => markets}/HousingMarketRecord.java (99%) diff --git a/src/main/java/collectors/HousingMarketStats.java b/src/main/java/collectors/HousingMarketStats.java index 9f26e502..1b387525 100644 --- a/src/main/java/collectors/HousingMarketStats.java +++ b/src/main/java/collectors/HousingMarketStats.java @@ -2,6 +2,10 @@ import housing.*; +import markets.HouseBuyerRecord; +import markets.HouseSaleRecord; +import markets.HousingMarket; +import markets.HousingMarketRecord; import org.apache.commons.math3.stat.descriptive.DescriptiveStatistics; /************************************************************************************************** @@ -18,7 +22,7 @@ public class HousingMarketStats extends CollectorBase { //------------------// // General fields - private HousingMarket market; // Declared HousingMarket so that it can accommodate both sale and rental markets + private HousingMarket market; // Declared HousingMarket so that it can accommodate both sale and rental markets private Config config = Model.config; // Passes the Model's configuration parameters object to a private field // Variables computed at initialisation diff --git a/src/main/java/collectors/MicroDataRecorder.java b/src/main/java/collectors/MicroDataRecorder.java index 5c770f81..6b7b53fd 100644 --- a/src/main/java/collectors/MicroDataRecorder.java +++ b/src/main/java/collectors/MicroDataRecorder.java @@ -1,6 +1,10 @@ package collectors; import housing.*; +import markets.HouseBuyerRecord; +import markets.HouseSaleMarket; +import markets.HouseSaleRecord; +import markets.HousingMarket; import java.io.FileNotFoundException; import java.io.PrintWriter; diff --git a/src/main/java/collectors/RentalMarketStats.java b/src/main/java/collectors/RentalMarketStats.java index d20a8b84..ae34fba1 100644 --- a/src/main/java/collectors/RentalMarketStats.java +++ b/src/main/java/collectors/RentalMarketStats.java @@ -1,6 +1,8 @@ package collectors; import housing.*; +import markets.HouseRentalMarket; +import markets.HouseSaleRecord; import java.util.Arrays; diff --git a/src/main/java/housing/Construction.java b/src/main/java/housing/Construction.java index 917ca360..fffbb92f 100644 --- a/src/main/java/housing/Construction.java +++ b/src/main/java/housing/Construction.java @@ -1,5 +1,6 @@ package housing; +import markets.HouseSaleRecord; import org.apache.commons.math3.random.MersenneTwister; import java.io.Serializable; diff --git a/src/main/java/housing/House.java b/src/main/java/housing/House.java index 66d44df3..2cd524ed 100644 --- a/src/main/java/housing/House.java +++ b/src/main/java/housing/House.java @@ -1,5 +1,7 @@ package housing; +import markets.HouseSaleRecord; + import java.io.Serializable; /************************************************************************************************** @@ -21,7 +23,7 @@ public class House implements Comparable, Serializable { public Household resident; public int id; - HouseSaleRecord saleRecord; + HouseSaleRecord saleRecord; HouseSaleRecord rentalRecord; private int quality; diff --git a/src/main/java/housing/Household.java b/src/main/java/housing/Household.java index 661f9b1c..4a19e089 100644 --- a/src/main/java/housing/Household.java +++ b/src/main/java/housing/Household.java @@ -6,6 +6,7 @@ import java.util.Map.Entry; import java.util.TreeMap; +import markets.HouseSaleRecord; import org.apache.commons.math3.random.MersenneTwister; /************************************************************************************************** diff --git a/src/main/java/housing/HouseholdBehaviour.java b/src/main/java/housing/HouseholdBehaviour.java index 14b2f345..088a5d9b 100644 --- a/src/main/java/housing/HouseholdBehaviour.java +++ b/src/main/java/housing/HouseholdBehaviour.java @@ -2,6 +2,7 @@ import java.io.Serializable; +import markets.HouseSaleRecord; import org.apache.commons.math3.distribution.LogNormalDistribution; import org.apache.commons.math3.random.MersenneTwister; diff --git a/src/main/java/housing/IHouseOwner.java b/src/main/java/housing/IHouseOwner.java index 39b62418..e2e85ba8 100644 --- a/src/main/java/housing/IHouseOwner.java +++ b/src/main/java/housing/IHouseOwner.java @@ -1,5 +1,7 @@ package housing; +import markets.HouseSaleRecord; + /*************************************************** * Common interface for agents who enter the sale * market as sellers and, thus, house owners. This diff --git a/src/main/java/housing/Model.java b/src/main/java/housing/Model.java index 9c358017..9a64c6e3 100644 --- a/src/main/java/housing/Model.java +++ b/src/main/java/housing/Model.java @@ -8,6 +8,8 @@ import collectors.*; +import markets.HouseRentalMarket; +import markets.HouseSaleMarket; import org.apache.commons.math3.random.MersenneTwister; import org.apache.commons.cli.*; import org.apache.commons.io.FileUtils; diff --git a/src/main/java/housing/BTLBuyerRecord.java b/src/main/java/markets/BTLBuyerRecord.java similarity index 73% rename from src/main/java/housing/BTLBuyerRecord.java rename to src/main/java/markets/BTLBuyerRecord.java index 6be8b367..f1772b05 100644 --- a/src/main/java/housing/BTLBuyerRecord.java +++ b/src/main/java/markets/BTLBuyerRecord.java @@ -1,4 +1,7 @@ -package housing; +package markets; + +import housing.Household; +import markets.HouseBuyerRecord; public class BTLBuyerRecord extends HouseBuyerRecord { private static final long serialVersionUID = 5314886568148212605L; diff --git a/src/main/java/housing/HouseBuyerRecord.java b/src/main/java/markets/HouseBuyerRecord.java similarity index 96% rename from src/main/java/housing/HouseBuyerRecord.java rename to src/main/java/markets/HouseBuyerRecord.java index 60a6b7d3..7a1047d5 100644 --- a/src/main/java/housing/HouseBuyerRecord.java +++ b/src/main/java/markets/HouseBuyerRecord.java @@ -1,4 +1,6 @@ -package housing; +package markets; + +import housing.Household; import java.util.Comparator; diff --git a/src/main/java/housing/HouseRentalMarket.java b/src/main/java/markets/HouseRentalMarket.java similarity index 95% rename from src/main/java/housing/HouseRentalMarket.java rename to src/main/java/markets/HouseRentalMarket.java index 71e620bd..c356275d 100644 --- a/src/main/java/housing/HouseRentalMarket.java +++ b/src/main/java/markets/HouseRentalMarket.java @@ -1,4 +1,7 @@ -package housing; +package markets; + +import housing.House; +import housing.Model; /************************************************************************************************** * Class to represent the rental market diff --git a/src/main/java/housing/HouseSaleMarket.java b/src/main/java/markets/HouseSaleMarket.java similarity index 95% rename from src/main/java/housing/HouseSaleMarket.java rename to src/main/java/markets/HouseSaleMarket.java index 51a79604..85c2e2b0 100644 --- a/src/main/java/housing/HouseSaleMarket.java +++ b/src/main/java/markets/HouseSaleMarket.java @@ -1,7 +1,8 @@ -package housing; +package markets; import java.util.Iterator; +import housing.*; import utilities.PriorityQueue2D; /******************************************************* @@ -13,7 +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 + private Config config = Model.config; // Passes the Model's configuration parameters object to a private field private PriorityQueue2D offersPY; public HouseSaleMarket() { @@ -67,7 +68,7 @@ public void updateOffer(HouseSaleRecord hsr, double newPrice) { * This method overrides the main simulation step in order to sort the price-yield priorities. */ @Override - void clearMarket() { + public void clearMarket() { // Before any use, priorities must be sorted by filling in the uncoveredElements TreeSet at the corresponding // PriorityQueue2D. In particular, we sort here the price-yield priorities offersPY.sortPriorities(); @@ -114,4 +115,5 @@ void removeOfferFromQueues(Iterator record, HouseSaleRecord * @param maxPrice The maximum price that the household is willing to pay. ******************************************/ void BTLbid(Household buyer, double maxPrice) { bids.add(new BTLBuyerRecord(buyer, maxPrice)); } + } diff --git a/src/main/java/housing/HouseSaleRecord.java b/src/main/java/markets/HouseSaleRecord.java similarity index 97% rename from src/main/java/housing/HouseSaleRecord.java rename to src/main/java/markets/HouseSaleRecord.java index 543eed73..5ba9d979 100644 --- a/src/main/java/housing/HouseSaleRecord.java +++ b/src/main/java/markets/HouseSaleRecord.java @@ -1,4 +1,7 @@ -package housing; +package markets; + +import housing.House; +import housing.Model; import java.util.ArrayList; @@ -16,7 +19,7 @@ public class HouseSaleRecord extends HousingMarketRecord { //----- Fields -----// //------------------// - public House house; + public House house; ArrayList matchedBids; public double initialListedPrice; public int tInitialListing; // Time of initial listing diff --git a/src/main/java/housing/HousingMarket.java b/src/main/java/markets/HousingMarket.java similarity index 98% rename from src/main/java/housing/HousingMarket.java rename to src/main/java/markets/HousingMarket.java index 82697cdb..af605e26 100644 --- a/src/main/java/housing/HousingMarket.java +++ b/src/main/java/markets/HousingMarket.java @@ -1,9 +1,10 @@ -package housing; +package markets; import java.io.Serializable; import java.util.ArrayList; import java.util.Iterator; +import housing.*; import org.apache.commons.math3.distribution.GeometricDistribution; import org.apache.commons.math3.random.MersenneTwister; @@ -24,7 +25,7 @@ public abstract class HousingMarket implements Serializable { private static Authority authority = new Authority(); - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field + private Config config = Model.config; // Passes the Model's configuration parameters object to a private field private MersenneTwister rand = Model.rand; // Passes the Model's random number generator to a private field private PriorityQueue2D offersPQ; @@ -108,7 +109,7 @@ public void bid(Household buyer, double price) { /** * Main simulation step. For a number of rounds, matches bids with offers and clears the matches. */ - void clearMarket() { + public void clearMarket() { // Before any use, priorities must be sorted by filling in the uncoveredElements TreeSet at the corresponding // PriorityQueue2D offersPQ.sortPriorities(); diff --git a/src/main/java/housing/HousingMarketRecord.java b/src/main/java/markets/HousingMarketRecord.java similarity index 99% rename from src/main/java/housing/HousingMarketRecord.java rename to src/main/java/markets/HousingMarketRecord.java index f25bd731..90e45916 100644 --- a/src/main/java/housing/HousingMarketRecord.java +++ b/src/main/java/markets/HousingMarketRecord.java @@ -1,4 +1,4 @@ -package housing; +package markets; import java.io.Serializable; From 1a359aa278467d7a49110ca4ccf5db5b71cbc27a Mon Sep 17 00:00:00 2001 From: davidrpugh Date: Mon, 28 May 2018 12:42:21 +0300 Subject: [PATCH 2/2] Mass commit -of existing work. --- .../java/collectors/HousingMarketStats.java | 4 +- src/main/java/housing/Config.java | 4 +- src/main/java/housing/Construction.java | 3 +- src/main/java/housing/House.java | 1 + src/main/java/housing/Household.java | 34 +++--- src/main/java/housing/HouseholdBehaviour.java | 4 +- src/main/java/housing/Model.java | 6 +- src/main/java/markets/AltHousingMarket.java | 68 ++++++++++++ src/main/java/markets/Bid.java | 23 ++++ src/main/java/markets/HouseBuyerRecord.java | 11 +- src/main/java/markets/HouseRentalMarket.java | 10 +- src/main/java/markets/HouseSaleMarket.java | 44 ++++---- src/main/java/markets/HouseSaleRecord.java | 15 ++- src/main/java/markets/HousingMarket.java | 105 ++++++++++-------- .../java/markets/HousingMarketRecord.java | 8 +- src/main/java/markets/Offer.java | 31 ++++++ src/main/java/markets/Order.java | 20 ++++ src/main/resources/config.properties | 2 +- 18 files changed, 279 insertions(+), 114 deletions(-) create mode 100644 src/main/java/markets/AltHousingMarket.java create mode 100644 src/main/java/markets/Bid.java create mode 100644 src/main/java/markets/Offer.java create mode 100644 src/main/java/markets/Order.java diff --git a/src/main/java/collectors/HousingMarketStats.java b/src/main/java/collectors/HousingMarketStats.java index 1b387525..2fcec484 100644 --- a/src/main/java/collectors/HousingMarketStats.java +++ b/src/main/java/collectors/HousingMarketStats.java @@ -173,7 +173,7 @@ public void preClearingRecord() { bidPrices = new double[nBuyers]; - // Record bid prices and their average + // Record submitBid prices and their average int i = 0; for(HouseBuyerRecord bid : market.getBids()) { sumBidPrices += bid.getPrice(); @@ -181,7 +181,7 @@ public void preClearingRecord() { ++i; } - // Record offer prices, their average, and the number of empty and new houses + // Record submitOffer prices, their average, and the number of empty and new houses i = 0; for(HousingMarketRecord sale : market.getOffersPQ()) { sumOfferPrices += sale.getPrice(); diff --git a/src/main/java/housing/Config.java b/src/main/java/housing/Config.java index 7bad0893..5c81e630 100644 --- a/src/main/java/housing/Config.java +++ b/src/main/java/housing/Config.java @@ -36,7 +36,7 @@ public class Config { public int N_QUALITY; // Number of quality bands for houses // Housing market parameters - int DAYS_UNDER_OFFER; // Time (in days) that a house remains under offer + int DAYS_UNDER_OFFER; // Time (in days) that a house remains under submitOffer double BIDUP; // Smallest proportional increase in price that can cause a gazump public double MARKET_AVERAGE_PRICE_DECAY; // Decay constant for the exponential moving average of sale prices public double INITIAL_HPI; // Initial housing price index @@ -172,7 +172,7 @@ public class Config { public class DerivedParams { // Housing market parameters public int HPI_RECORD_LENGTH; // Number of months to record HPI (to compute price growth at different time scales) - double MONTHS_UNDER_OFFER; // Time (in months) that a house remains under offer + double MONTHS_UNDER_OFFER; // Time (in months) that a house remains under submitOffer double T; // Characteristic number of data-points over which to average market statistics public double E; // Decay constant for averaging days on market (in transactions) public double G; // Decay constant for averageListPrice averaging (in transactions) diff --git a/src/main/java/housing/Construction.java b/src/main/java/housing/Construction.java index fffbb92f..0534d258 100644 --- a/src/main/java/housing/Construction.java +++ b/src/main/java/housing/Construction.java @@ -42,6 +42,7 @@ public void init() { public void step() { // Initialise to zero the number of houses built this month nNewBuild = 0; + // First update prices of properties put on the market on previous time steps and still unsold for(House h : onMarket) { Model.houseSaleMarket.updateOffer(h.getSaleRecord(), h.getSaleRecord().getPrice()*0.95); @@ -67,7 +68,7 @@ public void step() { newHouse = new House((int)(rand.nextDouble()*config.N_QUALITY)); newHouse.owner = this; // ...put the house for sale in the house sale market at the reference price for that quality - Model.houseSaleMarket.offer(newHouse, + Model.houseSaleMarket.submitOffer(newHouse, Model.housingMarketStats.getReferencePriceForQuality(newHouse.getQuality())); // ...add the house to the portfolio of construction sector properties onMarket.add(newHouse); diff --git a/src/main/java/housing/House.java b/src/main/java/housing/House.java index 2cd524ed..7ef52155 100644 --- a/src/main/java/housing/House.java +++ b/src/main/java/housing/House.java @@ -55,6 +55,7 @@ public House(int quality) { HouseSaleRecord getRentalRecord() { return rentalRecord; } boolean isOnRentalMarket() { return rentalRecord != null; } + void putForSale(HouseSaleRecord saleRecord) { this.saleRecord = saleRecord; } void resetSaleRecord() { saleRecord = null; } diff --git a/src/main/java/housing/Household.java b/src/main/java/housing/Household.java index 4a19e089..5d06534b 100644 --- a/src/main/java/housing/Household.java +++ b/src/main/java/housing/Household.java @@ -162,7 +162,7 @@ public double getMonthlyGrossTotalIncome() { /** * Decide what to do with a house h owned by the household: * - if the household lives in the house, decide whether to sell it or not - * - if the house is up for sale, rethink its offer price, and possibly put it up for rent instead (only BTL investors) + * - if the house is up for sale, rethink its submitOffer price, and possibly put it up for rent instead (only BTL investors) * - if the house is up for rent, rethink the rent demanded * * @param house A house owned by the household @@ -177,14 +177,14 @@ private void manageHouse(House house) { if(newPrice > mortgageFor(house).principal) { Model.houseSaleMarket.updateOffer(forSale, newPrice); } else { - Model.houseSaleMarket.removeOffer(forSale); + Model.houseSaleMarket.cancelOffer(forSale); // TODO: First condition is redundant! if(house != home && house.resident == null) { - Model.houseRentalMarket.offer(house, buyToLetRent(house)); + Model.houseRentalMarket.submitOffer(house, buyToLetRent(house)); } } } else if(decideToSellHouse(house)) { // put house on market? - if(house.isOnRentalMarket()) Model.houseRentalMarket.removeOffer(house.getRentalRecord()); + if(house.isOnRentalMarket()) Model.houseRentalMarket.cancelOffer(house.getRentalRecord()); putHouseForSale(house); } @@ -208,7 +208,7 @@ private void putHouseForSale(House h) { } else { principal = 0.0; } - Model.houseSaleMarket.offer(h, behaviour.getInitialSalePrice(h.getQuality(), principal)); + Model.houseSaleMarket.submitOffer(h, behaviour.getInitialSalePrice(h.getQuality(), principal)); } ///////////////////////////////////////////////////////// @@ -252,7 +252,7 @@ void completeHousePurchase(HouseSaleRecord sale) { home = sale.house; sale.house.resident = this; } else if (sale.house.resident == null) { // put empty buy-to-let house on rental market - Model.houseRentalMarket.offer(sale.house, buyToLetRent(sale.house)); + Model.houseRentalMarket.submitOffer(sale.house, buyToLetRent(sale.house)); } isFirstTimeBuyer = false; } @@ -266,7 +266,7 @@ public void completeHouseSale(HouseSaleRecord sale) { bankBalance += sale.getPrice(); bankBalance -= mortgage.payoff(bankBalance); if(sale.house.isOnRentalMarket()) { - Model.houseRentalMarket.removeOffer(sale); + Model.houseRentalMarket.cancelOffer(sale); } // TODO: Warning, if bankBalance is not enough to pay mortgage back, then the house stays in housePayments, consequences to be checked! if(mortgage.nPayments == 0) { @@ -300,7 +300,7 @@ public void endOfLettingAgreement(House h, PaymentAgreement contract) { // if(h.resident != null) System.out.println("Strange: renting out a house that has a resident"); // if(h.resident != null && h.resident == h.owner) System.out.println("Strange: renting out a house that belongs to a homeowner"); if(h.isOnRentalMarket()) System.out.println("Strange: got endOfLettingAgreement on house on rental market"); - if(!h.isOnMarket()) Model.houseRentalMarket.offer(h, buyToLetRent(h)); + if(!h.isOnMarket()) Model.houseRentalMarket.submitOffer(h, buyToLetRent(h)); } /********************************************************** @@ -357,7 +357,7 @@ void completeHouseRental(HouseSaleRecord sale) { /******************************************************** - * Make the decision whether to bid on the housing market or rental market. + * Make the decision whether to submitBid on the housing market or rental market. * This is an "intensity of choice" decision (sigma function) * on the cost of renting compared to the cost of owning, with * COST_OF_RENTING being an intrinsic psychological cost of not @@ -370,11 +370,11 @@ private void bidForAHome() { price = Math.min(price, Model.bank.getMaxMortgage(this, true)); // Compare costs to decide whether to buy or rent... if(behaviour.decideRentOrPurchase(this, price)) { - // ... if buying, bid in the house sale market for the capped desired price - Model.houseSaleMarket.bid(this, price); + // ... if buying, submitBid in the house sale market for the capped desired price + Model.houseSaleMarket.submitBid(this, price); } else { - // ... if renting, bid in the house rental market for the desired rent price - Model.houseRentalMarket.bid(this, behaviour.desiredRent(this, monthlyGrossEmploymentIncome)); + // ... if renting, submitBid in the house rental market for the desired rent price + Model.houseRentalMarket.submitBid(this, behaviour.desiredRent(this, monthlyGrossEmploymentIncome)); } } @@ -399,7 +399,7 @@ private boolean decideToSellHouse(House h) { @Override public void completeHouseLet(HouseSaleRecord sale) { if(sale.house.isOnMarket()) { - Model.houseSaleMarket.removeOffer(sale.house.getSaleRecord()); + Model.houseSaleMarket.cancelOffer(sale.house.getSaleRecord()); } monthlyGrossRentalIncome += sale.getPrice(); } @@ -440,8 +440,8 @@ void transferAllWealthTo(Household beneficiary) { isHome = false; } if(h.owner == this) { - if(h.isOnRentalMarket()) Model.houseRentalMarket.removeOffer(h.getRentalRecord()); - if(h.isOnMarket()) Model.houseSaleMarket.removeOffer(h.getSaleRecord()); + if(h.isOnRentalMarket()) Model.houseRentalMarket.cancelOffer(h.getRentalRecord()); + if(h.isOnMarket()) Model.houseSaleMarket.cancelOffer(h.getSaleRecord()); if(h.resident != null) h.resident.getEvicted(); beneficiary.inheritHouse(h, isHome); } else { @@ -486,7 +486,7 @@ private void inheritHouse(House h, boolean wasHome) { if(decideToSellHouse(h)) { putHouseForSale(h); } else if(h.resident == null) { - Model.houseRentalMarket.offer(h, buyToLetRent(h)); + Model.houseRentalMarket.submitOffer(h, buyToLetRent(h)); } } else { // I'm an owner-occupier diff --git a/src/main/java/housing/HouseholdBehaviour.java b/src/main/java/housing/HouseholdBehaviour.java index 088a5d9b..d8d6698e 100644 --- a/src/main/java/housing/HouseholdBehaviour.java +++ b/src/main/java/housing/HouseholdBehaviour.java @@ -97,7 +97,7 @@ public class HouseholdBehaviour implements Serializable { //----- Owner-Occupier behaviour -----// /** - * Desired purchase price used to decide whether to buy a house and how much to bid for it + * Desired purchase price used to decide whether to buy a house and how much to submitBid for it * * @param monthlyIncome Monthly income of the household */ @@ -224,7 +224,7 @@ public boolean decideRentOrPurchase(Household me, double desiredPurchasePrice) { } /******************************************************** - * Decide how much to bid on the rental market + * Decide how much to submitBid on the rental market * Source: Zoopla rental prices 2008-2009 (at Bank of England) ********************************************************/ double desiredRent(Household me, double monthlyIncome) { diff --git a/src/main/java/housing/Model.java b/src/main/java/housing/Model.java index 9a64c6e3..f9d041d6 100644 --- a/src/main/java/housing/Model.java +++ b/src/main/java/housing/Model.java @@ -131,7 +131,7 @@ public static void main(String[] args) { // For each simulation, run config.N_STEPS time steps for (t = 0; t <= config.N_STEPS; t += 1) { - // Steps model and stores sale and rental markets bid and offer prices, and their averages, into their + // Steps model and stores sale and rental markets submitBid and submitOffer prices, and their averages, into their // respective variables modelStep(); @@ -185,13 +185,13 @@ private static void modelStep() { construction.step(); // Updates regional households consumption, housing decisions, and corresponding regional bids and offers for(Household h : households) h.step(); - // Stores sale market bid and offer prices and averages before bids are matched by clearing the market + // Stores sale market submitBid and submitOffer prices and averages before bids are matched by clearing the market housingMarketStats.preClearingRecord(); // Clears sale market and updates the HPI houseSaleMarket.clearMarket(); // Computes and stores several housing market statistics after bids are matched by clearing the market (such as HPI, HPA) housingMarketStats.postClearingRecord(); - // Stores rental market bid and offer prices and averages before bids are matched by clearing the market + // Stores rental market submitBid and submitOffer prices and averages before bids are matched by clearing the market rentalMarketStats.preClearingRecord(); // Clears rental market houseRentalMarket.clearMarket(); diff --git a/src/main/java/markets/AltHousingMarket.java b/src/main/java/markets/AltHousingMarket.java new file mode 100644 index 00000000..421fe964 --- /dev/null +++ b/src/main/java/markets/AltHousingMarket.java @@ -0,0 +1,68 @@ +package markets; + + +import org.apache.commons.math3.random.MersenneTwister; + +import java.util.*; + +public class AltHousingMarket { + + private MersenneTwister prng; + private List buyerRecords; + private HashSet sellerRecords; + private HashMap> potentialMatchedRecords; + + AltHousingMarket(MersenneTwister prng) { + this.prng = prng; + buyerRecords = new ArrayList<>(); + sellerRecords = new HashSet<>(); + potentialMatchedRecords = new HashMap<>(); + } + + public void submitBid(Bid bid) { + HouseBuyerRecord record = HouseBuyerRecord.from(bid); + buyerRecords.add(record); + } + + public void submitOffer(Offer offer) { + HouseSaleRecord record = HouseSaleRecord.from(offer); + sellerRecords.add(record); + } + + private void findPotentialMatches() { + for (HouseBuyerRecord buyerRecord : buyerRecords) { + HashSet affordableListings = new HashSet<>(); + for (HouseSaleRecord sellerRecord : sellerRecords) { + if (sellerRecord.getPrice() <= buyerRecord.getPrice()) { + affordableListings.add(sellerRecord); + } + } + HouseSaleRecord highestQualityAffordableListing = Collections.max(affordableListings, ???); + HashSet existingMatchedRecords = potentialMatchedRecords.getOrDefault(highestQualityAffordableListing, new HashSet<>()); + existingMatchedRecords.add(buyerRecord); + } + } + + private HashMap finalizePotentialMatches() { + HashMap finalizedMatchedRecords = new HashMap<>(); + for (HouseSaleRecord sellerRecord : sellerRecords) { + List potentialBuyers = potentialMatchedRecords.getOrDefault(sellerRecord, new ArrayList<>()); + if (potentialBuyers.isEmpty()) { + sellerRecords.remove(sellerRecord); // TODO is this the correct behavior if seller has no potential buyers? + } else if (potentialBuyers.size() == 1) { + HouseBuyerRecord matchedBuyerRecord = potentialBuyers.remove(0); + finalizedMatchedRecords.put(matchedBuyerRecord, sellerRecord); + buyerRecords.remove(matchedBuyerRecord); + sellerRecords.remove(sellerRecord); + } else { + int idx = prng.nextInt(potentialBuyers.size()); + HouseBuyerRecord matchedBuyerRecord = potentialBuyers.remove(idx); + finalizedMatchedRecords.put(matchedBuyerRecord, sellerRecord); + buyerRecords.remove(matchedBuyerRecord); + sellerRecords.remove(sellerRecord); + } + } + return finalizedMatchedRecords; + } + +} diff --git a/src/main/java/markets/Bid.java b/src/main/java/markets/Bid.java new file mode 100644 index 00000000..0f8ea6de --- /dev/null +++ b/src/main/java/markets/Bid.java @@ -0,0 +1,23 @@ +package markets; + + +import housing.Household; + + +/** Represents an offer by a specific household to purchase a yet to be determined house at some price. */ +public class Bid extends Order { + + Bid(Household household, double price) { + this.household = household; + this.price = price; + } + + public Household getHousehold() { + return household; + } + + public double getPrice() { + return price; + } + +} diff --git a/src/main/java/markets/HouseBuyerRecord.java b/src/main/java/markets/HouseBuyerRecord.java index 7a1047d5..907198c3 100644 --- a/src/main/java/markets/HouseBuyerRecord.java +++ b/src/main/java/markets/HouseBuyerRecord.java @@ -13,12 +13,19 @@ * **********************************************/ public class HouseBuyerRecord extends HousingMarketRecord { + private static final long serialVersionUID = -4092951887680947486L; + private Household buyer; + private double price; - public HouseBuyerRecord(Household h, double price) { + private HouseBuyerRecord(Household h, double price) { super(price); buyer = h; } + + public static HouseBuyerRecord from(Bid bid) { + return new HouseBuyerRecord(bid.getHousehold(), bid.getPrice()); + } public static class PComparator implements Comparator { @Override @@ -34,8 +41,6 @@ public int compare(HouseBuyerRecord arg0, HouseBuyerRecord arg1) { ///////////////////////////////////////////////////////////////// - - public Household buyer; // Who wants to buy the house @Override public int getQuality() { diff --git a/src/main/java/markets/HouseRentalMarket.java b/src/main/java/markets/HouseRentalMarket.java index c356275d..da45a314 100644 --- a/src/main/java/markets/HouseRentalMarket.java +++ b/src/main/java/markets/HouseRentalMarket.java @@ -26,18 +26,18 @@ public void completeTransaction(HouseBuyerRecord purchase, HouseSaleRecord sale) } @Override - public HouseSaleRecord offer(House house, double price) { + public HouseSaleRecord submitOffer(House house, double price) { if(house.isOnMarket()) { - System.out.println("Got offer on rental market of house already on sale market"); + System.out.println("Got submitOffer on rental market of house already on sale market"); } - HouseSaleRecord hsr = super.offer(house, price); + HouseSaleRecord hsr = super.submitOffer(house, price); house.putForRent(hsr); return(hsr); } @Override - public void removeOffer(HouseSaleRecord hsr) { - super.removeOffer(hsr); + public void cancelOffer(HouseSaleRecord hsr) { + super.cancelOffer(hsr); hsr.house.resetRentalRecord(); } } diff --git a/src/main/java/markets/HouseSaleMarket.java b/src/main/java/markets/HouseSaleMarket.java index 85c2e2b0..657f602b 100644 --- a/src/main/java/markets/HouseSaleMarket.java +++ b/src/main/java/markets/HouseSaleMarket.java @@ -42,19 +42,28 @@ public void completeTransaction(HouseBuyerRecord purchase, HouseSaleRecord sale) sale.house.owner = buyer; } - @Override - public HouseSaleRecord offer(House house, double price) { - HouseSaleRecord hsr = super.offer(house, price); - offersPY.add(hsr); - house.putForSale(hsr); - return(hsr); - } - - @Override - public void removeOffer(HouseSaleRecord hsr) { - super.removeOffer(hsr); - offersPY.remove(hsr); - hsr.house.resetSaleRecord(); + @Override + public void cancelOffer(HouseSaleRecord hsr) { + super.cancelOffer(hsr); + offersPY.remove(hsr); + hsr.house.resetSaleRecord(); + } + + /******************************************* + * Make a submitBid on the market as a Buy-to-let investor + * (i.e. make an submitOffer on a (yet to be decided) house). + * + * @param buyer The household that is making the submitBid. + * @param maxPrice The maximum price that the household is willing to pay. + ******************************************/ + void BTLbid(Household buyer, double maxPrice) { bids.add(new BTLBuyerRecord(buyer, maxPrice)); } + + @Override + public HouseSaleRecord submitOffer(House house, double price) { + HouseSaleRecord record = super.submitOffer(house, price); + offersPY.add(record); + house.putForSale(record); + return record; } @Override @@ -107,13 +116,4 @@ void removeOfferFromQueues(Iterator record, HouseSaleRecord offersPY.remove(offer); } - /******************************************* - * Make a bid on the market as a Buy-to-let investor - * (i.e. make an offer on a (yet to be decided) house). - * - * @param buyer The household that is making the bid. - * @param maxPrice The maximum price that the household is willing to pay. - ******************************************/ - void BTLbid(Household buyer, double maxPrice) { bids.add(new BTLBuyerRecord(buyer, maxPrice)); } - } diff --git a/src/main/java/markets/HouseSaleRecord.java b/src/main/java/markets/HouseSaleRecord.java index 5ba9d979..42894fed 100644 --- a/src/main/java/markets/HouseSaleRecord.java +++ b/src/main/java/markets/HouseSaleRecord.java @@ -1,6 +1,7 @@ package markets; import housing.House; +import housing.Household; import housing.Model; import java.util.ArrayList; @@ -31,11 +32,11 @@ public class HouseSaleRecord extends HousingMarketRecord { /** * Construct a new record - * - * @param h The house that is for sale + * @param household + * @param house The house that is for sale * @param price The initial list price for the house */ - public HouseSaleRecord(House h, double price) { + private HouseSaleRecord(Household household, House house, double price) { super(price); house = h; initialListedPrice = price; @@ -44,6 +45,10 @@ public HouseSaleRecord(House h, double price) { recalculateHouseSpecificYield(price); } + public static HouseSaleRecord from(Offer offer) { + return new HouseSaleRecord(offer.getHousehold(), offer.getHouse(), offer.getPrice()); + } + //-------------------// //----- Methods -----// //-------------------// @@ -63,9 +68,9 @@ private void recalculateHouseSpecificYield(double price) { } /** - * Record the match of the offer of this property with a bid + * Record the match of the submitOffer of this property with a submitBid * - * @param bid The bid being matched to the offer + * @param bid The submitBid being matched to the submitOffer */ void matchWith(HouseBuyerRecord bid) { matchedBids.add(bid); } diff --git a/src/main/java/markets/HousingMarket.java b/src/main/java/markets/HousingMarket.java index af605e26..99bbbb4c 100644 --- a/src/main/java/markets/HousingMarket.java +++ b/src/main/java/markets/HousingMarket.java @@ -17,6 +17,7 @@ * *************************************************************************************************/ public abstract class HousingMarket implements Serializable { + private static final long serialVersionUID = -7249221876467520088L; //------------------// @@ -25,17 +26,19 @@ public abstract class HousingMarket implements Serializable { private static Authority authority = new Authority(); - private Config config = Model.config; // Passes the Model's configuration parameters object to a private field - private MersenneTwister rand = Model.rand; // Passes the Model's random number generator to a private field - private PriorityQueue2D offersPQ; - - ArrayList bids; + private Config config; + private MersenneTwister prng; + private PriorityQueue2D offersPQ; + private ArrayList bids; //------------------------// //----- Constructors -----// //------------------------// - HousingMarket() { + HousingMarket(Config config, MersenneTwister prng) { + this.config = config; + this.prng = prng; + offersPQ = new PriorityQueue2D<>(new HousingMarketRecord.PQComparator()); //Priority Queue of (Price, Quality) // The integer passed to the ArrayList constructor is an initially declared capacity (for initial memory // allocation purposes), it will actually have size zero and only grow by adding elements @@ -62,20 +65,42 @@ private Authority() {} public void init() { offersPQ.clear(); } //----- Methods to add, update, remove offers and bids -----// - + + // TODO: I guess that bids are never cancelled? + + /** + * Take a house off the market + * + * @param hsr The HouseSaleRecord of the house to take off the market + */ + public void cancelOffer(HouseSaleRecord hsr) { offersPQ.remove(hsr); } + /** - * Put a new offer on the market + * Submit a bid to buy a (yet to be decided) house). * - * @param house House to put on the market - * @param price List price for the house + * @param bid an offer by a particular household to purchase a yet to be determined house. + * @return + */ + public HouseBuyerRecord submitBid(Bid bid) { + HouseBuyerRecord record = HouseBuyerRecord.from(bid); + bids.add(record); // SIDE EFFECT!! + return record; + } + + /** + * Submit an offer to sell a house. + * + * @param offer * @return HouseSaleRecord for the house */ - public HouseSaleRecord offer(House house, double price) { - HouseSaleRecord hsr = new HouseSaleRecord(house, price); - offersPQ.add(hsr); - return hsr; + public HouseSaleRecord submitOffer(Offer offer) { + HouseSaleRecord record = HouseSaleRecord.from(offer); + offersPQ.add(record); // SIDE EFFECT!! + return record; } - + + // TODO: I guess that Bids are never updated? + /** * Change the list-price on a house that is already on the market * @@ -87,22 +112,6 @@ public void updateOffer(HouseSaleRecord hsr, double newPrice) { hsr.setPrice(newPrice, authority); offersPQ.add(hsr); } - - /** - * Take a house off the market - * - * @param hsr The HouseSaleRecord of the house to take off the market - */ - public void removeOffer(HouseSaleRecord hsr) { offersPQ.remove(hsr); } - - /** - * Make a bid on the market (i.e. make an offer on a (yet to be decided) house - * - * @param buyer The household that is making the bid - * @param price The price that the household is willing to pay - */ - public void bid(Household buyer, double price) { - bids.add(new HouseBuyerRecord(buyer, price)); } //----- Market clearing methods -----// @@ -137,9 +146,9 @@ private void matchBidsWithOffers() { HouseSaleRecord offer; for(HouseBuyerRecord bid : bids) { offer = getBestOffer(bid); - // If buyer and seller is the same household, then the bid falls through and the household will need to - // reissue it next month. Also, if the bid price is not enough to buy anything in this market and at this - // time, the bid also falls through + // If buyer and seller is the same household, then the submitBid falls through and the household will need to + // reissue it next month. Also, if the submitBid price is not enough to buy anything in this market and at this + // time, the submitBid also falls through if(offer != null && (offer.house.owner != bid.buyer)) { offer.matchWith(bid); } @@ -150,8 +159,8 @@ private void matchBidsWithOffers() { } /** - * Second step to clear the market. Iterate through all offers and, for each offer, loop through its matched bids. - * If BIDUP is activated, the offer price is bid up according to a geometric distribution with mean dependent on the + * Second step to clear the market. Iterate through all offers and, for each submitOffer, loop through its matched bids. + * If BIDUP is activated, the submitOffer price is submitBid up according to a geometric distribution with mean dependent on the * number of matched bids. */ private void clearMatches() { @@ -167,9 +176,9 @@ private void clearMatches() { while(record.hasNext()) { offer = (HouseSaleRecord)record.next(); nBids = offer.matchedBids.size(); - // If matches for this offer are multiple... + // If matches for this submitOffer are multiple... if(nBids > 1) { - // ...first bid up the price + // ...first submitBid up the price if(config.BIDUP > 1.0) { // TODO: All this enough bids mechanism is not explained! The 10000/N factor, the 0.5 added, and the // TODO: topping of the function at 4 are not declared in the paper. Remove or explain! @@ -181,23 +190,23 @@ private void clearMatches() { } else { salePrice = offer.getPrice(); } - // ...then choose a bid above the new price + // ...then choose a submitBid above the new price offer.matchedBids.sort(new HouseBuyerRecord.PComparator()); // This orders the list with the highest price last while(nBids > 0 && offer.matchedBids.get(nBids - 1).getPrice() >= salePrice) { --nBids; // This counts the number of bids above the new price } if (offer.matchedBids.size() - nBids > 1) { - winningBid = nBids + rand.nextInt(offer.matchedBids.size()- nBids); // This chooses a random one if they are multiple + winningBid = nBids + prng.nextInt(offer.matchedBids.size()- nBids); // This chooses a random one if they are multiple } else if (offer.matchedBids.size() - nBids == 1) { winningBid = nBids; // This chooses the only one if there is only one } else { winningBid = nBids - 1; - salePrice = offer.matchedBids.get(winningBid).getPrice(); // This chooses the highest bid if all of them are below the new price + salePrice = offer.matchedBids.get(winningBid).getPrice(); // This chooses the highest submitBid if all of them are below the new price } - // Remove this offer from the offers priority queue, offersPQ, underlying the record iterator (and, for HouseSaleMarket, also from the PY queue) - // Note that this needs to be done before modifying offer, so that it can be also found in the PY queue for the HouseSaleMarket case + // Remove this submitOffer from the offers priority queue, offersPQ, underlying the record iterator (and, for HouseSaleMarket, also from the PY queue) + // Note that this needs to be done before modifying submitOffer, so that it can be also found in the PY queue for the HouseSaleMarket case removeOfferFromQueues(record, offer); - // ...update price for the offer + // ...update price for the submitOffer offer.setPrice(salePrice, authority); // ...complete successful transaction and record it into the corresponding housingMarketStats completeTransaction(offer.matchedBids.get(winningBid), offer); @@ -208,7 +217,7 @@ private void clearMatches() { } else if (nBids == 1) { // ...complete successful transaction and record it into the corresponding housingMarketStats completeTransaction(offer.matchedBids.get(0), offer); - // ...remove this offer from the offers priority queue, offersPQ, underlying the record iterator (and, for HouseSaleMarket, also from the PY queue) + // ...remove this submitOffer from the offers priority queue, offersPQ, underlying the record iterator (and, for HouseSaleMarket, also from the PY queue) removeOfferFromQueues(record, offer); } // Note that we skip the whole process if there are no matches @@ -230,8 +239,8 @@ void removeOfferFromQueues(Iterator record, HouseSaleRecord * This abstract method allows for the different implementations at HouseSaleMarket and HouseRentalMarket to be * called as appropriate * - * @param purchase HouseBuyerRecord with information on the offer - * @param sale HouseSaleRecord with information on the bid + * @param purchase HouseBuyerRecord with information on the submitOffer + * @param sale HouseSaleRecord with information on the submitBid */ public abstract void completeTransaction(HouseBuyerRecord purchase, HouseSaleRecord sale); @@ -244,7 +253,7 @@ void removeOfferFromQueues(Iterator record, HouseSaleRecord Iterator getOffersIterator() { return(offersPQ.iterator()); } /** - * Get the highest quality house being offered for a price up to that of the bid (OfferPrice <= bidPrice) + * Get the highest quality house being offered for a price up to that of the submitBid (OfferPrice <= bidPrice) * * @param bid The highest possible price the buyer is ready to pay */ diff --git a/src/main/java/markets/HousingMarketRecord.java b/src/main/java/markets/HousingMarketRecord.java index 90e45916..c7ecfc46 100644 --- a/src/main/java/markets/HousingMarketRecord.java +++ b/src/main/java/markets/HousingMarketRecord.java @@ -6,14 +6,15 @@ /************************************************************************************************** * Root class to encapsulate information on housing market transactions, both offers and bids. Both - * HouseSaleRecord, with information on the offer/seller, and HouseBuyerRecord, with information on - * the bid/buyer, will extend this class. Notably, the comparators for the ordering of both + * HouseSaleRecord, with information on the submitOffer/seller, and HouseBuyerRecord, with information on + * the submitBid/buyer, will extend this class. Notably, the comparators for the ordering of both * priority queues (price-quality, price-yield) are implemented here. * * @author daniel, Adrian Carro * *************************************************************************************************/ -public abstract class HousingMarketRecord implements Serializable { +abstract class HousingMarketRecord implements Serializable { + private static final long serialVersionUID = 942379254469390885L; //------------------// @@ -130,4 +131,5 @@ public double getPrice() { public void setPrice(double newPrice, HousingMarket.Authority auth) { price = newPrice; } + } diff --git a/src/main/java/markets/Offer.java b/src/main/java/markets/Offer.java new file mode 100644 index 00000000..cbd2fdd6 --- /dev/null +++ b/src/main/java/markets/Offer.java @@ -0,0 +1,31 @@ +package markets; + + +import housing.House; +import housing.Household; + + +/** Represents an offer by a specific household to sell a particular house at some price. */ +public class Offer extends Order { + + private House house; + + Offer(Household household, House house, double price) { + this.household = household; + this.house = house; + this.price = price; + } + + public Household getHousehold() { + return household; + } + + public House getHouse() { + return house; + } + + public double getPrice() { + return price; + } + +} diff --git a/src/main/java/markets/Order.java b/src/main/java/markets/Order.java new file mode 100644 index 00000000..f4ac13ea --- /dev/null +++ b/src/main/java/markets/Order.java @@ -0,0 +1,20 @@ +package markets; + + +import housing.Household; + + +class Order { + + Household household; + double price; + + public Household getHousehold() { + return household; + } + + public double getPrice() { + return price; + } + +} diff --git a/src/main/resources/config.properties b/src/main/resources/config.properties index f633bb4b..c295ca17 100644 --- a/src/main/resources/config.properties +++ b/src/main/resources/config.properties @@ -37,7 +37,7 @@ N_QUALITY = 48 ########### Housing market parameters ############ ################################################## -# Time, in days, that a house remains under offer (int) +# Time, in days, that a house remains under submitOffer (int) DAYS_UNDER_OFFER = 7 # Smallest proportional increase in price that can cause a gazump (double) BIDUP = 1.0075