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

Tom Pedron
17 min readJan 13, 2021


NOTE: See for an end-of-season follow-up to this post.

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”.

The first step to joining a league is to draft a team. When you draft a team, you try to pick the best available players of course. The best players that come to mind usually are the ones scoring the most goals and assists like Connor McDavid or Sidney Crosby. But when you add more and more statistic categories to your pools that are all weighted the same (as in, winning the goals category is equally as important as winning the penalty minutes category), theoretically a goon who gets in lots of fights could be as valuable as a player potting 40 goals a season. It really does make you wonder, how do I determine who the best player available is?

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.

Fantasy sports are pretty common in pop culture, there was even a 7-season sitcom called “The League” centered around a small, close-knit Fantasy Football league.

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

There are many pool types and they can be as simple as “Points Only” pools where you accumulate team points for the points your drafted players score as the season progresses (such as 2 points per goal, 1 point for an assist). They can also be quite complex, such as “Head to Head Category” pools where each fantasy team plays one other team each week and fights to win points based on winning various statistic categories (from the obvious goals, assists and goalie wins to the more unintuitive hits, penalty minutes, blocks, powerplay points, and faceoff wins).

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).

In the end, I needed to find a way to compare each statistical category equally against each other, not to determine how many penalty minutes equates to 1 goal or how many faceoff-wins is equal to 1 assist, but to help me generate a single “Fantasy Score” that ranks players by “completeness” across all of the statistical categories. If I could strategically draft players that may not be getting the most goals or assists but were still scoring at a high enough rate while also putting in the work in the other categories, I could build a team that allowed my team to compete every week in every category.

This algorithm had to:

  • 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).

This homework assignment started off as a small exercise in napkin math and very quickly became a week-long obsession that consumed every spare moment I had in order to have something useful ready by draft day, culminating in something I’m really proud and excited about: a complete, dynamic, and configurable web-app!

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.

You can view your team as you build it on the “My Fantasy Team” page which presents an average of the strength of your team for each category in the bottom row. While you may draft according to the Fantasy Score rankings, you may still have categories that you are stronger or weaker in to keep in mind for your next pick.

See section 6 to see the Fantasy Teams I ended up drafting.

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).

What I ended up with was a Fantasy Score player rankings table that looked like this:

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.

The above makes sense to me! While JT Miller may not exactly be a household name, last season he was a great fantasy player in so many ways! And there is no Connor McDavid in sight, remember this is “fantasy” hockey and McDavid does not block shots, take penalty minutes or make hits at an elite level in comparison to his peers (he does however have the creepiest house in the NHL but that's not relevant to the rankings)!

I shared the link to the app with friends, around the internet in various fantasy hockey spaces, and on Twitter with invite codes and got 10 beta users to sign up. It was so rewarding to get feedback from real people (see section 7 for more) saying they liked my project and that they actually used it to draft their teams! It influenced me to build out an admin panel for me to keep track of application usage and metrics like “most actioned-upon skaters” and “number of logins per user”.

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.

I bought a .ca domain name (the .com domain is taken but does not seem to have anything going on) and configured DNS using AWS Route 53, with a Cloudfront distribution securely pointing to the Heroku deployment of the app. At the time of writing this, I’m still waiting for DNS configurations to propagate so that HTTPS is working (if the website seems to be down, you can go to instead).

The app is written using Ruby on Rails on both the backend and the frontend. The frontend is also leveraging HTML, CSS/ Bootstrap, and JS as well as ERB (embedded Ruby) because frontend development is not my strong suit and I don’t know how to build something quickly using something more modern like React. The website at this time does look like something from 10 years ago but that's fine! The HTML presented is server-side rendered, it's fast, lightweight, and easy for me to tinker with!

For analysis of the NHL statistics, I am leveraging a statistics gem called “Descriptive Statistics”. It works really well with ActiveRecord models (part of the Ruby on Rails framework) and made a lot of my Fantasy Score algorithm fairly easy to implement.

For the sign-up and login functionality, I am leveraging a gem called Devise. It’s pretty commonly used in Rails apps and out of the box it does so much! I modified it to require an Invite Code string in the sign-up form which was pretty simple.

The result of combining all these technologies is a cloud-hosted website I could easily deploy code to (calling it a production deployment felt funny to me since this a pet project) that stored all the data I needed and allowed users to run my Fantasy Score algorithm dynamically and on-demand. I explain all this as pretty trivial (and for the most part it was) but having all this free or cheap technology available these days is so powerful! 10 years ago, putting this project together with similar results would have been impossible in the 1-week timeframe I had to work with.

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.

I was positioned to draft the 3rd overall pick and decided that my strategy was to pick a top goalie right away. Remember that there are only 31 NHL teams, ~31 starting goalies, and ~62 full-time goalie positions). With this many teams in the fantasy league and 2 active Goalie spots on the fantasy roster (meaning you probably want to draft 3 goalies in total), securing top-notch goaltending was critical since there won’t be much talent available on the waiver list throughout the season and goaltending stats make up approximately a third of the statistic categories. With that in mind, I decided that goaltending would make-or-break you in this league and unfortunately, FantasyHockeyHelper would not be able to help with that since it's only for skaters (for now). So I did what any die-hard Habs fan would do and drafted Carey Price at 3rd overall (and was laughed at for it in the in-draft chat room but I stand by it). I have very high hopes for the Habs this season and after seeing what a well-rested Price was able to accomplish in the “Toronto Bubble” during the playoffs last summer, I had to pick him up. It also makes it easy to name my team “The Price is Right” which is a nice bonus.

I continued my “Goalie-First” strategy by securing 2 more starting goalies in Phillip Grubauer (COL) drafted in round 3 and Mackenzie Blackwood (NJD) in round 11, giving me ~10% of all starting goalies in the league.

From there, I began to heavily rely on the FantasyHockeyHelper rankings and put together a team that doesn't exactly scream “Stanley Cup Contender” in real hockey but in fantasy hockey, it has solid numbers across all statistic categories that make up a strong contender in this fantasy league.

Below is the team I ended up with (note that the goalies I drafted in rounds 1, 3, and 11 are not shown):

Note the stats presented in this view are just from last season and not prorated.

Some notes I want to make about the players above:

  • 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.

I also learned a lot about my tool from the draft:

  • 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.

FantasyHockeyHelper and I were much more prepared for this draft:

  • 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).

Since there are so few goalies available (7 teams * 2 spots = 14), we decided that each team will draft the starter of 1 team, thereby claiming all goalies on that team (the last round of the draft was reserved for picking up the backup goalie to put in a bench spot). I was positioned to draft 2nd overall and just like in Draft 1, I went with Carey Price. With so few goalies available and no ability to pick up any goaltending help off the waiver wire other than whatever goalies are on the Ottawa Senators this season (sorry Matt Murray), it seemed obvious to go “Goalie-First” again with the expectation that FantasyHockerHelper would help me make some smart picks in later rounds, making up for lack of star skaters I would end up drafting.

I followed my rankings much more strictly in this pool (with one exception explained later) partly due to the Fantasy Score algorithm improvements I implemented after Draft 1 but also because there was much less obvious talent to choose from in the later rounds when our pool was limited to only a quarter of the league. The draft became quite difficult around round 8 as strong players became less and less obvious. I noticed my tool became really helpful in determining which players even played on Canadian teams!

Below is the team I ended up with (note that the goalies I drafted in rounds 1 and 14 (Jake Allen) are not shown):

Forgive my css skills…

Some notes I want to make about the players above:

  • 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):

“So I like your tool, and will definitely become a customer once you get up and running assuming your projections go well, however you’re missing a lot more categories as options. For my league you’re missing points, +/-, and goalie categories. Your tool is one that we need though. Good luck!”

“I think it's pretty cool. Will be nice to add goalies to the mix. Also to consider the strength of the schedule, I know that is more of an NFL concept, but I think it impacts the NHL as well to a certain extent, more so this year.”

“So far its pretty cool, I like how it works. It would also be cool to put priority on certain stats (depending on if you want to punt a stat in order to maximize your other stats). But it is definitely super useful.”

“It would be cool to be able to export into CSV.”

For me, I have some things I found too:

  • 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?

While the draft season is over for now since the NHL regular season begins tonight, I have big plans to continue building out the project with the goal of having a much more mature, polished product ready to go for the start of the next NHL season (or maybe even the 2021 NHL playoff season though playoff fantasy hockey can be a bit of a different game with a different strategy around picking players from teams you think will go far). I have algorithm tweaks I want to include, website features to implement, other pool types to consider, more statistical data to capture, and many UX and visual improvements to make.

I also want to write a follow-up blog once the NHL regular season and fantasy season is over in early May when I will hopefully have a couple of fantasy league championships to my name and some prize money in my bank account too. I want to see if my “Goalie-First” strategy was right (please don’t let me down Jesus Price). I want to see if JT Miller’s last season was not a fluke. I want to see if my algorithm was actually helpful in determining success in category pools. I want to know what can be improved!

If you are interested in taking a look at, here are 10 single-use invite codes (first-come-first-serve):

  • MEDIUM10

Thanks for reading and please get in touch if you have any thoughts or questions!