Money-Puck: Optimizing Your Fantasy Hockey Draft Using Statistics and Ruby

1. Introduction

I’ve been playing fantasy hockey for about 15 years now. That means that technically I’ve been sports-gambling since I was a child. One year I actually won my Dad’s office playoff pool (I don’t know why they let a child join to be honest) with a prize of ~$800 dollars which bought me an Xbox 360 and Rock Band (his coworkers weren’t pleased)! Illegalities aside, fantasy hockey is an incredibly fun, several-months long game that further ignites my interest in professional hockey and brings friends, teammates, and coworkers together in pursuit of a small cash prize and bragging rights that they “know hockey the best”.

2. What is Fantasy Hockey?

Taking a step back, if you've never played, fantasy hockey is like most fantasy sports. You draft a fantasy team comprised of players from across teams in the NHL that you think will score the most points. A web-app like Yahoo! Fantasy Sports or keeps track of the total points your players pick up and ranks the fantasy teams from first to worst.

Fortunately for my leagues, the draft order is generated for us by Yahoo! Fantasy Sports.

3. Hockey Homework

Doing your homework is critical in preparing for a fantasy draft. Usually, I’ll prepare by making a list of key players I want to draft, buy The Hockey News’ yearly Pool guide which has projections for the upcoming season and hope for the best. But this year I took a different approach and did more homework than I ever have before! I began to define an algorithm that would help me rank players factoring in all the statistic categories measured in the fantasy hockey pools I participate in (goals, assists, hits, penalty minutes, blocks, shots, faceoff wins, powerplay points, and shorthanded points).

  • Compare skaters to only their peers in the league (forwards only against forwards, defensemen only against defensemen) to ensure we don’t undervalue defensemen who don’t usually score the points that forwards do but are still key pieces in a fantasy team (they often do more of the shot-blocking and hitting).
  • Analyze the most recent NHL season’s player stats but also look at stats from the last few seasons to avoid filtering out players like Josh Anderson who had an off-year last season.
  • Be able to prorate stats over a full standard 82 game season to put the players on an even footing and not ignore players like Vladimir Tarasenko who was injured for most of last season (and is still recovering) but put up 10 points in 10 games beforehand and has generally been a high scoring forward for most of his career.
  • Be configurable to include a different set of categories for each fantasy league.
  • Filter out low-end and outlier players that negatively skewed the results of the high-end players that you do want to draft. When there are more and more players who do not contribute to the stat categories factored in, it makes it harder to distinguish between the better players.
  • Be scalable and dynamic so I could quickly generate Fantasy Score rankings for different sets of stat categories, positions, and settings without waiting around for possibly thousands of calculations, database reads/writes and HTTP calls to execute.
  • It had to help project the most effective, smart, and reliable skaters to draft based on history without team allegiance or emotions getting involved (for goalies, that's another story I’ll dig into later on).

4. Fantasy Hockey Helper Dot See Eh is the result of my hard work. This website performs everything I had hoped to do to support my 2 fantasy hockey drafts this winter and much more! It has the last 3 NHL seasons’ skater statistics loaded in the database and analyzed to rank the best players to draft. It also has sign-up and login functionality gated by single-use invite codes (I have provided some at the bottom of this blog for you to use), mock draft features, and individual skater view pages too.

4.1 Dynamic Fantasy Score Rankings

FantasyHockeyHelper is highly configurable so that it applies to both of my fantasy pools and other people’s pools too (each pool is unique with different stat categories being factored in). I implemented a simple form that would allow for setting all of these along with statistic preferences and more:

4.2 Mock Draft Your Team

The tool really becomes more than a glorified spreadsheet with this feature. Each player in the Fantasy Score rankings has a “Draft” (or if already drafted, an “Undraft” button) along with a “Hide” button to allow for building out your team as you draft and removing players that are no longer available from your rankings list.

4.3 Color Coded Assessment

There is also a color-coded assessment system (a pretty CSS gradient) for each individual stat column and player in the rankings table as well as the dynamically calculated Fantasy Score indicating how strongly they rank among their peers across all the categories.

The Fantasy Score is generated by an aggregation of the numbers that also define this color legend (10–0).
In this example, we are only looking at the top 15 Forwards with prorated stats from just last season for the categories presented in the table.

5. Technology

The website is hosted in Heroku. I love Heroku, I used it at a previous job and saw the benefits of offloading operations management to a 3rd party and focusing instead on product development, user satisfaction, and quick iteration. All my other web-dev companies have had an expensive DevOps team managing the application in AWS and it was always so much work. I know my way around AWS (I achieved my Cloud Practitioner and Solutions Architect certifications last year and have done a number of freelance gigs involving AWS) but it seemed like a headache I didn’t want to deal with for this project just to save a couple bucks. Heroku links to my private Github repository and auto-deploys on code merge to the master branch. I started out on the free tier Dynos for the app but decided to upgrade to the Hobby tier (only $7 a month) for the SSL support. Heroku also auto-setup a free-tier Postgres database for me that allows a maximum of 10K records to be stored. That’s more than enough for now.

6. Draft Results

As mentioned above, I participated in 2 fantasy drafts leading up to the start of the 2021 NHL regular season (Wednesday, January 13th, 2021). Below are reviews and reflections about each draft and the findings I found in relation to how I could improve FantasyHockeyHelper.

6.1 Draft 1: “Chel 21”

This fantasy league is one that I’ve been playing in for many years run by a childhood friend. There are 16 teams in this league and it's a very long draft process (usually a little over 2 hours). This draft went well despite FantasyHockeyHelper not being entirely complete (or at least not in the state it would be in for Draft 2):

  • There was no prorated stats option yet.
  • Only last season’s stats were loaded into the system.
  • The Fantasy Score algorithm needed some further tinkering and optimizations.
Note the stats presented in this view are just from last season and not prorated.
  • Max Pacioretty does it all in the key categories! A complete player not nearly appreciated enough during his time in Montreal.
  • Tony DeAngelo is an awful human but he plays a pretty okay game of fantasy hockey. Real hockey though, not so much
  • I’ve got some heavy reliance on the duo of Danault & Tatar who play on the Habs top line with Brendan Gallagher and are absolutely elite at 5-on-5 play (not something currently factored into my algorithm and not exactly a bonus since we have categories for powerplay and shorthanded points but not for even-strength).
  • Bryan Rust is ranked very high on my rankings (#5) and I was quite pleased to pick him up so late. This is a clear example of FantasyHockeyHelper helping me identify high quality and not-obvious talent.
  • There was no “Hide Player” functionality implemented then (I hadn’t even considered it yet) but during the draft I very quickly began to lose track of which players in my rankings were still available in the pool, making it harder to make picks in a timely manner.
  • Only factoring in last season’s stats caused a serious recency bias to affect my Fantasy Score rankings.

6.2 Draft 2: “Canada Cup”

This is a league I run with some friends from my hockey team, high school, and my brother. Its a small 6-team league and because of this year’s fascinating North Division (all Canadian teams in one division playing each other 9–10 times this season due to the Canada/USA border shutdown), we decided to limit drafting to the 7 Canadian teams since realistically, us Habs, Leafs, and Sens fans are likely only going to be paying attention to Canadian hockey for the next few months.

  • I had implemented the “Hide Player” functionality.
  • I rewrote the entire Fantasy Score algorithm and underlying data model over 2 days to load in the last 3 years of NHL stats and offer statistic options for considering only last season, the last 3 seasons equally, and the last 3 seasons with a recency bias towards last season (my personal favorite option).
  • I implemented a new Players filter to return just players on Canadian teams which also required writing a rake script to make updates to all players that joined or left Canadian teams over the off-season (welcome to the Habs Tyler Toffoli).
Forgive my css skills…
  • Mark Scheifele is highly underrated! He is a top 5 centerman in the North Division.
  • I never thought I would be so happy to be able to draft Tyler Myers in a fantasy pool.
  • I only went off the board from my Fantasy Score rankings list once when I drafted Jonathan Drouin in round 11 because I have faith he is going to tear it up this season with Nick Suzuki and newly acquired “PowerHorse” Josh Anderson. Before getting injured last season, he had 15 points in 19 games, got injured, and returned too soon to lack-luster results. Then after the COVID-break, he was tied for the team-lead in scoring over the Habs’ 2 round summer Playoff run. The algorithm I put together doesn't take playoff scoring into account nor does it consider the healthiness of players after returning from an injury.
  • Unfortunately for me, JT Miller is now in a 2-week quarantine due to a possible COVID exposure. I picked up Ilya “Soup” Mikheyev (TOR) off of waivers to temporarily fill that void. I have similar hopes for him that I do for Jonathan Drouin.
  • Luca Sbisa got stolen off of waivers by Nashville after the draft so I had to drop him as per league rules. I picked up Travis Hamonic (VAN) as a replacement purely because he had the highest Fantasy Score of the remaining defensemen. In FantasyHockeyHelper we trust!

7. Feedback and Findings

Below are some snippets of feedback I was sent by my beta users (edited only to add some sentence structure where needed):

  • Factoring in the age of the player would be another interesting variable to include in the Fantasy Score calculation. Forwards often hit the prime of their career in their mid-late 20s and begin to slow down in their early 30s.
  • Ranking players by Fantasy Score definitely helps you pick a player to draft in each round but you also need to factor in what round is the best to draft a player in. If you think you can pick up a very underrated player with a high Fantasy Score in a later round and draft someone more obvious in an earlier round, it's important to not leave value on the table to go for the sleeper picks that FantasyHockeyHelper may be providing. This is why in Draft 1, I waited to pick up Bryan Rust in round 9 despite being ranked at position 5 last season (prorated).
  • The Fantasy Score ranking ideally pushes you to draft players that are strong in all categories but does not (yet) dynamically compare your under-construction Fantasy Team to the remaining players to recommend players that are strong in your weak categories. This would require a large change to the algorithm though.
  • The AVG row in the “My Fantasy Team” page does not factor in which draft position each player was taken at and likely looks pretty blue or purple on the Color Assessment gradient after a long draft. Max Pacioretty is a good pick in the second round but I would argue that my pick of Ryan Getzlaf in the 13th round was a more valuable pickup in comparison to the talent still available at that time. But that doesn’t get factored in.
  • I need better data. All Forwards are marked as position = “F” when ideally I would like Centerman marked as position = “C” so I can ensure that we only factor in the Faceoff-Wins category for Centerman. Right now if that statistic is included then the Forward rankings skew towards Centermen.

8. What’s Next?

I loved working on this project more than any personal project I’ve ever worked on. It combined my love of backend development with my love of hockey and fantasy hockey! What could be better?

  • MEDIUM10



Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Tom Pedron

Tom Pedron

Staff Software Engineer @ Copper CRM