Getting started

[1]:
import pprint

Standard imports

[15]:
import numpy as np
import pandas as pd
import pymarket as pm

import pprint

We begin by creating an instance of a market, the basic interface for all mechanisms.

[16]:
mar = pm.Market() # Creates a new market

A market accepts buying and selling bids. The standard format of a bid is

\[bid = (quantity, price, userId, isBuying)\]

A buying bid can be interpreted as follows: \(userId\) is willing to buy any fraction of \(quantity\) at price \(price\) or lower.

A selling bid can be interpreted as follows: \(userId\) is willing to sell any fraction of \(quantity\) at price \(price\) or higher.

Submitting two bids in the market

Each bid gets a unique identifier within the market when it is accepted. That value is returned by the market after accepting the bid.

[17]:
mar.accept_bid(1, 2, 0, True) # User 0 want to buy (True) 1 unit at price 2
[17]:
0
[18]:
mar.accept_bid(2, 1, 1, False) # User 1 wants to sell (False) 2 units at price 2
[18]:
1

The bids dataframe

All bids are stored in a BidManager (bm). The bid manager can return a pandas DataFrame describing all available bids.

[19]:
mar.bm.get_df()
[19]:
quantity price user buying time divisible
0 1 2 0 True 0 True
1 2 1 1 False 0 True

Bids can have additional attributes (which are optional and do not have to be necesarily supplied while submitting a bid. Those attributes are: time (when was the bid added, useful if priority should be given to the first bids) and divisible (indicates whether the offer can be fractional or if it is all or nothing).

Running the market

Each market has a function run that executes the market with all available offers. In this case, we are using a peer-to-peer exchange.

[20]:
transactions, extras = mar.run('p2p') # run the p2p mechanism with the 2 bids

Each run of the market returns the list of all the transactions between users who traded, as well as extra information dependant on each mechanism.

The transactions dataframe

The transactions object returned by run is a TransactionManager and as well as the BidManager, it has a get_df() method to get all the transactions in the DataFrame.

[21]:
transactions.get_df()
[21]:
bid quantity price source active
0 0 1 1.5 1 False
1 1 1 1.5 0 True

The dataframe can be interpreted as follows:

  • Bid 0 traded a quantity 1 at price 1.5 with bid 1 and after it, it had traded as much as desired.
  • Bid 1 traded a quantity 1 at price 1.5 with bid 0 and after it, it still had some quantity that wished to trade.

Because there were no more players to trade with, bid 1 could not trade all its desired quantity.

The extra information

For the P2P mechanism, the extra information returned concerns how many rounds of trading ocurred and who traded with whom.

[22]:
pprint.pprint(extras)
{'trading_list': [[(0, 1)]]}

trading_list is a list of rounds. Each round is a list of tuples containing the pairs that traded. We can see that there was only one ronund, and in it, only one trade.

Statistics

It is possible to get statistics about the market. The available statistics are:

  • Percentage of all the tradable quantity traded
  • Percentage of the maximum social welfare achieved
  • Profits of the market maker
  • Profits of the users, asuming that they bided their true valuations
  • Profits of the users, given external reservation price information

Assume that user \(0\) valuated each unit at \(3\) instead of at \(2\), and that user \(1\) bided his true value. We can obtain the statistics from the market as follows:

[23]:
reservation_prices = {0: 10} # We do not need to specify the users who bided truthfully
statistics = mar.statistics(reservation_prices=reservation_prices)
[24]:
pprint.pprint(statistics)
{'percentage_traded': 1.0,
 'percentage_welfare': 1.0,
 'profits': {'market': 0.0,
             'player_bid': array([0.5, 0.5]),
             'player_reservation': array([8.5, 0.5])}}

It can be seen that:

  • All that could be traded was traded
  • The maximum social welfare was achieved
  • The market made no profit (reasonable since it is a pure peer to peer exchange)
  • Assuming that players bided their valuations, both players obtained a profit of \(0.5\)
  • Taking into account player \(0\) true valuation, player \(0\) made a profit of \(8.5\) instead.

This kind of information is useful to see that P2P does not incentize users to bid their true valuations (it is not strategy-proof)

Adding an extra bid

What happens if there was an extra buyer?

Bids are not removed from the market, so we can just add an extra bid and run the market again.

[25]:
mar.accept_bid(1, 0.5, 5, True, 4)
[25]:
2

For instance, we can model that this bid was added at time 4, but that the market did not trade until time 5, therefore all of the 3 bids get to trade together.

[26]:
mar.bm.get_df()
[26]:
quantity price user buying time divisible
0 1 2.0 0 True 0 True
1 2 1.0 1 False 0 True
2 1 0.5 5 True 4 True

Because the market is not deterministic, we need to pass a random state to it if we want to be able to reproduce its results.

[27]:
r = np.random.RandomState(1234)
[28]:
transactions, extras = mar.run('p2p', r=r)
[29]:
transactions.get_df()
[29]:
bid quantity price source active
0 2 0 0.0 1 True
1 1 0 0.0 2 True
2 0 1 1.5 1 False
3 1 1 1.5 0 True

This time there were 2 rounds with 1 trade each. In the first one, Bid 2 traded with Bid 1, and in the second one, Bid 1 traded with Bid 0. However, because user \(5\) had a very low buying price, it did not trade exchange any good with user \(1\).

[33]:
extras
[33]:
{'trading_list': [[(2, 1)], [(0, 1)]]}
[34]:
reservation_prices = {0: 10} # We do not need to specify the users who bided truthfully
statistics = mar.statistics(reservation_prices=reservation_prices)
pprint.pprint(statistics)
{'percentage_traded': 1.0,
 'percentage_welfare': 1.0,
 'profits': {'market': 0.0,
             'player_bid': array([0.5, 0.5, 0. ]),
             'player_reservation': array([8.5, 0.5, 0. ])}}

We see the same results as in the previous case, with the addition of player \(5\) who has a \(0\) profit for not trading