Example #1
0
File: mm.py Project: lgh27/mixture
 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
Example #2
0
File: mm.py Project: lgh27/mixture
    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])
Example #3
0
File: mm.py Project: lgh27/mixture
    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:])))
Example #4
0
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
Example #5
0
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)
Example #7
0
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
Example #8
0
 def std_rv(self):
     """
     discrete rv for the sigmas
     """
     return DiscreteRV(self.weights, self.sigma)
Example #9
0
 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()