def add_loan_profit(
            self,
            gained_asset: typing.Asset,
            gained_amount: FVal,
            gain_in_profit_currency: FVal,
            lent_amount: FVal,
            open_time: typing.Timestamp,
            close_time: typing.Timestamp,
    ) -> None:
        if not self.create_csv:
            return

        self.loan_profits_csv.append({
            'open_time': tsToDate(open_time, formatstr='%d/%m/%Y %H:%M:%S'),
            'close_time': tsToDate(close_time, formatstr='%d/%m/%Y %H:%M:%S'),
            'gained_asset': gained_asset,
            'gained_amount': gained_amount,
            'lent_amount': lent_amount,
            'profit_in_{}'.format(self.profit_currency): gain_in_profit_currency
        })
        self.add_to_allevents(
            event_type=EV_INTEREST_PAYMENT,
            paid_in_profit_currency=FVal(0),
            paid_asset=S_EMPTYSTR,
            paid_in_asset=FVal(0),
            received_asset=gained_asset,
            received_in_asset=gained_amount,
            received_in_profit_currency=gain_in_profit_currency,
            timestamp=close_time,
        )
Exemple #2
0
    def query_fiat_pair(self, base: FiatAsset, quote: FiatAsset) -> FVal:
        if base == quote:
            return FVal(1.0)

        now = ts_now()
        date = tsToDate(ts_now(), formatstr='%Y-%m-%d')
        price = self._get_cached_forex_data(date, base, quote)
        if price:
            return price

        price = _query_exchanges_rateapi(base, quote)
        if not price:
            price = _query_currency_converterapi(base, quote)

        if not price:
            # Search the cache for any price in the last month
            for i in range(1, 31):
                now = Timestamp(now - Timestamp(86401))
                date = tsToDate(now, formatstr='%Y-%m-%d')
                price = self._get_cached_forex_data(date, base, quote)
                if price:
                    log.debug(
                        f'Could not query online apis for a fiat price. '
                        f'Used cached value from {i} days ago.',
                        base_currency=base,
                        quote_currency=quote,
                        price=price,
                    )
                    return price

            raise ValueError('Could not find a "{}" price for "{}"'.format(
                base, quote))

        self._save_forex_rate(date, base, quote, price)
        return price
Exemple #3
0
    def add_loan_settlement(
        self,
        asset,
        amount,
        rate_in_profit_currency,
        total_fee_in_profit_currency,
        timestamp,
    ):
        if not self.create_csv:
            return

        self.loan_settlements_csv.append({
            'asset':
            asset,
            "amount":
            amount,
            "price_in_{}".format(self.profit_currency):
            rate_in_profit_currency,
            "fee_in_{}".format(self.profit_currency):
            total_fee_in_profit_currency,
            "time":
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
        })
        self.add_to_allevents(
            event_type='loan_settlement',
            paid_in_profit_currency=amount * rate_in_profit_currency +
            total_fee_in_profit_currency,
            paid_asset=asset,
            paid_in_asset=amount,
            received_asset='',
            received_in_asset=0,
            received_in_profit_currency=0,
            timestamp=timestamp,
        )
Exemple #4
0
    def add_tx_gas_cost(
        self,
        transaction_hash: bytes,
        eth_burned_as_gas: FVal,
        rate: FVal,
        timestamp: typing.Timestamp,
    ) -> None:
        if not self.create_csv:
            return

        self.tx_gas_costs_csv.append({
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'transaction_hash':
            transaction_hash,
            'eth_burned_as_gas':
            eth_burned_as_gas,
            'cost_in_{}'.format(self.profit_currency):
            eth_burned_as_gas * rate,
        })
        self.add_to_allevents(
            event_type=EV_TX_GAS_COST,
            paid_in_profit_currency=eth_burned_as_gas * rate,
            paid_asset=S_ETH,
            paid_in_asset=eth_burned_as_gas,
            received_asset=S_EMPTYSTR,
            received_in_asset=FVal(0),
            taxable_received_in_profit_currency=FVal(0),
            timestamp=timestamp,
        )
Exemple #5
0
    def add_margin_position(
        self,
        margin_notes: str,
        gain_loss_asset: typing.Asset,
        net_gain_loss_amount: FVal,
        gain_loss_in_profit_currency: FVal,
        timestamp: typing.Timestamp,
    ) -> None:
        if not self.create_csv:
            return

        self.margin_positions_csv.append({
            'name':
            margin_notes,
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'gain_loss_asset':
            gain_loss_asset,
            'gain_loss_amount':
            net_gain_loss_amount,
            'profit_loss_in_{}'.format(self.profit_currency):
            gain_loss_in_profit_currency,
        })
        self.add_to_allevents(
            event_type=EV_MARGIN_CLOSE,
            paid_in_profit_currency=FVal(0),
            paid_asset=S_EMPTYSTR,
            paid_in_asset=FVal(0),
            received_asset=gain_loss_asset,
            received_in_asset=net_gain_loss_amount,
            taxable_received_in_profit_currency=gain_loss_in_profit_currency,
            timestamp=timestamp,
        )
Exemple #6
0
    def add_margin_position(
        self,
        margin_notes,
        gained_asset,
        net_gain_amount,
        gain_in_profit_currency,
        timestamp,
    ):
        if not self.create_csv:
            return

        self.margin_positions_csv.append({
            'name':
            margin_notes,
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'gained_asset':
            gained_asset,
            'gained_amount':
            net_gain_amount,
            'profit_in_{}'.format(self.profit_currency):
            gain_in_profit_currency
        })
        self.add_to_allevents(
            event_type='margin_position_close',
            paid_in_profit_currency=0,
            paid_asset='',
            paid_in_asset=0,
            received_asset=gained_asset,
            received_in_asset=net_gain_amount,
            received_in_profit_currency=gain_in_profit_currency,
            timestamp=timestamp,
        )
Exemple #7
0
    def add_tx_gas_cost(
        self,
        transaction_hash,
        eth_burned_as_gas,
        rate,
        timestamp,
    ):
        if not self.create_csv:
            return

        self.tx_gas_costs_csv.append({
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'transaction_hash':
            transaction_hash,
            'eth_burned_as_gas':
            eth_burned_as_gas,
            'cost_in_{}'.format(self.profit_currency):
            eth_burned_as_gas * rate,
        })
        self.add_to_allevents(
            event_type='tx_gas_cost',
            paid_in_profit_currency=eth_burned_as_gas * rate,
            paid_asset='ETH',
            paid_in_asset=eth_burned_as_gas,
            received_asset='',
            received_in_asset=0,
            received_in_profit_currency=0,
            timestamp=timestamp,
        )
    def add_asset_movement(
            self,
            exchange: str,
            category: str,
            asset: typing.Asset,
            fee: FVal,
            rate: FVal,
            timestamp: typing.Timestamp,
    ) -> None:
        if not self.create_csv:
            return

        self.asset_movements_csv.append({
            'time': tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'exchange': exchange,
            'type': category,
            'moving_asset': asset,
            'fee_in_asset': fee,
            'fee_in_{}'.format(self.profit_currency): fee * rate,
        })
        self.add_to_allevents(
            event_type=EV_ASSET_MOVE,
            paid_in_profit_currency=fee * rate,
            paid_asset=asset,
            paid_in_asset=fee,
            received_asset=S_EMPTYSTR,
            received_in_asset=FVal(0),
            received_in_profit_currency=FVal(0),
            timestamp=timestamp,
        )
    def add_loan_settlement(
            self,
            asset: typing.Asset,
            amount: FVal,
            rate_in_profit_currency: FVal,
            total_fee_in_profit_currency: FVal,
            timestamp: typing.Timestamp,
    ) -> None:
        if not self.create_csv:
            return

        self.loan_settlements_csv.append({
            'asset': asset,
            "amount": amount,
            "price_in_{}".format(self.profit_currency): rate_in_profit_currency,
            "fee_in_{}".format(self.profit_currency): total_fee_in_profit_currency,
            "time": tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
        })
        paid_in_profit_currency = amount * rate_in_profit_currency + total_fee_in_profit_currency
        self.add_to_allevents(
            event_type=EV_LOAN_SETTLE,
            paid_in_profit_currency=paid_in_profit_currency,
            paid_asset=asset,
            paid_in_asset=amount,
            received_asset=S_EMPTYSTR,
            received_in_asset=FVal(0),
            received_in_profit_currency=FVal(0),
            timestamp=timestamp,
        )
Exemple #10
0
    def add_buy(self,
                bought_asset,
                bought_amount,
                paid_with_asset,
                trade_rate,
                trade_fee,
                fee_currency,
                timestamp,
                is_virtual=False):

        paid_with_asset_rate = self.get_rate_in_profit_currency(
            paid_with_asset, timestamp)
        buy_rate = paid_with_asset_rate * trade_rate
        fee_price_in_profit_currency = 0
        if trade_fee != 0:
            fee_price_in_profit_currency = self.price_historian.query_historical_price(
                fee_currency, self.profit_currency, timestamp)

        self.handle_prefork_acquisitions(bought_asset=bought_asset,
                                         bought_amount=bought_amount,
                                         paid_with_asset=paid_with_asset,
                                         trade_rate=trade_rate,
                                         trade_fee=trade_fee,
                                         fee_currency=fee_currency,
                                         timestamp=timestamp)

        if bought_asset not in self.events:
            self.events[bought_asset] = Events(list(), list())

        fee_cost = fee_price_in_profit_currency * trade_fee
        gross_cost = bought_amount * buy_rate
        cost = gross_cost + fee_cost

        self.events[bought_asset].buys.append(
            BuyEvent(amount=bought_amount,
                     timestamp=timestamp,
                     rate=buy_rate,
                     fee_rate=fee_cost / bought_amount,
                     cost=cost))
        if logger.isEnabledFor(logging.DEBUG):
            logger.debug(
                'Buying {} "{}" for {} "{}" ({} "{}" per "{}" or {} "{}" per '
                '"{}") at {}'.format(
                    bought_amount, bought_asset, bought_amount * trade_rate,
                    paid_with_asset, trade_rate, paid_with_asset, bought_asset,
                    buy_rate, self.profit_currency, bought_asset,
                    tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S')))

        self.csv_exporter.add_buy(
            bought_asset=bought_asset,
            rate=buy_rate,
            fee_cost=fee_cost,
            amount=bought_amount,
            gross_cost=gross_cost,
            cost=cost,
            paid_with_asset=paid_with_asset,
            paid_with_asset_rate=paid_with_asset_rate,
            timestamp=timestamp,
            is_virtual=is_virtual,
        )
Exemple #11
0
    def add_to_allevents(
        self,
        event_type,
        paid_in_profit_currency,
        paid_asset,
        paid_in_asset,
        received_asset,
        received_in_asset,
        received_in_profit_currency,
        timestamp,
        is_virtual=False,
        taxable_amount='',
        taxable_bought_cost='',
    ):
        row = len(self.all_events_csv) + 2
        if event_type == 'buy':
            net_profit_or_loss = 0  # no profit by buying
            net_profit_or_loss_csv = 0  # no profit by buying
        elif event_type == 'sell':
            net_profit_or_loss = 0 if taxable_amount == 0 else received_in_asset - taxable_bought_cost
            net_profit_or_loss_csv = '=IF(E{}=0,0,H{}-F{})'.format(
                row, row, row)
        elif event_type in ('tx_gas_cost', 'asset_movement',
                            'loan_settlement'):
            net_profit_or_loss = paid_in_profit_currency
            net_profit_or_loss_csv = '=-B{}'.format(row)
        elif event_type in ('interest_rate_payment', 'margin_position_close'):
            net_profit_or_loss = received_in_profit_currency
            net_profit_or_loss_csv = '=H{}'.format(row)
        else:
            raise ValueError(
                'Illegal event type "{}" at add_to_allevents'.format(
                    event_type))

        entry = {
            'type': event_type,
            'paid_in_profit_currency': paid_in_profit_currency,
            'paid_asset': paid_asset,
            'paid_in_asset': paid_in_asset,
            'taxable_amount': taxable_amount,
            'taxable_bought_cost': taxable_bought_cost,
            'received_asset': received_asset,
            'received_in_profit_currency': received_in_profit_currency,
            'received_in_asset': received_in_asset,
            'net_profit_or_loss': net_profit_or_loss,
            'time': timestamp,
            'is_virtual': is_virtual
        }
        self.all_events.append(entry)
        new_entry = entry.copy()
        new_entry['net_profit_or_loss'] = net_profit_or_loss_csv
        new_entry['time'] = tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
        new_entry['paid_in_{}'.format(
            self.profit_currency)] = paid_in_profit_currency
        new_entry['received_in_{}'.format(
            self.profit_currency)] = received_in_profit_currency
        del new_entry['paid_in_profit_currency']
        del new_entry['received_in_profit_currency']
        self.all_events_csv.append(new_entry)
Exemple #12
0
    def add_buy(
        self,
        bought_asset: typing.Asset,
        rate: FVal,
        fee_cost: typing.Fee,
        amount: FVal,
        gross_cost: FVal,
        cost: FVal,
        paid_with_asset: typing.Asset,
        paid_with_asset_rate: FVal,
        timestamp: typing.Timestamp,
        is_virtual: bool,
    ) -> None:
        if not self.create_csv:
            return

        exchange_rate_key = f'exchanged_asset_{self.profit_currency}_exchange_rate'
        self.trades_csv.append({
            'type':
            'buy',
            'asset':
            bought_asset,
            'price_in_{}'.format(self.profit_currency):
            rate,
            'fee_in_{}'.format(self.profit_currency):
            fee_cost,
            'gained_or_invested_{}'.format(self.profit_currency):
            cost,
            'amount':
            amount,
            'taxable_amount':
            'not applicable',  # makes no difference for buying
            'exchanged_for':
            paid_with_asset,
            exchange_rate_key:
            paid_with_asset_rate,
            'taxable_bought_cost_in_{}'.format(self.profit_currency):
            'not applicable',
            'taxable_gain_in_{}'.format(self.profit_currency):
            FVal(0),
            'taxable_profit_loss_in_{}'.format(self.profit_currency):
            FVal(0),
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'is_virtual':
            is_virtual,
        })
        self.add_to_allevents(
            event_type=EV_BUY,
            paid_in_profit_currency=cost,
            paid_asset=self.profit_currency,
            paid_in_asset=cost,
            received_asset=bought_asset,
            received_in_asset=amount,
            taxable_received_in_profit_currency=FVal(0),
            timestamp=timestamp,
            is_virtual=is_virtual,
        )
Exemple #13
0
    def add_to_allevents(
            self,
            event_type: typing.EventType,
            paid_in_profit_currency: FVal,
            paid_asset: Union[typing.Asset, typing.EmptyStr],
            paid_in_asset: FVal,
            received_asset: Union[typing.Asset, typing.EmptyStr],
            received_in_asset: FVal,
            received_in_profit_currency: FVal,
            timestamp: typing.Timestamp,
            is_virtual: bool = False,
            taxable_amount: FVal = FVal(0),
            taxable_bought_cost: FVal = FVal(0),
    ) -> None:
        row = len(self.all_events_csv) + 2
        if event_type == EV_BUY:
            net_profit_or_loss = FVal(0)  # no profit by buying
            net_profit_or_loss_csv = '0'  # no profit by buying
        elif event_type == EV_SELL:
            if taxable_amount == 0:
                net_profit_or_loss = FVal(0)
            else:
                net_profit_or_loss = received_in_asset - taxable_bought_cost
            net_profit_or_loss_csv = '=IF(E{}=0,0,H{}-F{})'.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 = '=-B{}'.format(row)
        elif event_type in (EV_INTEREST_PAYMENT, EV_MARGIN_CLOSE):
            net_profit_or_loss = received_in_profit_currency
            net_profit_or_loss_csv = '=H{}'.format(row)
        else:
            raise ValueError('Illegal event type "{}" at add_to_allevents'.format(event_type))

        entry = {
            'type': event_type,
            'paid_in_profit_currency': paid_in_profit_currency,
            'paid_asset': paid_asset,
            'paid_in_asset': paid_in_asset,
            'taxable_amount': taxable_amount,
            'taxable_bought_cost': taxable_bought_cost,
            'received_asset': received_asset,
            'received_in_profit_currency': received_in_profit_currency,
            'received_in_asset': received_in_asset,
            'net_profit_or_loss': net_profit_or_loss,
            'time': timestamp,
            'is_virtual': is_virtual
        }
        log.debug('csv event', **make_sensitive(entry))
        self.all_events.append(entry)
        new_entry = entry.copy()
        new_entry['net_profit_or_loss'] = net_profit_or_loss_csv
        new_entry['time'] = tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S')
        new_entry['paid_in_{}'.format(self.profit_currency)] = paid_in_profit_currency
        new_entry['received_in_{}'.format(self.profit_currency)] = received_in_profit_currency
        del new_entry['paid_in_profit_currency']
        del new_entry['received_in_profit_currency']
        self.all_events_csv.append(new_entry)
Exemple #14
0
    def handle_prefork_asset_sells(
        self,
        sold_asset: Asset,
        sold_amount: FVal,
        timestamp: Timestamp,
    ) -> None:
        if sold_asset == S_ETH and timestamp < ETH_DAO_FORK_TS:
            if not self.reduce_asset_amount(asset=S_ETC, amount=sold_amount):
                log.critical(
                    'No documented buy found for ETC (ETH equivalent) before {}'
                    .format(tsToDate(timestamp,
                                     formatstr='%d/%m/%Y %H:%M:%S'), ), )

        if sold_asset == S_BTC and timestamp < BTC_BCH_FORK_TS:
            if not self.reduce_asset_amount(asset=S_BCH, amount=sold_amount):
                log.critical(
                    'No documented buy found for BCH (BTC equivalent) before {}'
                    .format(tsToDate(timestamp,
                                     formatstr='%d/%m/%Y %H:%M:%S'), ), )
Exemple #15
0
def test_fallback_to_cached_values_within_a_month(inquirer):
    def mock_api_remote_fail(uri):
        return MockResponse(500, '{"msg": "shit hit the fan"')

    # Get a date 15 days ago and insert a cached entry for EUR JPY then
    now = ts_now()
    eurjpy_val = FVal('124.123')
    date = tsToDate(now - 86400 * 15, formatstr='%Y-%m-%d')
    inquirer._save_forex_rate(date, 'EUR', 'JPY', eurjpy_val)
    # Get a date 31 days ago and insert a cache entry for EUR CNY then
    date = tsToDate(now - 86400 * 31, formatstr='%Y-%m-%d')
    inquirer._save_forex_rate(date, 'EUR', 'CNY', FVal('7.719'))

    with patch('requests.get', side_effect=mock_api_remote_fail):
        # We fail to find a response but then go back 15 days and find the cached response
        result = inquirer.query_fiat_pair('EUR', 'JPY')
        assert result == eurjpy_val
        # The cached response for EUR CNY is too old so we will fail here
        with pytest.raises(ValueError):
            result = inquirer.query_fiat_pair('EUR', 'CNY')
Exemple #16
0
    def add_sell(
            self,
            selling_asset: typing.Asset,
            rate_in_profit_currency: FVal,
            total_fee_in_profit_currency: FVal,
            gain_in_profit_currency: FVal,
            selling_amount: FVal,
            receiving_asset: typing.Asset,
            receiving_amount: FVal,
            receiving_asset_rate_in_profit_currency: FVal,
            taxable_amount: FVal,
            taxable_bought_cost: FVal,
            timestamp: typing.Timestamp,
            is_virtual: bool,
    ):
        if not self.create_csv:
            return

        gross_key = 'gross_gained_or_invested_{}'.format(self.profit_currency)
        self.trades_csv.append({
            'type': 'sell',
            'asset': selling_asset,
            'price_in_{}'.format(self.profit_currency): rate_in_profit_currency,
            'fee_in_{}'.format(self.profit_currency): total_fee_in_profit_currency,
            gross_key: gain_in_profit_currency + total_fee_in_profit_currency,
            'net_gained_or_invested_{}'.format(self.profit_currency): gain_in_profit_currency,
            'amount': selling_amount,
            'exchanged_for': receiving_asset,
            'exchanged_asset_euro_exchange_rate': receiving_asset_rate_in_profit_currency,
            'time': tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'is_virtual': is_virtual,
        })
        paid_in_profit_currency = (
            selling_amount * rate_in_profit_currency + total_fee_in_profit_currency
        )
        self.add_to_allevents(
            event_type=EV_SELL,
            paid_in_profit_currency=paid_in_profit_currency,
            paid_asset=selling_asset,
            paid_in_asset=selling_amount,
            received_asset=receiving_asset,
            received_in_asset=receiving_amount,
            received_in_profit_currency=taxable_gain_for_sell(
                taxable_amount=taxable_amount,
                rate_in_profit_currency=rate_in_profit_currency,
                total_fee_in_profit_currency=total_fee_in_profit_currency,
                selling_amount=selling_amount,
            ),
            timestamp=timestamp,
            is_virtual=is_virtual,
            taxable_amount=taxable_amount,
            taxable_bought_cost=taxable_bought_cost,
        )
Exemple #17
0
    def query_historical_fiat_exchange_rates(
        self,
        from_currency: FiatAsset,
        to_currency: FiatAsset,
        timestamp: Timestamp,
    ) -> Optional[FVal]:
        date = tsToDate(timestamp, formatstr='%Y-%m-%d')
        rate = self._get_cached_forex_data(date, from_currency, to_currency)
        if rate:
            return rate

        log.debug(
            'Querying exchangeratesapi',
            from_currency=from_currency,
            to_currency=to_currency,
            timestamp=timestamp,
        )

        query_str = (f'https://api.exchangeratesapi.io/{date}?'
                     f'base={from_currency}')
        resp = retry_calls(
            5,
            'query_exchangeratesapi',
            'requests.get',
            requests.get,
            query_str,
        )

        if resp.status_code != 200:
            return None

        try:
            result = rlk_jsonloads_dict(resp.text)
        except JSONDecodeError:
            return None

        if 'rates' not in result or to_currency not in result['rates']:
            return None

        if date not in self.cached_forex_data:
            self.cached_forex_data[date] = {}

        if from_currency not in self.cached_forex_data[date]:
            self.cached_forex_data[date][from_currency] = {}

        for key, value in result['rates'].items():
            self.cached_forex_data[date][from_currency][key] = FVal(value)

        rate = FVal(result['rates'][to_currency])
        log.debug('Exchangeratesapi query succesful', rate=rate)
        return rate
Exemple #18
0
    def add_buy(
        self,
        bought_asset,
        rate,
        fee_cost,
        amount,
        gross_cost,
        cost,
        paid_with_asset,
        paid_with_asset_rate,
        timestamp,
        is_virtual,
    ):
        if not self.create_csv:
            return

        self.trades_csv.append({
            'type':
            'buy',
            'asset':
            bought_asset,
            "price_in_{}".format(self.profit_currency):
            rate,
            "fee_in_{}".format(self.profit_currency):
            fee_cost,
            "amount":
            amount,
            "gross_gained_or_invested_{}".format(self.profit_currency):
            gross_cost,
            "net_gained_or_invested_{}".format(self.profit_currency):
            cost,
            "exchanged_for":
            paid_with_asset,
            "exchanged_asset_euro_exchange_rate":
            paid_with_asset_rate,
            "time":
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            "is_virtual":
            is_virtual
        })
        self.add_to_allevents(event_type='buy',
                              paid_in_profit_currency=cost,
                              paid_asset=self.profit_currency,
                              paid_in_asset=cost,
                              received_asset=bought_asset,
                              received_in_asset=amount,
                              received_in_profit_currency=0,
                              timestamp=timestamp,
                              is_virtual=is_virtual)
Exemple #19
0
    def add_loan_profit(
        self,
        gained_asset,
        gained_amount,
        gain_in_profit_currency,
        lent_amount,
        open_time,
        close_time,
    ):
        if not self.create_csv:
            return

        self.loan_profits_csv.append({
            'open_time':
            tsToDate(open_time, formatstr='%d/%m/%Y %H:%M:%S'),
            'close_time':
            tsToDate(close_time, formatstr='%d/%m/%Y %H:%M:%S'),
            'gained_asset':
            gained_asset,
            'gained_amount':
            gained_amount,
            'lent_amount':
            lent_amount,
            'profit_in_{}'.format(self.profit_currency):
            gain_in_profit_currency
        })
        self.add_to_allevents(
            event_type='interest_rate_payment',
            paid_in_profit_currency=0,
            paid_asset='',
            paid_in_asset=0,
            received_asset=gained_asset,
            received_in_asset=gained_amount,
            received_in_profit_currency=gain_in_profit_currency,
            timestamp=close_time,
        )
Exemple #20
0
    def add_loan_settlement(
        self,
        asset: typing.Asset,
        amount: FVal,
        rate_in_profit_currency: FVal,
        total_fee_in_profit_currency: FVal,
        timestamp: typing.Timestamp,
    ) -> None:
        if not self.create_csv:
            return

        row = len(self.loan_settlements_csv) + 2
        loss_formula = '=B{}*C{}+D{}'.format(row, row, row)
        self.loan_settlements_csv.append({
            'asset':
            asset,
            'amount':
            amount,
            'price_in_{}'.format(self.profit_currency):
            rate_in_profit_currency,
            'fee_in_{}'.format(self.profit_currency):
            total_fee_in_profit_currency,
            'loss_in_{}'.format(self.profit_currency):
            loss_formula,
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
        })
        paid_in_profit_currency = amount * rate_in_profit_currency + total_fee_in_profit_currency
        self.add_to_allevents(
            event_type=EV_LOAN_SETTLE,
            paid_in_profit_currency=paid_in_profit_currency,
            paid_asset=asset,
            paid_in_asset=amount,
            received_asset=S_EMPTYSTR,
            received_in_asset=FVal(0),
            taxable_received_in_profit_currency=FVal(0),
            timestamp=timestamp,
        )
Exemple #21
0
    def add_asset_movement(
        self,
        exchange,
        category,
        asset,
        fee,
        rate,
        timestamp,
    ):
        if not self.create_csv:
            return

        self.asset_movements_csv.append({
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'exchange':
            exchange,
            'type':
            category,
            'moving_asset':
            asset,
            'fee_in_asset':
            fee,
            'fee_in_{}'.format(self.profit_currency):
            fee * rate,
        })
        self.add_to_allevents(
            event_type='asset_movement',
            paid_in_profit_currency=fee * rate,
            paid_asset=asset,
            paid_in_asset=fee,
            received_asset='',
            received_in_asset=0,
            received_in_profit_currency=0,
            timestamp=timestamp,
        )
Exemple #22
0
    def query_historical_price(self, from_asset, to_asset, timestamp):
        """
        Query the historical price on `timestamp` for `from_asset` in `to_asset`.
        So how much `to_asset` does 1 unit of `from_asset` cost.

        Args:
            from_asset (str): The ticker symbol of the asset for which we want to know
                              the price.
            to_asset (str): The ticker symbol of the asset against which we want to
                            know the price.
            timestamp (int): The timestamp at which to query the price
        """
        if from_asset == to_asset:
            return 1

        if from_asset not in self.cryptocompare_coin_list:
            raise PriceQueryUnknownFromAsset(from_asset)

        data = self.get_historical_data(from_asset, to_asset, timestamp)

        # all data are sorted and timestamps are always increasing by 1 hour
        # find the closest entry to the provided timestamp
        # print("loaded {}_{}".format(from_asset, to_asset))
        assert timestamp > data[0]['time']
        index = convert_to_int((timestamp - data[0]['time']) / 3600, accept_only_exact=False)
        # print("timestamp: {} index: {} data_length: {}".format(timestamp, index, len(data)))
        diff = abs(data[index]['time'] - timestamp)
        if index + 1 <= len(data) - 1:
            diff_p1 = abs(data[index + 1]['time'] - timestamp)
            if diff_p1 < diff:
                index = index + 1

        if data[index]['high'] is None or data[index]['low'] is None:
            # If we get some None in the hourly set price to 0 so that we check daily price
            price = FVal(0)
        else:
            price = FVal((data[index]['high'] + data[index]['low'])) / 2

        if price == 0:
            if from_asset != 'BTC' and to_asset != 'BTC':
                # Just get the BTC price
                asset_btc_price = self.query_historical_price(from_asset, 'BTC', timestamp)
                btc_to_asset_price = self.query_historical_price('BTC', to_asset, timestamp)
                price = asset_btc_price * btc_to_asset_price
            else:
                # attempt to get the daily price by timestamp
                query_string = (
                    'https://min-api.cryptocompare.com/data/pricehistorical?'
                    'fsym={}&tsyms={}&ts={}'.format(
                        from_asset, to_asset, timestamp
                    ))
                if to_asset == 'BTC':
                    query_string += '&tryConversion=false'
                resp = urlopen(Request(query_string))
                resp = rlk_jsonloads(resp.read())
                print('DAILY PRICE OF ASSET: "{}"'.format(resp))
                if from_asset not in resp:
                    error_message = 'Failed to query cryptocompare for: "{}"'.format(query_string)
                    raise ValueError(error_message)
                price = FVal(resp[from_asset][to_asset])

                if price == 0:
                    raise NoPriceForGivenTimestamp(
                        from_asset,
                        to_asset,
                        tsToDate(timestamp, formatstr='%d/%m/%Y, %H:%M:%S')
                    )

        return price
Exemple #23
0
    def add_sell(
        self,
        selling_asset: typing.Asset,
        rate_in_profit_currency: FVal,
        total_fee_in_profit_currency: typing.Fee,
        gain_in_profit_currency: FVal,
        selling_amount: FVal,
        receiving_asset: typing.Asset,
        receiving_amount: FVal,
        receiving_asset_rate_in_profit_currency: FVal,
        taxable_amount: FVal,
        taxable_bought_cost: FVal,
        timestamp: typing.Timestamp,
        is_virtual: bool,
    ):
        if not self.create_csv:
            return

        exchange_rate_key = f'exchanged_asset_{self.profit_currency}_exchange_rate'
        taxable_profit_received = taxable_gain_for_sell(
            taxable_amount=taxable_amount,
            rate_in_profit_currency=rate_in_profit_currency,
            total_fee_in_profit_currency=total_fee_in_profit_currency,
            selling_amount=selling_amount,
        )
        row = len(self.trades_csv) + 2
        taxable_profit_formula = '=IF(G{}=0,0,K{}-J{})'.format(row, row, row)
        self.trades_csv.append({
            'type':
            'sell',
            'asset':
            selling_asset,
            'price_in_{}'.format(self.profit_currency):
            rate_in_profit_currency,
            'fee_in_{}'.format(self.profit_currency):
            total_fee_in_profit_currency,
            'gained_or_invested_{}'.format(self.profit_currency):
            gain_in_profit_currency,
            'amount':
            selling_amount,
            'taxable_amount':
            taxable_amount,
            'exchanged_for':
            receiving_asset,
            exchange_rate_key:
            receiving_asset_rate_in_profit_currency,
            'taxable_bought_cost_in_{}'.format(self.profit_currency):
            taxable_bought_cost,
            'taxable_gain_in_{}'.format(self.profit_currency):
            taxable_profit_received,
            'taxable_profit_loss_in_{}'.format(self.profit_currency):
            taxable_profit_formula,
            'time':
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            'is_virtual':
            is_virtual,
        })
        paid_in_profit_currency = ZERO
        self.add_to_allevents(
            event_type=EV_SELL,
            paid_in_profit_currency=paid_in_profit_currency,
            paid_asset=selling_asset,
            paid_in_asset=selling_amount,
            received_asset=receiving_asset,
            received_in_asset=receiving_amount,
            taxable_received_in_profit_currency=taxable_profit_received,
            timestamp=timestamp,
            is_virtual=is_virtual,
            taxable_amount=taxable_amount,
            taxable_bought_cost=taxable_bought_cost,
        )
Exemple #24
0
    def query_historical_price(self, from_asset, to_asset, timestamp):
        """
        Query the historical price on `timestamp` for `from_asset` in `to_asset`.
        So how much `to_asset` does 1 unit of `from_asset` cost.

        Args:
            from_asset (str): The ticker symbol of the asset for which we want to know
                              the price.
            to_asset (str): The ticker symbol of the asset against which we want to
                            know the price.
            timestamp (int): The timestamp at which to query the price
        """
        log.debug(
            'Querying historical price',
            from_asset=from_asset,
            to_asset=to_asset,
            timestamp=timestamp,
        )
        if from_asset == to_asset:
            return 1

        if from_asset not in self.cryptocompare_coin_list:
            raise PriceQueryUnknownFromAsset(from_asset)

        data = self.get_historical_data(from_asset, to_asset, timestamp)

        # all data are sorted and timestamps are always increasing by 1 hour
        # find the closest entry to the provided timestamp
        assert timestamp > data[0]['time']

        index = convert_to_int((timestamp - data[0]['time']) / 3600,
                               accept_only_exact=False)
        # print("timestamp: {} index: {} data_length: {}".format(timestamp, index, len(data)))
        diff = abs(data[index]['time'] - timestamp)
        if index + 1 <= len(data) - 1:
            diff_p1 = abs(data[index + 1]['time'] - timestamp)
            if diff_p1 < diff:
                index = index + 1

        if data[index]['high'] is None or data[index]['low'] is None:
            # If we get some None in the hourly set price to 0 so that we check daily price
            price = FVal(0)
        else:
            price = FVal((data[index]['high'] + data[index]['low'])) / 2

        if price == 0:
            if from_asset != 'BTC' and to_asset != 'BTC':
                log.debug(
                    f"Coudn't find historical price from {from_asset} to "
                    f"{to_asset}. Comparing with BTC...", )
                # Just get the BTC price
                asset_btc_price = self.query_historical_price(
                    from_asset, 'BTC', timestamp)
                btc_to_asset_price = self.query_historical_price(
                    'BTC', to_asset, timestamp)
                price = asset_btc_price * btc_to_asset_price
            else:
                log.debug(
                    f"Coudn't find historical price from {from_asset} to "
                    f"{to_asset}. Attempting to get daily price...", )
                # attempt to get the daily price by timestamp
                cc_from_asset = world_to_cryptocompare(from_asset)
                cc_to_asset = world_to_cryptocompare(to_asset)
                log.debug(
                    'Querying cryptocompare for daily historical price',
                    from_asset=from_asset,
                    to_asset=to_asset,
                    timestamp=timestamp,
                )
                query_string = (
                    'https://min-api.cryptocompare.com/data/pricehistorical?'
                    'fsym={}&tsyms={}&ts={}'.format(
                        cc_from_asset,
                        cc_to_asset,
                        timestamp,
                    ))
                if to_asset == 'BTC':
                    query_string += '&tryConversion=false'
                resp = request_get(query_string)

                if cc_from_asset not in resp:
                    error_message = 'Failed to query cryptocompare for: "{}"'.format(
                        query_string)
                    log.error(
                        'Cryptocompare query for daily historical price failed',
                        from_asset=from_asset,
                        to_asset=to_asset,
                        timestamp=timestamp,
                        error=error_message,
                    )
                    raise ValueError(error_message)

                price = FVal(resp[cc_from_asset][cc_to_asset])

                if price == 0:
                    raise NoPriceForGivenTimestamp(
                        from_asset, to_asset,
                        tsToDate(timestamp, formatstr='%d/%m/%Y, %H:%M:%S'))

        log.debug('Got historical price',
                  from_asset=from_asset,
                  to_asset=to_asset,
                  timestamp=timestamp,
                  price=price)
        return price
Exemple #25
0
    def add_sell(
        self,
        selling_asset,
        rate_in_profit_currency,
        total_fee_in_profit_currency,
        gain_in_profit_currency,
        selling_amount,
        receiving_asset,
        receiving_amount,
        receiving_asset_rate_in_profit_currency,
        taxable_amount,
        taxable_bought_cost,
        timestamp,
        is_virtual,
    ):
        if not self.create_csv:
            return

        self.trades_csv.append({
            'type':
            'sell',
            'asset':
            selling_asset,
            "price_in_{}".format(self.profit_currency):
            rate_in_profit_currency,
            "fee_in_{}".format(self.profit_currency):
            total_fee_in_profit_currency,
            "gross_gained_or_invested_{}".format(self.profit_currency):
            gain_in_profit_currency + total_fee_in_profit_currency,
            "net_gained_or_invested_{}".format(self.profit_currency):
            gain_in_profit_currency,
            "amount":
            selling_amount,
            "exchanged_for":
            receiving_asset,
            "exchanged_asset_euro_exchange_rate":
            receiving_asset_rate_in_profit_currency,
            "time":
            tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
            "is_virtual":
            is_virtual,
        })
        self.add_to_allevents(
            event_type='sell',
            paid_in_profit_currency=selling_amount * rate_in_profit_currency +
            total_fee_in_profit_currency,
            paid_asset=selling_asset,
            paid_in_asset=selling_amount,
            received_asset=receiving_asset,
            received_in_asset=receiving_amount,
            received_in_profit_currency=taxable_gain_for_sell(
                taxable_amount=taxable_amount,
                rate_in_profit_currency=rate_in_profit_currency,
                total_fee_in_profit_currency=total_fee_in_profit_currency,
                selling_amount=selling_amount,
            ),
            timestamp=timestamp,
            is_virtual=is_virtual,
            taxable_amount=taxable_amount,
            taxable_bought_cost=taxable_bought_cost,
        )
Exemple #26
0
    def search_buys_calculate_profit(
        self,
        selling_amount: FVal,
        selling_asset: Asset,
        timestamp: Timestamp,
    ) -> Tuple[FVal, FVal, FVal]:
        """
        When selling `selling_amount` of `selling_asset` at `timestamp` this function
        calculates using the first-in-first-out rule the corresponding buy/s from
        which to do profit calculation. Also applies the one year rule after which
        a sell is not taxable in Germany.

        Returns a tuple of 3 values:
            - `taxable_amount`: The amount out of `selling_amount` that is taxable,
                                calculated from the 1 year rule.
            - `taxable_bought_cost`: How much it cost in `profit_currency` to buy
                                     the `taxable_amount`
            - `taxfree_bought_cost`: How much it cost in `profit_currency` to buy
                                     the taxfree_amount (selling_amount - taxable_amount)
        """
        remaining_sold_amount = selling_amount
        stop_index = -1
        taxfree_bought_cost = FVal(0)
        taxable_bought_cost = FVal(0)
        taxable_amount = FVal(0)
        taxfree_amount = FVal(0)
        remaining_amount_from_last_buy = -1
        for idx, buy_event in enumerate(self.events[selling_asset].buys):
            if self.taxfree_after_period is None:
                at_taxfree_period = False
            else:
                at_taxfree_period = (buy_event.timestamp +
                                     self.taxfree_after_period < timestamp)

            if remaining_sold_amount < buy_event.amount:
                stop_index = idx
                buying_cost = remaining_sold_amount.fma(
                    buy_event.rate,
                    (buy_event.fee_rate * remaining_sold_amount),
                )

                if at_taxfree_period:
                    taxfree_amount += remaining_sold_amount
                    taxfree_bought_cost += buying_cost
                else:
                    taxable_amount += remaining_sold_amount
                    taxable_bought_cost += buying_cost

                remaining_amount_from_last_buy = buy_event.amount - remaining_sold_amount
                log.debug(
                    'Sell uses up part of historical buy',
                    sensitive_log=True,
                    tax_status='TAX-FREE' if at_taxfree_period else 'TAXABLE',
                    used_amount=remaining_sold_amount,
                    from_amount=buy_event.amount,
                    asset=selling_asset,
                    trade_buy_rate=buy_event.rate,
                    profit_currency=self.profit_currency,
                    trade_timestamp=buy_event.timestamp,
                )
                # stop iterating since we found all buys to satisfy this sell
                break
            else:
                buying_cost = buy_event.amount.fma(
                    buy_event.rate,
                    (buy_event.fee_rate * buy_event.amount),
                )
                remaining_sold_amount -= buy_event.amount
                if at_taxfree_period:
                    taxfree_amount += buy_event.amount
                    taxfree_bought_cost += buying_cost
                else:
                    taxable_amount += buy_event.amount
                    taxable_bought_cost += buying_cost

                log.debug(
                    'Sell uses up entire historical buy',
                    sensitive_log=True,
                    tax_status='TAX-FREE' if at_taxfree_period else 'TAXABLE',
                    bought_amount=buy_event.amount,
                    asset=selling_asset,
                    trade_buy_rate=buy_event.rate,
                    profit_currency=self.profit_currency,
                    trade_timestamp=buy_event.timestamp,
                )

                # If the sell used up the last historical buy
                if idx == len(self.events[selling_asset].buys) - 1:
                    stop_index = idx + 1

        if len(self.events[selling_asset].buys) == 0:
            log.critical(
                'No documented buy found for "{}" before {}'.format(
                    selling_asset,
                    tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
                ), )
            # That means we had no documented buy for that asset. This is not good
            # because we can't prove a corresponding buy and as such we are burdened
            # calculating the entire sell as profit which needs to be taxed
            return selling_amount, FVal(0), FVal(0)

        # Otherwise, delete all the used up buys from the list
        del self.events[selling_asset].buys[:stop_index]
        # and modify the amount of the buy where we stopped if there is one
        if remaining_amount_from_last_buy != -1:
            self.events[selling_asset].buys[
                0].amount = remaining_amount_from_last_buy
        elif remaining_sold_amount != ZERO:
            # if we still have sold amount but no buys to satisfy it then we only
            # found buys to partially satisfy the sell
            adjusted_amount = selling_amount - taxfree_amount
            log.critical(
                'Not enough documented buys found for "{}" before {}.'
                'Only found buys for {} {}'.format(
                    selling_asset,
                    tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S'),
                    taxable_amount + taxfree_amount,
                    selling_asset,
                ), )
            return adjusted_amount, taxable_bought_cost, taxfree_bought_cost

        return taxable_amount, taxable_bought_cost, taxfree_bought_cost
Exemple #27
0
    def search_buys_calculate_profit(self, selling_amount, selling_asset,
                                     timestamp):
        """
        When selling `selling_amount` of `selling_asset` at `timestamp` this function
        calculates using the first-in-first-out rule the corresponding buy/s from
        which to do profit calculation. Also applies the one year rule after which
        a sell is not taxable in Germany.

        Returns a tuple of 3 values:
            - `taxable_amount`: The amount out of `selling_amount` that is taxable,
                                calculated from the 1 year rule.
            - `taxable_bought_cost`: How much it cost in `profit_currency` to buy
                                     the `taxable_amount`
            - `taxfree_bought_cost`: How much it cost in `profit_currency` to buy
                                     the taxfree_amount (selling_amount - taxable_amount)
        """
        remaining_sold_amount = selling_amount
        stop_index = -1
        taxfree_bought_cost = 0
        taxable_bought_cost = 0
        taxable_amount = 0
        taxfree_amount = 0
        debug_enabled = logger.isEnabledFor(logging.DEBUG)
        for idx, buy_event in enumerate(self.events[selling_asset].buys):
            if self.taxfree_after_period is None:
                at_taxfree_period = False
            else:
                at_taxfree_period = (buy_event.timestamp +
                                     self.taxfree_after_period < timestamp)

            if remaining_sold_amount < buy_event.amount:
                stop_index = idx
                buying_cost = remaining_sold_amount.fma(
                    buy_event.rate,
                    -(buy_event.fee_rate * remaining_sold_amount))

                if at_taxfree_period:
                    taxfree_amount += remaining_sold_amount
                    taxfree_bought_cost += buying_cost
                else:
                    taxable_amount += remaining_sold_amount
                    taxable_bought_cost += buying_cost

                remaining_amount_from_last_buy = buy_event.amount - remaining_sold_amount
                if debug_enabled:
                    logger.debug(
                        '[{}] Using up {}/{} "{}" from the buy for {} "{}" per "{}"  at {}'
                        .format(
                            'TAX-FREE' if at_taxfree_period else 'TAXABLE',
                            remaining_sold_amount, buy_event.amount,
                            selling_asset, buy_event.rate,
                            self.profit_currency, selling_asset,
                            tsToDate(buy_event.timestamp,
                                     formatstr='%d/%m/%Y %H:%M:%S')))
                # stop iterating since we found all buys to satisfy this sell
                break
            else:
                remaining_sold_amount -= buy_event.amount
                if at_taxfree_period:
                    taxfree_amount += buy_event.amount
                    taxfree_bought_cost += buy_event.cost
                else:
                    taxable_amount += buy_event.amount
                    taxable_bought_cost += buy_event.cost

                if debug_enabled:
                    logger.debug(
                        '[{}] Using up the entire buy of {} "{}" for {} "{}" per {} at {}'
                        .format(
                            'TAX-FREE' if at_taxfree_period else 'TAXABLE',
                            buy_event.amount, selling_asset, buy_event.rate,
                            self.profit_currency, selling_asset,
                            tsToDate(buy_event.timestamp,
                                     formatstr='%d/%m/%Y %H:%M:%S')))

        if stop_index == -1:
            logger.critical(
                'No documented buy found for "{}" before {}'.format(
                    selling_asset,
                    tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S')))
            # That means we had no documented buy for that asset. This is not good
            # because we can't prove a corresponding buy and as such we are burdened
            # calculating the entire sell as profit which needs to be taxed
            return selling_amount, 0, 0

        # Otherwise, delete all the used up buys from the list
        del self.events[selling_asset].buys[:stop_index]
        # and modify the amount of the buy where we stopped
        self.events[selling_asset].buys[0] = self.events[selling_asset].buys[
            0]._replace(amount=remaining_amount_from_last_buy)

        return taxable_amount, taxable_bought_cost, taxfree_bought_cost
Exemple #28
0
    def add_sell(self,
                 selling_asset,
                 selling_amount,
                 receiving_asset,
                 receiving_amount,
                 gain_in_profit_currency,
                 total_fee_in_profit_currency,
                 trade_rate,
                 rate_in_profit_currency,
                 timestamp,
                 loan_settlement=False,
                 is_virtual=False):

        if selling_asset not in self.events:
            self.events[selling_asset] = Events(list(), list())

        self.events[selling_asset].sells.append(
            SellEvent(
                amount=selling_amount,
                timestamp=timestamp,
                rate=rate_in_profit_currency,
                fee_rate=total_fee_in_profit_currency / selling_amount,
                gain=gain_in_profit_currency,
            ))

        debug_enabled = logger.isEnabledFor(logging.DEBUG)
        if debug_enabled:
            if loan_settlement:
                logger.debug(
                    'Loan Settlement Selling {} of "{}" for {} "{}" at {}'.
                    format(selling_amount, selling_asset,
                           gain_in_profit_currency, self.profit_currency,
                           tsToDate(timestamp, formatstr='%d/%m/%Y %H:%M:%S')))
            else:
                logger.debug(
                    'Selling {} of "{}" for {} "{}" ({} "{}" per "{}" or {} "{}" '
                    'per "{}") for a gain of {} "{}" and a fee of {} "{} at {}'
                    .format(selling_amount, selling_asset, receiving_amount,
                            receiving_asset, trade_rate, receiving_asset,
                            selling_asset, rate_in_profit_currency,
                            self.profit_currency, selling_asset,
                            gain_in_profit_currency, self.profit_currency,
                            total_fee_in_profit_currency, self.profit_currency,
                            tsToDate(timestamp,
                                     formatstr='%d/%m/%Y %H:%M:%S')))

        # now search the buys for `paid_with_asset` and  calculate profit/loss
        (taxable_amount, taxable_bought_cost,
         taxfree_bought_cost) = self.search_buys_calculate_profit(
             selling_amount, selling_asset, timestamp)
        general_profit_loss = 0
        taxable_profit_loss = 0

        # If we don't include crypto2crypto and we sell for crypto, stop here
        if receiving_asset not in FIAT_CURRENCIES and not self.include_crypto2crypto:
            return

        # calculate profit/loss
        if not loan_settlement or (loan_settlement
                                   and self.count_profit_for_settlements):
            taxable_gain = taxable_gain_for_sell(
                taxable_amount=taxable_amount,
                rate_in_profit_currency=rate_in_profit_currency,
                total_fee_in_profit_currency=total_fee_in_profit_currency,
                selling_amount=selling_amount,
            )

            general_profit_loss = gain_in_profit_currency - (
                taxfree_bought_cost + taxable_bought_cost +
                total_fee_in_profit_currency)
            taxable_profit_loss = taxable_gain - taxable_bought_cost

        # should never happen, should be stopped at the main loop
        assert timestamp <= self.query_end_ts, (
            "Trade time > query_end_ts found in adding to sell event")

        # count profit/losses if we are inside the query period
        if timestamp >= self.query_start_ts:
            if loan_settlement:
                # If it's a loan settlement we are charged both the fee and the gain
                settlement_loss = gain_in_profit_currency + total_fee_in_profit_currency
                self.settlement_losses += settlement_loss
                if debug_enabled:
                    logger.debug("Loan Settlement Loss: {} {}".format(
                        settlement_loss, self.profit_currency))
            elif debug_enabled:
                logger.debug("Taxable P/L: {} {} General P/L: {} {}".format(
                    taxable_profit_loss,
                    self.profit_currency,
                    general_profit_loss,
                    self.profit_currency,
                ))

            self.general_trade_profit_loss += general_profit_loss
            self.taxable_trade_profit_loss += taxable_profit_loss

            if loan_settlement:
                self.csv_exporter.add_loan_settlement(
                    asset=selling_asset,
                    amount=selling_amount,
                    rate_in_profit_currency=rate_in_profit_currency,
                    total_fee_in_profit_currency=total_fee_in_profit_currency,
                    timestamp=timestamp,
                )
            else:
                self.csv_exporter.add_sell(
                    selling_asset=selling_asset,
                    rate_in_profit_currency=rate_in_profit_currency,
                    total_fee_in_profit_currency=total_fee_in_profit_currency,
                    gain_in_profit_currency=gain_in_profit_currency,
                    selling_amount=selling_amount,
                    receiving_asset=receiving_asset,
                    receiving_amount=receiving_amount,
                    receiving_asset_rate_in_profit_currency=self.
                    get_rate_in_profit_currency(
                        receiving_asset,
                        timestamp,
                    ),
                    taxable_amount=taxable_amount,
                    taxable_bought_cost=taxable_bought_cost,
                    timestamp=timestamp,
                    is_virtual=is_virtual,
                )