Zoltar is my computer program to predict the results of NFL football games. The 2025 season is coming up. Zoltar is a complicated system, and I make changes to the system every year. Therefore, I have to spend several hours before each NFL season to review Zoltar so that when the inevitable bugs arise, I can fix them as quicky as possible.
Zoltar computes the predicted margin of victory for a game. For example, “In week 4, Zoltar predicts the (home team) Eagles will beat the (visitor team) Jaguars by 6 points.” Zoltar does not compute exact score predictions — just a margin of victory.
Ratings
At a high level, before each week’s games, each of the 32 NFL teams has a rating, like 1950 or 2106. The rating is computed based on the results of the previous weeks. The ratings are use to compute a margin of victory (or loss).
Ratings are stored into an int[i][j] allRatings matrix where i is the team ID (Bears = 0, . . Vikings = 31) and j is a week number (0, 1 . . 18). The stored value is the rating of the team i AFTER the week j. So allRatings[31][4] = 1968 means the rating of the Vikings (team 31) after week 4 (meaning before the week 5 game) is 1968.
The special case when j = week = 0 is the rating of a team after week 0, which means before week 1, which is a team’s initial rating. For example, allRatings[29][0] = 1978 means the initial rating of the Texans (team 29) is 1978.
Ratings are initialized using teams won-loss records from the previous year. If a team finished 8-8 (not really possible unless a game is canceled because teams play 17 games) then team initial rating is set to 2000. The higher percentage of wins in the previous year, the higher the initial rating.
The equation is
base = 1800 rating = base + (winPct * 2 * (2000 - base))
For example, if a team won 0.75 percent of their games in the previous season, the initial rating is
rating = 1800 + (0.75 * 2 * (2000 - 1800))
= 1800 + (0.75 * 400)
= 1800 + 300
= 2100
Each team’s rating is updated after a game is played based on the rating before the game, how much the team won or lost by. If a better team wins, the rating only increases a little. If a worse team wins, the rating increases a lot. There are special clauses for an underdog visiting team victory, a blowout win, and so on.
Predictions
Predicted margins of victory are stored into an int[i][j] allPredictions matrix, where i is the team ID (0 to 31) and j is the week number. For example, allPredictions[31][1] = +5 means that the Vikings (team 31), in week #1 (first game of the season) are predicted to beat their opponent by 5 points. If the Vikings were playing the Rams (team #23) in week #1, then allPredictions[23][1] would be -5.
Note that the sign of Zoltar’s predicted margin of victory is the opposite of that used for Las Vegas point spread data. For example, if Vegas thinks that the Vikings will beat the Rams by 3.5 points, then the Vegas point spread line would read, “Vikings -3.5 Rams”.
Because there are no games in week 0, allPredictions[i][0] for all i are arbitrarily set to 0.
Predicted margins of victory are based on each team’s rating before the game is played, and whether or not a team is the home team or visiting team (or if the game is being played at a neutral site, such as an international game in London). The home team factor is big in NFL football games.
The GameResult Class
The key data structure is a class / structure:
public class GameResult
{
public int week; // 1-based week in which game was played
public int visitorID; // 0 to 31
public int homeID;
public int visitorScore; // actual score
public int homeScore;
public double visitorVegasMarginVictory; // Vegas prediction
public double homeVegasMarginVictory;
public bool neutralSite;
public double vegasOverUnder; // future use
public int visitorRating; // before game is played
public int homeRating;
public int visitorPredictedMV; // Zoltar's prediction
public int homePredictedMV;
public override string ToString() { . . }
public string ToFancyString(string[] teamNamesShort) { . . }
}
It’s easy for me to make mistakes here. For example, the homeScore is a score AFTER a game has been played, but the homeRating is the rating BEFORE a game gas been played.
Data Files
Zoltar uses three data files: game schedule data, game result data, Vegas point spread data.
The schedule data comes from http://www.pro-football-reference.com and is available before the start of the season, but the data is updated often during the season due to canceled games, TV accommodations, etc.) The format looks like:
// ScheduleData2024.txt
//
1,Thu,September 5,Baltimore Ravens,,@,Kansas City Chiefs,,8:20 PM
1,Fri,September 6,Green Bay Packers,,@,Philadelphia Eagles,,8:15 PM
1,Sun,September 8,Pittsburgh Steelers,,@,Atlanta Falcons,,1:00 PM
. . .
The fields are week #, day, date, home team, null, “@”, visiting team, null, time. Neutral-site games have one team arbitrarily assigned as the home team.
The game result data also comes from http://www.pro-football-reference.com. It is published weekly. It looks like:
// ResultData2024.txt
1,Thu,2024-09-05,8:20PM,Kansas City Chiefs,,Baltimore Ravens,boxscore,27,20,353,1,452,1
1,Fri,2024-09-06,8:15PM,Philadelphia Eagles,,Green Bay Packers,boxscore,34,29,410,3,414,1
1,Sun,2024-09-08,1:00PM,Pittsburgh Steelers,@,Atlanta Falcons,boxscore,18,10,270,0,226,3
. . .
The fields are week #, day, date, time, winning team, null or “@”, losing team, “boxscore”, winning team score, losing team score, winning team yards gained, winning team turnovers, losing team yards, losing team turnovers. If there is an “@” character, the visiting team won, if there is a null, the home team won (as expected).
The Vegas point spread data looks like:
// PointSpreadData2024.txt # 1 09/5 8:20 ET At chiefs -2.5 ravens 09/6 8:15 ET At eagles -1.5 packers 09/8 1:00 ET texans -1.5 At colts 09/8 1:00 ET At falcons -2.5 steelers . . . # 2 09/12 8:15 ET bills -0.5 At dolphins 09/15 1:00 ET chargers -3.5 At panthers 09/15 1:00 ET At cowboys -6.5 saints . . .
The space-delimited fields are data, time, “ET”, “At” or not, favored team, Vegas point spread (where negative means favored by Vegas), “At” or not, underdog team. The format is wacky and was established years ago, and I’ve been too busy to rationalize. The point spread data comes from various sources, such as:
http://www.bovada.lv/sports/football/nfl
http://www.vegasinsider.com/nfl/odds/las-vegas
http://www.oddsshark.com/nfl/odds?sl=true
http://www.sportsdataio.com.
I go to one of these sites and then run a utility program that prompts me for input, which I manually supply.
Getting point spread data is a significant pain point for Zoltar. Point spread data can change significantly from Tuesday morning (after a MNF game is played) and Saturday (just before most games are played on Sunday).
Advice
Zoltar gives betting advice. For most games, Zoltar’s predicted margin of victory are quite close to the Vegas point spread. Zoltar uses an advice threshold. For example, if the threshold is 2.0 points, Zoltar will recommend a bet only if Zoltar and Vegas disagree by more than 2.0 points.
Suppose the Vegas point spread is “Vikings -5.5 Jaguars”, meaning Vegas believes that the Vikings will beat the Jaguars by 5.5 points. If the threshold is set to 2.0, and Zoltar predicts that the Vikings will win by 9 points, then Zoltar recommends a bet on the Vegas favorite Vikings. But if Zoltar predicts that the Vikings will win by 7 points, Zoltar does not recommend a bet because the prediction is too close to the Vegas line.
Setting the threshold value is critical.
Sorcerer
I have another system, called Sorcerer, that predicts the total number of points scored by both teams in a game. This is useful for over-under betting. Sorcerer is a very rough work in progress.
Briefly, I look at all the game data from the previous season, and create a standalone neural network regression model (using a system that I haven’t given a name to) that predicts total points scored by both teams, based on the teams’ Zoltar ratings before the game was played, and which teams are playing.
So, Sorcerer needs Zoltar’s team ratings, which encapsulate a lot of information about a team. The Sorcerer program is essentially a wrapper that imports the neural network predictions, reads in Vegas over-under data, and uses a lot of Zoltar’s plumbing to measure how good the total points scored predictions are.

Left: My system is named after the Zoltar fortune teller machine, which was named after a Zoltar machine from the 1988 movie “Big”, which was named after a 1960s arcade fortune teller machine named Zoltan.
Center: An image of a robot gypsy fortune teller from OpenAI DALL-E.
Right: An image of a mysterious fortune teller device from Google Gemini.

.NET Test Automation Recipes
Software Testing
SciPy Programming Succinctly
Keras Succinctly
R Programming
Visual Studio Live
Microsoft MLADS Conference
DevIntersection Conference
Machine Learning Week
Ai4 Conference
G2E Conference
iSC West Conference
You must be logged in to post a comment.