{-# 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"