def test_search_buys_calculate_profit_after_year(accountant):
    asset = 'BTC'
    events = accountant.events.events
    events[asset] = Events([], [])
    events[asset].buys.append(
        BuyEvent(
            amount=FVal(5),
            timestamp=1446979735,  # 08/11/2015
            rate=FVal(268.1),
            fee_rate=FVal(0.0001),
        ),
    )
    events['BTC'].buys.append(
        BuyEvent(
            amount=FVal(15),
            timestamp=1467378304,  # 31/06/2016
            rate=FVal(612.45),
            fee_rate=FVal(0.0019),
        ),
    )
    events['BTC'].buys.append(
        BuyEvent(
            amount=FVal(3),  # 25/10/2016
            timestamp=1477378304,
            rate=FVal(603.415),
            fee_rate=FVal(0.0017),
        ),
    )

    (
        taxable_amount,
        taxable_bought_cost,
        taxfree_bought_cost,
    ) = accountant.events.search_buys_calculate_profit(
        selling_amount=FVal(8),
        selling_asset=asset,
        timestamp=1480683904,  # 02/12/2016
    )

    assert taxable_amount == 3, '3 out of 8 should be taxable (within a year)'
    assert taxfree_bought_cost.is_close(FVal('1340.5005'))
    assert taxable_bought_cost.is_close(FVal('1837.3557'))

    assert (len(accountant.events.events[asset].buys)) == 2, 'first buy should have been used'
    remaining_amount = accountant.events.events[asset].buys[0].amount
    assert remaining_amount == FVal(12), '3 of 15 should have been consumed'
Exemple #2
0
    def add_margin_position(
        self,
        gain_loss_asset: Asset,
        gain_loss_amount: FVal,
        fee_in_asset: Fee,
        margin_notes: str,
        timestamp: Timestamp,
    ) -> None:

        rate = self.get_rate_in_profit_currency(gain_loss_asset, timestamp)

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

        net_gain_loss_amount = gain_loss_amount - fee_in_asset
        gain_loss_in_profit_currency = net_gain_loss_amount * rate

        if net_gain_loss_amount > 0:
            self.events[gain_loss_asset].buys.append(
                BuyEvent(
                    amount=net_gain_loss_amount,
                    timestamp=timestamp,
                    rate=rate,
                    fee_rate=ZERO,
                ), )
        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'{timestamp_to_date(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

            log.debug(
                'Accounting for margin position',
                sensitive_log=True,
                notes=margin_notes,
                gain_loss_asset=gain_loss_asset,
                net_gain_loss_amount=net_gain_loss_amount,
                gain_loss_in_profit_currency=gain_loss_in_profit_currency,
                timestamp=timestamp,
            )

            self.csv_exporter.add_margin_position(
                margin_notes=margin_notes,
                gain_loss_asset=gain_loss_asset,
                net_gain_loss_amount=net_gain_loss_amount,
                gain_loss_in_profit_currency=gain_loss_in_profit_currency,
                timestamp=timestamp,
            )
Exemple #3
0
    def add_loan_gain(
        self,
        gained_asset: Asset,
        gained_amount: FVal,
        fee_in_asset: Fee,
        lent_amount: FVal,
        open_time: Timestamp,
        close_time: Timestamp,
    ) -> None:
        """Account for gains from the given loan
        May raise:
        - PriceQueryUnknownFromAsset if the from asset is known to miss from cryptocompare
        - NoPriceForGivenTimestamp if we can't find a price for the asset in the given
        timestamp from the external service.
        - RemoteError if there is a problem reaching the price oracle server
        or with reading the response returned by the server
        """

        timestamp = close_time
        rate = self.get_rate_in_profit_currency(gained_asset, timestamp)

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

        net_gain_amount = gained_amount - fee_in_asset
        gain_in_profit_currency = net_gain_amount * rate
        assert gain_in_profit_currency > 0, "Loan profit is negative. Should never happen"
        self.events[gained_asset].buys.append(
            BuyEvent(
                amount=net_gain_amount,
                timestamp=timestamp,
                rate=rate,
                fee_rate=ZERO,
            ), )
        # count profits if we are inside the query period
        if timestamp >= self.query_start_ts:
            log.debug(
                'Accounting for loan profit',
                sensitive_log=True,
                gained_asset=gained_asset,
                gained_amount=gained_amount,
                gain_in_profit_currency=gain_in_profit_currency,
                lent_amount=lent_amount,
                open_time=open_time,
                close_time=close_time,
            )

            self.loan_profit += gain_in_profit_currency
            self.csv_exporter.add_loan_profit(
                gained_asset=gained_asset,
                gained_amount=gained_amount,
                gain_in_profit_currency=gain_in_profit_currency,
                lent_amount=lent_amount,
                open_time=open_time,
                close_time=close_time,
            )
Exemple #4
0
def test_search_buys_calculate_profit_1_buy_used_by_2_sells_taxable(
        accountant):
    """ Make sure that when 1 buy is used by 2 sells bought cost is correct

    Regression test for taxable part of:
    https://github.com/rotkehlchenio/rotkehlchen/issues/223
    """
    asset = 'BTC'
    events = accountant.events.events
    events[asset] = Events(list(), list())
    events[asset].buys.append(
        BuyEvent(
            amount=FVal(5),
            timestamp=1446979735,  # 08/11/2015
            rate=FVal(268.1),
            fee_rate=FVal(0.0001),
        ), )

    (
        taxable_amount,
        taxable_bought_cost,
        taxfree_bought_cost,
    ) = accountant.events.search_buys_calculate_profit(
        selling_amount=FVal(3),
        selling_asset=asset,
        timestamp=1467378304,  # 31/06/2016
    )
    assert taxable_amount == 3, '3 out of 3 should be taxable (within a year)'
    assert taxfree_bought_cost.is_close(FVal('0'))
    assert taxable_bought_cost.is_close(FVal('804.3003'))

    assert (len(
        accountant.events.events[asset].buys)) == 1, 'whole buy was not used'
    remaining_amount = accountant.events.events[asset].buys[0].amount
    assert remaining_amount == FVal(2), '3 of 5 should have been consumed'

    # now eat up all the rest
    (
        taxable_amount,
        taxable_bought_cost,
        taxfree_bought_cost,
    ) = accountant.events.search_buys_calculate_profit(
        selling_amount=FVal(2),
        selling_asset=asset,
        timestamp=1467378404,  # bit after previous sell
    )
    assert taxable_amount == 2, '2 out of 2 should be taxable (within a year)'
    assert taxfree_bought_cost.is_close(FVal('0'))
    assert taxable_bought_cost.is_close(FVal('536.2002'))

    assert (len(accountant.events.events[asset].buys)
            ) == 0, 'the buy should have been used'
Exemple #5
0
def test_reduce_asset_amount_more_that_bought(accountant):
    asset = 'BTC'
    events = accountant.events.events
    events[asset] = Events(list(), list())
    events[asset].buys.append(
        BuyEvent(
            amount=FVal(1),
            timestamp=1446979735,  # 08/11/2015
            rate=FVal(268.1),
            fee_rate=FVal(0.0001),
        ), )
    events['BTC'].buys.append(
        BuyEvent(
            amount=FVal(1),
            timestamp=1467378304,  # 31/06/2016
            rate=FVal(612.45),
            fee_rate=FVal(0.0019),
        ), )

    assert not accountant.events.reduce_asset_amount(asset, FVal(3))
    assert (len(
        accountant.events.events[asset].buys)) == 0, 'all buys should be used'
Exemple #6
0
def test_search_buys_calculate_profit_sell_more_than_bought_after_year(accountant):
    asset = 'BTC'
    events = accountant.events.events
    events[asset] = Events(list(), list())
    events[asset].buys.append(
        BuyEvent(
            amount=FVal(1),
            timestamp=1446979735,  # 08/11/2015
            rate=FVal(268.1),
            fee_rate=FVal(0.0001),
        ),
    )
    events['BTC'].buys.append(
        BuyEvent(
            amount=FVal(1),
            timestamp=1467378304,  # 31/06/2016
            rate=FVal(612.45),
            fee_rate=FVal(0.0019),
        ),
    )

    (
        taxable_amount,
        taxable_bought_cost,
        taxfree_bought_cost,
    ) = accountant.events.search_buys_calculate_profit(
        selling_amount=FVal(3),
        selling_asset=asset,
        timestamp=1523399409,  # 10/04/2018
    )

    assert taxable_amount == 1, '1 out of 3 should be taxable (after a year)'
    assert taxfree_bought_cost.is_close(FVal('880.552'))
    assert taxable_bought_cost.is_close(FVal('0'))

    assert (len(accountant.events.events[asset].buys)) == 0, 'only buy should have been used'
Exemple #7
0
    def add_loan_gain(
        self,
        gained_asset: Asset,
        gained_amount: FVal,
        fee_in_asset: Fee,
        lent_amount: FVal,
        open_time: Timestamp,
        close_time: Timestamp,
    ) -> None:

        timestamp = close_time
        rate = self.get_rate_in_profit_currency(gained_asset, timestamp)

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

        net_gain_amount = gained_amount - fee_in_asset
        gain_in_profit_currency = net_gain_amount * rate
        assert gain_in_profit_currency > 0, "Loan profit is negative. Should never happen"
        self.events[gained_asset].buys.append(
            BuyEvent(
                amount=net_gain_amount,
                timestamp=timestamp,
                rate=rate,
                fee_rate=ZERO,
            ), )
        # count profits if we are inside the query period
        if timestamp >= self.query_start_ts:
            log.debug(
                'Accounting for loan profit',
                sensitive_log=True,
                gained_asset=gained_asset,
                gained_amount=gained_amount,
                gain_in_profit_currency=gain_in_profit_currency,
                lent_amount=lent_amount,
                open_time=open_time,
                close_time=close_time,
            )

            self.loan_profit += gain_in_profit_currency
            self.csv_exporter.add_loan_profit(
                gained_asset=gained_asset,
                gained_amount=gained_amount,
                gain_in_profit_currency=gain_in_profit_currency,
                lent_amount=lent_amount,
                open_time=open_time,
                close_time=close_time,
            )
Exemple #8
0
    def add_ledger_action(self, action: LedgerAction) -> None:
        log.debug(
            'Accounting for LedgerAction',
            sensitive_log=True,
            action=action,
        )
        # should never happen, should be stopped at the main loop
        assert action.timestamp <= self.query_end_ts, (
            'Ledger action time > query_end_ts found in processing')
        rate = self.get_rate_in_profit_currency(action.asset, action.timestamp)
        profit_loss = action.amount * rate

        if action.asset not in self.events:
            self.events[action.asset] = Events([], [])
        if action.is_profitable():
            if action.timestamp > self.query_start_ts:
                self.ledger_actions_profit_loss += profit_loss
            self.events[action.asset].buys.append(
                BuyEvent(
                    amount=action.amount,
                    timestamp=action.timestamp,
                    rate=rate,
                    fee_rate=ZERO,
                ), )
        else:
            if action.timestamp > self.query_start_ts:
                self.ledger_actions_profit_loss -= profit_loss
            result = self.reduce_asset_amount(
                asset=action.asset,
                amount=action.amount,
            )
            if not result:
                log.critical(
                    f'No documented buy found for {action.asset} before '
                    f'{self.csv_exporter.timestamp_to_date(action.timestamp)}',
                )

        if action.timestamp > self.query_start_ts:
            self.csv_exporter.add_ledger_action(
                action=action,
                profit_loss_in_profit_currency=profit_loss,
            )
Exemple #9
0
    def add_margin_position(self, margin: MarginPosition) -> None:
        """Account for the given margin position

        May raise:
        - PriceQueryUnknownFromAsset if the from asset is known to miss from cryptocompare
        - NoPriceForGivenTimestamp if we can't find a price for the asset in the given
        timestamp from the external service.
        - RemoteError if there is a problem reaching the price oracle server
        or with reading the response returned by the server
        """
        if margin.pl_currency not in self.events:
            self.events[margin.pl_currency] = Events([], [])
        if margin.fee_currency not in self.events:
            self.events[margin.fee_currency] = Events([], [])

        pl_currency_rate = self.get_rate_in_profit_currency(
            margin.pl_currency, margin.close_time)
        fee_currency_rate = self.get_rate_in_profit_currency(
            margin.pl_currency, margin.close_time)
        net_gain_loss_in_profit_currency = (
            margin.profit_loss * pl_currency_rate -
            margin.fee * fee_currency_rate)

        # Add or remove to the pl_currency asset
        if margin.profit_loss > 0:
            self.events[margin.pl_currency].buys.append(
                BuyEvent(
                    amount=margin.profit_loss,
                    timestamp=margin.close_time,
                    rate=pl_currency_rate,
                    fee_rate=ZERO,
                ), )
        elif margin.profit_loss < 0:
            result = self.reduce_asset_amount(
                asset=margin.pl_currency,
                amount=-margin.profit_loss,
            )
            if not result:
                log.critical(
                    f'No documented buy found for {margin.pl_currency} before '
                    f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}',
                )

        # Reduce the fee_currency asset
        result = self.reduce_asset_amount(asset=margin.fee_currency,
                                          amount=margin.fee)
        if not result:
            log.critical(
                f'No documented buy found for {margin.fee_currency} before '
                f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}',
            )

        # count profit/loss if we are inside the query period
        if margin.close_time >= self.query_start_ts:
            self.margin_positions_profit_loss += net_gain_loss_in_profit_currency

            log.debug(
                'Accounting for margin position',
                sensitive_log=True,
                notes=margin.notes,
                gain_loss_asset=margin.pl_currency,
                gain_loss_amount=margin.profit_loss,
                net_gain_loss_in_profit_currency=
                net_gain_loss_in_profit_currency,
                timestamp=margin.close_time,
            )

            self.csv_exporter.add_margin_position(
                margin_notes=margin.notes,
                gain_loss_asset=margin.pl_currency,
                gain_loss_amount=margin.profit_loss,
                gain_loss_in_profit_currency=net_gain_loss_in_profit_currency,
                timestamp=margin.close_time,
            )
Exemple #10
0
    def add_buy(
        self,
        bought_asset: Asset,
        bought_amount: FVal,
        paid_with_asset: Asset,
        trade_rate: FVal,
        fee_in_profit_currency: Fee,
        fee_currency: Asset,
        timestamp: Timestamp,
        is_virtual: bool = False,
    ) -> None:
        """
        Account for the given buy

        May raise:
        - PriceQueryUnknownFromAsset if the from asset is known to miss from cryptocompare
        - NoPriceForGivenTimestamp if we can't find a price for the asset in the given
        timestamp from cryptocompare
        - RemoteError if there is a problem reaching the price oracle server
        or with reading the response returned by the server
        """
        paid_with_asset_rate = self.get_rate_in_profit_currency(
            paid_with_asset, timestamp)
        buy_rate = paid_with_asset_rate * trade_rate

        self.handle_prefork_asset_buys(
            bought_asset=bought_asset,
            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,
        )

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

        gross_cost = bought_amount * buy_rate
        cost_in_profit_currency = gross_cost + fee_in_profit_currency

        self.events[bought_asset].buys.append(
            BuyEvent(
                amount=bought_amount,
                timestamp=timestamp,
                rate=buy_rate,
                fee_rate=fee_in_profit_currency / bought_amount,
            ), )
        log.debug(
            'Buy Event',
            sensitive_log=True,
            bought_amount=bought_amount,
            bought_asset=bought_asset,
            paid_with_asset=paid_with_asset,
            rate=trade_rate,
            rate_in_profit_currency=buy_rate,
            profit_currency=self.profit_currency,
            timestamp=timestamp,
        )

        if timestamp >= self.query_start_ts:
            self.csv_exporter.add_buy(
                bought_asset=bought_asset,
                rate=buy_rate,
                fee_cost=fee_in_profit_currency,
                amount=bought_amount,
                cost=cost_in_profit_currency,
                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_buy(
        self,
        location: Location,
        bought_asset: Asset,
        bought_amount: FVal,
        paid_with_asset: Asset,
        trade_rate: FVal,
        fee_in_profit_currency: Fee,
        fee_currency: Asset,
        timestamp: Timestamp,
        is_virtual: bool = False,
    ) -> None:
        """
        Account for the given buy

        May raise:
        - PriceQueryUnsupportedAsset if from/to asset is missing from all price oracles
        - NoPriceForGivenTimestamp if we can't find a price for the asset in the given
        timestamp from cryptocompare
        - RemoteError if there is a problem reaching the price oracle server
        or with reading the response returned by the server
        """
        skip_trade = (not self.include_crypto2crypto
                      and not bought_asset.is_fiat()
                      and not paid_with_asset.is_fiat())
        if skip_trade:
            return

        logger.debug(
            f'Processing buy trade of {bought_asset.identifier} with '
            f'{paid_with_asset.identifier} at {timestamp}', )

        paid_with_asset_rate = self.get_rate_in_profit_currency(
            paid_with_asset, timestamp)
        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,
            trade_rate=trade_rate,
            fee_in_profit_currency=fee_in_profit_currency,
            fee_currency=fee_currency,
            timestamp=timestamp,
        )

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

        gross_cost = bought_amount * buy_rate
        cost_in_profit_currency = gross_cost + fee_in_profit_currency

        self.events[bought_asset].buys.append(
            BuyEvent(
                amount=bought_amount,
                timestamp=timestamp,
                rate=buy_rate,
                fee_rate=fee_in_profit_currency / bought_amount,
            ), )
        log.debug(
            'Buy Event',
            sensitive_log=True,
            location=str(location),
            bought_amount=bought_amount,
            bought_asset=bought_asset,
            paid_with_asset=paid_with_asset,
            rate=trade_rate,
            rate_in_profit_currency=buy_rate,
            profit_currency=self.profit_currency,
            timestamp=timestamp,
        )

        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,
                amount=bought_amount,
                cost=cost_in_profit_currency,
                paid_with_asset=paid_with_asset,
                paid_with_asset_rate=paid_with_asset_rate,
                timestamp=timestamp,
                is_virtual=is_virtual,
            )
Exemple #12
0
    def add_buy(
        self,
        bought_asset: Asset,
        bought_amount: FVal,
        paid_with_asset: Asset,
        trade_rate: FVal,
        fee_in_profit_currency: Fee,
        fee_currency: Asset,
        timestamp: Timestamp,
        is_virtual: bool = False,
    ) -> None:
        paid_with_asset_rate = self.get_rate_in_profit_currency(
            paid_with_asset, timestamp)
        buy_rate = paid_with_asset_rate * trade_rate

        self.handle_prefork_asset_buys(
            bought_asset=bought_asset,
            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,
        )

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

        gross_cost = bought_amount * buy_rate
        cost_in_profit_currency = gross_cost + fee_in_profit_currency

        self.events[bought_asset].buys.append(
            BuyEvent(
                amount=bought_amount,
                timestamp=timestamp,
                rate=buy_rate,
                fee_rate=fee_in_profit_currency / bought_amount,
            ), )
        log.debug(
            'Buy Event',
            sensitive_log=True,
            bought_amount=bought_amount,
            bought_asset=bought_asset,
            paid_with_asset=paid_with_asset,
            rate=trade_rate,
            rate_in_profit_currency=buy_rate,
            profit_currency=self.profit_currency,
            timestamp=timestamp,
        )

        if timestamp >= self.query_start_ts:
            self.csv_exporter.add_buy(
                bought_asset=bought_asset,
                rate=buy_rate,
                fee_cost=fee_in_profit_currency,
                amount=bought_amount,
                cost=cost_in_profit_currency,
                paid_with_asset=paid_with_asset,
                paid_with_asset_rate=paid_with_asset_rate,
                timestamp=timestamp,
                is_virtual=is_virtual,
            )
Exemple #13
0
    def add_margin_position(self, margin: MarginPosition) -> None:
        if margin.pl_currency not in self.events:
            self.events[margin.pl_currency] = Events(list(), list())
        if margin.fee_currency not in self.events:
            self.events[margin.fee_currency] = Events(list(), list())

        pl_currency_rate = self.get_rate_in_profit_currency(
            margin.pl_currency, margin.close_time)
        fee_currency_rate = self.get_rate_in_profit_currency(
            margin.pl_currency, margin.close_time)
        net_gain_loss_in_profit_currency = (
            margin.profit_loss * pl_currency_rate -
            margin.fee * fee_currency_rate)

        # Add or remove to the pl_currency asset
        if margin.profit_loss > 0:
            self.events[margin.pl_currency].buys.append(
                BuyEvent(
                    amount=margin.profit_loss,
                    timestamp=margin.close_time,
                    rate=pl_currency_rate,
                    fee_rate=ZERO,
                ), )
        elif margin.profit_loss < 0:
            result = self.reduce_asset_amount(
                asset=margin.pl_currency,
                amount=-margin.profit_loss,
            )
            if not result:
                log.critical(
                    f'No documented buy found for {margin.pl_currency} before '
                    f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}',
                )

        # Reduce the fee_currency asset
        result = self.reduce_asset_amount(asset=margin.fee_currency,
                                          amount=margin.fee)
        if not result:
            log.critical(
                f'No documented buy found for {margin.fee_currency} before '
                f'{timestamp_to_date(margin.close_time, formatstr="%d/%m/%Y %H:%M:%S")}',
            )

        # count profit/loss if we are inside the query period
        if margin.close_time >= self.query_start_ts:
            self.margin_positions_profit_loss += net_gain_loss_in_profit_currency

            log.debug(
                'Accounting for margin position',
                sensitive_log=True,
                notes=margin.notes,
                gain_loss_asset=margin.pl_currency,
                gain_loss_amount=margin.profit_loss,
                net_gain_loss_in_profit_currency=
                net_gain_loss_in_profit_currency,
                timestamp=margin.close_time,
            )

            self.csv_exporter.add_margin_position(
                margin_notes=margin.notes,
                gain_loss_asset=margin.pl_currency,
                gain_loss_amount=margin.profit_loss,
                gain_loss_in_profit_currency=net_gain_loss_in_profit_currency,
                timestamp=margin.close_time,
            )