Welcome to PyMarket’s documentation!¶
PyMarket is a python library designed to ease the simulation and comparison of different market mechanisms.
Marketplaces can be proposed to solve a diverse array of problems. They are used to sell ads online, bandwith spectrum, energy, etc. PyMarket provides a simple environment to try, simulate and compare different market mechanisms, a task that is inherent to the process of establishing a new market.
As an example, Local Energy Markets (LEMs) have been proposed to syncronize energy consumption with surplus of renewable generation. Several mechanisms have been proposed for such a market: from double sided auctions to p2p trading.
This library aims to provide a simple interface for such process, making results reproducible.
Installation¶
Stable release¶
To install pymarket, run this command in your terminal:
First check your Python version, PyMarket requires Python 3.5.2 or newer.
$ python --version
Verify that pip is installed
$ python -m pip --version
You can proceed to install PyMarket with the following command (the –user flag is optimal but recommended).
$ python -m pip install pymarket --user
This is the preferred method to install pymarket, as it will always install the most recent stable release.
If you don’t have pip installed, this Python installation guide can guide you through the process.
Warning
Python ` >=3.5.2 ` is required. PyMarket won’t run with Python 2 nor previous versions of Python 3.
Dependencies¶
- PyMarket has been tested in Ubuntu 16.04, Ubuntu 18.04, Manjaro 18.1.1 and mac OS 10.14.4 (through travis only).
- PyMarket does not require additional dependencies outside for those specified in the requeriments.txt file. Nevertheless, PulP might benefit from having access to additional solvers such as CPLEX (not required).
From sources¶
The sources for pymarket can be downloaded from the Github repo.
You can either clone the public repository:
$ git clone git://github.com/gus0k/pymarket
Or download the tarball:
$ curl -OL https://github.com/gus0k/pymarket/tarball/master
Installing from source requires additional dependencies:
$ apt-get install --yes pkg-config
$ apt-get install --yes libfreetype6-dev
$ apt-get install --yes libpng12-dev
$ python -m pip install 'setuptools>=27.3' --user
Once you have a copy of the source, you can install it with:
$ python setup.py install
Running Tests¶
To run the tests an additional dependency is needed. It can be installed by running:
$ python -m pip install pytest --user
Test can be run from the main directory of the project by running:
$ python -m pytest
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
Examples¶
P2P¶
[3]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import networkx as nx
import pymarket as pm
Creates new market¶
[4]:
r = np.random.RandomState(1234)
mar = pm.Market()
mar.accept_bid(1, 6.7, 0, True, 0)
mar.accept_bid(1, 6.6, 1, True, 0)
mar.accept_bid(1, 6.5, 2, True, 0)
mar.accept_bid(1, 6.4, 3, True, 0)
mar.accept_bid(1, 6.3, 4, True, 0)
mar.accept_bid(1, 6, 5, True, 0)
mar.accept_bid(1, 1, 6, False, 0)
mar.accept_bid(1, 2, 7, False, 0)
mar.accept_bid(2, 3, 8, False, 0)
mar.accept_bid(2, 4, 9, False, 0)
mar.accept_bid(1, 6.1, 10, False, 0)
bids = mar.bm.get_df()
transactions, extras = mar.run('p2p', r=r)
stats = mar.statistics()
[5]:
bids # bids dataframe
[5]:
quantity | price | user | buying | time | divisible | |
---|---|---|---|---|---|---|
0 | 1 | 6.7 | 0 | True | 0 | True |
1 | 1 | 6.6 | 1 | True | 0 | True |
2 | 1 | 6.5 | 2 | True | 0 | True |
3 | 1 | 6.4 | 3 | True | 0 | True |
4 | 1 | 6.3 | 4 | True | 0 | True |
5 | 1 | 6.0 | 5 | True | 0 | True |
6 | 1 | 1.0 | 6 | False | 0 | True |
7 | 1 | 2.0 | 7 | False | 0 | True |
8 | 2 | 3.0 | 8 | False | 0 | True |
9 | 2 | 4.0 | 9 | False | 0 | True |
10 | 1 | 6.1 | 10 | False | 0 | True |
[6]:
transactions.get_df() # transactions dataframe
[6]:
bid | quantity | price | source | active | |
---|---|---|---|---|---|
0 | 3 | 1 | 3.70 | 6 | False |
1 | 6 | 1 | 3.70 | 3 | False |
2 | 5 | 0 | 0.00 | 10 | True |
3 | 10 | 0 | 0.00 | 5 | True |
4 | 2 | 1 | 4.25 | 7 | False |
5 | 7 | 1 | 4.25 | 2 | False |
6 | 4 | 1 | 5.15 | 9 | False |
7 | 9 | 1 | 5.15 | 4 | True |
8 | 0 | 1 | 4.85 | 8 | False |
9 | 8 | 1 | 4.85 | 0 | True |
10 | 5 | 1 | 5.00 | 9 | False |
11 | 9 | 1 | 5.00 | 5 | False |
12 | 1 | 1 | 4.80 | 8 | False |
13 | 8 | 1 | 4.80 | 1 | False |
[7]:
extras # additional information characteristic of P2P trading
[7]:
{'trading_list': [[(3, 6), (5, 10), (2, 7), (4, 9), (0, 8)], [(5, 9), (1, 8)]]}
Trades among participants¶
[9]:
ax = mar.plot_method('p2p')
/home/guso/anaconda3/lib/python3.6/site-packages/networkx/drawing/nx_pylab.py:611: MatplotlibDeprecationWarning: isinstance(..., numbers.Number)
if cb.is_numlike(alpha):

Analysis of the results¶
Round 1¶
- 3 trades with 6, they both trade all their quantity and are not considered for next round
- 5 trades with 10, the asked price by 10 is to high and no trade happens, they continue in next round
- 2 trades with 7, they both trade all their quantity and are not considered for next round
- 4 trades with 9, they trade one unit and 9 goes to next one with one remaining unit
- 0 trades with 8, they trade one unit and 8 goes to next one with one remaining unit
- 1 is not paired with anyone and continues to round 2
Round 2¶
- 5 trades with 9, they both trade all their remaining quantity and are not considered for the next round
- 1 trades with 8, they both trade all their remaining quantity and are not considered for the next round
- 10 is not paired and continues to the round 3
Round 3¶
- Only 10 remains, so no trade can ocurr, the algorithm ends.
Statistics¶
[10]:
print('Percentage of the maximum possible traded quantity')
stats['percentage_traded']
Percentage of the maximum possible traded quantity
[10]:
0.9999999999989999
[11]:
print('Percentage of the maximum possible total welfare')
stats['percentage_welfare']
Percentage of the maximum possible total welfare
[11]:
1.0
[12]:
print('Profits per user')
for u in bids.user.unique():
print(f'User {u:2} obtained a profit of {stats["profits"]["player_bid"][u]:0.2f}')
Profits per user
User 0 obtained a profit of 1.85
User 1 obtained a profit of 1.80
User 2 obtained a profit of 2.25
User 3 obtained a profit of 2.70
User 4 obtained a profit of 1.15
User 5 obtained a profit of 1.00
User 6 obtained a profit of 2.70
User 7 obtained a profit of 2.25
User 8 obtained a profit of 3.65
User 9 obtained a profit of 2.15
User 10 obtained a profit of 0.00
[13]:
print(f'Profit to Market Maker was {stats["profits"]["market"]:0.2f}')
Profit to Market Maker was 0.00
MUDA¶
[1]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import networkx as nx
import pymarket as pm
Creates new market¶
[2]:
r = np.random.RandomState(1234)
mar = pm.Market()
mar.accept_bid(1, 6.7, 0, True, 0)
mar.accept_bid(1, 6.6, 1, True, 0)
mar.accept_bid(1, 6.5, 2, True, 0)
mar.accept_bid(1, 6.4, 3, True, 0)
mar.accept_bid(1, 6.3, 4, True, 0)
mar.accept_bid(1, 6, 5, True, 0)
mar.accept_bid(1, 1, 6, False, 0)
mar.accept_bid(1, 2, 7, False, 0)
mar.accept_bid(2, 3, 8, False, 0)
mar.accept_bid(2, 4, 9, False, 0)
mar.accept_bid(1, 6.1, 10, False, 0)
bids = mar.bm.get_df()
transactions, extras = mar.run('muda', r=r)
stats = mar.statistics()
Analysis of the Left Side¶
Participants¶
- Buying: 1, 3, 4
- Selling: 7, 8, 9
Trading price¶
- \(6.3\)
Results¶
- The long side is the supply, all demand side buys as much as they want
- The demand side pays no fees, they are the short side
- Bid 7, results in bid 9 not trading a unit, so the fee is $ 1 :nbsphinx-math:`times `(6.3 - 4) = 2.3$
- Bid 8, results in bid 9 not trading a 2 units so the fee is \(2 \times (6.3 - 4) = 4.6\)
Analysis of the Right Side¶
Participants¶
- Buying: 0, 2, 5
- Selling: 6, 10 (10 does not trade because bid price is greater than trading price)
Trading price¶
- 4.65
Statistics¶
[5]:
print('Percentage of the maximum possible traded quantity')
stats['percentage_traded']
Percentage of the maximum possible traded quantity
[5]:
0.6666666666659999
[6]:
print('Percentage of the maximum possible total welfare')
stats['percentage_welfare']
Percentage of the maximum possible total welfare
[6]:
0.7906976744186046
[7]:
print('Profits per user')
for u in bids.user.unique():
print(f'User {u:2} obtained a profit of {stats["profits"]["player_bid"][u]:0.2f}')
Profits per user
User 0 obtained a profit of 2.05
User 1 obtained a profit of 0.30
User 2 obtained a profit of 0.00
User 3 obtained a profit of 0.10
User 4 obtained a profit of 0.00
User 5 obtained a profit of 0.00
User 6 obtained a profit of 3.65
User 7 obtained a profit of 4.30
User 8 obtained a profit of 6.60
User 9 obtained a profit of 0.00
User 10 obtained a profit of 0.00
[8]:
print(f'Profit to Market Maker was {stats["profits"]["market"]:0.2f}')
Profit to Market Maker was 8.75
[ ]:
Huang¶
[2]:
%matplotlib inline
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import networkx as nx
import pymarket as pm
Creates new market¶
[4]:
mar = pm.Market()
mar.accept_bid(1, 6.7, 0, True, 0)
mar.accept_bid(1, 6.6, 1, True, 0)
mar.accept_bid(1, 6.5, 2, True, 0)
mar.accept_bid(1, 6.4, 3, True, 0)
mar.accept_bid(1, 6.3, 4, True, 0)
mar.accept_bid(1, 6, 5, True, 0)
mar.accept_bid(1, 1, 6, False, 0)
mar.accept_bid(1, 2, 7, False, 0)
mar.accept_bid(2, 3, 8, False, 0)
mar.accept_bid(2, 4, 9, False, 0)
mar.accept_bid(1, 6.1, 10, False, 0)
bids = mar.bm.get_df()
transactions, extras = mar.run('huang')
stats = mar.statistics()
Supply and demand curves after market is splitted¶
[11]:
fig, ax = plt.subplots(figsize=(8, 6))
ax = mar.plot_method('huang', ax=ax)

Analysis of the trade¶
Trading price¶
- Selling Price: 4, defined by bid 9, consequently, 9 does not trade
- Buying Price: 6, defined by bid 5, consequently, 5 does not trade
Actually trading¶
- Buying: 0, 1, 2, 3
- Selling: 6, 7, 8
Results¶
- Supply and demand have the same size.
- The profit of the market maker coincides with the blue shaded area
Statistics¶
[7]:
print('Percentage of the maximum possible traded quantity')
stats['percentage_traded']
Percentage of the maximum possible traded quantity
[7]:
0.6666666666659999
[8]:
print('Percentage of the maximum possible total welfare')
stats['percentage_welfare']
Percentage of the maximum possible total welfare
[8]:
0.4186046511627907
[9]:
print('Profits per user')
for u in bids.user.unique():
print(f'User {u:2} obtained a profit of {stats["profits"]["player_bid"][u]:0.2f}')
Profits per user
User 0 obtained a profit of 0.56
User 1 obtained a profit of 0.48
User 2 obtained a profit of 0.40
User 3 obtained a profit of 0.32
User 4 obtained a profit of 0.24
User 5 obtained a profit of 0.00
User 6 obtained a profit of 3.00
User 7 obtained a profit of 2.00
User 8 obtained a profit of 2.00
User 9 obtained a profit of 0.00
User 10 obtained a profit of 0.00
[10]:
print(f'Profit to Market Maker was {stats["profits"]["market"]:0.2f}')
Profit to Market Maker was 8.00
[ ]:
Efficiency and Performance¶
[1]:
%matplotlib inline
import time
import pymarket as pm
import numpy as np
import matplotlib.pyplot as plt
Create a set of markets with varying number of participants¶
[41]:
markets = []
range_players = np.arange(20, 200, 20)
M = len(range_players)
for i in range_players:
bids = pm.datasets.generate(i, i, 2, 1)
mar = pm.Market()
for b in bids:
mar.accept_bid(*b)
markets.append(mar)
Run the diferent markets¶
[42]:
elapsed = np.zeros(M)
for i in range(M):
mar = markets[i]
start = time.time()
mar.run('huang')
stop = time.time()
elapsed[i] = stop - start
[43]:
fig, ax = plt.subplots()
ax.plot(range_players, elapsed)
_ = ax.set_xlabel('Number of Players')
_ = ax.set_ylabel('Elapsed Time (s)')
_ = ax.set_title('Performance')

Obtains the statistics (optimization problems have to be solved)¶
[49]:
traded = np.zeros(M)
welfare = np.zeros(M)
stats_time = np.zeros(M)
limit = M
for i in range(limit):
mar = markets[i]
start = time.time()
stats = mar.statistics()
stop = time.time()
stats_time[i] = stop - start
welfare[i] = stats['percentage_welfare']
traded[i] = stats['percentage_traded']
Plots the results¶
[51]:
fig, ax = plt.subplots(1, 3, figsize=(18, 6))
ax[0].plot(range_players[:limit], stats_time[:limit])
ax[0].set_ylabel('Elapsed Time (s)')
ax[1].plot(range_players[:limit], welfare[:limit])
ax[1].set_ylabel(' % Total welfare')
ax[2].plot(range_players[:limit], traded[:limit])
ax[2].set_ylabel(' % Total traded')
for ax_ in ax:
ax_.set_xlabel('Number of Players')

[ ]:
Creating a new mechanism¶
[1]:
import numpy as np
import pandas as pd
import pymarket as pm
import matplotlib.pyplot as plt
from pprint import pprint
One of the advantages of PyMarket
is the ability to easily implement and test a new idea for a mechanism. Here we will show how to implement a new mechanism and use it.
The uniform price mechanism¶
We are going to implement a uniform price mechanism that charges every trading player the clearing price.
As a reference we are going to be implement the example Here
We can begin by adding the corresponding bids to a new market
[2]:
mar = pm.Market()
buyers_names = ['CleanRetail', 'El4You', 'EVcharge', 'QualiWatt', 'IntelliWatt']
mar.accept_bid(250, 200, 0, True) # CleanRetail 0
mar.accept_bid(300, 110, 1, True) # El4You 1
mar.accept_bid(120, 100, 2, True) # EVcharge 2
mar.accept_bid( 80, 90, 3, True) # QualiWatt 3
mar.accept_bid( 40, 85, 4, True) # IntelliWatt 4
mar.accept_bid( 70, 75, 1, True) # El4You 5
mar.accept_bid( 60, 65, 0, True) # CleanRetail 6
mar.accept_bid( 45, 40, 4, True) # IntelliWatt 7
mar.accept_bid( 30, 38, 3, True) # QualiWatt 8
mar.accept_bid( 35, 31, 4, True) # IntelliWatt 9
mar.accept_bid( 25, 24, 0, True) # CleanRetail 10
mar.accept_bid( 10, 21, 1, True) # El4You 11
sellers_names = ['RT', 'WeTrustInWind', 'BlueHydro', 'KøbenhavnCHP', 'DirtyPower', 'SafePeak']
mar.accept_bid(120, 0, 5, False) # RT 12
mar.accept_bid(50, 0, 6, False) # WeTrustInWind 13
mar.accept_bid(200, 15, 7, False) # BlueHydro 14
mar.accept_bid(400, 30, 5, False) # RT 15
mar.accept_bid(60, 32.5, 8, False) # KøbenhavnCHP 16
mar.accept_bid(50, 34, 8, False) # KøbenhavnCHP 17
mar.accept_bid(60, 36, 8, False) # KøbenhavnCHP 18
mar.accept_bid(100,37.5, 9, False) # DirtyPower 19
mar.accept_bid(70, 39, 9, False) # DirtyPower 20
mar.accept_bid(50, 40, 9, False) # DirtyPower 21
mar.accept_bid(70, 60, 5, False) # RT 22
mar.accept_bid(45, 70, 5, False) # RT 23
mar.accept_bid(50, 100, 10, False) # SafePeak 24
mar.accept_bid(60, 150, 10, False) # SafePeak 25
mar.accept_bid(50, 200, 10, False) # SafePeak 26
[2]:
26
[3]:
12, 15, 22, 23
[3]:
(12, 15, 22, 23)
[4]:
mar.plot()

Implementing the mechanism¶
All market mechanisms take as arguements a bids dataframe (as well as possibly extra parameters) and returns a TransactionManager
and an extras
dictionary.
[13]:
def uniform_price_mechanism(bids: pd.DataFrame) -> (pm.TransactionManager, dict):
trans = pm.TransactionManager()
buy, _ = pm.bids.demand_curve_from_bids(bids) # Creates demand curve from bids
sell, _ = pm.bids.supply_curve_from_bids(bids) # Creates supply curve from bids
# q_ is the quantity at which supply and demand meet
# price is the price at which that happens
# b_ is the index of the buyer in that position
# s_ is the index of the seller in that position
q_, b_, s_, price = pm.bids.intersect_stepwise(buy, sell)
buying_bids = bids.loc[bids['buying']].sort_values('price', ascending=False)
selling_bids = bids.loc[~bids['buying']].sort_values('price', ascending=True)
## Filter only the trading bids.
buying_bids = buying_bids.iloc[: b_ + 1, :]
selling_bids = selling_bids.iloc[: s_ + 1, :]
# Find the long side of the market
buying_quantity = buying_bids.quantity.sum()
selling_quantity = selling_bids.quantity.sum()
if buying_quantity > selling_quantity:
long_side = buying_bids
short_side = selling_bids
else:
long_side = selling_bids
short_side = buying_bids
traded_quantity = short_side.quantity.sum()
## All the short side will trade at `price`
## The -1 is there because there is no clear 1 to 1 trade.
for i, x in short_side.iterrows():
t = (i, x.quantity, price, -1, False)
trans.add_transaction(*t)
## The long side has to trade only up to the short side
quantity_added = 0
for i, x in long_side.iterrows():
if x.quantity + quantity_added <= traded_quantity:
x_quantity = x.quantity
else:
x_quantity = traded_quantity - quantity_added
t = (i, x_quantity, price, -1, False)
trans.add_transaction(*t)
quantity_added += x.quantity
extra = {
'clearing quantity': q_,
'clearing price': price
}
return trans, extra
Wrapping the algorithm as a mechanism¶
[14]:
# Observe that we add as the second argument of init the algorithm just coded
class UniformPrice(pm.Mechanism):
"""
Interface for our new uniform price mechanism.
Parameters
-----------
bids
Collection of bids to run the mechanism
with.
"""
def __init__(self, bids, *args, **kwargs):
"""TODO: to be defined1. """
pm.Mechanism.__init__(self, uniform_price_mechanism, bids, *args, **kwargs)
Adding the new mechanism to the list of available mechanism of the market¶
[15]:
pm.market.MECHANISM['uniform'] = UniformPrice
Running the new mechanism and comparing it with Huang’s and P2P¶
[24]:
stats = {}
for mec in ['uniform', 'huang', 'p2p']:
t, e = mar.run(mec)
stat = mar.statistics()
stats[mec] = stat
Profits for the players in the different mechanism¶
[33]:
profits = pd.DataFrame([v['profits']['player_bid'] for k, v in stats.items()]).T
profits.columns = stats.keys()
profits
[33]:
uniform | huang | p2p | |
---|---|---|---|
0 | 42275.0 | 41529.375 | 22890.0 |
1 | 24375.0 | 23849.375 | 12980.0 |
2 | 7500.0 | 7246.250 | 3150.0 |
3 | 4215.0 | 3997.500 | 2630.0 |
4 | 2012.5 | 1816.875 | 1162.5 |
5 | 7500.0 | 7500.000 | 14647.5 |
6 | 1875.0 | 1875.000 | 810.0 |
7 | 4500.0 | 4500.000 | 18500.0 |
8 | 565.0 | 565.000 | 3910.0 |
9 | 0.0 | 0.000 | 4570.0 |
10 | 0.0 | 0.000 | 375.0 |
Percentage traded by mechanism¶
[36]:
traded = pd.DataFrame([v['percentage_traded'].round(3) for k, v in stats.items()]).T
traded.columns = stats.keys()
traded
[36]:
uniform | huang | p2p | |
---|---|---|---|
0 | 0.934 | 0.883 | 0.995 |
Percentage of the maximum social welfare achieved by mechanism¶
[38]:
welfare = pd.DataFrame([v['percentage_welfare'].round(3) for k, v in stats.items()]).T
welfare.columns = stats.keys()
welfare
[38]:
uniform | huang | p2p | |
---|---|---|---|
0 | 1.0 | 0.98 | 0.903 |
[ ]:
pymarket¶
pymarket package¶
Subpackages¶
pymarket.bids package¶
Top-level package for pymarket.
Submodules¶
-
class
pymarket.bids.bids.
BidManager
[source]¶ Bases:
object
A class used to store and manipulate a collection of all the bids in the market.
-
col_names
¶ Column names for the different attributes in the dataframe to be created. Currently and in order: quantity, price, user, buying, time, divisible.
Type: list
ofstr
-
n_bids
¶ Number of bids currently stored. Used as a unique identifier for each bid within a BidManager.
Type: int
-
add_bid
(quantity, price, user, buying=True, time=0, divisible=True)[source]¶ Appends a bid to the bid list
Parameters: - quantity (float) – Quantity of good desired. If divisible=True then any fraction of the good is an acceptable outcome of the market.
- price (float) – Uniform price offered in the market for each unit of the the good.
- user (int) – Identifier of the user submitting the bid.
- buying (bool) – True if the bid is for buying the good and False`otherwise. Default is `True.
- time (float) – Instant at which the offer was made. This is relevant only if the market mechanism has perferences for earlier bids. Default is 0
- divisible (bool) – True is the user accepts a fraction of the asked quantity as a result and False otherwise.
Returns: Unique identifier of the added bid.
Return type: Examples
>>> bm = pm.BidManager() >>> bm.add_bid(2, 1, 0) 0
-
col_names
= ['quantity', 'price', 'user', 'buying', 'time', 'divisible']
-
get_df
()[source]¶ Creates a dataframe with the bids
Returns: Dataframe with each row a different bid and each column each of the different attributes. Return type: pd.DataFrame Examples
>>> bm = pm.BidManager() >>> bm.add_bid(2, 1, 0) 0 >>> bm.add_bid(1, 3, 1, buying=False) 1 >>> print(bm.get_df()) quantity price user buying time divisible 0 2 1 0 True 0 True 1 1 3 1 False 0 True
-
-
pymarket.bids.demand_curves.
demand_curve_from_bids
(bids)[source]¶ Creates a demand curve from a set of buying bids. It is the inverse cumulative distribution of quantity as a function of price.
Parameters: bids – Collection of all the bids in the market. The algorithm filters only the buying bids. Returns: - demand_curve (np.ndarray) – Stepwise constant demand curve represented as a collection of the N rightmost points of each interval (N-1 bids). It is stored as a (N, 2) matrix where the first column is the x-coordinate and the second column is the y-coordinate. An extra point is a))dded with x coordinate at infinity and price at 0 to represent the end of the curve.
- index (np.ndarray) – The order of the identifier of each bid in the demand curve.
Examples
A minimal example, selling bid is ignored:
>>> bm = pm.BidManager() >>> bm.add_bid(1, 1, 0, buying=True) 0 >>> bm.add_bid(1, 1, 1, buying=False) 1 >>> dc, index = pm.demand_curve_from_bids(bm.get_df()) >>> dc array([[ 1., 1.], [inf, 0.]]) >>> index array([0])
A larger example with reordering of bids:
>>> bm = pm.BidManager() >>> bm.add_bid(1, 1, 0, buying=True) 0 >>> bm.add_bid(1, 1, 1, buying=False) 1 >>> bm.add_bid(3, 0.5, 2, buying=True) 2 >>> bm.add_bid(2.3, 0.1, 3, buying=True) 3 >>> dc, index = pm.demand_curve_from_bids(bm.get_df()) >>> dc array([[1. , 1. ], [4. , 0.5], [6.3, 0.1], [inf, 0. ]]) >>> index array([0, 2, 3])
-
pymarket.bids.demand_curves.
get_value_stepwise
(x, f)[source]¶ Returns the value of a stepwise constant function defined by the right extrems of its interval Functions are assumed to be defined in (0, inf).
Parameters: - x (float) – Value in which the function is to be evaluated
- f (np.ndarray) – Stepwise function represented as a 2 column matrix. Each row is the rightmost extreme point of each constant interval. The first column contains the x coordinate and is sorted increasingly. f is assumed to be defined only in the interval :math: (0, infty)
Returns: The image of x under f: f(x). If x is negative, then None is returned instead. If x is outside the range of the function (greater than f[-1, 0]), then the method returns None.
Return type: Examples
>>> f = np.array([ ... [1, 1], ... [3, 4]]) >>> [pm.get_value_stepwise(x, f) ... for x in [-1, 0, 0.5, 1, 2, 3, 4]] [None, 1, 1, 1, 4, 4, None]
-
pymarket.bids.demand_curves.
intersect_stepwise
(f, g, k=0.5)[source]¶ Finds the intersection of two stepwise constants functions where f is assumed to be bigger at 0 than g. If no intersection is found, None is returned.
Parameters: - f (np.ndarray) – Stepwise constant function represented as a 2 column matrix where each row is the rightmost point of the constat interval. The first column is sorted increasingly. Preconditions: f is non-increasing.
- g (np.ndarray) – Stepwise constant function represented as a 2 column matrix where each row is the rightmost point of the constat interval. The first column is sorted increasingly. Preconditions: g is non-decreasing and f[0, 0] > g[0, 0]
- k (float) – If the intersection is empty or an interval, a convex combination of the y-values of f and g will be returned and k will be used to determine hte final value. k=1 will be the value of g while k=0 will be the value of f.
Returns: - x_ast (float or None) – Axis coordinate of the intersection of both functions. If the intersection is empty, then it returns None.
- f_ast (int or None) – Index of the rightmost extreme of the interval of f involved in the intersection. If the intersection is empty, returns None
- g_ast (int or None) – Index of the rightmost extreme of the interval of g involved in the intersection. If the intersection is empty, returns None.
- v (float or None) – Ordinate of the intersection if it is uniquely identified, otherwise the k-convex combination of the y values of f and g in the last point when they were both defined.
Examples
Simple intersection with diferent domains
>>> f = np.array([[1, 3], [3, 1]]) >>> g = np.array([[2,2]]) >>> pm.intersect_stepwise(f, g) (1, 0, 0, 2)
Empty intersection, returning the middle value
>>> f = np.array([[1,3], [2, 2.5]]) >>> g = np.array([[1,1], [2, 2]]) >>> pm.intersect_stepwise(f, g) (None, None, None, 2.25)
-
pymarket.bids.demand_curves.
supply_curve_from_bids
(bids)[source]¶ Creates a supply curve from a set of selling bids. It is the cumulative distribution of quantity as a function of price.
Parameters: bids (pd.DataFrame) – Collection of all the bids in the market. The algorithm filters only the selling bids. Returns: - supply_curve (np.ndarray) – Stepwise constant demand curve represented as a collection of the N rightmost points of each interval (N-1 bids). It is stored as a (N, 2) matrix where the first column is the x-coordinate and the second column is the y-coordinate. An extra point is added with x coordinate at infinity and price at infinity to represent the end of the curve.
- index (np.ndarray) – The order of the identifier of each bid in the supply curve.
Examples
A minimal example, selling bid is ignored:
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0, False) 0 >>> bm.add_bid(2.1, 3, 3, True) 1 >>> sc, index = pm.supply_curve_from_bids(bm.get_df()) >>> sc array([[ 1., 3.], [inf, inf]]) >>> index array([0])
A larger example with reordering:
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0, False) 0 >>> bm.add_bid(2.1, 3, 3, True) 1 >>> bm.add_bid(0.2, 1, 3, False) 2 >>> bm.add_bid(1.7, 6, 4, False) 3 >>> sc, index = pm.supply_curve_from_bids(bm.get_df()) >>> sc array([[0.2, 1. ], [1.2, 3. ], [2.9, 6. ], [inf, inf]]) >>> index array([2, 0, 3])
Implements processing techniques applied to bids before mechanisms can use them
-
pymarket.bids.processing.
merge_same_price
(df, prec=5)[source]¶ Process a collection of bids by merging in each side (buying or selling) all players with the same price into a new user with their aggregated quantity
Parameters: - df (pd.DataFrame) – Collection of bids to process
- prec (float) – Number of digits to use after the comma while comparing floating point prices as equal.
Returns: - dataframe_new (pd.DataFrame) – The new collection of bids where players with the same price have been merged into one.
- final_maping (dict) – Maping from new bids index to the old bids index.
Examples
>>> bm = BidManager() >>> bm.add_bid(0.3, 1, 0) 0 >>> bm.add_bid(0.7, 1, 1) 1 >>> bm.add_bid(2, 1, 2, False) 2 >>> bm.add_bid(1, 2.444446, 3, False) 3 >>> bm.add_bid(3, 2.444447, 4, False) 4 >>> bm.get_df() quantity price user buying time divisible 0 0.3 1.000000 0 True 0 True 1 0.7 1.000000 1 True 0 True 2 2.0 1.000000 2 False 0 True 3 1.0 2.444446 3 False 0 True 4 3.0 2.444447 4 False 0 True >>> bids, index = pm.merge_same_price(bm.get_df(), 5) >>> bids quantity price user buying time divisible 0 1.0 1.00000 5 True 0 True 1 2.0 1.00000 2 False 0 True 2 4.0 2.44445 6 False 0 True >>> index {0: [0, 1], 1: [2], 2: [3, 4]}
>>> mar = pm.Market() >>> mar.accept_bid(250, 200, 0, True) # CleanRetail 0 >>> mar.accept_bid(300, 110, 1, True) # El4You 1 >>> mar.accept_bid(120, 100, 2, True) # EVcharge 2 >>> mar.accept_bid( 80, 90, 3, True) # QualiWatt 3 >>> mar.accept_bid( 40, 85, 4, True) # IntelliWatt 4 >>> mar.accept_bid( 70, 75, 1, True) # El4You 5 >>> mar.accept_bid( 60, 65, 0, True) # CleanRetail 6 >>> mar.accept_bid( 45, 40, 4, True) # IntelliWatt 7 >>> mar.accept_bid( 30, 38, 3, True) # QualiWatt 8 >>> mar.accept_bid( 35, 31, 4, True) # IntelliWatt 9 >>> mar.accept_bid( 25, 24, 0, True) # CleanRetail 10 >>> mar.accept_bid( 10, 21, 1, True) # El4You 11
>>> mar.accept_bid(120, 0, 5, False) # RT 12 >>> mar.accept_bid(50, 0, 6, False) # WeTrustInWind 13 >>> mar.accept_bid(200, 15, 7, False) # BlueHydro 14 >>> mar.accept_bid(400, 30, 5, False) # RT 15 >>> mar.accept_bid(60, 32.5, 8, False) # KøbenhavnCHP 16 >>> mar.accept_bid(50, 34, 8, False) # KøbenhavnCHP 17 >>> mar.accept_bid(60, 36, 8, False) # KøbenhavnCHP 18 >>> mar.accept_bid(100,37.5, 9, False) # DirtyPower 19 >>> mar.accept_bid(70, 39, 9, False) # DirtyPower 20 >>> mar.accept_bid(50, 40, 9, False) # DirtyPower 21 >>> mar.accept_bid(70, 60, 5, False) # RT 22 >>> mar.accept_bid(45, 70, 5, False) # RT 23 >>> mar.accept_bid(50, 100, 10, False) # SafePeak 24 >>> mar.accept_bid(60, 150, 10, False) # SafePeak 25 >>> mar.accept_bid(50, 200, 10, False) # SafePeak 26 >>> bids, index = pm.merge_same_price(mar.bm.get_df()) >>> mar.bm.get_df() quantity price user buying time divisible 0 250 200.0 0 True 0 True 1 300 110.0 1 True 0 True 2 120 100.0 2 True 0 True 3 80 90.0 3 True 0 True 4 40 85.0 4 True 0 True 5 70 75.0 1 True 0 True 6 60 65.0 0 True 0 True 7 45 40.0 4 True 0 True 8 30 38.0 3 True 0 True 9 35 31.0 4 True 0 True 10 25 24.0 0 True 0 True 11 10 21.0 1 True 0 True 12 120 0.0 5 False 0 True 13 50 0.0 6 False 0 True 14 200 15.0 7 False 0 True 15 400 30.0 5 False 0 True 16 60 32.5 8 False 0 True 17 50 34.0 8 False 0 True 18 60 36.0 8 False 0 True 19 100 37.5 9 False 0 True 20 70 39.0 9 False 0 True 21 50 40.0 9 False 0 True 22 70 60.0 5 False 0 True 23 45 70.0 5 False 0 True 24 50 100.0 10 False 0 True 25 60 150.0 10 False 0 True 26 50 200.0 10 False 0 True
-
pymarket.bids.processing.
new_player_id
(index)[source]¶ Helper function for merge_same_price. Creates a function that returns consecutive integers.
Parameters: index (int) – First identifier to use for the new fake players Returns: Callable – Function that maps a list of user ids into a new user id. Return type: function Examples
>>> id_gen = new_player_id(6) >>> id_gen([3]) 3 >>> id_gen([5]) 5 >>> id_gen([0, 1]) 6 >>> id_gen([2, 4]) 7
pymarket.datasets package¶
Top-level package for pymarket.
Submodules¶
-
pymarket.datasets.uniform_bidders.
generate
(cant_buyers, cant_sellers, offset_sellers=0, offset_buyers=0, r=None, eps=0.0001)[source]¶ Generates random bids. All the volumes and reservation prices are sampled independently from a uniform distribution. For sellers, the reservation price is shifted offset_seller while for the buyers is shifter offset_buyers. If there are two sellers or two buyers with the same price, the reservation price of one of them is resampled until in both side of the market, all players have different values.
The maximum number of players is limited by 1/eps, although the parameter currently updates itself to allow the requested quantity of buyers and sellers.
Parameters: - cant_buyers (int) – Number of buyers to generate. Has to be positiv
- cant_sellers (int) – Number of sellers to generate. Has to be positive.
- offset_sellers (float) – Quantity to shift the reservation price of sellers
- offset_buyers (float) – Quantity to shift the reservation price of buyers
- r (optional, RandomState) – RandomState used to generate the data
- eps (optional, float) – Minimum precision of the prices.
Returns: List of tuples of all the bids generated
Return type: bids
Examples
>>> r = np.random.RandomState(420) >>> generate(2, 3, 1, 2, r, 0.1) [(0.5, 2.8, 0, True, 0, True), (0.7000000000000001, 2.5, 1, True, 0, True), (0.6000000000000001, 1.2, 2, False, 0, True), (0.1, 1.7000000000000002, 3, False, 0, True), (0.2, 1.3, 4, False, 0, True)]
pymarket.mechanisms package¶
Top-level package for pymarket.
Submodules¶
-
class
pymarket.mechanisms.huang_auction.
HuangAuction
(bids, *args, **kwargs)[source]¶ Bases:
pymarket.mechanisms.mechanism.Mechanism
Iinterface for the HuangAuction
Parameters: - bids (pd.DataFrame) – Collection of bids to use in the market
- merge (bool) – Wheather to merge players with the same price. Always True
-
pymarket.mechanisms.huang_auction.
huang_auction
(bids)[source]¶ Implements the auction described in [1]
Parameters: bids (pd.DataFrame) – Collection of all the bids to take into account by the mechanism Returns: - trans (TransactionManager) – Collection of all the trasactions cleared by the mechanism
- extra (dict) – Extra information provided by the mecanism. Keys: * price_sell: price at which sellers traded * price_buy: price at which the buyers traded * quantity_traded: the total quantity traded
Notes
[1] Huang, Pu, Alan Scheller–Wolf, and Katia Sycara. “Design of a multi–unit double auction e–market.” Computational Intelligence 18.4 (2002): 596-617.
Examples
No trade because price setters don’t trade:
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(2, 1, 1) 1 >>> bm.add_bid(2, 2, 2, False) 2 >>> trans, extra = huang_auction(bm.get_df()) >>> trans.get_df() Empty DataFrame Columns: [bid, quantity, price, source, active] Index: [] >>> extra OrderedDict([('price_sell', 2.0), ('price_buy', 3.0), ('quantity_traded', 0)])
Adding small bids at the beginning, those can trade because they don’t define de market price:
>>> bm.add_bid(0.3, 1, 3, False) 3 >>> bm.add_bid(0.2, 3.3, 4) 4 >>> trans, extra = huang_auction(bm.get_df()) >>> trans.get_df() bid quantity price source active 0 3 0.2 2.0 -1 False 1 4 0.2 3.0 -1 False >>> extra OrderedDict([('price_sell', 2.0), ('price_buy', 3.0), ('quantity_traded', 0.2)])
-
pymarket.mechanisms.huang_auction.
update_quantity
(quantity, gap)[source]¶ Implements the footnote in page 8 of [1], where the long side updates their trading quantities to match the short side.
Parameters: - quantity (np.ndarray) – List of the quantities to be traded by each player.
- gap (float) – Difference between the short and long side
Returns: quantity – Updated list of quantities to be traded by each player
Return type: np.ndarray
Notes
[1] Huang, Pu, Alan Scheller–Wolf, and Katia Sycara. “Design of a multi–unit double auction e–market.” Computational Intelligence 18.4 (2002): 596-617.
Examples
All keep trading, with less quantity
>>> l, g = np.array([1, 2, 3]), 0.6 >>> update_quantity(l, g) array([0.8, 1.8, 2.8])
The gap is to big for small trader:
>>> l,g = np.array([1, 0.5, 2]), 1.8 >>> update_quantity(l, g) array([0.35, 0. , 1.35])
-
class
pymarket.mechanisms.mechanism.
Mechanism
(algo, bids, *args, merge=False, **kwargs)[source]¶ Bases:
object
Implements a standard interface for mechanisms
-
algo
¶ Algorithm to execute to solve the market.
Type: Callable
-
bids
¶ Collection of bids to use, with processing.
Type: pd.DataFrame
-
old_bids
¶ Collection of bids previous to proecssing.
Type: pd.DataFrame
-
merge
¶ Wheather to merge different players with the same price into one player. Useful for algorithms that require players to have different prices.
Type: bool
Examples
Run p2p mechanism channging parameters with default parameters.
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 0.5, 1) 1 >>> bm.add_bid(1, 1, 2, False) 2 >>> bm.add_bid(1, 2, 3, False) 3 >>> r = np.random.RandomState(420) >>> p2p = pm.mechanisms.p2p_random >>> mec = Mechanism(p2p, bm.get_df(), r=r) >>> trans, extra = mec.run() >>> extra {'trading_list': [[(0, 3), (1, 2)]]} >>> trans.get_df() bid quantity price source active 0 0 1 2.5 3 False 1 3 1 2.5 0 False 2 1 0 0.0 2 True 3 2 0 0.0 1 True
>>> r = np.random.RandomState(420) >>> mec = Mechanism(p2p, bm.get_df(), r=r, p_coef=1) >>> trans, extra = mec.run() >>> extra {'trading_list': [[(0, 3), (1, 2)]]} >>> trans.get_df() bid quantity price source active 0 0 1 3.0 3 False 1 3 1 3.0 0 False 2 1 0 0.0 2 True 3 2 0 0.0 1 True
-
-
class
pymarket.mechanisms.muda_auction.
MudaAuction
(bids, *args, **kwargs)[source]¶ Bases:
pymarket.mechanisms.mechanism.Mechanism
Interface for MudaAuction.
Parameters: bids – Collection of bids to run the mechanism with.
-
pymarket.mechanisms.muda_auction.
compute_fee
(df, index, user, quantity, price)[source]¶ Computes the fee that a user has to pay by not letting others trade
Parameters: - df (pd.DataFrame) – Dataframe for one side of the market resulting from reseting the index of a bid dataframe, getting the bid as the first column in addition to all the standard ones. Precondition: all bids should be willing to trade at the trading price.
- index (int) – Index of the last trading bid
- user (int) – User identifier for which the fee should be computed
- quantity (float) – Total quantity that the side of the market trades
- price (float) – Price at which the market clears.
Returns: fee – Fee that user ùser will have to pay for not letting others trade as well.
Return type: Examples
>>> bm = pm.BidManager() >>> bm.add_bid(1, 1, 1) 0 >>> bm.add_bid(1, 2, 3) 1 >>> compute_fee(bm.get_df(), 0, 1, 1, 2.5) 0.5
-
pymarket.mechanisms.muda_auction.
find_competitive_price
(bids)[source]¶ Finds the unique trading price of the intersection of supply and demand.
Parameters: bids (pd.DataFrame) – Collection of bids to process the mechanism with. Returns: price – The price at which the market clears. Return type: float Notes
See also: intersect_stepwise.
-
pymarket.mechanisms.muda_auction.
get_trading_bids
(bids, quantity_traded)[source]¶ Finds the index of the rightmost trading bid in a side of the market. If the bid has to be split, it does so, and returns the a new bid dataframe with two bids in stade of the original one.
Parameters: - bids (pd.DataFrame) – Collection of bids in one side of the market Precondition: the dataframe is sorted by price. Reverse order for buying and selling side.
- quantity_traded (float) – Total quantity that the side of the market can trade.
Returns: - bids_trading (pd.DataFrame) – Same as bids, but the index (which represent the bid identifier) is added as the first column. If a bid had to be splitted, that bid is replaced by two, with the quantity in both summing up to the original quantity. The index is reseted but both splitted bids retain the oringal bid number in the column.
- bid_index (int) – Index of the worst bid that gets to trade.
Examples
No splitting needed
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.get_df() quantity price user buying time divisible 0 1 3 0 True 0 True 1 1 2 1 True 0 True >>> bids, index = get_trading_bids(bm.get_df(), 1) >>> bids bid quantity price user buying time divisible 0 0 1 3 0 True 0 True 1 1 1 2 1 True 0 True >>> index 0
Splitting needed:
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.get_df() quantity price user buying time divisible 0 1 3 0 True 0 True 1 1 2 1 True 0 True >>> bids, index = get_trading_bids(bm.get_df(), 0.3) >>> bids bid quantity price user buying time divisible 0 0 0.3 3 0 True 0 True 1 0 0.7 3 0 True 0 True 2 1 1 2 1 True 0 True >>> index 0
-
pymarket.mechanisms.muda_auction.
muda
(bids, r=None)[source]¶ Implements the Vickrey MUDA as described in [1].
The mechanism does not support two players in the same side of the market with the same price.
Parameters: - bids (pd.DataFrame) – Collection of bids to be used in the market
- r (np.random.RandomState) – A numpy random state generator. If not given, a new one will be created and the output will be random.
Returns: - trans (TransactionManager) – A collection of all the transactions performed.
- extra (dict) – Dictionary with extra information provided by the mechanism. Keys: * left: players in the left market * right: players in the right market * price_left: clearing price of the left market * price_right: clearing price of the right_market * fees: Fees that players have to pay to participate
Notes
[1] Segal-Halevi, Erel, Avinatan Hassidim, and Yonatan Aumann. “MUDA: a truthful multi-unit double-auction mechanism.” Thirty-Second AAAI Conference on Artificial Intelligence. 2018.
Examples
A case in which the market puts all the players in the same side and no one trades.
>>> bm = pm.BidManager() >>> bm.add_bid(1, 4, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1, 3, 2, False) 2 >>> bm.add_bid(1, 1, 3, False) 3 >>> r = np.random.RandomState(420) >>> trans, extra = muda(bm.get_df(), r) >>> extra OrderedDict([('left', []), ('right', [0, 1, 2, 3]), ('price_left', inf), ('price_right', 2.5), ('fees', array([0., 0., 0., 0.]))]) >>> trans.get_df() Empty DataFrame Columns: [bid, quantity, price, source, active] Index: []
A case in which there are 2 players in each side but the cleared prices makes it impossible to trade:
>>> r = np.random.RandomState(69) >>> trans, extra = muda(bm.get_df(), r) >>> extra OrderedDict([('left', [1, 3]), ('right', [0, 2]), ('price_left', 1.5), ('price_right', 3.5), ('fees', array([0., 0., 0., 0.]))]) >>> trans.get_df() Empty DataFrame Columns: [bid, quantity, price, source, active] Index: []
A case with trade:
>>> bm.add_bid(1, 5, 4) 4 >>> r = np.random.RandomState(69) >>> trans, extra = muda(bm.get_df(), r) >>> trans.get_df() bid quantity price source active 0 3 1 3.5 -1 False 1 4 1 3.5 -1 False 2 2 1 3.0 -1 False 3 0 1 3.0 -1 False >>> extra OrderedDict([('left', [1, 3, 4]), ('right', [0, 2]), ('price_left', 3.0), ('price_right', 3.5), ('fees', array([0., 0., 0., 0., 0.]))])
-
pymarket.mechanisms.muda_auction.
solve_market_side_with_exogenous_price
(bids, price, fees)[source]¶ Clears the market based on an external price. First it removes all biders that are not willing to trade at the given price, and then it fits the best allocation. Fees are calculated based on users that were willing but could not trade.
Parameters: - bids (pd.DataFrame) – Collection of bids to clear the market with
- price (float) – Price at which all the trades will ocurr
- fees (list of floats) – List of all the fees that players will have to pay. It gets updated.
Returns: - trans (TransactionManager) – Collection of the transactions that clear the market
- fees (list of floats) – Fees to be paid by each player. Is a list where the fee of player with id u is located at fees[u].
Examples
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 0.5, 1) 1 >>> bm.add_bid(1, 1, 2, False) 2 >>> bm.add_bid(1, 2, 3, False) 3 >>> fees = [0, 0, 0, 0] >>> trans, fees = solve_market_side_with_exogenous_price(bm.get_df(),2.5, fees) >>> trans.get_df() bid quantity price source active 0 0 1 2.5 -1 False 1 2 1 2.5 -1 False >>> fees [0, 0, 0.5, 0]
-
class
pymarket.mechanisms.p2p_random.
P2PTrading
(bids, *args, **kwargs)[source]¶ Bases:
pymarket.mechanisms.mechanism.Mechanism
Interface for P2PTrading.
Parameters: bids (pd.DataFrame) – Collections of bids to use
-
pymarket.mechanisms.p2p_random.
p2p_random
(bids, p_coef=0.5, r=None)[source]¶ Computes all the trades using a P2P random trading process inspired in [1].
Parameters: - bids (pd.DataFrame) – Collection of bids that will trade. Precondition: a user participates only in one side of the market, i.e, it cannot sell and buy in the same run.
- p_coef (float) – coefficient to calculate the trading price as a convex combination of the price of the seller and the price of the buyer. If 1, the seller gets all the profit and if 0, the buyer gets all the profit.
- r (np.random.RandomState) – Random state to generate stochastic values. If None, then the outcome of the market will be different on each run.
Returns: - trans (TransactionManger) – Collection of all the transactions that ocurred in the market
- extra (dict) – Extra information provided by the mechanisms.
Keys:
- trading_list: list of list of tuples of all the pairs that traded in each round.
Notes
[1] Blouin, Max R., and Roberto Serrano. “A decentralized market with common values uncertainty: Non-steady states.” The Review of Economic Studies 68.2 (2001): 323-346.
Examples
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 0.5, 1) 1 >>> bm.add_bid(1, 1, 2, False) 2 >>> bm.add_bid(1, 2, 3, False) 3 >>> r = np.random.RandomState(420) >>> trans, extra = p2p_random(bm.get_df(), r=r) >>> extra {'trading_list': [[(0, 3), (1, 2)]]} >>> trans.get_df() bid quantity price source active 0 0 1 2.5 3 False 1 3 1 2.5 0 False 2 1 0 0.0 2 True 3 2 0 0.0 1 True
pymarket.plot package¶
Submodules¶
-
pymarket.plot.demand_curves.
plot_demand_curves
(bids, ax=None, margin_X=1.2, margin_Y=1.2)[source]¶ Plots the demand curves. If ax is none, creates a new figure
Parameters: - bids – Collection of bids to be used
- ax (TODO, optional) – (Default value = None)
- margin_X – (Default value = 1.2)
- margin_Y – (Default value = 1.2)
-
pymarket.plot.huang.
plot_huang_auction
(bids, price_sell, price_buy, quantity_traded, ax=None)[source]¶ Plots the results of the huang auction with some of the characteristics of such auction
Parameters: - (pandas dataframe) (bids) – Table with all the bids submitted
- (list) (price_buy) – The price at which all sellers sell
- (list) – The price at which all players buy
- traded (float) (quantity) – The total quantity traded
Returns: axe – The axe in which the figure was plotted.
Return type: matplotlib.axes._subplots.AxesSubplot
-
pymarket.plot.muda.
plot_both_side_muda
(bids, left_players, right_players, left_price, right_price, FIGSIZE=(12, 6), **kwargs)[source]¶ Plots the two sides in which MUDA divides the trades with the corresponding prices
Parameters: - (pandas dataframe) (bids) – Table with all the bids submitted
- (list) (right) – List of players in the left side
- (list) – List of players in the right side
- (float) (right_price) – Price obtained from the left side to be used in the right side
- (float) – Price obtained from the right side to be used in the left side
- (tuple) (FIGSIZE) – Tuple (width, height) of the figure to be created
Returns: axe – The axe in which the figure was plotted.
Return type: matplotlib.axes._subplots.AxesSubplot
-
pymarket.plot.trades.
plot_trades_as_graph
(bids, transactions, ax=None)[source]¶ Plots all the bids as a bipartit graph with buyers and trades and an edge between each pair that traded
Parameters: - bids (pd.DataFrame) – Collection of bids to be used
- transactions (pd.DataFrame) – Collection of transactions to be used
- ax (pyplot.axe) – The axe in which the figure should be ploted
Returns: axe – The axe in which the figure was plotted.
Return type: matplotlib.axes._subplots.AxesSubplot
pymarket.statistics package¶
Submodules¶
-
pymarket.statistics.maximum_aggregated_utility.
maximum_aggregated_utility
(bids, *args, reservation_prices=None)[source]¶ Maximizes the total welfare
Parameters: - bids (pd.DataFrame) – Collection of bids
- reservation_prices (dict of floats or None, (Default value = None)) – A maping from user ids to reservation prices. If no reservation price for a user is given, his bid will be assumed to be his true value.
Returns: - status (str) – Status of the optimization problem. Desired output is ‘Optimal’
- objective (float) – Maximum aggregated utility that can be obtained
- variables (dict) – A set of values achieving the objective. Maps a pair of bids to the quantity traded by them.
Examples
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1.5, 1, 2, False) 2 >>> s, o, v = maximum_aggregated_utility(bm.get_df()) >>> s 'Optimal' >>> o 2.5 >>> v OrderedDict([((0, 2), 1.0), ((1, 2), 0.5)])
If in reality the seller had 0 value for his commodity, the social welfare will be 1.5 units larger
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1.5, 1, 2, False) 2 >>> rp = {2: 0} >>> s, o, v = maximum_aggregated_utility(bm.get_df(), ... reservation_prices=rp) >>> s 'Optimal' >>> o 4.0 >>> v OrderedDict([((0, 2), 1.0), ((1, 2), 0.5)])
-
pymarket.statistics.maximum_aggregated_utility.
percentage_welfare
(bids, transactions, reservation_prices=None, **kwargs)[source]¶ Percentage of the total welfare that could be achieved calculated based on the transaction lists
Parameters: - (pandas dataframe) (transactions) – Table with all the submited bids
- (pandas dataframe) – Table with all the transactions that ocurred in the market
- (dict, optional) (reservation_prices) – Reservation prices of the different participants. If None, the bids will be assumed to be the truthfull values.
Returns: ratio – The ratio of the maximum social welfare achieved by the collection of transactions.
Return type: Examples
Only bid 0 and 2 trade. That represents a net utility of 2 which is 80% of the total max utility 2.5
>>> tm = pm.TransactionManager() >>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1.5, 1, 2, False) 2 >>> tm.add_transaction(0, 1, 2, 2, False) 0 >>> tm.add_transaction(2, 1, 2, 0, False) 1 >>> percentage_welfare(bm.get_df(), tm.get_df()) 0.8
-
pymarket.statistics.maximum_traded_volume.
maximum_traded_volume
(bids, *args, reservation_prices={})[source]¶ Parameters: - bids (pd.DataFrame) – Collections of bids
- reservation_prices (dict of floats or None, (Default value = None)) – A maping from user ids to reservation prices. If no reservation price for a user is given, his bid will be assumed to be his true value.
Returns: - status (str) – Status of the optimization problem. Desired output is ‘Optimal’
- objective (float) – Maximum tradable volume that can be obtained
- variables (dict) – A set of values achieving the objective. Maps a pair of bids to the quantity traded by them.
Examples
>>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1.5, 1, 2, False) 2 >>> s, o, v = maximum_traded_volume(bm.get_df()) >>> s 'Optimal' >>> o 1.5 >>> v OrderedDict([((0, 2), 0.5), ((1, 2), 1.0)])
-
pymarket.statistics.maximum_traded_volume.
percentage_traded
(bids, transactions, reservation_prices={}, **kwargs)[source]¶ Calculates from the transaction dataframe the percentage of the total maximum possible traded quantity.
Parameters: - (pandas dataframe) (transactions) – Table with all the submited bids
- (pandas dataframe) – Table with all the transactions that ocurred in the market
- (dict, optional) (reservation_prices) – Reservation prices of the different participants. If None, the bids will be assumed to be the truthfull values.
Returns: ratio – The ratio of the maximum social welfare achieved by the collection of transactions.
Return type: Examples
Only bid 0 and 2 trade 1 unit. That represents the 66% of all that could have been traded.
>>> tm = pm.TransactionManager() >>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1.5, 1, 2, False) 2 >>> tm.add_transaction(0, 1, 2, 2, False) 0 >>> tm.add_transaction(2, 1, 2, 0, False) 1 >>> percentage_traded(bm.get_df(), tm.get_df()) 0.6666666666666666
-
pymarket.statistics.profits.
calculate_profits
(bids, transactions, reservation_prices=None, fees=None, **kwargs)[source]¶ Extras from the transactions and the bids the profit of each player and the market maker
Parameters: - bids (pd.DataFrame) – Collections of bids to be used
- transactions (pd.DataFrame) – Collection of transactions to be taken into account
- reservation_prices (dict, (Default value = None)) – Maping between users and their reservation prices. If None, it is assumed that each user bided truthfully and the information is extracted from the bid.
- fees (np.ndarray, (Default value = None)) – List of fees that each user has to pay to trade in the market.
Returns: profit – A dictionary with three values: * player_bid: A list with the profits of each user using their bids as reservation prices * player_reservation: Same as above but using their reservation prices, if none are provided, it is the same as player_bid * market: profit of the market maker
Return type: Examples
>>> tm = pm.TransactionManager() >>> bm = pm.BidManager() >>> bm.add_bid(1, 3, 0) 0 >>> bm.add_bid(1, 2, 1) 1 >>> bm.add_bid(1.5, 1, 2, False) 2 >>> tm.add_transaction(0, 1, 2, 2, False) 0 >>> tm.add_transaction(2, 1, 2, 0, False) 1 >>> rp = {2: 0} >>> profits = calculate_profits(bm.get_df(), tm.get_df(), ... reservation_prices=rp) >>> profits['player_bid'] array([1., 0., 1.]) >>> profits['player_reservation'] array([1., 0., 2.]) >>> profits['market'] 0.0
pymarket.transactions package¶
Submodules¶
Some processing functions to deal with transactions
-
pymarket.transactions.processing.
split_transactions_merged_players
(transactions, bids, maping, fees=None)[source]¶ Splits the transactions of a market that used merged bids into the original bids Uses a proportional split, based on the offered (or asked) quantity by each player.
Parameters: - transactions (TransactionManager) – the transactions manager returned by the mechanism.
- bids (pandas dataframe) – the original bid dataframe where some players might be repeated
- maping (pandas dataframe) – A maping between the bids in the transaction dataframe and the original bids.
Returns: - transactions_splited (pandas dataframe) – the result of splitting each merged bid in the transactions dataframe
- fees (dict or None) – dictionary obtained by splitting the fees equal to the transactions
Examples
>>> bm = pm.BidManager() >>> tm = pm.TransactionManager() >>> bm.add_bid(1, 1, 0) 0 >>> bm.add_bid(2, 1, 1) 1 >>> tm.add_transaction(0, 1, 1, -1, False) 0 >>> tm_2 = split_transactions_merged_players(tm, bm.get_df(), {0:[0,1]}) >>> tm_2.get_df() bid quantity price source active 0 0 0.333333 1 -1 False 1 1 0.666667 1 -1 False
-
class
pymarket.transactions.transactions.
TransactionManager
[source]¶ Bases:
object
An interaface to store and manage all transactions. Transactions are the minimal unit to represent the outcome of a market.
-
name_col
¶ Name of the columns to use in the dataframe returned.
Type: list of str
-
trans
¶ List of the actual transactions available
Type: list of tuples
-
add_transaction
(bid, quantity, price, source, active)[source]¶ Add a transaction to the transactions list
Parameters: - bid (int) – Unique identifier of the bid
- quantity (float) – transacted quantity
- price (float) – transacted price
- source (int) – Identifier of the second party in the trasaction, -1 if there is no clear second party, such as in a double auction.
- active – True` if the bid is still active after the transaction.
Returns: trans_id – id of the added transaction, -1 if fails
Return type: Examples
>>> tm = pm.TransactionManager() >>> tm.add_transaction(1, 0.5, 2.1, -1, False) 0 >>> tm.trans [(1, 0.5, 2.1, -1, False)] >>> tm.n_trans 1
-
get_df
()[source]¶ Returns the transaction dataframe
Returns: df – A pandas dataframe representing all the transactions stored. Return type: pd.DataFrame Examples
>>> tm = pm.TransactionManager() >>> tm.add_transaction(1, 0.5, 2.1, -1, False) 0 >>> tm.add_transaction(5, 0, 0, 3, True) 1 >>> tm.get_df() bid quantity price source active 0 1 0.5 2.1 -1 False 1 5 0.0 0.0 3 True
-
merge
(other)[source]¶ Merges two transaction managers with each other There are no checks on whether the new TransactionManger is consisten after the merge.
Parameters: other (TransactionManager) – A different transaction manager to merge with Returns: trans – A new transaction Manager with the transactions of the two. Return type: TransactionManager Examples
>>> tm_1 = pm.TransactionManager() >>> tm_1.add_transaction(1, 0.5, 2.1, -1, False) 0 >>> tm_2 = pm.TransactionManager() >>> tm_2.add_transaction(5, 0, 0, 3, True) 0 >>> tm_3 = tm_1.merge(tm_2) >>> tm_3.get_df() bid quantity price source active 0 1 0.5 2.1 -1 False 1 5 0.0 0.0 3 True
-
name_col
= ['bid', 'quantity', 'price', 'source', 'active']
-
pymarket.utils package¶
Top-level package for pymarket.
Submodules¶
pymarket.market module¶
-
class
pymarket.market.
Market
[source]¶ Bases:
object
General interface for calling the different market mechanisms
Parameters: - bm (BidManager) – All bids are stored in the bid manager
- transactions (TransactionManager) – The set of all tranasactions in the Market. This argument get updated after the market ran.
- extra (dict) – Extra information provided by the mechanisms. Gets updated after an execution of the run.
Examples
If everyone is buying, the transaction dataframe is returned empty as well as the extra dictionary.
>>> mar = pm.Market() >>> mar.accept_bid(1, 2, 0, True) 0 >>> mar.accept_bid(2, 3, 1, True) 1 >>> trans, extra = mar.run('huang') >>> extra OrderedDict() >>> trans.get_df() Empty DataFrame Columns: [bid, quantity, price, source, active] Index: []
If everyone is buying, the transaction dataframe is returned empty as well as the extra dictionary.
>>> mar = pm.Market() >>> mar.accept_bid(1, 2, 0, False) 0 >>> mar.accept_bid(2, 3, 1, False) 1 >>> trans, extra = mar.run('huang') >>> extra OrderedDict() >>> trans.get_df() Empty DataFrame Columns: [bid, quantity, price, source, active] Index: []
A very simple auction where nobody trades
>>> mar = pm.Market() >>> mar.accept_bid(1, 3, 0, True) 0 >>> mar.accept_bid(1, 2, 1, False) 1 >>> trans, extra = mar.run('huang') >>> extra OrderedDict([('price_sell', 2.0), ('price_buy', 3.0), ('quantity_traded', 0)]) >>> trans.get_df() Empty DataFrame Columns: [bid, quantity, price, source, active] Index: []
-
accept_bid
(*args)[source]¶ Adds a bid to the bid manager
Parameters: *args – List of parameters requried to create a bid. See BidManager documentation. Returns: bid_id – The id of the new created bid in the BidManger Return type: int
-
plot_method
(method, ax=None)[source]¶ Plots a figure specific for a given method, reflecting the main characteristics of its solution. It requires that the algorithm has run before.
Parameters: - method (str) – One of p2p, muda, huang
- ax – (Default value = None)
-
run
(algo, *args, **kwargs)[source]¶ Runs a given mechanism with the current bids
Parameters: - algo (str) –
- One of:
- ’p2p’
- ’huang’
- ’muda’
- *args – Extra arguments to pass to the algorithm.
- **kwargs – Extra keyworded arguments to pass to the algorithm
Returns: - transactions (TransactionManager) – The transaction manager holding all the transactions returned by the mechanism.
- extra (dict) – Dictionary with extra information returned by the executed method.
- algo (str) –
-
statistics
(reservation_prices=None, exclude=[])[source]¶ Computes the standard statistics of the market
Parameters: - (dict, optional) (reservation_prices) – the reservation prices of the users. If there is none, the bid will be assumed truthfull
- reservation_prices – (Default value = None)
- exclude – List of mechanisms to ignore will comuting statistics
Returns: stats –
- Dictionary with the differnt statistics. Currently:
- percentage_welfare
- percentage_traded
- profits
Return type:
Contributing¶
Contributions are welcome, and they are greatly appreciated! Every little bit helps, and credit will always be given.
You can contribute in many ways:
Types of Contributions¶
Report Bugs¶
Report bugs at https://github.com/gus0k/pymarket/issues.
If you are reporting a bug, please include:
- Your operating system name and version.
- Any details about your local setup that might be helpful in troubleshooting.
- Detailed steps to reproduce the bug.
Fix Bugs¶
Look through the GitHub issues for bugs. Anything tagged with “bug” and “help wanted” is open to whoever wants to implement it.
Implement Features¶
Look through the GitHub issues for features. Anything tagged with “enhancement” and “help wanted” is open to whoever wants to implement it.
Write Documentation¶
pymarket could always use more documentation, whether as part of the official pymarket docs, in docstrings, or even on the web in blog posts, articles, and such.
Submit Feedback¶
The best way to send feedback is to file an issue at https://github.com/gus0k/pymarket/issues.
If you are proposing a feature:
- Explain in detail how it would work.
- Keep the scope as narrow as possible, to make it easier to implement.
- Remember that this is a volunteer-driven project, and that contributions are welcome :)
Get Started!¶
Ready to contribute? Here’s how to set up pymarket for local development.
Fork the pymarket repo on GitHub.
Clone your fork locally:
$ git clone git@github.com:your_name_here/pymarket.git
Install your local copy into a virtualenv. Assuming you have virtualenvwrapper installed, this is how you set up your fork for local development:
$ mkvirtualenv pymarket $ cd pymarket/ $ python setup.py develop
Create a branch for local development:
$ git checkout -b name-of-your-bugfix-or-feature
Now you can make your changes locally.
When you’re done making changes, check that your changes pass flake8 and the tests, including testing other Python versions with tox:
$ flake8 pymarket tests $ python setup.py test or py.test $ tox
To get flake8 and tox, just pip install them into your virtualenv.
Commit your changes and push your branch to GitHub:
$ git add . $ git commit -m "Your detailed description of your changes." $ git push origin name-of-your-bugfix-or-feature
Submit a pull request through the GitHub website.
Pull Request Guidelines¶
Before you submit a pull request, check that it meets these guidelines:
- The pull request should include tests.
- If the pull request adds functionality, the docs should be updated. Put your new functionality into a function with a docstring, and add the feature to the list in README.rst.
- The pull request should work for Python 2.7, 3.4, 3.5 and 3.6, and for PyPy. Check https://travis-ci.org/gus0k/pymarket/pull_requests and make sure that the tests pass for all supported Python versions.
Deploying¶
A reminder for the maintainers on how to deploy. Make sure all your changes are committed (including an entry in HISTORY.rst). Then run:
$ bumpversion patch # possible: major / minor / patch
$ git push
$ git push --tags
Travis will then deploy to PyPI if tests pass.
Credits¶
Team¶
- Diego Kiedanski
- Daniel Kofman
- José Horta
Development Lead¶
- Diego Kiedanki <gusok@protonmail.com>
Contributors¶
None yet. Why not be the first?
References¶
Algorithms Used¶
- Segal-Halevi, Erel, Avinatan Hassidim, and Yonatan Aumann. “MUDA: a truthful multi-unit double-auction mechanism.” Thirty-Second AAAI Conference on Artificial Intelligence. 2018.
- Huang, Pu, Alan Scheller–Wolf, and Katia Sycara. “Design of a multi–unit double auction e–market.” Computational Intelligence 18.4 (2002): 596-617.
- Blouin, Max R., and Roberto Serrano. “A decentralized market with common values uncertainty: Non-steady states.” The Review of Economic Studies 68.2 (2001): 323-346.