/** 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"]]