Skip to content

Instantly share code, notes, and snippets.

@mds2
Created November 29, 2014 01:36
Show Gist options
  • Save mds2/621144c6428d6ec031a0 to your computer and use it in GitHub Desktop.
Save mds2/621144c6428d6ec031a0 to your computer and use it in GitHub Desktop.
adventures in information theory and optimal gambling
;;; Every time I read about information theory, gambling, and the optimal
;;; way to bet on horse races, I am sufficiently surprised by what the math is
;;; telling me that I develop a desire to run experiments to double-check it
;;; for myself.
;;;
;;; See : http://en.wikipedia.org/wiki/Gambling_and_information_theory
;;; See : http://en.wikipedia.org/wiki/Kelly_criterion
;;;
;;; Note that my notion of "odds" might not exactly match up with the
;;; conventional notion. Mine are easier to compute with.
(defn unitize [vector-in]
(let [sum-vector (* 1.0 (reduce + vector-in))]
(apply vector (map (fn [el] (/ el sum-vector)) vector-in))))
(defn cdf-from-pdf [probs]
(apply vector (reverse (reduce
(fn [accum next]
(cons (+ next (first accum)) accum))
'(0) (unitize probs)))))
(defn select-from-cdf
([cdf]
(select-from-cdf cdf (rand 1.0)))
([cdf prob]
(first (filter (fn [i] (and (<= (nth cdf i) prob)
(> (nth cdf (+ i 1)) prob)))
(range (- (count cdf) 1))))))
(defn make-sane-odds
{:doc "Makes horse-racing style odds given a quantity bet on each outcome"
:todo "May not correspond to everone else's notion of what \"odds\" means"}
[bet-amounts-vector]
(let [total (* 1.0 (reduce + bet-amounts-vector))]
(apply vector (map (fn [x] (/ total x)) bet-amounts-vector))))
(defn make-experiment
{:doc "Makes a horse-racing experiment"}
[odds probs]
(let [unit-probs (unitize probs)
cdf-probs (cdf-from-pdf probs)
run-once (fn [bet-vector]
(let [bets (unitize bet-vector)]
(let [winner (select-from-cdf cdf-probs)]
(* (nth bets winner) (nth odds winner)))))
run-n-times (fn [bet-vector n]
(reduce * (map (fn [i] (run-once bet-vector)) (range n))))]
{:odds odds,
:probs unit-probs,
:run-once run-once,
:run-n-times run-n-times,
:sample-trials (fn [bet-vector rounds-per-trial num-trials]
(map (fn [i] (Math/log (run-n-times bet-vector
rounds-per-trial)))
(range num-trials)))
}
))
@mds2
Copy link
Author

mds2 commented Nov 29, 2014

How to use it

Start the repl and load the file

REPL started; server listening on localhost port 19134
user=> (load-file "horse-race.clj")
#'user/make-experiment

make-experiment will let you build a horse-racing experiment given a set of odds and a vector containing the probabilities that each horse will win.

user=> (def even-odds-experiment (make-experiment (make-sane-odds [1 1]) [1 1]))
#'user/even-odds-experiment
user=> (def skewed-odds-experiment (make-experiment (make-sane-odds [5 1]) [1 1]))
#'user/skewed-odds-experiment

Here we make one with skewed odds and one with odds that match the probability of winning.

user=> (:odds even-odds-experiment)
[2.0 2.0]
user=> (:odds skewed-odds-experiment)
[1.2 6.0]

You can run individual experiments with this. Here we bet in each race (repeatedly) with a clearly flawed vector of bets (0.75 of our money on one horse and 0.25 of our money on the other)

user=> ((:run-once even-odds-experiment) [0.75 0.25])
1.5
user=> ((:run-once even-odds-experiment) [0.75 0.25])
0.5
user=> ((:run-once even-odds-experiment) [0.75 0.25])
1.5
user=> ((:run-once skewed-odds-experiment) [0.75 0.25])
0.8999999999999999

Or you can run a bunch of races back to back, each time re-investing your total cash according to the same bet vector (here we use a uniform betting vector -- which the code will even out for us)

user=> ((:run-n-times even-odds-experiment) [1 1] 10)
1.0
user=> ((:run-n-times even-odds-experiment) [1 1] 10)
1.0
user=> ((:run-n-times skewed-odds-experiment) [1 1] 10)
3.779136
user=> ((:run-n-times skewed-odds-experiment) [1 1] 10)
94.47839999999997
user=> ((:run-n-times skewed-odds-experiment) [1 1] 10)
3.7791359999999994

Note that the even-odds-experiment always gives us exactly our money back if we split it 50/50 among both horses.

We can also run a bunch of repeated series of races and print out the log of our final money at the end of each run. Recall that (Math/log 1.0) gives you 0.0.

user=> ((:sample-trials skewed-odds-experiment) [1 1] 10 10)
(-0.2799425003576061 -1.8893804127917064 -0.279942500357606 2.9389333245105944 4.548371236944695 7.767247061812896 1.3294954120764946 1.3294954120764946 -0.279942500357606 4.548371236944695)
user=> ((:sample-trials skewed-odds-experiment) [1 1] 100 10)
(47.09315028188105 35.82708489484235 37.436522807276454 40.65539863214465 26.17045742023775 35.82708489484235 40.65539863214465 14.904392033199045 27.77989533267185 19.732705770501347)
user=> ((:sample-trials even-odds-experiment) [1 1] 100 10)
(0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0)

Playing with this lets one experience that the optimal bet is to bet to the probability of winning, while the odds only affect how much you win (or whether you should even place a bet in the first place).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment