View New Posts
1. ## Simultaneous Event Kelly Calculator Beta

To all those interested, I'm looking for comments on the beta version of my Simultaneous Event Kelly Calculator.

Unfortunately, no documentation is yet available.

Go to SBR Betting Tools and click "Kelly Calculator (Simultaneous Events)".

2. It looks sharp but what the fuk is it? How does it work?

Thanks Ganch

3. Originally Posted by jjgold
It looks sharp but what the fuk is it? How does it work?

Thanks Ganch
The Kelly formula as traditionally stated (K = [% Edge]/[Decimal Odds-1]) is only correct in the case of single, non-simultaneous bets. This Simultaneous Event Kelly Calculator, as the name implies, calculates exact Kelly-style stakes for up to 15 simultaneous events (bets). It does so by implementing the recursive methodology I outlined in this post.

Inputs:
1. # of Simultaneous Events on which you're considering placing bets.
2. Kelly Multiplier: A Kelly multiplier of ½ would imply half-Kelly, of 1 would imply full Kelly, and 2 would imply double Kelly. Note that results will true Kelly fractions, a concept I explain to discuss in Part II of my Kelly article series, and as half-Kelly won't always be exactly one-half of full Kelly.
3. Consecutive Series: This represents the number of times you anticipate placing bets similar to these consecutively. See example below.
4. Bankroll: The size of your betting bankroll. Setting to 1 or 100% will give reasults as percentage of bankroll. Setting to a dollar value with no decimal point will give integer results. With 2 decimal points, results would be given to the nearest penny.
5. Calculate Kelly button calculates true Kelly stakes.
6. The textarea shows the Kelly weights as a set of multiple parlays. The given weights may be edited compare the Kelly expectations with those of another staking plan.
7. Expected Profit shows expected dollar or percentage profit from making the bet one time.
8. Expected Growth shows the theoretical expected dollar or percentage bankroll growth from making the bet one time.
9. Expected Bankroll shows the expected bankroll value after placing the bets the number of times show in the "Consecutive Series" box above. Most of time you'll do worse than this, but a small percentage of the time you'll do much better.
10. Median Bankroll shows the bankroll you'd be most likely to see after placing the bets the number of times show in the "Consecutive Series" box above (assuming it's sufficiently large). It also represents the outcome such that you're just as likely to underperfom as you'd be to outperform.

Example:
Playing full Kelly, Chuck has a bankroll of \$10,000.00. During NFL season, he typically makes 5 bets each of the 17 regular season NFL weeks at -107, on which you identify an edge of 5% on each, you'd set Simult. Events to 5, Consecutive Series to 17, enter -107 in the five US text boxes, and 5% in the five Edge/Probability text boxes.

Click the "Calculate Kelly" button.

Optimal Kelly bets are \$432.47 on each of the 5 singles, \$24.69 on each of the 10 2-team parlays that can be made on from the 5 singles, \$1.41 on each of the 10 3-team parlays, \$0.08 on the 5 4-team parlays, and nothing on the one 5-team parlay.

Chuck's expected profit the first week is \$135.73 and his expected bankroll growth is \$67.85.

After 17 weeks, Chuck's expectation bankroll is \$12,575.82. His most likely bankroll is \$11,218.23.

However, because Chuck only has limited time, he's not going to bother with the 16 larger parlays (3-teams or more), but is worried how much it's going to hurt him. So clicking in each of 3-team, 4-team and 5-team parlays he changes the weights to \$0 for each one. He click "Calculate Expectations" and sees by limiting himself only to single bets and 2-team parlays, he reduces his expected end-of-season bankroll by \$34.58 to \$12,541.24, and his most-likely end-of-season bankroll by \$0.25 to \$11,217.98.

Chuck decides he can live with it and sticks with the singles and 2-team parlays only.

4. Hi, I'm trying to understand how the calculator works for mutually exclusive events but can't work it out from the post linked (nor from the java, don't really know it unfortunately...).

Would be really grateful of an explanation!

5. Originally Posted by jokerjoe
Hi, I'm trying to understand how the calculator works for mutually exclusive events but can't work it out from the post linked (nor from the java, don't really know it unfortunately...).

Would be really grateful of an explanation!
This is the relevant function in the JavaScript:

Code:
```function calcMutExKelly(a_dEdge, a_dOdds, dKellyMult)  {
var lSingles;
var a_sParlayNames;
var a_dRealKellyStakes;

if (dKellyMult == undefined || dKellyMult <= 0 || isNaN(dKellyMult)) dKellyMult = 1;
dKellyMult = parseFloat(dKellyMult);

if (!isArray(a_dEdge) || !isArray(a_dOdds) ) {
var err = "calcMutExKelly: Odds and Edge arguments must both be arrays";
return err;
}
lSingles = a_dOdds.length;
if(lSingles != a_dEdge.length) {
var err = "calcMutExKelly: Edge (size=" + a_dEdge.length + ") and odds (size=" + lSingles + ") arrays are different sizes";
return err;
}
a_dRealKellyStakes = new Array(Math.pow(2,lSingles)-1);
a_sParlayNames = new Array(Math.pow(2,lSingles)-1);
var oSortedByEdge = new Array(lSingles-1);
var dTotProb = 0;
for (var i=0; i<=lSingles-1;i++) {
var mydProb = edge2prob(a_dEdge[i],  a_dOdds[i]);
dTotProb += mydProb ;
oSortedByEdge[i] = { n: i, dEdge: a_dEdge[i], dOdds: a_dOdds[i], dProb: mydProb};
}
if(dTotProb > 1 + 1e-6) {
var err = "calcMutExKelly: Sum of probabilities of mutually exclusive outcomes (" + dTotProb + ") may not be > 1";
return err;
}
var fnSortByEdge = function(a,b) { return(b.dEdge - a.dEdge); } ;
oSortedByEdge.sort(fnSortByEdge);
var dMinResult = 1, dOverround = 0, dSumProb = 0, dSumOddsRecip = 0;

for (var i = 0; i<=lSingles-1; i++) {
dSumProb += oSortedByEdge[i].dProb;
if ( dSumProb > 1 ) dSumProb = 1; // due to rounding error probability may erroneously be slightly > 1
dOverround += 1 / oSortedByEdge[i].dOdds;
var dProposedMinResult = (1-dSumProb) / (1-dOverround );
if (dProposedMinResult > 0 && dProposedMinResult < dMinResult) {
dMinResult = dProposedMinResult ;
}
}
for (var i = 0; i<=lSingles-1; i++) {
if (dOverround < 1 && dSumProb >= 1 - 1e-7 ) {
a_dRealKellyStakes[Math.pow(2,oSortedByEdge[i].n)] = oSortedByEdge[i].dProb;
} else {
a_dRealKellyStakes[Math.pow(2,oSortedByEdge[i].n)] = Math.max(0, oSortedByEdge[i].dProb - dMinResult / oSortedByEdge[i].dOdds);
}
a_sParlayNames[Math.pow(2,oSortedByEdge[i].n)] = ''+(1+oSortedByEdge[i].n);
}
g_arrStakes = a_dRealKellyStakes;
return {arrNames: a_sParlayNames, arrStakes: a_dRealKellyStakes};
}```
With the long variable names it's pretty straightforward to the point of almost being pseudo code.

I highlighted in red the two loops that do the real "work".

What part exactly are you stuck on?

6. Thanks, was just looking at the html but still can't work out how to get kelly.js, just comes up as gibberish. I must be missing a trick somewhere...

Trying to convert the code into VBA, couple of things I don't get, perhaps I need to see the rest. What's the purpose of fnSortByEdge and how is it sorted? And why are dRealKellyStakes and sParlayNames dimensioned 2^n when it the loops only run to n?

Thanks again.

7. They're dimensioned by 2n because the same structure used for mutually exclusive outcome bets is also used for independent outcome bets. For the latter, there are 2n - 1 possible bets when we include parlays. This is obviously irrelevant for mutually exclusive outcome bets, but it still needs to fit in to the existing programmatic structure.

In other words, it's a pure programming issued that can be completely ignored when considering the problem algorithmically.

Anyway, here's a plain-English description of the algorithm to calculate Kelly stakes on mutually exclusive events.

1. Sort all bets by edge, from highest to lowest.
2. Calculate the fair implied probability for each bet. This is just the reciprocal of the decimal odds.
3. Starting with the highest edge bet, calculate a running total of the the implied probability and the actual probability. The running total for each bet includes the sum of the implied and actual probabilities for that bet and every bet with a higher edge.
4. If the sum of all the implied probabilities is less than 1 (i.e., a true arb exists), then for each bet the stake will be the actual probability. If this is the case, we can stop here.
5. If the sum of all the implied probabilities is greater than 1, then for each bet calculate the quotient (1 – the sum of actual probabilities) / (1- sum of implied probabilities).
6. Find the smallest value of this quotient that’s greater than zero. If no quotient is greater than zero then no bets will be made.
7. Then for each bet the stake will be the actual probability minus the minimum quotient from 6) above multiplied by the fair implied probability.

Consider the following bets on 4 mutually exclusive outcomes:
• actual prob=50%, odds=1.99
• actual prob=25%, odds=4
• actual prob=15%, odds=6.5
• actual prob=10%, odds=10.5

Sorting by edge yields:
1. p=10%, odds=10.5, edge=5%, implied prob=9.5238%
2. p=25%, odds=4, edge=0%, implied prob=25.0000%
3. p=50%, odds=1.99, edge=-0.5%, implied prob=50.2513%
4. p=15%, odds=6.5, edge=-2.5%, implied prob=15.3846%

The running implied probabilities totals are:
1. 9.5238%
2. 34.5238%
3. 84.7751%
4. 100.1597%

Because the sum of implied probabilities is 100.1597% > 100%, no true arb exists and we proceed.

The running actual probabilities totals are:
1. 10%
2. 35%
3. 85%
4. 100%

The quotients are then:
1. 0.994736842
2. 0.992727273
3. 0.985225933
4. 0

The minimum quotient greater than 0 is 0.985225933.

Hence the stakes for each bet are:
1. stake=max(10% - 0.985225933 * 9.5238%,0) ≈ 0.6169%
2. stake=max(25% - 0.985225933 * 25.000%,0) ≈ 0.3694%
3. stake=max(50% - 0.985225933 * 50.2513%,0) ≈ 0.4912%
4. stake=max(15% - 0.985225933 * 15.3846%,0) = 0%

Consider the following bets on 4 mutually exclusive outcomes:
• actual prob=50%, odds=2
• actual prob=25%, odds=4
• actual prob=15%, odds=6.5
• actual prob=10%, odds=10.5

Sorting by edge yields:
1. p=10%, odds=10.5, edge=5%, implied prob=9.5238%
2. p=25%, odds=4, edge=0%, implied prob=25.0000%
3. p=50%, odds=2, edge=0%, implied prob=50.0000%
4. p=15%, odds=6.5, edge=-2.5%, implied prob=15.3848%

The running implied probabilities totals are:
1. 9.5238%
2. 34.5238%
3. 84.5238%
4. 99.9084%

Because the sum of implied probabilities is 99.908% < 100% a true arb exists and the optimal stakes are just equal to the actual outcome probabilities.

1. stake = 10%
2. stake = 25%
3. stake = 50%
4. stake = 15%

8. Thanks a lot Ganchrow, just sorting out a few issues but looks like it's working!

9. Hi, Is this example code available? When I click the "Show Code" buttons it does not work.

10. Originally Posted by handikapa
Hi, Is this example code available? When I click the "Show Code" buttons it does not work.

If you view the source of this page you can get at it (not formatted properly):

function calcMutExKelly(a_dEdge, a_dOdds, dKellyMult) **
var lSingles;
var a_sParlayNames;
var a_dRealKellyStakes;

if (dKellyMult == undefined || dKellyMult <= 0 || isNaN(dKellyMult)) dKellyMult = 1;
dKellyMult = parseFloat(dKellyMult);

if (!isArray(a_dEdge) || !isArray(a_dOdds) ) **
var err = "calcMutExKelly: Odds and Edge arguments must both be arrays";
return err;
**
lSingles = a_dOdds.length;
if(lSingles != a_dEdge.length) **
var err = "calcMutExKelly: Edge (size=" + a_dEdge.length + ") and odds (size=" + lSingles + ") arrays are different sizes";
return err;
**
a_dRealKellyStakes = new Array(Math.pow(2,lSingles)-1);
a_sParlayNames = new Array(Math.pow(2,lSingles)-1);
var oSortedByEdge = new Array(lSingles-1);
var dTotProb = 0;
for (var i=0; i<=lSingles-1;i++) **
var mydProb = edge2prob(a_dEdge[i], a_dOdds[i]);
dTotProb += mydProb ;
oSortedByEdge[i] = ** n: i, dEdge: a_dEdge[i], dOdds: a_dOdds[i], dProb: mydProb**;
**
if(dTotProb > 1 + 1e-6) **
var err = "calcMutExKelly: Sum of probabilities of mutually exclusive outcomes (" + dTotProb + ") may not be > 1";
return err;
**
var fnSortByEdge = function(a,b) ** return(b.dEdge - a.dEdge); ** ;
oSortedByEdge.sort(fnSortByEdge);
var dMinResult = 1, dOverround = 0, dSumProb = 0, dSumOddsRecip = 0;

<font color="Red"> for (var i = 0; i<=lSingles-1; i++) **
dSumProb += oSortedByEdge[i].dProb;
if ( dSumProb > 1 ) dSumProb = 1; // due to rounding error probability may erroneously be slightly > 1
dOverround += 1 / oSortedByEdge[i].dOdds;
var dProposedMinResult = (1-dSumProb) / (1-dOverround );
if (dProposedMinResult > 0 &amp;&amp; dProposedMinResult < dMinResult) **
dMinResult = dProposedMinResult ;
**
**
for (var i = 0; i<=lSingles-1; i++) **
if (dOverround < 1 &amp;&amp; dSumProb >= 1 - 1e-7 ) **
a_dRealKellyStakes[Math.pow(2,oSortedByEdge[i].n)] = oSortedByEdge[i].dProb;
** else **
a_dRealKellyStakes[Math.pow(2,oSortedByEdge[i].n)] = Math.max(0, oSortedByEdge[i].dProb - dMinResult / oSortedByEdge[i].dOdds);
**
a_sParlayNames[Math.pow(2,oSortedByEdge[i].n)] = ''+(1+oSortedByEdge[i].n);
**</font>
g_arrStakes = a_dRealKellyStakes;
return {arrNames: a_sParlayNames, arrStakes: a_dRealKellyStakes**;

11. The javascript source for the Kelly Calculator is located here:

12. Thanks very much to both of you for the quick reply.

13. Originally Posted by handikapa
Hi, Is this example code available? When I click the "Show Code" buttons it does not work.

The calcMutExKelly() function posted by durito is only for mutually exclusive events (i.e., multi-way contests).

The calcKelly() function in the .js to which MonkeyF0cker linked handles (sloppily) simultaneous independent events.

A much better algorithm (described here) for independent event Kelly staking is implemented in the following C snippet:
Code:
```// Author: ganchrow@heritagesports.com

#define ODDS_ARE_DECIMAL 0
#define ODDS_ARE_US 1
#define ODDS_ARE_MIXED 2

double KellyVector(
double *dProbs,		// array of independent event probabilities (input not changed)
double *dOdds,		// array of independent event odds (input not changed)
double *dKellyOutVector,// array of Kelly stakes (output)
long lEvents,		// number of events (input not changed)
double dKellyMult,	// Kelly multiplier > 0 (input not changed)
long lOddsType		/* odds type (input not changed)
0 ==> decimal odds
1 ==> US odds
2 ==> US odds if < 0 or ≥100
*/
){
long i, j;
long lParlaySize, lParlayNum, lThisAND, lOutcomes, lBetIdx, lResIdx;

lOutcomes = 1<<lEvents;

long *lCombins = (long*)malloc( (lEvents+1) * sizeof(long));
long *lBetsPerSize = (long*)malloc( (lEvents+1) * sizeof(long));
long *lMapV = (long*)malloc( lOutcomes * sizeof(long));
double *dSingKellyV = (double*)malloc(lEvents * sizeof(double));

if(lCombins==NULL || lBetsPerSize==NULL || lMapV==NULL || dSingKellyV==NULL)
// memory allocation error
goto END;

for(i=0; i<lEvents; i++) {
if(i) {
lBetsPerSize[i] = (lEvents-i+1)/i * (lBetsPerSize[i-1] || 1);
lCombins[i] = lCombins[i-1] + lBetsPerSize[i];
} else {
lCombins[0] = lBetsPerSize[0] = 0;
}

lMapV[i] = 1<<(lEvents - i - 1);

if (dOdds[i] < 0) {
dOdds[i] = g_US2DEC(dOdds[i]);
} else {
switch(lOddsType) {
case ODDS_ARE_DECIMAL :
break;
case ODDS_ARE_US :
dOdds[i] = g_US2DEC(dOdds[i]);
break;
case ODDS_ARE_MIXED :
default :
if(dOdds[i] >= 100)
dOdds[i] = g_US2DEC(dOdds[i]);
}
}
if(dOdds[i] == NULL) goto END;
dSingKellyV[i] = SBKelly(dProbs[i], dOdds[i], dKellyMult);
}

lMapV[lOutcomes-1] = 0;
lBetsPerSize[lEvents] = 1;
lCombins[lEvents] = lOutcomes-1;
for(i=1; i<lOutcomes; i++) {
lParlaySize = lParlayNum = 0;
for(j=0; j<lEvents; j++) {
lThisAND = i & lMapV[j];
lParlayNum += lThisAND;
lParlaySize += lThisAND ? 1 : 0;
}

lResIdx = lCombins[lParlaySize-1] + lBetsPerSize[lParlaySize] - 1;
lMapV[lResIdx] = lParlayNum;
lBetsPerSize[lParlaySize]--;
dKellyOutVector[lResIdx] = 1;
}

for(lResIdx=0;lResIdx<lOutcomes-1;lResIdx++) {
for(i=0; i<lEvents; i++) {
lBetIdx = 1<<(lEvents - i - 1);
if(lMapV[lResIdx] & lBetIdx)
dKellyOutVector[lResIdx] *= dSingKellyV[i];
else
dKellyOutVector[lResIdx] *= (1 - dSingKellyV[i]);
}
}

END:
// clean up
free(lCombins);
free(lBetsPerSize);
free(lMapV);
free(dSingKellyV);

return dKellyOutVector;
}

inline double SBKelly(double dWinProb, double dDecOdds, double dKellyMult){
double dFracOdds, dFairOdds, dOddsRatio;
if(dDecOdds <= 1 || dWinProb > 1 || dWinProb < 0 || dKellyMult <= 0)
return NULL;
else if(dDecOdds * dWinProb <= 1)	// non-positive edge ==> no bet
return 0;

dFracOdds = dDecOdds - 1;
dFairOdds = 1/dWinProb - 1;
dOddsRatio = pow(dFairOdds/dFracOdds, dKellyMult);

return (1 - dOddsRatio) / (1 + dFracOdds * dOddsRatio);
}

inline double g_US2DEC(double dUSOdds){
if(dUSOdds<0)
return 1-100/dUSOdds;
else if(dUSOdds>0)
return 1+dUSOdds/100;
else
return 1;
}```

14. Hey Ganch,

You mind if I post a C# port of your function?

15. Originally Posted by MonkeyF0cker
You mind if I post a C# port of your function?
Not in the slightest.

16. Cool. Good to see you around, man.

Hope all is well.

17. Here is the code (a C# port of Ganch's function above). Doesn't format very well on here but oh well...

Code:
```	public const ushort ODDS_ARE_DECIMAL = 0;
public const ushort ODDS_ARE_US = 1;
public const ushort ODDS_ARE_MIXED = 2;

private List<double> KellyVector(List<double> dProbs, List<double> dOdds, int lEvents, double dKellyMult, ushort lOddsType)
{
int lParlaySize, lParlayNum, lThisAND, lOutcomes, lBetIdx, lResIdx;
List<double> dKellyOutVector = new List<double>();
lOutcomes = 1 << lEvents;

int[] lCombins = new int[lEvents + 1];
int[] lBetsPerSize = new int[lEvents + 1];
int[] lMapV = new int[lOutcomes];
double[] dSingKellyV = new double[lEvents];

for(int i = 0; i < lEvents; i++)
{
if (i > 0)
{
lBetsPerSize[i] = combin(lEvents, i);
lCombins[i] = lCombins[i-1] + lBetsPerSize[i];
}
else
{
lCombins[0] = 0;
lBetsPerSize[0] = 0;
}
lMapV[i] = 1 << (lEvents - i - 1);
if (dOdds[i] < 0)
{
dOdds[i] = g_US2DEC(dOdds[i]);
}
else
{
switch(lOddsType)
{
case ODDS_ARE_DECIMAL :
break;
case ODDS_ARE_US :
dOdds[i] = g_US2DEC(dOdds[i]);
break;
default :
if(dOdds[i] >= 100)
dOdds[i] = g_US2DEC(dOdds[i]);
break;
}
}
dSingKellyV[i] = SBKelly(dProbs[i], dOdds[i], dKellyMult);
}

lMapV[lOutcomes-1] = 0;
lBetsPerSize[lEvents] = 1;
lCombins[lEvents] = lOutcomes-1;
for(int i = 1; i < lOutcomes; i++)
{
lParlaySize = lParlayNum = 0;
for(int j = 0; j < lEvents; j++)
{
lThisAND = i & lMapV[j];
lParlayNum += lThisAND;
lParlaySize += lThisAND > 0 ? 1 : 0;
}
lResIdx = lCombins[lParlaySize-1] + lBetsPerSize[lParlaySize] - 1;
lMapV[lResIdx] = lParlayNum;
lBetsPerSize[lParlaySize]--;
}

for(lResIdx = 0; lResIdx < lOutcomes - 1; lResIdx++)
{
for(int i = 0; i < lEvents; i++)
{
lBetIdx = 1 << (lEvents - i - 1);
long lMapAnd = lMapV[lResIdx] & lBetIdx;
if( lMapAnd > 0)
{
dKellyOutVector[lResIdx] *= dSingKellyV[i];
}
else
{
dKellyOutVector[lResIdx] *= (1 - dSingKellyV[i]);
}
}
}
return dKellyOutVector;
}

private double SBKelly(double dWinProb, double dDecOdds, double dKellyMult)
{
double dFracOdds, dFairOdds, dOddsRatio;
if(dDecOdds <= 1 || dWinProb > 1 || dWinProb < 0 || dKellyMult <= 0)
return 0;
else if(dDecOdds * dWinProb <= 1)	// non-positive edge ==> no bet
return 0;

dFracOdds = dDecOdds - 1;
dFairOdds = 1/dWinProb - 1;
dOddsRatio = Math.Pow(dFairOdds/dFracOdds, dKellyMult);

return (1 - dOddsRatio) / (1 + dFracOdds * dOddsRatio);
}

private double g_US2DEC(double dUSOdds)
{
if(dUSOdds<0)
return 1 - 100 / dUSOdds;
else if(dUSOdds>0)
return 1 + dUSOdds / 100;
else
return 1;
}

private int combin(int n, int k)
{
if (k > n)
{
return 0;
}
long a, b;
a = 1;
b = 1;
int v = n - k;
for (int i = v + 1; i < n + 1; i++)
{
a *= i;
}
b = factorial(k);
a = a / b;
return (int)a;
}

private long factorial(int n)
{
int i;
long a = 1;
if (n > 1)
{
for (i = 2; i < n + 1; i++)
a *= i;
}
return a;
}```

18. Originally Posted by MonkeyF0cker
Here is the code (a C# port of Ganch's function above).
Well ported.

Looking over your code I realized my snippet omitted the definition of the combinatorial function (which you helpfully included).

It dawned on me, however, that the function calls are actually unnecessary (and wasteful) as the combinatorial progression can be evaluated recursively by:

combin(n,r) = 1 for r = 0
combin(n,r) = combin(n,r-1) * (n-r+1)/r for r > 0

Modification to C code above shown in red.
Points Awarded:
 Chipp gave Ganchrow 2 SBR Point(s) for this post. subs gave Ganchrow 2 SBR Point(s) for this post.

19. Originally Posted by Ganchrow

1. Sort all bets by edge, from highest to lowest.

[/Extra]
Great to see you back and am a huge fan of your work. however, you just eliminated 99%+ of the board with that one. additionally, and i fully accept that i am a loser and keep bookies and books in business, but i humble suggest that Kelly is flawed as it is built on the assumption that one can accurately gauge his edge.

keep up the solid work Ganch, asset to the board. I still refer back to your thread defending Parlays, loved it.

20. Originally Posted by Ganchrow
Well ported.

Looking over your code I realized my snippet omitted the definition of the combinatorial function (which you helpfully included).

It dawned on me, however, that the function calls are actually unnecessary (and wasteful) as the combinatorial progression can be evaluated recursively by:

combin(n,r) = 1 for r = 0
combin(n,r) = combin(n,r-1) * (n-r+1)/r for r > 0

Modification to C code above shown in red.
Good call. Here's the newly ported C# code...

Code:
```        public const ushort ODDS_ARE_DECIMAL = 0;
public const ushort ODDS_ARE_US = 1;
public const ushort ODDS_ARE_MIXED = 2;

private List<double> KellyVector(List<double> dProbs, List<double> dOdds, int lEvents, double dKellyMult, ushort lOddsType)
{
int lParlaySize, lParlayNum, lThisAND, lOutcomes, lBetIdx, lResIdx;
List<double> dKellyOutVector = new List<double>();
lOutcomes = 1 << lEvents;

int[] lCombins = new int[lEvents + 1];
int[] lBetsPerSize = new int[lEvents + 1];
int[] lMapV = new int[lOutcomes];
double[] dSingKellyV = new double[lEvents];

for(int i = 0; i < lEvents; i++)
{
if (i == 0)
{
lCombins[0] = 0;
lBetsPerSize[0] = 0;
}
else if (i == 1)
{
lBetsPerSize[i] = (lEvents - i + 1) / i;
lCombins[i] = lCombins[i - 1] + lBetsPerSize[i];
}
else
{
lBetsPerSize[i] = (lEvents - i + 1) / i * lBetsPerSize[i - 1];
lCombins[i] = lCombins[i - 1] + lBetsPerSize[i];
}
lMapV[i] = 1 << (lEvents - i - 1);
if (dOdds[i] < 0)
{
dOdds[i] = g_US2DEC(dOdds[i]);
}
else
{
switch(lOddsType)
{
case ODDS_ARE_DECIMAL :
break;
case ODDS_ARE_US :
dOdds[i] = g_US2DEC(dOdds[i]);
break;
default :
if(dOdds[i] >= 100)
dOdds[i] = g_US2DEC(dOdds[i]);
break;
}
}
dSingKellyV[i] = SBKelly(dProbs[i], dOdds[i], dKellyMult);
}

lMapV[lOutcomes-1] = 0;
lBetsPerSize[lEvents] = 1;
lCombins[lEvents] = lOutcomes-1;

for(int i = 1; i < lOutcomes; i++)
{
lParlaySize = lParlayNum = 0;
for(int j = 0; j < lEvents; j++)
{
lThisAND = i & lMapV[j];
lParlayNum += lThisAND;
lParlaySize += lThisAND > 0 ? 1 : 0;
}
lResIdx = lCombins[lParlaySize-1] + lBetsPerSize[lParlaySize] - 1;
lMapV[lResIdx] = lParlayNum;
lBetsPerSize[lParlaySize]--;
}

for(lResIdx = 0; lResIdx < lOutcomes - 1; lResIdx++)
{
for(int i = 0; i < lEvents; i++)
{
lBetIdx = 1 << (lEvents - i - 1);
long lMapAnd = lMapV[lResIdx] & lBetIdx;
if( lMapAnd > 0)
{
dKellyOutVector[lResIdx] *= dSingKellyV[i];
}
else
{
dKellyOutVector[lResIdx] *= (1 - dSingKellyV[i]);
}
}
}
return dKellyOutVector;
}

private double SBKelly(double dWinProb, double dDecOdds, double dKellyMult)
{
double dFracOdds, dFairOdds, dOddsRatio;
if(dDecOdds <= 1 || dWinProb > 1 || dWinProb < 0 || dKellyMult <= 0)
return 0;
else if(dDecOdds * dWinProb <= 1)	// non-positive edge ==> no bet
return 0;

dFracOdds = dDecOdds - 1;
dFairOdds = 1/dWinProb - 1;
dOddsRatio = Math.Pow(dFairOdds/dFracOdds, dKellyMult);

return (1 - dOddsRatio) / (1 + dFracOdds * dOddsRatio);
}

private double g_US2DEC(double dUSOdds)
{
if(dUSOdds<0)
return 1 - 100 / dUSOdds;
else if(dUSOdds>0)
return 1 + dUSOdds / 100;
else
return 1;
}```
Points Awarded:
 Chipp gave MonkeyF0cker 2 SBR Point(s) for this post. subs gave MonkeyF0cker 2 SBR Point(s) for this post.

21. Thanks Ganchrow and MonkeyF0cker for the additional code.

I am trying to add Kelly to a program I am doing for horse racing. Would you have a .net example for mutually exclusing results. I am using VB.net but any .net would be great.

22. c# is part of .net .. also, why wouldn't you do it yourself to any other language? Seems straightforward enough with the code examples above.

23. Here's a C# port of the mutually exclusive Kelly staking function. You should be able to port it over to VB pretty easily. There are a couple of classes at the end - the SortableEdge class just makes it easier to sort the edges and the MutualExKellyStakes class combines the string and double (Bet Number and Stakes) variables so both can be returned by the function.

I removed the 2^n parlay ordering so you don't have to sift through null events.

Code:
```        private MutualExKellyStakes calcMutExKelly(List<double> a_dEdge, List<double> a_dOdds, double dKellyMult)
{
if (a_dEdge.Count != a_dOdds.Count)
{
MessageBox.Show("Edges and Odds mismatch: The arrays must be the same length.");
return null;
}
if (dKellyMult <= 0)
{
dKellyMult = 1;
}

int lSingles = a_dOdds.Count;
string[] a_sParlayNames = new string[lSingles];
double[] a_dRealKellyStakes = new double[lSingles];
List<SortableEdge> oSortedByEdge = new List<SortableEdge>();
MutualExKellyStakes mutStakes = new MutualExKellyStakes();
double dTotProb = 0;

for (int i = 0; i < lSingles; i++)
{
double mydProb = edge2prob(a_dEdge[i],  a_dOdds[i]);
dTotProb += mydProb ;
SortableEdge sEdge = new SortableEdge();
sEdge.Id = i;
sEdge.Edge = a_dEdge[i];
sEdge.Odds = a_dOdds[i];
sEdge.Probability = mydProb;
}
if (dTotProb > 1 + 1e-6)
{
MessageBox.Show("Sum of probabilities of mutually exclusive outcomes (" + dTotProb + ") may not be > 1");
return null;
}
oSortedByEdge.Sort();

double dMinResult = 1, dOverround = 0, dSumProb = 0;
for (int i = 0; i < lSingles; i++)
{
dSumProb += oSortedByEdge[i].Probability;
if ( dSumProb > 1 )
{
dSumProb = 1;
}
dOverround += 1 / oSortedByEdge[i].Odds;
var dProposedMinResult = (1-dSumProb) / (1-dOverround );
if (dProposedMinResult > 0 && dProposedMinResult < dMinResult)
{
dMinResult = dProposedMinResult;
}
}
for (int i = 0; i < lSingles; i++)
{
if (dOverround < 1 && dSumProb >= 1 - 1e-7 )
{
a_dRealKellyStakes[i] = oSortedByEdge[i].Probability;
}
else
{
a_dRealKellyStakes[i] = Math.Max(0, oSortedByEdge[i].Probability - dMinResult / oSortedByEdge[i].Odds);
}
a_sParlayNames[i] = "" + (1 + oSortedByEdge[i].Id);
}

mutStakes.Names = a_sParlayNames;
mutStakes.KellyStakes = a_dRealKellyStakes;
return mutStakes;
}

private double edge2prob(double edge, double odds)
{
return (1 + edge) / odds;
}

public class MutualExKellyStakes
{
public string[] Names;
public double[] KellyStakes;
}

public class SortableEdge : IComparable
{
public int Id;
public double Edge, Odds, Probability;

public SortableEdge()
{

}

public int CompareTo(object obj)
{
if (obj == null) return 1;
SortableEdge a = obj as SortableEdge;
if (a != null)
return -(this.Edge.CompareTo(a.Edge));
else
throw new ArgumentException("Object is not a SortableEdge class member.");

}

}```
Points Awarded:
 handikapa gave MonkeyF0cker 2 SBR Point(s) for this post.

24. That was written in .NET4. So, if you're porting to an earlier version of .NET, you may have to change the IComparable routine.

25. Thanks very much, you have saved me a lot of work and frustration. I tried to give you some additional points but was not able to (sorry).

26. Ganchrow I came across your thread and see that your well adverse in excel. Do you happen to have a -1 RL calculator or know where I can find the formula to produce such calculation as such? Greatly appreciated!

http://sportsobjective.com/wordpress/?page_id=3167

27. Originally Posted by LoneStar
Ganchrow I came across your thread and see that your well adverse in excel. Do you happen to have a -1 RL calculator or know where I can find the formula to produce such calculation as such? Greatly appreciated!

http://sportsobjective.com/wordpress/?page_id=3167
....

28. Originally Posted by Ganchrow
Example:
Playing full Kelly, Chuck has a bankroll of \$10,000.00. During NFL season, he typically makes 5 bets each of the 17 regular season NFL weeks at -107, on which you identify an edge of 5% on each, you'd set Simult. Events to 5, Consecutive Series to 17, enter -107 in the five US text boxes, and 5% in the five Edge/Probability text boxes.

Click the "Calculate Kelly" button.

Optimal Kelly bets are \$432.47 on each of the 5 singles, \$24.69 on each of the 10 2-team parlays that can be made on from the 5 singles, \$1.41 on each of the 10 3-team parlays, \$0.08 on the 5 4-team parlays, and nothing on the one 5-team parlay.

Chuck's expected profit the first week is \$135.73 and his expected bankroll growth is \$67.85.

After 17 weeks, Chuck's expectation bankroll is \$12,575.82. His most likely bankroll is \$11,218.23.

However, because Chuck only has limited time, he's not going to bother with the 16 larger parlays (3-teams or more), but is worried how much it's going to hurt him. So clicking in each of 3-team, 4-team and 5-team parlays he changes the weights to \$0 for each one. He click "Calculate Expectations" and sees by limiting himself only to single bets and 2-team parlays, he reduces his expected end-of-season bankroll by \$34.58 to \$12,541.24, and his most-likely end-of-season bankroll by \$0.25 to \$11,217.98.

Chuck decides he can live with it and sticks with the singles and 2-team parlays only.

What if Chuck is betting those games at reduced lines and the -107 is only available on single games?

Any advice on how to calculate how much Chuck should bet if the odds for parlays are actually -112 per game?

29. Another way to look at it.....How do I bet the following 3 game correctly using kelly correctly while including parlays.

Marlins -102 (55%)
Mets +102 (55%)
Rangers -114 (60%)

Knowing that the odds for parlay will be.

Marlins -106
Mets -102
Rangers -117

30. ^

bump

31. To avoid having to manually go through and make 2^n - 1 possible wagers, I created a little piece of code in Octave that navigates through a grid of parlay combinations and outputs the parlays and corresponding stakes to a text file, which you could then use with a script to wager with. Don't know many books that will take 1023 wagers on 10 simutaneous events, perhaps if you spread the wagers across multiple books such actions won't seem overly conspicuous

In octave you can use "dec2bin" to output a sequence of numbers to their corresponding binary form, which has the added convenience of also being every possible combination of the indexes for those numbers. After creating a grid from all these combinations, then you can start to loop through and populate an array:

stakes is a vector of single game stakes calculated basically by product((1-stakesk:n))/(1-stakesk ) * stakesk

Code:
```A=()
for i = 2:length(grid)
f = find(grid(i,:)==1);
g = find(grid(i,:)==0);
if length(f') == 1
A(i) = ([f, stakes(f)]);
else
A(i) = ([f, prod(stakes(f))*prod(1-stakes(g))]);
end
end

save parlay_combs A```
The variable 'A' should be encapsulated by braces rather than parenthesis, but the braces automatically got replaced by '**' so I just put parenthesis.

Now you have a file named 'parlay_combs' that you can call with a betting script

32. Originally Posted by MonkeyF0cker
Good call. Here's the newly ported C# code...
Sorry for waking up this very old thread, but I want to save future programmers some pain. The second C# port has a bug. The first port works fine

To reproduce, test four odds with 2.0 and 52%. Expected output is 3.5% for all odds, but 0.14% is returned for the fourth odds.

(I didn't locate the exact bug, but one reason is that mapV[resIdx] are getting invalid numbers).Anyway, thanks to Ganchrow and MonkeyF0cker. Your code helped me tremendously!

33. Kelly is extremely overrated.

34. Hello everyone,

I remember have reading somewhere that Ganchrow shared an excel format of his simultaneous event Kelly calculator. Do someone still have it or know where it can be found ?

Any informations would be appreciated.