A python code for the basic minority game (MG)

The minority game (MG) is probably the simplest mathematical model of a market, and has become a hot topic of research due to its simplicity and rich phenomena. Here I’m going to present a Python code for the basic MG. This code is simple and easily extensible. The readers of this article are assumed to be familiar with Python. Knowledge about the MG is preferred but not required. If you want to have a basic understanding of the MG, you may refer to here (Wiki). Here I’m not going to repeat the definition of the MG, but will go straight to build the code, during which the MG will be introduced in details naturally.

First of all, we need to define 3 basic objects: a strategy, a user, and a system. Their relationships are that a system consists of many users, and a user holds a set of strategies.

Here is the definition of a strategy:

<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>
class Strategy:
    def __init__(self):
        self.score = 0 # Strategy' initial score
        self.Responses = {} # Dict obj. for recording the action for each input (key)
    def init(self, state):
        self.Responses[state] = rand_pm() # rand_pm() generates randomly either -1 or 1
    def act(self, state): # act according to the current state (the global info)
        if not state in self.Responses: self.init(state)
        return self.Responses[state]
    def update(self, v=1):
        self.score += v #update the score by adding v to it

In the above definition of a strategy, we initialize each strategy with a score = 0. Each strategy takes a global state as the input by the method Strategy.act() and output an action according to the records in Strategy.Responses. We don’t need to initialize the responses for all possible inputting states. Instead, we initialize it only when it is requested/used for the first time. By doing this, it would save our memory space and speed up the simulation significantly. In addition, we also provide a function for updating the score, Strategy.update().

Next, we define a user:

class User:
    def __init__(self, s=2):
        self.s = s # number of strategies a user can have
        self.Strategies = [Strategy() for i in range(self.s)] # the user's strategies
        self.action = None # for recording the user's latest action
        self.score = 0 #the user's score, i.e., cumulative number of wins
        self.acted = False # signal telling if the user has acted

    def act(self, state):
        self.state = state # record the current global state
        # respond using the best strategy
        self.strategy_id = max_idx([x.score for x in self.Strategies])
        self.action = self.Strategies[self.strategy_id].act(state) #from the current state get the action
        self.acted = True # signal that the user has acted

    def update(self,w):
        # update the scores of the strategies based on if the user loses or wins
        assert self.acted, 'act() first before update()'
        # update the score of the strategies
        for strategy in self.Strategies:
            ds = bool_pm(strategy.act(self.state) == w)
            strategy.update(ds)
        # update the score of the user
        self.score += ds
        self.acted = False # reset the signal

In the User object, we initialize the user by s strategies. User.action is the current action of the user, could be either +1 or -1. In addition, we define an function User.act(), which takes the current global state as the input, and outputs an action. After each user returns an action, the net action is calculated by a System method (to be introduced below), and according to the net action, the user will either win or lose; and the scores of the strategies held by each user will be updated by User.update() that takes the current global winning action (w, to be introduced below) as the input. The flag User.acted ensures that the User.update() method is called after the calling of User.act(). The function max_idx() returns the index at which the value is the maximum (if there are more than 1 indices found, it randomly selects one to return).

Next, we define a system consisting of N users, who make their decision based on the global winning actions of the nearest past m steps:

class System:
    def __init__(self, T = 1, N = 101, m=3, s=2):
        self.T = T # number of steps to run each time
        self.N = N # number of users
        self.m = m # memory length
        self.s = s # number of strategies for each user
        self.Users = [User(s=self.s) for i in range(self.N)] #initialize the users
        self.Prices = [0] # for storing the prices, initialize it with zero
        self.SuccessRates = [] # for storing the global success rates
        self.W = [rand_pm() for i in range(self.m)]# initialize the global winning actions
        self.D = [] # net actions. sum_i a_i, where a_i is the i-th user's action +1 or -1

    @property
    def d(self): # current net action
        d = 0
        for u in self.Users:
            d += u.action
        return d<span 				data-mce-type="bookmark" 				id="mce_SELREST_start" 				data-mce-style="overflow:hidden;line-height:0" 				style="overflow:hidden;line-height:0" 			></span>

    def update(self): # update the system
        d = self.d
        self.D.append(d)
        self.W.append(minority(d)) # minority() returns -1 if d>0, 1 if d<0, and randomly -1 or 1 if d==0
        rate = (self.N-abs(d))/(2.0*self.N) # the success rate
        self.SuccessRates.append(rate)
        d = d/float(self.N) # normalize by N to get the price
        self.Prices.append(self.Prices[-1] + d) # update the price

    @property
    def state(self):
        return w2s(self.W[-self.m:]) # w2s() input a list of winning action, and output a state (integer)

    def run(self): #run for T steps
        for t in range(self.T):
            state = self.state
            for u in self.Users:
                u.act(state)
            self.update()
            for u in self.Users:
                u.update(self.W[-1])

We initialize the System object by N User objects. The meaning of other parameters are mentioned in the code. By default, System records the prices and the winning rates vs. time. If you want to measure any other quantity vs. time, you can set T=1, execute System.run() in a for loop, and record whatever parameters vs. time you want.

Here are some testing results:

prices
Fig. 1: Prices vs. time for 5 independent runs
variance-vs-m
Fig. 2: Volatility vs. m
success_rate-vs-m
Fig. 3: Success rate vs. m
strategy_scores_vs_t-s2m3
Fig. 4: Strategy scores vs. time for m=3, s=2, and N=101
strategy_scores_vs_t-s2m7
Fig. 5: Strategy scores vs. time for m=7, s=2, and N=101

The code is available on  GitHub.

Here are some further readings on the MG:

[1] Challet, Damien, Matteo Marsili, and Yi-Cheng Zhang. “Minority games: interacting agents in financial markets.” OUP Catalogue (2013).

[2] Moro, Esteban. “The minority game: an introductory guide.” arXiv preprint cond-mat/0402651 (2004).

[3] Björkman, Henrik, and Andreas Olsson. “The evolution of strategy scores in the Grand Canonical Minority Game.”

One thought on “A python code for the basic minority game (MG)

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s