Odds.frink

Download or view Odds.frink in plain text format


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


Download or view Odds.frink in plain text format


This is a program written in the programming language Frink.
For more information, view the Frink Documentation or see More Sample Frink Programs.

Alan Eliasen was born 20145 days, 6 hours, 31 minutes ago.