/** This is a library that helps parse and generate odds statements. There is a lot of semi-standardized terminology and relationship between probability and odds but they are not the same. See: https://en.wikipedia.org/wiki/Odds * "X in Y" means that the probability is p = X / Y. * "X to Y in favor" means that the probability is p = X / (X + Y). * "X to Y against" means that the probability is p = Y / (X + Y). * "pays X to Y" means that the bet is a fair bet if the probability is p = Y / (X + Y). * "pays X for Y" means that the bet is a fair bet if the probability is p = Y / X. * "pays +X" or "moneyline +X" means that the bet is fair if the probability is p = 100 / (X + 100). * "pays -X" or "moneyline -X" means that the bet is fair if the probability is p = X / (X + 100). "Moneyline" odds have a somewhat simple interpretation for bettors: When moneyline odds are positive, like "+200", the figure indicates the net winnings for a $100 wager (this is done for an outcome that is considered less probable to happen than not). The team with the positive moneyline odds is considered the "underdog" and less likely to win. When moneyline odds are negative, like "-200" the figure indicates how much money must be wagered to for a net winning of $100 (this is done for an outcome that is considered more probable to happen than not). The team with the negative moneyline odds is considered more likely to win. TODO: Verify that probability is in [0, 1] TODO: Check for weird negation sign in pays/moneyline TODO: Summarize functions TODO: Explain moneyline TODO: Move test calls to test file */ class Odds { /** This parses an odds statement in any of the formats given above. */ class parseToProbability[str] := { str = trim[str] // "X in Y" -> p = X / Y if [x, y] = str =~ %r/^(.*?)\s+in\s+(.*)$/i { x = asRational[x] y = asRational[y] if x != undef AND y != undef { // println["$x in $y"] return x/y } } // "X to Y in favor" -> p = X / (X+Y) if [x, y] = str =~ %r/^(.*?)\s+to\s+(.*?)\s+in\s+favor$/i { x = asRational[x] y = asRational[y] if x != undef AND y != undef { // println["$x to $y in favor"] return x/(x+y) } } // "X to Y against" -> p = Y / (X+Y) if [x, y] = str =~ %r/^(.*?)\s+to\s+(.*?)\s+against$/i { x = asRational[x] y = asRational[y] if x != undef AND y != undef { // println["$x to $y against"] return y/(x+y) } } // "pays X to Y" -> p = Y / (X + Y) if [x, y] = str =~ %r/^pays\s+(.*?)\s+to\s+(.*?)$/i { x = asRational[x] y = asRational[y] if x != undef AND y != undef { // println["pays $x to $y"] return y/(x+y) } } // "pays X for Y" -> p = Y / X // This case seems weird and wrong because if you do something like // "pays 5 for 50" then p = 10 which is outside the range [0,1] and // is thus not a valid probability. if [x, y] = str =~ %r/^pays\s+(.*?)\s+for\s+(.*?)$/i { x = asRational[x] y = asRational[y] if x != undef AND y != undef { // println["pays $x for $y"] return y/x } } // "pays +X" // or // "moneyline +X" // -> p = 100 / (x + 100) // This is equivalent to American / Moneyline bets for positive numbers if [x, y] = str =~ %r/^(?:moneyline|pays)\s+(\+.*?)$/i { x = asRational[x] if x != undef { // println["pays +$x"] return 100 / (x + 100) } } // "pays -X" // or // "moneyline" -X // -> p = X / (X + 100) (X is without negative sign!) // This is equivalent to American / Moneyline bets for negative numbers if [x, y] = str =~ %r/^(?:moneyline|pays)\s+(\-.*?)$/i { x = asRational[x] if x != undef { // println["pays $x"] // Negative sign is emitted return (-x) / ((-x) + 100) // Note negation of negative number } } return undef } /** Returns a string in the format "X in Y" given a probability. p = x / y so fixing Y at 1 gives x = p y */ class XinY[p, ugly=false] := { // It's easier to calculate "x to 1" than like "3 to 2" so set y=1 y = 1 x = p y if ugly // You wanted the "3 to 2" behavior though. Okay. [x,y] = numeratorDenominator[x] return inputForm[x] + " in " + inputForm[y] } /** Returns a string in the format "X to Y in favor" given a probability. p = X / (X + Y) so fixing Y at 1 gives x = p y / (1-p) */ class XtoYinFavor[p, ugly=false] := { // It's easier to calculate "x to 1" than like "3 to 2" so set y=1 y = 1 x = p y / (1-p) if ugly // You wanted the "3 to 2" behavior though. Okay. [x,y] = numeratorDenominator[x] return inputForm[x] + " to " + inputForm[y] + " in favor" } /** Returns a string in the format "X to Y against" given a probability. p = Y / (X + Y) so fixing Y at 1 gives x = y/p - y */ class XtoYAgainst[p, ugly=false] := { // It's easier to calculate "x to 1" than like "3 to 2" so set y=1 y = 1 x = y/p - y if ugly // You wanted the "3 to 2" behavior though. Okay. [x,y] = numeratorDenominator[x] return inputForm[x] + " to " + inputForm[y] + " against" } /** Returns a string in the format "pays X to Y" given a probability. p = Y / (X + Y) so fixing Y at 1 gives x = y/p - y */ class paysXtoY[p, ugly=false] := { // It's easier to calculate "x to 1" than like "3 to 2" so set y=1 y = 1 x = y/p - y if ugly // You wanted the "3 to 2" behavior though. Okay. [x,y] = numeratorDenominator[x] return "pays " + inputForm[x] + " to " + inputForm[y] } /** Returns a string in the format "pays X for Y" given a probability. p = y / x so fixing Y at 1 gives x = y/p */ class paysXforY[p, ugly=false] := { // It's easier to calculate "x to 1" than like "3 for 2" so set y=1 y = 1 x = y/p if ugly // You wanted the "3 for 2" behavior though. Okay. [x,y] = numeratorDenominator[x] return "pays " + inputForm[x] + " for " + inputForm[y] } /** Returns a string in the format "pays +-X" given a probability. The sign is decided by the probability: If the probability is less than 1/2, the sign is positive. If the probability is greater than 1/2, the sign is negative. Equal odds (p = 1/2) should result in +/- 100 (they are equivalent) "pays +X" means that the bet is fair if the probability is p = 100 / (X + 100) "pays -X" means that the bet is fair if the probability is p = X / (X + 100) THINK ABOUT: Add a flag to round/ceil/floor rational numbers to an integer? */ class pays[p, word="pays"] := { if p <= 1/2 return paysPlus[p, word] else return paysMinus[p, word] } /** Returns a string in the format "Moneylines +-X" given a probability. This is the same as "pays +-X" so see the comments in pays[p]. */ class moneyline[p] := { return pays[p, "Moneyline"] } /** Returns a string in the format "pays +X" given a probability. "pays +X" means that the bet is fair if the probability is p = 100 / (X + 100) so x = 100/p - 100 This should probably only be called if p <= 1/2 and should be called from the "pays" function. You can also choose another word than "pays" by setting the second argument to a string. This is, for example, used by the "moneyline" function. THINK ABOUT: Add a flag to round/ceil/floor rational numbers to an integer? */ class paysPlus[p, word="pays"] := { x = 100/p - 100 return "$word +" + inputForm[x] } /** Returns a string in the format "pays -X" given a probability. "pays -X" means that the bet is fair if the probability is p = x / (x + 100) so x = 100 p / (1-p) This should probably only be called if p >= 1/2 and should be called from the "pays" function. THINK ABOUT: Add a flag to round/ceil/floor rational numbers to an integer? */ class paysMinus[p, word="pays"] := { x = 100 p / (1-p) return "$word -" + inputForm[x] } /** Parses a numeric dimensionless string and returns it as a rational number if it is a numeric value, or returns undef otherwise. */ class asRational[str] := { val = eval[str] if isUnit[val] and (val conforms dimensionless) return toRational[val] else return undef } } /* println[Odds.parseToProbability["1 in 10"]] println[Odds.parseToProbability["2 to 20 in favor"]] println[Odds.parseToProbability["3 to 30 against"]] println[Odds.parseToProbability["pays 4 to 40"]] println[Odds.parseToProbability["pays 5 for 50"]] println[Odds.parseToProbability["pays +600"]] println[Odds.parseToProbability["pays -700"]] */