Exemple #1
0
def experiment(results_csv_file: str,
               auction_function: Callable,
               auction_name: str,
               recipe: tuple,
               stocks_prices: list = None,
               stock_names: list = None):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.
    :param results_csv_file: the experiment result file.
    :param auction_function: the function for executing the auction under consideration.
    :param auction_name: title of the experiment, for printouts.
    :param recipe: can be any vector of ones, e.g. (1,1,1), for our trade-reduction mechanism, or any vector of positive integers for our ascending-auction mechanism.
    :param stocks_prices: list of prices for each stock and each agent.
    :param stock_names: list of stocks names which prices are belongs, for naming only.
    """
    if stocks_prices is None:
        (stocks_prices, stock_names) = getStocksPrices(recipe)
    results_table = TeeTable(TABLE_COLUMNS, results_csv_file)
    recipe_str = ":".join(map(str, recipe))
    for i in range(len(stocks_prices)):
        market = Market([
            AgentCategory("agent", category) for category in stocks_prices[i]
        ])
        num_of_possible_ps = min([
            len(stocks_prices[i][j]) / recipe[j]
            for j in range(len(stocks_prices[i]))
        ])
        (optimal_trade, _) = market.optimal_trade(recipe)
        auction_trade = auction_function(market, recipe)
        optimal_count = optimal_trade.num_of_deals()
        auction_count = auction_trade.num_of_deals()
        if (auction_trade.num_of_deals() > optimal_trade.num_of_deals()):
            print(
                "Warning!!! the number of deals in action is greater than optimal!"
            )
            print("Optimal num of deals: ", optimal_trade.num_of_deals())
            print("Auction num of deals: ", auction_trade.num_of_deals())
        optimal_gft = optimal_trade.gain_from_trade()
        auction_gft = auction_trade.gain_from_trade(including_auctioneer=True)
        auction_market_gft = auction_trade.gain_from_trade(
            including_auctioneer=False)
        results_table.add(
            OrderedDict((
                ("stock_name", stock_names[i]),
                ("auction_name", auction_name),
                ("recipe", recipe_str),
                ("num_possible_trades", round(num_of_possible_ps)),
                ("optimal_count", round(optimal_count, 2)),
                ("auction_count", round(auction_count, 2)),
                ("count_ratio", 0 if optimal_count == 0 else int(
                    (auction_count / optimal_count) * 100000) / 1000),
                ("optimal_gft", round(optimal_gft, 2)),
                ("auction_gft", round(auction_gft, 2)),
                ("auction_gft_ratio", 0 if optimal_gft == 0 else round(
                    auction_gft / optimal_gft * 100, 3)),
                ("auction_market_gft", round(auction_market_gft, 2)),
                ("market_gft_ratio", 0 if optimal_gft == 0 else round(
                    auction_market_gft / optimal_gft * 100, 3)),
            )))
    results_table.done()
Exemple #2
0
def start_testing(args):
    ## instantiate exchanges, update candles, and run strategy
    profits = defaultdict(dict)
    for x in args["exchanges"]:
        new_exchange = ccxtExchange(x)
        portfolio = [Market(m, new_exchange) for m in args["markets"][x]]
        new_exchange.markets = {
            mkt_obj.symbol: mkt_obj
            for mkt_obj in portfolio
        }
        candles_magnitude = new_exchange.get_ccxt_candles_magnitude_format(
            args["candles_m_size"], args["candles_m_unit"])

        ## run real strategy
        strat_profits = run_model(args["strategy"], x, portfolio,
                                  candles_magnitude, args)

        ## run benchmark strategy (holding)
        hold_profits = run_model("hold", x, portfolio, candles_magnitude, args)

        ## run study stats (it does not return anything)
        run_model("study_stats", x, portfolio, candles_magnitude, args)

        exchange_profits = {mkt: {args["strategy"]: strat_prf, "hold": hold_profits[mkt]} \
                            for mkt, strat_prf in \
                            zip(strat_profits.keys(), strat_profits.values())}

        profits[x] = exchange_profits

    return profits
Exemple #3
0
 def check_110_101(buyers: List[float], sellersA: List[float], sellersB: List[float],
                    expected_num_of_deals: int, expected_prices: List[float]):
     market = Market([
         AgentCategory("buyer", buyers),
         AgentCategory("sellerA", sellersA),
         AgentCategory("sellerB", sellersB),
     ])
     # ps_recipes = [[1, 1, 0], [1, 0, 1]]
     ps_recipe_struct = [0, [1, None, 2, None]]
     self._check_market(market, ps_recipe_struct, expected_num_of_deals, expected_prices)
Exemple #4
0
 def check_1_1(buyers: List[float], sellers: List[float],
               expected_num_of_deals: int,
               expected_prices: List[float]):
     market = Market([
         AgentCategory("buyer", buyers),
         AgentCategory("seller", sellers),
     ])
     ps_recipe = [1, 1]
     self._check_market(market, ps_recipe, expected_num_of_deals,
                        expected_prices)
Exemple #5
0
 def check_1100_1011(buyers: List[float], sellers: List[float], producers: List[float], movers: List[float],
                    expected_num_of_deals: int, expected_prices: List[float]):
     market = Market([
         AgentCategory("buyer", buyers),
         AgentCategory("seller", sellers),
         AgentCategory("producer", producers),
         AgentCategory("mover", movers),
     ])
     # ps_recipes = [[1, 1, 0, 0], [1, 0, 1, 1]]
     ps_recipe_struct = [0, [1, None, 2, [3, None]]]
     self._check_market(market, ps_recipe_struct, expected_num_of_deals, expected_prices)
Exemple #6
0
 def check_11100_10011(buyers: List[float], sellers1: List[float], sellers2: List[float], producers1: List[float], producers2: List[float],
                    expected_num_of_deals: int, expected_prices: List[float]):
     market = Market([
         AgentCategory("buyer", buyers),
         AgentCategory("seller1", sellers1),
         AgentCategory("seller2", sellers2),
         AgentCategory("producer1", producers1),
         AgentCategory("producer2", producers2),
     ])
     ps_recipe_struct = [0, [1, [2, None], 3, [4, None]]]
     self._check_market(market, ps_recipe_struct, expected_num_of_deals, expected_prices)
Exemple #7
0
def test_trader_pipeline():

    hitbtc2 = ExchangeTrader("hitbtc2")
    eth_btc_market = Market("ETH/BTC", hitbtc2)

    try:
        hitbtc2.buy(eth_btc_market, 0.001, 0.00059498)
        time.sleep(0.1)
        hitbtc2.sell(eth_btc_market, 0.001, 0.1)
        time.sleep(0.1)
    except:
        raise

    time.sleep(1)

    for order in hitbtc2.orders_alive:
        print(order.status())
        time.sleep(0.1)
        print(order.status())
        time.sleep(0.1)
        print(order.status())
        time.sleep(0.1)
        assert (order.status() == 'open')

    time.sleep(1)

    num_orders = len(hitbtc2.orders_alive)
    print hitbtc2.orders_history
    for order in hitbtc2.orders_history:
        order.cancel()
        time.sleep(0.1)
        num_orders -= 1
        print hitbtc2.orders_alive
        assert (len(hitbtc2.orders_alive) == num_orders)

    time.sleep(1)

    for order in hitbtc2.orders_history:
        assert (order.status() == 'canceled')
Exemple #8
0
def experiment(results_csv_file: str,
               auction_functions: list,
               auction_names: str,
               recipe: tuple,
               iterations: int,
               nums_of_agents: list = None,
               stocks_prices: list = None,
               stock_names: list = None):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.
    :param results_csv_file: the experiment result file.
    :param auction_functions: list of functions for executing the auction under consideration.
    :param auction_names: titles of the experiment, for printouts.
    :param recipe: can be any vector of ones, e.g. (1,1,1), for our trade-reduction mechanism, or any vector of positive integers for our ascending-auction mechanism.
    :param nums_of_agents: list of n(s) for number of possible trades to make the calculations.
    :param stocks_prices: list of prices for each stock and each agent.
    :param stock_names: list of stocks names which prices are belongs, for naming only.
    """
    TABLE_COLUMNS = [
        "stockname", "recipe", "numpossibletrades", "optimalcount",
        "optimalcountwithgftzero", "optimalgft"
    ]
    AUCTION_COLUMNS = ["auctioncount", "countratio", "gft", "gftratio"]
    print(recipe)
    if stocks_prices is None:
        (stocks_prices, stock_names) = getStocksPricesShuffled()
    column_names = TABLE_COLUMNS
    column_names += [
        auction_name + column for auction_name in auction_names
        for column in AUCTION_COLUMNS
    ]
    results_table = TeeTable(column_names, results_csv_file)
    recipe_str = ":".join(map(str, recipe))
    recipe_sum = sum(recipe)
    if nums_of_agents is None:
        nums_of_agents = [10000000]
    average_total_results = {}
    for num_of_agents_per_category in nums_of_agents:
        average_total_results[str(num_of_agents_per_category)] = []

    for i in range(len(stocks_prices)):
        total_results = {}
        for num_of_agents_per_category in nums_of_agents:
            total_results[str(num_of_agents_per_category)] = []
        stock_prices = stocks_prices[i]
        last_iteration = False
        for num_of_agents_per_category in nums_of_agents:
            for iteration in range(iterations):
                num_of_possible_ps = min(num_of_agents_per_category,
                                         int(len(stock_prices) / recipe_sum))
                if last_iteration and num_of_possible_ps < num_of_agents_per_category:
                    break
                if num_of_possible_ps < num_of_agents_per_category:
                    if last_iteration:
                        break
                last_iteration = True
                categories = []
                buyer_agent_count = recipe[0]
                index = 0
                for category in recipe:
                    next_index = index + num_of_possible_ps * category
                    price_value_multiple = -1 * buyer_agent_count if index > 0 else recipe_sum - buyer_agent_count
                    categories.append(
                        AgentCategory("agent", [
                            int(price * price_value_multiple)
                            for price in stock_prices[index:next_index]
                        ]))
                    index = next_index
                market = Market(categories)

                (optimal_trade,
                 _) = market.optimal_trade(ps_recipe=list(recipe),
                                           max_iterations=10000000,
                                           include_zero_gft_ps=False)
                optimal_count = optimal_trade.num_of_deals()
                optimal_gft = optimal_trade.gain_from_trade()
                (optimal_trade_with_gft_zero,
                 _) = market.optimal_trade(ps_recipe=list(recipe),
                                           max_iterations=10000000)
                optimal_count_with_gft_zero = optimal_trade_with_gft_zero.num_of_deals(
                )

                results = [
                    ("stockname", stock_names[i]), ("recipe", recipe_str),
                    ("numpossibletrades", round(num_of_possible_ps)),
                    ("optimalcount", optimal_count),
                    ("optimalcountwithgftzero", optimal_count_with_gft_zero),
                    ("optimalgft", optimal_gft)
                ]
                for auction_index in range(len(auction_functions)):
                    if 'mcafee' in auction_names[auction_index]:
                        results.append((auction_name + "auctioncount", 0))
                        results.append((auction_name + "countratio", 0))
                        results.append((auction_name + "gft", 0))
                        results.append((auction_name + "gftratio", 0))

                    auction_trade = auction_functions[auction_index](market,
                                                                     recipe)
                    auction_count = auction_trade.num_of_deals()
                    # for j in range(len(stocks_prices[i])):
                    #     print(sorted(stocks_prices[i][j][:num_of_possible_ps*recipe[j]]))
                    if (auction_trade.num_of_deals() >
                            optimal_trade_with_gft_zero.num_of_deals()):
                        # print(sorted(stocks_prices[i][0][:num_of_possible_ps*recipe[0]]))
                        # print(sorted(stocks_prices[i][1][:num_of_possible_ps*recipe[1]]))
                        print(
                            "Warning!!! the number of deals in action is greater than optimal!"
                        )
                        print("Optimal num of deals: ",
                              optimal_trade.num_of_deals())
                        print("Auction num of deals: ",
                              auction_trade.num_of_deals())
                        print("Auction name: ", auction_names[auction_index])
                    gft = auction_trade.gain_from_trade(
                        including_auctioneer=False)
                    auction_name = auction_names[auction_index]
                    results.append((auction_name + "auctioncount",
                                    auction_trade.num_of_deals()))
                    results.append(
                        (auction_name + "countratio",
                         0 if optimal_count_with_gft_zero == 0 else
                         (auction_count / optimal_count_with_gft_zero) * 100))
                    results.append((auction_name + "gft", gft))
                    results.append(
                        (auction_name + "gftratio",
                         0 if optimal_gft == 0 else gft / optimal_gft * 100))

                #results_table.add(OrderedDict(results))
                if len(total_results[str(num_of_agents_per_category)]) == 0:
                    total_results[str(
                        num_of_agents_per_category)] = results[0:len(results)]
                else:
                    sum_result = total_results[str(num_of_agents_per_category)]
                    for index in range(len(results)):
                        if index > 2:
                            sum_result[index] = (results[index][0],
                                                 sum_result[index][1] +
                                                 results[index][1])
        for num_of_agents_per_category in nums_of_agents:
            results = total_results[str(num_of_agents_per_category)]
            if len(results) == 0:
                continue
            for index in range(len(results)):
                if index > 2:
                    if 'ratio' in results[index][0]:
                        results[index] = (results[index][0],
                                          results[index][1] / iterations)
                    else:
                        results[index] = (results[index][0],
                                          results[index][1] / iterations)
                #elif index == 0:
                #    results[index] = (results[index][0], 'Average')
            results_table.add(OrderedDict(results))

            if len(average_total_results[str(
                    num_of_agents_per_category)]) == 0:
                average_total_results[str(
                    num_of_agents_per_category)] = results[0:len(results)]
            else:
                sum_result = average_total_results[str(
                    num_of_agents_per_category)]
                for index in range(len(results)):
                    if index > 2:
                        sum_result[index] = (sum_result[index][0],
                                             sum_result[index][1] +
                                             results[index][1])
    for num_of_agents_per_category in nums_of_agents:
        results = average_total_results[str(num_of_agents_per_category)]
        if len(results) == 0:
            continue
        for index in range(len(results)):
            if index > 2:
                if 'ratio' in results[index][0]:
                    results[index] = (
                        results[index][0],
                        int(results[index][1] / len(stocks_prices) * 1000) /
                        1000)
                else:
                    results[index] = (results[index][0],
                                      round(
                                          results[index][1] /
                                          len(stocks_prices), 1))
            elif index == 0:
                results[index] = (results[index][0], 'Average')
        results_table.add(OrderedDict(results))
    results_table.done()
#!python3
"""
Demonstration of a multiple-clock strongly-budget-balanced ascending auction
for a multi-lateral market with one buyer per two sellers (recipe: 1,2)

Author: Erel Segal-Halevi
Since:  2019-08
"""

from markets import Market
from agents import AgentCategory
import ascending_auction_protocol, prices
from ascending_auction_protocol import budget_balanced_ascending_auction

import logging

ascending_auction_protocol.logger.setLevel(logging.INFO)
prices.logger.setLevel(logging.INFO)

print("\n\n###### EXAMPLE OF PS with GFT=0")

market = Market([
    AgentCategory("buyer", [1, 1, 1, 1, 1]),
    AgentCategory("seller", [-1, -1, -1, -1, -1]),
])

print(budget_balanced_ascending_auction(market, [1, 1]))
Exemple #10
0
# market = Market([
#     AgentCategory("C0", [400, 300, 200, 100]),
#     AgentCategory("C1", [-1, -11]),
#     AgentCategory("C2", [-2, -22]),
#     AgentCategory("C3", [-3, -33]),
#     AgentCategory("C4", [-4, -44]),
#     AgentCategory("C5", [-5, -55]),
#     AgentCategory("C6", [-6, -66]),
# ])

market = Market([
    AgentCategory("C0", [400, 300, 200, 100]),
    AgentCategory("C1", [-1, -11]),
    AgentCategory("C2", [-2]),
    AgentCategory("C3", [-3]),
    AgentCategory("C4", [-4, -44]),
    AgentCategory("C5", [-5]),
    AgentCategory("C6", [-6]),
])


recipes_4paths = [0, [1, [2, None, 3, None], 4, [5, None, 6, None]]]
# The recipes are:
#     1,1,1,0,0,0,0   [C0, C1, C2]
#     1,1,0,1,0,0,0   [C0, C1, C3]
#     1,0,0,0,1,1,0   [C0, C4, C5]
#     1,0,0,0,1,0,1   [C0, C4, C6]

print(budget_balanced_ascending_auction(market, recipes_4paths))
Exemple #11
0
def experiment(results_csv_file: str, recipe: list, value_ranges: list,
               nums_of_agents: list, num_of_iterations: int,
               agent_counts: list, agent_values: list,
               recipe_tree_agent_counts: list, num_recipes: int):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.
    :param results_csv_file: the experiment result file.
    :param auction_functions: list of functions for executing the auction under consideration.
    :param auction_names: titles of the experiment, for printouts.
    :param recipe: can be any vector of ones, e.g. (1,1,1), for our trade-reduction mechanism, or any vector of positive integers for our ascending-auction mechanism.
    :param nums_of_agents: list of n(s) for number of possible trades to make the calculations.
    :param stocks_prices: list of prices for each stock and each agent.
    :param stock_names: list of stocks names which prices are belongs, for naming only.
    """
    TABLE_COLUMNS = [
        "iterations", "recipe", "numofagents", "optimalcount", "optimalkmin",
        "optimalkmax", "gftformula", "auctioncount", "auctionkmin",
        "auctionkmax", "countratio", "optimalgft", "auctiongft", "gftratio"
    ]
    print('recipe:', recipe, 'size:', num_recipes)
    GFT_ROUND = 1 - 1
    K_ROUND = 2 - 1
    RATIO_ROUND = 3 - 1
    results_table = TeeTable(TABLE_COLUMNS, results_csv_file)
    recipe_str = str(recipe).replace(',', '-')
    is_binary = len(set(recipe_tree_agent_counts)) == 1

    for i in range(len(nums_of_agents)):
        now = datetime.now()
        sum_optimal_count = sum_auction_count = sum_optimal_kmin = sum_optimal_kmax = 0  # count the number of deals done in the optimal vs. the actual auction.
        sum_optimal_gft = sum_auction_total_gft = sum_auction_kmin = sum_auction_kmax = 0
        for iteration in range(num_of_iterations):
            #if iteration % 10000 == 0:
            #    print('iteration:', iteration)
            agents = []
            for category in range(len(recipe_tree_agent_counts)):
                sign = 0 if category == 0 else 1
                agents.append(
                    AgentCategory.uniformly_random(
                        "agent",
                        int(nums_of_agents[i] * agent_counts[category]),
                        value_ranges[sign][0] * agent_values[category],
                        value_ranges[sign][1] * agent_values[category]))
                #agents.append(AgentCategory.uniformly_random("agent", nums_of_agents[i], value_ranges[sign][0], value_ranges[sign][1]))
            market = Market(agents)
            #print(market)
            #print(agents)
            recipe_tree = RecipeTree(market.categories, recipe,
                                     recipe_tree_agent_counts)
            optimal_trade, optimal_count, optimal_gft, kmin, kmax, categories_optimal_counters = recipe_tree.optimal_trade_with_counters(
            )
            #print('counters:' + str(categories_counters))
            #print('optimal trade:', optimal_trade, optimal_count, optimal_gft)
            auction_trade = budget_balanced_ascending_auction(
                market, recipe, recipe_tree_agent_counts)
            auction_count = auction_trade.num_of_deals()
            auction_kmin = auction_trade.min_num_of_deals()
            auction_kmax = auction_trade.max_num_of_deals()
            path_counters = auction_trade.path_counters
            gft = auction_trade.gain_from_trade()
            #print('Compare:', categories_optimal_counters, path_counters)
            for counter in categories_optimal_counters.keys():
                if categories_optimal_counters[
                        counter] > path_counters[counter] + 1 and False:
                    print(market)
                    print('Compare:', categories_optimal_counters,
                          path_counters)
                    print('Warning counters', str(counter),
                          'are not in same size!',
                          categories_optimal_counters[counter], '!=',
                          path_counters[counter])
            #for i in range(len(path_counters)):
            #    print('Compare:', categories_optimal_counters, path_counters)
            #if optimal_count > 0 and gft < optimal_gft * (kmin - 1)/(kmin + 2):
            #the auction count is less more than 1 than the optimal count.
            #    print('Warning GFT!!!', 'optimal_count:', optimal_count, 'auction_count:', auction_count,
            #          'num_of_possible_ps:', nums_of_agents[i], 'optimal_gft:', optimal_gft, 'gft:', gft, 'lower bound:', optimal_gft * (1 - 1/optimal_count))
            #    if nums_of_agents[i] < 20:
            #        print(market.categories)

            sum_optimal_count += optimal_count
            sum_auction_count += auction_count

            sum_optimal_kmin += kmin
            sum_optimal_kmax += kmax

            sum_auction_kmin += auction_kmin
            sum_auction_kmax += auction_kmax

            sum_optimal_gft += optimal_gft
            sum_auction_total_gft += gft

            #if auction_count < optimal_count - 2:
            #the auction count is less more than 1 than the optimal count.
            #print('Warning!!!', 'optimal_count:', optimal_count, 'auction_count:', auction_count, 'num_of_possible_ps:', nums_of_agents[i])
            #if nums_of_agents[i] < 10:
            #    print(market.categories)

        # print("Num of times {} attains the maximum GFT: {} / {} = {:.2f}%".format(title, count_optimal_gft, num_of_iterations, count_optimal_gft * 100 / num_of_iterations))
        # print("GFT of {}: {:.2f} / {:.2f} = {:.2f}%".format(title, sum_auction_gft, sum_optimal_gft, 0 if sum_optimal_gft==0 else sum_auction_gft * 100 / sum_optimal_gft))
        kmin_mean = sum_optimal_kmin / num_of_iterations
        if is_binary:
            gft_formula = int_round(
                (kmin_mean - 1) / kmin_mean * 100 if kmin_mean - 1 > 0 else 0,
                RATIO_ROUND)
        else:
            gft_formula = int_round(
                (kmin_mean - num_recipes) / (kmin_mean + num_recipes) *
                100 if kmin_mean - num_recipes > 0 else 0, RATIO_ROUND)

        results_table.add(
            OrderedDict([
                ("iterations", num_of_iterations),
                ("recipe", recipe_str),
                ("numofagents", nums_of_agents[i]),
                ("optimalcount",
                 int_round(sum_optimal_count / num_of_iterations, K_ROUND)),
                ("optimalkmin", int_round(kmin_mean, K_ROUND)),
                ("optimalkmax",
                 int_round(sum_optimal_kmax / num_of_iterations, K_ROUND)),
                ("gftformula", gft_formula),
                ("auctioncount",
                 int_round(sum_auction_count / num_of_iterations, K_ROUND)),
                ("auctionkmin",
                 int_round(sum_auction_kmin / num_of_iterations, K_ROUND)),
                ("auctionkmax",
                 int_round(sum_auction_kmax / num_of_iterations, K_ROUND)),
                ("countratio",
                 int_round(
                     0 if sum_optimal_count == 0 else
                     (sum_auction_count / sum_optimal_count) * 100,
                     RATIO_ROUND)),
                ("optimalgft",
                 int_round(sum_optimal_gft / num_of_iterations, GFT_ROUND)),
                ("auctiongft",
                 int_round(sum_auction_total_gft / num_of_iterations,
                           GFT_ROUND)),
                ("gftratio", '0.00' if sum_optimal_gft == 0 else int_round(
                    sum_auction_total_gft / sum_optimal_gft *
                    100, RATIO_ROUND)),
            ]))
        print('took', (datetime.now() - now).seconds)
    results_table.done()
def budget_balanced_ascending_auction(
        market: Market, ps_recipes: List[List]) -> TradeWithMultipleRecipes:
    """
    Calculate the trade and prices using generalized-ascending-auction.
    Allows multiple recipes, but they must all be binary, and must all start with 1. E.g.:
    [ [1,1,0,0], [1,0,1,1] ]
    I.e., there is 1 buyer category and n-1 seller categories.
    Each buyer may wish to buy a different combination of products.

    :param market:     contains a list of k categories, each containing several agents.
    :param ps_recipes: a list of lists of integers, one integer per category.
                       Each integer i represents the number of agents of category i
                       that should be in each procurement-set.
    :return: Trade object, representing the trade and prices.
    >>> # ONE BUYER, ONE SELLER
    >>> recipe_11 = [[1,1]]
    >>>
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    No trade

    >>> logger.setLevel(logging.WARNING)
    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0]: all 1 agents trade and pay 4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -8.0
    buyer: [9.0]: all 1 agents trade and pay 8.0
    """

    num_recipes = len(ps_recipes)
    if num_recipes < 1:
        raise ValueError("Empty list of recipes")

    num_categories = market.num_categories
    for i, ps_recipe in enumerate(ps_recipes):
        if len(ps_recipe) != num_categories:
            raise ValueError(
                "There are {} categories but {} elements in PS recipe #{}".
                format(num_categories, len(ps_recipe), i))
        if any((r != 1 and r != 0) for r in ps_recipe):
            raise ValueError(
                "Currently, the multi-recipe protocol supports only recipes of zeros and ones; {} was given"
                .format(ps_recipe))

    logger.info("\n#### Multi-Recipe Budget-Balanced Ascending Auction\n")
    logger.info(market)
    logger.info("Procurement-set recipes: {}".format(ps_recipes))

    map_category_index_to_recipe_indices, common_category_indices, map_recipe_index_to_unique_category_indices = \
        _analyze_recipes(num_categories, ps_recipes)

    # Calculating the optimal trade with multiple recipes is left for future work.
    # optimal_trade = market.optimal_trade(ps_recipe)[0]
    # logger.info("For comparison, the optimal trade is: %s\n", optimal_trade)

    remaining_market = market.clone()
    prices = SimultaneousAscendingPriceVectors(ps_recipes, -MAX_VALUE)

    # Functions for calculating the number of potential PS that can be supported by a category:
    # Currently we assume a recipe of ones, so the number of potential PS is simply the category size:
    fractional_potential_ps = lambda category_index: remaining_market.categories[
        category_index].size()
    integral_potential_ps = lambda category_index: remaining_market.categories[
        category_index].size()

    while True:
        # find a category with a largest number of potential PS, and increase its price

        largest_common_category_index = max(common_category_indices, key=fractional_potential_ps) \
            if len(common_category_indices)>0 \
            else None
        largest_common_category_size  = fractional_potential_ps(largest_common_category_index) \
            if len(common_category_indices) > 0 \
            else 0
        logger.info("Largest common category is %d and its size is %d",
                    largest_common_category_index,
                    largest_common_category_size)

        map_recipe_index_to_largest_unique_category_index = [
            max(unique_category_indices, key=fractional_potential_ps)
            for unique_category_indices in
            map_recipe_index_to_unique_category_indices
        ]
        if len(map_recipe_index_to_largest_unique_category_index) == 0:
            raise ValueError("No unique categories")
        unique_categories_size = sum([
            fractional_potential_ps(largest_unique_category_index)
            for largest_unique_category_index in
            map_recipe_index_to_largest_unique_category_index
        ])
        logger.info(
            "Largest unique categories are %s and their total size is %d",
            map_recipe_index_to_largest_unique_category_index,
            unique_categories_size)

        if unique_categories_size == 0:
            logger.info("\nThe unique categories %s became empty - no trade!",
                        map_recipe_index_to_largest_unique_category_index)
            logger.info("  Final price-per-unit vector: %s", prices)
            logger.info(remaining_market)
            return TradeWithMultipleRecipes(
                remaining_market.categories, ps_recipes,
                prices.map_category_index_to_price())

        if largest_common_category_size >= unique_categories_size:
            logger.info(
                "Raising price of the largest common category (%d) in all recipes",
                largest_common_category_index)
            main_category_index = largest_common_category_index
            main_category = remaining_market.categories[main_category_index]
            logger.info("%s before: %d agents remain", main_category.name,
                        main_category.size())
            increases = [
                (main_category_index, main_category.lowest_agent_value(),
                 main_category.name)
            ] * num_recipes

        else:  # largest_common_category_size < unique_categories_size
            logger.info(
                "Raising price of the largest unique categories in each recipe: %s",
                map_recipe_index_to_largest_unique_category_index)
            increases = []
            for recipe_index, main_category_index in enumerate(
                    map_recipe_index_to_largest_unique_category_index):
                main_category = remaining_market.categories[
                    main_category_index]
                logger.info("%s before: %d agents remain", main_category.name,
                            main_category.size())
                if main_category.size() == 0:
                    logger.info(
                        "\nThe %s category became empty - no trade in recipe %d",
                        main_category.name, recipe_index)
                    del ps_recipes[recipe_index]
                    if len(ps_recipes) > 0:
                        return budget_balanced_ascending_auction(
                            market, ps_recipes)
                    else:
                        logger.info("\nNo recipes left - no trade!")
                        logger.info("  Final price-per-unit vector: %s",
                                    prices)
                        logger.info(remaining_market)
                        return TradeWithMultipleRecipes(
                            remaining_market.categories, ps_recipes,
                            map_category_index_to_price)
                increases.append(
                    (main_category_index, main_category.lowest_agent_value(),
                     main_category.name))
            if len(increases) == 0:
                raise ValueError("No increases!")

        logger.info("Planned increases: %s", increases)
        prices.increase_prices(increases)
        map_category_index_to_price = prices.map_category_index_to_price()
        if prices.status == PriceStatus.STOPPED_AT_ZERO_SUM:
            logger.info("\nPrice crossed zero.")
            logger.info("  Final price-per-unit vector: %s",
                        map_category_index_to_price)
            logger.info(remaining_market)
            return TradeWithMultipleRecipes(remaining_market.categories,
                                            ps_recipes,
                                            map_category_index_to_price)

        for category_index in range(num_categories):
            category = remaining_market.categories[category_index]
            if map_category_index_to_price[category_index] is not None \
                and category.size()>0 \
                and category.lowest_agent_value() <= map_category_index_to_price[category_index]:
                category.remove_lowest_agent()
                logger.info(
                    "{} after: {} agents remain,  {} PS supported".format(
                        category.name, category.size(),
                        integral_potential_ps(category_index)))
Exemple #13
0
for a bilateral market with buyers and sellers.

Since:  2019-08

Author: Erel Segal-Halevi
"""

from markets import Market
from agents import AgentCategory
import mcafee_protocol
from mcafee_protocol import mcafee_trade_reduction

import logging
mcafee_protocol.logger.setLevel(logging.INFO)

recipe = [1, 1]

print("\n\n### Example without trade reduction")
market = Market([
    AgentCategory("buyer", [17, 14, 13, 9, 6]),
    AgentCategory("seller", [-1, -4, -5, -8, -11]),
])
print(mcafee_trade_reduction(market, recipe))

print("\n\n### Example with trade reduction")
market = Market([
    AgentCategory("buyer", [17, 14, 13, 9, 6]),
    AgentCategory("seller", [-1, -4, -5, -8, -13]),
])
print(mcafee_trade_reduction(market, recipe))
def experiment(results_csv_file: str, recipe: list, agent_counts:list, agent_values:list,
               nums_of_agents:list = None, num_of_iterations:int = 1, stocks_prices=None, stock_names=None):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.
    :param results_csv_file: the experiment result file.
    :param auction_functions: list of functions for executing the auction under consideration.
    :param auction_names: titles of the experiment, for printouts.
    :param recipe: can be any vector of ones, e.g. (1,1,1), for our trade-reduction mechanism, or any vector of positive integers for our ascending-auction mechanism.
    :param nums_of_agents: list of n(s) for number of possible trades to make the calculations.
    :param stocks_prices: list of prices for each stock and each agent.
    :param stock_names: list of stocks names which prices are belongs, for naming only.
    """
    TABLE_COLUMNS = ["stockname", "recipe", "numpossibletrades",
                     "optimalkmin", "optimalkmax","gftformula",
                     "optimalcount", "optimalgft", "auctioncount", "countratio", "gft", "gftratio"]
    print('recipe:', recipe)
    results_table = TeeTable(TABLE_COLUMNS, results_csv_file)
    recipe_str = str(recipe).replace(',', '->')
    if stocks_prices is None:
        (stocks_prices, stock_names) = getStocksTreePrices(recipe, agent_counts, agent_values)

    if nums_of_agents is None:
        nums_of_agents = [10000000]
    total_results = {}
    for num_of_agents_per_category in nums_of_agents:
        total_results[str(num_of_agents_per_category)] = []
    for i in range(len(stock_names)):
        for _ in range(num_of_iterations):
            last_iteration = False
            for j in range(len(stocks_prices[i])):
                random.shuffle(stocks_prices[i][j])
            for num_of_agents_per_category in nums_of_agents:
                num_of_possible_ps = min(num_of_agents_per_category, len(stocks_prices[i][0]))
                if last_iteration is True and num_of_possible_ps < num_of_agents_per_category:
                    break
                if num_of_possible_ps < num_of_agents_per_category:
                    if last_iteration is True:
                        break
                    else:
                        last_iteration = True
                        market = Market([AgentCategory("agent", stocks_prices[i][j]) for j in range(len(stocks_prices[i]))])
                else:
                    market = Market([AgentCategory("agent", stocks_prices[i][j][0:int(num_of_possible_ps*agent_counts[j])]) for j in range(len(stocks_prices[i]))])
                if num_of_agents_per_category == 6 and _ == 0:
                    print(stock_names[i], market.categories)
                recipe_tree = RecipeTree(market.categories, recipe)
                optimal_trade, optimal_count, optimal_gft, kmin, kmax = recipe_tree.optimal_trade_with_counters()
                #print('optimal trade:', optimal_trade, optimal_count, optimal_gft)
                auction_trade = budget_balanced_ascending_auction(market, recipe)
                auction_count = auction_trade.num_of_deals()
                gft = auction_trade.gain_from_trade()
                #if auction_count < optimal_count - 1:
                #    #the auction count is less more than 1 than the optimal count.
                #    print('Warning!!!', 'optimal_count:', optimal_count, 'auction_count:', auction_count, 'num_of_possible_ps:', num_of_possible_ps)
                #    if num_of_possible_ps < 10:
                #        print(market.categories)
                #if optimal_count > 0 and gft < optimal_gft * (1 - 1/optimal_count):
                #    #the auction count is less more than 1 than the optimal count.
                #    print('Warning GFT!!!', 'optimal_count:', optimal_count, 'auction_count:', auction_count,
                #          'num_of_possible_ps:', num_of_possible_ps, 'optimal_gft:', optimal_gft, 'gft:', gft)
                #    if num_of_possible_ps < 20:
                #        print(market.categories)
                if optimal_count < auction_count :
                    #the auction count is less more than 1 than the optimal count.
                    print('Warning count!!!', 'optimal_count:', optimal_count, 'auction_count:', auction_count,
                          'num_of_possible_ps:', num_of_possible_ps, 'optimal_gft:', optimal_gft, 'gft:', gft)
                    if num_of_possible_ps < 20:
                        print(market.categories)
                results = [("stockname", stock_names[i]),
                           ("recipe", recipe_str),
                           ("numpossibletrades", num_of_possible_ps),
                           ("optimalcount", optimal_count),
                           ("optimalkmin", kmin),
                           ("optimalkmax", kmax),
                           ("optimalgft", optimal_gft),
                           ("auctioncount", auction_count),
                           ("countratio", 0 if optimal_count==0 else auction_count / optimal_count*100),
                           ("gft", gft),
                           ("gftratio", 0 if optimal_gft==0 else gft / optimal_gft*100)
                           ]
                #results_table.add(OrderedDict(results))
                if len(total_results[str(num_of_agents_per_category)]) == 0:
                    total_results[str(num_of_agents_per_category)] = results[0:len(results)]
                else:
                    sum_result = total_results[str(num_of_agents_per_category)]
                    for index in range(len(results)):
                        if index > 2:
                            sum_result[index] = (results[index][0], sum_result[index][1] + results[index][1])
    for num_of_agents_per_category in nums_of_agents:
        results = total_results[str(num_of_agents_per_category)]
        kmin = 0
        for index in range(len(results)):
            if index > 2:
                #if 'ratio' in results[index][0]:
                #    results[index] = (results[index][0], results[index][1]/len(stock_names))
                #elif 'count' in results[index][0]:
                #    results[index] = (results[index][0], results[index][1]/len(stock_names))
                #else:
                results[index] = (results[index][0], results[index][1]/len(stock_names)/num_of_iterations)
            elif index == 0:
                results[index] = (results[index][0], 'Average')
            if results[index][0] == 'optimalkmin':
                kmin = results[index][1]
        results.append(('gftformula', 0 if kmin <= 1 else (1-1/kmin)*100))
        results_table.add(OrderedDict(results))
    results_table.done()
        -13.75, -13.7, -13.15, -12.9, -12.9, -12.65
    ]
    #
    # buyers = removeSeconds(buyers, reduce_number)
    # sellers = removeSeconds(sellers, reduce_number)
    # mediators = removeSeconds(mediators, reduce_number)
    # mediatorsB = removeSeconds(mediatorsB, reduce_number)
    #
    # random.shuffle(buyers)
    # random.shuffle(sellers)
    # random.shuffle(mediators)
    # random.shuffle(mediatorsB)

    market = Market([
        AgentCategory("buyer", buyers),
        AgentCategory("seller", sellers),
        AgentCategory("mediator", mediators),
        AgentCategory("mediatorB", mediatorsB),
    ])
    without_gft0 = budget_balanced_trade_reduction(market, recipe, False)
    with_gft0 = budget_balanced_trade_reduction(market, recipe, True)
    print(without_gft0)
    print(with_gft0)

    without_count = without_gft0.num_of_deals()
    with_count = with_gft0.num_of_deals()
    without_gft = without_gft0.gain_from_trade()
    with_gft = with_gft0.gain_from_trade()

    print('Compare: Without:', without_gft, "With:", with_gft)
    print('Compare: Without:', without_count, "With:", with_count)
    break
Exemple #16
0
def experiment(results_csv_file: str, auction_function: Callable,
               auction_name: str, recipe: tuple, value_ranges: list,
               nums_of_agents: list, num_of_iterations: int):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.

    :param auction_function: the function for executing the auction under consideration.
    :param auction_name: title of the experiment, for printouts.
    :param nums_of_agents: a list of the numbers of agents with which to run the experiment.
    :param value_ranges: for each category, a pair (min_value,max_value). The value for each agent in this category is selected uniformly at random between min_value and max_value.
    :param num_of_iterations: how many times to repeat the experiment for each num of agents.
    """
    results_table = TeeTable(TABLE_COLUMNS, results_csv_file)
    recipe_str = ":".join(map(str, recipe))
    num_of_categories = len(recipe)
    for num_of_agents_per_category in nums_of_agents:
        sum_optimal_count = sum_auction_count = 0  # count the number of deals done in the optimal vs. the actual auction.
        sum_optimal_gft = sum_auction_total_gft = sum_auction_market_gft = 0
        for _ in range(num_of_iterations):
            market = Market([
                AgentCategory.uniformly_random(
                    "agent", num_of_agents_per_category * recipe[category],
                    value_ranges[category][0], value_ranges[category][1])
                for category in range(num_of_categories)
            ])
            (optimal_trade, _) = market.optimal_trade(recipe)
            auction_trade = auction_function(market, recipe)

            sum_optimal_count += optimal_trade.num_of_deals()
            sum_auction_count += auction_trade.num_of_deals()

            sum_optimal_gft += optimal_trade.gain_from_trade()
            sum_auction_total_gft += auction_trade.gain_from_trade(
                including_auctioneer=True)
            sum_auction_market_gft += auction_trade.gain_from_trade(
                including_auctioneer=False)

        # print("Num of times {} attains the maximum GFT: {} / {} = {:.2f}%".format(title, count_optimal_gft, num_of_iterations, count_optimal_gft * 100 / num_of_iterations))
        # print("GFT of {}: {:.2f} / {:.2f} = {:.2f}%".format(title, sum_auction_gft, sum_optimal_gft, 0 if sum_optimal_gft==0 else sum_auction_gft * 100 / sum_optimal_gft))
        results_table.add(
            OrderedDict((
                ("iterations", num_of_iterations),
                ("auction_name", auction_name),
                ("recipe", recipe_str),
                ("num_of_agents", num_of_agents_per_category),
                ("mean_optimal_count",
                 round(sum_optimal_count / num_of_iterations, 2)),
                ("mean_auction_count",
                 round(sum_auction_count / num_of_iterations, 2)),
                ("count_ratio", 0 if sum_optimal_count == 0 else int(
                    (sum_auction_count / sum_optimal_count) * 10000) / 100),
                ("mean_optimal_gft",
                 round(sum_optimal_gft / num_of_iterations, 2)),
                ("mean_auction_total_gft",
                 round(sum_auction_total_gft / num_of_iterations, 2)),
                ("total_gft_ratio", 0 if sum_optimal_gft == 0 else round(
                    sum_auction_total_gft / sum_optimal_gft * 100, 2)),
                ("mean_auction_market_gft",
                 round(sum_auction_market_gft / num_of_iterations, 2)),
                ("market_gft_ratio", 0 if sum_optimal_gft == 0 else round(
                    sum_auction_market_gft / sum_optimal_gft * 100, 2)),
            )))
    results_table.done()
from agents import AgentCategory
from old import ascending_auction_multirecipe_protocol
from old.ascending_auction_multirecipe_protocol import budget_balanced_ascending_auction
import prices

import logging
ascending_auction_multirecipe_protocol.logger.setLevel(logging.INFO)
prices.logger.setLevel(logging.INFO)

print(
    "\n\n###### TEST MULTI RECIPE AUCTION WITH TWO EQUIVALENT RECIPES: [1,0,1] [1,1,0]"
)

market = Market([
    AgentCategory("buyer", [17, 14, 13, 9, 6]),
    AgentCategory("sellerA", [-1, -3, -4, -5, -8, -10]),
    AgentCategory("sellerB", [-2, -7, -11]),
])

# print(budget_balanced_ascending_auction(market, [[1, 1, 0], [1, 0, 1]]))

print(
    "\n\n###### TEST MULTI RECIPE AUCTION WITH TWO DIFFERENT RECIPES: [1,1,0,0] [1,0,1,1]"
)

market = Market([
    AgentCategory("buyer", [17, 14, 13, 9, 6]),
    AgentCategory("seller", [-1, -3, -4, -5, -8, -10]),
    AgentCategory("producerA", [-1, -3, -5]),
    AgentCategory("producerB", [-1, -4, -6]),
])
Exemple #18
0
def mcafee_trade_reduction(market: Market,
                           ps_recipe: list,
                           price_heuristic=True):
    """
    Calculate the trade and prices using generalized-trade-reduction.
    :param market:   contains a list of k categories, each containing several agents.
    :param ps_recipe:  a list of integers, one integer per category.
                       Each integer i represents the number of agents of category i
                       that should be in each procurement-set.
    :param price_heuristic: whether to use the heuristic of setting the price to (s_{k+1)+b_{k+1})/2.
                            Default is true, as in the original paper.
    :return: Trade object, representing the trade and prices.

    >>> # ONE BUYER, ONE SELLER
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(mcafee_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(mcafee_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("seller", [-4.]), AgentCategory("buyer", [9.,8.])])
    >>> print(market); print(mcafee_trade_reduction(market, [1,1]))
    Traders: [seller: [-4.0], buyer: [9.0, 8.0]]
    No trade

    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.])])
    >>> print(market); print(mcafee_trade_reduction(market, [1,1]))
    Traders: [seller: [-3.0, -4.0], buyer: [9.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(mcafee_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(mcafee_trade_reduction(market, [1,1]))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    buyer: [9.0]: all 1 agents trade and pay 8.0
    seller: [-3.0]: all 1 agents trade and pay -4.0

    >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.,8.])])
    >>> print(mcafee_trade_reduction(market, [1,1]))
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0]: all 1 agents trade and pay 8.0

    >>> market = Market([AgentCategory("seller", [-8.,-3.]), AgentCategory("buyer", [9.,4.])])
    >>> print(mcafee_trade_reduction(market, [1,1]))
    seller: [-3.0]: all 1 agents trade and pay -6.0
    buyer: [9.0]: all 1 agents trade and pay 6.0

    #
    # >>>
    # >>> # ONE BUYER, ONE SELLER, ONE MEDIATOR
    # >>> market = Market([AgentCategory("seller", [-4.,-3.]), AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.])])
    # >>> print(market); print(mcafee_trade_reduction(market, [1,1,1]))
    # Traders: [seller: [-3.0, -4.0], buyer: [9.0, 8.0], mediator: [-1.0, -2.0]]
    # seller: [-3.0]: all 1 agents trade and pay -4.0
    # buyer: [9.0]: all 1 agents trade and pay 8.0
    # mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -4.0
    #
    # >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-10.])])
    # >>> print(market); print(mcafee_trade_reduction(market, [1,1,1]))
    # Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-3.0, -4.0, -10.0]]
    # buyer: [9.0]: all 1 agents trade and pay 8.0
    # mediator: [-1.0]: all 1 agents trade and pay -2.0
    # seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -6.0
    #
    # >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-5.])])
    # >>> print(market); print(mcafee_trade_reduction(market, [1,1,1]))
    # Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-3.0, -4.0, -5.0]]
    # buyer: [9.0]: all 1 agents trade and pay 8.0
    # mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -3.0
    # seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -5.0
    #
    # >>> market = Market([AgentCategory("buyer", [9.,8.]), AgentCategory("mediator", [-1.,-2.]), AgentCategory("seller", [-4.,-3.,-2.])])
    # >>> print(market); print(mcafee_trade_reduction(market, [1,1,1]))
    # Traders: [buyer: [9.0, 8.0], mediator: [-1.0, -2.0], seller: [-2.0, -3.0, -4.0]]
    # buyer: [9.0]: all 1 agents trade and pay 8.0
    # mediator: [-1.0, -2.0]: random 1 out of 2 agents trade and pay -4.0
    # seller: [-2.0, -3.0]: random 1 out of 2 agents trade and pay -4.0
    #
    # >>> market = Market([AgentCategory("buyer", [9.,8.,7.]), AgentCategory("mediator", [-1.,-2.,-3.]), AgentCategory("seller", [-4.,-3.,-2.])])
    # >>> print(market); print(mcafee_trade_reduction(market, [1,1,1]))
    # Traders: [buyer: [9.0, 8.0, 7.0], mediator: [-1.0, -2.0, -3.0], seller: [-2.0, -3.0, -4.0]]
    # buyer: [9.0, 8.0]: all 2 agents trade and pay 7.0
    # mediator: [-1.0, -2.0]: all 2 agents trade and pay -3.0
    # seller: [-2.0, -3.0]: all 2 agents trade and pay -4.0
    #
    # >>> market = Market([AgentCategory("buyer", [9.,8.,4.]), AgentCategory("mediator", [-1.,-2.,-3.]), AgentCategory("seller", [-4.,-3.,-2.])])
    # >>> print(market); print(mcafee_trade_reduction(market, [1,1,1]))
    # Traders: [buyer: [9.0, 8.0, 4.0], mediator: [-1.0, -2.0, -3.0], seller: [-2.0, -3.0, -4.0]]
    # buyer: [9.0, 8.0]: all 2 agents trade and pay 7.0
    # mediator: [-1.0, -2.0]: all 2 agents trade and pay -3.0
    # seller: [-2.0, -3.0]: all 2 agents trade and pay -4.0

    """
    if len(ps_recipe) != market.num_categories:
        raise ValueError(
            "There are {} categories but {} elements in the PS recipe".format(
                market.num_categories, len(ps_recipe)))

    if any(r != 1 for r in ps_recipe):
        raise ValueError(
            "Currently, the trade-reduction protocol supports only recipes of ones; {} was given"
            .format(ps_recipe))

    logger.info("\n#### McAfee Trade Reduction\n")
    logger.info(market)
    (optimal_trade, remaining_market) = market.optimal_trade(ps_recipe)
    for category in remaining_market.categories:
        if len(category) == 0:
            category.append(-MAX_VALUE)
    logger.info("Optimal trade, by increasing GFT: {}".format(optimal_trade))
    first_negative_ps = remaining_market.get_highest_agents(ps_recipe)
    if price_heuristic:
        price_candidate = sum([abs(x) for x in first_negative_ps
                               ]) / len(first_negative_ps)
        logger.info("First negative PS: {}, candidate price: {}".format(
            first_negative_ps, price_candidate))
    actual_traders = market.empty_agent_categories()

    if optimal_trade.num_of_deals() > 0:
        last_positive_ps = optimal_trade.procurement_sets[0]

        if price_heuristic and is_price_good_for_ps(price_candidate,
                                                    last_positive_ps):
            # All optimal traders trade in the candidate price - no reduction
            prices = [
                price_candidate * (-1 if last_positive_ps[i] < 0 else +1)
                for i in range(market.num_categories)
            ]

        else:
            # Trade reduction
            del optimal_trade.procurement_sets[0]
            prices = last_positive_ps

        for ps in optimal_trade.procurement_sets:
            for i in range(market.num_categories):
                if ps[i] is not None:
                    actual_traders[i].append(ps[i])
    else:
        prices = [0 for i in range(market.num_categories)]

    logger.info("\n")
    return TradeWithSinglePrice(actual_traders, ps_recipe, prices)
"""
Demonstration of a multiple-clock strongly-budget-balanced ascending auction
for a multi-lateral market with two buyers per three sellers (recipe: 2,3)

Author: Erel Segal-Halevi
Since:  2019-08
"""
import sys, os
sys.path.insert(0, os.path.abspath('..'))

from markets import Market
from agents import AgentCategory
import ascending_auction_protocol
from ascending_auction_protocol import budget_balanced_ascending_auction

import logging
ascending_auction_protocol.logger.setLevel(logging.INFO)

market = Market([
    AgentCategory("buyer", [20., 18., 16., 9., 2., 1.]),
    AgentCategory("seller", [-2., -4., -6., -8., -10., -12., -14.]),
])

print(budget_balanced_ascending_auction(market, [2, 3]))
# Here, k=1, and the final trade involves 2 buyers and 5 sellers
# (so 3 sellers should be selected at random).

print(budget_balanced_ascending_auction(market, [3, 2]))
# Here, k=1, and the final trade involves 5 buyers and 3 sellers
#  (so 3 buyers and 2 sellers should be selected at random).
def experiment(results_csv_file: str,
               auction_functions: list,
               auction_names: str,
               recipe: tuple,
               nums_of_agents=None,
               stocks_prices: list = None,
               stock_names: list = None,
               num_of_iterations=1000,
               run_with_stock_prices=True,
               report_diff=False):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.
    :param results_csv_file: the experiment result file.
    :param auction_functions: list of functions for executing the auction under consideration.
    :param auction_names: titles of the experiment, for printouts.
    :param recipe: can be any vector of ones, e.g. (1,1,1), for our trade-reduction mechanism, or any vector of positive integers for our ascending-auction mechanism.
    :param stocks_prices: list of prices for each stock and each agent.
    :param stock_names: list of stocks names which prices are belongs, for naming only.
    """
    TABLE_COLUMNS = [
        "iterations", "stockname", "recipe", "numpossibletrades",
        "optimalcount", "gftratioformula", "optimalcountwithgftzero",
        "optimalgft", "optimalgftwithgftzero"
    ]
    AUCTION_COLUMNS = [
        "count", "countratio", "totalgft", "totalgftratio",
        "withoutgftzerocountratio", "withoutgftzerototalgft",
        "withoutgftzerototalgftratio", "marketgft", "marketgftratio"
    ]

    if path.exists(results_csv_file):
        print('The file', results_csv_file, 'already exists, skipping')
        return
    else:
        print('Running for the file', results_csv_file)
    if stocks_prices is None:
        (stocks_prices, stock_names) = getStocksPricesShuffled()
    column_names = TABLE_COLUMNS
    column_names += [
        auction_name + column for auction_name in auction_names
        for column in AUCTION_COLUMNS
    ]
    results_table = TeeTable(column_names, results_csv_file)
    recipe_str = ":".join(map(str, recipe))
    recipe_sum = sum(recipe)
    recipe_sum_for_buyer = (recipe_sum - recipe[0]) / recipe[0]
    if nums_of_agents is None:
        nums_of_agents = [10000000]
    #print(nums_of_agents)
    total_results = {}
    for num_of_agents_per_category in nums_of_agents:
        total_results[str(num_of_agents_per_category)] = []
    #print(total_results)
    for i in range(len(stocks_prices)):
        stock_prices = stocks_prices[i]
        for num_of_possible_ps in nums_of_agents:
            for iteration in range(num_of_iterations):
                categories = []
                if run_with_stock_prices:
                    while len(stock_prices) < num_of_possible_ps * recipe_sum:
                        stock_prices = stock_prices + stock_prices
                    random.shuffle(stock_prices)
                    index = 0
                    for category in recipe:
                        next_index = index + num_of_possible_ps * category
                        price_sign = recipe_sum_for_buyer if index == 0 else -1
                        #price_value_multiple = -1 * buyer_agent_count if index > 0 else recipe_sum - buyer_agent_count
                        categories.append(
                            AgentCategory("agent", [
                                int(price * price_sign)
                                for price in stock_prices[index:next_index]
                            ]))
                        index = next_index
                else:  #prices from random.
                    for index in range(len(recipe)):
                        #for category in recipe:
                        min_value = -100000 if index > 0 else recipe_sum_for_buyer
                        max_value = -1 if index > 0 else 100000 * recipe_sum_for_buyer
                        categories.append(
                            AgentCategory.uniformly_random(
                                "agent", num_of_possible_ps * recipe[index],
                                min_value, max_value))
                market = Market(categories)
                (optimal_trade,
                 _) = market.optimal_trade(ps_recipe=list(recipe),
                                           max_iterations=10000000,
                                           include_zero_gft_ps=False)
                optimal_count = optimal_trade.num_of_deals()
                optimal_gft = optimal_trade.gain_from_trade()
                (optimal_trade_with_gft_zero,
                 _) = market.optimal_trade(ps_recipe=list(recipe),
                                           max_iterations=10000000)
                optimal_count_with_gft_zero = optimal_trade_with_gft_zero.num_of_deals(
                )
                optimal_gft_with_gft_zero = optimal_trade_with_gft_zero.gain_from_trade(
                )

                results = [
                    ("iterations", num_of_iterations),
                    ("stockname", stock_names[i]), ("recipe", recipe_str),
                    ("numpossibletrades", int(num_of_possible_ps)),
                    ("optimalcount", optimal_count),
                    ("gftratioformula", (optimal_count - 1) * 100 /
                     (optimal_count if min(recipe) == max(recipe)
                      and recipe[0] == 1 else optimal_count + 1)
                     if optimal_count > 1 else 0),
                    ("optimalcountwithgftzero", optimal_count_with_gft_zero),
                    ("optimalgft", optimal_gft),
                    ("optimalgftwithgftzero", optimal_gft_with_gft_zero)
                ]
                for auction_index in range(len(auction_functions)):
                    auction_trade = auction_functions[auction_index](market,
                                                                     recipe)
                    count = auction_trade.num_of_deals()
                    total_gft = auction_trade.gain_from_trade(
                        including_auctioneer=True)
                    market_gft = auction_trade.gain_from_trade(
                        including_auctioneer=False)
                    auction_name = auction_names[auction_index]
                    results.append(
                        (auction_name + "count", auction_trade.num_of_deals()))

                    results.append(
                        (auction_name + "countratio",
                         0 if optimal_count == 0 else
                         (count / optimal_count_with_gft_zero) * 100))
                    results.append((auction_name + "totalgft", total_gft))
                    results.append((auction_name + "totalgftratio",
                                    0 if optimal_gft == 0 else total_gft /
                                    optimal_gft_with_gft_zero * 100))
                    results.append((auction_name + "marketgft", market_gft))
                    results.append((auction_name + "marketgftratio",
                                    0 if optimal_gft == 0 else market_gft /
                                    optimal_gft_with_gft_zero * 100))
                    results.append((auction_name + "withoutgftzerocountratio",
                                    0 if optimal_count == 0 else
                                    (count / optimal_count) * 100))
                    results.append(
                        (auction_name + "withoutgftzerototalgft", total_gft))
                    results.append(
                        (auction_name + "withoutgftzerototalgftratio",
                         0 if optimal_gft == 0 else total_gft / optimal_gft *
                         100))
                #We check which auction did better and print the market and their results.
                if report_diff:
                    gft_to_compare = -1
                    k_to_compare = -1
                    gft_found = False
                    k_found = False
                    for (label, value) in results:
                        if 'SBB' in label:
                            if gft_found is False and label.endswith(
                                    'totalgft'):
                                if gft_to_compare < 0:
                                    gft_to_compare = value
                                elif gft_to_compare != value:
                                    with open('diff_in_sbbs_gft.txt',
                                              'a') as f:
                                        f.write(
                                            'There is diff in gft between two auctions: '
                                            + str(gft_to_compare) + ' ' +
                                            str(value) + '\n')
                                        f.write(str(results) + '\n')
                                        if num_of_possible_ps < 10:
                                            f.write(str(market) + '\n')
                                    gft_found = True
                            elif k_found is False and label.endswith('count'):
                                if k_to_compare < 0:
                                    k_to_compare = value
                                elif k_to_compare != value:
                                    with open('diff_in_sbbs_k.txt', 'a') as f:
                                        f.write(
                                            'There is diff in gft between two auctions: '
                                            + str(k_to_compare) + ' ' +
                                            str(value) + '\n')
                                        f.write(str(results) + '\n')
                                        if num_of_possible_ps < 10:
                                            f.write(str(market) + '\n')
                                    k_found = True
                compare_sbbs = True
                if compare_sbbs:
                    gft_to_compare = -1
                    k_to_compare = -1
                    gft_found = False
                    k_found = False
                    for (label, value) in results:
                        if 'SBB' in label:
                            if gft_found is False and label.endswith(
                                    'totalgft'):
                                if gft_to_compare < 0:
                                    gft_to_compare = value
                                elif gft_to_compare > value:
                                    with open('diff_in_sbbs_gft.txt',
                                              'a') as f:
                                        f.write(
                                            'There is diff in gft between two auctions: '
                                            + str(gft_to_compare) + ' ' +
                                            str(value) + '\n')
                                        f.write(str(results) + '\n')
                                        if num_of_possible_ps < 10:
                                            f.write(str(market) + '\n')
                                    gft_found = True
                            elif k_found is False and label.endswith('count'):
                                if k_to_compare < 0:
                                    k_to_compare = value
                                elif k_to_compare > value:
                                    with open('diff_in_sbbs_k.txt', 'a') as f:
                                        f.write(
                                            'There is diff in gft between two auctions: '
                                            + str(k_to_compare) + ' ' +
                                            str(value) + '\n')
                                        f.write(str(results) + '\n')
                                        if num_of_possible_ps < 10:
                                            f.write(str(market) + '\n')
                                    k_found = True
                #results_table.add(OrderedDict(results))
                #print(results)
                if len(total_results[str(num_of_possible_ps)]) == 0:
                    total_results[str(
                        num_of_possible_ps)] = results[0:len(results)]
                else:
                    sum_result = total_results[str(num_of_possible_ps)]
                    for index in range(len(results)):
                        if index > 3:
                            sum_result[index] = (results[index][0],
                                                 sum_result[index][1] +
                                                 results[index][1])
            #print(total_results)
        print(stock_names[i], end=',')
        #break
    print()
    division_number = num_of_iterations * len(stocks_prices)
    #division_number = num_of_iterations
    for num_of_possible_ps in nums_of_agents:
        results = total_results[str(num_of_possible_ps)]
        for index in range(len(results)):
            if 'gftratio' in results[index][0]:
                results[index] = (results[index][0],
                                  padding_zeroes(
                                      results[index][1] / division_number, 3))
            elif index > 3:
                results[index] = (results[index][0],
                                  padding_zeroes(
                                      results[index][1] / division_number, 2))
            elif index == 1:
                results[index] = (results[index][0], 'Average')
        #print(results)
        results_table.add(OrderedDict(results))
    results_table.done()
#     AgentCategory("sellerB", [-3, -4]),
# ])
# print(budget_balanced_ascending_auction_twolevels(market, [1, 1 ,1]))

# print("\n\n###### TWO NON-BINARY RECIPES: [1,2,0], [1,0,2]")
# market = Market([
#     AgentCategory("buyer", [19,18,17,16,15,14,13]),
#     AgentCategory("sellerA", [-1, -2]),
#     AgentCategory("sellerB", [-3, -4]),
# ])
# print(budget_balanced_ascending_auction_twolevels(market, [1, 2, 2]))

print("\n\n###### COUNTER-EXAMPLE FOR CEILING CHILDREN")
num_of_seller_categories = 8
market = Market(
    [AgentCategory("buyer", [101] * 10 * (num_of_seller_categories + 1))] +
    [AgentCategory("producer", [-50] * 20)] +
    [AgentCategory("seller", [-100] + [-2] * 21)] * num_of_seller_categories)
# print(budget_balanced_ascending_auction_twolevels(market, [1, 2] + [2] * num_of_seller_categories))

print("\n\n###### DVIR'S EXAMPLE BINARY")
num_of_seller_categories = 8
market = Market([AgentCategory("buyer", [100] * num_of_seller_categories)] + [
    AgentCategory("seller", [-80 + i]) for i in range(num_of_seller_categories)
])
print(
    budget_balanced_ascending_auction_twolevels(
        market, [1] + [1] * num_of_seller_categories))

ascending_auction_recipetree_twolevels_protocol.logger.setLevel(logging.INFO)
prices.logger.setLevel(logging.INFO)
print("\n\n###### DVIR'S EXAMPLE")
Exemple #22
0
from markets import Market

if __name__ == '__main__':

    m = Market()
    hd = m.full_historic_data()
    hd['next_price'] = hd['price'].shift(-1)  #target
    hd['increase'] = (hd['next_price'] - hd['price']
                      ) / hd['price'] > 0.01  #increase at least by 1%

    features = [
        'date', 'volume24', 'marketCap', 'availableSupplyNumber', 'price',
        'next_price', 'increase'
    ]

    hd[features].to_csv('~/Data/crypto/ETH.csv', index=False)

    print "done"
# market = Market([
#     AgentCategory("buyer",   [20, 19, 18, 17, 16, 15]),
#     AgentCategory("seller",   [-1, -2, -5, -6, -9, -10]),
#     AgentCategory("producerA", [-4, -8]),
#     AgentCategory("producerB", [-3, -7]),
# ])
# print(budget_balanced_ascending_auction(market, recipes_1100_1011))

import sys, os; sys.path.insert(0, os.path.abspath('..'))


print("\n\n###### TEST MULTI RECIPE AUCTION - 4 PATHS")

market = Market([
    AgentCategory("C0", [400, 300, 200, 100]),
    AgentCategory("C1", [-1, -11]),
    AgentCategory("C2", [-2, -22]),
    AgentCategory("C3", [-3, -33]),
    AgentCategory("C4", [-4, -44]),
    AgentCategory("C5", [-5, -55]),
    AgentCategory("C6", [-6, -66]),
])
recipes_4paths = [0, [1, [2, None, 3, None], 4, [5, None, 6, None]]]
# The recipes are:
#     1,1,1,0,0,0,0   [C0, C1, C2]
#     1,1,0,1,0,0,0   [C0, C1, C3]
#     1,0,0,0,1,1,0   [C0, C4, C5]
#     1,0,0,0,1,0,1   [C0, C4, C6]

print(budget_balanced_ascending_auction(market, recipes_4paths))
Exemple #24
0
    buyers = randomArray(2, 20, 10)
    sellers = randomArray(-10, -1, 20)

    #
    # buyers = [135.9, 136.7999, 143.5499, 135.9, 136.7999, 143.5499, 144.0]
    # sellers = [-135.9, -136.7999, -143.5499, -18.95, -18.9, -17.95, -17.9, -17.7999, -17.7999, -17.2999, -17.0, -16.95, -16.7999, -15.15, -15.0, -15.0, -14.9, -14.2, -14.2, -14.1, -13.95]
    #
    # random.shuffle(buyers)
    # random.shuffle(sellers)
    #
    # buyers = removeSeconds(buyers, reduce_number)
    # sellers = removeSeconds(sellers, reduce_number)

    market = Market([
        AgentCategory("buyer",    buyers),
        AgentCategory("seller",   sellers),
    ])
    without_gft0 = budget_balanced_trade_reduction(market, recipe, False)
    with_gft0 = budget_balanced_trade_reduction(market, recipe, True)
    print(without_gft0)
    print(with_gft0)

    without_count = without_gft0.num_of_deals()
    with_count = with_gft0.num_of_deals()
    without_gft = without_gft0.gain_from_trade()
    with_gft = with_gft0.gain_from_trade()

    print('Compare: Without:', without_gft, "With:", with_gft)
    print('Compare: Without:', without_count, "With:", with_count)
    if without_count != with_count and with_gft != without_gft:
        print("Reached end")
Exemple #25
0
Since:  2019-11
"""
import sys, os
sys.path.insert(0, os.path.abspath('..'))

from markets import Market
from agents import AgentCategory
import ascending_auction_protocol
from ascending_auction_protocol import budget_balanced_ascending_auction

import logging
ascending_auction_protocol.logger.setLevel(logging.INFO)

print("\n\n###### RUNNING EXAMPLE 3 FROM THE PAPER FOR TYPE (2,2,3)")

market = Market([
    AgentCategory("buyer", [17, 16, 15, 14, 13, 12, 10, 6]),
    AgentCategory("mediator", [-3, -4, -5, -6, -7, -8, -9, -10]),
    AgentCategory("seller", [-1, -2, -3, -4, -5, -6, -7, -8]),
])
# print(budget_balanced_ascending_auction(market, [2,2,3]))

print("\n\n###### OTHER EXAMPLES FOR (2,2,3)")

market = Market([
    AgentCategory("buyer", [17, 16, 15, 14, 13, 12, 10, 6]),
    AgentCategory("mediator", [-3, -4, -5, -6, -7, -8, -9, -10]),
    AgentCategory("seller", [-1, -2, -3, -4, -5, -6, -7, -8]),
])
print(budget_balanced_ascending_auction(market, [2, 2, 3]))
def budget_balanced_ascending_auction(
        market: Market,
        ps_recipe_struct: List[Any],
        agent_counts: List[int] = None) -> TradeWithMultipleRecipes:
    """
    Calculate the trade and prices using generalized-ascending-auction.
    Allows multiple recipes, but they must be represented by a *recipe tree*.

    :param market:           contains a list of k categories, each containing several agents.
    :param ps_recipe_struct: a nested list of integers. Each integer represents a category-index.
                             The nested list represents a tree, where each path from root to leaf represents a recipe.
                             For example: [0, [1, None]] is a single recipe with categories {0,1}.
                                    [0, [1, None, 2, None]] is two recipes with categories {0,1} and {0,2}.

    :return: Trade object, representing the trade and prices.

    >>> logger.setLevel(logging.DEBUG)
    >>> # ONE BUYER, ONE SELLER
    >>> recipe_11 = [0, [1, None]]
    >>> agent_counts = [1, 2, 1, 2]
    >>> recipe_1100_1011 = [0, [1, None, 2, [3, None]]]
    >>>
    >>> market = Market([AgentCategory("buyer", [101,101,101,101,101]), AgentCategory("seller1", [-1,-90]), AgentCategory("seller2", [-1,-90]), AgentCategory("seller", [-50, -50, -50, -50, -50, -50])])
    >>> RecipeTree(market.categories, [0, [1, None, 2, None, 3, None]], [1,2,2,2]).optimal_trade_with_counters()
    ([([101, -1, -90], {'counter': 1, 'index': 1}), ([101, -1, -90], {'counter': 1, 'index': 2}), ([101, -50, -50], {'counter': 3, 'index': 3}), ([101, -50, -50], {'counter': 3, 'index': 3}), ([101, -50, -50], {'counter': 3, 'index': 3})], 5, 23, 1, 3, {'1': 1, '2': 1, '3': 3})
    >>> print(market); print(budget_balanced_ascending_auction(market, [0, [1, None, 2, None, 3, None]], [1,2,2,2]))
    Traders: [buyer: [101, 101, 101, 101, 101], seller1: [-1, -90], seller2: [-1, -90], seller: [-50, -50, -50, -50, -50, -50]]
    seller1: 0 potential deals, price=-50.5
    seller2: 0 potential deals, price=-50.5
    seller: 3 potential deals, price=-50.5
    buyer: all 3 remaining traders selected, price=101.0
    (seller1 + seller2 + seller): all 3 remaining deals selected
    3 deals overall

    >>> market = Market([AgentCategory("buyer", [101,101,101]), AgentCategory("seller1", [-1,-90]), AgentCategory("seller2", [-1,-90]), AgentCategory("seller", [-50, -50, -50, -50, -50, -50])])
    >>> RecipeTree(market.categories, [0, [1, None, 2, None, 3, None]], [1,2,2,2]).optimal_trade_with_counters()
    ([([101, -1, -90], {'counter': 1, 'index': 1}), ([101, -1, -90], {'counter': 1, 'index': 2}), ([101, -50, -50], {'counter': 1, 'index': 3})], 3, 21, 1, 1, {'1': 1, '2': 1, '3': 1})
    >>> print(market); print(budget_balanced_ascending_auction(market, [0, [1, None, 2, None, 3, None]], [1,2,2,2]))
    Traders: [buyer: [101, 101, 101], seller1: [-1, -90], seller2: [-1, -90], seller: [-50, -50, -50, -50, -50, -50]]
    seller1: 0 potential deals, price=-50.0
    seller2: 0 potential deals, price=-50.0
    seller: 2 potential deals, price=-50.0
    buyer: 2 out of 3 remaining traders selected, price=100.0
    (seller1 + seller2 + seller): all 2 remaining deals selected
    2 deals overall


    >>> market = Market([AgentCategory("buyer", [27.,21., 17.,11., 3.]), AgentCategory("seller", [-4.0, -5.0, -11.0]), AgentCategory("A", [-2.0, -3.0, -11.0]), AgentCategory("B", [-1.0, -2.0, -8.0])])
    >>> RecipeTree(market.categories, recipe_1100_1011, agent_counts).optimal_trade_with_counters()
    ([([27.0, -2.0, -1.0, -2.0], {'counter': 1, 'index': 3}), ([21.0, -4.0, -5.0], {'counter': 1, 'index': 1})], 2, 34.0, 1, 1, {'3': 1, '1': 1})
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_1100_1011, agent_counts))
    Traders: [buyer: [27.0, 21.0, 17.0, 11.0, 3.0], seller: [-4.0, -5.0, -11.0], A: [-2.0, -3.0, -11.0], B: [-1.0, -2.0, -8.0]]
    seller: 1 potential deals, price=-8.5
    B: 1 potential deals, price=-7.0
    A: all 1 remaining traders selected, price=-3.0
    (B): all 1 remaining deals selected
    buyer: all 2 remaining traders selected, price=17.0
    (seller + A): all 2 remaining deals selected
    2 deals overall

    >>> market = Market([AgentCategory("buyer", [17.,11.]), AgentCategory("seller", [-5.0]), AgentCategory("A", [-3.0]), AgentCategory("B", [-2.0])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_1100_1011))
    Traders: [buyer: [17.0, 11.0], seller: [-5.0], A: [-3.0], B: [-2.0]]
    No trade


    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    seller: 1 potential deals, price=-8.0
    buyer: all 1 remaining traders selected, price=8.0
    (seller): all 1 remaining deals selected
    1 deals overall

    >>> logger.setLevel(logging.WARNING)
    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    No trade

    >>> logger.setLevel(logging.WARNING)
    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    seller: 1 potential deals, price=-4.0
    buyer: 1 out of 2 remaining traders selected, price=4.0
    (seller): all 1 remaining deals selected
    1 deals overall

    """

    logger.info("\n#### Multi-Recipe Budget-Balanced Ascending Auction\n")
    logger.info(market)
    logger.info("Procurement-set recipe struct: {}".format(ps_recipe_struct))
    logger.info("Procurement-set recipe agent counts: {}".format(agent_counts))

    remaining_market = market.clone()
    recipe_tree = RecipeTree(remaining_market.categories, ps_recipe_struct,
                             agent_counts)
    logger.info("Tree of recipes: {}".format(recipe_tree.paths_to_leaf()))
    ps_recipes = recipe_tree.recipes()
    logger.info("Procurement-set recipes: {}".format(ps_recipes))

    optimal_trade, optimal_count, optimal_GFT = recipe_tree.optimal_trade()
    logger.info("For comparison, the optimal trade has k=%d, GFT=%f: %s\n",
                optimal_count, optimal_GFT, optimal_trade)
    # optimal_trade = market.optimal_trade(ps_recipe)[0]

    #### STOPPED HERE

    prices = SimultaneousAscendingPriceVectors(ps_recipes, -MAX_VALUE,
                                               agent_counts)
    while True:
        largest_category_size, combined_category_size, indices_of_prices_to_increase = recipe_tree.largest_categories(
            indices=True)
        logger.info("\n")
        logger.info(remaining_market)
        logger.info(
            "Largest category indices are %s. Largest category size = %d, combined category size = %d",
            indices_of_prices_to_increase, largest_category_size,
            combined_category_size)

        if combined_category_size == 0:
            logger.info("\nCombined category size is 0 - no trade!")
            logger.info("  Final price-per-unit vector: %s", prices)
            logger.info(remaining_market)
            return TradeWithMultipleRecipes(
                remaining_market.categories, recipe_tree,
                prices.map_category_index_to_price(), agent_counts)

        increases = []
        for category_index in indices_of_prices_to_increase:
            category = remaining_market.categories[category_index]
            target_price = category.lowest_agent_value(
            ) if category.size() > 0 else MAX_VALUE
            increases.append((category_index, target_price, category.name))

        logger.info("Planned price-increases: %s", increases)
        prices.increase_prices(increases)
        map_category_index_to_price = prices.map_category_index_to_price()

        if prices.status == PriceStatus.STOPPED_AT_ZERO_SUM:
            logger.info("\nPrice crossed zero.")
            logger.info("  Final price-per-unit vector: %s",
                        map_category_index_to_price)
            logger.info(remaining_market)
            return TradeWithMultipleRecipes(remaining_market.categories,
                                            recipe_tree,
                                            map_category_index_to_price,
                                            agent_counts)

        remove_lowest_agent(market, remaining_market,
                            map_category_index_to_price,
                            indices_of_prices_to_increase)
def budget_balanced_ascending_auction(
        market: Market,
        ps_recipe_struct: List[Any]) -> TradeWithMultipleRecipes:
    """
    Calculate the trade and prices using generalized-ascending-auction.
    Allows multiple recipes, but they must be represented by a *recipe tree*.

    :param market:           contains a list of k categories, each containing several agents.
    :param ps_recipe_struct: a nested list of integers. Each integer represents a category-index.
                             The nested list represents a tree, where each path from root to leaf represents a recipe.
                             For example: [0, [1, None]] is a single recipe with categories {0,1}.
                                    [0, [1, None, 2, None]] is two recipes with categories {0,1} and {0,2}.

    :return: Trade object, representing the trade and prices.

    >>> logger.setLevel(logging.WARNING)
    >>> # ONE BUYER, ONE SELLER
    >>> recipe_11 = [0, [1, None]]
    >>>
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    seller: 1 potential deals, price=-8.0
    buyer: all 1 traders selected, price=8.0
    seller: all 1 traders selected
    1 deals overall

    >>> logger.setLevel(logging.WARNING)
    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    No trade

    >>> logger.setLevel(logging.WARNING)
    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, recipe_11))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    seller: 1 potential deals, price=-4.0
    buyer: 1 out of 2 traders selected, price=4.0
    seller: all 1 traders selected
    1 deals overall
    """
    logger.info("\n#### Multi-Recipe Budget-Balanced Ascending Auction\n")
    logger.info(market)
    logger.info("Procurement-set recipe struct: {}".format(ps_recipe_struct))

    remaining_market = market.clone()
    recipe_tree = RecipeTree(remaining_market.categories, ps_recipe_struct)
    logger.info("Tree of recipes: {}".format(recipe_tree.paths_to_leaf()))
    ps_recipes = recipe_tree.recipes()
    logger.info("Procurement-set recipes: {}".format(ps_recipes))

    optimal_trade, optimal_count, optimal_GFT = recipe_tree.optimal_trade()
    logger.info("For comparison, the optimal trade has k=%d, GFT=%f: %s\n",
                optimal_count, optimal_GFT, optimal_trade)
    # optimal_trade = market.optimal_trade(ps_recipe)[0]

    #### STOPPED HERE

    prices = SimultaneousAscendingPriceVectors(ps_recipes, -MAX_VALUE)
    while True:
        largest_category_size, combined_category_size, indices_of_prices_to_increase = recipe_tree.largest_categories(
            indices=True)
        logger.info("\n")
        logger.info(remaining_market)
        logger.info(
            "Largest category indices are %s. Largest category size = %d, combined category size = %d",
            indices_of_prices_to_increase, largest_category_size,
            combined_category_size)

        if combined_category_size == 0:
            logger.info("\nCombined category size is 0 - no trade!")
            logger.info("  Final price-per-unit vector: %s", prices)
            logger.info(remaining_market)
            return TradeWithMultipleRecipes(
                remaining_market.categories, recipe_tree,
                prices.map_category_index_to_price())

        increases = []
        for category_index in indices_of_prices_to_increase:
            category = remaining_market.categories[category_index]
            target_price = category.lowest_agent_value(
            ) if category.size() > 0 else MAX_VALUE
            increases.append((category_index, target_price, category.name))

        logger.info("Planned price-increases: %s", increases)
        prices.increase_prices(increases)
        map_category_index_to_price = prices.map_category_index_to_price()

        if prices.status == PriceStatus.STOPPED_AT_ZERO_SUM:
            logger.info("\nPrice crossed zero.")
            logger.info("  Final price-per-unit vector: %s",
                        map_category_index_to_price)
            logger.info(remaining_market)
            return TradeWithMultipleRecipes(remaining_market.categories,
                                            recipe_tree,
                                            map_category_index_to_price)

        for category_index in range(market.num_categories):
            category = remaining_market.categories[category_index]
            if map_category_index_to_price[category_index] is not None \
                and category.size()>0 \
                and category.lowest_agent_value() <= map_category_index_to_price[category_index]:
                category.remove_lowest_agent()
                logger.info("{} after: {} agents remain".format(
                    category.name, category.size()))
"""

from markets import Market
from agents import AgentCategory
import ascending_auction_protocol
from ascending_auction_protocol import budget_balanced_ascending_auction

import logging
ascending_auction_protocol.logger.setLevel(logging.INFO)

ps_recipe = [1,1,1]

print("\n\n###### RUNNING EXAMPLE FROM THE PAPER FOR TYPE (1,1,1)")
# Price stops between buyers, k=3:
market = Market([
    AgentCategory("buyer",    [17, 14, 13, 9, 6]),
    AgentCategory("seller",   [-1, -4, -5, -8, -11]),
    AgentCategory("mediator", [-1, -3, -4, -7, -10])])
print(budget_balanced_ascending_auction(market, ps_recipe))


print("\n\n###### SIMILAR EXAMPLE, WHERE PRICE STOPS BETWEEN SELLERS:")
market = Market([
    AgentCategory("buyer",    [17, 14, 13, 9, 6]),
    AgentCategory("seller",   [-1, -4, -5, -8, -11]),
    AgentCategory("mediator", [-1, -3, -6, -7, -10])])
print(budget_balanced_ascending_auction(market, ps_recipe))

print("\n\n###### SIMILAR EXAMPLE, WHERE PRICE STOPS BETWEEN MEDIATORS:")
market = Market([
    AgentCategory("buyer",    [17, 14, 13, 9, 6]),
    AgentCategory("seller",   [-1, -4, -6.5, -8, -11]),
Exemple #29
0
def experiment(results_csv_file: str, recipe: list, value_ranges: list,
               nums_of_agents: list, num_of_iterations: int,
               agent_counts: list, agent_values: list,
               agent_counts_integer: list):
    """
    Run an experiment similar to McAfee (1992) experiment on the given auction.
    :param results_csv_file: the experiment result file.
    :param auction_functions: list of functions for executing the auction under consideration.
    :param auction_names: titles of the experiment, for printouts.
    :param recipe: can be any vector of ones, e.g. (1,1,1), for our trade-reduction mechanism, or any vector of positive integers for our ascending-auction mechanism.
    :param nums_of_agents: list of n(s) for number of possible trades to make the calculations.
    :param stocks_prices: list of prices for each stock and each agent.
    :param stock_names: list of stocks names which prices are belongs, for naming only.
    """
    TABLE_COLUMNS = [
        "iterations", "recipe", "numofagents", "optimalcount", "optimalkmin",
        "optimalkmax", "gftformula", "auctioncount", "auctionkmin",
        "auctionkmax", "countratio", "optimalgft", "auctiongft", "gftratio"
    ]
    GFT_ROUND = 1
    K_ROUND = 2
    RATIO_ROUND = 3
    print('recipe:', recipe)
    results_table = TeeTable(TABLE_COLUMNS, results_csv_file)
    results_table_integer = TeeTable(TABLE_COLUMNS,
                                     results_csv_file[0:-4] + '_integer.csv')
    recipe_str = str(recipe).replace(',', '-')
    category_size_list = get_agents_analyze(recipe)
    for i in range(len(nums_of_agents)):
        sum_optimal_count = sum_auction_count = sum_optimal_kmin = sum_optimal_kmax = 0  # count the number of deals done in the optimal vs. the actual auction.
        sum_optimal_gft = sum_auction_total_gft = sum_auction_kmin = sum_auction_kmax = 0
        sum_optimal_count_integer = sum_auction_count_integer = sum_optimal_kmin_integer = sum_optimal_kmax_integer = 0  # count the number of deals done in the optimal vs. the actual auction.
        sum_optimal_gft_integer = sum_auction_total_gft_integer = sum_auction_kmin_integer = sum_auction_kmax_integer = 0

        for iteration in range(num_of_iterations):
            if iteration % 10000 == 0:
                print('iteration:', iteration)
            agents = []
            agents_integer = []
            for category in range(len(category_size_list)):
                sign = 0 if category == 0 else 1
                agent_category = AgentCategory.uniformly_random(
                    "agent", int(nums_of_agents[i] * agent_counts[category]),
                    value_ranges[sign][0] * agent_values[category],
                    value_ranges[sign][1] * agent_values[category])
                agents.append(agent_category)
                agents_integer.append(
                    AgentCategory(
                        agent_category.name,
                        agent_category.values + agent_category.values))
                #agents.append(AgentCategory.uniformly_random("agent", nums_of_agents[i], value_ranges[sign][0], value_ranges[sign][1]))
            market = Market(agents)
            market_integer = Market(agents_integer)

            #print(agents)
            recipe_tree = RecipeTree(market.categories, recipe)
            recipe_tree_integer = RecipeTree(market_integer.categories, recipe,
                                             agent_counts_integer)
            optimal_trade, optimal_count, optimal_gft, kmin, kmax = recipe_tree.optimal_trade_with_counters(
            )
            optimal_trade_integer, optimal_count_integer, optimal_gft_integer, kmin_integer, kmax_integer = recipe_tree_integer.optimal_trade_with_counters(
            )
            #print('optimal trade:', optimal_trade, optimal_count, optimal_gft)
            auction_trade = budget_balanced_ascending_auction(market, recipe)
            auction_trade_integer = budget_balanced_ascending_auction(
                market_integer, recipe, agent_counts_integer)
            auction_count = auction_trade.num_of_deals()
            auction_count_integer = auction_trade_integer.num_of_deals()
            auction_kmin = auction_trade.min_num_of_deals()
            auction_kmin_integer = auction_trade_integer.min_num_of_deals()
            auction_kmax = auction_trade.max_num_of_deals()
            auction_kmax_integer = auction_trade_integer.max_num_of_deals()
            gft = auction_trade.gain_from_trade()
            gft_integer = auction_trade_integer.gain_from_trade()
            #if optimal_count > 0 and gft < optimal_gft * (1 - 1/optimal_count):
            #the auction count is less more than 1 than the optimal count.
            #    print('Warning GFT!!!', 'optimal_count:', optimal_count, 'auction_count:', auction_count,
            #          'num_of_possible_ps:', nums_of_agents[i], 'optimal_gft:', optimal_gft, 'gft:', gft)
            #    if nums_of_agents[i] < 20:
            #        print(market.categories)

            sum_optimal_count += optimal_count
            sum_optimal_count_integer += optimal_count_integer
            sum_auction_count += auction_count
            sum_auction_count_integer += auction_count_integer

            sum_optimal_kmin += kmin
            sum_optimal_kmin_integer += kmin_integer
            sum_optimal_kmax += kmax
            sum_optimal_kmax_integer += kmax_integer

            sum_auction_kmin += auction_kmin
            sum_auction_kmin_integer += auction_kmin_integer
            sum_auction_kmax += auction_kmax
            sum_auction_kmax_integer += auction_kmax_integer

            sum_optimal_gft += optimal_gft
            sum_optimal_gft_integer += optimal_gft_integer
            sum_auction_total_gft += gft
            sum_auction_total_gft_integer += gft_integer

            if auction_count < optimal_count - 2:
                #the auction count is less more than 1 than the optimal count.
                print('Warning!!!', 'optimal_count:', optimal_count,
                      'auction_count:', auction_count, 'num_of_possible_ps:',
                      nums_of_agents[i])
                if nums_of_agents[i] < 10:
                    print(market.categories)

        # print("Num of times {} attains the maximum GFT: {} / {} = {:.2f}%".format(title, count_optimal_gft, num_of_iterations, count_optimal_gft * 100 / num_of_iterations))
        # print("GFT of {}: {:.2f} / {:.2f} = {:.2f}%".format(title, sum_auction_gft, sum_optimal_gft, 0 if sum_optimal_gft==0 else sum_auction_gft * 100 / sum_optimal_gft))
        kmin_mean = sum_optimal_kmin / num_of_iterations
        kmin_mean_integer = sum_optimal_kmin_integer / num_of_iterations

        results_table.add(
            OrderedDict([
                ("iterations", num_of_iterations),
                ("recipe", recipe_str),
                ("numofagents", nums_of_agents[i]),
                ("optimalcount",
                 int_round(sum_optimal_count / num_of_iterations, K_ROUND)),
                ("optimalkmin", int_round(kmin_mean, K_ROUND)),
                ("optimalkmax",
                 int_round(sum_optimal_kmax / num_of_iterations, K_ROUND)),
                ("gftformula",
                 int_round((1 - 1 / kmin_mean) * 100 if kmin_mean > 1 else 0,
                           RATIO_ROUND)),
                ("auctioncount",
                 int_round(sum_auction_count / num_of_iterations, K_ROUND)),
                ("auctionkmin",
                 int_round(sum_auction_kmin / num_of_iterations, K_ROUND)),
                ("auctionkmax",
                 int_round(sum_auction_kmax / num_of_iterations, K_ROUND)),
                ("countratio",
                 int_round(
                     0 if sum_optimal_count == 0 else
                     (sum_auction_count / sum_optimal_count) * 100,
                     RATIO_ROUND)),
                ("optimalgft",
                 int_round(sum_optimal_gft / num_of_iterations, GFT_ROUND)),
                ("auctiongft",
                 int_round(sum_auction_total_gft / num_of_iterations,
                           GFT_ROUND)),
                ("gftratio", '0.00' if sum_optimal_gft == 0 else int_round(
                    sum_auction_total_gft / sum_optimal_gft *
                    100, RATIO_ROUND)),
            ]))
        results_table_integer.add(
            OrderedDict([
                ("iterations", num_of_iterations),
                ("recipe", recipe_str),
                ("numofagents", nums_of_agents[i]),
                ("optimalcount",
                 int_round(sum_optimal_count_integer / num_of_iterations,
                           K_ROUND)),
                ("optimalkmin", int_round(kmin_mean_integer, K_ROUND)),
                ("optimalkmax",
                 int_round(sum_optimal_kmax_integer / num_of_iterations,
                           K_ROUND)),
                ("gftformula",
                 int_round((1 - 1 / kmin_mean_integer) *
                           100 if kmin_mean_integer > 1 else 0, RATIO_ROUND)),
                ("auctioncount",
                 int_round(sum_auction_count_integer / num_of_iterations,
                           K_ROUND)),
                ("auctionkmin",
                 int_round(sum_auction_kmin_integer / num_of_iterations,
                           K_ROUND)),
                ("auctionkmax",
                 int_round(sum_auction_kmax_integer / num_of_iterations,
                           K_ROUND)),
                ("countratio",
                 int_round(
                     0 if sum_optimal_count_integer == 0 else
                     (sum_auction_count_integer / sum_optimal_count_integer) *
                     100, RATIO_ROUND)),
                ("optimalgft",
                 int_round(sum_optimal_gft_integer / num_of_iterations,
                           GFT_ROUND)),
                ("auctiongft",
                 int_round(sum_auction_total_gft_integer / num_of_iterations,
                           GFT_ROUND)),
                ("gftratio",
                 '0.00' if sum_optimal_gft_integer == 0 else int_round(
                     sum_auction_total_gft_integer / sum_optimal_gft_integer *
                     100, RATIO_ROUND)),
            ]))
    results_table.done()
def budget_balanced_ascending_auction(
        market:Market, ps_recipes: list)->TradeWithMultipleRecipes:
    """
    Calculate the trade and prices using generalized-ascending-auction.
    Allows multiple recipes, but only of the following kind:
    [ [1,0,0,x], [0,1,0,y], [0,0,1,z] ]
    (i.e., there are n-1 buyer categories and 1 seller category.
    One agent of category 1 buys x units; of category 2 buys y units; of category 3 buys z units; etc.)

    :param market:     contains a list of k categories, each containing several agents.
    :param ps_recipes: a list of lists of integers, one integer per category.
                       Each integer i represents the number of agents of category i
                       that should be in each procurement-set.
    :return: Trade object, representing the trade and prices.

    >>> # ONE BUYER, ONE SELLER
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, [[1,1]]))
    Traders: [buyer: [9.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, [[1,1]]))
    Traders: [buyer: [9.0, 8.0], seller: [-4.0]]
    No trade

    >>> market = Market([AgentCategory("buyer", [9.]), AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, [[1,1]]))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    seller: [-3.0]: all 1 agents trade and pay -4.0
    buyer: [9.0]: all 1 agents trade and pay 4.0

    >>> market = Market([AgentCategory("buyer", [9.,8.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, [[1,1]]))
    Traders: [buyer: [9.0, 8.0], seller: [-3.0, -4.0]]
    seller: [-3.0, -4.0]: random 1 out of 2 agents trade and pay -8.0
    buyer: [9.0]: all 1 agents trade and pay 8.0

    >>> # ONE BUYER, TWO SELLERS
    >>> market = Market([AgentCategory("buyer", [9.]),  AgentCategory("seller", [-4.,-3.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, [[1,2]]))
    Traders: [buyer: [9.0], seller: [-3.0, -4.0]]
    No trade
    >>> market = Market([AgentCategory("buyer", [9., 8., 7., 6.]),  AgentCategory("seller", [-6., -5., -4.,-3.,-2.,-1.])])
    >>> print(market); print(budget_balanced_ascending_auction(market, [[1,2]]))
    Traders: [buyer: [9.0, 8.0, 7.0, 6.0], seller: [-1.0, -2.0, -3.0, -4.0, -5.0, -6.0]]
    seller: [-1.0, -2.0, -3.0, -4.0]: random 2 out of 4 agents trade and pay -4.0
    buyer: [9.0]: all 1 agents trade and pay 8.0
    """
    logger.info("\n#### Budget-Balanced Ascending Auction with Multiple Recipes - n-1 buyer categories\n")
    logger.info(market)
    logger.info("Procurement-set recipes: %s", ps_recipes)

    map_buyer_category_to_seller_count = _convert_recipes_to_seller_counts(ps_recipes, market.num_categories)
    logger.info("Map buyer category index to seller count: %s", map_buyer_category_to_seller_count)

    # NOTE: Calculating the optimal trade cannot be done greedily -
    #         it requires solving a restricted instance of Knapsack.
    # optimal_trade = market.optimal_trade(ps_recipe, max_iterations=max_iterations)[0]
    # logger.info("For comparison, the optimal trade is: %s\n", optimal_trade)

    remaining_market = market.clone()
    buyer_categories = remaining_market.categories[:-1]
    num_buyer_categories = market.num_categories-1
    seller_category = remaining_market.categories[-1]


    prices = AscendingPriceVector([1, 1], -MAX_VALUE)
    buyer_price_index = 0
    seller_price_index = 1
    # prices[0] represents the price for all buyer-categories per single unit.
    # prices[1] represents the price for all sellers.
    try:
        num_units_offered  = len(seller_category)
        num_units_demanded = sum([len(buyer_categories[i])*map_buyer_category_to_seller_count[i] for i in range(num_buyer_categories)])
        target_unit_count  = min(num_units_demanded, num_units_offered)
        logger.info("%d units demanded by buyers, %d units offered by sellers, minimum is %d",
            num_units_demanded, num_units_offered, target_unit_count)

        while True:
            logger.info("Prices: %s, Target unit count: %d", prices, target_unit_count)

            price_index = buyer_price_index
            while True:
                num_units_demanded = sum([len(buyer_categories[i]) * map_buyer_category_to_seller_count[i] for i in range(num_buyer_categories)])
                logger.info("  Buyers demand %d units", num_units_demanded)
                if num_units_demanded == 0:                 raise EmptyCategoryException()
                if num_units_demanded <= target_unit_count: break
                map_buyer_category_to_lowest_value = [category.lowest_agent_value() for category in buyer_categories]
                logger.debug("  map_buyer_category_to_lowest_value=%s", map_buyer_category_to_lowest_value)
                map_buyer_category_to_lowest_value_per_unit = [value / count for value,count in zip(map_buyer_category_to_lowest_value,map_buyer_category_to_seller_count)]
                logger.debug("  map_buyer_category_to_lowest_value_per_unit=%s", map_buyer_category_to_lowest_value_per_unit)
                category_index_with_lowest_value_per_unit = min(range(num_buyer_categories), key=lambda i:map_buyer_category_to_lowest_value_per_unit[i])
                category_with_lowest_value_per_unit = buyer_categories[category_index_with_lowest_value_per_unit]
                lowest_value_per_unit = map_buyer_category_to_lowest_value_per_unit[category_index_with_lowest_value_per_unit]
                logger.info("  lowest value per unit is %f, of category %d (%s)", lowest_value_per_unit, category_index_with_lowest_value_per_unit, category_with_lowest_value_per_unit.name)
                prices.increase_price_up_to_balance(price_index, category_with_lowest_value_per_unit.lowest_agent_value()/map_buyer_category_to_seller_count[category_index_with_lowest_value_per_unit], category_with_lowest_value_per_unit.name)
                category_with_lowest_value_per_unit.remove_lowest_agent()

            category    = seller_category
            # logger.info("\n### Step 1a: balancing the sellers (%s)", category.name)
            price_index = seller_price_index
            while True:
                num_units_offered = len(category)
                logger.info("  Sellers offer %d units", num_units_offered)
                if num_units_offered == 0:                 raise EmptyCategoryException()
                if num_units_offered <= target_unit_count: break
                prices.increase_price_up_to_balance(price_index, category.lowest_agent_value(), category.name)
                category.remove_lowest_agent()

            target_unit_count -= 1

    except EmptyCategoryException:
        logger.info("\nOne of the categories became empty. No trade!")
        logger.info("  Final price-per-unit vector: %s", prices)

    # Construct the final price-vector:
    buyer_price_per_unit = prices[buyer_price_index]
    seller_price_per_unit = prices[seller_price_index]
    final_prices = \
        [buyer_price_per_unit * unit_count for unit_count in map_buyer_category_to_seller_count] + \
        [seller_price_per_unit]
    logger.info("  %s", remaining_market)
    return TradeWithMultipleRecipes(remaining_market.categories, map_buyer_category_to_seller_count, final_prices)