Instead of computing the whole metrics again, only compute the missing part in saved stats (ie filter the closed_history on date >= stats[metric].time[-2])
The last point drastically reduces the size of the data to group and count + size of the list to upsert
The current upsert algorithm starts from the beginning of both lists. However in practice we would only upsert recent data, therefore we should upsert from the last element to the first. (ie if the stats have 1000 values stored, the new values are on average of size < 10, and the upsert algorithm would always be in the worst case and iterating through 999 values)
With both ideas, the whole upsert algorithm would actually be on average O(1) complexity, only O(n) would be closed_history preprocessing.