# Copyright (C) 2008, 2009 by Exstrom Laboratories LLC # # This software is part of the book: # # Bet Smart: The Kelly System for Gambling and Investing. # by Stefan Hollos and Richard Hollos # # and is governed by the copyrights and laws pertaining to the book. # # This software is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. # # You can contact the authors via Email at: # # stefan(AT)exstrom.com # richard(AT)exstrom.com # # The website for the book and this software is: # http://www.quantwolf.com/betsmart.html # # kelly.py provides functions for calculating the Kelly fractions for # a single bet and two independent bets. # ####################################################################### # The single_iter and double_iter objects provide functions that will # be used in the kfsingle and kfidouble functions below to calculate # Kelly fractions. These objects are a way of getting around the limit # in Python that only alows the creation of closures for single line # functions. class single_iter(object): def __init__(self, rv, pv): self.pv = pv self.rv = rv def f(self, x): s1 = s2 = s3 = d1 = d2 = d3 = 0 for k in xrange(len(self.pv)): pk = self.pv[k] rk = self.rv[k] d1 = 1 + rk * x if d1 == 0: d1 = 0.000001 d2 = rk / d1 d3 = pk * d2 s1 += d2 s2 += d3 s3 += d2 * d3 return x - s2 / ( s1 * s2 - s3) ######################################### class idouble_iter(object): def __init__(self, rv1, rv2, pv1, pv2): self.pv1 = pv1 self.rv1 = rv1 self.pv2 = pv2 self.rv2 = rv2 def f(self, v0): x = v0[0] y = v0[1] s1 = s2 = s3 = s4 = s5 = s6 = s7 = d1 = 0 Rkl = Skl = pklRkl = pklSkl = 0 for k in xrange(len(self.pv1)): pk = self.pv1[k] rk = self.rv1[k] for l in xrange(len(self.pv2)): pl = self.pv2[l] sl = self.rv2[l] d1 = 1 + rk * x + sl * y if d1 == 0: d1 = 0.000001 Rkl = rk / d1 Skl = sl / d1 pklRkl = pk * pl * Rkl pklSkl = pk * pl * Skl s1 += Rkl s2 += Skl s3 += pklRkl s4 += pklSkl s5 += pklRkl * Rkl s6 += pklSkl * Skl s7 += pklRkl * Skl fx = s1 * s3 - s5 fy = s2 * s3 - s7 gx = s1 * s4 - s7 gy = s2 * s4 - s6 det = fx * gy - fy * gx dx = (s4 * fy - s3 * gy) / det dy = (s3 * gx - s4 * fx) / det return [x + dx, y + dy] ######################################### def expectation(pv, rv): """ Calculates the return expectation given: pv = vector of probabilities rv = vector of returns The value returned is just the scalar product of the two vectors. """ e = 0 for i in xrange(len(pv)): e += pv[i] * rv[i] return e ######################################### def dgsingle(rv, pv, f): """ Evaluates the derivative of the exponential growth factor for a single bet. rv = vector of returns pv = vector of probabilities f = bankroll fraction for the bet """ sum = 0.0 for i in xrange(len(pv)): sum += pv[i]*rv[i]/(1.0 + rv[i] * f) return sum ######################################### def dgidouble(rv1, rv2, pv1, pv2, f1, f2): """ Evaluates the derivative of the exponential growth factor for two independent bets. rv1 = vector of returns for bet 1 rv2 = vector of returns for bet 2 pv1 = vector of probabilities for bet 1 pv2 = vector of probabilities for bet 2 f1 = bankroll fraction for bet 1 f2 = bankroll fraction for bet 2 """ sum1 = sum2 = d = 0 for i in xrange(len(rv1)): for j in xrange(len(rv2)): d = pv1[i]*pv2[j]/(1.0+rv1[i]*f1+rv2[j]*f2) sum1 += rv1[i]*d sum2 += rv2[j]*d return [sum1, sum2] ######################################### def kfsingle(rv, pv): """ Calculates the optimal Kelly fraction for a single bet. rv = vector of returns pv = vector of probabilities """ fiter = single_iter(rv, pv) rmax = max(rv) rmin = min(rv) if rmax <= 0: return -1000000.0 # go short to the max if rmin >= 0: return 1000000.0 # go long to the max # set the initial max and min values for x if expectation(pv, rv) >= 0: xmax = -1.0 / rmin xmin = 0.0 x = xmax / 2.0 else: xmax = 0.0 xmin = -1.0 / rmax x = xmin / 2.0 # use a simple bisection method to get a tight box around x while (xmax - xmin) > 1.0e-8: if dgsingle(rv, pv, x) > 0: xmin = x else: xmax = x x = (xmax + xmin) / 2.0 # zero in on x using Newton's method x0 = x x1 = fiter.f(x) while abs(x1 - x0) > 1.0e-10: x0 = x1 x1 = fiter.f(x1) return x1 ######################################### def kfidouble(rv1, rv2, pv1, pv2): """ Calculates the optimal Kelly fraction for two independent bets. rv1 = vector of returns for bet 1 rv2 = vector of returns for bet 2 pv1 = vector of probabilities for bet 1 pv2 = vector of probabilities for bet 2 """ fiter = idouble_iter(rv1, rv2, pv1, pv2) # get Kelly fractions for each bet individually fs1 = kfsingle(rv1, pv1) fs2 = kfsingle(rv2, pv2) # if any of the individual fractions is max long or short # then we can return immediately if abs(fs1) == 1000000.0: if abs(fs2) == 1000000.0: return [fs1, fs2] else: return [fs1, 0] if abs(fs2) == 1000000.0: return [0, fs2] # set the initial max and min values for the bet 1 fraction if fs1 > 0: xmax = fs1 xmin = 0 else: xmax = 0 xmin = fs1 x = fs1 / 2.0 # set the initial max and min values for the bet 2 fraction if fs2 > 0: ymax = fs2 ymin = 0 else: ymax = 0 ymin = fs2 y = fs2 / 2.0 # use a simple bisection method to tighten the box around the # optimal x and y values while ((xmax - xmin) > 1.0e-8) and ((ymax - ymin) > 1.0e-8): fv = dgidouble(rv1, rv2, pv1, pv2, x, y) if fv[0] > 0: xmin = x else: xmax = x if fv[1] > 0: ymin = y else: ymax = y x = (xmax + xmin) / 2.0 y = (ymax + ymin) / 2.0 # zero in on x and y with Newton's method v0 = [x, y] v1 = fiter.f([x, y]) while abs(v1[0]-v0[0]) > 1.0e-10 or abs(v1[1]-v0[1]) > 1.0e-10: v0 = v1 v1 = fiter.f(v1) return v1