class World(): def __init__(self): self.cities = [City(i) for i in range(N_CITIES)] self.goodMarket = GoodMarket() self.forexMarket = ForexMarket() self.calendar = Calendar() def evolve(self): ''' evolve one day in the world, which includes all the activities of all agents in a cycle return None ''' global_ask_order = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] global_bid_order = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] for city in self.cities: city.firms_decide_budget(self.goodMarket.prices, self.goodMarket.trends) for city in self.cities: city.firms_make_products() for city in self.cities: ask_order = city.firms_get_ask_orders() for i in range(N_CITIES): for j in range(N_GOODS): global_ask_order[i][j] += ask_order[i][j] for city in self.cities: bid_order = city.firms_get_bid_orders(self.goodMarket.prices, self.forexMarket.prices) for i in range(N_CITIES): for j in range(N_GOODS): global_bid_order[i][j] += bid_order[i][j] for city in self.cities: order = city.decide_purchase(self.goodMarket.prices) for k, v in order.items(): global_ask_order[v["city"]][k] += v["amount"] self.goodMarket.update_ask_transactions(global_ask_order) self.goodMarket.update_bid_transactions(global_bid_order) ask_ratios, bid_ratios = self.goodMarket.update_price() for city in self.cities: city.process_transactions(ask_ratios, bid_ratios, self.goodMarket.prices) # decide forex transactions # decide investment # decide employment for city in self.cities: city.firms_decide_employ() # city logistics # miscellaneous for city in self.cities: city.advance_day() self.goodMarket.advance_day() self.forexMarket.advance_day() self.calendar.advance_day() def output_price(self, f_price_hist): f_price_hist.write("{},{},{},{},{},{}\n".format( self.calendar.year, self.calendar.month, self.calendar.day, self.goodMarket.prices[0][WATER],self.goodMarket.prices[0][WHEAT],self.goodMarket.prices[0][BREAD] ))
class ForexMarket(): def __init__(self): self.prices = [[1.0 for i in range(N_CITIES)] for j in range(N_CITIES)] self.prices_ema9 = [[1.0 for i in range(N_CITIES)] for j in range(N_CITIES)] self.prices_ema21 = [[1.0 for i in range(N_CITIES)] for j in range(N_CITIES)] self.ask = [[0 for i in range(N_CITIES)] for j in range(N_CITIES)] self.bid = [[0 for i in range(N_CITIES)] for j in range(N_CITIES)] self.trends = [[0 for i in range(N_CITIES)] for j in range(N_CITIES)] # compute how much fraction can be bought, sold self.ask_ratios = [[0 for i in range(N_CITIES)] for j in range(N_CITIES)] self.bid_ratios = [[0 for i in range(N_CITIES)] for j in range(N_CITIES)] # internal calendar self.calendar = Calendar() def update_ask_transactions(self, transactions): ''' collect and record all ask orders transaction : list => a tuple contains 1. city_id of the firm 2. firm_id of the firm 3. city_id where the product is sold 4. the forex (id) to be bought 5. amount return None ''' # transaction contain # [0-2] self.city_id, self.firm_id, self.city_id # [3-4] forex_index, amount for i in range(N_CITIES): for j in range(N_CITIES): self.ask[i][j] = 0 for trans in transactions: self.ask[trans[2]][trans[3]] += trans[4] def update_bid_transactions(self, transactions): ''' collect and record all ask orders transaction : list => a tuple contains 1. city_id of the firm 2. firm_id of the firm 3. city_id where the product is sold 4. the forex (id) to be sold 5. amount return None ''' # transaction contain # [0-2] self.city_id, self.firm_id, self.city_id # [3-4] good_index, amount for i in range(N_CITIES): for j in range(N_CITIES): self.bid[i][j] = 0 for trans in transactions: self.bid[trans[2]][trans[3]] += trans[4] def update_price(self): ''' update the product prices based on non-equilibrium pricing based on the demand and supply of that round return self.ask_ratios: list => the fraction to be bought self.bid_ratios: list => the fraction to be sold ''' for i in range(N_CITIES): for j in range(N_CITIES): self.prices[i][j] *= exp((self.ask[i][j] - self.bid[i][j]) / (self.ask[i][j] + self.bid[i][j] + 1)) if self.ask[i][j] > self.bid[i][j]: self.ask_ratios[i][j] = self.bid[i][j] / self.ask[i][j] self.bid_ratios[i][j] = 1 elif self.ask[i][j] < self.bid[i][j]: self.ask_ratios[i][j] = 1 self.bid_ratios[i][j] = self.ask[i][j] / self.bid[i][j] else: self.ask_ratios[i][j] = 1 self.bid_ratios[i][j] = 1 self.prices_ema9[i][j] = 0.2 * self.prices[i][j] + 0.8 * self.prices_ema9[i][j] self.prices_ema21[i][j] = 0.099 * self.prices[i][j] + 0.901 * self.prices_ema21[i][j] if self.prices_ema9[i][j] > self.prices_ema21[i][j]: self.trends[i][j] = 1 elif self.prices_ema9[i][j] > self.prices_ema21[i][j]: self.trends[i][j] = -1 return self.ask_ratios, self.bid_ratios def advance_day(self): ''' advance to the next day return None ''' self.calendar.advance_day()
class Firm(): def __init__(self, city_id, firm_id, product): self.city_id = city_id self.firm_id = firm_id self.product = product self.risk_beta = uniform(0, 2) self.cash = [0 for i in range(N_CITIES)] self.salary_budget = [0 for i in range(N_CITIES)] self.purchase_budget = [0 for i in range(N_CITIES)] self.employ_budget = [0 for i in range(N_CITIES)] self.invest_budget = [0 for i in range(N_CITIES)] self.debt = [0 for i in range(N_CITIES)] # initial cash self.salary_budget[city_id] = INIT_FIRM_CASH self.employees = INIT_EMPLOYEES self.salary = INIT_SALARY self.productivity = PRODUCTIVITIES[self.product] self.ask_orders = {} self.bid_orders = {} self.ingredients = [] for i in range(N_GOODS): if INGREDIENTS[self.product][i] > 0: self.ingredients.append((i, INGREDIENTS[self.product][i])) self.ask_orders[i] = {} self.bid_orders[self.product] = {} self.inventory = [0 for i in range(N_GOODS)] for ingredient in self.ingredients: self.inventory[ingredient[ 0]] = self.employees * self.productivity * ingredient[1] self.change_prod = False self.only_domestic_sales = True self.calendar = Calendar() self.employ_date = randint(1, 20) def allocate_budget(self, prices, trends): ''' decides how much budget is allocated to (1) buying ingredient (2) salary (3) expansion and (4) investment prices : List => Containing prices of all products in all cities trends : List => Containing trends (if price is rising) of all products in all cities return None ''' x = max( self.employees * self.salary - self.salary_budget[self.city_id], 0) #x = round(x, 2) self.cash[self.city_id] -= x self.salary_budget[self.city_id] += x x = 0 for ingredient in self.ingredients: x = self.employees * self.productivity * prices[self.city_id][ ingredient[0]] #x = round(x, 2) self.cash[self.city_id] -= x self.purchase_budget[self.city_id] += x if self.cash[self.city_id] < 0 or ( self.cash[self.city_id] >= 0 and trends[self.city_id][self.product] > 0): x = self.cash[self.city_id] self.cash[self.city_id] -= x self.employ_budget[self.city_id] += x else: x = self.cash[self.city_id] self.cash[self.city_id] -= x self.invest_budget[self.city_id] += x #print("City{}-Firm{} allocate_budget: cash:{} salary:{} purchase:{} employ:{} invest:{}".format( # self.city_id, self.firm_id, self.cash[self.city_id], self.salary_budget[self.city_id], # self.purchase_budget[self.city_id], self.employ_budget[self.city_id], self.invest_budget[self.city_id] #)) def decide_prod_ask_order(self): ''' decide to purchase the ingredient from the market based on the daily production return ask_order : Dict => key = [product] subkey = [city, amount] ''' for ingredient in self.ingredients: self.ask_orders[ingredient[0]]["city"] = self.city_id self.ask_orders[ingredient[0]][ "amount"] = ingredient[1] * self.employees * self.productivity #print("City{}-Firm{} ask_order: {}".format( # self.city_id, self.firm_id, self.ask_orders #)) return self.ask_orders def decide_prod_bid_order(self, prices, rates): ''' decide how many products to be sold based on inventory prices : List => prices of all products in all cities rates : List => exchange rate of all currencies return ask_order : Dict => key = [product] subkey = [city, amount] ''' # decide which city to sell the product if self.only_domestic_sales: city_id = self.city_id self.bid_orders[self.product]["city"] = city_id self.bid_orders[self.product]["amount"] = self.inventory[self.product] #print("City{}-Firm{} bid_order: {}".format( # self.city_id, self.firm_id, self.bid_orders #)) return self.bid_orders def process_transactions(self, ask_ratios, bid_ratios, prices): ''' receive transaction result from market (through city) and complete the tranasaction record ask_ratios : List => fraction of received amount of products bid_ratios : List => fraction of sold amount of products return None ''' for k, v in self.ask_orders.items(): amt = v["amount"] * ask_ratios[v["city"]][k] * prices[v["city"]][k] #amt = round(amt, 2) self.inventory[k] += ask_ratios[v["city"]][k] * v["amount"] self.purchase_budget[v["city"]] -= amt for k, v in self.bid_orders.items(): amt = v["amount"] * bid_ratios[v["city"]][k] * prices[v["city"]][k] #amt = round(amt, 2) self.inventory[k] -= ask_ratios[v["city"]][k] * v["amount"] self.cash[v["city"]] += amt #print("City{}-Firm{} cash:{} inventory:{} Ask:{} Bid:{}".format( # self.city_id, self.firm_id, self.cash[self.city_id], self.inventory, self.ask_orders, self.bid_orders #)) def make_product(self): ''' make the product based on current ingredient inventory and workers available return tuple (firm's city id, amount of salary to pay to workers) ''' x = self.employees * self.productivity for ingredient in self.ingredients: x = min(x, self.inventory[ingredient[0]] / ingredient[1]) for ingredient in self.ingredients: self.inventory[ingredient[0]] -= ingredient[1] * x self.inventory[self.product] += x salary = self.employees * self.salary #salary = round(salary, 2) self.salary_budget[self.city_id] -= salary return (self.city_id, salary) def decide_employ(self): ''' decide employ or send away workers depending on the loan status return employment : tuple => (city_id, firm_id, city_to_pay, amount_to_pay, change in debt, change in labour workforce) ''' employment = None x = self.employees * self.salary * 21 y = x / (1 + self.risk_beta) if self.employ_budget[self.city_id] > y: debt = y * self.risk_beta self.debt[self.city_id] += debt self.employ_budget[self.city_id] -= y self.employees += 1 employment = (self.city_id, self.firm_id, self.city_id, x, debt, 1) elif self.employees > 1 and self.employ_budget[ self.city_id] < -(self.employees - 1) * y: unemployed = 1 payable = (self.employees - 1) * x for i in range(self.employees - 1, 1, -1): if payable > -self.employ_budget[self.city_id]: break unemployed += 1 payable += (i - 1) * x self.employees -= unemployed debt_reduction = payable * self.risk_beta / (1 + self.risk_beta) self.debt[self.city_id] -= debt_reduction self.employ_budget[self.city_id] += payable / (1 + self.risk_beta) employment = (self.city_id, self.firm_id, self.city_id, -payable, -debt_reduction, -unemployed) #if employment: # print("City{}-Firm{} employ:{} employment:{}".format( # self.city_id, self.firm_id, self.employ_budget[self.city_id], employment # )) return employment def change_production(self, rank_goods): ''' decide switch to other production line rank_goods : List => ranking of the products according to their productivity return None ''' if self.change_prod and self.employees == 1: cum_prod = 0.5 switched_job = False for i in range(N_GOODS): x = random() if x < 0.5: job = i self.product = rank_goods[self.city_id][job] switched_job = True break if switched_job: self.ask_orders.clear() self.bid_orders.clear() for i in range(self.ingredients): self.ingredients.pop() for i in range(N_GOODS): if INGREDIENTS[self.product] > 0: self.ingredients.append((i, INGREDIENTS[self.product])) self.ask_orders[i] = {} self.bid_orders[job] = {} def advance_day(self): self.calendar.advance_day()
class City(): def __init__(self, city_id): self.city_id = city_id self.citizens = INIT_CITIZENS self.employed = INIT_CITIZENS # instantiate and populate the firms idx = 0 self.firms = [] for i in range(N_GOODS): for j in range(INIT_FIRMS[i]): self.firms.append(Firm(self.city_id, idx, i)) idx += 1 self.cash = [0 for i in range(N_CITIES)] self.cash[self.city_id] = INIT_CITY_CASH self.liability = [0 for i in range(N_CITIES)] self.markup = BASE_MARKUP self.ref_salary = INIT_SALARY self.budget = 0 self.budget_ratio = [BUDGET_RATIO[i] for i in range(N_GOODS)] self.reserve = [0 for i in range(N_CITIES)] self.debt = [0 for i in range(N_CITIES)] self.ask_order = {} for i in range(N_GOODS): if BUDGET_RATIO[i] > 0: self.ask_order[i] = {} # internal calendar self.calendar = Calendar() def firms_get_ask_orders(self): ''' get from firm the ask orders for their purchase and add them up for the total demand return firms_ask: dict => key = [city][item], value = amount ''' firms_ask = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] for firm in self.firms: firm_orders = firm.decide_prod_ask_order() for k, v in firm_orders.items(): firms_ask[v["city"]][k] += v["amount"] return firms_ask def firms_get_bid_orders(self, prices, rates): ''' get from firm the ask orders for their purchase and add them up for the total supply return firms_ask: dict => key = [city][item] value=amount ''' firms_bid = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] for firm in self.firms: firm_orders = firm.decide_prod_bid_order(prices, rates) for k, v in firm_orders.items(): firms_bid[v["city"]][k] += v["amount"] return firms_bid def firms_make_products(self): ''' ask all the firms to make the products return None ''' for firm in self.firms: salary = firm.make_product() self.cash[salary[0]] += salary[1] def firms_decide_budget(self, prices, trends): ''' ask all the firms to allocate budget for buying ingredients, paying salary, expand and invest prices: list => the list containing prices from all cities of all products trends: list => the list containing whether the product is growing popular return None ''' for firm in self.firms: firm.allocate_budget(prices, trends) def decide_purchase(self, prices: list): ''' the state decide how much new liabilities (new money) to support the system and return the the dict for the ask order prices: list: the list containing all products price in all cities return ask_order: dict => key = [city][product] value=amount ''' # first print the money budget = self.ref_salary * self.citizens new_money = budget * self.markup #new_money = round(new_money, 2) self.liability[self.city_id] += new_money self.cash[self.city_id] += new_money budget += new_money if self.cash[self.city_id] > 0: for i in range(N_GOODS): if self.budget_ratio[i] > 0: x = int(budget * self.budget_ratio[i] / prices[self.city_id][i]) self.ask_order[i]["city"] = self.city_id self.ask_order[i]["amount"] = x print("City:{} Ask order:{} New money:{}".format( self.city_id, self.ask_order, new_money)) return self.ask_order def process_transactions(self, ask_ratios, bid_ratios, prices): ''' ask all firms to process the payment and delivery (recipient) of the bought and sold products. also resolve the state's purchase order ask_ratios: List => how much fraction of good can be received from purchase bid_ratios: List => how much fraction of good can be sold in selling prices: List => the list containing prices of all products in all cities return None ''' for firm in self.firms: firm.process_transactions(ask_ratios, bid_ratios, prices) for k, v in self.ask_order.items(): amt = v["amount"] * ask_ratios[v["city"]][k] * prices[v["city"]][k] #amt = round(amt, 2) self.cash[v["city"]] -= amt def population_growth(self): ''' the population grows assuming some simple birth rates from statistics return None ''' annual_salary = 252 * self.ref_salary self.citizens += self.reserve / annual_salary def firms_decide_employ(self): ''' the firm decides how many new employees to be employed or how many employees to resign due to shortage of budget return None ''' for firm in self.firms: if firm.employ_date == self.calendar.day: employment = firm.decide_employ() if employment: self.reserve[self.city_id] += employment[3] self.debt[self.city_id] += employment[4] self.employed += employment[5] def grow_new_firm(self, rank_goods: list): ''' generate new firms with the product selected by how popular the product is return None ''' n_new_firms = int(len(self.firms) * 0.01) for i in range(n_new_firms): found_job = False for j in range(N_GOODS): x = random() if x < 0.5: self.product = rank_goods[self.city_id][j] job = j found_job = True break if not found_job: job = rank_goods[self.city_id][-1] self.firms.append(Firm(self.city_id, len(self.firms), job)) def advance_day(self): ''' advance the calendar day return None ''' self.calendar.advance_day() for firm in self.firms: firm.advance_day()
class GoodMarket(): def __init__(self): self.prices = [[INIT_PRICES[i] for i in range(N_GOODS)] for j in range(N_CITIES)] self.prices_ema9 = [[INIT_PRICES[i] for i in range(N_GOODS)] for j in range(N_CITIES)] self.prices_ema21 = [[INIT_PRICES[i] for i in range(N_GOODS)] for j in range(N_CITIES)] self.ask = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] self.bid = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] self.trends = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] # compute how much fraction can be bought, sold self.ask_ratios = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] self.bid_ratios = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] self.ref_salary = [INIT_SALARY for i in range(N_CITIES)] self.ref_productivity = [PRODUCTIVITIES[i] for i in range(N_GOODS)] # ranking of products self.ranks = [[0 for i in range(N_GOODS)] for j in range(N_CITIES)] # internal calendar self.calendar = Calendar() def update_ask_transactions(self, transactions): ''' collect and record all ask orders transaction : list => a tuple contains 1. city_id of the firm 2. firm_id of the firm 3. city_id where the product is sold 4. the product (id) to be sold 5. amount return None ''' # transaction contain # [0-2] self.city_id, self.firm_id, self.city_id # [3-4] good_index, amount for i in range(N_CITIES): for j in range(N_GOODS): self.ask[i][j] = transactions[i][j] def update_bid_transactions(self, transactions): ''' collect and record all bid orders transaction : list => a tuple contains 1. city_id of the firm 2. firm_id of the firm 3. city_id where the product is sold 4. the product (id) to be sold 5. amount return None ''' # transaction contain # [0-2] self.city_id, self.firm_id, self.city_id # [3-4] good_index, amount for i in range(N_CITIES): for j in range(N_GOODS): self.bid[i][j] = transactions[i][j] def update_price(self): ''' update the product prices based on non-equilibrium pricing based on the demand and supply of that round return self.ask_ratios: list => the fraction to be bought self.bid_ratios: list => the fraction to be sold ''' for i in range(N_CITIES): for j in range(N_GOODS): self.prices[i][j] *= exp(0.05 * (self.ask[i][j] - self.bid[i][j]) / (self.ask[i][j] + self.bid[i][j] + 1)) #self.prices[i][j] = round(self.prices[i][j], 2) if self.ask[i][j] > self.bid[i][j]: self.ask_ratios[i][j] = self.bid[i][j] / self.ask[i][j] self.bid_ratios[i][j] = 1 elif self.ask[i][j] < self.bid[i][j]: self.ask_ratios[i][j] = 1 self.bid_ratios[i][j] = self.ask[i][j] / self.bid[i][j] else: self.ask_ratios[i][j] = 1 self.bid_ratios[i][j] = 1 self.prices_ema9[i][ j] = 0.2 * self.prices[i][j] + 0.8 * self.prices_ema9[i][j] self.prices_ema21[i][j] = 0.099 * self.prices[i][ j] + 0.901 * self.prices_ema21[i][j] if self.prices_ema9[i][j] > self.prices_ema21[i][j]: self.trends[i][j] = 1 elif self.prices_ema9[i][j] > self.prices_ema21[i][j]: self.trends[i][j] = -1 print("Updated price:{} ask:{} bid{}".format(self.prices, self.ask, self.bid)) return self.ask_ratios, self.bid_ratios def rank_goods(self): ''' order the product according to their profitability i.e. how much profit for selling one item return None ''' # compute the market mark up costs = [[ self.ref_salary[j] / self.ref_productivity[i] for i in range(N_GOODS) ] for j in range(N_CITIES)] for i in range(N_CITIES): for j1 in range(N_GOODS): for j2 in range(N_GOODS): costs[i][j1] += costs[i][j2] * INGREDIENTS[j1][j2] markup = [[self.price[i][j] / costs[i][j] for j in range(N_GOODS)] for i in range(N_CITIES)] for i in range(N_CITIES): ranks = np.argsort(np.array(self.costs[i]))[::-1] for j in range(N_GOODS): self.rank_goods[i][j] = ranks[j] def advance_day(self): ''' advance to the next day return None ''' self.calendar.advance_day()