def equation(x_var): """ local moments condition equation """ model = DiscreteRV(x=x_var[0:k], w=np.append(x_var[k:], 1 - np.sum(x_var[k:]))) return model.moment(num) - moments
def mom_symbol(self, moments): """ Symbolic solver for ordinary method of moments find k-point representative distribution Args: moments: a sequence of estimated moments, starting from degree 1 only use first 2k-1 moments num_comps: number of atoms in the output RV Returns: U(finiteRV): estimated RV, whose first 2k-1 moments equal m """ num = 2 * self.k - 1 assert len(moments) >= num p_var = sympy.symbols('p0:%d' % (self.k), nonnegative=True, real=True) x_var = sympy.symbols('x0:%d' % (self.k), real=True) eqn = -1 for i in range(self.k): eqn = eqn + p_var[i] equations = [eqn] for i in range(num): eqn = -moments[i] ########### truncate a real number to rational # eq = -nsimplify(m[i], tolerance=0.1) # eq = float('%.2g' % -m[i]) for j in range(self.k): eqn = eqn + p_var[j] * (x_var[j]**(i + 1)) equations.append(eqn) var = [x for xs in zip(p_var, x_var) for x in xs] # format: [p1,x1,p2,x2,...] s_var = sympy.solve(equations, var) # s = solveset(equations,list(p)+list(x)) # s = nonlinsolve(equations,list(p)+list(x)) ########## some other tools in SymPy # print(equations) # s = solve_poly_system(equations,list(p)+list(x)) # s = solve_triangulated(equations,list(p)+list(x)) # s = solve_triangulated(equations,p[0],p[1],x[0],x[1]) ########## test over simple rational coefficients # testEq = [x[0]*p[0] - 2*p[0], 2*p[0]**2 - x[0]**2] # s = solve_triangulated(testEq, x[0], p[0]) if len(s_var) == 0: return DiscreteRV(w=[], x=[]) else: return DiscreteRV(w=s_var[0][0::2], x=s_var[0][1::2])
def mom_numeric(self, moments, start): """ numerical solver for ordinary method of moments find k-point representative distribution Args: moments: a sequence of estimated moments, starting from degree 1 only use first 2k-1 moments start: initial guess. k = the number of components in start. Returns: U(finiteRV): estimated RV """ k = len(start.p) assert k == self.k num = 2 * k - 1 assert len(moments) >= num moments = moments[0:num] def equation(x_var): """ local moments condition equation """ model = DiscreteRV(x=x_var[0:k], w=np.append(x_var[k:], 1 - np.sum(x_var[k:]))) return model.moment(num) - moments x0_var = np.concatenate((start.x, start.p[0:-1])) x_var = scipy.optimize.fsolve(equation, x0_var) # x = scipy.optimize.broyden2(equation, x0) return DiscreteRV(x=x_var[0:k], w=np.append(x_var[k:], 1 - np.sum(x_var[k:])))
def mc_sample_path(P, init=0, sample_size=1000): # === set up array to store output === # X = np.empty(sample_size, dtype=int) if isinstance(init, int): X[0] = init else: X[0] = DiscreteRV(init).draw() # === turn each row into a distribution === # # In particular, let P_dist[i] be the distribution corresponding to the # i-th row P[i,:] n = len(P) P_dist = [DiscreteRV(P[i, :]) for i in range(n)] # === generate the sample path === # for t in range(sample_size - 1): X[t+1] = P_dist[X[t]].draw() return X
def quadmom(moments, dettol=0, inf=1e10): """ compute quadrature from moments ref: Gene Golub, John Welsch, Calculation of Gaussian Quadrature Rules Args: m: moments sequence dettol: tolerant of singularity of moments sequence (quantified by determinant of moment matrix) INF: infinity Returns: U: quadrature """ moments = np.asarray(moments) # INF = float('inf') inf = 1e10 if len(moments) % 2 == 1: moments = np.append(moments, inf) num = int(len(moments) / 2) moments = np.insert(moments, 0, 1) h_mat = hankel(moments[:num + 1:], moments[num::]) # Hankel matrix for i in range(len(h_mat)): # check positive definite and decide to use how many moments if np.linalg.det( h_mat[0:i + 1, 0:i + 1]) <= dettol: # alternative: less than some threshold h_mat = h_mat[0:i + 1, 0:i + 1] h_mat[i, i] = inf num = i break r_mat = np.transpose( np.linalg.cholesky(h_mat)) # upper triangular Cholesky factor # Compute alpha and beta from r, using Golub and Welsch's formula. alpha = np.zeros(num) alpha[0] = r_mat[0][1] / r_mat[0][0] for i in range(1, num): alpha[i] = r_mat[i][i + 1] / r_mat[i][i] - r_mat[i - 1][i] / r_mat[ i - 1][i - 1] beta = np.zeros(num - 1) for i in range(num - 1): beta[i] = r_mat[i + 1][i + 1] / r_mat[i][i] jacobi = np.diag(alpha, 0) + np.diag(beta, 1) + np.diag(beta, -1) eigval, eigvec = np.linalg.eig(jacobi) atoms = eigval weights = moments[0] * np.power(eigvec[0], 2) return DiscreteRV(w=weights, x=atoms)
def __init__(self, agents, D, D_probs, aggregate_asset_endowment=1.0, seed=None, lucas_tree=False): ''' Market simply consists of collection of agents. lucas_tree: True means agents get same allocation of xi0 every morning ''' self.seed=seed self.RNG = np.random.RandomState(self.seed) self.lucas_tree = lucas_tree self.agents = agents self.price_history = [] self.volume_history = [] self.payoff_history = [] self.bilateral_price_history = [] self.bilateral_volume_history = [] self.bilateral_trade_partner_history = [] self.total_agent_cash_wealth_history = [] self.total_agent_initial_asset_endowment = [] #self.total_agent_asset_volume = [] self.aggregate_asset_endowment = aggregate_asset_endowment # Set up storage for agent values: self.agents_history = [] # Store agent variables here. for i in range(len(self.agents)): temp_storage = {'y':[],'c':[],'xi':[]} self.agents_history.append(temp_storage) # To find reasonable "first guess" price, find the risk-neutral asset # asset price for first-agent's preferences: agent0 = self.agents[0] self.p0_guess = agent0.beta * np.dot(agent0.D, agent0.D_probs) if self.p0_guess <= 0.0: self.p0_guess = 1e-5 self.p_tm1 = self.p0_guess # Set up the dividend process: # Ensure that dividend payoffs are sorted from smallest to largest: self.D = np.array(D) self.D_probs = np.array(D_probs) ii = np.argsort(self.D) self.D = self.D[ii] self.D_probs = self.D_probs[ii] # Quick check: assert np.isclose(np.sum(D_probs), 1.0), "Problem: p_dividend does not sum to 1.0: np.sum(D_probs) = " + str(np.sum(D_probs)) D_seed = self.RNG.randint(low=0, high=(2**31)-1) self.D_process = DiscreteRV(self.D_probs, self.D, seed=D_seed)
def mc_sample_path(P, init=0, sample_size=1000): """ Generates one sample path from a finite Markov chain with (n x n) Markov matrix P on state space S = {0,...,n-1}. Parameters ========== P : A nonnegative 2D NumPy array with rows that sum to 1 init : Either an integer in S or a nonnegative array of length n with elements that sum to 1 sample_size : int If init is an integer, the integer is treated as the determinstic initial condition. If init is a distribution on S, then X_0 is drawn from this distribution. Returns ======== A NumPy array containing the sample path """ # === set up array to store output === # X = np.empty(sample_size, dtype=int) if isinstance(init, int): X[0] = init else: X[0] = DiscreteRV(init).draw() # === turn each row into a distribution === # # In particular, let P_dist[i] be the distribution corresponding to the # i-th row P[i,:] n = len(P) P_dist = [DiscreteRV(P[i,:]) for i in range(n)] # === generate the sample path === # for t in range(sample_size - 1): X[t+1] = P_dist[X[t]].draw() return X
def std_rv(self): """ discrete rv for the sigmas """ return DiscreteRV(self.weights, self.sigma)
def mean_rv(self): """ discrete rv for the means """ return DiscreteRV(self.weights, self.centers)
def __init__(self, agents, D, D_probs, aggregate_asset_endowment=1.0, seed=None, lucas_tree=False): ''' Market simply consists of collection of agents. lucas_tree: True means agents get same allocation of xi0 every morning ''' self.seed = seed self.RNG = np.random.RandomState(self.seed) self.lucas_tree = lucas_tree self.agents = agents self.price_history = [] self.volume_history = [] self.payoff_history = [] self.bilateral_price_history = [] self.bilateral_volume_history = [] self.bilateral_trade_partner_history = [] self.total_agent_cash_wealth_history = [] self.total_agent_initial_asset_endowment = [] #self.total_agent_asset_volume = [] self.aggregate_asset_endowment = aggregate_asset_endowment # Set up storage for agent values: self.agents_history = [] # Store agent variables here. for i in range(len(self.agents)): temp_storage = {'y': [], 'c': [], 'xi': []} self.agents_history.append(temp_storage) # To find reasonable "first guess" price, find the risk-neutral asset # asset price for first-agent's preferences: agent0 = self.agents[0] self.p0_guess = agent0.beta * np.dot(agent0.D, agent0.D_probs) if self.p0_guess <= 0.0: self.p0_guess = 1e-5 self.p_tm1 = self.p0_guess # Set up the dividend process: # Ensure that dividend payoffs are sorted from smallest to largest: self.D = np.array(D) self.D_probs = np.array(D_probs) ii = np.argsort(self.D) self.D = self.D[ii] self.D_probs = self.D_probs[ii] # Quick check: assert np.isclose( np.sum(D_probs), 1.0 ), "Problem: p_dividend does not sum to 1.0: np.sum(D_probs) = " + str( np.sum(D_probs)) D_seed = self.RNG.randint(low=0, high=(2**31) - 1) self.D_process = DiscreteRV(self.D_probs, self.D, seed=D_seed)
class AssetMarket(object): def __init__(self, agents, D, D_probs, aggregate_asset_endowment=1.0, seed=None, lucas_tree=False): ''' Market simply consists of collection of agents. lucas_tree: True means agents get same allocation of xi0 every morning ''' self.seed = seed self.RNG = np.random.RandomState(self.seed) self.lucas_tree = lucas_tree self.agents = agents self.price_history = [] self.volume_history = [] self.payoff_history = [] self.bilateral_price_history = [] self.bilateral_volume_history = [] self.bilateral_trade_partner_history = [] self.total_agent_cash_wealth_history = [] self.total_agent_initial_asset_endowment = [] #self.total_agent_asset_volume = [] self.aggregate_asset_endowment = aggregate_asset_endowment # Set up storage for agent values: self.agents_history = [] # Store agent variables here. for i in range(len(self.agents)): temp_storage = {'y': [], 'c': [], 'xi': []} self.agents_history.append(temp_storage) # To find reasonable "first guess" price, find the risk-neutral asset # asset price for first-agent's preferences: agent0 = self.agents[0] self.p0_guess = agent0.beta * np.dot(agent0.D, agent0.D_probs) if self.p0_guess <= 0.0: self.p0_guess = 1e-5 self.p_tm1 = self.p0_guess # Set up the dividend process: # Ensure that dividend payoffs are sorted from smallest to largest: self.D = np.array(D) self.D_probs = np.array(D_probs) ii = np.argsort(self.D) self.D = self.D[ii] self.D_probs = self.D_probs[ii] # Quick check: assert np.isclose( np.sum(D_probs), 1.0 ), "Problem: p_dividend does not sum to 1.0: np.sum(D_probs) = " + str( np.sum(D_probs)) D_seed = self.RNG.randint(low=0, high=(2**31) - 1) self.D_process = DiscreteRV(self.D_probs, self.D, seed=D_seed) # Done def run_markets(self, T): ''' Run market for T periods. ''' # Draw dividend: D = self.D_process.draw(T) for t in range(T): # Clear markets: pstar = self.clear_market() # Update self.price_history.append(pstar) self.payoff_history.append(D[t]) # Get agent starting wealth, total agent initial asset endowment, # and total volume traded. # Important to do the following update before updating agent values: total_agent_initial_cash = 0.0 total_agent_initial_asset_endowment = 0.0 total_agent_asset_volume = 0.0 for agent in self.agents: total_agent_initial_cash += agent.y # Get wealth they started the period with total_agent_initial_asset_endowment += agent.xi0 total_agent_asset_volume += np.abs(agent.xi0 - agent.demand(pstar)) self.total_agent_cash_wealth_history.append( total_agent_initial_cash) self.total_agent_initial_asset_endowment.append( total_agent_initial_asset_endowment) self.volume_history.append(total_agent_asset_volume) # Now update agents and histories: self.update_agents(pstar=pstar, d=D[t]) # Done def excess_demand(self, p, total_supply, agents): ''' Iterate over all agents and ask for demand given price. ''' ''' if agents is None: agents = self.agents if total_supply is None: total_supply = self.aggregate_asset_endowment ''' total_demand = 0.0 for an_agent in agents: total_demand += an_agent.demand(p) return total_demand - total_supply def clear_market(self, these_agents=None, p0=None): ''' Given an initial price guess, zero-find on total asset demand. p0 is initial price. ''' if these_agents is None: these_agents = self.agents # Set intial price guess if p0 is None: p0 = self.p_tm1 # Note: currently not using first guess.... # Zero-find to determine price: supply_to_use = sum((agent.xi0 for agent in these_agents)) p, root_results = brentq(f=self.excess_demand, args=(supply_to_use, these_agents), a=0.0001, b=1000, full_output=True, disp=True) if not root_results.converged: print "WARNING: root_results.converged not True!: root_results.converged = ", root_results.converged print "root_results:", root_results return p def run_bilateral_markets(self, T): ''' Run market for T periods. ''' # Draw dividend: D = self.D_process.draw(T) for t in range(T): # Get agent starting wealth, total agent initial asset endowment # Important to do the following update before updating agent values: total_agent_initial_cash = 0.0 total_agent_initial_asset_endowment = 0.0 for agent in self.agents: total_agent_initial_cash += agent.y # Get wealth they started the period with total_agent_initial_asset_endowment += agent.xi0 self.total_agent_cash_wealth_history.append( total_agent_initial_cash) self.total_agent_initial_asset_endowment.append( total_agent_initial_asset_endowment) # Clear markets: volume_weighted_price, intra_prices, intra_volumes, intra_price_partners, agent_bilateral_holdings = self.bilateral_trade( ) # Update self.price_history.append(volume_weighted_price) self.bilateral_price_history = [deepcopy(intra_prices)] self.bilateral_volume_history = [deepcopy(intra_volumes)] self.bilateral_trade_partner_history = [ deepcopy(intra_price_partners) ] self.payoff_history.append(D[t]) # Update total volume traded. # Important to do the following update before updating agent values: total_agent_asset_volume = 0.0 self.volume_history.append(total_agent_asset_volume) # Now update agents and histories: self.update_agents_bilateral(d=D[t]) # Done def update_agents_intraperiod(self, these_agents, pstar): ''' Given pstar, update all agent histories. ''' for i, agent in enumerate(these_agents): # Determine and save choices: xi = agent.demand(pstar) # Determine new cash on hand: y_new = agent.y + pstar * (agent.xi0 - xi) # Determine cash "set aside" for consumption: ct = agent.y + pstar * agent.xi0 - pstar * xi # NOTE TODO Super important: confirm and figure out proper values here! # Update agent value: agent.y = y_new agent.xi0 = xi agent.update_utility_demand() agent.p_prev = pstar def update_agents_bilateral(self, d): ''' Given pstar, update all agent histories. Definitely need to work through. ''' for i, agent in enumerate(self.agents): # Determine and save choices: xi = agent.xi0 #ct = agent.y + pstar*agent.xi0 - pstar*xi ct = agent.y #print "new c=calc" self.agents_history[i]['c'].append(ct) self.agents_history[i]['xi'].append(xi) # Update agent value: agent.y += xi * d self.agents_history[i]['y'].append(agent.y) if not self.lucas_tree: agent.xi0 = xi agent.update_utility_demand() # Done def update_agents_d_only(self, d): ''' Given pstar, update all agent histories. ''' for i, agent in enumerate(these_agents): # Determine and save choices: xi = agent.demand(pstar) # Determine new cash on hand: y_new = agent.y + pstar * (agent.xi0 - xi) # Update agent value: agent.y = y_new agent.xi0 = xi agent.update_utility_demand() def confirm_trade(self, p, trading_agents): trade_occurred = True for an_agent in trading_agents: xi_new = an_agent.demand(p) trade_occurred = trade_occurred and not (np.isclose( xi_new, an_agent.xi0)) # so agents did not trade here return trade_occurred def bilateral_trade(self, upper_trade_limit=None): ''' Given a list of agents, pair up agents to trade until no trades are realized. ''' # Need to transfer the bilateral market code here. Then need to be careful about how agents are updated and how all of this is saved in market history. # Agent updates need to be carried out such that agents themselves are correctly advanced through their asset laws of motion. N = len(self.agents) n_to_trade = 2 # Construct a set of all possible n=2-combos of agents: set_of_all_combos = set( [frozenset(combo) for combo in combinations(range(N), n_to_trade)]) if upper_trade_limit is None: upper_trade_limit = max(10000, len(set_of_all_combos) * 5) intra_prices = [] intra_volumes = [] intra_price_partners = [] essentially_no_trade = set([]) ctr = 0 no_trade_ctr = 0 no_trade_rounds = 2 # How many rounds do we want to go past total no trade "just to check?" no_price_progress = False while ctr < upper_trade_limit and not ( no_price_progress) and no_trade_ctr < no_trade_rounds: # Update ctr: ctr += 1 # Randomly choose two agents, clear their own trades, and update them. # How: choose N, "order all," grab first two agent_indices = range(N) self.RNG.shuffle(agent_indices) trading_agent_indicies = agent_indices[:n_to_trade] trading_agents = [self.agents[i] for i in trading_agent_indicies] # Choose 2 and get price for clearing pstar = self.clear_market(these_agents=trading_agents) # Check to see if the agents actually trade at this price: trade_occurred = self.confirm_trade(pstar, trading_agents) if not trade_occurred: # Then add the pair to the list of "no trading pairs": essentially_no_trade.update( [frozenset(trading_agent_indicies)]) print "no trade occurred between", trading_agent_indicies else: print "trade occurred between", trading_agent_indicies # Ensure that agents who traded are removed from "no trade" set: essentially_no_trade -= set( [frozenset(trading_agent_indicies)]) # Record the "intra-day" prices, volume, and update agents: intra_prices.append(pstar) vol1 = np.abs(trading_agents[0].xi0 - trading_agents[0].demand(pstar)) for agent in trading_agents[1:]: vol2 = np.abs(agent.xi0 - agent.demand(pstar)) assert np.isclose( vol1, vol2 ), "Trading agents are not close! vol1, vol2 = " + str( [vol1, vol2]) vol2 = vol1 intra_volumes.append(vol1) intra_price_partners.append( deepcopy(agent_indices[:n_to_trade])) # Update agents: self.update_agents_intraperiod(trading_agents, pstar) # FINAL CHECK to see if any agents are if essentially_no_trade == set_of_all_combos: print "All agents not trading" no_trade_ctr += 1 # Manually loop over all agents and see if possible trading opportunities to exploit: for trade_pair in essentially_no_trade: # Run all possible trading; if a single trade is successful then # execute it and kick back out: trading_agent_indicies = trade_pair trading_agents = [ self.agents[i] for i in trading_agent_indicies ] # Choose 2 and get price for clearing pstar = self.clear_market(these_agents=trading_agents) # Check if any trade: trade_occurred = self.confirm_trade(pstar, trading_agents) if trade_occurred: # Execute the trade and kick back out no_trade_ctr = 0 # Reset to kick back out essentially_no_trade = set( []) #-= set([frozenset( trading_agent_indicies )]) # Record the "intra-day" prices and update agents: intra_prices.append(pstar) vol1 = np.abs(trading_agents[0].demand(pstar)) for agent in trading_agents[1:]: vol2 = np.abs(agent.demand(pstar)) #assert np.isclose(vol1, vol2), "Trading agents are not close! vol1, vol2 = " +str([vol1, vol2]) vol2 = vol1 intra_volumes.append(vol1) intra_price_partners.append( deepcopy(agent_indices[:n_to_trade])) # Update agents: self.update_agents_intraperiod(trading_agents, pstar) if no_trade_ctr > 0: print "Confirmed between all trading pairs that no bilateral trades are possible." if ctr == upper_trade_limit: raise Exception, "Allowable number of bilateral trades exceeded: ctr=" + str( ctr) + ", upper_trade_limit=" + str(upper_trade_limit) vol_weights = np.array(intra_prices) / float(np.sum(intra_prices)) volume_weighted_price = np.dot(vol_weights, intra_prices) #intra_prices #intra_volumes #intra_price_partners agent_bilateral_holdings = deepcopy( [agent.xi0 for agent in self.agents]) return volume_weighted_price, intra_prices, intra_volumes, intra_price_partners, agent_bilateral_holdings def clear_market2(self, alt_title=""): agents_to_use = [] for agent in self.agents: if agent.participate: agents_to_use.append(agent) supply_to_use = 0.0 #self.aggregate_asset_endowment for an_agent in agents_to_use: supply_to_use += an_agent.xi0 pstar, root_results = brentq(f=self.excess_demand, a=1e-6, b=2000, full_output=True, disp=True, args=(supply_to_use, agents_to_use)) if not root_results.converged: print "WARNING: root_results.converged not True!: root_results.converged = ", root_results.converged print "root_results:", root_results print "Market-clearing price for " + str( len(agents_to_use)) + " agents, total supply " + str( supply_to_use) + ":", pstar return pstar def update_agents(self, pstar, d): ''' Given pstar, update all agent histories. ''' for i, agent in enumerate(self.agents): # Determine and save choices: xi = agent.demand(pstar) ct = agent.y + pstar * agent.xi0 - pstar * xi #print "new c=calc" self.agents_history[i]['c'].append(ct) self.agents_history[i]['y'].append(agent.y) self.agents_history[i]['xi'].append(xi) # Update agent value: agent.y = xi * d if not self.lucas_tree: agent.xi0 = xi agent.update_utility_demand()
class AssetMarket(object): def __init__(self, agents, D, D_probs, aggregate_asset_endowment=1.0, seed=None, lucas_tree=False): ''' Market simply consists of collection of agents. lucas_tree: True means agents get same allocation of xi0 every morning ''' self.seed=seed self.RNG = np.random.RandomState(self.seed) self.lucas_tree = lucas_tree self.agents = agents self.price_history = [] self.volume_history = [] self.payoff_history = [] self.bilateral_price_history = [] self.bilateral_volume_history = [] self.bilateral_trade_partner_history = [] self.total_agent_cash_wealth_history = [] self.total_agent_initial_asset_endowment = [] #self.total_agent_asset_volume = [] self.aggregate_asset_endowment = aggregate_asset_endowment # Set up storage for agent values: self.agents_history = [] # Store agent variables here. for i in range(len(self.agents)): temp_storage = {'y':[],'c':[],'xi':[]} self.agents_history.append(temp_storage) # To find reasonable "first guess" price, find the risk-neutral asset # asset price for first-agent's preferences: agent0 = self.agents[0] self.p0_guess = agent0.beta * np.dot(agent0.D, agent0.D_probs) if self.p0_guess <= 0.0: self.p0_guess = 1e-5 self.p_tm1 = self.p0_guess # Set up the dividend process: # Ensure that dividend payoffs are sorted from smallest to largest: self.D = np.array(D) self.D_probs = np.array(D_probs) ii = np.argsort(self.D) self.D = self.D[ii] self.D_probs = self.D_probs[ii] # Quick check: assert np.isclose(np.sum(D_probs), 1.0), "Problem: p_dividend does not sum to 1.0: np.sum(D_probs) = " + str(np.sum(D_probs)) D_seed = self.RNG.randint(low=0, high=(2**31)-1) self.D_process = DiscreteRV(self.D_probs, self.D, seed=D_seed) # Done def run_markets(self, T): ''' Run market for T periods. ''' # Draw dividend: D = self.D_process.draw(T) for t in range(T): # Clear markets: pstar = self.clear_market() # Update self.price_history.append(pstar) self.payoff_history.append(D[t]) # Get agent starting wealth, total agent initial asset endowment, # and total volume traded. # Important to do the following update before updating agent values: total_agent_initial_cash = 0.0 total_agent_initial_asset_endowment = 0.0 total_agent_asset_volume = 0.0 for agent in self.agents: total_agent_initial_cash += agent.y # Get wealth they started the period with total_agent_initial_asset_endowment += agent.xi0 total_agent_asset_volume += np.abs(agent.xi0 - agent.demand(pstar)) self.total_agent_cash_wealth_history.append(total_agent_initial_cash) self.total_agent_initial_asset_endowment.append(total_agent_initial_asset_endowment) self.volume_history.append(total_agent_asset_volume) # Now update agents and histories: self.update_agents(pstar=pstar, d=D[t]) # Done def excess_demand(self, p, total_supply, agents): ''' Iterate over all agents and ask for demand given price. ''' ''' if agents is None: agents = self.agents if total_supply is None: total_supply = self.aggregate_asset_endowment ''' total_demand = 0.0 for an_agent in agents: total_demand += an_agent.demand(p) return total_demand - total_supply def clear_market(self, these_agents=None, p0=None): ''' Given an initial price guess, zero-find on total asset demand. p0 is initial price. ''' if these_agents is None: these_agents = self.agents # Set intial price guess if p0 is None: p0 = self.p_tm1 # Note: currently not using first guess.... # Zero-find to determine price: supply_to_use = sum((agent.xi0 for agent in these_agents)) p, root_results = brentq(f=self.excess_demand, args=(supply_to_use, these_agents), a=0.0001, b=1000, full_output=True, disp=True) if not root_results.converged: print "WARNING: root_results.converged not True!: root_results.converged = ", root_results.converged print "root_results:", root_results return p def run_bilateral_markets(self, T): ''' Run market for T periods. ''' # Draw dividend: D = self.D_process.draw(T) for t in range(T): # Get agent starting wealth, total agent initial asset endowment # Important to do the following update before updating agent values: total_agent_initial_cash = 0.0 total_agent_initial_asset_endowment = 0.0 for agent in self.agents: total_agent_initial_cash += agent.y # Get wealth they started the period with total_agent_initial_asset_endowment += agent.xi0 self.total_agent_cash_wealth_history.append(total_agent_initial_cash) self.total_agent_initial_asset_endowment.append(total_agent_initial_asset_endowment) # Clear markets: volume_weighted_price, intra_prices, intra_volumes, intra_price_partners, agent_bilateral_holdings = self.bilateral_trade() # Update self.price_history.append(volume_weighted_price) self.bilateral_price_history = [deepcopy(intra_prices)] self.bilateral_volume_history = [deepcopy(intra_volumes)] self.bilateral_trade_partner_history = [deepcopy(intra_price_partners)] self.payoff_history.append(D[t]) # Update total volume traded. # Important to do the following update before updating agent values: total_agent_asset_volume = 0.0 self.volume_history.append(total_agent_asset_volume) # Now update agents and histories: self.update_agents_bilateral(d=D[t]) # Done def update_agents_intraperiod(self, these_agents, pstar): ''' Given pstar, update all agent histories. ''' for i, agent in enumerate(these_agents): # Determine and save choices: xi = agent.demand(pstar) # Determine new cash on hand: y_new = agent.y + pstar*(agent.xi0 - xi) # Determine cash "set aside" for consumption: ct = agent.y + pstar*agent.xi0 - pstar*xi # NOTE TODO Super important: confirm and figure out proper values here! # Update agent value: agent.y = y_new agent.xi0 = xi agent.update_utility_demand() agent.p_prev = pstar def update_agents_bilateral(self, d): ''' Given pstar, update all agent histories. Definitely need to work through. ''' for i, agent in enumerate(self.agents): # Determine and save choices: xi = agent.xi0 #ct = agent.y + pstar*agent.xi0 - pstar*xi ct = agent.y #print "new c=calc" self.agents_history[i]['c'].append(ct) self.agents_history[i]['xi'].append(xi) # Update agent value: agent.y += xi*d self.agents_history[i]['y'].append(agent.y) if not self.lucas_tree: agent.xi0 = xi agent.update_utility_demand() # Done def update_agents_d_only(self, d): ''' Given pstar, update all agent histories. ''' for i, agent in enumerate(these_agents): # Determine and save choices: xi = agent.demand(pstar) # Determine new cash on hand: y_new = agent.y + pstar*(agent.xi0 - xi) # Update agent value: agent.y = y_new agent.xi0 = xi agent.update_utility_demand() def confirm_trade(self, p, trading_agents): trade_occurred = True for an_agent in trading_agents: xi_new = an_agent.demand(p) trade_occurred = trade_occurred and not(np.isclose(xi_new, an_agent.xi0)) # so agents did not trade here return trade_occurred def bilateral_trade(self, upper_trade_limit=None): ''' Given a list of agents, pair up agents to trade until no trades are realized. ''' # Need to transfer the bilateral market code here. Then need to be careful about how agents are updated and how all of this is saved in market history. # Agent updates need to be carried out such that agents themselves are correctly advanced through their asset laws of motion. N = len(self.agents) n_to_trade = 2 # Construct a set of all possible n=2-combos of agents: set_of_all_combos = set([frozenset(combo) for combo in combinations(range(N), n_to_trade)]) if upper_trade_limit is None: upper_trade_limit = max(10000, len(set_of_all_combos)*5) intra_prices = [] intra_volumes = [] intra_price_partners = [] essentially_no_trade = set([]) ctr=0 no_trade_ctr = 0 no_trade_rounds = 2 # How many rounds do we want to go past total no trade "just to check?" no_price_progress = False while ctr < upper_trade_limit and not(no_price_progress) and no_trade_ctr < no_trade_rounds: # Update ctr: ctr += 1 # Randomly choose two agents, clear their own trades, and update them. # How: choose N, "order all," grab first two agent_indices = range(N) self.RNG.shuffle(agent_indices) trading_agent_indicies = agent_indices[:n_to_trade] trading_agents = [self.agents[i] for i in trading_agent_indicies] # Choose 2 and get price for clearing pstar = self.clear_market(these_agents=trading_agents) # Check to see if the agents actually trade at this price: trade_occurred = self.confirm_trade(pstar, trading_agents) if not trade_occurred: # Then add the pair to the list of "no trading pairs": essentially_no_trade.update( [frozenset( trading_agent_indicies )] ) print "no trade occurred between", trading_agent_indicies else: print "trade occurred between", trading_agent_indicies # Ensure that agents who traded are removed from "no trade" set: essentially_no_trade -= set([frozenset( trading_agent_indicies )]) # Record the "intra-day" prices, volume, and update agents: intra_prices.append(pstar) vol1 = np.abs(trading_agents[0].xi0 - trading_agents[0].demand(pstar)) for agent in trading_agents[1:]: vol2 = np.abs(agent.xi0 - agent.demand(pstar)) assert np.isclose(vol1, vol2), "Trading agents are not close! vol1, vol2 = " +str([vol1, vol2]) vol2=vol1 intra_volumes.append(vol1) intra_price_partners.append(deepcopy(agent_indices[:n_to_trade])) # Update agents: self.update_agents_intraperiod(trading_agents, pstar) # FINAL CHECK to see if any agents are if essentially_no_trade == set_of_all_combos: print "All agents not trading" no_trade_ctr += 1 # Manually loop over all agents and see if possible trading opportunities to exploit: for trade_pair in essentially_no_trade: # Run all possible trading; if a single trade is successful then # execute it and kick back out: trading_agent_indicies = trade_pair trading_agents = [self.agents[i] for i in trading_agent_indicies] # Choose 2 and get price for clearing pstar = self.clear_market(these_agents=trading_agents) # Check if any trade: trade_occurred = self.confirm_trade(pstar, trading_agents) if trade_occurred: # Execute the trade and kick back out no_trade_ctr = 0 # Reset to kick back out essentially_no_trade = set([]) #-= set([frozenset( trading_agent_indicies )]) # Record the "intra-day" prices and update agents: intra_prices.append(pstar) vol1 = np.abs(trading_agents[0].demand(pstar)) for agent in trading_agents[1:]: vol2 = np.abs(agent.demand(pstar)) #assert np.isclose(vol1, vol2), "Trading agents are not close! vol1, vol2 = " +str([vol1, vol2]) vol2=vol1 intra_volumes.append(vol1) intra_price_partners.append(deepcopy(agent_indices[:n_to_trade])) # Update agents: self.update_agents_intraperiod(trading_agents, pstar) if no_trade_ctr > 0: print "Confirmed between all trading pairs that no bilateral trades are possible." if ctr == upper_trade_limit: raise Exception, "Allowable number of bilateral trades exceeded: ctr="+str(ctr)+", upper_trade_limit="+str(upper_trade_limit) vol_weights = np.array(intra_prices) / float(np.sum(intra_prices)) volume_weighted_price = np.dot(vol_weights, intra_prices) #intra_prices #intra_volumes #intra_price_partners agent_bilateral_holdings = deepcopy([agent.xi0 for agent in self.agents]) return volume_weighted_price, intra_prices, intra_volumes, intra_price_partners, agent_bilateral_holdings def clear_market2(self, alt_title=""): agents_to_use = [] for agent in self.agents: if agent.participate: agents_to_use.append(agent) supply_to_use = 0.0 #self.aggregate_asset_endowment for an_agent in agents_to_use: supply_to_use += an_agent.xi0 pstar, root_results = brentq(f=self.excess_demand, a=1e-6, b=2000, full_output=True, disp=True, args=(supply_to_use, agents_to_use)) if not root_results.converged: print "WARNING: root_results.converged not True!: root_results.converged = ", root_results.converged print "root_results:", root_results print "Market-clearing price for "+ str(len(agents_to_use)) +" agents, total supply "+str(supply_to_use)+":", pstar return pstar def update_agents(self, pstar, d): ''' Given pstar, update all agent histories. ''' for i, agent in enumerate(self.agents): # Determine and save choices: xi = agent.demand(pstar) ct = agent.y + pstar*agent.xi0 - pstar*xi #print "new c=calc" self.agents_history[i]['c'].append(ct) self.agents_history[i]['y'].append(agent.y) self.agents_history[i]['xi'].append(xi) # Update agent value: agent.y = xi*d if not self.lucas_tree: agent.xi0 = xi agent.update_utility_demand()