ELO Ratings in CoffeeScript

ELO is a method of assigning a relative proficiency rating to a team or individual after recording the result of set of matches. This link explains the process in the context of a soccer league.

Below is code to update the ELO rating of a team based on a game result and a short sample random tournament.

# the expected value of a result for team is
# calculated by taking the difference in elo ratings
# applying a home field advantage
expected = (home_rating, away_rating) ->  
  dr = (home_rating + 100) - away_rating
  1 / (Math.pow(10, -1 * (dr / 400)) + 1)

# the weight of the match is increased for lop-sided wins and losses
modified_weight = (weight, gf, ga) ->  
  gd = Math.abs gf - ga
  return weight * 1.5 if gd is 2
  return weight * 1.75 if gd is 3
  return weight * 1.75 + (gd - 3) / 8 if gd > 3
  weight

# actual result defined as 1 for a win, 0 for a loss and 0.5 for a draw
result = (gf, ga) ->  
  return 1 if gf > ga
  return 0 if gf < ga
  0.5

# return a two element list containing the modified elo
# for the home and away team
elo = (home_rating, away_rating, weight, gf, ga) ->  
  weight = modified_weight weight, gf, ga
  home_exp = expected home_rating, away_rating
  away_exp = 1 - home_exp
  home_delta = parseInt weight * (result(gf, ga) - home_exp)
  away_delta = parseInt weight * (result(ga, gf) - away_exp)
  [home_rating + home_delta, away_rating + away_delta]

Tournament

# game result description
description = (us, them) ->  
  return "Won #{us}-#{them}" if us > them
  return "Lost #{us}-#{them}" if us < them
  "Draw #{us}-#{them}"

# construct a team
team = (idx) ->  
  initial_elo = parseInt(Math.random() * 200 + 1400)
  t =
    name: "team #{idx}"
    elo: initial_elo
    history: [initial_elo]
    games: []

# build a set of teams
teams = (team(idx) for idx in [0..2])

console.log teams

# run a tournament
for team1, i1 in teams  
  for team2, i2 in teams
    if i1 isnt i2
      t1s = parseInt(Math.random() * 4)
      t2s = parseInt(Math.random() * 4)
      [team1_elo, team2_elo] = elo team1.elo, team2.elo, 30, t1s, t2s
      team1.history.push team1_elo
      team1.games.push description t1s, t2s
      team1.elo = team1_elo
      team2.history.push team2_elo
      team2.games.push description t2s, t1s
      team2.elo = team2_elo

console.log teams  

Output

# initial random teams
[ { name: 'team 0', elo: 1478, history: [ 1478 ], games: [] },
  { name: 'team 1', elo: 1563, history: [ 1563 ], games: [] },
  { name: 'team 2', elo: 1468, history: [ 1468 ], games: [] } ]

# after the tournament
[ { name: 'team 0',
    elo: 1468,
    history: [ 1478, 1478, 1474, 1463, 1468 ],
    games: [ 'Draw 3-3', 'Draw 0-0', 'Lost 1-3', 'Draw 2-2' ] },
  { name: 'team 1',
    elo: 1532,
    history: [ 1563, 1563, 1574, 1552, 1532 ],
    games: [ 'Draw 3-3', 'Won 3-1', 'Lost 0-1', 'Lost 0-2' ] },
  { name: 'team 2',
    elo: 1509,
    history: [ 1468, 1472, 1494, 1489, 1509 ],
    games: [ 'Draw 0-0', 'Won 1-0', 'Draw 2-2', 'Won 2-0' ] } ]