Esempio n. 1
0
def test_no_corresponding_buy_for_sell(accountant, google_service):
    """Test that if there is no corresponding buy for a sell, the entire sell counts as profit"""
    history = [
        Trade(
            timestamp=1476979735,
            location=Location.KRAKEN,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal(1),
            rate=FVal('2519.62'),
            fee=FVal('0.02'),
            fee_currency=A_EUR,
            link=None,
        )
    ]
    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=history,
    )

    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=FVal('2519.62'), free=ZERO),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-0.02'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 2
0
def test_buying_selling_eth_before_daofork(accountant, google_service):
    history3 = [
        Trade(
            timestamp=1446979735,  # 11/08/2015
            location=Location.EXTERNAL,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(1450),
            rate=FVal('0.2315893'),
            fee=None,
            fee_currency=None,
            link=None,
        ), Trade(  # selling ETH prefork should also reduce our ETC amount
            timestamp=1461021812,  # 18/04/2016 (taxable)
            location=Location.KRAKEN,
            base_asset=A_ETH,  # cryptocompare hourly ETC/EUR price: 7.88
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal(50),
            rate=FVal('7.88'),
            fee=FVal('0.5215'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # selling ETC after the fork
            timestamp=1481979135,  # 17/12/2016
            location=Location.KRAKEN,
            base_asset=A_ETC,  # cryptocompare hourly ETC/EUR price: 7.88
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,  # not-taxable -- considered bought with ETH so after year
            amount=FVal(550),
            rate=FVal('1.78'),
            fee=FVal('0.9375'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # selling ETH after the fork
            timestamp=1482138141,  # 19/12/2016
            location=Location.KRAKEN,
            base_asset=A_ETH,  # cryptocompare hourly ETH/EUR price: 7.45
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,  # not-taxable -- after 1 year
            amount=FVal(10),
            rate=FVal('7.45'),
            fee=FVal('0.12'),
            fee_currency=A_EUR,
            link=None,
        ),
    ]
    accounting_history_process(accountant, 1436979735, 1495751688, history3)
    no_message_errors(accountant.msg_aggregator)
    # make sure that the intermediate ETH sell before the fork reduced our ETC
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount('ETC') == FVal(850)
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount('ETH') == FVal(1390)

    expected_pnls = PnlTotals({
        AccountingEventType.TRADE: PNL(taxable=FVal('382.4205350'), free=FVal('923.8099920')),
        AccountingEventType.FEE: PNL(taxable=FVal('-1.579'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 3
0
def test_selling_crypto_bought_with_crypto(accountant, google_service):
    history = [
        Trade(
            timestamp=1446979735,
            location=Location.EXTERNAL,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(82),
            rate=FVal('268.678317859'),
            fee=None,
            fee_currency=None,
            link=None,
        ),
        Trade(
            timestamp=1449809536,  # cryptocompare hourly BTC/EUR price: 386.175
            location=Location.POLONIEX,
            base_asset=A_XMR,  # cryptocompare hourly XMR/EUR price: 0.39665
            quote_asset=A_BTC,
            trade_type=TradeType.BUY,
            amount=FVal(375),
            rate=FVal('0.0010275'),
            fee=FVal('0.9375'),
            fee_currency=A_XMR,
            link=None,
        ),
        Trade(
            timestamp=
            1458070370,  # cryptocompare hourly rate XMR/EUR price: 1.0443027675
            location=Location.KRAKEN,
            base_asset=A_XMR,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal(45),
            rate=FVal('1.0443027675'),
            fee=FVal('0.117484061344'),
            fee_currency=A_XMR,
            link=None,
        ),
    ]
    accounting_history_process(accountant, 1436979735, 1495751688, history)
    no_message_errors(accountant.msg_aggregator)
    # Make sure buying XMR with BTC also creates a BTC sell
    sells = accountant.pots[0].cost_basis.get_events(A_BTC).spends
    assert len(sells) == 1
    assert sells[0].timestamp == 1449809536
    assert sells[0].amount.is_close(FVal('0.3853125'))
    assert sells[0].rate.is_close(FVal('386.03406326'))

    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=FVal('74.3118704999540625'), free=ZERO),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-0.419658351381311222'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 4
0
def test_no_taxfree_period(accountant, google_service):
    accounting_history_process(accountant, 1436979735, 1519693374, history5)
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=FVal('265253.1283582327833875'), free=ZERO),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-0.238868129979988140934107'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 5
0
def test_simple_accounting(accountant, google_service):
    accounting_history_process(accountant, 1436979735, 1495751688, history1)
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=FVal('559.6947154'), free=ZERO),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-0.23886813'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 6
0
def test_nocrypto2crypto(accountant, google_service):
    accounting_history_process(accountant, 1436979735, 1519693374, history5)
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=ZERO, free=FVal('264693.433642820')),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-1.1708853227087498964'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 7
0
def test_fees_count_in_cost_basis(accountant, google_service):
    """Make sure that asset amounts used in fees are reduced."""
    history = [
        Trade(
            timestamp=1609537953,
            location=Location.KRAKEN,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=ONE,
            rate=FVal('598.26'),
            fee=ONE,
            fee_currency=A_EUR,
            link=None,
        ), Trade(
            # PNL: 0.5 * 1862.06 - 0.5 * 599.26 -> 631.4
            # fee: -0.5 * 1862.06 + 0.5 * 1862.06 - 0.5 * 599.26 -> -299.63
            timestamp=1624395186,
            location=Location.KRAKEN,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('0.5'),
            rate=FVal('1862.06'),
            fee=FVal('0.5'),
            fee_currency=A_ETH,
            link=None,
        ), Trade(
            timestamp=1625001464,
            location=Location.KRAKEN,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('0.5'),
            rate=FVal('1837.31'),
            fee=None,
            fee_currency=None,
            link=None,
        ),
    ]
    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1625001466,
        history_list=history,
    )

    expected_pnls = PnlTotals({
        AccountingEventType.TRADE: PNL(taxable=FVal('1550.055'), free=ZERO),
        AccountingEventType.FEE: PNL(taxable=FVal('-300.630'), free=ZERO),
    })
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_ETH) is None
    warnings = accountant.msg_aggregator.consume_warnings()
    assert len(warnings) == 0
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 8
0
def test_kfee_price_in_accounting(accountant, google_service):
    """
    Test that KFEEs are correctly handled during accounting

    KFEE price is fixed at $0.01
    """
    history = [
        LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),  # 178.615 EUR/ETH
            action_type=LedgerActionType.INCOME,
            location=Location.KRAKEN,
            amount=FVal(1),
            asset=A_ETH,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ), LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),  # 0.8612 USD/EUR. 1 KFEE = $0.01 so 8.612 EUR
            action_type=LedgerActionType.INCOME,
            location=Location.KRAKEN,
            amount=FVal(1000),
            asset=A_KFEE,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ), Trade(
            timestamp=1609537953,
            location=Location.KRAKEN,  # 0.89 USDT/EUR -> PNL: 20 * 0.89 - 0.02*178.615 ->  14.2277
            base_asset=A_ETH,
            quote_asset=A_USDT,
            trade_type=TradeType.SELL,
            amount=FVal('0.02'),
            rate=FVal(1000),
            fee=FVal(30),  # KFEE should not be taken into account
            fee_currency=A_KFEE,
            link=None,
        ),
    ]
    accounting_history_process(
        accountant,
        start_ts=1539713238,
        end_ts=1624395187,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE: PNL(taxable=ZERO, free=FVal('14.2277')),
        AccountingEventType.LEDGER_ACTION: PNL(taxable=FVal('187.227'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 9
0
def test_big_taxfree_period(accountant, google_service):
    accounting_history_process(accountant, 1436979735, 1519693374, history5)
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=ZERO, free=FVal('265253.1283582327833875')),
        AccountingEventType.FEE:
        PNL(
            taxable=FVal('-1.170885322708749896'),
            free=FVal('0.932017192728761755465893'),
        ),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 10
0
def test_fees_in_received_asset(accountant, google_service):
    """
    Test the sell trade where the fee is nominated in the asset received. We had a bug
    where the PnL report said that there was no documented acquisition.
    """
    history = [
        LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),  # 178.615 EUR/ETH
            action_type=LedgerActionType.INCOME,
            location=Location.BINANCE,
            amount=ONE,
            asset=A_ETH,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ),
        Trade(
            # Sell 0.02 ETH for USDT with rate 1000 USDT/ETH and 0.10 USDT fee
            # So acquired 20 USDT for 0.02 ETH + 0.10 USDT
            # So acquired 20 USDT for 0.02 * 598.26 + 0.10 * 0.89 -> 12.0542 EUR
            # So paid 12.0542/20 -> 0.60271 EUR/USDT
            timestamp=1609537953,  # 0.89 EUR/USDT
            location=Location.BINANCE,
            base_asset=A_ETH,  # 598.26 EUR/ETH
            quote_asset=A_USDT,
            trade_type=TradeType.SELL,
            amount=FVal('0.02'),
            rate=FVal(1000),
            fee=FVal('0.10'),
            fee_currency=A_USDT,
            link=None,
        ),
    ]

    accounting_history_process(
        accountant,
        start_ts=1539713238,
        end_ts=1624395187,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_USDT.identifier).is_close('19.90')  # noqa: E501
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE: PNL(taxable=ZERO, free=FVal('14.2277')),
        AccountingEventType.FEE: PNL(taxable=FVal('-0.060271'), free=ZERO),
        AccountingEventType.LEDGER_ACTION: PNL(taxable=FVal('178.615'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 11
0
def test_receiving_value_from_tx(accountant, google_service):
    """
    Test that receiving a transaction that provides value works fine
    """
    addr2 = make_ethereum_address()
    tx_hash = '0x5cc0e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde7a'
    history = [
        HistoryBaseEntry(
            event_identifier=tx_hash,
            sequence_index=0,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            location_label=make_ethereum_address(),
            asset=A_ETH,
            balance=Balance(amount=FVal('1.5')),
            notes=f'Received 1.5 ETH from {addr2}',
            event_type=HistoryEventType.RECEIVE,
            event_subtype=HistoryEventSubType.NONE,
            counterparty=addr2,
        )
    ]
    accounting_history_process(
        accountant,
        start_ts=0,
        end_ts=1640493376,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRANSACTION_EVENT:
        PNL(taxable=FVal('242.385'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 12
0
def test_taxable_ledger_action_setting(accountant, expected, google_service):
    """Test that ledger actions respect the taxable setting"""
    history = [
        LedgerAction(
            identifier=1,
            timestamp=1476979735,
            action_type=LedgerActionType.INCOME,
            location=Location.EXTERNAL,
            amount=FVal(1),  # 578.505 EUR/BTC from mocked prices
            asset=A_BTC,
            rate=None,
            rate_asset=None,
            link=None,
            notes=None,
        ), LedgerAction(
            identifier=2,
            timestamp=1491062063,
            action_type=LedgerActionType.AIRDROP,
            location=Location.EXTERNAL,
            amount=FVal(10),  # 47.865 EUR/ETH from mocked prices
            asset=A_ETH,
            rate=None,
            rate_asset=None,
            link='foo',
            notes='boo',
        ), LedgerAction(
            identifier=3,
            timestamp=1501062063,
            action_type=LedgerActionType.LOSS,
            location=Location.BLOCKCHAIN,
            amount=FVal(2),  # 175.44 EUR/ETH  from mocked prices
            asset=A_ETH,
            rate=FVal(400),  # but should use the given rate of 400 EUR
            rate_asset=A_EUR,
            link='goo',
            notes='hoo',
        ), LedgerAction(  # include a non taxed ledger action too
            identifier=4,
            timestamp=1501062064,
            action_type=LedgerActionType.EXPENSE,
            location=Location.BLOCKCHAIN,
            amount=FVal(1),
            asset=A_ETH,
            rate=FVal(400),
            rate_asset=A_EUR,
            link='goo2',
            notes='hoo2',
        ),
    ]
    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=history,
    )
    expected_pnls = PnlTotals()
    if expected != 0:
        expected_pnls[AccountingEventType.LEDGER_ACTION] = PNL(taxable=FVal(expected), free=ZERO)
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 13
0
def test_kraken_staking_events(accountant, google_service):
    """
    Test that staking events from kraken are correctly processed
    """
    history = [
        HistoryBaseEntry(
            event_identifier='XXX',
            sequence_index=0,
            timestamp=1640493374000,
            location=Location.KRAKEN,
            location_label='Kraken 1',
            asset=A_ETH2,
            balance=Balance(
                amount=FVal(0.0000541090),
                usd_value=FVal(0.212353475950),
            ),
            notes=None,
            event_type=HistoryEventType.STAKING,
            event_subtype=HistoryEventSubType.REWARD,
        ),
        HistoryBaseEntry(
            event_identifier='YYY',
            sequence_index=0,
            timestamp=1636638550000,
            location=Location.KRAKEN,
            location_label='Kraken 1',
            asset=A_ETH2,
            balance=Balance(
                amount=FVal(0.0000541090),
                usd_value=FVal(0.212353475950),
            ),
            notes=None,
            event_type=HistoryEventType.STAKING,
            event_subtype=HistoryEventSubType.REWARD,
        )
    ]
    _, events = accounting_history_process(
        accountant,
        start_ts=1636638549,
        end_ts=1640493376,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.STAKING:
        PNL(taxable=FVal('0.471505826'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
    assert len(events) == 2
    expected_pnls = [FVal('0.25114638241'), FVal('0.22035944359')]
    for idx, event in enumerate(events):
        assert event.pnl.taxable == expected_pnls[idx]
        assert event.type == AccountingEventType.STAKING
Esempio n. 14
0
def handle_prefork_asset_acquisitions(
        cost_basis: 'CostBasisCalculator',
        location: Location,
        timestamp: Timestamp,
        asset: Asset,
        amount: FVal,
        price: Price,
        starting_index: int,
) -> List['ProcessedAccountingEvent']:
    """
        Calculate the prefork asset acquisitions, meaning how is the acquisition
        of ETC pre ETH fork handled etc.

        TODO: This should change for https://github.com/rotki/rotki/issues/1610

        Returns the acquisition events to append to the pot
    """
    acquisitions = []
    if asset == A_ETH and timestamp < ETH_DAO_FORK_TS:
        acquisitions = [(A_ETC, 'Prefork acquisition for ETC')]
    elif asset == A_BTC and timestamp < BTC_BCH_FORK_TS:
        # Acquiring BTC before the BCH fork provides equal amount of BCH and BSV
        acquisitions = [
            (A_BCH, 'Prefork acquisition for BCH'),
            (A_BSV, 'Prefork acquisition for BSV'),
        ]
    elif asset == A_BCH and timestamp < BCH_BSV_FORK_TS:
        # Acquiring BCH before the BSV fork provides equal amount of BSV
        acquisitions = [(A_BSV, 'Prefork acquisition for BSV')]

    events = []
    for acquisition in acquisitions:
        event = ProcessedAccountingEvent(
            type=AccountingEventType.PREFORK_ACQUISITION,
            notes=acquisition[1],
            location=location,
            timestamp=timestamp,
            asset=acquisition[0],
            taxable_amount=amount,
            free_amount=ZERO,
            price=price,
            pnl=PNL(),
            cost_basis=None,
            index=starting_index,
        )
        cost_basis.obtain_asset(event)
        events.append(event)
        starting_index += 1

    return events
Esempio n. 15
0
    def calculate_pnl(
            self,
            count_entire_amount_spend: bool,
            count_cost_basis_pnl: bool,
    ) -> PNL:
        """Calculate PnL for this event and return it.
        Only called for events that should have PnL counted

        If count_entire_amount_spend is True then the entire amount is counted as a spend.
        Which means an expense (negative pnl).

        If count_cost_basis_pnl is True then the PnL between buying the asset amount
        and spending it is calculated and added to PnL.
        """
        self.count_entire_amount_spend = count_entire_amount_spend
        self.count_cost_basis_pnl = count_cost_basis_pnl
        taxable_bought_cost = taxfree_bought_cost = ZERO
        taxable_value = self.taxable_amount * self.price
        free_value = self.free_amount * self.price
        self.pnl = PNL()
        if count_entire_amount_spend:
            # for fees and other types we also need to consider the entire amount as spent
            self.pnl -= PNL(taxable=taxable_value + free_value, free=ZERO)

        if self.asset.is_fiat() or count_cost_basis_pnl is False:
            return self.pnl  # no need to calculate spending pnl if asset is fiat

        if self.cost_basis is not None:
            taxable_bought_cost = self.cost_basis.taxable_bought_cost
            taxfree_bought_cost = self.cost_basis.taxfree_bought_cost

        self.pnl += PNL(
            taxable=taxable_value - taxable_bought_cost,
            free=free_value - taxfree_bought_cost,
        )

        return self.pnl
Esempio n. 16
0
def test_ignored_assets(accountant, google_service):
    history = history1 + [
        Trade(
            timestamp=1476979735,
            location=Location.KRAKEN,
            base_asset=A_DASH,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(10),
            rate=FVal('9.76775956284'),
            fee=FVal('0.0011'),
            fee_currency=A_DASH,
            link=None,
        ),
        Trade(
            timestamp=1496979735,
            location=Location.KRAKEN,
            base_asset=A_DASH,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal(5),
            rate=FVal('128.09'),
            fee=FVal('0.015'),
            fee_currency=A_EUR,
            link=None,
        ),
    ]
    accounting_history_process(accountant, 1436979735, 1519693374, history)
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=FVal('559.6947154127833875'), free=ZERO),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-0.238868129979988140934107'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 17
0
def test_gas_fees_after_year(accountant, google_service):
    """
    Test that for an expense like gas fees after year the "selling" part is tax free
    PnL, and the expense part is taxable pnl.
    """
    tx_hash = '0x5cc0e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde7a'
    history = [
        LedgerAction(
            identifier=0,
            timestamp=Timestamp(1539713238),  # 178.615 EUR/ETH
            action_type=LedgerActionType.
            GIFT,  # gift so not counting as income
            location=Location.KRAKEN,
            amount=FVal(1),
            asset=A_ETH,
            rate=None,
            rate_asset=None,
            link=None,
            notes='',
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash,
            sequence_index=0,
            timestamp=1640493374000,  # 4072.51 EUR/ETH
            location=Location.BLOCKCHAIN,
            location_label=make_ethereum_address(),
            asset=A_ETH,
            balance=Balance(amount=FVal('0.01')),
            notes='Burned 0.01 ETH in gas',
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            counterparty=CPT_GAS,
        )
    ]
    accounting_history_process(
        accountant,
        start_ts=0,
        end_ts=1640493376,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRANSACTION_EVENT:
        PNL(taxable=FVal('-40.7251'), free=FVal('38.93895')),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 18
0
def test_eth2_staking(accountant, google_service):
    """Test that ethereum 2 staking is accounted for properly"""
    history = [
        ValidatorDailyStats(
            validator_index=1,
            timestamp=1607727600,  # ETH price: 449.68 ETH/EUR
            start_amount=FVal('32'),
            end_amount=FVal('32.05'),
            pnl=FVal('0.05'),  # 0.05 * 449.68 = 22.484
        ),
        ValidatorDailyStats(
            validator_index=1,
            timestamp=1607814000,  # ETH price: 469.82 ETH/EUR
            start_amount=FVal('32.05'),
            end_amount=FVal('32.045'),
            pnl=FVal(
                '-0.005'
            ),  # -0.005 * 469.82 + 0.005 * 469.82 - 0.005*449.68 = -2.2484
        ),
        ValidatorDailyStats(
            validator_index=1,
            timestamp=1607900400,  # ETH price: 486.57 ETH/EUR
            start_amount=FVal('32.045'),
            end_amount=FVal('32.085'),
            pnl=FVal('0.04'),  # 0.04 * 486.57 = 19.4628
        ),
        ValidatorDailyStats(
            validator_index=2,
            timestamp=1607900400,
            start_amount=FVal('32'),
            end_amount=FVal('32.045'),
            pnl=FVal('0.045'),  # 0.045 * 486.57 = 21.89565
        ),
    ]

    accounting_history_process(
        accountant,
        start_ts=1606727600,
        end_ts=1640493376,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({  # 22.484 - 2.2484 + 19.4628 + 21.89565
        AccountingEventType.STAKING: PNL(taxable=FVal('61.59405'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 19
0
def test_include_gas_costs(accountant, google_service):
    addr1 = '0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12'
    tx_hash = '0x5cc0e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde7a'
    history = [
        Trade(
            timestamp=1539388574,
            location=Location.EXTERNAL,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(10),
            rate=FVal('168.7'),
            fee=None,
            fee_currency=None,
            link=None,
        ),
        HistoryBaseEntry(
            event_identifier=tx_hash,
            sequence_index=0,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            location_label=addr1,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.000030921')),
            notes=f'Burned 0.000030921 ETH in gas from {addr1}',
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            counterparty=CPT_GAS,
        )
    ]
    accounting_history_process(accountant,
                               start_ts=1436979735,
                               end_ts=1619693374,
                               history_list=history)  # noqa: E501
    no_message_errors(accountant.msg_aggregator)
    expected = ZERO
    expected_pnls = PnlTotals()
    if accountant.pots[0].settings.include_gas_costs:
        expected = FVal('-0.0052163727')
        expected_pnls[AccountingEventType.TRANSACTION_EVENT] = PNL(
            taxable=expected, free=ZERO)
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 20
0
    def deserialize_from_db(cls: Type[T], timestamp: Timestamp, stringified_json: str) -> T:
        """May raise:
        - DeserializationError if something is wrong with reading this from the DB
        """
        try:
            data = json.loads(stringified_json)
        except json.decoder.JSONDecodeError as e:
            raise DeserializationError(
                f'Could not decode processed accounting event json from the DB due to {str(e)}',
            ) from e

        try:
            pnl_taxable = deserialize_fval(data['pnl_taxable'], name='pnl_taxable', location='processed event decoding')  # noqa: E501
            pnl_free = deserialize_fval(data['pnl_free'], name='pnl_free', location='processed event decoding')  # noqa: E501
            if data['cost_basis'] is None:
                cost_basis = None
            else:
                cost_basis = CostBasisInfo.deserialize(data['cost_basis'])
            event = cls(
                type=AccountingEventType.deserialize(data['type']),
                notes=data['notes'],
                location=Location.deserialize(data['location']),
                timestamp=timestamp,
                asset=Asset(data['asset']),
                free_amount=deserialize_fval(data['free_amount'], name='free_amount', location='processed event decoding'),  # noqa: E501
                taxable_amount=deserialize_fval(data['taxable_amount'], name='taxable_amount', location='processed event decoding'),  # noqa: E501
                price=deserialize_price(data['price']),
                pnl=PNL(free=pnl_free, taxable=pnl_taxable),
                cost_basis=cost_basis,
                index=data['index'],
                extra_data=data['extra_data'],
            )
            event.count_cost_basis_pnl = data['count_cost_basis_pnl']
            event.count_entire_amount_spend = data['count_entire_amount_spend']
            return event
        except KeyError as e:
            raise DeserializationError(f'Could not decode processed accounting event json from the DB due to missing key {str(e)}') from e  # noqa: E501
Esempio n. 21
0
def test_margin_events_affect_gained_lost_amount(accountant, google_service):
    history = [
        Trade(
            timestamp=1476979735,
            location=Location.KRAKEN,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(5),
            rate=FVal('578.505'),
            fee=FVal('0.0012'),
            fee_currency=A_BTC,
            link=None,
        ),
        Trade(  # 2519.62 - 0.02 - ((0.0012*578.505)/5 + 578.505)
            timestamp=1476979735,
            location=Location.KRAKEN,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal(1),
            rate=FVal('2519.62'),
            fee=FVal('0.02'),
            fee_currency=A_EUR,
            link=None,
        ),
    ]
    history += [
        MarginPosition(
            location=Location.POLONIEX,  # BTC/EUR: 810.49
            open_time=1484438400,  # 15/01/2017
            close_time=1484629704,  # 17/01/2017
            profit_loss=FVal('-0.5'),
            pl_currency=A_BTC,
            fee=FVal('0.001'),
            fee_currency=A_BTC,
            link='1',
            notes='margin1',
        ),
        MarginPosition(
            location=Location.POLONIEX,  # BTC/EUR: 979.39
            open_time=1487116800,  # 15/02/2017
            close_time=1487289600,  # 17/02/2017
            profit_loss=FVal('0.25'),
            pl_currency=A_BTC,
            fee=FVal('0.001'),
            fee_currency=A_BTC,
            link='2',
            notes='margin2',
        )
    ]

    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(
        'BTC').is_close('3.7468')
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE:
        PNL(taxable=FVal('1940.9761588'), free=ZERO),
        AccountingEventType.FEE:
        PNL(taxable=FVal('-1.87166029184'), free=ZERO),
        AccountingEventType.MARGIN_POSITION:
        PNL(taxable=FVal('-44.47442060'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 22
0
def test_ignored_transactions(accountant, google_service):
    addr1 = '0x2B888954421b424C5D3D9Ce9bB67c9bD47537d12'
    tx_hash = '0x5cc0e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde7a'
    ignored_tx_hash = '0x1000e6e62753551313412492296d5e57bea0a9d1ce507cc96aa4aa076c5bde11'
    accountant.db.add_to_ignored_action_ids(
        action_type=ActionType.ETHEREUM_TRANSACTION,
        identifiers=[ignored_tx_hash],
    )
    history = [
        Trade(
            timestamp=1539388574,
            location=Location.EXTERNAL,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(10),
            rate=FVal('168.7'),
            fee=None,
            fee_currency=None,
            link=None,
        ),
        HistoryBaseEntry(
            identifier=
            'uniqueid1',  # should normally be given by DB at write time
            event_identifier=tx_hash,
            sequence_index=0,
            timestamp=1569924574000,
            location=Location.BLOCKCHAIN,
            location_label=addr1,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.000030921')),
            notes=f'Burned 0.000030921 ETH in gas from {addr1}',
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            counterparty=CPT_GAS,
        ),
        HistoryBaseEntry(
            identifier=
            'uniqueid2',  # should normally be given by DB at write time
            event_identifier=ignored_tx_hash,
            sequence_index=0,
            timestamp=1569934574000,
            location=Location.BLOCKCHAIN,
            location_label=addr1,
            asset=A_ETH,
            balance=Balance(amount=FVal('0.000040921')),
            notes=f'Burned 0.000040921 ETH in gas from {addr1}',
            event_type=HistoryEventType.SPEND,
            event_subtype=HistoryEventSubType.FEE,
            counterparty=CPT_GAS,
        )
    ]
    _, events = accounting_history_process(accountant,
                                           start_ts=1436979735,
                                           end_ts=1619693374,
                                           history_list=history)  # noqa: E501
    assert len(events) == 3
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals({
        AccountingEventType.TRANSACTION_EVENT:
        PNL(taxable=FVal('-0.0052163727'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 23
0
def test_ledger_actions_accounting(accountant, google_service):
    """Test for accounting for ledger actions

    Makes sure that Ledger actions are processed in accounting, range is respected
    and that they contribute to the "bought" amount per asset and that also if
    a rate is given then that is used instead of the queried price
    """
    history = [LedgerAction(  # before range - read only for amount not profit
        identifier=1,
        timestamp=1435979735,  # 0.1 EUR per ETH
        action_type=LedgerActionType.INCOME,
        location=Location.EXTERNAL,
        asset=A_ETH,
        amount=AssetAmount(FVal(1)),
        rate=None,
        rate_asset=None,
        link=None,
        notes=None,
    ), LedgerAction(
        identifier=2,
        timestamp=1437279735,  # 250 EUR per BTC
        action_type=LedgerActionType.INCOME,
        location=Location.BLOCKCHAIN,
        asset=A_BTC,
        amount=AssetAmount(FVal(1)),
        rate=FVal('400'),
        rate_asset=A_EUR,
        link='foo',
        notes='we give a rate here',
    ), LedgerAction(
        identifier=3,
        timestamp=1447279735,  # 0.4 EUR per XMR
        action_type=LedgerActionType.DIVIDENDS_INCOME,
        location=Location.KRAKEN,
        asset=A_XMR,
        amount=AssetAmount(FVal(10)),
        rate=None,
        rate_asset=None,
        link=None,
        notes=None,
    ), LedgerAction(
        identifier=4,
        timestamp=1457279735,  # 1 EUR per ETH
        action_type=LedgerActionType.EXPENSE,
        location=Location.EXTERNAL,
        asset=A_ETH,
        amount=AssetAmount(FVal('0.1')),
        rate=None,
        rate_asset=None,
        link=None,
        notes=None,
    ), LedgerAction(
        identifier=5,
        timestamp=1467279735,  # 420 EUR per BTC
        action_type=LedgerActionType.LOSS,
        location=Location.EXTERNAL,
        asset=A_BTC,
        amount=AssetAmount(FVal('0.1')),
        rate=FVal(500),
        rate_asset=A_USD,
        link='foo2',
        notes='we give a rate here',
    ), LedgerAction(  # after range and should be completely ignored
        identifier=6,
        timestamp=1529693374,
        action_type=LedgerActionType.EXPENSE,
        location=Location.EXTERNAL,
        asset=A_ETH,
        amount=AssetAmount(FVal('0.5')),
        rate=FVal(400),
        rate_asset=A_EUR,
        link='foo3',
        notes='we give a rate here too but doesnt matter',
    )]

    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=history,
    )
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_BTC).is_close('0.9')
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_ETH).is_close('0.9')
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_XMR).is_close('10')
    expected_pnls = PnlTotals({
        # 400 + 0.4*10 - 1*0.1 + 1*0.1 - 1*0.01 - 0.1*500*0.9004 + 0.1*500*0.9004 - 0.1* 400
        AccountingEventType.LEDGER_ACTION: PNL(taxable=FVal('363.99'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 24
0
def assert_csv_export(
    accountant: 'Accountant',
    expected_pnls: PnlTotals,
    google_service: Optional['GoogleService'] = None,
) -> None:
    """Test the contents of the csv export match the actual result

    If google_service exists then it's also uploaded to a sheet to check the formular rendering
    """
    csvexporter = accountant.csvexporter
    if len(accountant.pots[0].processed_events) == 0:
        return  # nothing to do for no events as no csv is generated

    with tempfile.TemporaryDirectory() as tmpdirname:
        tmpdir = Path(tmpdirname)
        # first make sure we export without formulas
        csvexporter.settings = csvexporter.settings._replace(
            pnl_csv_with_formulas=False)
        accountant.csvexporter.export(
            events=accountant.pots[0].processed_events,
            pnls=accountant.pots[0].pnls,
            directory=tmpdir,
        )

        calculated_pnls = PnlTotals()
        expected_csv_data = []
        with open(tmpdir / FILENAME_ALL_CSV, newline='') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                expected_csv_data.append(row)
                if row['type'] == '':
                    continue  # have summaries and reached the end

                event_type = AccountingEventType.deserialize(row['type'])
                taxable = FVal(row['pnl_taxable'])
                free = FVal(row['pnl_free'])
                if taxable != ZERO or free != ZERO:
                    calculated_pnls[event_type] += PNL(taxable=taxable,
                                                       free=free)

        assert_pnl_totals_close(expected_pnls, calculated_pnls)

        # export with formulas and summary
        csvexporter.settings = csvexporter.settings._replace(
            pnl_csv_with_formulas=True,
            pnl_csv_have_summary=True)  # noqa: E501
        accountant.csvexporter.export(
            events=accountant.pots[0].processed_events,
            pnls=accountant.pots[0].pnls,
            directory=tmpdir,
        )
        index = CSV_INDEX_OFFSET
        at_summaries = False
        to_upload_data = []
        with open(tmpdir / FILENAME_ALL_CSV, newline='') as csvfile:
            reader = csv.DictReader(csvfile)
            for row in reader:
                to_upload_data.append(row)

                if at_summaries:
                    _check_summaries_row(row, accountant)
                    continue

                if row['type'] == '':
                    at_summaries = True
                    continue  # have summaries and reached the end

                if row['pnl_taxable'] != '0':
                    value = f'G{index}*H{index}'
                    if row['type'] == AccountingEventType.TRADE and 'Amount out' in row[
                            'notes']:
                        assert row['pnl_taxable'] == f'={value}-J{index}'
                    elif row['type'] == AccountingEventType.FEE:
                        assert row[
                            'pnl_taxable'] == f'={value}+{value}-J{index}'

                if row['pnl_free'] != '0':
                    value = f'F{index}*H{index}'
                    if row['type'] == AccountingEventType.TRADE and 'Amount out' in row[
                            'notes']:
                        assert row['pnl_free'] == f'={value}-L{index}'
                    elif row['type'] == AccountingEventType.FEE:
                        assert row['pnl_free'] == f'={value}+{value}-:{index}'

                index += 1

        if google_service is not None:
            upload_csv_and_check(
                service=google_service,
                csv_data=to_upload_data,
                expected_csv_data=expected_csv_data,
                expected_pnls=expected_pnls,
            )
Esempio n. 25
0
def test_buying_selling_btc_before_bchfork(accountant, google_service):
    history = [
        Trade(
            timestamp=1491593374,  # 04/07/2017
            location=Location.EXTERNAL,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal('6.5'),
            rate=FVal('1128.905'),
            fee=FVal('0.55'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # selling BTC prefork should also reduce the BCH equivalent -- taxable
            timestamp=1500595200,  # 21/07/2017
            location=Location.EXTERNAL,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('0.5'),
            rate=FVal('2380.835'),
            fee=FVal('0.15'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(   # selling BCH after the fork -- taxable
            timestamp=1512693374,  # 08/12/2017
            location=Location.KRAKEN,
            base_asset=A_BCH,  # cryptocompare hourly BCH/EUR price: 995.935
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('2.1'),
            rate=FVal('995.935'),
            fee=FVal('0.26'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(
            timestamp=1514937600,  # 03/01/2018
            location=Location.KRAKEN,
            base_asset=A_BTC,  # cryptocompare hourly BCH/EUR price: 995.935
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('1.2'),
            rate=FVal('12404.88'),
            fee=FVal('0.52'),
            fee_currency=A_EUR,
            link=None,
        ),
    ]

    accounting_history_process(accountant, 1436979735, 1519693374, history)
    no_message_errors(accountant.msg_aggregator)
    amount_bch = FVal(3.9)
    amount_btc = FVal(4.8)
    buys = accountant.pots[0].cost_basis.get_events(A_BCH).acquisitions
    assert len(buys) == 1
    assert buys[0].remaining_amount == amount_bch
    assert buys[0].timestamp == 1491593374
    assert buys[0].rate.is_close('1128.98961538')
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_BCH) == amount_bch
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_BTC) == amount_btc

    expected_pnls = PnlTotals({
        AccountingEventType.TRADE: PNL(taxable=FVal('13877.57646153846153846153846'), free=ZERO),
        AccountingEventType.FEE: PNL(taxable=FVal('-1.48'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 26
0
    if expected != ZERO:
        expected_pnls[AccountingEventType.ASSET_MOVEMENT] = PNL(
            taxable=expected, free=ZERO)  # noqa: E501
    check_pnls_and_csv(accountant, expected_pnls, google_service)


@pytest.mark.parametrize('mocked_price_queries', [prices])
@pytest.mark.parametrize('db_settings, expected', [
    (
        {
            'calculate_past_cost_basis': False,
            'taxfree_after_period': -1
        },
        PnlTotals({
            AccountingEventType.TRADE:
            PNL(taxable=FVal('2292.44'), free=ZERO),
            AccountingEventType.FEE:
            PNL(taxable=FVal('-0.01'), free=ZERO),
        }),
    ),
    (
        {
            'calculate_past_cost_basis': True,
            'taxfree_after_period': -1
        },
        PnlTotals({
            AccountingEventType.TRADE:
            PNL(taxable=FVal('1755.083364282'), free=ZERO),
            AccountingEventType.FEE:
            PNL(taxable=FVal('-0.01'), free=ZERO),
        }),
Esempio n. 27
0
def test_buying_selling_bch_before_bsvfork(accountant, google_service):
    history = [
        Trade(  # 6.5 BTC 6.5 BCH 6.5 BSV
            timestamp=1491593374,  # 04/07/2017
            location=Location.EXTERNAL,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal('6.5'),
            rate=FVal('1128.905'),
            fee=FVal('0.55'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # selling BTC prefork should also reduce the BCH and BSV equivalent -- taxable
            # 6 BTC 6 BCH 6 BSV
            timestamp=1500595200,  # 21/07/2017
            location=Location.EXTERNAL,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('0.5'),
            rate=FVal('2380.835'),
            fee=FVal('0.15'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # selling BCH after the fork should also reduce BSV equivalent -- taxable
            # 6 BTC 3.9 BCH 3.9 BSV
            timestamp=1512693374,  # 08/12/2017
            location=Location.KRAKEN,
            base_asset=A_BCH,  # cryptocompare hourly BCH/EUR price: 995.935
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('2.1'),
            rate=FVal('995.935'),
            fee=FVal('0.26'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # 4.8 BTC 3.9 BCH 3.9 BSV
            timestamp=1514937600,  # 03/01/2018
            location=Location.KRAKEN,
            base_asset=A_BTC,  # cryptocompare hourly BCH/EUR price: 995.935
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('1.2'),
            rate=FVal('12404.88'),
            fee=FVal('0.52'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(  # buying BCH before the BSV fork should increase BSV equivalent
            # 4.8 BTC 4.9 BCH 4.9 BSV
            timestamp=1524937600,
            location=Location.KRAKEN,
            base_asset=A_BCH,  # cryptocompare hourly BCH/EUR price: 1146.98
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=ONE,
            rate=FVal('1146.98'),
            fee=FVal('0.52'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(   # selling BCH before the BSV fork should decrease the BSV equivalent
            # 4.8 BTC 4.6 BCH 4.6 BSV
            timestamp=1525937600,
            location=Location.KRAKEN,
            base_asset=A_BCH,  # cryptocompare hourly BCH/EUR price: 1146.98
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('0.3'),
            rate=FVal('1272.05'),
            fee=FVal('0.52'),
            fee_currency=A_EUR,
            link=None,
        ), Trade(   # selling BCH after the BSV fork should not affect the BSV equivalent
            # 4.8 BTC 4.1 BCH 4.6 BSV
            timestamp=1552304352,
            location=Location.KRAKEN,
            base_asset=A_BCH,  # cryptocompare hourly BCH/EUR price: 114.27
            quote_asset=A_EUR,
            trade_type=TradeType.SELL,
            amount=FVal('0.5'),
            rate=FVal('114.27'),
            fee=FVal('0.52'),
            fee_currency=A_EUR,
            link=None,
        ),
    ]

    accounting_history_process(accountant, 1436979735, 1569693374, history)
    no_message_errors(accountant.msg_aggregator)
    amount_btc = FVal(4.8)
    amount_bch = FVal(4.1)
    amount_bsv = FVal(4.6)
    bch_buys = accountant.pots[0].cost_basis.get_events(A_BCH).acquisitions
    assert len(bch_buys) == 2
    assert sum(x.remaining_amount for x in bch_buys) == amount_bch
    assert bch_buys[0].timestamp == 1491593374
    assert bch_buys[1].timestamp == 1524937600
    bsv_buys = accountant.pots[0].cost_basis.get_events(A_BSV).acquisitions
    assert len(bsv_buys) == 2
    assert sum(x.remaining_amount for x in bsv_buys) == amount_bsv
    assert bsv_buys[0].timestamp == 1491593374
    assert bsv_buys[1].timestamp == 1524937600
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_BCH) == amount_bch
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_BTC) == amount_btc
    assert accountant.pots[0].cost_basis.get_calculated_asset_amount(A_BSV) == amount_bsv
    expected_pnls = PnlTotals({
        AccountingEventType.TRADE: PNL(
            taxable=FVal('13877.57646153846153846153846'),
            free=FVal('-464.4416923076923076923076920'),
        ),
        AccountingEventType.FEE: PNL(taxable=FVal('-3.04'), free=ZERO),
    })
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 28
0
    def add_spend(
        self,
        event_type: AccountingEventType,
        notes: str,
        location: Location,
        timestamp: Timestamp,
        asset: Asset,
        amount: FVal,
        taxable: bool,
        given_price: Optional[Price] = None,
        taxable_amount_ratio: FVal = ONE,
        count_entire_amount_spend: bool = True,
        count_cost_basis_pnl: bool = True,
        extra_data: Optional[Dict[str, Any]] = None,
    ) -> Tuple[FVal, FVal]:
        """Add an asset spend event for the pot and count it in PnL if needed

        If a custom price for the asset should be used it can be passed here via
        given_price. Price is always in profit currency during accounting.

        If taxable_ratio is given then this is how we initialize the taxable and
        free amounts in the case of missing cost basis. By default it's all taxable.

        If count_entire_amount_spend is True then the entire amount is counted as a spend.
        Which means an expense (negative pnl).

        If count_cost_basis_pnl is True then we also count any profit/loss the asset
        may have had compared to when it was acquired.

        Returns (free, taxable) amounts.
        """
        if amount == ZERO:  # do nothing for zero spends
            return ZERO, ZERO

        if asset.is_fiat() and event_type != AccountingEventType.FEE:
            taxable = False

        handle_prefork_asset_spends(
            cost_basis=self.cost_basis,
            asset=asset,
            amount=amount,
            timestamp=timestamp,
        )
        if given_price is not None:
            price = given_price
        else:
            price = self.get_rate_in_profit_currency(
                asset=asset,
                timestamp=timestamp,
            )

        if asset == A_KFEE:
            count_cost_basis_pnl = False
            taxable = False

        spend_cost = None
        if count_cost_basis_pnl:
            spend_cost = self.cost_basis.spend_asset(
                location=location,
                timestamp=timestamp,
                asset=asset,
                amount=amount,
                rate=price,
                taxable_spend=taxable,
            )
        taxable_amount = taxable_amount_ratio * amount
        free_amount = amount - taxable_amount
        if spend_cost:
            taxable_amount = spend_cost.taxable_amount
            free_amount = amount - spend_cost.taxable_amount

        spend_event = ProcessedAccountingEvent(
            type=event_type,
            notes=notes,
            location=location,
            timestamp=timestamp,
            asset=asset,
            taxable_amount=taxable_amount,
            free_amount=free_amount,
            price=price,
            pnl=PNL(),  # filled out later
            cost_basis=spend_cost,
            index=len(self.processed_events),
        )
        if extra_data:
            spend_event.extra_data = extra_data
        # count profit/losses if we are inside the query period
        if timestamp >= self.query_start_ts and taxable:
            self.pnls[event_type] += spend_event.calculate_pnl(
                count_entire_amount_spend=count_entire_amount_spend,
                count_cost_basis_pnl=count_cost_basis_pnl,
            )

        self._add_processed_event(spend_event)
        return free_amount, taxable_amount
Esempio n. 29
0
    def add_acquisition(
            self,  # pylint: disable=unused-argument
            event_type: AccountingEventType,
            notes: str,
            location: Location,
            timestamp: Timestamp,
            asset: Asset,
            amount: FVal,
            taxable: bool,
            given_price: Optional[Price] = None,
            extra_data: Optional[Dict] = None,
            **kwargs:
        Any,  # to be able to consume args given by add_asset_change_event
    ) -> None:
        """Add an asset acquisition event for the pot and count it in PnL if needed.

        If a custom price for the asset should be used it can be passed here via
        given_price. Price is always in profit currency during accounting."""
        if amount == ZERO:  # do nothing for zero acquisitions
            return

        if given_price is not None:
            price = given_price
        else:
            price = self.get_rate_in_profit_currency(asset=asset,
                                                     timestamp=timestamp)

        prefork_events = handle_prefork_asset_acquisitions(
            cost_basis=self.cost_basis,
            location=location,
            timestamp=timestamp,
            asset=asset,
            amount=amount,
            price=price,
            starting_index=len(self.processed_events),
        )
        for prefork_event in prefork_events:
            self._add_processed_event(prefork_event)

        event = ProcessedAccountingEvent(
            type=event_type,
            notes=notes,
            location=location,
            timestamp=timestamp,
            asset=asset,
            taxable_amount=amount,
            free_amount=ZERO,
            price=price,
            pnl=PNL(),  # filled out later
            cost_basis=None,
            index=len(self.processed_events),
        )
        if extra_data:
            event.extra_data = extra_data
        self.cost_basis.obtain_asset(event)
        # count profit/losses if we are inside the query period
        if timestamp >= self.query_start_ts and taxable:
            self.pnls[event_type] += event.calculate_pnl(
                count_entire_amount_spend=False,
                count_cost_basis_pnl=True,
            )

        self._add_processed_event(event)
Esempio n. 30
0
def test_assets_movements_not_accounted_for(accountant, expected,
                                            google_service):
    # asset_movements_list partially copied from
    # rotkehlchen/tests/integration/test_end_to_end_tax_report.py
    history = [
        Trade(
            timestamp=1446979735,
            location=Location.EXTERNAL,
            base_asset=A_BTC,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(82),
            rate=FVal('268.678317859'),
            fee=None,
            fee_currency=None,
            link=None,
        ),
        Trade(
            timestamp=1446979735,
            location=Location.EXTERNAL,
            base_asset=A_ETH,
            quote_asset=A_EUR,
            trade_type=TradeType.BUY,
            amount=FVal(1450),
            rate=FVal('0.2315893'),
            fee=None,
            fee_currency=None,
            link=None,
        ),
        AssetMovement(
            # before query period -- 8.915 * 0.001 = 8.915e-3
            location=Location.KRAKEN,
            category=AssetMovementCategory.WITHDRAWAL,
            address=None,
            transaction_id=None,
            timestamp=Timestamp(1479510304),  # 18/11/2016,
            asset=A_ETH,  # cryptocompare hourly ETH/EUR: 8.915
            amount=FVal('95'),
            fee_asset=A_ETH,
            fee=Fee(FVal('0.001')),
            link='krakenid1',
        ),
        AssetMovement(  # 0.00029*1964.685 = 0.56975865
            location=Location.POLONIEX,
            address='foo',
            transaction_id='0xfoo',
            category=AssetMovementCategory.WITHDRAWAL,
            timestamp=Timestamp(1495969504),  # 28/05/2017,
            asset=A_BTC,  # cryptocompare hourly BTC/EUR: 1964.685
            amount=FVal('8.5'),
            fee_asset=A_BTC,
            fee=Fee(FVal('0.00029')),
            link='poloniexid1',
        ),
    ]

    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals()
    if expected != ZERO:
        expected_pnls[AccountingEventType.ASSET_MOVEMENT] = PNL(
            taxable=expected, free=ZERO)  # noqa: E501
    check_pnls_and_csv(accountant, expected_pnls, google_service)