1. #1
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

    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)".
    Last edited by SBR Jonelyn; 04-16-15 at 03:18 PM. Reason: link does not work

  2. #2
    jjgold
    Update your status
    jjgold's Avatar Horses Moderator
    Join Date: 07-20-05
    Posts: 351,462
    Betpoints: 806

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

    Thanks Ganch

  3. #3
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

    Quote Originally Posted by jjgold View Post
    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.
    Last edited by SBR Jonelyn; 04-16-15 at 03:18 PM. Reason: image does not exist

  4. #4
    jokerjoe
    jokerjoe's Avatar Become A Pro!
    Join Date: 05-29-08
    Posts: 10

    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. #5
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

    Quote Originally Posted by jokerjoe View Post
    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";
    		alert(err);
    		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";
    		alert(err);
    		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";
    		alert(err);
    		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. #6
    jokerjoe
    jokerjoe's Avatar Become A Pro!
    Join Date: 05-29-08
    Posts: 10

    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. #7
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

    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. #8
    jokerjoe
    jokerjoe's Avatar Become A Pro!
    Join Date: 05-29-08
    Posts: 10

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

  9. #9
    handikapa
    handikapa's Avatar Become A Pro!
    Join Date: 05-30-11
    Posts: 4
    Betpoints: 28

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

    Thanks for your help.

  10. #10
    durito
    escarabajo negro
    durito's Avatar Become A Pro!
    Join Date: 07-03-06
    Posts: 13,182

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

    Thanks for your help.
    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";
    alert(err);
    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";
    alert(err);
    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";
    alert(err);
    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. #11
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    The javascript source for the Kelly Calculator is located here:
    Last edited by SBR Jonelyn; 04-16-15 at 03:19 PM. Reason: image does not exist

  12. #12
    handikapa
    handikapa's Avatar Become A Pro!
    Join Date: 05-30-11
    Posts: 4
    Betpoints: 28

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

  13. #13
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

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

    Thanks for your help.
    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;
    }
    Last edited by Ganchrow; 05-11-12 at 08:43 PM. Reason: removed call to missing combinatorial function

  14. #14
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    Hey Ganch,

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

  15. #15
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

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

  16. #16
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    Cool. Good to see you around, man.

    Hope all is well.

  17. #17
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    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]--;
    		        dKellyOutVector.Add(1);
    	        }
    
    	        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;
            }
    Last edited by MonkeyF0cker; 05-11-12 at 03:19 AM.

  18. #18
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

    Quote Originally Posted by MonkeyF0cker View Post
    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. #19
    statnerds
    Put me in coach
    statnerds's Avatar Become A Pro!
    Join Date: 09-23-09
    Posts: 4,047
    Betpoints: 103

    Quote Originally Posted by Ganchrow View Post

    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. #20
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    Quote Originally Posted by Ganchrow View Post
    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]--;
    		dKellyOutVector.Add(1);
    	    }
                
                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. #21
    handikapa
    handikapa's Avatar Become A Pro!
    Join Date: 05-30-11
    Posts: 4
    Betpoints: 28

    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.

    Thanks in advance!

  22. #22
    Dash2in1
    Dash2in1's Avatar Become A Pro!
    Join Date: 12-31-11
    Posts: 11
    Betpoints: 306

    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. #23
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    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;
                    oSortedByEdge.Add(sEdge);
    	    }
                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.");
    
            }
    
        }
    Last edited by MonkeyF0cker; 05-18-12 at 02:34 AM.
    Points Awarded:

    handikapa gave MonkeyF0cker 2 SBR Point(s) for this post.


  24. #24
    MonkeyF0cker
    Update your status
    MonkeyF0cker's Avatar Become A Pro!
    Join Date: 06-12-07
    Posts: 12,146
    Betpoints: 1126

    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. #25
    handikapa
    handikapa's Avatar Become A Pro!
    Join Date: 05-30-11
    Posts: 4
    Betpoints: 28

    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. #26
    LoneStar
    I'm Kind of A Big Deal
    LoneStar's Avatar Become A Pro!
    Join Date: 07-12-09
    Posts: 190

    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. #27
    Ganchrow
    Nolite te bastardes carborundorum.
    Ganchrow's Avatar Become A Pro!
    Join Date: 08-28-05
    Posts: 5,013
    Betpoints: 1052

    Quote Originally Posted by LoneStar View Post
    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
    ....
    Last edited by SBR Jonelyn; 04-16-15 at 03:19 PM. Reason: link does not work

  28. #28
    Paddy
    Paddy's Avatar Become A Pro!
    Join Date: 05-31-12
    Posts: 3
    Betpoints: 30

    Quote Originally Posted by Ganchrow View Post
    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. #29
    Paddy
    Paddy's Avatar Become A Pro!
    Join Date: 05-31-12
    Posts: 3
    Betpoints: 30

    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. #30
    Paddy
    Paddy's Avatar Become A Pro!
    Join Date: 05-31-12
    Posts: 3
    Betpoints: 30

    ^

    bump

  31. #31
    uva3021
    uva3021's Avatar Become A Pro!
    Join Date: 03-01-07
    Posts: 537
    Betpoints: 30

    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
    Last edited by uva3021; 07-03-12 at 01:58 PM.

  32. #32
    Arbinator
    Arbinator's Avatar Become A Pro!
    Join Date: 01-20-11
    Posts: 2
    Betpoints: 36

    Quote Originally Posted by MonkeyF0cker View Post
    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. #33
    deeppckts
    deeppckts's Avatar Become A Pro!
    Join Date: 12-19-12
    Posts: 728
    Betpoints: 124

    Kelly is extremely overrated.

  34. #34
    Skull City
    Update your status
    Skull City's Avatar Become A Pro!
    Join Date: 08-16-20
    Posts: 8
    Betpoints: 300

    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.

Top