mls-elo
Version:
Calculate ELO rankings for Major League Soccer
124 lines (112 loc) • 4.02 kB
JavaScript
const db = require('sqlite');
const fs = require('fs-extra');
const teams = require('../data/teams');
const results = require('../data/results');
module.exports = async (argv) => {
const {dbPath, verbose, _} = argv;
if (verbose) {
console.info(`using db in ${dbPath}`);
}
await db.open(dbPath, { Promise });
// pop the command name
_.shift();
if (!_[1]) {
console.info(`Predicting from now until ${_[0]}`);
await predictMatchesByDate(_[0], verbose);
} else {
let hometeam, awayteam;
if (_[1] === 'at') {
hometeam = _[2];
awayteam = _[0];
} else if (_[1] === 'vs') {
hometeam = _[0];
awayteam = _[2];
} else {
throw new Error(`Unrecognized conjunction ${_[1]}; please use "at" or "vs"`);
}
await predictMatchByName(hometeam, awayteam, verbose);
}
}
async function predictMatchesByDate(date, verbose) {
const results = await db.all(`
WITH currentelo AS (
SELECT *
FROM rankings
INNER JOIN (
SELECT max(date) as maxDate, rankingteamid as teamid
FROM rankings
GROUP BY teamid
) as dates
ON dates.teamid = rankings.rankingteamid
AND dates.maxDate = rankings.date
)
SELECT
awayteam.teamname as awayName,
away.elo as awayElo,
hometeam.teamname as homeName,
home.elo as homeElo,
matches.date as matchDate
FROM matches
INNER JOIN currentelo as away
ON away.rankingteamid = matches.awayteam
INNER JOIN currentelo as home
on home.rankingteamid = matches.hometeam
INNER JOIN teams AS awayteam
ON awayteam.teamid = matches.awayteam
INNER JOIN teams AS hometeam
ON hometeam.teamid = matches.hometeam
WHERE matches.date <= ${(new Date(date)).getTime()}
AND matches.date >= ${(new Date()).getTime()}
`);
results.forEach(result => predictMatch(result, verbose));
}
async function predictMatchByName(hometeam, awayteam, verbose) {
const homeTeamStanding = await db.get(`
SELECT teamname, elo
FROM rankings
INNER JOIN teams on teams.teamid = rankings.rankingteamid
WHERE date = (
SELECT max(date)
FROM rankings
WHERE rankings.rankingteamid = teams.teamid
)
AND (teams.abbreviation = "${hometeam}" OR teams.teamname = "${hometeam}")
ORDER BY elo DESC
`);
const awayTeamStanding = await db.get(`
SELECT teamname, elo
FROM rankings
INNER JOIN teams on teams.teamid = rankings.rankingteamid
WHERE date = (
SELECT max(date)
FROM rankings
WHERE rankings.rankingteamid = teams.teamid
)
AND (teams.abbreviation = "${awayteam}" OR teams.teamname = "${awayteam}")
ORDER BY elo DESC
`);
predictMatch({
homeName: homeTeamStanding.teamname,
homeElo: homeTeamStanding.elo,
awayName: awayTeamStanding.teamname,
awayElo: awayTeamStanding.elo
}, verbose);
}
function predictMatch({matchDate, homeElo, awayElo, homeName, awayName}, verbose) {
const probability = 1 / (10 ** (-1 * (homeElo + 100 - awayElo)/400) + 1)
const homeWinProbability = 1 / (Math.pow(10, (-1 * ((homeElo + 100) - awayElo)/400)) + 1);
const awayWinProbability = 1 / (Math.pow(10, (-1 * (awayElo - homeElo)/400)) + 1);
const formattedDate = (new Date(matchDate)).toLocaleDateString();
if (verbose) {
console.info(`\n${formattedDate}:
${homeName} (${Math.round(homeElo)}) ${Math.round(homeWinProbability * 100)}% to win
${awayName} (${Math.round(awayElo)}) ${Math.round(awayWinProbability * 100)}% to win`);
}
if (homeWinProbability > awayWinProbability) {
console.info(`I predict ${homeName} will win vs ${awayName} on ${formattedDate} (${Math.round(homeWinProbability * 100)}% to win).`)
} else if (homeWinProbability < awayWinProbability) {
console.info(`I predict ${awayName} will win at ${homeName} on ${formattedDate} (${Math.round(awayWinProbability * 100)}% to win).`)
} else {
console.info(`I predict ${homeName} will draw vs ${awayName} on ${formattedDate}`);
}
}