bet-0.1.0.0: src/Data/Bet.hs
{-# LANGUAGE FunctionalDependencies #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE AutoDeriveTypeable #-}
{-# LANGUAGE DeriveTraversable #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE DeriveFoldable #-}
{-# LANGUAGE DeriveFunctor #-}
{-# LANGUAGE ViewPatterns #-}
{-# LANGUAGE LambdaCase #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE CPP #-}
-- | Data types and functions to work with odds and stakes.
--
module Data.Bet
(
-- * Bets
BetType(..)
, oppositeBetType
, betBool
, Bet(..)
, BetFlipped(..)
, betType
, odds
, stake
-- ** Derived lenses
-- | The values these lenses manipulate are calculated on the fly and not
-- stored directly. These may not necessarily follow lens laws with 100%
-- accuracy but only because there are inaccuracies in floating point
-- numerical values.
, liability
, winningPotential
-- ** Pattern synonyms
, pattern BetType
, pattern BetLiability
, pattern BetWinningPotential
-- * Choosing stakes
, bestTradingStake
, bestTradingStake2
)
where
import Control.Applicative
import Control.Lens
import Data.Aeson
import Data.Bifoldable
import Data.Bitraversable
import Data.Foldable
import Data.Semigroup
import Data.Semigroup.Foldable
import Data.Semigroup.Bifoldable
import Data.Traversable
import Data.Typeable
-- | A bet type. In a betting exchange, you usually can choose if you want to
-- make a back or lay bet. With traditional bookmakers, you usually only back.
data BetType = Back | Lay
deriving ( Eq, Ord, Show, Read, Typeable, Enum )
-- | An `Iso` from `BetType` to `Bool`.
betBool :: Iso' BetType Bool
betBool = iso (\case
Back -> False
Lay -> True)
(\case
False -> Back
True -> Lay)
{-# INLINEABLE betBool #-}
-- | Returns the opposite bet type.
oppositeBetType :: BetType -> BetType
oppositeBetType Back = Lay
oppositeBetType Lay = Back
-- | Describes a bet in terms of its (European) odds and the stake size.
--
-- @
-- Bet odds money = Bet BetType odds money
-- ^ ^
-- | |
-- | +--- Data type of the money to use.
-- |
-- +-- Describes the data type used as odds.
-- Some betting environments don't allow just any odds so
-- it may be useful to use this type variable to restrict
-- available odds.
-- @
--
data Bet odds money = Bet !BetType odds money
deriving ( Traversable
, Foldable
, Typeable
, Functor
, Read
, Show
, Ord
, Eq )
instance Foldable1 (Bet odds)
instance Bifoldable1 Bet
instance Bifoldable Bet where
bifoldMap f g (Bet _ o m) = f o `mappend` g m
{-# INLINEABLE bifoldMap #-}
instance Bitraversable Bet where
bitraverse f g (Bet t o m) = Bet t <$> f o <*> g m
instance (Semigroup odds, Semigroup money) => Semigroup (Bet odds money) where
b1 <> b2 = b1 & (stake %~ (<> b2^.stake)) .
(odds %~ (<> b2^.odds))
instance Bifunctor Bet where
bimap fun1 fun2 = (odds %~ fun1) . (stake %~ fun2)
{-# INLINEABLE bimap #-}
-- | A wrapper to use odds as last type argument.
newtype BetFlipped money odds = BetFlipped { getFlipped :: Bet odds money }
deriving ( Typeable
, Read
, Show
, Ord
, Eq )
instance Functor (BetFlipped money) where
fmap f (getFlipped -> Bet t o m) = BetFlipped $ Bet t (f o) m
instance Foldable (BetFlipped money) where
foldMap f (getFlipped -> Bet _ o _) = f o
foldr f r (getFlipped -> Bet _ o _) = f o r
instance Traversable (BetFlipped money) where
traverse f (getFlipped -> bet) =
BetFlipped . (\x -> bet & odds .~ x) <$> f (bet^.odds)
sequenceA (getFlipped -> Bet t o m) =
BetFlipped <$> (Bet t <$> o <*> pure m)
instance (Semigroup odds, Semigroup money)
=> Semigroup (BetFlipped money odds) where
(getFlipped -> b1) <> (getFlipped -> b2) =
BetFlipped $ b1 <> b2
instance Bifunctor BetFlipped where
bimap fun2 fun1 (getFlipped -> b) =
BetFlipped $ b & (odds %~ fun1) . (stake %~ fun2)
{-# INLINEABLE bimap #-}
instance Bifoldable BetFlipped where
bifoldMap f g (getFlipped -> Bet _ o m) = g o `mappend` f m
instance Bifoldable1 BetFlipped
instance Bitraversable BetFlipped where
bitraverse f g (getFlipped -> Bet t o m) =
BetFlipped <$> (Bet t <$> g o <*> f m)
instance Foldable1 (BetFlipped money)
odds :: Lens (Bet odds1 money) (Bet odds2 money) odds1 odds2
odds = lens (\(Bet _ o _) -> o)
(\(Bet t _ m) o -> Bet t o m)
{-# INLINEABLE odds #-}
stake :: Lens (Bet odds money1) (Bet odds money2) money1 money2
stake = lens (\(Bet _ _ m) -> m)
(\(Bet t o _) m -> Bet t o m)
{-# INLINEABLE stake #-}
betType :: Lens' (Bet odds money) BetType
betType = lens (\(Bet t _ _) -> t)
(\(Bet _ o m) t -> Bet t o m)
{-# INLINEABLE betType #-}
-- | Liability is the amount of money you stand to lose for a bet if you lose
-- it.
--
-- For back bets, liability equals stake. For lay bets, liability is stake
-- multiplied by (odds-1).
--
-- An unfortunate flaw of this function: odds and money data types need to be
-- the same.
liability :: (Fractional odds, odds ~ money) => Lens' (Bet odds money) money
liability = lens getLiability setLiability
{-# INLINEABLE liability #-}
setLiability :: (Fractional odds, odds ~ money)
=> Bet odds money -> money -> Bet odds money
setLiability bet@(BetType Back) new_m = bet & stake .~ new_m
setLiability bet@(Bet Lay o _) new_l = bet & stake .~ new_l/(o-1)
setLiability _ _ = error "setLiability: impossible"
getLiability :: (Num odds, odds ~ money) => Bet odds money -> money
getLiability (Bet Back _ m) = m
getLiability (Bet Lay o m) = m*(o-1)
-- | Winning potential tells you how much you could win if this bet pays.
--
-- This is profit value. If you back bet 5 dollars at odds 2.0 then your
-- winning potential is 5 dollars. (You will have 10 dollars if you started
-- with 5 dollars).
winningPotential :: (Fractional odds, odds ~ money)
=> Lens' (Bet odds money) money
winningPotential = lens getWinningPotential setWinningPotential
getWinningPotential :: (Num odds, odds ~ money) => Bet odds money -> money
getWinningPotential (Bet Back o m) = (o-1) * m
getWinningPotential (Bet Lay _ m) = m
setWinningPotential :: (Fractional odds, odds ~ money)
=> Bet odds money -> money -> Bet odds money
setWinningPotential bet@(BetType Back) new_w =
bet & stake .~ new_w / (bet^.odds - 1)
setWinningPotential bet new_w =
bet & stake .~ new_w
-- | Match the bet type only.
pattern BetType b <- Bet b _ _
-- | Match with liability.
pattern BetLiability b o l <- Bet b o (getLiability -> l)
-- | Match with winning potential.
pattern BetWinningPotential b o w <- Bet b o (getWinningPotential -> w)
-- | Given a bet and opposing bet odds, calculates the ideal stake size to
-- minimize potential loss.
--
-- This is useful in bet trading, which gives this function its name.
bestTradingStake :: ( Fractional odds, odds ~ money )
=> Bet odds money -> odds -> money
bestTradingStake (Bet bt o m) opposing_odds = case bt of
Back -> o*m / opposing_odds
Lay -> m*o / opposing_odds
-- | Same as `bestTradingStake` but wraps the result in a new bet.
bestTradingStake2 :: ( Fractional odds, odds ~ money )
=> Bet odds money -> odds -> Bet odds money
bestTradingStake2 bet opposing_odds =
Bet (oppositeBetType $ bet^.betType)
opposing_odds
(bestTradingStake bet opposing_odds)
instance FromJSON BetType where
parseJSON (String "BACK") = pure Back
parseJSON (String "LAY") = pure Lay
parseJSON _ = empty
instance ToJSON BetType where
toJSON Back = String "BACK"
toJSON Lay = String "LAY"