Esempio n. 1
0
def assert_pnl_totals_close(expected: PnlTotals, got: PnlTotals) -> None:
    # ignore prefork acquisitions for these tests
    got.pop(AccountingEventType.PREFORK_ACQUISITION)

    assert len(expected) == len(got)
    for event_type, expected_pnl in expected.items():
        assert expected_pnl.free.is_close(got[event_type].free)
        assert expected_pnl.taxable.is_close(got[event_type].taxable)
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. 3
0
    def add_report_overview(
        self,
        report_id: int,
        last_processed_timestamp: Timestamp,
        processed_actions: int,
        total_actions: int,
        pnls: PnlTotals,
    ) -> None:
        """Inserts the report overview data

        May raise:
        - InputError if the given report id does not exist
        """
        cursor = self.db.conn_transient.cursor()
        cursor.execute(
            'UPDATE pnl_reports SET last_processed_timestamp=?,'
            ' processed_actions=?, total_actions=? WHERE identifier=?',
            (last_processed_timestamp, processed_actions, total_actions,
             report_id),
        )
        if cursor.rowcount != 1:
            raise InputError(
                f'Could not insert overview for {report_id}. '
                f'Report id could not be found in the DB', )

        tuples = []
        for event_type, entry in pnls.items():
            tuples.append(
                (report_id, event_type.serialize(), str(entry.taxable),
                 str(entry.free)))  # noqa: E501
        cursor.executemany(
            'INSERT OR IGNORE INTO pnl_report_totals(report_id, name, taxable_value, free_value) VALUES(?, ?, ?, ?)',  # noqa: E501
            tuples,
        )
        self.db.conn_transient.commit()
Esempio n. 4
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. 5
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. 6
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. 7
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. 8
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. 9
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. 10
0
 def __init__(
     self,
     database: 'DBHandler',
     evm_accounting_aggregator: 'EVMAccountingAggregator',
     msg_aggregator: MessagesAggregator,
 ) -> None:
     super().__init__(database=database)
     self.profit_currency = self.settings.main_currency
     self.cost_basis = CostBasisCalculator(
         database=database,
         msg_aggregator=msg_aggregator,
     )
     self.pnls = PnlTotals()
     self.processed_events: List[ProcessedAccountingEvent] = []
     self.transactions = TransactionsAccountant(
         evm_accounting_aggregator=evm_accounting_aggregator,
         pot=self,
     )
     self.query_start_ts = self.query_end_ts = Timestamp(0)
Esempio n. 11
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. 12
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. 13
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. 14
0
def test_accounting_works_for_empty_history(accountant, google_service):
    history = []
    accounting_history_process(
        accountant=accountant,
        start_ts=1436979735,
        end_ts=1519693374,
        history_list=history,
    )
    no_message_errors(accountant.msg_aggregator)
    expected_pnls = PnlTotals()
    check_pnls_and_csv(accountant, expected_pnls, google_service)
Esempio n. 15
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. 16
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. 17
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. 18
0
def test_report_settings(database):
    dbreport = DBAccountingReports(database)

    settings = DBSettings(
        main_currency=A_GBP,
        calculate_past_cost_basis=False,
        include_gas_costs=False,
        account_for_assets_movements=True,
        pnl_csv_have_summary=False,
        pnl_csv_with_formulas=True,
        taxfree_after_period=15,
    )
    start_ts = 1
    first_processed_timestamp = 4
    last_processed_timestamp = 9
    end_ts = 10
    report_id = dbreport.add_report(
        first_processed_timestamp=first_processed_timestamp,
        start_ts=start_ts,
        end_ts=end_ts,
        settings=settings,
    )
    total_actions = 10
    processed_actions = 2
    dbreport.add_report_overview(
        report_id=report_id,
        last_processed_timestamp=last_processed_timestamp,
        processed_actions=processed_actions,
        total_actions=total_actions,
        pnls=PnlTotals(),
    )
    data, entries_num = dbreport.get_reports(report_id=report_id, with_limit=False)
    assert len(data) == 1
    assert entries_num == 1
    report = data[0]
    assert report['identifier'] == report_id
    assert report['start_ts'] == start_ts
    assert report['first_processed_timestamp'] == first_processed_timestamp
    assert report['last_processed_timestamp'] == last_processed_timestamp
    assert report['processed_actions'] == processed_actions
    assert report['total_actions'] == total_actions

    returned_settings = report['settings']
    assert len(returned_settings) == 6
    for x in ('account_for_assets_movements', 'calculate_past_cost_basis', 'include_crypto2crypto', 'include_gas_costs', 'profit_currency', 'taxfree_after_period'):  # noqa: E501
        setting_name = 'main_currency' if x == 'profit_currency' else x
        assert returned_settings[x] == getattr(settings, setting_name)
Esempio n. 19
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. 20
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. 21
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. 22
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. 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 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)
Esempio n. 25
0
class AccountingPot(CustomizableDateMixin):
    """
    Represents a single accounting depot for which events are processed
    under a specific set of rules
    """
    def __init__(
        self,
        database: 'DBHandler',
        evm_accounting_aggregator: 'EVMAccountingAggregator',
        msg_aggregator: MessagesAggregator,
    ) -> None:
        super().__init__(database=database)
        self.profit_currency = self.settings.main_currency
        self.cost_basis = CostBasisCalculator(
            database=database,
            msg_aggregator=msg_aggregator,
        )
        self.pnls = PnlTotals()
        self.processed_events: List[ProcessedAccountingEvent] = []
        self.transactions = TransactionsAccountant(
            evm_accounting_aggregator=evm_accounting_aggregator,
            pot=self,
        )
        self.query_start_ts = self.query_end_ts = Timestamp(0)

    def _add_processed_event(self, event: ProcessedAccountingEvent) -> None:
        dbpnl = DBAccountingReports(self.database)
        self.processed_events.append(event)
        try:
            dbpnl.add_report_data(
                report_id=self.report_id,
                time=event.timestamp,
                ts_converter=self.timestamp_to_date,
                event=event,
            )
        except (DeserializationError, InputError) as e:
            log.error(str(e))
            return

        log.debug(event.to_string(self.timestamp_to_date))

    def get_rate_in_profit_currency(self, asset: Asset,
                                    timestamp: Timestamp) -> Price:
        """Get the profit_currency price of asset in the given timestamp

        May raise:
        - PriceQueryUnsupportedAsset if from/to asset is missing from price oracles
        - NoPriceForGivenTimestamp if we can't find a price for the asset in the given
        timestamp from the price oracle
        - RemoteError if there is a problem reaching the price oracle server
        or with reading the response returned by the server
        """
        if asset == self.profit_currency:
            rate = Price(FVal(1))
        else:
            rate = PriceHistorian().query_historical_price(
                from_asset=asset,
                to_asset=self.profit_currency,
                timestamp=timestamp,
            )
        return rate

    def reset(
        self,
        settings: DBSettings,
        start_ts: Timestamp,
        end_ts: Timestamp,
        report_id: int,
    ) -> None:
        self.settings = settings
        self.report_id = report_id
        self.profit_currency = self.settings.main_currency
        self.query_start_ts = start_ts
        self.query_end_ts = end_ts
        self.pnls.reset()
        self.cost_basis.reset(settings)
        self.transactions.reset()
        self.processed_events = []

    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)

    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

    def add_asset_change_event(
        self,
        method: Literal['acquisition', 'spend'],
        event_type: AccountingEventType,
        notes: str,
        location: Location,
        timestamp: Timestamp,
        asset: Asset,
        amount: FVal,
        taxable: bool,
        given_price: Optional[Price] = None,
        **kwargs: Any,
    ) -> None:
        fn = getattr(self, f'add_{method}')
        return fn(
            event_type=event_type,
            notes=notes,
            location=location,
            timestamp=timestamp,
            asset=asset,
            amount=amount,
            taxable=taxable,
            given_price=given_price,
            **kwargs,
        )

    def get_prices_for_swap(
        self,
        timestamp: Timestamp,
        amount_in: FVal,
        asset_in: Asset,
        amount_out: FVal,
        asset_out: Asset,
        fee: Optional[FVal],
        fee_asset: Optional[Asset],
    ) -> Optional[Tuple[Price, Price]]:
        """Calculates the prices for assets going in and out of a swap/trade.

        The rules are:
        - For the asset_in we get the equivalent rate from asset_out + fee if any.
        If there is no price found for fee_currency we ignore it.
        If there is no price for asset_out then we switch to using the asset_in price itself.
        If neither of the 2 assets can have their price known, we bail.

        - For the asset_out we get the equivalent rate from asset_in.
        if there is no price found for asset_in then we switch to using the asset_out price.
        If neither of the 2 assets can have their price known we bail.

        Returns (out_price, in_price) or None if it can't find proper prices
        """
        if ZERO in (amount_in, amount_out):
            log.error(
                f'At get_prices_for_swap got a zero amount. {asset_in=} {amount_in=} '
                f'{asset_out=} {amount_out=}. Skipping ...')
            return None

        try:
            out_price = self.get_rate_in_profit_currency(
                asset=asset_out,
                timestamp=timestamp,
            )
        except (PriceQueryUnsupportedAsset, NoPriceForGivenTimestamp,
                RemoteError):
            out_price = None

        fee_price = None
        if fee is not None and fee_asset is not None and fee != ZERO:
            # also checking fee_asset != None due to https://github.com/rotki/rotki/issues/4172
            try:
                fee_price = self.get_rate_in_profit_currency(
                    asset=fee_asset,
                    timestamp=timestamp,
                )
            except (PriceQueryUnsupportedAsset, NoPriceForGivenTimestamp,
                    RemoteError):
                fee_price = None
        try:
            in_price = self.get_rate_in_profit_currency(
                asset=asset_in,
                timestamp=timestamp,
            )
        except (PriceQueryUnsupportedAsset, NoPriceForGivenTimestamp,
                RemoteError):
            in_price = None

        if out_price is None and in_price is None:
            return None

        if out_price is not None:
            paid = amount_out * out_price
            if fee_price is not None:
                paid += fee_price * fee  # type: ignore # fee should exist here
            calculated_in = Price(paid / amount_in)
        else:
            calculated_in = in_price  # type: ignore # in_price should exist here

        if in_price is not None:
            calculated_out = Price((amount_in * in_price) / amount_out)
        else:
            calculated_out = out_price  # type: ignore # out_price should exist here

        return (calculated_out, calculated_in)
Esempio n. 26
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. 27
0
    def _maybe_add_summary(self, events: List[Dict[str, Any]],
                           pnls: PnlTotals) -> None:
        """Depending on given settings, adds a few summary lines at the end of
        the all events PnL report"""
        if self.settings.pnl_csv_have_summary is False:
            return

        length = len(events) + 1
        template: Dict[str, Any] = {
            'type': '',
            'notes': '',
            'location': '',
            'timestamp': '',
            'asset': '',
            'free_amount': '',
            'taxable_amount': '',
            'price': '',
            'pnl_taxable': '',
            'cost_basis_taxable': '',
            'pnl_free': '',
            'cost_basis_free': '',
        }
        events.append(template)  # separate with 2 new lines
        events.append(template)

        entry = template.copy()
        entry['taxable_amount'] = 'TAXABLE'
        entry['price'] = 'FREE'
        events.append(entry)

        start_sums_index = length + 4
        sums = 0
        for name, value in pnls.items():
            if value.taxable == ZERO and value.free == ZERO:
                continue
            sums += 1
            entry = template.copy()
            entry['free_amount'] = f'{str(name)} total'
            entry['taxable_amount'] = self._add_sumif_formula(
                check_range=f'A2:A{length}',
                condition=f'"{str(name)}"',
                sum_range=f'I2:I{length}',
                actual_value=value.taxable,
            )
            entry['price'] = self._add_sumif_formula(
                check_range=f'A2:A{length}',
                condition=f'"{str(name)}"',
                sum_range=f'J2:J{length}',
                actual_value=value.free,
            )
            events.append(entry)

        entry = template.copy()
        entry['free_amount'] = 'TOTAL'
        if sums != 0:
            entry[
                'taxable_amount'] = f'=SUM(G{start_sums_index}:G{start_sums_index+sums-1})'
            entry[
                'price'] = f'=SUM(H{start_sums_index}:H{start_sums_index+sums-1})'
        else:
            entry['taxable_amount'] = entry['price'] = 0
        events.append(entry)

        events.append(template)  # separate with 2 new lines
        events.append(template)

        version_result = get_current_version(check_for_updates=False)
        entry = template.copy()
        entry['free_amount'] = 'rotki version'
        entry['taxable_amount'] = version_result.our_version
        events.append(entry)

        for setting in ACCOUNTING_SETTINGS:
            entry = template.copy()
            entry['free_amount'] = setting
            entry['taxable_amount'] = str(getattr(self.settings, setting))
            events.append(entry)
Esempio n. 28
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. 29
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. 30
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,
            )