Repository: rotki/rotki

Issue:
Discrepancies between CSV export with and without formulas for Ledger actions
## Problem Definition

There is some discrepancies between the PNL CSV Export for reports containing ledger actions when the formulas are on and when they are off.

Also some other discrepancies which gonna investigate as part of this issue

## Task

Fix them
Discrepancies between CSV export with and without formulas for Ledger actions
## Problem Definition

There is some discrepancies between the PNL CSV Export for reports containing ledger actions when the formulas are on and when they are off.

Also some other discrepancies which gonna investigate as part of this issue

## Task

Fix them


Discussion:

Resolving patch:
diff --git a/rotkehlchen/csv_exporter.py b/rotkehlchen/csv_exporter.py
--- a/rotkehlchen/csv_exporter.py
+++ b/rotkehlchen/csv_exporter.py
@@ -79,8 +79,8 @@ def _dict_to_csv_file(path: Path, dictionary_list: List) -> None:
         log.debug('Skipping writting empty CSV for {}'.format(path))
         return
 
-    with open(path, 'w') as f:
-        w = csv.DictWriter(f, dictionary_list[0].keys())
+    with open(path, 'w', newline='') as f:
+        w = csv.DictWriter(f, fieldnames=dictionary_list[0].keys())
         w.writeheader()
         try:
             for dic in dictionary_list:
@@ -343,17 +343,29 @@ def add_to_allevents(
                 actual_value=net_profit_or_loss,
             )
         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 = -paid_in_profit_currency
             net_profit_or_loss_csv = self._add_equals_formula(
                 expression=f'-K{row}',
                 actual_value=net_profit_or_loss,
             )
-        elif event_type in (EV_INTEREST_PAYMENT, EV_MARGIN_CLOSE, EV_DEFI, EV_LEDGER_ACTION):
+        elif event_type == EV_INTEREST_PAYMENT:
             net_profit_or_loss = taxable_received_in_profit_currency
             net_profit_or_loss_csv = self._add_equals_formula(
                 expression=f'L{row}',
                 actual_value=net_profit_or_loss,
             )
+        elif event_type in (EV_MARGIN_CLOSE, EV_DEFI, EV_LEDGER_ACTION):
+            if total_received_in_profit_currency > ZERO:
+                net_profit_or_loss = total_received_in_profit_currency
+            else:
+                net_profit_or_loss = -paid_in_profit_currency
+
+            net_profit_or_loss_csv = self._add_if_formula(
+                condition=f'P{row}=0',  # total_received_in_profit_currency is 0
+                if_true=f'-K{row}',  # then -paid_in_profit_currency
+                if_false=f'P{row}',  # else use total_received_in_profit_currency
+                actual_value=net_profit_or_loss,
+            )
         else:
             raise ValueError('Illegal event type "{}" at add_to_allevents'.format(event_type))
 
@@ -630,16 +642,28 @@ def add_margin_position(
             'gain_loss_amount': gain_loss_amount,
             f'profit_loss_in_{self.profit_currency.symbol}': gain_loss_in_profit_currency,
         })
+
+        paid_in_profit_currency = ZERO
+        received_in_profit_currency = ZERO
+        paid_in_asset = ZERO
+        received_in_asset = ZERO
+        if gain_loss_amount >= ZERO:
+            received_in_profit_currency = gain_loss_in_profit_currency
+            received_in_asset = gain_loss_amount
+        else:
+            paid_in_profit_currency = -gain_loss_in_profit_currency
+            paid_in_asset = -gain_loss_amount
+
         self.add_to_allevents(
             event_type=EV_MARGIN_CLOSE,
             location=location,
-            paid_in_profit_currency=ZERO,
+            paid_in_profit_currency=paid_in_profit_currency,
             paid_asset=S_EMPTYSTR,
-            paid_in_asset=ZERO,
+            paid_in_asset=paid_in_asset,
             received_asset=gain_loss_asset,
-            received_in_asset=gain_loss_amount,
-            taxable_received_in_profit_currency=gain_loss_in_profit_currency,
-            total_received_in_profit_currency=gain_loss_in_profit_currency,
+            received_in_asset=received_in_asset,
+            taxable_received_in_profit_currency=received_in_profit_currency,
+            total_received_in_profit_currency=received_in_profit_currency,
             timestamp=timestamp,
         )
 


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
@@ -12,8 +12,10 @@
 from rotkehlchen.accounting.accountant import FREE_PNL_EVENTS_LIMIT
 from rotkehlchen.constants import (
     EV_ASSET_MOVE,
+    EV_BUY,
     EV_DEFI,
     EV_INTEREST_PAYMENT,
+    EV_LEDGER_ACTION,
     EV_LOAN_SETTLE,
     EV_MARGIN_CLOSE,
     EV_SELL,
@@ -288,6 +290,7 @@ def _assert_column(keys, letter, expected_column_name, location):
 
 
 CSV_SELL_RE = re.compile(r'=IF\(([A-Z]).*=0;0;([A-Z]).*-([A-Z]).*\)')
+CSV_PROFITLOSS_RE = re.compile(r'=IF\(P.*=0;-K.*;P.*\)')
 
 
 def assert_csv_formulas_trades(row, profit_currency):
@@ -352,13 +355,20 @@ def assert_csv_formulas_all_events(row, profit_currency):
             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):
+    elif row['type'] == EV_INTEREST_PAYMENT:
         _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',
         )
+    elif row['type'] in (EV_LEDGER_ACTION, EV_MARGIN_CLOSE, EV_DEFI):
+        match = CSV_PROFITLOSS_RE.search(net_profit_loss)
+        assert match, 'entry does not match the expected formula'
+    elif row['type'] == EV_BUY:
+        pass  # nothing to check for these types
+    else:
+        raise AssertionError(f'Unexpected CSV row type {row["type"]} encountered')
 
 
 def assert_csv_export_response(response, profit_currency, csv_dir):