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
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