Repository: rotki/rotki

Issue:
Balance save snapshots do not save "0 amount" balances, which can cause unexpected behavior in API calls and reporting
## Problem Definition

Basically when a token has a 0 balance/amount, no entry for it is saved during the balance snapshot (this makes sense as you wouldn't want to save a "0" entry for all possible tokens in every snapshot). However, in the cases where a token previously had a balance, that was maybe then sold / transferred / swapped / whatever, there _should_ be a 0 balance saved for any subsequent saves, otherwise you get confusing things in the premium statistics graph, e.g.:

What it looks like:
![image](https://user-images.githubusercontent.com/27592/111897945-25f16e80-8a23-11eb-8128-c783fe19ecf8.png)

What it should look like:
![image](https://user-images.githubusercontent.com/27592/111897950-28ec5f00-8a23-11eb-8c20-3872a4233fc0.png)

One way to fix this is obviously to build balances based on historical transactions, but since this is very far away, in the interim two possible fixes are:
* When saving token balances, if a token has previously had balances > 0, it should save a 0 balance for that token
* Don't save zero balances, but when retrieving token balances, if a token has previously had balance > 0, it should create and return "virtual" 0 balances for any dates after that (until there is a >0 balance again)

Personally I find the top method preferable because I don't like putting "virtual balance logic" in the queries as opposed to in the data itself (i.e. if you implement the 2nd method and then go look at the balances in the db, or try to create a new call at some point in the future, you will have to remember that some of these 0-token balances are virtual).

## Logs

Not needed, verified by Lefteris in discord



### System Description

Operating system: Win10
Rotki version: 1.15.1



Discussion:
I will try to take this up and see how it could be done.

Resolving patch:
diff --git a/rotkehlchen/db/dbhandler.py b/rotkehlchen/db/dbhandler.py
--- a/rotkehlchen/db/dbhandler.py
+++ b/rotkehlchen/db/dbhandler.py
@@ -52,6 +52,7 @@
 from rotkehlchen.chain.ethereum.trades import AMMSwap
 from rotkehlchen.constants.assets import A_USD
 from rotkehlchen.constants.ethereum import YEARN_VAULTS_PREFIX, YEARN_VAULTS_V2_PREFIX
+from rotkehlchen.constants.timing import DAY_IN_SECONDS
 from rotkehlchen.db.eth2 import ETH2_DEPOSITS_PREFIX
 from rotkehlchen.db.loopring import DBLoopring
 from rotkehlchen.db.schema import DB_SCRIPT_CREATE_TABLES
@@ -2688,15 +2689,32 @@ def query_timed_balances(
         results = cursor.execute(querystr)
         results = results.fetchall()
         balances = []
-        for result in results:
+        results_length = len(results)
+        for idx, result in enumerate(results):
+            entry_time = result[0]
+            category = BalanceType.deserialize_from_db(result[3])
             balances.append(
                 SingleDBAssetBalance(
-                    time=result[0],
+                    time=entry_time,
                     amount=result[1],
                     usd_value=result[2],
-                    category=BalanceType.deserialize_from_db(result[3]),
+                    category=category,
                 ),
             )
+            if idx == results_length - 1:
+                continue
+
+            next_result_time = results[idx + 1][0]
+            while next_result_time - entry_time > DAY_IN_SECONDS:
+                entry_time = entry_time + DAY_IN_SECONDS
+                balances.append(
+                    SingleDBAssetBalance(
+                        time=Timestamp(min(entry_time, next_result_time - 1)),
+                        amount='0',
+                        usd_value='0',
+                        category=category,
+                    ),
+                )
 
         return balances
 


Test patch:
diff --git a/rotkehlchen/tests/db/test_db.py b/rotkehlchen/tests/db/test_db.py
--- a/rotkehlchen/tests/db/test_db.py
+++ b/rotkehlchen/tests/db/test_db.py
@@ -574,11 +574,13 @@ def test_query_timed_balances(data_dir, username):
     assert result[0].amount == '500'
     assert result[0].usd_value == '500'
 
-    result = data.db.query_timed_balances(
+    all_data = data.db.query_timed_balances(
         from_ts=1451606300,
         to_ts=1485907000,
         asset=A_ETH,
     )
+    assert len(all_data) == 158
+    result = [x for x in all_data if x.amount != '0']
     assert len(result) == 2
     assert result[0].time == 1451606401
     assert result[0].category == BalanceType.ASSET
@@ -589,7 +591,9 @@ def test_query_timed_balances(data_dir, username):
     assert result[1].amount == '10'
     assert result[1].usd_value == '123'
 
-    result = data.db.query_timed_balances(A_ETH)
+    all_data = data.db.query_timed_balances(A_ETH)
+    assert len(all_data) == 399
+    result = [x for x in all_data if x.amount != '0']
     assert len(result) == 4
     result = data.db.query_timed_balances(A_ETH, balance_type=BalanceType.LIABILITY)
     assert len(result) == 1