Search code examples
pythonscipysympymathematical-optimizationscipy-optimize

How to calculate the logarithmic maximum of n different independent simultaneous bets in Python?


I am trying to calculate the logarithmic maximum of n different bets. However, for this example, I have 2 independent simultaneous bets.

  1. Bet 1 has a win probability of 30% and decimal odds of 12.80.
  2. Bet 2 also has a win probability of 30% and decimal odds of 12.80.

To calculate the logarithmic maximum of 2 independent simultaneous bets, I need to work out the probability of all 4 combinations:

  1. Bet 1 Winning/Bet 2 Winning
  2. Bet 1 Winning/Bet 2 Losing
  3. Bet 1 Losing/Bet 2 Winning
  4. Bet 1 Losing/Bet 2 Losing

Assuming x0 is the amount between 0% and 100% of my portfolio on Bet 1 and x1 is the amount between 0% and 100% of my portfolio on Bet 2, the mathematically optimum stakes on both bets can be solved by maximising the following expression: 0.09log(1 + 11.8x0 + 11.8x1) + 0.21log(1 + 11.8x0 - x1) + 0.21log(1 - x0 + 11.8x1) + 0.49log(1 - x0 - x1) which equals x0: 0.214648, x1: 0.214648 (The 11.8 is not a typo, it is simply 12.8 - 1, the profit).

I have tried to implement this calculation in python, with little success. Here is my current code that I need assistance with:

from scipy.optimize import minimize
from math import log
from itertools import product
from sympy import symbols

Bets = [[0.3, 12.8], [0.3, 12.8]]

Odds = [([i[0], 1 - i[0]]) for i in Bets]
OddsList = list(product(Odds[0], Odds[1]))
#Output [(0.3, 0.3), (0.3, 0.7), (0.7, 0.3), (0.7, 0.7)]

Probability = []
for i in range(0, len(OddsList)):
    Probability.append(OddsList[i][0] * OddsList[i][1])
#Output [0.09, 0.21, 0.21, 0.49]

Win = [([i[1] - 1, - 1]) for i in Bets]
WinList = list(product(Win[0], Win[1]))
#Output [(11.8, 11.8), (11.8, -1), (-1, 11.8), (-1, -1)]

xValues = []
for j in range(0, len(Bets)):
    xValues.append(symbols('x' + str(j)))
#Output [x0, x1]

def logarithmic_return(xValues, Probability, WinList):
    Sum = 0
    for i in range(0, len(Probability)):
        Sum += Probability[i] * log (1 + (WinList[i][0] * xValues[0]) + ((WinList[i][1] * xValues[1])))
    return Sum

minimize(logarithmic_return(xValues, Probability, WinList))
#Error TypeError: Cannot convert expression to float

# However, when I do this, it works perfectly:
logarithmic_return([0.214648, 0.214648], Probability, WinList)
#Output 0.3911621722324154

Solution

  • Seems like this is your first time mixing numerical Python with symbolic. In short, you cannot use numerical functions (like math.log or scipy.optimize.minimize) on symbolic expressions. You need to convert your symbolic expressions to lambda function first.

    Let's try to fix it:

    from scipy.optimize import minimize
    from itertools import product
    from sympy import symbols, lambdify, log
    import numpy as np
    
    Bets = [[0.3, 12.8], [0.3, 12.8]]
    
    Odds = [([i[0], 1 - i[0]]) for i in Bets]
    OddsList = list(product(Odds[0], Odds[1]))
    #Output [(0.3, 0.3), (0.3, 0.7), (0.7, 0.3), (0.7, 0.7)]
    
    Probability = []
    for i in range(0, len(OddsList)):
        Probability.append(OddsList[i][0] * OddsList[i][1])
    #Output [0.09, 0.21, 0.21, 0.49]
    
    Win = [([i[1] - 1, - 1]) for i in Bets]
    WinList = list(product(Win[0], Win[1]))
    #Output [(11.8, 11.8), (11.8, -1), (-1, 11.8), (-1, -1)]
    
    xValues = []
    for j in range(0, len(Bets)):
        xValues.append(symbols('x' + str(j)))
    #Output [x0, x1]
    
    def logarithmic_return(xValues, Probability, WinList):
        Sum = 0
        for i in range(0, len(Probability)):
            Sum += Probability[i] * log (1 + (WinList[i][0] * xValues[0]) + ((WinList[i][1] * xValues[1])))
        return Sum
    
    # this is the symbolic expression
    expr = logarithmic_return(xValues, Probability, WinList)
    # convert the symbolic expression to a lambda function for
    # numerical evaluation
    f = lambdify(xValues, expr)
    # minimize expect a function of the type f(x), not f(x0, x1).
    # hence, we create a wrapper function
    func_to_minimize = lambda x: f(x[0], x[1])
    initial_guess = [0.5, 0.5]
    minimize(func_to_minimize, initial_guess)
    
    #      fun: -inf
    # hess_inv: array([[1, 0],
    #       [0, 1]])
    #      jac: array([nan, nan])
    #  message: 'NaN result encountered.'
    #     nfev: 3
    #      nit: 0
    #     njev: 1
    #   status: 3
    #  success: False
    #        x: array([0.5, 0.5])
    

    As you can see, the minimization works. However it didn't find any solution. This is your problem to fix. Here, I just hint you the shape of the function you are trying to minimize.

    enter image description here