— 2020–2021 Season Review

Tom Pedron
13 min readMay 20, 2021


NOTE: This blog post is a follow-up to

TITLE EDIT: Note that I have renamed this to reflect that technically this season was the 2020–2021 season, even though it only started in early 2021.

I started in early January 2021 to keep me busy during Ontario’s seemingly never-ending COVID lockdown (big shout out to Doug Ford for making this so much worse than it had to be). I used the app to help draft my 2 Fantasy Hockey teams by ranking players in a way that I thought made sense. Mainly, by prioritizing players that produced in all relevant stat categories and had done so consistently over the past few seasons. The tool grew over the season to rank players by the current season too in order to support team management and waiver pickups. It’s been a fun journey building this project and I think it helped me a lot in my 2 leagues. I’d like to reflect on how it all went now that the regular season is over.

1. End of Season Fantasy Score Rankings

First, I’d like to show the final rankings from the app. Remember this is ranking for fantasy hockey and not real hockey!

The Fantasy Score is generated by an aggregation of the numbers that also define this color legend (10–0).

1.1 Forwards

Below are the top 10 forwards based on the Fantasy Score algorithm:

I want to specifically point out that while Connor McDavid obviously had an incredible year with 105 points in 56 games, he still wasn’t getting it done in all of the stat categories, allowing others with a more “complete” fantasy hockey game to pass him. In fantasy Hockey, you can crush your team in the Goals and Assists categories, but being weak on blocks, hits and PIM will still result in a loss. Still, I do think McDavid should be ranked higher though and I touch on this more later in section 4.4.

1.2 Defensemen

Below are the top 10 defensemen:

2. Continued Development of FantasyHockeyHelper

I spent a lot of time continuing to build the website post-drafts. I didn’t start immediately though, as I had put in a TON of hours in the 5 days leading up to the January 13th start of the NHL season. The app was quite different even between my first and second drafts. I was tired and also didn't know what to do next. I had some high-level ideas but no immediate quick wins which were all I had energy for up until early March. I also felt confident in my drafted rosters and thought I would be able to cruise to victory without much more work. I was wrong.

As I’m sure anyone who played Fantasy Hockey this season knows, this season was a trainwreck! Games were canceled, sometimes with little notice, and often rescheduled to after the Fantasy season ended. Full teams were shut down for weeks on end and Yahoo didn’t always make them IR-eligible. Sometimes your best players were unable to play for an undetermined period of time or were out quarantining for 2 weeks after being traded to a Canadian team. Getting your minimum goalie starts was often a challenge. You had to manage and prioritize your limited weekly Waiver pickups carefully to fill in the gaps without using them too soon in the week in case you suddenly lost a player and their important man-games. It was a challenging season but also a good time-sink while being stuck inside all day.

Due to COVID, some drafting mistakes (that I’ll talk about in more detail in section 3), and an over-investment in playing fantasy hockey this year, I eventually found that I needed more tools to get the job done.

2.1 Team Schedule Views For Maximizing Man-Games

For all the reasons mentioned about losing man-games due to COVID, I found myself struggling to pick the best player on waivers. One thing I began factoring in more than usual was man-games in a week. When trying to increase how many hits my team has, that 1 extra man-game on a Sunday afternoon to end the week could make all the difference. Finding out how many games a player plays in a week is easy of course, but having to do it 10 or 20 times every few days is a process that was screaming for some sort of automation or helper tool.

I knew I needed a way to calculate it and knew that generally, man-games are more of a team stat than a player stat. I need to know how many games does the team of the player I am interested in play this week and next week? If I pick a player that is playing lots this week and next, that might save me a transaction next week.

Implementing this was pretty easy. I inspected the API calls being made on’s schedule page, changed the start/end-date parameters to cover the full season (normally it runs for a smaller period of time), ran the HTTP call, and saved the JSON response (it was several MBs).

376093 lines of JSON, 14.1MB

I wrote a rake script to parse the data into rows in a database. I went for a denormalized form where each team has a single schedule instance with a nested JSON blob containing the data for each game (just date, home team, and away team). This made some of the code for all this kinda ugly but I did this to save money. The app is running on a Hobby-tier Postgres database on Heroku and they limit how many rows you have but not how many columns you have or how much data is stored in those columns. Since each team schedule has a small upper bound of 56 games this season, I figured it would perform fine and save me some money. It did.

From there, I made a simple page to display the number of man-games a team had last week, this week, and next week in order to help note which teams are playing a lot when looking for potential Waiver pickups. See:

One thing I did not do is keep the team schedules up to date as games were postponed and rescheduled due to COVID. This caused the game counts for last week, this week, and next week to sometimes be inaccurate. Oops! I should have made this an easily repeatable process I could run daily with a reloaded schedule. Hopefully, this won’t be an issue I need to worry about in the 2021–2022 season as the pandemic comes to an end (knock on wood).

2.2 Rank Players By Current Stats

Being able to rank players based on the current season was an obvious want even before the season started. I developed the Fantasy Score algorithm script used for drafting to be easily repeatable and basically just required an input CSV of player stats, so I roughly knew how to accomplish this task using current stats. I also knew that I needed a new page different from the one I used to draft teams (renamed to “Draft Center”, see: to view this different ranking. The “Draft Center” is for looking at the last 1–3 years of stats. This new page, “Current Season Rankings (see: was for looking at this season’s (and only this season’s) stats. Very similar pages, some similar configurations (including pro-rating options), different purposes.

The blocker here was getting data easily and repeatedly. The data source of the last 3 seasons' stats (for the “Draft Center”) was just CSVs I put together through a somewhat manual process I did begrudgingly without bothering to automate it because it was a 1-and-done process. Getting current and up-to-date stats every day required finding a reliable data source, automation of retrieving data from them and loading them into the app. My real issue was just finding this data source. I thought it would be a pain to deal with but it wasn’t too bad in the end.

I built a crawler in Ruby using Nokogiri (an HTML parser gem, see: to scrape stats from a website and save them into CSV format (the same one as the format from the CSVs I had manually pulled for the last 3 seasons). I run that crawler on my local machine, send the result CSV to the app server, then run a remote rake script to process the new dataset which updates the “Current Season Rankings” page. All this is automated on a cronjob so the website is always up to date.

I wish I had done this sooner, I didn’t implement it until a little over halfway through the season. It would have been so helpful early on in the season when the Waiver wire was full of talent that was eventually picked up and held on to by teams.

Implementing this also provided some nice bonuses to the app:

  • I no longer needed to manually update the team of players when they got traded. I could just update a player’s team based on the daily stats which had this information.
  • Rookies (players who were not in the system previously since they did not play last season) were suddenly present in the system once they played their first game.

2.3 No More Required Invite Codes

I started the website requiring an invite code. I had hoped that this would protect me from spam accounts and give off a vibe of exclusivity. I think it became more of a barrier to sign-up in the end and I got rid of this requirement eventually. The website is free for all signups, no code is needed. Go for it!

3. How Did My Pools Go?

I played in 2 pools this season, each of which was drafted using This section serves as a follow-up to my post-draft reviews from section 6 of

3.1 Chel21

I placed 9/16 in this pool, though I did had a solid second half of the season (I credit in part due to having current stats in and almost made the playoffs (only top 8 moved on).

I made critical errors in the draft that made the season an uphill battle. The biggest one was obviously drafting Carey Price with the 3rd overall pick. I love the Habs, I made an emotional pick over a logical and safer one. If I really wanted to go “Goalie First” as I wrote, I should have gone with someone safer like Andrei Vasilevsky. I’ll note that I did still do okay in terms of drafting goaltending with Philip Grubauer of Colorado later on but regardless, a 3rd overall pick was wasted and would have made all the difference as I spent so much effort mining the Waiver wire for scoring power.

Another error was drafting Anthony DeAngelo who was banished from the league. I wrote that “Tony DeAngelo is an awful human but he plays a pretty okay game of fantasy hockey.” However, you can’t play a good game of fantasy hockey if you aren’t playing any hockey at all.

As well, several players on my team had mediocre years. Phillip Danualt took like half a season to score his first goal. Ryan Getzlaf finally regressed this year due to age (more on that in section 4.3). Pierre Luc Dubois had a down season that included a 2-week quarantine on his way to Winnipeg. Tanner Pearson regressed too. Tyler Bertuzzi did have a good start to the season but spent the majority of it in an IR slot.

By the end of the season, my team barely resembled the one I drafted. It was probably the biggest turnover of a fantasy roster I had ever seen.

3.2 Canada Cup

I came in first in the regular season and the core of my team (Mark Scheifele, Jimothy Timothy Miller, Elias Lindholm, and Jeff Petry) carried my team the whole way. Ryan Nugent-Hopkins was a good pick as well but I traded him for Matthew Tkachuk towards the end of the season as he (and his brother even more so) are beasts when it comes to the Fantasy Score algorithm. They do it all, even though Matthew did regress a bit on the score sheet this season.

I again picked Price too early (same mistake as in Chel21) but that pick also came with Jake Allen since we drafted a team’s goalie tandem, and he held my team together all season. Absolutely the MVP for the Habs this year.

I’ll note that I did end up losing the consolation playoff final (technically a 4th place finish) but in fantasy hockey playoffs where 1 week of play will make or break you, combined with all the complications of COVID and man-games lost, it doesn’t worry me too much.

4. What’s Next For

4.1 No Playoffs This Year

First of all, I’m not going to support playoff pools, at least for this season. I don’t think it would be worth the effort especially considering most playoff pools don’t allow for tinkering with lineups. You set it and forget it!

Maybe I’ll do something for next year, but it would need to include some sort of bracket selection to help you prioritize players on your chosen teams.

4.2 Personalized Fantasy Score For Your Team During The Season

The Fantasy Score needs to connect back to the team you’ve drafted. If you keep your fantasy team up to date on the app (a manual process for now), it should know your team’s strengths and weaknesses. And now that there are up-to-date stats loaded too, the app knows what categories you are struggling with right now. This should be reflected in the fantasy score since really, the best Waiver pickup for your team may not be the best pickup for another’s team when each team has different strengths and weaknesses.

4.3 Draft Center Fantasy Score Improvements

The Fantasy Score algorithm used for drafting needs to consider factors other than the last 1–3 years of stats. Below are a few examples of ideas I want to play around with this summer:

We know that players tend to regress as they get older. It shouldn’t have been a surprise to me that Ryan Getzlaf finally had a mediocre year (17 points, ~29 in a full season) after consistently trending downwards for the last few years. He’s 36 years old now, if anything it should have happened sooner.

The algorithm also needs to better filter out outliers that skew the data in negative ways. I tried to do this by running the Fantasy Score algorithm, marking players under a certain threshold as outliers, then rerunning the algorithm on everyone else again to get a better score and this mostly works but could be better.

4.4 Recognizing Greatness

Still on the topic of outliers, I don’t think the Fantasy Score algorithm recognizes greatness enough. Connor McDavid ranks 8th overall for forwards in Fantasy Score, which is very good, but what he did this season was just unbelievable. His 72 assists alone would have put him in second for scoring in the league behind his teammate Leon Draisaitl. When you have a player getting 1.5–2x the assists of any other top-tier players, that needs to mean something more. I think his score should be higher. If he had scored the same amount of assists as Brad Marchand his score would be about the same. To me that is wrong and this logic can easily apply to other stat categories too. More tinkering needs to be done for this.

4.5 Goalies

I want to figure out how to evaluate goalies so I can rank them for the Draft Center and Current Season Stats pages. It’s on my roadmap for the Fall but I’m still not sure how best to accomplish it. The stats they are evaluated on are often considered team stats. Goalies who have more wins generally play on stronger teams. Goalies with lower GAA often play on teams with strong defense. Perhaps I should be considering goalies based on team strength and schedule strength? I’ll let this keep marinating over the summer…

4.6 Frontend Improvements

As I mentioned in my first blog, I am not a frontend developer, and I don’t really want to be. It doesn’t interest me, I’d much rather work with JSON (but I don’t others do). The website is built in basic Rails + HTML ERB. It does the job, it's not super ugly, but obviously, there is room for improvement. I’m not going to rewrite the site in React, I like server-side rendered web pages. But I do want to make the site look a bit nicer and more intuitive to use. My girlfriend gave me a critique of things to improve so I’ll see what I come up with.

4.7 Integrate with Yahoo Sports?

This is a stretch goal. Yahoo Sports does have a developer API, see: If I could have users log in to through OAuth with their Yahoo account, I could do a lot of cool things like:

  • Easily load a user’s team(s) so it's no longer a manual process to keep them in sync.
  • Regularly load which players are available on Waivers so that the “Current Season Stats” is up to date and only shows players who are actually available.
  • Perform analysis of the teams a user is facing each week to further optimize the Fantasy Score algorithm towards areas of weakness and maybe even advise on lineup decisions each day!
  • Perhaps I could get current stats from Yahoo and do-away with my web scraping work?

I’m still pondering the possibilities but there’s a lot of power in gaining more access to the pool data.

5. Conclusion

This was and still is a really fun project for me and I look forward to building on it over the summer. There’s lots of work to do and I hope to deliver a more polished and mature project in September in advance of Fantasy Hockey drafts for the 2021–2022 season.

If you have any questions, feedback, or want to help out in some way, please get in touch!