Repository: rotki/rotki

Issue:
Add an accounting setting to ignore past actions for cost basis calculation
## Abstract

At the moment when a user creates a tax report in a given time period, Rotki will always go through all previous actions (before the given time period) in order to be able to establish a cost basis for all assets.

While this is good default behavior and is the only way to provide accurate accounting results there can be use cases, where we don't want to do that and just want to focus completely on the given time period ignoring cost basis calculation from the past.

## Task

Introduce a new setting for accounting that will achieve this. It should be called `calculate_past_cost_basis` and its default value should be `True`.

When set to `False` then all past actions before the time period should be ignored completely and cost basis should only be calculated from the given time period.


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
@@ -318,8 +318,9 @@ def process_history(
                 (
                     should_continue,
                     prev_time,
-                ) = self.process_action(
+                ) = self._process_action(
                     action=action,
+                    start_ts=start_ts,
                     end_ts=end_ts,
                     prev_time=prev_time,
                     db_settings=db_settings,
@@ -406,9 +407,10 @@ def process_history(
             'all_events': self.csvexporter.all_events,
         }
 
-    def process_action(
+    def _process_action(
             self,
             action: TaxableAction,
+            start_ts: Timestamp,
             end_ts: Timestamp,
             prev_time: Timestamp,
             db_settings: DBSettings,
@@ -433,7 +435,12 @@ def process_action(
         )
         prev_time = timestamp
 
+        if not db_settings.calculate_past_cost_basis and timestamp < start_ts:
+            # ignore older actions than start_ts if we don't want past cost basis
+            return True, prev_time
+
         if timestamp > end_ts:
+            # reached the end of the time period for the report
             return False, prev_time
 
         self.currently_processing_timestamp = timestamp
diff --git a/rotkehlchen/api/v1/encoding.py b/rotkehlchen/api/v1/encoding.py
--- a/rotkehlchen/api/v1/encoding.py
+++ b/rotkehlchen/api/v1/encoding.py
@@ -703,6 +703,7 @@ class ModifiableSettingsSchema(Schema):
         ),
         missing=None,
     )
+    calculate_past_cost_basis = fields.Bool(missing=None)
 
     @validates_schema  # type: ignore
     def validate_settings_schema(  # pylint: disable=no-self-use
@@ -742,6 +743,7 @@ def transform_data(  # pylint: disable=no-self-use
             frontend_settings=data['frontend_settings'],
             account_for_assets_movements=data['account_for_assets_movements'],
             btc_derivation_gap_limit=data['btc_derivation_gap_limit'],
+            calculate_past_cost_basis=data['calculate_past_cost_basis'],
         )
 
 
diff --git a/rotkehlchen/db/settings.py b/rotkehlchen/db/settings.py
--- a/rotkehlchen/db/settings.py
+++ b/rotkehlchen/db/settings.py
@@ -26,6 +26,7 @@
 DEFAULT_ACTIVE_MODULES = AVAILABLE_MODULES
 DEFAULT_ACCOUNT_FOR_ASSETS_MOVEMENTS = True
 DEFAULT_BTC_DERIVATION_GAP_LIMIT = 20
+DEFAULT_CALCULATE_PAST_COST_BASIS = True
 
 
 class DBSettings(NamedTuple):
@@ -51,6 +52,7 @@ class DBSettings(NamedTuple):
     frontend_settings: str = ''
     account_for_assets_movements: bool = DEFAULT_ACCOUNT_FOR_ASSETS_MOVEMENTS
     btc_derivation_gap_limit: int = DEFAULT_BTC_DERIVATION_GAP_LIMIT
+    calculate_past_cost_basis: bool = DEFAULT_CALCULATE_PAST_COST_BASIS
 
 
 class ModifiableDBSettings(NamedTuple):
@@ -71,6 +73,7 @@ class ModifiableDBSettings(NamedTuple):
     frontend_settings: Optional[str] = None
     account_for_assets_movements: Optional[bool] = None
     btc_derivation_gap_limit: Optional[int] = None
+    calculate_past_cost_basis: Optional[bool] = None
 
     def serialize(self) -> Dict[str, Any]:
         settings_dict = {}
@@ -115,6 +118,7 @@ def read_boolean(value: Union[str, bool]) -> bool:
     'premium_should_sync',
     'submit_usage_analytics',
     'account_for_assets_movements',
+    'calculate_past_cost_basis',
 )
 INTEGER_KEYS = (
     'version',


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
@@ -27,6 +27,7 @@
     DEFAULT_MAIN_CURRENCY,
     DEFAULT_START_DATE,
     DEFAULT_UI_FLOATING_PRECISION,
+    DEFAULT_CALCULATE_PAST_COST_BASIS,
     ROTKEHLCHEN_DB_VERSION,
     DBSettings,
     ModifiableDBSettings,
@@ -279,6 +280,7 @@ def test_writing_fetching_data(data_dir, username):
         'frontend_settings': '',
         'account_for_assets_movements': DEFAULT_ACCOUNT_FOR_ASSETS_MOVEMENTS,
         'btc_derivation_gap_limit': DEFAULT_BTC_DERIVATION_GAP_LIMIT,
+        'calculate_past_cost_basis': DEFAULT_CALCULATE_PAST_COST_BASIS,
     }
     assert len(expected_dict) == len(DBSettings()), 'One or more settings are missing'
 
diff --git a/rotkehlchen/tests/unit/test_accounting.py b/rotkehlchen/tests/unit/test_accounting.py
--- a/rotkehlchen/tests/unit/test_accounting.py
+++ b/rotkehlchen/tests/unit/test_accounting.py
@@ -548,3 +548,62 @@ def test_assets_movements_not_accounted_for(accountant, expected):
     assert FVal(result['overview']['asset_movement_fees']).is_close(expected)
     assert FVal(result['overview']['total_taxable_profit_loss']).is_close(-expected)
     assert FVal(result['overview']['total_profit_loss']).is_close(-expected)
+
+
+@pytest.mark.parametrize('mocked_price_queries', [prices])
+@pytest.mark.parametrize('db_settings', [
+    {'calculate_past_cost_basis': False, 'taxfree_after_period': -1},
+    {'calculate_past_cost_basis': True, 'taxfree_after_period': -1},
+])
+def test_not_calculate_past_cost_basis(accountant, db_settings):
+    # trades copied from
+    # rotkehlchen/tests/integration/test_end_to_end_tax_report.py
+
+    history = [{
+        'timestamp': 1446979735,  # 08/11/2015
+        'pair': 'BTC_EUR',
+        'trade_type': 'buy',
+        'rate': 268.678317859,
+        'fee': 0,
+        'fee_currency': 'BTC',
+        'amount': 5,
+        'location': 'external',
+    }, {
+        'timestamp': 1446979735,  # 08/11/2015
+        'pair': 'ETH_EUR',
+        'trade_type': 'buy',
+        'rate': 0.2315893,
+        'fee': 0,
+        'fee_currency': 'ETH',
+        'amount': 1450,
+        'location': 'external',
+    }, {
+        'timestamp': 1488373504,  # 29/02/2017
+        'pair': 'BTC_EUR',
+        'trade_type': 'sell',
+        'rate': 1146.22,
+        'fee': 0.01,
+        'fee_currency': 'EUR',
+        'amount': 2,
+        'location': 'kraken',
+    }]
+    result = accounting_history_process(
+        accountant=accountant,
+        start_ts=1466979735,
+        end_ts=1519693374,
+        history_list=history,
+    )
+
+    sell_gain = (
+        FVal(history[-1]['rate']) * FVal(history[-1]['amount']) -
+        FVal(history[-1]['fee'])
+    )
+    if db_settings['calculate_past_cost_basis'] is True:
+        buy_cost = FVal(history[0]['rate']) * FVal(history[-1]['amount'])
+        expected = sell_gain - buy_cost
+    else:
+        expected = sell_gain
+
+    assert FVal(result['overview']['taxable_trade_profit_loss']).is_close(expected)
+    assert FVal(result['overview']['total_taxable_profit_loss']).is_close(expected)
+    assert FVal(result['overview']['total_profit_loss']).is_close(expected)