Repository: rotki/rotki

Issue:
Margin trade loss should also reduce affected asset amount
## Problem Definition

When processing a margin trade the current logic adds an amount of the won asset if there was profit but if there was a loss the amount is not reduced as can be seen below.

https://github.com/rotkehlchenio/rotkehlchen/blob/1ae0ad38961558e0e971a20ea240da2630875f1b/rotkehlchen/accounting/events.py#L707-L734

This can lead to us having more of an asset than in reality.

## Task

- [x] During processing of margin trades, if there is loss of an asset take into account and reduce the available amount of that asset.
- [x] Write tests for the above.


Discussion:

Resolving patch:
diff --git a/rotkehlchen/accounting/accountant.py b/rotkehlchen/accounting/accountant.py
--- a/rotkehlchen/accounting/accountant.py
+++ b/rotkehlchen/accounting/accountant.py
@@ -498,15 +498,16 @@ def process_action(
             selling_rate = selling_asset_rate * trade.rate
             fee_in_profit_currency = self.get_fee_in_profit_currency(trade)
             gain_in_profit_currency = selling_rate * trade.amount
+            selling_amount = trade.cost
             self.events.add_sell(
                 selling_asset=selling_asset,
-                selling_amount=trade.amount,
+                selling_amount=selling_amount,
                 receiving_asset=None,
                 receiving_amount=None,
                 gain_in_profit_currency=gain_in_profit_currency,
                 total_fee_in_profit_currency=fee_in_profit_currency,
                 trade_rate=trade.rate,
-                rate_in_profit_currency=selling_rate,
+                rate_in_profit_currency=selling_asset_rate,
                 timestamp=trade.timestamp,
                 loan_settlement=True,
             )
diff --git a/rotkehlchen/accounting/events.py b/rotkehlchen/accounting/events.py
--- a/rotkehlchen/accounting/events.py
+++ b/rotkehlchen/accounting/events.py
@@ -730,6 +730,17 @@ def add_margin_position(
                     fee_rate=0,
                 ),
             )
+        elif net_gain_loss_amount < 0:
+            result = self.reduce_asset_amount(
+                asset=gain_loss_asset,
+                amount=-gain_loss_amount,
+            )
+            if not result:
+                log.critical(
+                    f'No documented buy found for {gain_loss_asset} before '
+                    f'{tsToDate(timestamp, formatstr="%d/%m/%Y %H:%M:%S")}',
+                )
+
         # count profit/loss if we are inside the query period
         if timestamp >= self.query_start_ts:
             self.margin_positions_profit_loss += gain_loss_in_profit_currency


Test patch:
diff --git a/rotkehlchen/tests/test_accounting.py b/rotkehlchen/tests/test_accounting.py
--- a/rotkehlchen/tests/test_accounting.py
+++ b/rotkehlchen/tests/test_accounting.py
@@ -2,8 +2,12 @@
 
 from rotkehlchen.errors import CorruptData
 from rotkehlchen.fval import FVal
+from rotkehlchen.order_formatting import MarginPosition
 from rotkehlchen.tests.utils.accounting import accounting_history_process
 
+DUMMY_ADDRESS = '0x0'
+DUMMY_HASH = '0x0'
+
 history1 = [
     {
         "timestamp": 1446979735,
@@ -348,3 +352,178 @@ def test_buy_event_creation(accountant):
     assert buys[1].timestamp == 1476979735
     assert buys[1].rate == FVal('578.505')
     assert buys[1].fee_rate == FVal('2.4e-4')
+
+
+@pytest.mark.parametrize('accounting_include_gas_costs', [False])
+def test_not_include_gas_costs(accountant):
+    history = [{
+        "timestamp": 1476979735,
+        "pair": "BTC_EUR",
+        "type": "buy",
+        "rate": 578.505,
+        "cost": 2892.525,
+        "cost_currency": "EUR",
+        "fee": 0.0012,
+        "fee_currency": "BTC",
+        "amount": 5,
+        "location": "kraken",
+    }, {
+        "timestamp": 1496979735,
+        "pair": "BTC_EUR",
+        "type": "sell",
+        "rate": 2519.62,
+        "cost": 2519.62,
+        "cost_currency": "EUR",
+        "fee": 0.02,
+        "fee_currency": "EUR",
+        "amount": 1,
+        "location": "kraken",
+    }]
+    eth_tx_list = [{
+        'timestamp': 1491062063,  # 01/04/2017
+        'block_number': 3458409,  # cryptocompare hourly ETH/EUR: 47.5
+        'hash': DUMMY_HASH,
+        'from_address': DUMMY_ADDRESS,
+        'to_address': DUMMY_ADDRESS,
+        'value': 12323,
+        'gas': 5000000,
+        'gas_price': 2000000000,
+        'gas_used': 1000000,
+    }]
+    result = accounting_history_process(
+        accountant,
+        1436979735,
+        1519693374,
+        history,
+        eth_transaction_list=eth_tx_list,
+    )
+    assert FVal(result['overview']['total_taxable_profit_loss']).is_close("1940.9561588")
+
+
+@pytest.mark.parametrize('accounting_ignored_assets', [['DASH']])
+def test_ignored_assets(accountant):
+    history = history1 + [{
+        "timestamp": 1476979735,
+        "pair": "DASH_EUR",
+        "type": "buy",
+        "rate": 9.76775956284,
+        "cost": 97.6775956284,
+        "cost_currency": "EUR",
+        "fee": 0.0011,
+        "fee_currency": "DASH",
+        "amount": 10,
+        "location": "kraken",
+    }, {
+        "timestamp": 1496979735,
+        "pair": "DASH_EUR",
+        "type": "sell",
+        "rate": 128.09,
+        "cost": 640.45,
+        "cost_currency": "EUR",
+        "fee": 0.015,
+        "fee_currency": "EUR",
+        "amount": 5,
+        "location": "kraken",
+    }]
+    result = accounting_history_process(accountant, 1436979735, 1519693374, history)
+    assert FVal(result['overview']['total_taxable_profit_loss']).is_close("557.528104903")
+
+
+def test_settlement_buy(accountant):
+    history = [{
+        "timestamp": 1476979735,
+        "pair": "BTC_EUR",
+        "type": "buy",
+        "rate": 578.505,
+        "cost": 2892.525,
+        "cost_currency": "EUR",
+        "fee": 0.0012,
+        "fee_currency": "BTC",
+        "amount": 5,
+        "location": "kraken",
+    }, {  # 0.0079275 * 810.49 + 0.15 * 12.4625608386372145 = 8.29454360079
+        'timestamp': 1484629704,  # 17/01/2017
+        'pair': 'BTC_DASH',  # DASH/EUR price: 12.4625608386372145
+        'type': 'settlement_buy',  # Buy DASH with BTC to settle. Essentially BTC loss
+        'rate': 0.015855,  # BTC/EUR price: 810.49
+        'cost': 0.0079275,
+        'cost_currency': 'BTC',
+        'fee': 0.15,
+        'fee_currency': 'DASH',
+        'amount': 0.5,
+        'location': 'poloniex',
+    }, {
+        "timestamp": 1496979735,
+        "pair": "BTC_EUR",
+        "type": "sell",
+        "rate": 2519.62,
+        "cost": 2519.62,
+        "cost_currency": "EUR",
+        "fee": 0.02,
+        "fee_currency": "EUR",
+        "amount": 1,
+        "location": "kraken",
+    }]
+
+    result = accounting_history_process(
+        accountant,
+        1436979735,
+        1519693374,
+        history,
+    )
+    assert accountant.get_calculated_asset_amount('BTC').is_close('3.9920725')
+    assert FVal(result['overview']['total_taxable_profit_loss']).is_close('1932.6616152')
+    assert FVal(result['overview']['settlement_losses']).is_close('8.29454360079')
+
+
+def test_margin_events_affect_gaine_lost_amount(accountant):
+    history = [{
+        "timestamp": 1476979735,
+        "pair": "BTC_EUR",
+        "type": "buy",
+        "rate": 578.505,
+        "cost": 2892.525,
+        "cost_currency": "EUR",
+        "fee": 0.0012,
+        "fee_currency": "BTC",
+        "amount": 5,
+        "location": "kraken",
+    }, {  # 2519.62-0.02-((0.0012*578.505)/5 + 578.505)
+        "timestamp": 1496979735,
+        "pair": "BTC_EUR",
+        "type": "sell",
+        "rate": 2519.62,
+        "cost": 2519.62,
+        "cost_currency": "EUR",
+        "fee": 0.02,
+        "fee_currency": "EUR",
+        "amount": 1,
+        "location": "kraken",
+    }]
+    margin_history = [MarginPosition(
+        exchange='poloniex',  # BTC/EUR: 810.49
+        open_time=1484438400,  # 15/01/2017
+        close_time=1484629704,  # 17/01/2017
+        profit_loss=FVal('-0.5'),
+        pl_currency='BTC',
+        notes='margin1',
+    ), MarginPosition(
+        exchange='poloniex',  # BTC/EUR: 979.39
+        open_time=1487116800,  # 15/02/2017
+        close_time=1487289600,  # 17/02/2017
+        profit_loss=FVal('0.25'),
+        pl_currency='BTC',
+        notes='margin2',
+    )]
+
+    result = accounting_history_process(
+        accountant,
+        1436979735,
+        1519693374,
+        history,
+        margin_list=margin_history,
+    )
+    assert accountant.get_calculated_asset_amount('BTC').is_close('3.75')
+    assert FVal(result['overview']['general_trade_profit_loss']).is_close('1940.9561588')
+    assert FVal(result['overview']['margin_positions_profit_loss']).is_close('-160.3975')
+    assert FVal(result['overview']['total_taxable_profit_loss']).is_close('1780.5586588')
diff --git a/rotkehlchen/tests/test_end_to_end_tax_report.py b/rotkehlchen/tests/test_end_to_end_tax_report.py
--- a/rotkehlchen/tests/test_end_to_end_tax_report.py
+++ b/rotkehlchen/tests/test_end_to_end_tax_report.py
@@ -105,6 +105,17 @@
         'fee_currency': 'EUR',
         'amount': 10,
         'location': 'kraken',
+    }, {  # 0.0079275 * 810.49 + 0.15 * 12.4625608386372145 = 8.29454360079
+        'timestamp': 1484629704,  # 17/01/2017
+        'pair': 'BTC_DASH',  # DASH/EUR price: 12.4625608386372145
+        'type': 'settlement_buy',  # Buy DASH with BTC to settle. Essentially BTC loss
+        'rate': 0.015855,  # BTC/EUR price: 810.49
+        'cost': 0.0079275,
+        'cost_currency': 'BTC',
+        'fee': 0.15,
+        'fee_currency': 'DASH',
+        'amount': 0.5,
+        'location': 'poloniex',
     }, {  # 0.00244725 * 942.78 + 0.01*15.36169816590634019 = 2.46083533666
         'timestamp': 1486299904,  # 05/02/2017
         'pair': 'DASH_BTC',  # cryptocompare hourly DASH/EUR price: 15.36169816590634019
@@ -122,10 +133,10 @@
         'type': 'sell',  # sell BTC for EUR -- partly taxable (within 1 year)
         'rate': 1146.22,  # cryptocompare hourly BTC/EUR price: 1146.22
         'cost': 2292.44,
-        'cost_currency': 'EUR',  # Non taxable BTC: 1.034862500. Then 0.554125
-        'fee': 0.01,             # from 1475042230 and 3.3039 from 1476536704
-        'fee_currency': 'EUR',   # Also note that fees at corresponding buys are
-        'amount': 2,             # not yet accounted
+        'cost_currency': 'EUR',
+        'fee': 0.01,
+        'fee_currency': 'EUR',
+        'amount': 2,
         'location': 'kraken',
     },
 ]
@@ -304,13 +315,13 @@ def test_end_to_end_tax_report(accountant):
     )
     result = result['overview']
     general_trade_pl = FVal(result['general_trade_profit_loss'])
-    assert general_trade_pl.is_close('5048.20025137')
+    assert general_trade_pl.is_close('5032.30394444')
     taxable_trade_pl = FVal(result['taxable_trade_profit_loss'])
-    assert taxable_trade_pl.is_close('3927.02376907')
+    assert taxable_trade_pl.is_close('3954.94067484')
     loan_profit = FVal(result['loan_profit'])
     assert loan_profit.is_close('0.114027511004')
     settlement_losses = FVal(result['settlement_losses'])
-    assert settlement_losses.is_close('3.56089563177')
+    assert settlement_losses.is_close('11.8554392326')
     asset_movement_fees = FVal(result['asset_movement_fees'])
     assert asset_movement_fees.is_close('2.39417915')
     ethereum_transaction_gas_costs = FVal(result['ethereum_transaction_gas_costs'])
@@ -352,13 +363,13 @@ def test_end_to_end_tax_report_in_period(accountant):
     )
     result = result['overview']
     general_trade_pl = FVal(result['general_trade_profit_loss'])
-    assert general_trade_pl.is_close('1522.86543605')
+    assert general_trade_pl.is_close('1506.96912912')
     taxable_trade_pl = FVal(result['taxable_trade_profit_loss'])
-    assert taxable_trade_pl.is_close('614.735631324')
+    assert taxable_trade_pl.is_close('642.652537097')
     loan_profit = FVal(result['loan_profit'])
     assert loan_profit.is_close('0.111881296004')
     settlement_losses = FVal(result['settlement_losses'])
-    assert settlement_losses.is_close('2.46083533666')
+    assert settlement_losses.is_close('10.7553789375')
     asset_movement_fees = FVal(result['asset_movement_fees'])
     assert asset_movement_fees.is_close('2.38526415')
     ethereum_transaction_gas_costs = FVal(result['ethereum_transaction_gas_costs'])
@@ -463,25 +474,47 @@ def test_end_to_end_tax_report_in_period(accountant):
 
 # profit: 129.2417 - 89.5410160122 = 39.7006839878
 
+# --> 1484629704 (taxable)
+
+# Buy Dash with BTC for settlement. BTC Loss
+
+# loss in EUR: 0.0079275 * 810.49 + 0.15 * 12.4625608386372145 = 8.29454360079
+# loss in BTC: 0.0079275
+
 # --> 1488373504 (partly taxable)
 
+
 # Sell 2 BTC for EUR
 
 # gain: 2292.44 - 0.01 = 2292.43
 
-# taxfree_bought_cost = 1.034862500 * 268.678317859 = 278.045115715
+# taxfree_bought_cost = 0.984935 * 268.678317859 = 264.630679
 
-# part_from_1st_btc_buy: 2.5-1.4651375 = 1.0348625
+# part_from_1st_btc_buy: 5-2.5-0.9291375-0.536-0.0079275  = 0.984935
 # part_from_1nd_margin_profit: 0.05
 # part_from_2nd_btc_buy: 0.554125
-# part_from_3rd_btc_buy: 2 - 1.0348625 - 0.554125 - 0.05 = 0.3610125
+# part_from_3rd_btc_buy: 2 - 0.984935 - 0.554125 - 0.05 = 0.41094
 
 # taxable_bought_cost = 0.05 * 422.90 + 0.554125 * ((1 / 0.022165) * 11.925) + 0.001 *11.925 +
-# 0.3610125 * ((1 / 0.018355) * 10.775) + (0.3610125/3.3039) * 0.01 * 10.775
-# taxable_bought_cost = 531.220132224
-
-# general_pl = 2292.43 - (531.220132224 + 278.045115715)
-# general_pl = 1483.16475206
-
-# taxable_pl = ((0.554125+0.4110125)/2)*2292.43 - 531.220132224
-# taxable_pl = 575.034947336
+# 0.41094 * ((1 / 0.018355) * 10.775) + (0.41094/3.3039) * 0.01 * 10.775
+# taxable_bought_cost = 560.530875871
+
+# general_pl = 2292.43 - (560.530875871 + 264.630679)
+# general_pl = 1467.26844513
+
+# taxable_pl = ((0.05+0.554125+0.41094)/2)*2292.43 - 560.530875871
+# taxable_pl = 602.951853109
+
+
+# ---> BTC movements appendix
+# 1446979735 - 1st buy: 5
+# 1464393600 - 1st margin: 0.05
+# 1467378304 - 1st sell: 2.5
+# 1473505138 - 2nd sell: 0.9291375
+# 1473897600 - 2nd margin: -0.042
+# 1475042230 - 2nd buy: 0.554125
+# 1476536704 - 3rd buy: 3.3039
+# 1479200704 - 3rd sell: 0.536
+# 1480683904 - 4th buy: 0.00146445
+# 1484629704 - 4th sell: 0.0079275
+# 1486299904 - 5th buy: 0.00244725