I have the following two pandas dataframes: new & outcome
new = pd.DataFrame([[5,5,1.6],[0.22,0.22,0.56]]).T
new.index = ['Visitor','Draw','Home']
new.columns = ['Decimal odds', 'Win prob']
new['Bet amount'] = np.zeros((len(new),1))
With output:
Decimal odds Win prob Bet amount
Visitor 5.0 0.22 0.0
Draw 5.0 0.22 0.0
Home 1.6 0.56 0.0
And dataframe 'outcome'
outcome = pd.DataFrame([[0.22,0.22,0.56],[100,100,100]]).T
outcome.index = ['Visitor win','Draw','Home win']
outcome.columns = ['Prob.','Starting bankroll']
outcome['Wins'] = ((new['Decimal odds'] - 1) * new['Bet amount']).values
outcome['Losses'] = [sum(new['Bet amount'][[1,2]]) , sum(new['Bet amount'][[0,2]]), sum(new['Bet amount'][[0,1]])]
outcome['Ending bankroll'] = outcome['Starting bankroll'] + outcome['Wins'] - outcome['Losses']
outcome['Logarithm'] = np.log(outcome['Ending bankroll'])
With output:
Prob. Starting bankroll Wins Losses Ending bankroll Logarithm
Visitor win 0.22 100.0 0.0 0.0 100.0 4.60517
Draw 0.22 100.0 0.0 0.0 100.0 4.60517
Home win 0.56 100.0 0.0 0.0 100.0 4.60517
Hereby the objective is calculated by the formula below:
objective = sum(outcome['Prob.'] * outcome['Logarithm'])
Now I want to maximize the objective by the values contained in column `new['Bet amount']. The constraints are that a, b, and c are bounded between 0 and 100. Also the summation of a, b and c must be below 100. Reason is that a,b,c resemble the ratio of your bankroll that is used to place a sports bet.
Want to achieve this using the scipy library. My code so far looks like:
from scipy.optimize import minimize
prob = new['Win prob']
decimal = new['Decimal odds']
bank = outcome['Starting bankroll'][0]
def constraint1(bet):
a,b,c = bet
return 100 - a + b + c
con1 = {'type': 'ineq', 'fun': constraint1}
cons = [con1]
b0, b1, b2 = (0,100), (0,100), (0,100)
bnds = (b0, b1, b2)
def f(bet, sign = -1):
global prob, decimal, bank
p0,p1,p2 = prob
d0,d1,d2 = decimal
a,b,c = bet
wins0 = a * (d0-1)
wins1 = b * (d1-1)
wins2 = c * (d2-1)
loss0 = b + c
loss1 = a + c
loss2 = a + b
log0 = np.log(bank + wins0 - loss0)
log1 = np.log(bank + wins1 - loss1)
log2 = np.log(bank + wins2 - loss2)
objective = (log0 * p0 + log1 * p1 + log2 * p2)
return sign * objective
bet = [5,8,7]
result = minimize(f, bet, method = 'SLSQP', bounds = bnds, constraints = cons)
This however, does not result in the desired result. Desired result would be:
a = 3.33
b = 3.33
c = 0
My question is also how to set the method and initial values? Results seem to differ a lot by assigning different method's and initial values for the bets.
Any help would be greatly appreciated!
(This is an example posted on the pinnacle website: https://www.pinnacle.com/en/betting-articles/Betting-Strategy/the-real-kelly-criterion/HZKJTFCB3KNYN9CJ)
If you print out the "bet" values inside your function, you can see where it's going wrong.
[5. 8. 7.]
[5.00000001 8. 7. ]
[5. 8.00000001 7. ]
[5. 8. 7.00000001]
[5.00040728 7.9990977 6.99975556]
[5.00040729 7.9990977 6.99975556]
[5.00040728 7.99909772 6.99975556]
[5.00040728 7.9990977 6.99975558]
[5.00244218 7.99458802 6.99853367]
[5.0024422 7.99458802 6.99853367]
The algorithm is trying to optimize the formula with very small adjustments relative to your initial values, and it never adjusts enough to get to the values you're looking for.
If you check scipy webpage, you find https://docs.scipy.org/doc/scipy/reference/optimize.minimize-slsqp.html#optimize-minimize-slsqp
eps float
Step size used for numerical approximation of the Jacobian.
result = minimize(f, bet, method='SLSQP', bounds=bnds, constraints=cons,
options={'maxiter': 100, 'ftol': 1e-06, 'iprint': 1, 'disp': True,
'eps': 1.4901161193847656e-08, 'finite_diff_rel_step': None})
So you're starting off with a step size of 1.0e-08, so your initial estimates are off by many orders of magnitude outside the range where the algorithm is going to be looking.
I'd recommend normalizing your bets to values between zero and 1. So instead of saying I'm placing a bet between 0 and 100, just say you're wagering a fraction of your net wealth between 0 and 1. A lot of algorithms are designed to work with standardized inputs (between 0 and 1) or normalized inputs (standard deviations from the mean).
Also, it looks like :
def constraint1(bet):
a,b,c = bet
return 100 - a + b + c
should be:
def constraint1(bet):
a,b,c = bet
return 100 - (a + b + c)
but I don't think that impacts your results
Related
Good day!
I am using python v3.8 and I want to calculate the value range (minimum and maximum) of a given formula with (also given) bounds e.g.:
formula1: a * sqrt(b/c)
formula2: a^2 * b/1000 + 3 * (a+b)
formula3: (1/(2 * PI * (a * 1000) * (b * 1000)) * 10^12
..with a=[0,5], b=[10,20], c=[30,40]
I am not too familiar with scipy, numpy, sympy.. and I wonder if there is an "easy" way to just calculate the formula with different values, write it into an array and get the min/max from that? The problem I have with "writing into an array and get min/max" is, that there are some bounds [-100000, 100000] with given float numbers and that would generate way too much values.
I do not need the information with which values the minimum/maximum was reached, but only which min/max values can be reached.
Try SymPy solvers, they have solveset that can do the following:
>>> solveset(Eq(x**2, 1), x)
{-1, 1}
Symbolic:
Maximum and minimum points are called critical points. To find a critical point you need to a partial derivative respect every variable (f'_a = df/da, f'_b = df/db, f'_c = df/dc), and solve the system of equations where all of them are equal to 0. We can do this with sympy.
import sympy as sp
a = sp.Symbol('a', real=True)
b = sp.Symbol('b', real=True)
c = sp.Symbol('c', real=True)
functions = [
a * sp.sqrt(b / c),
a**2 * b/1000 + 3*(a+b),
10**6 / (2 * sp.pi * a * b),
]
for f in functions:
f_a, f_b, f_c = f.diff(a), f.diff(b), f.diff(c)
print(f"f(a, b, c) = {f}")
print(f"f'_a(a, b, c) = {f_a} = 0")
print(f"f'_b(a, b, c) = {f_b} = 0")
print(f"f'_c(a, b, c) = {f_c} = 0")
print("Critical points:", sp.solve([f_a, f_b, f_c], a, b, c))
print()
As you can see if you execute this code, there is no critical point, so no absolute maximum nor minimum value for any of these funtions in the Real domain (there are two critical points for the second equation in the Imaginary domain).
Numeric approach:
Using numpy and pandas we can create a matrix with all the possible combinations and then apply each of our functions. The maximum and minimum values are per column, they are not related between the columns. As expected, the max and min values for the a, b and c columns are the range lower and upper bounds.
import pandas as pd
from pandas.core.reshape.util import cartesian_product
import numpy as np
# Number of values per range
N = 21
# Functions
functions = [
lambda row: row['a'] * np.sqrt(row['b'] / row['c']),
lambda row: row['a']**2 * row['b']/1000 + 3*(row['a']+row['b']),
lambda row: 10**6 / (2 * np.pi * row['a'] * row['b']),
]
# Lower and upper bounds
a_lower, a_upper = 0, 5
b_lower, b_upper = 10, 20
c_lower, c_upper = 30, 40
def min_max(col):
return pd.Series(index=['min', 'max'], data=[col.min(), col.max()])
values = [
np.linspace(a_lower, a_upper, N),
np.linspace(b_lower, b_upper, N),
np.linspace(c_lower, c_upper, N),
]
df = pd.DataFrame(cartesian_product(values), index=['a', 'b', 'c']).T
for i, f in enumerate(functions):
df[f'f_{i + 1}'] = df.apply(f, axis=1)
print(df.apply(min_max))
Output:
a b c f_1 f_2 f_3
min 0.0 10.0 30.0 0.000000 30.0 1591.549431
max 5.0 20.0 40.0 4.082483 75.5 inf
N = 101 has exactly the same output (it takes a bit to process as it has to compute the formulas 101^3 > 1M times)
Assuming we have the below function to optimize for 4 parameters, we have to write the function as below, but if we want the same function with more number of parameters, we have to rewrite the function definition.
def radius (z,a0,a1,k0,k1,):
k = np.array([k0,k1,])
a = np.array([a0,a1,])
w = 1.0
phi = 0.0
rs = r0 + np.sum(a*np.sin(k*z +w*t +phi), axis=1)
return rs
The question is if this can be done easier in a more automatic way, and more intuitive than this question suggests.
example would be as following which has to be written by hand.
def radius (z,a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,k0,k1,k2,k3,k4,k5,k6,k7,k8,k9,):
k = np.array([k0,k1,k2,k3,k4,k5,k6,k7,k8,k9,])
a = np.array([a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,])
w = 1.0
phi = 0.0
rs = r0 + np.sum(a*np.sin(k*z +w*t +phi), axis=1)
return rs
Excel sheet solver function predicts a=-12.7705719809672 and b=4.65590041575483 value based on the following dataframe.
Maturity (C-Days) Strength, (Mpa) x y y^2 (y-x)^2
10.8 23.8 23.8495018161717 -0.6 36 0.002450429804294
28.2 28.4 28.3164712450952 -1.4 1.96000000000001 0.006977052895941
70.7 32.6 32.5941766134432 2.8 7.84 3.3911830989322E-05
105.0 34.4 34.4398346638965 4.6 21.16 0.001586800447746
Calculate dataframe row value by the following the formula.
x[i] = -a + b*ln(M[i])
y[i] = S[i] - avg_strength
y^2[i] = y[i]^2
(y-x)^2[i] = S[i] - x[i]
sum(y^2) = 208.0944
sum((y-x)^2) = 0.011048194978971
Where
avg_strength = 23.86 # Avg. Strength
i - Row number
S - Strength
M - Maturity
calculate R^2
First time assumes a and b value to calculate x and (y-x)^2.
R^2 = 1 - sum((y-x)^2)/sum(y^2)
where must be R^2 >= 0.9 and predict a and b value.
I'm looking solution same as excel solver function in python to
predict a and b value.
My Python Code:
import pandas as pd
import numpy as np
from pulp import LpVariable, LpMinimize, LpProblem,lpSum,LpStatus,lpSum,LpMaximize,LpInteger
import math
m_s_data = {'maturity':[0.1,10.8,28.2,70.7,105.0],'strength':[0.1,23.8,28.4,32.6,34.4]}
df = pd.DataFrame(m_s_data)
strength_avg = round(df['strength'].mean(),2)
df['y'] = df['strength'] - strength_avg
df['y2'] = df['y']**2
y2_sum = sum([df['y2'][idx] for idx in df.index])
x = LpVariable.dicts("x", df.index,lowBound=-100,upBound=100)
y3 = LpVariable.dicts("y3", df.index,lowBound=-100,upBound=100)
mod = LpProblem("calAB", LpMinimize)
a=1
b=1
for idx in df.index:
x_row_data = -a + b * np.log(df['maturity'][idx])
mod += x[idx] == x_row_data
strength = df['strength'][idx]
mod += y3[idx] == math.pow(strength,2) + x_row_data * x_row_data -2* strength * x_row_data
#R^2 must be greater than or equal to 0.9
mod += 1- (lpSum(y3[idx] for idx in df.index)/y2_sum) >= 0.9
mod.solve()
print(df)
# Each of the variables is printed with it's resolved optimum value
for idx in df.index:
print(y3[idx].name, "=", y3[idx].value)
Input dataframe:
Excepted output dataframe:
You can simply use any kind of linear solver using a least square algorithm. Here I use np.linalg.lstsq().
import numpy as np
#we have a system of linear equation: Ax = b, according to your equation :-x[0] + x[1]*ln(M) = b
M = np.log([10.80000,28.20000,70.70000,105.00000])
A = np.vstack((-np.ones(M.size),M))
b = np.array([23.84950,28.31647,32.59418,34.43983])
x = np.linalg.lstsq(A.transpose(),b)[0]
Results:
x = array([-12.77023019, 4.65571618])
I have a function with three parameters a,b and c and I want to define different priors for each of these parameters. I am using the emcee package.
I started with the simple uniform (non-informative) prior:
def lnprior(theta):
m, b, c = theta
if 1.0 < m < 2.0 and 1.0 < b < 2.0 and 1.0 < c < 2.0:
return 0.0
return -np.inf
I would like to have for each parameter a different prior. For instance for a I would like to have a Normal(mu,sigma) prior, while for b an uniform and for c a Jeffreys prior (1/c). Up to now I come out with the following:
def lnprior(theta):
a, b, c = theta
mu = 0.5 # mean of the Normal prior
sigma = 0.1 # standard deviation of the Normal prior
if not (1.0 < b < 2.0): # the bound on the uniform
return -np.inf
if c < 0.0: # the bound on the Jeffreys
return -np.inf
return .... # total log-prior to be determined
As far as I understood in log-scale I have to add together all the probabilities to define the total one (the return value of lnprior). So let's start with the Normal on a:
log_Pr(a) = np.log( 1.0 / (np.sqrt(2*np.pi)*sigma) ) - 0.5*(a - mu)**2/sigma**2;
then the prior on c:
log_Pr(c) = -log(c).
Thus the total log-prior should be: Pr(a)+Pr(c). My question, is this approach correct?
Thanks
Try the following one:
def lnprior(theta):
a, b, c = theta
#flat priors on b, c
if not 1.0 < b < 2.0 and c > 0:
return -np.inf
#gaussian prior on a and c
mu = 0.5
sigma = 0.1
### your prior is gaussian * (1/c), take natural log is the following:
return np.log(1.0/(np.sqrt(2*np.pi)*sigma))-0.5*(a-mu)**2/sigma**2 - np.log(c)
Let's suppose that we have got a list which appends an integer in each iteration which is between 15, 32(let's call the integer rand). I want to design an algorithm which assigns a reward around 1 (between 1.25 and 0.75) to each rand. the rule for assigning the reward goes like this.
first we calculate the average of the list. Then if rand is more than average, we expect the reward to be less than 1, and if rand is less than average, the reward gets higher than 1. The more distance between average and rand, the more reward increases/decreases.
for example:
rand = 15, avg = 23 then reward = 1.25
rand = 32, avg = 23 then reward = 0.75
rand = 23, avg = 23 then reward = 1
and so on.
I had developed the code below for this algorithm:
import numpy as np
rollouts = np.array([])
i = 0
def modify_reward(lst, rand):
reward = 1
constant1 = 0.25
constant2 = 1
std = np.std(lst)
global avg
avg = np.mean(lst)
sub = np.subtract(avg, rand)
landa = sub / std if std != 0 else 0
coefficient = -1 + ( 2 / (1 + np.exp(-constant2 * landa)))
md_reward = reward + (reward * constant1 * coefficient)
return md_reward
while i < 100:
rand = np.random.randint(15, 33)
rollouts = np.append(rollouts, rand)
modified_reward = modify_reward(rollouts, rand)
i += 1
print([i,rand, avg, modified_reward])
# test the reward for upper bound and lower bound
rand1, rand2 = 15, 32
reward1, reward2 = modify_reward(rollouts, rand1), modify_reward(rollouts, rand2)
print(['reward for upper bound', rand1, avg, reward1])
print(['reward for lower bound', rand2, avg, reward2])
The algorithm works quite fine, but if you look at examples below, you would notice the problem with algorithm.
rand = 15, avg = 23.94 then reward = 1.17 # which has to be 1.25
rand = 32, avg = 23.94 then reward = 0.84 # which has to be 0.75
rand = 15, avg = 27.38 then reward = 1.15 # which has to be 1.25
rand = 32, avg = 27.38 then reward = 0.93 # which has to be 0.75
As you might have noticed, Algorithm doesn't consider the distance between avg and bounds (15, 32).
The more avg moves towards lower bound or higher bound, the more modified_reward gets unbalanced.
I need modified_reward to be uniformly assigned, no matter avg moves toward upper bound or lower bound.
Can anyone suggest some modification to this algorithm which could consider the distance between avg and bounds of the list.
Putting together these two requirements:
if rand is more than average, we expect the reward to be less than 1, and if rand is less than average, the reward gets higher than 1.
I need modified_reward to be uniformly assigned, no matter avg moves toward upper bound or lower bound.
is slightly tricky, depending on what you mean by 'uniformly'.
If you want 15 to always be rewarded with 1.25, and 32 to always be rewarded with 0.75, you can't have a single linear relationship while also respecting your first requirement.
If you are happy with two linear relationships, you can aim for a situation where modified_reward depends on rand like this:
which I produced with this Wolfram Alpha query. As you can see, this is two linear relationships, with a 'knee' at avg. I expect you'll be able to derive the formulae for each part without too much trouble.
This code implements a linear distribution of weights proportional to the distance from average towards your given limits.
import numpy as np
class Rewarder(object):
lo = 15
hi = 32
weight = 0.25
def __init__(self):
self.lst = np.array([])
def append(self, x):
self.lst = np.append(self.lst, [x])
def average(self):
return np.mean(self.lst)
def distribution(self, a, x, b):
'''
Return a number between 0 and 1 proportional to
the distance of x from a towards b.
Note: Modify this fraction if you want a normal distribution
or quadratic etc.
'''
return (x - a) / (b - a)
def reward(self, x):
avg = self.average()
if x > avg :
w = self.distribution(avg, x, self.hi)
else:
w = - self.distribution(avg, x, self.lo)
return 1 - self.weight * w
rollouts = Rewarder()
rollouts.append(23)
print rollouts.reward(15)
print rollouts.reward(32)
print rollouts.reward(23)
Producing:
1.25
0.75
1.0
The code in your question seems to be using np.std which I presume is an attempt to get a normal distribution. Remember that the normal distribution never actually gets to zero.
If you tell me what shape you want for the distribution we can modify Rewarder.distribution to suit.
Edit:
I can't access the paper you refer to but infer that you want a sigmoid style distribution of rewards giving a 0 at mean and approximately +/-0.25 at min and max. Using the error function as the weighting if we scale by 2 we get approximately 0.995 at min and max.
Override the Rewarder.distribution:
import math
class RewarderERF(Rewarder):
def distribution(self, a, x, b):
"""
Return an Error Function (sigmoid) weigthing of the distance from a.
Note: scaled to reduce error at max to ~0.003
ref: https://en.wikipedia.org/wiki/Sigmoid_function
"""
return math.erf(2.0 * super(RewarderERF, self).distribution(a, x, b))
rollouts = RewarderERF()
rollouts.append(23)
print rollouts.reward(15)
print rollouts.reward(32)
print rollouts.reward(23)
results in:
1.24878131454
0.75121868546
1.0
You can choose which error function suits your application and how much error you can accept at min and max. I'd also expect that you'd integrate all these functions into your class, I've split everything out so we can see the parts.
Regarding the calculating the mean, do you need to keep the list of values and recalculate each time or can you keep a count and running total of the sum? Then you would not need numpy for this calculation.
I don't understand why you are calculating md_reward like this. Please provide logic and reason. But
landa = sub / std if std != 0 else 0
coefficient = -1 + ( 2 / (1 + np.exp(-constant2 * landa)))
md_reward = reward + (reward * constant1 * coefficient)
will not give what you are looking for. Because lets consider below cases
for md_reward to be .75
--> coefficient should be -1
--> landa == -infinite (negative large value, i.e. , rand should be much larger than 32)
for md_reward to be 1
--> coefficient should be 0
--> landa == 0 (std == 0 or sub == 0) # which is possible
for md_reward to be 1.25
--> coefficient should be 1
--> landa == infinite (positive large value, i.e. , rand should be much smaller than 15)
If you want to normalize reward from avg to max and avg to min. check below links.
https://stats.stackexchange.com/questions/70801/how-to-normalize-data-to-0-1-range
https://stats.stackexchange.com/questions/70553/what-does-normalization-mean-and-how-to-verify-that-a-sample-or-a-distribution
Now modify your function with something below.
def modify_reward(lst, rand):
reward = 1
constant1 = 0.25
min_value = 15
max_value = 32
avg = np.mean(lst)
if rand >= avg:
md_reward = reward - constant1*(rand - avg)/(max_value - avg) # normalize rand from avg to max
else:
md_reward = reward + constant1*(1 - (rand - min_value)/(avg - min_value)) # normalize rand from min to avg
return md_reward
I have used below method
Normalized:
(X−min(X))/(max(X)−min(X))
for case rand >= avg
min(X) will be avg and max(X) is max_value
and for case rand < avg
min(X) in min_value and max(X) is avg
Hope this helps.
Try this
def modify_reward(lst, rand):
reward = 1
constant = 0.25 #Think of this as the +/- amount from initial reward
global avg
avg = np.mean(lst)
sub = np.subtract(avg, rand)
dreward = 0
if sub>0:
dreward = sub/(avg-15) #put your lower boundary instead of 15
elif sub<0:
dreward = sub/(32-avg) #put your higher boundary instead of 32
md_reward = reward +(dreward*constant)
return md_reward
This is the linear solution inspired by #AakashM. I don't know if this is what you were looking for, but this fits your description.