Repository: rotki/rotki

Issue:
Add each taxable action location in the tax report table
## Problem Definition

As indicated by @davbre [here](https://github.com/rotki/rotki/issues/1212#issue-665581857) it would be really useful for users if they could see the location of each entry in the tax report table. In other words, if it's an exchange trade and if yes where, if it's an OTC trade et.c.

## Task

- Return the location of the trade with the tax report generation API call
- Show it in the table in the frontend




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
@@ -224,6 +224,7 @@ def trade_add_to_sell_events(self, trade: Trade, loan_settlement: bool) -> None:
 
         if not loan_settlement:
             self.events.add_sell_and_corresponding_buy(
+                location=trade.location,
                 selling_asset=selling_asset,
                 selling_amount=trade.amount,
                 receiving_asset=receiving_asset,
@@ -236,6 +237,7 @@ def trade_add_to_sell_events(self, trade: Trade, loan_settlement: bool) -> None:
             )
         else:
             self.events.add_sell(
+                location=trade.location,
                 selling_asset=selling_asset,
                 selling_amount=trade.amount,
                 receiving_asset=None,
@@ -462,6 +464,7 @@ def process_action(
         if action_type == 'loan':
             action = cast(Loan, action)
             self.events.add_loan_gain(
+                location=action.location,
                 gained_asset=action.currency,
                 lent_amount=action.amount_lent,
                 gained_amount=action.earned,
@@ -494,6 +497,7 @@ def process_action(
         # costs in cost_currency
         if trade.trade_type == TradeType.BUY:
             self.events.add_buy_and_corresponding_sell(
+                location=trade.location,
                 bought_asset=trade.base_asset,
                 bought_amount=trade.amount,
                 paid_with_asset=trade.quote_asset,
@@ -523,6 +527,7 @@ def process_action(
             # (amount*rate) of the original buy
             selling_amount = trade.rate * trade.amount
             self.events.add_sell(
+                location=trade.location,
                 selling_asset=selling_asset,
                 selling_amount=selling_amount,
                 receiving_asset=None,
diff --git a/rotkehlchen/accounting/events.py b/rotkehlchen/accounting/events.py
--- a/rotkehlchen/accounting/events.py
+++ b/rotkehlchen/accounting/events.py
@@ -11,7 +11,7 @@
 from rotkehlchen.fval import FVal
 from rotkehlchen.history import PriceHistorian
 from rotkehlchen.logging import RotkehlchenLogsAdapter
-from rotkehlchen.typing import Fee, Timestamp
+from rotkehlchen.typing import Fee, Location, Timestamp
 from rotkehlchen.utils.misc import taxable_gain_for_sell, timestamp_to_date, ts_now
 
 logger = logging.getLogger(__name__)
@@ -143,6 +143,7 @@ def reduce_asset_amount(self, asset: Asset, amount: FVal) -> bool:
 
     def handle_prefork_asset_buys(
             self,
+            location: Location,
             bought_asset: Asset,
             bought_amount: FVal,
             paid_with_asset: Asset,
@@ -154,26 +155,28 @@ def handle_prefork_asset_buys(
         # TODO: Should fee also be taken into account here?
         if bought_asset == 'ETH' and timestamp < ETH_DAO_FORK_TS:
             self.add_buy(
-                A_ETC,
-                bought_amount,
-                paid_with_asset,
-                trade_rate,
-                fee_in_profit_currency,
-                fee_currency,
-                timestamp,
+                location=location,
+                bought_asset=A_ETC,
+                bought_amount=bought_amount,
+                paid_with_asset=paid_with_asset,
+                trade_rate=trade_rate,
+                fee_in_profit_currency=fee_in_profit_currency,
+                fee_currency=fee_currency,
+                timestamp=timestamp,
                 is_virtual=True,
             )
 
         if bought_asset == 'BTC' and timestamp < BTC_BCH_FORK_TS:
             # Acquiring BTC before the BCH fork provides equal amount of BCH
             self.add_buy(
-                A_BCH,
-                bought_amount,
-                paid_with_asset,
-                trade_rate,
-                fee_in_profit_currency,
-                fee_currency,
-                timestamp,
+                location=location,
+                bought_asset=A_BCH,
+                bought_amount=bought_amount,
+                paid_with_asset=paid_with_asset,
+                trade_rate=trade_rate,
+                fee_in_profit_currency=fee_in_profit_currency,
+                fee_currency=fee_currency,
+                timestamp=timestamp,
                 is_virtual=True,
             )
 
@@ -200,6 +203,7 @@ def handle_prefork_asset_sells(
 
     def add_buy_and_corresponding_sell(
             self,
+            location: Location,
             bought_asset: Asset,
             bought_amount: FVal,
             paid_with_asset: Asset,
@@ -219,6 +223,7 @@ def add_buy_and_corresponding_sell(
         or with reading the response returned by the server
         """
         self.add_buy(
+            location=location,
             bought_asset=bought_asset,
             bought_amount=bought_amount,
             paid_with_asset=paid_with_asset,
@@ -271,6 +276,7 @@ def add_buy_and_corresponding_sell(
             gain_in_profit_currency = with_sold_asset_gain
 
         self.add_sell(
+            location=location,
             selling_asset=paid_with_asset,
             selling_amount=sold_amount,
             receiving_asset=receiving_asset,
@@ -285,6 +291,7 @@ def add_buy_and_corresponding_sell(
 
     def add_buy(
             self,
+            location: Location,
             bought_asset: Asset,
             bought_amount: FVal,
             paid_with_asset: Asset,
@@ -308,6 +315,7 @@ def add_buy(
         buy_rate = paid_with_asset_rate * trade_rate
 
         self.handle_prefork_asset_buys(
+            location=location,
             bought_asset=bought_asset,
             bought_amount=bought_amount,
             paid_with_asset=paid_with_asset,
@@ -334,6 +342,7 @@ def add_buy(
         log.debug(
             'Buy Event',
             sensitive_log=True,
+            location=str(location),
             bought_amount=bought_amount,
             bought_asset=bought_asset,
             paid_with_asset=paid_with_asset,
@@ -345,6 +354,7 @@ def add_buy(
 
         if timestamp >= self.query_start_ts:
             self.csv_exporter.add_buy(
+                location=location,
                 bought_asset=bought_asset,
                 rate=buy_rate,
                 fee_cost=fee_in_profit_currency,
@@ -358,6 +368,7 @@ def add_buy(
 
     def add_sell_and_corresponding_buy(
             self,
+            location: Location,
             selling_asset: Asset,
             selling_amount: FVal,
             receiving_asset: Asset,
@@ -394,15 +405,16 @@ def add_sell_and_corresponding_buy(
         or with reading the response returned by the server
         """
         self.add_sell(
-            selling_asset,
-            selling_amount,
-            receiving_asset,
-            receiving_amount,
-            gain_in_profit_currency,
-            total_fee_in_profit_currency,
-            trade_rate,
-            rate_in_profit_currency,
-            timestamp,
+            location=location,
+            selling_asset=selling_asset,
+            selling_amount=selling_amount,
+            receiving_asset=receiving_asset,
+            receiving_amount=receiving_amount,
+            gain_in_profit_currency=gain_in_profit_currency,
+            total_fee_in_profit_currency=total_fee_in_profit_currency,
+            trade_rate=trade_rate,
+            rate_in_profit_currency=rate_in_profit_currency,
+            timestamp=timestamp,
             is_virtual=False,
         )
 
@@ -414,6 +426,7 @@ def add_sell_and_corresponding_buy(
         )
         # else then you are also buying some other asset through your sell
         self.add_buy(
+            location=location,
             bought_asset=receiving_asset,
             bought_amount=receiving_amount,
             paid_with_asset=selling_asset,
@@ -426,6 +439,7 @@ def add_sell_and_corresponding_buy(
 
     def add_sell(
             self,
+            location: Location,
             selling_asset: Asset,
             selling_amount: FVal,
             receiving_asset: Optional[Asset],
@@ -560,6 +574,7 @@ def add_sell(
 
             if loan_settlement:
                 self.csv_exporter.add_loan_settlement(
+                    location=location,
                     asset=selling_asset,
                     amount=selling_amount,
                     rate_in_profit_currency=rate_in_profit_currency,
@@ -569,6 +584,7 @@ def add_sell(
             else:
                 assert receiving_asset, 'Here receiving asset should have a value'
                 self.csv_exporter.add_sell(
+                    location=location,
                     selling_asset=selling_asset,
                     rate_in_profit_currency=rate_in_profit_currency,
                     total_fee_in_profit_currency=total_fee_in_profit_currency,
@@ -713,6 +729,7 @@ def search_buys_calculate_profit(
 
     def add_loan_gain(
             self,
+            location: Location,
             gained_asset: Asset,
             gained_amount: FVal,
             fee_in_asset: Fee,
@@ -751,6 +768,7 @@ def add_loan_gain(
             log.debug(
                 'Accounting for loan profit',
                 sensitive_log=True,
+                location=location,
                 gained_asset=gained_asset,
                 gained_amount=gained_amount,
                 gain_in_profit_currency=gain_in_profit_currency,
@@ -761,6 +779,7 @@ def add_loan_gain(
 
             self.loan_profit += gain_in_profit_currency
             self.csv_exporter.add_loan_profit(
+                location=location,
                 gained_asset=gained_asset,
                 gained_amount=gained_amount,
                 gain_in_profit_currency=gain_in_profit_currency,
@@ -834,6 +853,7 @@ def add_margin_position(self, margin: MarginPosition) -> None:
             )
 
             self.csv_exporter.add_margin_position(
+                location=margin.location,
                 margin_notes=margin.notes,
                 gain_loss_asset=margin.pl_currency,
                 gain_loss_amount=margin.profit_loss,
diff --git a/rotkehlchen/csv_exporter.py b/rotkehlchen/csv_exporter.py
--- a/rotkehlchen/csv_exporter.py
+++ b/rotkehlchen/csv_exporter.py
@@ -80,6 +80,7 @@ def reset_csv_lists(self) -> None:
     def add_to_allevents(
             self,
             event_type: EventType,
+            location: Location,
             paid_in_profit_currency: FVal,
             paid_asset: Union[Asset, EmptyStr],
             paid_in_asset: FVal,
@@ -118,6 +119,7 @@ def add_to_allevents(
         )
         entry = {
             'type': event_type,
+            'location': str(location),
             'paid_in_profit_currency': paid_in_profit_currency,
             'paid_asset': exported_paid_asset,
             'paid_in_asset': paid_in_asset,
@@ -147,6 +149,7 @@ def add_to_allevents(
 
     def add_buy(
             self,
+            location: Location,
             bought_asset: Asset,
             rate: FVal,
             fee_cost: Fee,
@@ -163,6 +166,7 @@ def add_buy(
         exchange_rate_key = f'exchanged_asset_{self.profit_currency.identifier}_exchange_rate'
         self.trades_csv.append({
             'type': 'buy',
+            'location': str(location),
             'asset': bought_asset.identifier,
             'price_in_{}'.format(self.profit_currency.identifier): rate,
             'fee_in_{}'.format(self.profit_currency.identifier): fee_cost,
@@ -179,6 +183,7 @@ def add_buy(
         })
         self.add_to_allevents(
             event_type=EV_BUY,
+            location=location,
             paid_in_profit_currency=cost,
             paid_asset=self.profit_currency,
             paid_in_asset=cost,
@@ -191,6 +196,7 @@ def add_buy(
 
     def add_sell(
             self,
+            location: Location,
             selling_asset: Asset,
             rate_in_profit_currency: FVal,
             total_fee_in_profit_currency: Fee,
@@ -223,6 +229,7 @@ def add_sell(
         taxable_profit_formula = '=IF(G{}=0,0,K{}-J{})'.format(row, row, row)
         self.trades_csv.append({
             'type': 'sell',
+            'location': str(location),
             'asset': selling_asset.identifier,
             f'price_in_{self.profit_currency.identifier}': rate_in_profit_currency,
             f'fee_in_{self.profit_currency.identifier}': total_fee_in_profit_currency,
@@ -240,6 +247,7 @@ def add_sell(
         paid_in_profit_currency = ZERO
         self.add_to_allevents(
             event_type=EV_SELL,
+            location=location,
             paid_in_profit_currency=paid_in_profit_currency,
             paid_asset=selling_asset,
             paid_in_asset=selling_amount,
@@ -254,6 +262,7 @@ def add_sell(
 
     def add_loan_settlement(
             self,
+            location: Location,
             asset: Asset,
             amount: FVal,
             rate_in_profit_currency: FVal,
@@ -267,6 +276,7 @@ def add_loan_settlement(
         loss_formula = '=B{}*C{}+D{}'.format(row, row, row)
         self.loan_settlements_csv.append({
             'asset': asset.identifier,
+            'location': str(location),
             'amount': amount,
             f'price_in_{self.profit_currency.identifier}': rate_in_profit_currency,
             f'fee_in_{self.profit_currency.identifier}': total_fee_in_profit_currency,
@@ -276,6 +286,7 @@ def add_loan_settlement(
         paid_in_profit_currency = amount * rate_in_profit_currency + total_fee_in_profit_currency
         self.add_to_allevents(
             event_type=EV_LOAN_SETTLE,
+            location=location,
             paid_in_profit_currency=paid_in_profit_currency,
             paid_asset=asset,
             paid_in_asset=amount,
@@ -287,6 +298,7 @@ def add_loan_settlement(
 
     def add_loan_profit(
             self,
+            location: Location,
             gained_asset: Asset,
             gained_amount: FVal,
             gain_in_profit_currency: FVal,
@@ -298,6 +310,7 @@ def add_loan_profit(
             return
 
         self.loan_profits_csv.append({
+            'location': str(location),
             'open_time': timestamp_to_date(open_time, formatstr='%d/%m/%Y %H:%M:%S'),
             'close_time': timestamp_to_date(close_time, formatstr='%d/%m/%Y %H:%M:%S'),
             'gained_asset': gained_asset.identifier,
@@ -306,6 +319,7 @@ def add_loan_profit(
             f'profit_in_{self.profit_currency.identifier}': gain_in_profit_currency,
         })
         self.add_to_allevents(
+            location=location,
             event_type=EV_INTEREST_PAYMENT,
             paid_in_profit_currency=FVal(0),
             paid_asset=S_EMPTYSTR,
@@ -318,6 +332,7 @@ def add_loan_profit(
 
     def add_margin_position(
             self,
+            location: Location,
             margin_notes: str,
             gain_loss_asset: Asset,
             gain_loss_amount: FVal,
@@ -332,6 +347,7 @@ def add_margin_position(
 
         self.margin_positions_csv.append({
             'name': margin_notes,
+            'location': str(location),
             'time': timestamp_to_date(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
             'gain_loss_asset': gain_loss_asset.identifier,
             'gain_loss_amount': gain_loss_amount,
@@ -339,6 +355,7 @@ def add_margin_position(
         })
         self.add_to_allevents(
             event_type=EV_MARGIN_CLOSE,
+            location=location,
             paid_in_profit_currency=FVal(0),
             paid_asset=S_EMPTYSTR,
             paid_in_asset=FVal(0),
@@ -370,6 +387,7 @@ def add_asset_movement(
         })
         self.add_to_allevents(
             event_type=EV_ASSET_MOVE,
+            location=exchange,
             paid_in_profit_currency=fee * rate,
             paid_asset=asset,
             paid_in_asset=fee,
@@ -396,6 +414,7 @@ def add_tx_gas_cost(
             f'cost_in_{self.profit_currency.identifier}': eth_burned_as_gas * rate,
         })
         self.add_to_allevents(
+            location=Location.BLOCKCHAIN,
             event_type=EV_TX_GAS_COST,
             paid_in_profit_currency=eth_burned_as_gas * rate,
             paid_asset=A_ETH,
@@ -439,6 +458,7 @@ def add_defi_event(self, event: DefiEvent, profit_loss_in_profit_currency: FVal)
 
         self.add_to_allevents(
             event_type=EV_DEFI,
+            location=Location.BLOCKCHAIN,
             paid_in_profit_currency=paid_in_profit_currency,
             paid_asset=paid_asset,
             paid_in_asset=paid_in_asset,


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
@@ -314,7 +314,8 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
-            assert len(row) == 14
+            assert len(row) == 15
+            assert row['location'] in ('kraken', 'bittrex', 'binance', 'poloniex')
             assert row['type'] in ('buy', 'sell')
             assert Asset(row['asset']).identifier is not None
             assert FVal(row[f'fee_in_{profit_currency.identifier}']) >= ZERO
@@ -341,7 +342,8 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
-            assert len(row) == 6
+            assert len(row) == 7
+            assert row['location'] == 'poloniex'
             assert create_timestamp(row['open_time'], '%d/%m/%Y %H:%M:%S') > 0
             assert create_timestamp(row['close_time'], '%d/%m/%Y %H:%M:%S') > 0
             assert Asset(row['gained_asset']).identifier is not None
@@ -384,7 +386,8 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
-            assert len(row) == 5
+            assert len(row) == 6
+            assert row['location'] == 'bitmex'
             assert row['name'] is not None
             assert create_timestamp(row['time'], '%d/%m/%Y %H:%M:%S') > 0
             assert Asset(row['gain_loss_asset']).identifier is not None
@@ -414,7 +417,15 @@ def assert_csv_export_response(response, profit_currency, csv_dir):
         reader = csv.DictReader(csvfile)
         count = 0
         for row in reader:
-            assert len(row) == 12
+            assert len(row) == 13
+            assert row['location'] in (
+                'kraken',
+                'bittrex',
+                'binance',
+                'poloniex',
+                'blockchain',
+                'bitmex',
+            )
             assert row['type'] in (
                 'buy',
                 'sell',