Repository: rotki/rotki

Issue:
csv export formulae need updates
Rotki v1.6.2

With the addition of 'Location' to the csv export, I believe some of the formulae need updating. For example:

https://github.com/rotki/rotki/blob/4e1d6050b65dece9f002608fe738b09c5fb2b773/rotkehlchen/csv_exporter.py#L229

I think references to columns K and J should be to L and K, i.e.:

`taxable_profit_formula = '=IF(G{}=0,0,L{}-K{})'.format(row, row, row)`

Other examples:
https://github.com/rotki/rotki/blob/4e1d6050b65dece9f002608fe738b09c5fb2b773/rotkehlchen/csv_exporter.py#L104
https://github.com/rotki/rotki/blob/4e1d6050b65dece9f002608fe738b09c5fb2b773/rotkehlchen/csv_exporter.py#L107
https://github.com/rotki/rotki/blob/4e1d6050b65dece9f002608fe738b09c5fb2b773/rotkehlchen/csv_exporter.py#L110
https://github.com/rotki/rotki/blob/4e1d6050b65dece9f002608fe738b09c5fb2b773/rotkehlchen/csv_exporter.py#L276


Discussion:
Ah good point. Thanks for reminding us of this. For now you can edit the CSV directly via google docs as bulk edit for a quick solution. We will fix it in the next release.

Also need to think if we can add some tests for this. But may be hard.

Resolving patch:
diff --git a/rotkehlchen/csv_exporter.py b/rotkehlchen/csv_exporter.py
--- a/rotkehlchen/csv_exporter.py
+++ b/rotkehlchen/csv_exporter.py
@@ -101,13 +101,13 @@ def add_to_allevents(
                 net_profit_or_loss = FVal(0)
             else:
                 net_profit_or_loss = taxable_received_in_profit_currency - taxable_bought_cost
-            net_profit_or_loss_csv = '=IF(D{}=0,0,K{}-L{})'.format(row, row, row)
+            net_profit_or_loss_csv = '=IF(E{}=0,0,L{}-M{})'.format(row, row, row)
         elif event_type in (EV_TX_GAS_COST, EV_ASSET_MOVE, EV_LOAN_SETTLE):
             net_profit_or_loss = paid_in_profit_currency
-            net_profit_or_loss_csv = '=-J{}'.format(row)
+            net_profit_or_loss_csv = '=-K{}'.format(row)
         elif event_type in (EV_INTEREST_PAYMENT, EV_MARGIN_CLOSE, EV_DEFI):
             net_profit_or_loss = taxable_received_in_profit_currency
-            net_profit_or_loss_csv = '=K{}'.format(row)
+            net_profit_or_loss_csv = '=L{}'.format(row)
         else:
             raise ValueError('Illegal event type "{}" at add_to_allevents'.format(event_type))
 
@@ -226,7 +226,7 @@ def add_sell(
             selling_amount=selling_amount,
         )
         row = len(self.trades_csv) + 2
-        taxable_profit_formula = '=IF(G{}=0,0,K{}-J{})'.format(row, row, row)
+        taxable_profit_formula = '=IF(H{}=0,0,L{}-K{})'.format(row, row, row)
         self.trades_csv.append({
             'type': 'sell',
             'location': str(location),
@@ -273,7 +273,7 @@ def add_loan_settlement(
             return
 
         row = len(self.loan_settlements_csv) + 2
-        loss_formula = '=B{}*C{}+D{}'.format(row, row, row)
+        loss_formula = '=C{}*D{}+E{}'.format(row, row, row)
         self.loan_settlements_csv.append({
             'asset': asset.identifier,
             'location': str(location),


Test patch:
diff --git a/rotkehlchen/tests/api/test_history.py b/rotkehlchen/tests/api/test_history.py
--- a/rotkehlchen/tests/api/test_history.py
+++ b/rotkehlchen/tests/api/test_history.py
@@ -1,5 +1,6 @@
 import csv
 import os
+import re
 from contextlib import ExitStack
 from http import HTTPStatus
 from pathlib import Path
@@ -8,6 +9,15 @@
 import requests
 
 from rotkehlchen.assets.asset import Asset
+from rotkehlchen.constants import (
+    EV_ASSET_MOVE,
+    EV_DEFI,
+    EV_INTEREST_PAYMENT,
+    EV_LOAN_SETTLE,
+    EV_MARGIN_CLOSE,
+    EV_SELL,
+    EV_TX_GAS_COST,
+)
 from rotkehlchen.constants.misc import ZERO
 from rotkehlchen.csv_exporter import (
     FILENAME_ALL_CSV,
@@ -301,6 +311,85 @@ def test_query_history_errors(rotkehlchen_api_server):
     )
 
 
+def _assert_column(keys, letter, expected_column_name, location):
+    msg = f'{location} should be {expected_column_name}'
+    assert keys[ord(letter) - ord('A')] == expected_column_name, msg
+
+
+CSV_SELL_RE = re.compile(r'=IF\(([A-Z]).*=0,0,([A-Z]).*-([A-Z]).*\)')
+
+
+def assert_csv_formulas_trades(row, profit_currency):
+    keys = list(row.keys())
+    if row['type'] == 'sell':
+        taxable_profit_loss = row[f'taxable_profit_loss_in_{profit_currency.identifier}']
+        match = CSV_SELL_RE.search(taxable_profit_loss)
+        assert match
+        groups = match.groups()
+        assert len(groups) == 3
+
+        _assert_column(
+            keys=keys,
+            letter=groups[0],
+            expected_column_name='taxable_amount',
+            location='conditional column name in trades sell pnl',
+        )
+        _assert_column(
+            keys=keys,
+            letter=groups[1],
+            expected_column_name=f'taxable_gain_in_{profit_currency.identifier}',
+            location='taxable gain in trades sell pnl',
+        )
+        _assert_column(
+            keys=keys,
+            letter=groups[2],
+            expected_column_name=f'taxable_bought_cost_in_{profit_currency.identifier}',
+            location='taxable bought in trades sell pnl',
+        )
+
+
+def assert_csv_formulas_all_events(row, profit_currency):
+    keys = list(row.keys())
+    net_profit_loss = row['net_profit_or_loss']
+    if row['type'] == EV_SELL:
+        match = CSV_SELL_RE.search(net_profit_loss)
+        assert match
+        groups = match.groups()
+        assert len(groups) == 3
+        _assert_column(
+            keys=keys,
+            letter=groups[0],
+            expected_column_name='taxable_amount',
+            location='conditional column name in all events sell pnl',
+        )
+        _assert_column(
+            keys=keys,
+            letter=groups[1],
+            expected_column_name=f'taxable_received_in_{profit_currency.identifier}',
+            location='taxable received in all events sell pnl',
+        )
+        _assert_column(
+            keys=keys,
+            letter=groups[2],
+            expected_column_name=f'taxable_bought_cost_in_{profit_currency.identifier}',
+            location='taxable bought cost in all events sell pnl',
+        )
+    elif row['type'] in (EV_TX_GAS_COST, EV_ASSET_MOVE, EV_LOAN_SETTLE):
+        _assert_column(
+            keys=keys,
+            letter=net_profit_loss[2],
+            expected_column_name=f'paid_in_{profit_currency.identifier}',
+            location='paid in profit currency in all events tx gas cost and more pnl',
+        )
+    elif row['type'] in (EV_INTEREST_PAYMENT, EV_MARGIN_CLOSE, EV_DEFI):
+        _assert_column(
+            keys=keys,
+            letter=net_profit_loss[1],
+            expected_column_name=f'taxable_received_in_{profit_currency.identifier}',
+            location='gained in profit currenty in all events defi and more',
+        )
+
+
 def assert_csv_export_response(response, profit_currency, csv_dir):
     assert_proper_response(response)
     data = response.json()
@@ -314,6 +403,7 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
+            assert_csv_formulas_trades(row, profit_currency)
             assert len(row) == 15
             assert row['location'] in ('kraken', 'bittrex', 'binance', 'poloniex')
             assert row['type'] in ('buy', 'sell')
@@ -397,7 +487,7 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
     num_margins = 2
     assert count == num_margins, 'Incorrect amount of margin CSV entries found'
 
-    # None of this in the current history. TODO: add
+    # None of this in the current history. TODO: add and also test formula
     # with open(os.path.join(csv_dir, FILENAME_LOAN_SETTLEMENTS_CSV), newline='') as csvfile:
     #     reader = csv.DictReader(csvfile)
     #     count = 0
@@ -417,6 +507,7 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
+            assert_csv_formulas_all_events(row, profit_currency)
             assert len(row) == 13
             assert row['location'] in (
                 'kraken',