Exemplo n.º 1
0
def test_add_margin_positions(data_dir, username, caplog):
    """Test that adding and retrieving margin positions from the DB works fine.

    Also duplicates should be ignored and an error returned
    """
    msg_aggregator = MessagesAggregator()
    data = DataHandler(data_dir, msg_aggregator)
    data.unlock(username, '123', create_new=True)

    margin1 = MarginPosition(
        location=Location.BITMEX,
        open_time=1451606400,
        close_time=1451616500,
        profit_loss=FVal('1.0'),
        pl_currency=A_BTC,
        fee=Fee(FVal('0.01')),
        fee_currency=A_EUR,
        link='',
        notes='',
    )
    margin2 = MarginPosition(
        location=Location.BITMEX,
        open_time=1451626500,
        close_time=1451636500,
        profit_loss=FVal('0.5'),
        pl_currency=A_BTC,
        fee=Fee(FVal('0.01')),
        fee_currency=A_EUR,
        link='',
        notes='',
    )
    margin3 = MarginPosition(
        location=Location.POLONIEX,
        open_time=1452636501,
        close_time=1459836501,
        profit_loss=FVal('2.5'),
        pl_currency=A_BTC,
        fee=Fee(FVal('0.01')),
        fee_currency=A_EUR,
        link='',
        notes='',
    )

    # Add and retrieve the first 2 margins. All should be fine.
    data.db.add_margin_positions([margin1, margin2])
    errors = msg_aggregator.consume_errors()
    warnings = msg_aggregator.consume_warnings()
    assert len(errors) == 0
    assert len(warnings) == 0
    returned_margins = data.db.get_margin_positions()
    assert returned_margins == [margin1, margin2]

    # Add the last 2 margins. Since margin2 already exists in the DB it should be
    # ignored and a warning should be logged
    data.db.add_margin_positions([margin2, margin3])
    assert (
        'Did not add "Margin position with id 0a57acc1f4c09da0f194c59c4cd240e6'
        '8e2d36e56c05b3f7115def9b8ee3943f') in caplog.text
    returned_margins = data.db.get_margin_positions()
    assert returned_margins == [margin1, margin2, margin3]
Exemplo n.º 2
0
def assert_cointracking_import_results(rotki: Rotkehlchen):
    """A utility function to help assert on correctness of importing data from cointracking.info"""
    trades = rotki.data.db.get_trades()
    asset_movements = rotki.data.db.get_asset_movements()
    warnings = rotki.msg_aggregator.consume_warnings()
    errors = rotki.msg_aggregator.consume_warnings()
    assert len(errors) == 0
    assert len(warnings) == 3

    expected_trades = [
        Trade(
            timestamp=Timestamp(1566687660),
            location=Location.COINBASE,
            pair=TradePair('ETH_EUR'),
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal('0.05772716')),
            rate=Price(FVal('190.3783245183029963712055123')),
            fee=Fee(ZERO),
            fee_currency=A_ETH,
            link='',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(1567418400),
            location=Location.EXTERNAL,
            pair=TradePair('BTC_USD'),
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal('0.00100000')),
            rate=Price(ZERO),
            fee=Fee(ZERO),
            fee_currency=A_BTC,
            link='',
            notes='Just a small gift from someone',
        )
    ]
    assert expected_trades == trades

    expected_movements = [
        AssetMovement(
            location=Location.POLONIEX,
            category=AssetMovementCategory.DEPOSIT,
            timestamp=Timestamp(1565848620),
            asset=A_XMR,
            amount=AssetAmount(FVal('5')),
            fee_asset=A_XMR,
            fee=Fee(ZERO),
            link='',
        ),
        AssetMovement(
            location=Location.COINBASE,
            category=AssetMovementCategory.WITHDRAWAL,
            timestamp=Timestamp(1566726120),
            asset=A_ETH,
            amount=AssetAmount(FVal('0.05770427')),
            fee_asset=A_ETH,
            fee=Fee(ZERO),
            link='',
        )
    ]
    assert expected_movements == asset_movements
Exemplo n.º 3
0
    def _deserialize_asset_movement(
            self, raw_data: Dict[str, Any]) -> Optional[AssetMovement]:
        """Processes a single deposit/withdrawal from binance and deserializes it

        Can log error/warning and return None if something went wrong at deserialization
        """
        try:
            if 'insertTime' in raw_data:
                category = AssetMovementCategory.DEPOSIT
                time_key = 'insertTime'
                fee = Fee(ZERO)
            else:
                category = AssetMovementCategory.WITHDRAWAL
                time_key = 'applyTime'
                fee = Fee(deserialize_asset_amount(raw_data['transactionFee']))

            timestamp = deserialize_timestamp_from_binance(raw_data[time_key])
            asset = asset_from_binance(raw_data['asset'])
            tx_id = get_key_if_has_val(raw_data, 'txId')
            internal_id = get_key_if_has_val(raw_data, 'id')
            link_str = str(internal_id) if internal_id else str(
                tx_id) if tx_id else ''
            return AssetMovement(
                location=self.location,
                category=category,
                address=deserialize_asset_movement_address(
                    raw_data, 'address', asset),
                transaction_id=tx_id,
                timestamp=timestamp,
                asset=asset,
                amount=deserialize_asset_amount_force_positive(
                    raw_data['amount']),
                fee_asset=asset,
                fee=fee,
                link=link_str,
            )

        except UnknownAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {str(self.location)} deposit/withdrawal with unknown asset '
                f'{e.asset_name}. Ignoring it.', )
        except UnsupportedAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {str(self.location)} deposit/withdrawal with unsupported asset '
                f'{e.asset_name}. Ignoring it.', )
        except (DeserializationError, KeyError) as e:
            msg = str(e)
            if isinstance(e, KeyError):
                msg = f'Missing key entry for {msg}.'
            self.msg_aggregator.add_error(
                f'Error processing a {str(self.location)} deposit/withdrawal. Check logs '
                f'for details. Ignoring it.', )
            log.error(
                f'Error processing a {str(self.location)} deposit/withdrawal',
                asset_movement=raw_data,
                error=msg,
            )

        return None
Exemplo n.º 4
0
def test_add_asset_movements(data_dir, username):
    """Test that adding and retrieving asset movements from the DB works fine.

    Also duplicates should be ignored and an error returned
    """
    msg_aggregator = MessagesAggregator()
    data = DataHandler(data_dir, msg_aggregator)
    data.unlock(username, '123', create_new=True)

    movement1 = AssetMovement(
        location=Location.BITMEX,
        category=AssetMovementCategory.DEPOSIT,
        timestamp=1451606400,
        asset=A_BTC,
        amount=FVal('1.0'),
        fee_asset=A_EUR,
        fee=Fee(FVal('0')),
        link='',
    )
    movement2 = AssetMovement(
        location=Location.POLONIEX,
        category=AssetMovementCategory.WITHDRAWAL,
        timestamp=1451608501,
        asset=A_ETH,
        amount=FVal('1.0'),
        fee_asset=A_EUR,
        fee=Fee(FVal('0.01')),
        link='',
    )
    movement3 = AssetMovement(
        location=Location.BITTREX,
        category=AssetMovementCategory.WITHDRAWAL,
        timestamp=1461708501,
        asset=A_ETH,
        amount=FVal('1.0'),
        fee_asset=A_EUR,
        fee=Fee(FVal('0.01')),
        link='',
    )

    # Add and retrieve the first 2 margins. All should be fine.
    data.db.add_asset_movements([movement1, movement2])
    errors = msg_aggregator.consume_errors()
    warnings = msg_aggregator.consume_warnings()
    assert len(errors) == 0
    assert len(warnings) == 0
    returned_movements = data.db.get_asset_movements()
    assert returned_movements == [movement1, movement2]

    # Add the last 2 movements. Since movement2 already exists in the DB it should be
    # ignored and a warning should be shown
    data.db.add_asset_movements([movement2, movement3])
    errors = msg_aggregator.consume_errors()
    warnings = msg_aggregator.consume_warnings()
    assert len(errors) == 0
    assert len(warnings) == 1
    returned_movements = data.db.get_asset_movements()
    assert returned_movements == [movement1, movement2, movement3]
Exemplo n.º 5
0
def test_query_online_deposits_withdrawals(mock_bitstamp, start_ts, since_id):
    """Test `since_id` value will change depending on `start_ts` value.
    Also tests `db_asset_movements` are sorted by `link` (as int) in ascending
    mode.
    """
    asset_btc = A_BTC
    asset_usd = A_USD
    movements = [
        AssetMovement(
            timestamp=1606901400,
            location=Location.BITSTAMP,
            category=AssetMovementCategory.WITHDRAWAL,
            address=None,
            transaction_id=None,
            asset=asset_usd,
            amount=FVal('10000'),
            fee_asset=asset_usd,
            fee=Fee(FVal('50')),
            link='5',
        ),
        AssetMovement(
            timestamp=1606901400,
            location=Location.BITSTAMP,
            category=AssetMovementCategory.DEPOSIT,
            address=None,
            transaction_id=None,
            asset=asset_btc,
            amount=FVal('0.5'),
            fee_asset=asset_btc,
            fee=Fee(FVal('0.0005')),
            link='2',
        ),
    ]
    database = MagicMock()
    database.get_asset_movements.return_value = movements

    expected_call = call(
        start_ts=start_ts,
        end_ts=2,
        options={
            'since_id': since_id,
            'limit': 1000,
            'sort': 'asc',
            'offset': 0,
        },
        case='asset_movements',
    )
    with patch.object(mock_bitstamp,
                      'db',
                      new_callable=MagicMock(return_value=database)):
        with patch.object(mock_bitstamp,
                          '_api_query_paginated') as mock_api_query_paginated:
            mock_bitstamp.query_online_deposits_withdrawals(
                start_ts=Timestamp(start_ts),
                end_ts=Timestamp(2),
            )
            assert mock_api_query_paginated.call_args == expected_call
Exemplo n.º 6
0
def assert_kraken_asset_movements(
    to_check_list: List[Any],
    deserialized: bool,
    movements_to_check: Optional[Tuple[int, ...]] = None,
):
    expected = [
        AssetMovement(
            location=Location.KRAKEN,
            category=AssetMovementCategory.DEPOSIT,
            address=None,
            transaction_id=None,
            timestamp=Timestamp(1458994442),
            asset=A_BTC,
            amount=FVal('5.0'),
            fee_asset=A_BTC,
            fee=Fee(FVal('0.1')),
            link='1',
        ),
        AssetMovement(
            location=Location.KRAKEN,
            address=None,
            transaction_id=None,
            category=AssetMovementCategory.DEPOSIT,
            timestamp=Timestamp(1448994442),
            asset=A_ETH,
            amount=FVal('10.0'),
            fee_asset=A_ETH,
            fee=Fee(FVal('0.11')),
            link='2',
        ),
        AssetMovement(
            location=Location.KRAKEN,
            category=AssetMovementCategory.WITHDRAWAL,
            address=None,
            transaction_id=None,
            timestamp=Timestamp(1439994442),
            asset=A_ETH,
            amount=FVal('10.0'),
            fee_asset=A_ETH,
            fee=Fee(FVal('0.11')),
            link='5',
        ),
        AssetMovement(
            location=Location.KRAKEN,
            category=AssetMovementCategory.WITHDRAWAL,
            address=None,
            transaction_id=None,
            timestamp=Timestamp(1428994442),
            asset=A_BTC,
            amount=FVal('5.0'),
            fee_asset=A_BTC,
            fee=Fee(FVal('0.1')),
            link='4',
        )
    ]
    assert_asset_movements(expected, to_check_list, deserialized,
                           movements_to_check)
Exemplo n.º 7
0
def test_query_deposits_withdrawals(mock_ftx: Ftx):
    """Test happy path of deposits/withdrawls"""
    with patch.object(mock_ftx.session,
                      'get',
                      side_effect=mock_normal_ftx_query):
        movements = mock_ftx.query_online_deposits_withdrawals(
            start_ts=Timestamp(0),
            end_ts=TEST_END_TS,
        )
    warnings = mock_ftx.msg_aggregator.consume_warnings()
    errors = mock_ftx.msg_aggregator.consume_errors()
    assert len(warnings) == 0
    assert len(errors) == 0

    expected_movements = [
        AssetMovement(
            location=Location.FTX,
            category=AssetMovementCategory.DEPOSIT,
            address='0x541163adf0a2e830d9f940763e912807d1a359f5',
            transaction_id=
            '0xf787fa6b62edf1c97fb3f73f80a5eb7550bbf3dcf4269b9bfb9e8c1c0a3bc1a9',
            timestamp=Timestamp(1612159566),
            asset=A_ETH,
            amount=FVal('20'),
            fee_asset=A_ETH,
            fee=Fee(ZERO),
            link='3',
        ),
        AssetMovement(
            location=Location.FTX,
            category=AssetMovementCategory.WITHDRAWAL,
            timestamp=Timestamp(1612159566),
            address='0x903d12bf2c57a29f32365917c706ce0e1a84cce3',
            transaction_id=
            '0xbb27f24c2a348526fc23767d3d8bb303099e90f253ef9fdbb28ce38c1635d116',
            asset=A_ETH,
            amount=FVal('11.0'),
            fee_asset=A_ETH,
            fee=Fee(ZERO),
            link='1',
        ),
        AssetMovement(
            location=Location.FTX,
            category=AssetMovementCategory.WITHDRAWAL,
            address=None,
            transaction_id=None,
            timestamp=Timestamp(1612159566),
            asset=A_USD,
            amount=FVal('21.0'),
            fee_asset=A_USD,
            fee=Fee(ZERO),
            link='2',
        )
    ]

    assert len(movements) == 3
    assert movements == expected_movements
Exemplo n.º 8
0
def test_deserialize_asset_movement_withdrawal(mock_bitstamp):
    raw_movement = {
        'id': 5,
        'type': '1',
        'datetime': '2020-12-02 09:30:00',
        'btc': '0.00000000',
        'usd': '-10000.00000000',
        'btc_usd': '0.00',
        'fee': '50.00000000',
        'order_id': 2,
        'eur': '0.00',
    }
    asset = A_USD
    movement = AssetMovement(
        timestamp=1606901400,
        location=Location.BITSTAMP,
        category=AssetMovementCategory.WITHDRAWAL,
        address=None,
        transaction_id=None,
        asset=asset,
        amount=FVal('10000'),
        fee_asset=asset,
        fee=Fee(FVal('50')),
        link='5',
    )
    expected_movement = mock_bitstamp._deserialize_asset_movement(raw_movement)
    assert movement == expected_movement

    raw_movement = {
        'id': 5,
        'type': '1',
        'datetime': '2018-03-21 06:46:06.559877',
        'btc': '0',
        'usd': '0',
        'btc_usd': '0.00',
        'fee': '0.1',
        'order_id': 2,
        'eur': '500',
    }
    asset = A_EUR
    movement = AssetMovement(
        timestamp=1521614766,
        location=Location.BITSTAMP,
        category=AssetMovementCategory.WITHDRAWAL,
        address=None,
        transaction_id=None,
        asset=asset,
        amount=FVal('500'),
        fee_asset=asset,
        fee=Fee(FVal('0.1')),
        link='5',
    )
    expected_movement = mock_bitstamp._deserialize_asset_movement(raw_movement)
    assert movement == expected_movement
Exemplo n.º 9
0
def test_deserialize_asset_movement_deposit(mock_bitstamp):
    raw_movement = {
        'id': 2,
        'type': '0',
        'datetime': '2020-12-02 09:30:00',
        'btc': '0.50000000',
        'usd': '0.00000000',
        'btc_usd': '0.00',
        'fee': '0.00050000',
        'order_id': 2,
        'eur': '0.00',
    }
    asset = A_BTC
    movement = AssetMovement(
        timestamp=1606901400,
        location=Location.BITSTAMP,
        category=AssetMovementCategory.DEPOSIT,
        address=None,
        transaction_id=None,
        asset=asset,
        amount=FVal('0.5'),
        fee_asset=asset,
        fee=Fee(FVal('0.0005')),
        link='2',
    )
    expected_movement = mock_bitstamp._deserialize_asset_movement(raw_movement)
    assert movement == expected_movement

    raw_movement = {
        'id': 3,
        'type': '0',
        'datetime': '2018-03-21 06:46:06.559877',
        'btc': '0',
        'usd': '0.00000000',
        'btc_usd': '0.00',
        'fee': '0.1',
        'order_id': 2,
        'gbp': '1000.51',
    }
    asset = A_GBP
    movement = AssetMovement(
        timestamp=1521614766,
        location=Location.BITSTAMP,
        category=AssetMovementCategory.DEPOSIT,
        address=None,
        transaction_id=None,
        asset=asset,
        amount=FVal('1000.51'),
        fee_asset=asset,
        fee=Fee(FVal('0.1')),
        link='3',
    )
    expected_movement = mock_bitstamp._deserialize_asset_movement(raw_movement)
    assert movement == expected_movement
Exemplo n.º 10
0
def trade_from_conversion(trade_a: Dict[str, Any], trade_b: Dict[str, Any]) -> Optional[Trade]:
    """Turn information from a conversion into a trade

    Mary raise:
    - UnknownAsset due to Asset instantiation
    - DeserializationError due to unexpected format of dict entries
    - KeyError due to dict entires missing an expected entry
    """
    # Check that the status is complete
    if trade_a['status'] != 'completed':
        return None

    # Trade b will represent the asset we are converting to
    if trade_b['amount']['amount'].startswith('-'):
        trade_a, trade_b = trade_b, trade_a

    timestamp = deserialize_timestamp_from_date(trade_a['updated_at'], 'iso8601', 'coinbase')
    trade_type = deserialize_trade_type('sell')
    tx_amount = AssetAmount(abs(deserialize_asset_amount(trade_a['amount']['amount'])))
    tx_asset = asset_from_coinbase(trade_a['amount']['currency'], time=timestamp)
    native_amount = deserialize_asset_amount(trade_b['amount']['amount'])
    native_asset = asset_from_coinbase(trade_b['amount']['currency'], time=timestamp)
    amount = tx_amount
    # The rate is how much you get/give in quotecurrency if you buy/sell 1 unit of base currency
    rate = Price(native_amount / tx_amount)

    # Obtain fee amount in the native currency using data from both trades
    amount_after_fee = deserialize_asset_amount(trade_b['native_amount']['amount'])
    amount_before_fee = deserialize_asset_amount(trade_a['native_amount']['amount'])
    # amount_after_fee + amount_before_fee is a negative amount and the fee needs to be positive
    conversion_native_fee_amount = abs(amount_after_fee + amount_before_fee)
    if ZERO not in (tx_amount, conversion_native_fee_amount, amount_before_fee):
        # We have the fee amount in the native currency. To get it in the
        # converted asset we have to get the rate
        asset_native_rate = tx_amount / abs(amount_before_fee)
        fee_amount = Fee(conversion_native_fee_amount / asset_native_rate)
    else:
        fee_amount = Fee(ZERO)
    fee_asset = asset_from_coinbase(trade_a['amount']['currency'], time=timestamp)

    return Trade(
        timestamp=timestamp,
        location=Location.COINBASE,
        # in coinbase you are buying/selling tx_asset for native_asset
        base_asset=tx_asset,
        quote_asset=native_asset,
        trade_type=trade_type,
        amount=amount,
        rate=rate,
        fee=fee_amount,
        fee_currency=fee_asset,
        link=str(trade_a['trade']['id']),
    )
Exemplo n.º 11
0
def assert_poloniex_asset_movements(
        to_check_list: List[Any],
        deserialized: bool,
        movements_to_check: Optional[Tuple[int, ...]] = None,
) -> None:
    expected = [AssetMovement(
        location=Location.POLONIEX,
        category=AssetMovementCategory.WITHDRAWAL,
        address='0xB7E033598Cb94EF5A35349316D3A2e4f95f308Da',
        transaction_id='0xbd4da74e1a0b81c21d056c6f58a5b306de85d21ddf89992693b812bb117eace4',
        timestamp=Timestamp(1468994442),
        asset=A_ETH,
        amount=FVal('10.0'),
        fee_asset=A_ETH,
        fee=Fee(FVal('0.1')),
        link='2',
    ), AssetMovement(
        location=Location.POLONIEX,
        category=AssetMovementCategory.WITHDRAWAL,
        address='131rdg5Rzn6BFufnnQaHhVa5ZtRU1J2EZR',
        transaction_id='2d27ae26fa9c70d6709e27ac94d4ce2fde19b3986926e9f3bfcf3e2d68354ec5',
        timestamp=Timestamp(1458994442),
        asset=A_BTC,
        amount=FVal('5.0'),
        fee_asset=A_BTC,
        fee=Fee(FVal('0.5')),
        link='1',
    ), AssetMovement(
        location=Location.POLONIEX,
        category=AssetMovementCategory.DEPOSIT,
        address='131rdg5Rzn6BFufnnQaHhVa5ZtRU1J2EZR',
        transaction_id='b05bdec7430a56b5a5ed34af4a31a54859dda9b7c88a5586bc5d6540cdfbfc7a',
        timestamp=Timestamp(1448994442),
        asset=A_BTC,
        amount=FVal('50.0'),
        fee_asset=A_BTC,
        fee=Fee(FVal('0')),
        link='1',
    ), AssetMovement(
        location=Location.POLONIEX,
        category=AssetMovementCategory.DEPOSIT,
        address='0xB7E033598Cb94EF5A35349316D3A2e4f95f308Da',
        transaction_id='0xf7e7eeb44edcad14c0f90a5fffb1cbb4b80e8f9652124a0838f6906ca939ccd2',
        timestamp=Timestamp(1438994442),
        asset=A_ETH,
        amount=FVal('100.0'),
        fee_asset=A_ETH,
        fee=Fee(FVal('0')),
        link='2',
    )]
    assert_asset_movements(expected, to_check_list, deserialized, movements_to_check)
Exemplo n.º 12
0
def deserialize_fee(fee: Optional[str]) -> Fee:
    """Deserializes a fee from a json entry. Fee in the JSON entry can also be null
    in which case a ZERO fee is returned.

    Can throw DeserializationError if the fee is not as expected
    """
    if fee is None:
        return Fee(ZERO)

    try:
        result = Fee(FVal(fee))
    except ValueError as e:
        raise DeserializationError(f'Failed to deserialize a fee entry due to: {str(e)}') from e

    return result
Exemplo n.º 13
0
def _trade_from_independentreserve(raw_trade: Dict) -> Trade:
    """Convert IndependentReserve raw data to a trade

    https://www.independentreserve.com/products/api#GetClosedFilledOrders
    May raise:
    - DeserializationError
    - UnknownAsset
    - KeyError
    """
    log.debug(f'Processing raw IndependentReserve trade: {raw_trade}')
    trade_type = TradeType.BUY if 'Bid' in raw_trade['OrderType'] else TradeType.SELL
    base_asset = independentreserve_asset(raw_trade['PrimaryCurrencyCode'])
    quote_asset = independentreserve_asset(raw_trade['SecondaryCurrencyCode'])
    amount = FVal(raw_trade['Volume']) - FVal(raw_trade['Outstanding'])
    timestamp = deserialize_timestamp_from_date(
        date=raw_trade['CreatedTimestampUtc'],
        formatstr='iso8601',
        location='IndependentReserve',
    )
    rate = Price(FVal(raw_trade['AvgPrice']))
    fee_amount = FVal(raw_trade['FeePercent']) * amount
    fee_asset = base_asset
    return Trade(
        timestamp=timestamp,
        location=Location.INDEPENDENTRESERVE,
        base_asset=base_asset,
        quote_asset=quote_asset,
        trade_type=trade_type,
        amount=AssetAmount(amount),
        rate=rate,
        fee=Fee(fee_amount),
        fee_currency=fee_asset,
        link=str(raw_trade['OrderGuid']),
    )
Exemplo n.º 14
0
def mock_exchange_data_in_db(exchanges, rotki) -> None:
    db = rotki.data.db
    for exchange_name in exchanges:
        db.add_trades([
            Trade(
                timestamp=Timestamp(1),
                location=deserialize_location(exchange_name),
                base_asset=A_BTC,
                quote_asset=A_ETH,
                trade_type=TradeType.BUY,
                amount=AssetAmount(FVal(1)),
                rate=Price(FVal(1)),
                fee=Fee(FVal('0.1')),
                fee_currency=A_ETH,
                link='foo',
                notes='boo',
            )
        ])
        db.update_used_query_range(name=f'{exchange_name}_trades',
                                   start_ts=0,
                                   end_ts=9999)
        db.update_used_query_range(name=f'{exchange_name}_margins',
                                   start_ts=0,
                                   end_ts=9999)
        db.update_used_query_range(name=f'{exchange_name}_asset_movements',
                                   start_ts=0,
                                   end_ts=9999)  # noqa: E501
Exemplo n.º 15
0
 def get_fee_in_profit_currency(self, trade: Trade) -> Fee:
     fee_rate = self.query_historical_price(
         from_asset=trade.fee_currency,
         to_asset=self.profit_currency,
         timestamp=trade.timestamp,
     )
     return Fee(fee_rate * trade.fee)
Exemplo n.º 16
0
def test_deserialize_trade_buy(mock_bitfinex):
    mock_bitfinex.currency_map = {
        'WBT': 'WBTC',
        'UST': 'USDt',
    }
    mock_bitfinex.pair_bfx_symbols_map = {'WBTUST': ('WBT', 'UST')}
    raw_result = [
        399251013,
        'tWBT:UST',
        1573485493000,
        33963608932,
        0.26334268,
        187.37,
        'LIMIT',
        None,
        -1,
        -0.09868591,
        'USD',
    ]
    expected_trade = Trade(
        timestamp=Timestamp(1573485493),
        location=Location.BITFINEX,
        base_asset=A_WBTC,
        quote_asset=A_USDT,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('0.26334268')),
        rate=Price(FVal('187.37')),
        fee=Fee(FVal('0.09868591')),
        fee_currency=A_USD,
        link='399251013',
        notes='',
    )
    trade = mock_bitfinex._deserialize_trade(raw_result=raw_result)
    assert trade == expected_trade
Exemplo n.º 17
0
def test_deserialize_v1_trade(mock_kucoin):
    raw_result = {
        'id': 'xxxx',
        'symbol': 'NANO-ETH',
        'dealPrice': '0.015743',
        'dealValue': '0.00003441',
        'amount': '0.002186',
        'fee': '0.00000003',
        'side': 'sell',
        'createdAt': 1520471876,
    }
    expected_trade = Trade(
        timestamp=Timestamp(1520471876),
        location=Location.KUCOIN,
        pair=TradePair('NANO_ETH'),
        trade_type=TradeType.SELL,
        amount=AssetAmount(FVal('0.002186')),
        rate=Price(FVal('0.015743')),
        fee=Fee(FVal('0.00000003')),
        fee_currency=Asset('ETH'),
        link='xxxx',
        notes='',
    )
    trade = mock_kucoin._deserialize_trade(
        raw_result=raw_result,
        case=KucoinCase.OLD_TRADES,
    )
    assert trade == expected_trade
Exemplo n.º 18
0
def test_deserialize_trade_sell(mock_bitfinex):
    mock_bitfinex.currency_map = {'UST': 'USDt'}
    mock_bitfinex.pair_bfx_symbols_map = {'ETHUST': ('ETH', 'UST')}
    raw_result = [
        399251013,
        'tETHUST',
        1573485493000,
        33963608932,
        -0.26334268,
        187.37,
        'LIMIT',
        None,
        -1,
        -0.09868591,
        'USD',
    ]
    expected_trade = Trade(
        timestamp=Timestamp(1573485493),
        location=Location.BITFINEX,
        pair=TradePair('ETH_USDT'),
        trade_type=TradeType.SELL,
        amount=AssetAmount(FVal('0.26334268')),
        rate=Price(FVal('187.37')),
        fee=Fee(FVal('0.09868591')),
        fee_currency=Asset('USD'),
        link='399251013',
        notes='',
    )
    trade = mock_bitfinex._deserialize_trade(raw_result=raw_result)
    assert trade == expected_trade
Exemplo n.º 19
0
def test_get_associated_locations(
    rotkehlchen_api_server_with_exchanges,
    added_exchanges,
    ethereum_accounts,  # pylint: disable=unused-argument
    start_with_valid_premium,  # pylint: disable=unused-argument
):
    rotki = rotkehlchen_api_server_with_exchanges.rest_api.rotkehlchen
    mock_exchange_data_in_db(added_exchanges, rotki)
    db = rotki.data.db

    db.add_trades([Trade(
        timestamp=Timestamp(1595833195),
        location=Location.NEXO,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('1.0')),
        rate=Price(FVal('281.14')),
        fee=Fee(ZERO),
        fee_currency=A_EUR,
        link='',
        notes='',
    )])

    # get locations
    response = requests.get(
        api_url_for(
            rotkehlchen_api_server_with_exchanges,
            'associatedlocations',
        ),
    )
    result = assert_proper_response_with_result(response)
    assert set(result) == {'nexo', 'binance', 'poloniex'}
Exemplo n.º 20
0
def asset_movements_from_dictlist(
        given_data: List[Dict[str, Any]],
        start_ts: Timestamp,
        end_ts: Timestamp,
) -> List[AssetMovement]:
    """ Gets a list of dict asset movements, most probably read from the json files and
    a time period. Returns it as a list of the AssetMovement tuples that are inside the time period

    May raise:
        - KeyError: If the given_data dict contains data in an unexpected format
    """
    returned_movements = list()
    for movement in given_data:
        if movement['timestamp'] < start_ts:
            continue
        if movement['timestamp'] > end_ts:
            break

        returned_movements.append(AssetMovement(
            exchange=movement['exchange'],
            category=movement['category'],
            timestamp=movement['timestamp'],
            asset=Asset(movement['asset']),
            amount=FVal(movement['amount']),
            fee=Fee(FVal(movement['fee'])),
        ))
    return returned_movements
Exemplo n.º 21
0
def test_deserialize_asset_movement_deposit(mock_bitstamp):
    raw_movement = {
        'id': 2,
        'type': 0,
        'datetime': '2020-12-02 09:30:00',
        'btc': '0.50000000',
        'usd': '0.00000000',
        'btc_usd': '0.00',
        'fee': '0.00050000',
        'order_id': 2,
        'eur': "0.00",
    }
    asset = Asset('BTC')
    movement = AssetMovement(
        timestamp=1606901400,
        location=Location.BITSTAMP,
        category=AssetMovementCategory.DEPOSIT,
        address=None,
        transaction_id=None,
        asset=asset,
        amount=FVal('0.5'),
        fee_asset=asset,
        fee=Fee(FVal('0.0005')),
        link='2',
    )
    expected_movement = mock_bitstamp._deserialize_asset_movement(raw_movement)
    assert movement == expected_movement
Exemplo n.º 22
0
    def query_online_deposits_withdrawals(
        self,
        start_ts: Timestamp,
        end_ts: Timestamp,
    ) -> List[AssetMovement]:
        result = self._get_paginated_query(
            endpoint='transfers',
            start_ts=start_ts,
            end_ts=end_ts,
        )
        movements = []
        for entry in result:
            try:
                timestamp = deserialize_timestamp(entry['timestampms'])
                timestamp = Timestamp(int(timestamp / 1000))
                asset = Asset(entry['currency'])

                movement = AssetMovement(
                    location=Location.GEMINI,
                    category=deserialize_asset_movement_category(
                        entry['type']),
                    address=deserialize_asset_movement_address(
                        entry, 'destination', asset),
                    transaction_id=get_key_if_has_val(entry, 'txHash'),
                    timestamp=timestamp,
                    asset=asset,
                    amount=deserialize_asset_amount_force_positive(
                        entry['amount']),
                    fee_asset=asset,
                    # Gemini does not include withdrawal fees neither in the API nor in their UI
                    fee=Fee(ZERO),
                    link=str(entry['eid']),
                )
            except UnknownAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found gemini deposit/withdrawal with unknown asset '
                    f'{e.asset_name}. Ignoring it.', )
                continue
            except UnsupportedAsset as e:
                self.msg_aggregator.add_warning(
                    f'Found gemini deposit/withdrawal with unsupported asset '
                    f'{e.asset_name}. Ignoring it.', )
                continue
            except (DeserializationError, KeyError) as e:
                msg = str(e)
                if isinstance(e, KeyError):
                    msg = f'Missing key entry for {msg}.'
                self.msg_aggregator.add_error(
                    'Error processing a gemini deposit/withdrawal. Check logs '
                    'for details. Ignoring it.', )
                log.error(
                    'Error processing a gemini deposit_withdrawal',
                    asset_movement=entry,
                    error=msg,
                )
                continue

            movements.append(movement)

        return movements
Exemplo n.º 23
0
def test_deserialize_asset_movement_withdrawal(mock_kucoin):
    raw_result = {
        'id': '5c2dc64e03aa675aa263f1ac',
        'address': '0x5bedb060b8eb8d823e2414d82acce78d38be7fe9',
        'memo': '',
        'currency': 'ETH',
        'amount': 1,
        'fee': 0.01,
        'walletTxId': '3e2414d82acce78d38be7fe9',
        'isInner': False,
        'status': 'SUCCESS',
        'remark': 'test',
        'createdAt': 1612556794259,
        'updatedAt': 1612556795000,
    }
    expected_asset_movement = AssetMovement(
        timestamp=Timestamp(1612556794),
        location=Location.KUCOIN,
        category=AssetMovementCategory.WITHDRAWAL,
        address='0x5bedb060b8eb8d823e2414d82acce78d38be7fe9',
        transaction_id='3e2414d82acce78d38be7fe9',
        asset=Asset('ETH'),
        amount=AssetAmount(FVal('1')),
        fee_asset=Asset('ETH'),
        fee=Fee(FVal('0.01')),
        link='5c2dc64e03aa675aa263f1ac',
    )
    asset_movement = mock_kucoin._deserialize_asset_movement(
        raw_result=raw_result,
        case=KucoinCase.WITHDRAWALS,
    )
    assert asset_movement == expected_asset_movement
Exemplo n.º 24
0
def test_int_overflow_at_tuple_insertion(database, caplog):
    """Test that if somehow an int that will overflow makes it there we handle it

    Related: https://github.com/rotki/rotki/issues/2175
    """
    caplog.set_level(logging.INFO)
    database.add_asset_movements([
        AssetMovement(
            location=Location.BITTREX,
            category=AssetMovementCategory.DEPOSIT,
            timestamp=177778,
            address='0xfoo',
            transaction_id=99999999999999999999999999999999999999999,
            asset=A_BTC,
            amount=FVal(1),
            fee_asset=A_BTC,
            fee=Fee(FVal('0.0001')),
            link='a link',
        )
    ])

    errors = database.msg_aggregator.consume_errors()
    assert len(errors) == 1
    assert 'Failed to add "asset_movement" to the DB with overflow error' in errors[
        0]
    assert 'Overflow error while trying to add "asset_movement" tuples to the DB. Tuples:' in caplog.text  # noqa: E501
Exemplo n.º 25
0
    def _deserialize_asset_movement(self, raw_result: List[Any]) -> AssetMovement:
        """Process an asset movement (i.e. deposit or withdrawal) from Bitfinex
        and deserialize it.

        Bitfinex support confirmed the following in regards to
        DESTINATION_ADDRESS and TRANSACTION_ID:
          - Fiat movement: both attributes won't show any value.
          - Cryptocurrency movement: address and tx id on the blockchain.

        Timestamp is from MTS_STARTED (when the movement was created), and not
        from MTS_UPDATED (when it was completed/cancelled).

        Can raise DeserializationError.

        Schema reference in:
        https://docs.bitfinex.com/reference#rest-auth-movements
        """
        if raw_result[9] != 'COMPLETED':
            raise DeserializationError(
                f'Unexpected bitfinex movement with status: {raw_result[5]}. '
                f'Only completed movements are processed. Raw movement: {raw_result}',
            )
        try:
            fee_asset = asset_from_bitfinex(
                bitfinex_name=raw_result[1],
                currency_map=self.currency_map,
            )
        except (UnknownAsset, UnsupportedAsset) as e:
            asset_tag = 'Unknown' if isinstance(e, UnknownAsset) else 'Unsupported'
            raise DeserializationError(
                f'{asset_tag} {e.asset_name} found while processing movement asset '
                f'due to: {str(e)}',
            ) from e

        amount = deserialize_asset_amount(raw_result[12])
        category = (
            AssetMovementCategory.DEPOSIT
            if amount > ZERO
            else AssetMovementCategory.WITHDRAWAL
        )
        address = None
        transaction_id = None
        if fee_asset.is_fiat() is False:
            address = str(raw_result[16])
            transaction_id = str(raw_result[20])

        asset_movement = AssetMovement(
            timestamp=Timestamp(int(raw_result[5] / 1000)),
            location=Location.BITFINEX,
            category=category,
            address=address,
            transaction_id=transaction_id,
            asset=fee_asset,
            amount=abs(amount),
            fee_asset=fee_asset,
            fee=Fee(abs(deserialize_fee(raw_result[13]))),
            link=str(raw_result[0]),
        )
        return asset_movement
Exemplo n.º 26
0
    def _deserialize_asset_movement(
            self, raw_data: Dict[str, Any]) -> Optional[AssetMovement]:
        """Processes a single deposit/withdrawal from bittrex and deserializes it

        Can log error/warning and return None if something went wrong at deserialization
        """
        try:
            if raw_data['status'] != 'COMPLETED':
                # Don't mind failed/in progress asset movements
                return None

            if 'source' in raw_data:
                category = AssetMovementCategory.DEPOSIT
                fee = Fee(ZERO)
            else:
                category = AssetMovementCategory.WITHDRAWAL
                fee = deserialize_fee(raw_data.get('txCost', 0))

            timestamp = deserialize_timestamp_from_date(
                date=raw_data['completedAt'],  # we only check completed orders
                formatstr='iso8601',
                location='bittrex',
            )
            asset = asset_from_bittrex(raw_data['currencySymbol'])
            return AssetMovement(
                location=Location.BITTREX,
                category=category,
                address=deserialize_asset_movement_address(
                    raw_data, 'cryptoAddress', asset),
                transaction_id=get_key_if_has_val(raw_data, 'txId'),
                timestamp=timestamp,
                asset=asset,
                amount=deserialize_asset_amount_force_positive(
                    raw_data['quantity']),
                fee_asset=asset,
                fee=fee,
                link=str(raw_data.get('txId', '')),
            )
        except UnknownAsset as e:
            self.msg_aggregator.add_warning(
                f'Found bittrex deposit/withdrawal with unknown asset '
                f'{e.asset_name}. Ignoring it.', )
        except UnsupportedAsset as e:
            self.msg_aggregator.add_warning(
                f'Found bittrex deposit/withdrawal with unsupported asset '
                f'{e.asset_name}. Ignoring it.', )
        except (DeserializationError, KeyError) as e:
            msg = str(e)
            if isinstance(e, KeyError):
                msg = f'Missing key entry for {msg}.'
            self.msg_aggregator.add_error(
                'Unexpected data encountered during deserialization of a bittrex '
                'asset movement. Check logs for details and open a bug report.',
            )
            log.error(
                f'Unexpected data encountered during deserialization of bittrex '
                f'asset_movement {raw_data}. Error was: {str(e)}', )

        return None
Exemplo n.º 27
0
    def _deserialize_trade(self, raw_result: List[Any]) -> Trade:
        """Process a trade result from Bitfinex and deserialize it.

        The base and quote assets are instantiated using the `fee_currency_symbol`
        (from raw_result[10]) over the pair (from raw_result[1]).

        Known pairs format: 'tETHUST', 'tETH:UST'.

        Can raise DeserializationError.

        Schema reference in:
        https://docs.bitfinex.com/reference#rest-auth-trades
        """
        amount = deserialize_asset_amount(raw_result[4])
        trade_type = TradeType.BUY if amount >= ZERO else TradeType.SELL
        bfx_pair = self._process_bfx_pair(raw_result[1])
        if bfx_pair not in self.pair_bfx_symbols_map:
            raise DeserializationError(
                f'Could not deserialize bitfinex trade pair {raw_result[1]}. '
                f'Raw trade: {raw_result}', )

        bfx_base_asset_symbol, bfx_quote_asset_symbol = self.pair_bfx_symbols_map[
            bfx_pair]
        try:
            base_asset = asset_from_bitfinex(
                bitfinex_name=bfx_base_asset_symbol,
                currency_map=self.currency_map,
            )
            quote_asset = asset_from_bitfinex(
                bitfinex_name=bfx_quote_asset_symbol,
                currency_map=self.currency_map,
            )
            fee_asset = asset_from_bitfinex(
                bitfinex_name=raw_result[10],
                currency_map=self.currency_map,
            )
        except (UnknownAsset, UnsupportedAsset) as e:
            asset_tag = 'Unknown' if isinstance(
                e, UnknownAsset) else 'Unsupported'
            raise DeserializationError(
                f'{asset_tag} {e.asset_name} found while processing trade pair due to: {str(e)}',
            ) from e

        trade = Trade(
            timestamp=Timestamp(int(raw_result[2] / 1000)),
            location=Location.BITFINEX,
            base_asset=base_asset,
            quote_asset=quote_asset,
            trade_type=trade_type,
            amount=AssetAmount(abs(amount)),
            rate=deserialize_price(raw_result[5]),
            fee=Fee(abs(deserialize_fee(raw_result[9]))),
            fee_currency=fee_asset,
            link=str(raw_result[0]),
            notes='',
        )
        return trade
Exemplo n.º 28
0
    def _deserialize_asset_movement(
            self, raw_data: Dict[str, Any]) -> Optional[AssetMovement]:
        """Processes a single deposit/withdrawal from binance and deserializes it

        Can log error/warning and return None if something went wrong at deserialization
        """
        try:
            if 'insertTime' in raw_data:
                category = AssetMovementCategory.DEPOSIT
                time_key = 'insertTime'
            else:
                category = AssetMovementCategory.WITHDRAWAL
                time_key = 'applyTime'

            timestamp = deserialize_timestamp_from_binance(raw_data[time_key])
            asset = asset_from_binance(raw_data['asset'])
            location = Location.BINANCE if self.name == str(
                Location.BINANCE) else Location.BINANCE_US  # noqa: E501
            return AssetMovement(
                location=location,
                category=category,
                address=deserialize_asset_movement_address(
                    raw_data, 'address', asset),
                transaction_id=get_key_if_has_val(raw_data, 'txId'),
                timestamp=timestamp,
                asset=asset,
                amount=deserialize_asset_amount_force_positive(
                    raw_data['amount']),
                fee_asset=asset,
                # Binance does not include withdrawal fees neither in the API nor in their UI
                fee=Fee(ZERO),
                link=str(raw_data['txId']),
            )

        except UnknownAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {self.name} deposit/withdrawal with unknown asset '
                f'{e.asset_name}. Ignoring it.', )
        except UnsupportedAsset as e:
            self.msg_aggregator.add_warning(
                f'Found {self.name} deposit/withdrawal with unsupported asset '
                f'{e.asset_name}. Ignoring it.', )
        except (DeserializationError, KeyError) as e:
            msg = str(e)
            if isinstance(e, KeyError):
                msg = f'Missing key entry for {msg}.'
            self.msg_aggregator.add_error(
                f'Error processing a {self.name} deposit/withdrawal. Check logs '
                f'for details. Ignoring it.', )
            log.error(
                f'Error processing a {self.name} deposit_withdrawal',
                asset_movement=raw_data,
                error=msg,
            )

        return None
Exemplo n.º 29
0
    def get_fee_in_profit_currency(self, trade: Trade) -> Fee:
        """Get the profit_currency rate of the fee of the given trade

        May raise:
        - PriceQueryUnsupportedAsset if from/to asset is missing from all price oracles
        - NoPriceForGivenTimestamp if we can't find a price for the asset in the given
        timestamp from the price oracle
        - RemoteError if there is a problem reaching the price oracle server
        or with reading the response returned by the server
        """
        if trade.fee_currency is None or trade.fee is None:
            return Fee(ZERO)

        fee_rate = PriceHistorian().query_historical_price(
            from_asset=trade.fee_currency,
            to_asset=self.profit_currency,
            timestamp=trade.timestamp,
        )
        return Fee(fee_rate * trade.fee)
Exemplo n.º 30
0
def test_assets_movements_not_accounted_for(accountant, expected):
    # asset_movements_list partially copied from
    # rotkehlchen/tests/integration/test_end_to_end_tax_report.py
    asset_movements_list = [
        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',
        )
    ]
    history = []

    result = accounting_history_process(
        accountant,
        1436979735,
        1519693374,
        history,
        asset_movements_list=asset_movements_list,
    )
    assert FVal(result['overview']['asset_movement_fees']).is_close(expected)
    assert FVal(
        result['overview']['total_taxable_profit_loss']).is_close(-expected)
    assert FVal(result['overview']['total_profit_loss']).is_close(-expected)