Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Refactoring all markets related code #54

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions src/main/java/collectors/HousingMarketStats.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**************************************************************************************************
Expand All @@ -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
Expand Down Expand Up @@ -169,15 +173,15 @@ 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();
bidPrices[i] = bid.getPrice();
++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();
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/collectors/MicroDataRecorder.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/collectors/RentalMarketStats.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package collectors;

import housing.*;
import markets.HouseRentalMarket;
import markets.HouseSaleRecord;

import java.util.Arrays;

Expand Down
4 changes: 2 additions & 2 deletions src/main/java/housing/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/housing/Construction.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package housing;

import markets.HouseSaleRecord;
import org.apache.commons.math3.random.MersenneTwister;

import java.io.Serializable;
Expand Down Expand Up @@ -41,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);
Expand All @@ -66,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);
Expand Down
5 changes: 4 additions & 1 deletion src/main/java/housing/House.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package housing;

import markets.HouseSaleRecord;

import java.io.Serializable;

/**************************************************************************************************
Expand All @@ -21,7 +23,7 @@ public class House implements Comparable<House>, Serializable {
public Household resident;
public int id;

HouseSaleRecord saleRecord;
HouseSaleRecord saleRecord;
HouseSaleRecord rentalRecord;

private int quality;
Expand Down Expand Up @@ -53,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; }
Expand Down
35 changes: 18 additions & 17 deletions src/main/java/housing/Household.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import java.util.Map.Entry;
import java.util.TreeMap;

import markets.HouseSaleRecord;
import org.apache.commons.math3.random.MersenneTwister;

/**************************************************************************************************
Expand Down Expand Up @@ -161,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
Expand All @@ -176,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);
}

Expand All @@ -207,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));
}

/////////////////////////////////////////////////////////
Expand Down Expand Up @@ -251,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;
}
Expand All @@ -265,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) {
Expand Down Expand Up @@ -299,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));
}

/**********************************************************
Expand Down Expand Up @@ -356,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
Expand All @@ -369,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));
}
}

Expand All @@ -398,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();
}
Expand Down Expand Up @@ -439,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 {
Expand Down Expand Up @@ -485,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
Expand Down
5 changes: 3 additions & 2 deletions src/main/java/housing/HouseholdBehaviour.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -96,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
*/
Expand Down Expand Up @@ -223,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) {
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/housing/IHouseOwner.java
Original file line number Diff line number Diff line change
@@ -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
Expand Down
8 changes: 5 additions & 3 deletions src/main/java/housing/Model.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -129,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();

Expand Down Expand Up @@ -183,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();
Expand Down
68 changes: 68 additions & 0 deletions src/main/java/markets/AltHousingMarket.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package markets;


import org.apache.commons.math3.random.MersenneTwister;

import java.util.*;

public class AltHousingMarket {

private MersenneTwister prng;
private List<HouseBuyerRecord> buyerRecords;
private HashSet<HouseSaleRecord> sellerRecords;
private HashMap<HouseSaleRecord, List<HouseBuyerRecord>> 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<HouseSaleRecord> affordableListings = new HashSet<>();
for (HouseSaleRecord sellerRecord : sellerRecords) {
if (sellerRecord.getPrice() <= buyerRecord.getPrice()) {
affordableListings.add(sellerRecord);
}
}
HouseSaleRecord highestQualityAffordableListing = Collections.max(affordableListings, ???);
HashSet<HouseBuyerRecord> existingMatchedRecords = potentialMatchedRecords.getOrDefault(highestQualityAffordableListing, new HashSet<>());
existingMatchedRecords.add(buyerRecord);
}
}

private HashMap<HouseBuyerRecord, HouseSaleRecord> finalizePotentialMatches() {
HashMap<HouseBuyerRecord, HouseSaleRecord> finalizedMatchedRecords = new HashMap<>();
for (HouseSaleRecord sellerRecord : sellerRecords) {
List<HouseBuyerRecord> 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;
}

}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down
Loading