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 20217 days, 23 hours, 21 minutes ago.