# Formula from Glickman's paper: # http://glicko.net/glicko/glicko2.pdf config = TAU: 0.5 DEFAULT_RATING: 1500 DEFAULT_DEVIATION: 350 DEFAULT_VOLATILITY: 0.06 CONVERGENCE_TOLERANCE: 0.000001 GLICKO_CONSTANT = 173.1718 PI_SQUARED = Math.PI * Math.PI ratingFromGlicko = (rating) -> (rating - 1500) / GLICKO_CONSTANT deviationFromGlicko = (deviation) -> deviation / GLICKO_CONSTANT g = (deviation) -> 1 / Math.sqrt(1 + 3 * deviation * deviation / PI_SQUARED) E = (mu, opponentMu, opponentPhi) -> 1 / (1 + Math.exp(-g(opponentPhi) * (mu - opponentMu))) _extractPlayer = (player) -> {rating, deviation, volatility} = player createPlayer = -> rating: config.DEFAULT_RATING deviation: config.DEFAULT_DEVIATION volatility: config.DEFAULT_VOLATILITY calculate = (player, matches, options = {}) -> # Step 1: Initialize tau. Players with no rating are assumed to have been # handled already using the createPlayer function. tau = options.systemConstant || config.TAU extractPlayer = (options.extractPlayer || _extractPlayer) glickoPlayer = extractPlayer(player) playerRating = ratingFromGlicko(glickoPlayer.rating) playerDeviation = deviationFromGlicko(glickoPlayer.deviation) playerVolatility = glickoPlayer.volatility # Step 2: Convert from Glicko to Glicko2 gPlayers = [] ePlayers = [] scores = [] for match in matches {opponent, score} = match glickoOpponent = extractPlayer(opponent) opponentRating = ratingFromGlicko(opponent.rating) opponentDeviation = deviationFromGlicko(opponent.deviation) gPlayers.push g(opponentDeviation) ePlayers.push E(playerRating, opponentRating, opponentDeviation) scores.push score # Step 3: Compute estimated variance (v) estimatedVariance = calculateEstimatedVariance(gPlayers, ePlayers) # Step 4: Compute estimated improvement in rating (delta) improvementSum = calculateImprovementSum(gPlayers, ePlayers, scores) # Step 5: Determine new volatility (delta prime) newVolatility = calculateNewVolatility(improvementSum, playerDeviation, estimatedVariance, playerVolatility, tau) # Step 6: Update rating deviation to new pre-rating period value periodValue = playerDeviation * playerDeviation + newVolatility * newVolatility # Step 7: Determine new rating and rating deviation newDeviation = 1 / Math.sqrt(1 / periodValue + 1 / estimatedVariance) newRating = playerRating + newDeviation * newDeviation * improvementSum # Step 8: Convert back to Glicko scale rating = GLICKO_CONSTANT * newRating + config.DEFAULT_RATING deviation = GLICKO_CONSTANT * newDeviation volatility = newVolatility # Return results return {rating, deviation, volatility} calculateImprovementSum = (gPlayers, ePlayers, scores) -> improvementSum = 0.0 for gPlayer, i in gPlayers improvementSum += gPlayer * (scores[i] - ePlayers[i]) improvementSum calculateEstimatedVariance = (gPlayers, ePlayers) -> estimatedVariance = 0.0 for gPlayer, i in gPlayers gSquared = gPlayer * gPlayer ePlayer = ePlayers[i] estimatedVariance += gSquared * ePlayer * (1.0 - ePlayer) estimatedVariance = 1.0 / estimatedVariance estimatedVariance calculateNewVolatility = (improvementSum, deviation, variance, volatility, tau) -> # Step 5.1 A = a = Math.log(volatility * volatility) improvement = variance * improvementSum deviationSquared = deviation * deviation varianceSquared = variance * variance improvementSquared = improvement * improvement tauSquared = tau * tau convergenceTolerance = config.CONVERGENCE_TOLERANCE fIter = (x) -> f(deviationSquared, improvementSquared, tauSquared, variance, a, x) # Step 5.2 if improvementSquared > varianceSquared + variance B = Math.log(improvementSquared - varianceSquared - variance) else absTau = Math.abs(tau) k = 1 k += 1 while fIter(a - k * absTau) < 0 B = a - k * absTau # Step 5.3 fA = fIter(A) fB = fIter(B) while Math.abs(B - A) > convergenceTolerance # Step 5.4a C = A + (A - B) * fA / (fB - fA) fC = fIter(C) # Step 5.4b if fC * fB < 0 then [A, fA] = [B, fB] else fA /= 2 # Step 5.4c [B, fB] = [C, fC] # Step 5.5 return Math.exp(A / 2) f = (deviationSquared, improvementSquared, tauSquared, variance, a, x) -> expX = Math.exp(x) denominator = deviationSquared + variance + expX ((expX * (improvementSquared - denominator) / (2 * denominator * denominator)) - ((x - a) / tauSquared)) getRatingEstimate = (rating, deviation) -> return 0 if deviation > 100 rds = deviation * deviation sqr = sqrt(15.905694331435 * (rds + 221781.21786254)) inner = (1500.0 - rating) * Math.PI / sqr return Math.floor(10000.0 / (1.0 + Math.pow(10.0, inner)) + 0.5) / 100.0 module.exports = { calculate calculateImprovementSum calculateNewVolatility calculateEstimatedVariance createPlayer ratingFromGlicko deviationFromGlicko getRatingEstimate config g E }