In [1]:
    
import matplotlib.pylab as pl
import numpy as np
from scipy.optimize import fmin_cobyla
import pandas as pd
%matplotlib inline
    
In [2]:
    
result1_task1 = {
    'description': 'Attempt to reproduce the result 1 of this article',
    'doi': '10.1051/itmconf/20140201004',
    'reference': 'result 1, p. 4',
    'type': 'scientific task',
    'possible_outcomes': [
        'the result is reproducible',
        'the result is not reproducible'
        ]
}
    
Heavily inspired from this blog post: Augur’s Automated Market Maker: The LS-LMSR, By Dr. Abe Othman.
The cost function for the LMSR is given by:
$$ C(\textbf{q}) = b \log \left(\sum_{i=1}^n e^{\frac{q_i}{b}} \right) $$and the marginal prices on each event are given by the partial derivatives of the cost function: $$ p_j(\textbf{q}) = \frac{e^{\frac{q_j}{b}}}{\sum_{i=1}^n e^{\frac{q_i}{b}}} $$ where $b$, which is defined as a constant in the original LMSR model of Hanson, is here defined as a variable of q $$ b(\textbf{q})=\alpha \sum_{i=1}^n q_i $$
with $\alpha$ defined as
$$ \alpha = \frac{0.1}{n \log{n}} $$with $n$ being the number of dimensions of $\textbf{q}$
In [29]:
    
class LS_LMSRMarket(object):
    def __init__(self, task, vig=0.1, init=1.0, market='LS_LMSR', b=None):
        """
        Parameters
        ----------
        task    dict
                A dictionary describing the task for which the predictive market is created.
                Keys:
                -----
                type:               str 
                                    (e.g. 'scientific task')
                description:        str
                                    description of the task to be performed
                reference:          str
                                    Internal reference (e.g. 'result 1, p. 4')
                doi:                str
                                    DOI of the related publication
                possible_outcomes:  list
                                    List of strings describing the possible outcomes of the task
                                    
        vig     float
                parameter of the `alpha` variable used to calculate the `b` variable.
                Corresponds to the market "vig" value - typically between 5 and 30 percent in real-world markets
                
        init    float
                The initial subsidies of the market, spread equally in this algorithm on all the outcomes.
        
        market  srt, 'LS_LMSR' | 'LMSR'
                The market type. If 'LMSR' is selected, then a b value should be given.
        """
        self.market = market
        if self.market == 'LSMR':
            if b == None:
                raise Exception('b value is needed for LSMR markets')
            self._b = b
        for k, v in task.items():
            setattr(self, k, v)
        self.init = init
        self.n = len(self.possible_outcomes)
        self._x = [np.ones([self.n])*init/self.n]
        self._book = []
        self.market_value = init
        self._history = []
        self.alpha = vig*self.n/np.log(self.n)
        
    @property
    def b(self):
        if self.market == 'LMSR':
            return self._b
        elif self.market == 'LS_LMSR':
            return self._b_func(self.x)
        else:
            raise Exception('market must be set to either "LMSR" or "LS_LMSR"')
    
    def _b_func(self, x):
        """Calculate the `b` equation: b=\alpha \Sigma x"""
        return self.alpha * x.sum()
        
    @property
    def book(self):
        return pd.DataFrame(self._book)
    
    @property
    def x(self):
        return self._x[-1].copy()
    
    def cost(self, x):
        return self.b*np.log(np.exp(x/self.b).sum())
    
    def _new_x(self, shares, outcome):
        new_x = self.x
        new_x[outcome] += shares        
        return new_x
            
    def price(self, shares, outcome):
        return self._price(self._new_x(shares, outcome))
        
    def _price(self, x):
        return self.cost(x)-self.cost(self.x)
    
    def register_x(self, x):
        self._x.append(x)
        
    def calculate_shares(self, paid, outcome):
        obj_func = lambda s: np.abs(self.price(s, outcome) - paid)
        return fmin_cobyla(obj_func, paid/self.p[outcome], [])
    
    def buy_shares(self, name, paid, outcome):
        shares = self.calculate_shares(paid, outcome)
        self.register_x(self._new_x(shares, outcome))
        self._book.append({'name':name, 
                           'shares':shares, 
                           'outcome':outcome, 
                           'paid':paid})
        self._history.append(self.p)
        self.market_value += paid
        print("%s paid %2.2f EUR, for %2.2f shares of outcome %d, which will give him %2.2f EUR if he wins"%(
                name, paid, shares, outcome, shares/self.x[outcome]*self.market_value))
        return shares
    
    def sell_shares(self, name, shares, outcome):
        price = self.price(-share, outcome)
        self._book.append({'name':name, 
                           'shares':-shares, 
                           'outcome':outcome, 
                           'paid':-price}) 
        self.market_value -= price        
        self._history.append(self.p)        
        return price
    def outcome_probability(self):
        K = np.exp(self.x/self.b)
        return K/K.sum()
    
    @property
    def p(self):
        return self.outcome_probability()
    
    def history(self):
        return np.array(self._history)
    
In [30]:
    
pm = LS_LMSRMarket(result1_task1, init=10., vig=0.1)
    
In [31]:
    
pm.buy_shares('Mark', 1., 0)
    
    
    Out[31]:
In [32]:
    
pm.buy_shares('Erik', 300., 1)
pm.buy_shares('Soeren', 1., 0)
pm.buy_shares('Albert', 3., 1)
    
    
    Out[32]:
In [33]:
    
pm.market_value
    
    Out[33]:
In [34]:
    
pm.book
    
    Out[34]:
In [43]:
    
total_shares = pm.book.groupby('outcome').shares.sum()
book = pm.book
book['possible_payout'] = pm.market_value * pm.book.shares / total_shares.values[pm.book.outcome.values]
book['ownership_ratio'] = pm.book.shares / total_shares.values[pm.book.outcome.values]
grouped = book.groupby('name')
df = grouped.paid.sum().to_frame(name='paid')
df['possible_payout'] = grouped.possible_payout.sum()
df
    
    Out[43]:
In [44]:
    
book
    
    Out[44]:
In [45]:
    
pm.x
    
    Out[45]:
In [46]:
    
pm.market_value
    
    Out[46]:
In [9]:
    
pl.plot(pm.history())
pl.ylim([0.,1.])
pl.legend(['outcome 0', 'outcome 1'])
    
    Out[9]:
    
Inspired from Augur white paper
Joe is creating a new event
In [10]:
    
new_event = {
    "type": "CreateEvent", 
    "vin": [{
        "n": 0,
        "value": 0.01000000,
        "units": "bitcoin", 
        "scriptSig": """
            <Joe’s signature>
            <Joe´s public key >"""
        }], 
    "vout": [{
        "n": 0,
        "value" : 0.01000000, 
        "units": "bitcoin", 
        "event": {
            "id": "<event hash >", 
            "description": """Hillary Clinton 
                              wins the 2016 U.S. 
                              Presidential Election.""",
            "branch": "politics", 
            "is_binary": True, 
            "valid_range": [0, 1], 
            "expiration": 1478329200, 
            "creator": "<Joe’s address>"
            },
        "address": "<base-58 event ID>", 
        "script": """
            OP_DUP 
            OP_HASH160 
            <event hash > 
            OP_EQUALVERIFY 
            OP_MARKETCHECK"""
        }]
    }
    
Joe is creating a Market of events
In [11]:
    
new_market = {
    "type": "CreateMarket", 
    "loss_limit": 1.2, 
    "vin": [{
        "n": 0,
        "value": 27.72588722,
        "units": "bitcoin", 
        "tradingFee": 0.005, 
        "scriptSig": """<Joe’s signature>
                        <Joe ’s public key >"""
        }], 
    "vout": [{
        "n": 0,
        "value": 27.72588722, 
        "units": "bitcoin", 
        "script": """
            OP_DUP
            OP_HASH160 
            OP_EVENTLOOKUP 
            OP_ISSHARES 
            OP_MARKETCHECK"""
        },
        {
        "n": 1,
        "value": 10**9,
        "units": "shares", 
        "event": "<event -1 hash >", 
        "branch": "politics", 
        "script": """
            OP_DUP
            OP_HASH160 
            OP_EVENTLOOKUP 
            OP_ISBITCOIN 
            OP_MARKETCHECK"""
        },
        {
        "n": 2,
        "value": 10**9,
        "units": "shares",
        "event": "<event-2 hash>",
        "branch": "politics", 
        "script": """
            OP_DUP
            OP_HASH160 
            OP_EVENTLOOKUP 
            OP_ISBITCOIN
            OP_MARKETCHECK"""
        }],
    "id": "<market hash>",
    "creator": "<Joe’s address>"
}
    
In [12]:
    
n = 100
outcome = 0.001
# The amount is assumed to increase linearly with time, as the market increases its liquidity
amount = np.random.random([n]) * 100. #* (1+np.arange(n))/(1.*n)
outcomes = np.zeros([n])
outcomes[np.random.random([n])<outcome] = 1.0
    
Creating the new task prediction market
In [13]:
    
pm = LS_LMSRMarket(result1_task1, init=10., vig=0.1)
    
One company comes along and bet sh*t ton of money
In [14]:
    
pm.buy_shares('EvilMegaCorp', 1000, 1)
    
    
    Out[14]:
Performing the bets
In [15]:
    
for i, a, o in zip(range(n),amount, outcomes):
    pm.buy_shares('Trader-%d'%(i), a, int(o))
    
    
In [16]:
    
pm.buy_shares('EvilMegaCorp', 1000, 1)
    
    
    Out[16]:
The total to pay for each outcome
In [17]:
    
pm.market_value
    
    Out[17]:
In [18]:
    
total_shares = pm.book.groupby('outcome').shares.sum()
book = pm.book
book['possible_payout'] = pm.market_value * pm.book.shares / total_shares.values[pm.book.outcome.values]
grouped = book.groupby('name')
df = grouped.paid.sum().to_frame(name='paid')
df['possible_payout'] = grouped.possible_payout.sum()
    
In [19]:
    
df
    
    Out[19]:
In [20]:
    
total = pm.book.groupby('outcome').shares.sum()
total
    
    Out[20]:
In [21]:
    
pm.book.groupby('outcome').paid.sum()
    
    Out[21]:
In [22]:
    
pm.book.groupby('outcome').shares.sum()
    
    Out[22]:
In [23]:
    
pm.p
    
    Out[23]:
In [24]:
    
pm.book.groupby('outcome').sum().values/pm.book.groupby('outcome').sum().values.sum()
    
    Out[24]:
Plot the market prediction history
In [25]:
    
pl.plot(pm.history())
pl.ylim([0.,1.])
pl.legend(['outcome 0', 'outcome 1'])
pl.title('%d Trades, total market value=%2.2f EUR'%(n, pm.market_value))
    
    Out[25]:
    
The book of trades
In [26]:
    
book = pm.book
book['possible_win'] = pm.book.owed - pm.book.paid
book['p0'] = pm.history()[:,0]
book['p1'] = pm.history()[:,1]
book
    
    
In [ ]:
    
pm.market_value
    
In [ ]:
    
pm.p
    
In [ ]:
    
    
In [ ]:
    
    
In [ ]: