/** 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" 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). */ 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" -> 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" -> 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 + 10 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"]]