예제 #1
0
def test_get_trade_with_1_token_pool(
        rotkehlchen_api_server,
        ethereum_accounts,  # pylint: disable=unused-argument
        rotki_premium_credentials,  # pylint: disable=unused-argument
        start_with_valid_premium,  # pylint: disable=unused-argument
):
    """
    Test the special case of a swap within an 1 token pool.
    This can probably happen if the controller has since removed tokens from the pool.
    """
    rotki = rotkehlchen_api_server.rest_api.rotkehlchen
    setup = setup_balances(
        rotki,
        ethereum_accounts=ethereum_accounts,
        btc_accounts=None,
        original_queries=['zerion', 'logs', 'blocknobytime'],
    )
    with ExitStack() as stack:
        # patch ethereum/etherscan to not autodetect tokens
        setup.enter_ethereum_patches(stack)
        response = requests.get(
            api_url_for(rotkehlchen_api_server, 'balancertradeshistoryresource'),
            json={
                'from_timestamp': 1621358338,
                'to_timestamp': 1621358340,
            },
        )
        result = assert_proper_response_with_result(response)

    db_trades = rotki.data.db.get_amm_swaps()
    assert len(db_trades) == 29

    address_trades = result[BALANCER_TEST_ADDR4]
    assert len(address_trades) == 1
    assert address_trades[0] == AMMTrade(
        trade_type=TradeType.BUY,
        base_asset=A_WETH,
        quote_asset=A_WBTC,
        amount=AssetAmount(FVal('0.205421420618533148')),
        rate=Price(FVal('0.07606382992071615428519015532')),
        trade_index=0,
        swaps=[
            AMMSwap(
                tx_hash='0x4f9e0d8aa660a5d3db276a1ade038f7027f29838dd22d5276571d2e4ea7131ae',  # noqa: E501
                log_index=84,
                address=string_to_ethereum_address(BALANCER_TEST_ADDR4),  # noqa: E501
                from_address=string_to_ethereum_address('0xFD3dFB524B2dA40c8a6D703c62BE36b5D8540626'),  # noqa: E501
                to_address=string_to_ethereum_address('0x582818356331877553F3E9Cf9557b48e5DdbD54a'),  # noqa: E501
                timestamp=Timestamp(1621358339),
                location=Location.BALANCER,
                token0=A_WBTC,
                token1=A_WETH,
                amount0_in=AssetAmount(FVal('0.01562514')),
                amount1_in=AssetAmount(ZERO),
                amount0_out=AssetAmount(ZERO),
                amount1_out=AssetAmount(FVal('0.205421420618533148')),
            ),
        ],
    ).serialize()
예제 #2
0
def add_trades_from_swaps(
    swaps: List[AMMSwap],
    trades: List[AMMTrade],
    both_in: bool,
    quote_assets: Sequence[Tuple[Any, ...]],
    token_amount: AssetAmount,
    token: EthereumToken,
    trade_index: int,
) -> List[AMMTrade]:
    bought_amount = AssetAmount(token_amount / 2) if both_in else token_amount
    for entry in quote_assets:
        quote_asset = entry[0]
        sold_amount = entry[1]
        rate = sold_amount / bought_amount
        trade = AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=token,
            quote_asset=quote_asset,
            amount=bought_amount,
            rate=rate,
            swaps=swaps,
            trade_index=trade_index,
        )
        trades.append(trade)
        trade_index += 1

    return trades
예제 #3
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=A_ETH,
        amount=AssetAmount(FVal('1')),
        fee_asset=A_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
예제 #4
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']),
    )
예제 #5
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'}
예제 #6
0
    def process_entry(
        self,
        db: DBHandler,
        db_ledger: DBLedgerActions,
        timestamp: Timestamp,
        data: BinanceCsvRow,
    ) -> None:
        amount = data['Change']
        asset = data['Coin']
        category = AssetMovementCategory.DEPOSIT if data['Operation'] == 'Deposit' else AssetMovementCategory.WITHDRAWAL  # noqa: E501
        if category == AssetMovementCategory.WITHDRAWAL:
            amount = -amount

        asset_movement = AssetMovement(
            location=Location.BINANCE,
            category=category,
            address=None,
            transaction_id=None,
            timestamp=timestamp,
            asset=asset,
            amount=AssetAmount(amount),
            fee=Fee(ZERO),
            fee_asset=A_USD,
            link=f'Imported from binance CSV file. Binance operation: {data["Operation"]}',
        )
        db.add_asset_movements([asset_movement])
예제 #7
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,
        base_asset=A_ETH,
        quote_asset=A_USDT,
        trade_type=TradeType.SELL,
        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
예제 #8
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,
        base_asset=A_NANO,
        quote_asset=A_ETH,
        trade_type=TradeType.SELL,
        amount=AssetAmount(FVal('0.002186')),
        rate=Price(FVal('0.015743')),
        fee=Fee(FVal('0.00000003')),
        fee_currency=A_ETH,
        link='xxxx',
        notes='',
    )
    trade = mock_kucoin._deserialize_trade(
        raw_result=raw_result,
        case=KucoinCase.OLD_TRADES,
    )
    assert trade == expected_trade
예제 #9
0
def mock_exchange_data_in_db(exchange_locations, rotki) -> None:
    db = rotki.data.db
    for exchange_location in exchange_locations:
        db.add_trades([
            Trade(
                timestamp=Timestamp(1),
                location=exchange_location,
                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'{str(exchange_location)}_trades_{str(exchange_location)}',
            start_ts=0,
            end_ts=9999)  # noqa: E501
        db.update_used_query_range(
            name=f'{str(exchange_location)}_margins_{str(exchange_location)}',
            start_ts=0,
            end_ts=9999)  # noqa: E501
        db.update_used_query_range(
            name=
            f'{str(exchange_location)}_asset_movements_{str(exchange_location)}',
            start_ts=0,
            end_ts=9999)  # noqa: E501
예제 #10
0
def deserialize_asset_amount(amount: AcceptableFValInitInput) -> AssetAmount:
    try:
        result = AssetAmount(FVal(amount))
    except ValueError as e:
        raise DeserializationError(
            f'Failed to deserialize an amount entry: {str(e)}') from e

    return result
예제 #11
0
def deserialize_asset_amount_force_positive(
        amount: AcceptableFValInitInput) -> AssetAmount:
    """Acts exactly like deserialize_asset_amount but also forces the number to be positive

    Is needed for some places like some exchanges that list the withdrawal amounts as
    negative numbers because it's a withdrawal"""
    result = deserialize_asset_amount(amount)
    if result < ZERO:
        result = AssetAmount(abs(result))
    return result
예제 #12
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.
         - UnknownAsset
         - UnsupportedAsset

        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]
        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,
        )

        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
예제 #13
0
    def _deserialize_trade(
        self,
        raw_trade: Dict[str, Any],
    ) -> Trade:
        """Process a trade user transaction from Bitstamp and deserialize it.

        Can raise DeserializationError.
        """
        timestamp = deserialize_timestamp_from_bitstamp_date(
            raw_trade['datetime'])
        trade_pair_data = self._get_trade_pair_data_from_transaction(raw_trade)
        base_asset_amount = deserialize_asset_amount(
            raw_trade[trade_pair_data.base_asset_symbol], )
        quote_asset_amount = deserialize_asset_amount(
            raw_trade[trade_pair_data.quote_asset_symbol], )
        rate = deserialize_price(raw_trade[trade_pair_data.pair])
        fee_currency = trade_pair_data.quote_asset
        if base_asset_amount >= ZERO:
            trade_type = TradeType.BUY
        else:
            if quote_asset_amount < 0:
                raise DeserializationError(
                    f'Unexpected bitstamp trade format. Both base and quote '
                    f'amounts are negative: {raw_trade}', )
            trade_type = TradeType.SELL

        trade = Trade(
            timestamp=timestamp,
            location=Location.BITSTAMP,
            base_asset=trade_pair_data.base_asset,
            quote_asset=trade_pair_data.quote_asset,
            trade_type=trade_type,
            amount=AssetAmount(abs(base_asset_amount)),
            rate=rate,
            fee=deserialize_fee(raw_trade['fee']),
            fee_currency=fee_currency,
            link=str(raw_trade['id']),
            notes='',
        )
        return trade
예제 #14
0
def test_deserialize_v2_trade_sell(mock_kucoin):
    raw_result = {
        'symbol': 'BCHSV-USDT',
        'tradeId': '601da995e0ee8b00063a075c',
        'orderId': '601da9950c92050006bd45c5',
        'counterOrderId': '601da9950c92050006bd457d',
        'side': 'sell',
        'liquidity': 'taker',
        'forceTaker': True,
        'price': '37624.4',
        'size': '0.0013',
        'funds': '48.91172',
        'fee': '0.034238204',
        'feeRate': '0.0007',
        'feeCurrency': 'USDT',
        'stop': '',
        'tradeType': 'TRADE',
        'type': 'market',
        'createdAt': 1612556794259,
    }
    expected_trade = Trade(
        timestamp=Timestamp(1612556794),
        location=Location.KUCOIN,
        base_asset=A_BSV,
        quote_asset=A_USDT,
        trade_type=TradeType.SELL,
        amount=AssetAmount(FVal('0.0013')),
        rate=Price(FVal('37624.4')),
        fee=Fee(FVal('0.034238204')),
        fee_currency=A_USDT,
        link='601da995e0ee8b00063a075c',
        notes='',
    )
    trade = mock_kucoin._deserialize_trade(
        raw_result=raw_result,
        case=KucoinCase.TRADES,
    )
    assert trade == expected_trade
예제 #15
0
def test_deserialize_v2_trade_buy(mock_kucoin):
    raw_result = {
        'symbol': 'KCS-USDT',
        'tradeId': '601da9faf1297d0007efd712',
        'orderId': '601da9fa0c92050006bd83be',
        'counterOrderId': '601bad620c9205000642300f',
        'side': 'buy',
        'liquidity': 'taker',
        'forceTaker': True,
        'price': 1000,
        'size': '0.2',
        'funds': 200,
        'fee': '0.14',
        'feeRate': '0.0007',
        'feeCurrency': 'USDT',
        'stop': '',
        'tradeType': 'TRADE',
        'type': 'market',
        'createdAt': 1612556794259,
    }
    expected_trade = Trade(
        timestamp=Timestamp(1612556794),
        location=Location.KUCOIN,
        base_asset=A_KCS,
        quote_asset=A_USDT,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('0.2')),
        rate=Price(FVal('1000')),
        fee=Fee(FVal('0.14')),
        fee_currency=A_USDT,
        link='601da9faf1297d0007efd712',
        notes='',
    )
    trade = mock_kucoin._deserialize_trade(
        raw_result=raw_result,
        case=KucoinCase.TRADES,
    )
    assert trade == expected_trade
예제 #16
0
def trade_from_bitmex(bitmex_trade: Dict) -> MarginPosition:
    """Turn a bitmex trade returned from bitmex trade history to our common trade
    history format. This only returns margin positions as bitmex only deals in
    margin trading. May raise:

    - KeyError
    - DeserializationError
    """
    close_time = iso8601ts_to_timestamp(bitmex_trade['transactTime'])
    profit_loss = AssetAmount(
        satoshis_to_btc(deserialize_asset_amount(bitmex_trade['amount'])))
    currency = bitmex_to_world(bitmex_trade['currency'])
    fee = deserialize_fee(bitmex_trade['fee'])
    notes = bitmex_trade['address']
    assert currency == A_BTC, 'Bitmex trade should only deal in BTC'

    log.debug(
        'Processing Bitmex Trade',
        timestamp=close_time,
        profit_loss=profit_loss,
        currency=currency,
        fee=fee,
        notes=notes,
    )

    return MarginPosition(
        location=Location.BITMEX,
        open_time=None,
        close_time=close_time,
        profit_loss=profit_loss,
        pl_currency=currency,
        fee=fee,
        fee_currency=A_BTC,
        notes=notes,
        link=str(bitmex_trade['transactID']),
    )
예제 #17
0
from rotkehlchen.utils.version_check import get_current_version

if TYPE_CHECKING:
    from rotkehlchen.accounting.accountant import Accountant
    from rotkehlchen.db.dbhandler import DBHandler
    from rotkehlchen.rotkehlchen import Rotkehlchen
    from rotkehlchen.tests.fixtures.google import GoogleService

history1 = [
    Trade(
        timestamp=Timestamp(1446979735),
        location=Location.EXTERNAL,
        base_asset=A_BTC,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal(82)),
        rate=Price(FVal('268.678317859')),
        fee=None,
        fee_currency=None,
        link=None,
    ),
    Trade(
        timestamp=Timestamp(1446979735),
        location=Location.EXTERNAL,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal(1450)),
        rate=Price(FVal('0.2315893')),
        fee=None,
        fee_currency=None,
예제 #18
0
    def process_trades(
        db: DBHandler,
        timestamp: Timestamp,
        data: List[BinanceCsvRow],
    ) -> List[Trade]:
        """Processes multiple rows data and stores it into rotki's trades
        Each row has format: {'Operation': ..., 'Change': ..., 'Coin': ...}
        Change is amount, Coin is asset
        If amount is negative then this asset is sold, otherwise it's bought
        """
        # Because we can get mixed data (e.g. multiple Buys or Sells on a single timestamp) we need
        # to group it somehow. We are doing it by grouping the highest bought with the highest
        # sold value. We query usd equivalent for each amount because different Sells / Buys
        # may use different assets.

        # If we query price for the first time it can take long, so we would like to avoid it,
        # and therefore we check if all Buys / Sells use the same asset.
        # If so, we can group by original amount.

        # Checking assets
        same_assets = True
        assets: Dict[str, Optional[Asset]] = defaultdict(lambda: None)
        for row in data:
            if row['Operation'] == 'Fee':
                cur_operation = 'Fee'
            elif row['Change'] < 0:
                cur_operation = 'Sold'
            else:
                cur_operation = 'Bought'
            assets[cur_operation] = assets[cur_operation] or row['Coin']
            if assets[cur_operation] != row['Coin']:
                same_assets = False
                break

        # Querying usd value if needed
        if same_assets is False:
            for row in data:
                try:
                    price = PriceHistorian.query_historical_price(
                        from_asset=row['Coin'],
                        to_asset=A_USD,
                        timestamp=timestamp,
                    )
                except NoPriceForGivenTimestamp:
                    # If we can't find price we can't group, so we quit the method
                    log.warning(f'Couldn\'t find price of {row["Coin"]} on {timestamp}')
                    return []
                row['usd_value'] = row['Change'] * price

        # Group rows depending on whether they are fee or not and then sort them by amount
        rows_grouped_by_fee: Dict[bool, List[BinanceCsvRow]] = defaultdict(list)
        for row in data:
            is_fee = row['Operation'] == 'Fee'
            rows_grouped_by_fee[is_fee].append(row)

        for rows_group in rows_grouped_by_fee.values():
            rows_group.sort(key=lambda x: x['Change'] if same_assets else x['usd_value'], reverse=True)  # noqa: E501

        # Grouping by combining the highest sold with the highest bought and the highest fee
        # Using fee only we were provided with fee (checking by "True in rows_by_operation")
        grouped_trade_rows = []
        while len(rows_grouped_by_fee[False]) > 0:
            cur_batch = [rows_grouped_by_fee[False].pop(), rows_grouped_by_fee[False].pop(0)]
            if True in rows_grouped_by_fee:
                cur_batch.append(rows_grouped_by_fee[True].pop())
            grouped_trade_rows.append(cur_batch)

        # Creating trades structures based on grouped rows data
        raw_trades: List[Trade] = []
        for trade_rows in grouped_trade_rows:
            to_asset: Optional[Asset] = None
            to_amount: Optional[AssetAmount] = None
            from_asset: Optional[Asset] = None
            from_amount: Optional[AssetAmount] = None
            fee_asset: Optional[Asset] = None
            fee_amount: Optional[Fee] = None
            trade_type: Optional[TradeType] = None

            for row in trade_rows:
                cur_asset = row['Coin']
                amount = row['Change']
                if row['Operation'] == 'Fee':
                    fee_asset = cur_asset
                    fee_amount = Fee(amount)
                else:
                    trade_type = TradeType.SELL if row['Operation'] == 'Sell' else TradeType.BUY  # noqa:  E501
                    if amount < 0:
                        from_asset = cur_asset
                        from_amount = AssetAmount(-amount)
                    else:
                        to_asset = cur_asset
                        to_amount = amount

            # Validate that we have received proper assets and amounts.
            # There can be no fee, so we don't validate it
            if (
                to_asset is None or from_asset is None or trade_type is None or
                to_amount is None or to_amount == ZERO or
                from_amount is None or from_amount == ZERO
            ):
                log.warning(
                    f'Skipped binance rows {data} because '
                    f'it didn\'t have enough data',
                )
                db.msg_aggregator.add_warning('Skipped some rows because couldn\'t find amounts or it was zero')  # noqa: E501
                continue

            rate = to_amount / from_amount
            trade = Trade(
                timestamp=timestamp,
                location=Location.BINANCE,
                trade_type=trade_type,
                base_asset=to_asset,
                quote_asset=from_asset,
                amount=to_amount,
                rate=Price(rate),
                fee_currency=fee_asset,
                fee=fee_amount,
                link='',
                notes='Imported from binance CSV file. Binance operation: Buy / Sell',
            )
            raw_trades.append(trade)

        # Sometimes we can get absolutely identical trades (including timestamp) but the database
        # allows us to add only one of them. So we combine these trades into a huge single trade
        # First step: group trades
        grouped_trades: Dict[TradeID, List[Trade]] = defaultdict(list)
        for trade in raw_trades:
            grouped_trades[trade.identifier].append(trade)

        # Second step: combine them
        unique_trades = []
        for trades_group in grouped_trades.values():
            result_trade = trades_group[0]
            for trade in trades_group[1:]:
                result_trade.amount = AssetAmount(result_trade.amount + trade.amount)  # noqa: E501
                if result_trade.fee is not None and trade.fee is not None:
                    result_trade.fee = Fee(result_trade.fee + trade.fee)
            unique_trades.append(result_trade)

        return unique_trades
예제 #19
0
def test_associated_locations(database):
    """Test that locations imported in different places are correctly stored in database"""
    # Add trades from different locations
    trades = [Trade(
        timestamp=Timestamp(1595833195),
        location=Location.CRYPTOCOM,
        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_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1587825824),
        location=Location.CRYPTOCOM,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('50.0')),
        rate=Price(FVal('3.521')),
        fee=Fee(ZERO),
        fee_currency=A_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1596014214),
        location=Location.BLOCKFI,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('50.0')),
        rate=Price(FVal('3.521')),
        fee=Fee(ZERO),
        fee_currency=A_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1565888464),
        location=Location.NEXO,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('50.0')),
        rate=Price(FVal('3.521')),
        fee=Fee(ZERO),
        fee_currency=A_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1596014214),
        location=Location.NEXO,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('50.0')),
        rate=Price(FVal('3.521')),
        fee=Fee(ZERO),
        fee_currency=A_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1612051199),
        location=Location.BLOCKFI,
        base_asset=symbol_to_asset_or_token('USDC'),
        quote_asset=symbol_to_asset_or_token('LTC'),
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('6404.6')),
        rate=Price(FVal('151.6283999982779809352223797')),
        fee=None,
        fee_currency=None,
        link='',
        notes='One Time',
    ), Trade(
        timestamp=Timestamp(1595833195),
        location=Location.POLONIEX,
        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_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1596429934),
        location=Location.COINBASE,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('0.00061475')),
        rate=Price(FVal('309.0687271248474989833265555')),
        fee=Fee(ZERO),
        fee_currency=A_USD,
        link='',
        notes='',
    ), Trade(
        timestamp=Timestamp(1596429934),
        location=Location.EXTERNAL,
        base_asset=A_ETH,
        quote_asset=A_EUR,
        trade_type=TradeType.BUY,
        amount=AssetAmount(FVal('1')),
        rate=Price(FVal('320')),
        fee=Fee(ZERO),
        fee_currency=A_USD,
        link='',
        notes='',
    )]

    # Add multiple entries for same exchange + connected exchange
    database.add_trades(trades)
    kraken_api_key1 = ApiKey('kraken_api_key')
    kraken_api_secret1 = ApiSecret(b'kraken_api_secret')
    kraken_api_key2 = ApiKey('kraken_api_key2')
    kraken_api_secret2 = ApiSecret(b'kraken_api_secret2')
    binance_api_key = ApiKey('binance_api_key')
    binance_api_secret = ApiSecret(b'binance_api_secret')

    # add mock kraken and binance
    database.add_exchange('kraken1', Location.KRAKEN, kraken_api_key1, kraken_api_secret1)
    database.add_exchange('kraken2', Location.KRAKEN, kraken_api_key2, kraken_api_secret2)
    database.add_exchange('binance', Location.BINANCE, binance_api_key, binance_api_secret)

    # Add uniswap and sushiswap events
    database.add_amm_events([
        LiquidityPoolEvent(
            tx_hash='0x47ea26957ce09e84a51b51dfdab6a4ac1c3672a372eef77b15ef7677174ac847',
            log_index=23,
            address=ChecksumEthAddress('0x3163Bb273E8D9960Ce003fD542bF26b4C529f515'),
            timestamp=Timestamp(1590011534),
            event_type=EventType.MINT_SUSHISWAP,
            pool_address=ChecksumEthAddress('0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974'),
            token0=EthereumToken('0x514910771AF9Ca656af840dff83E8264EcF986CA'),
            token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            amount0=FVal('3.313676003468974932'),
            amount1=FVal('0.064189269269768657'),
            usd_price=FVal('26.94433946158740371839009166230438'),
            lp_amount=FVal('0.460858304063739927'),
        ),
    ])
    database.add_amm_swaps([
        AMMSwap(
            tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2',
            log_index=208,
            address=ChecksumEthAddress('0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974'),
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'),
            timestamp=Timestamp(1609301469),
            location=Location.UNISWAP,
            token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(FVal('2.6455727132446468')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('1936.810111')),
        ),
    ])
    database.add_balancer_events([
        BalancerEvent(
            tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b3',
            log_index=23,
            address=ChecksumEthAddress('0xa2107FA5B38d9bbd2C461D6EDf11B11A50F6b974'),
            timestamp=Timestamp(1609301469),
            event_type=BalancerBPTEventType.MINT,
            pool_address_token=EthereumToken('0x514910771AF9Ca656af840dff83E8264EcF986CA'),
            lp_balance=Balance(amount=FVal(2), usd_value=FVal(3)),
            amounts=[
                AssetAmount(FVal(1)),
                AssetAmount(FVal(2)),
            ],
        ),
    ])
    expected_locations = {
        Location.KRAKEN,
        Location.BINANCE,
        Location.BLOCKFI,
        Location.NEXO,
        Location.CRYPTOCOM,
        Location.POLONIEX,
        Location.COINBASE,
        Location.EXTERNAL,
        Location.SUSHISWAP,
        Location.UNISWAP,
        Location.BALANCER,
    }

    assert set(database.get_associated_locations()) == expected_locations
예제 #20
0
def get_balancer_test_addr2_expected_trades():
    """In a function since the new(unknown) assets needs to have been loaded in the DB"""
    A_WCRES = EthereumToken.initialize(  # noqa: N806
        address=string_to_ethereum_address('0xa0afAA285Ce85974c3C881256cB7F225e3A1178a'),
        decimals=18,
        symbol='wCRES',
    )
    return [
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_WETH,
            quote_asset=A_AAVE,
            amount=AssetAmount(FVal('1.616934038985744521')),
            rate=Price(FVal('6.963972908793392530935439799')),
            trade_index=1,
            swaps=[
                AMMSwap(
                    tx_hash='0x3c457da9b541ae39a7dc781ab04a03938b98b5649512aec2a2d32635c9bbf589',  # noqa: E501
                    log_index=24,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x7c90a3cd7Ec80dd2F633ed562480AbbEEd3bE546'),  # noqa: E501
                    timestamp=Timestamp(1607008178),
                    location=Location.BALANCER,
                    token0=A_AAVE,
                    token1=A_WETH,
                    amount0_in=AssetAmount(FVal('11.260284842802604032')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('1.616934038985744521')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_AAVE,
            quote_asset=A_WETH,
            amount=AssetAmount(FVal('11.260286362820602094')),
            rate=Price(FVal('0.1416068599966922676173010716')),
            trade_index=0,
            swaps=[
                AMMSwap(
                    tx_hash='0x3c457da9b541ae39a7dc781ab04a03938b98b5649512aec2a2d32635c9bbf589',  # noqa: E501
                    log_index=18,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x70985E557aE0CD6dC88189a532e54FbC61927BAd'),  # noqa: E501
                    timestamp=Timestamp(1607008178),
                    location=Location.BALANCER,
                    token0=A_WETH,
                    token1=A_AAVE,
                    amount0_in=AssetAmount(FVal('1.594533794502600192')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('11.260286362820602094')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_WETH,
            quote_asset=A_SYN,
            amount=AssetAmount(FVal('1.352902561458047718')),
            rate=Price(FVal('724.4303350385182691258363763')),
            trade_index=0,
            swaps=[
                AMMSwap(
                    tx_hash='0x5e235216cb03e4eb234014f5ccf3efbfddd40c4576424e2a8204f1d12b96ed35',  # noqa: E501
                    log_index=143,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x8982E9bBf7AC6A49c434aD81D2fF8e16895318e5'),  # noqa: E501
                    timestamp=Timestamp(1607008218),
                    location=Location.BALANCER,
                    token0=A_SYN,
                    token1=A_WETH,
                    amount0_in=AssetAmount(FVal('980.08365587152306176')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('1.352902561458047718')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_WETH,
            quote_asset=A_WCRES,
            amount=AssetAmount(FVal('0.205709519074945018')),
            rate=Price(FVal('232.7409943164679514496089589')),
            trade_index=0,
            swaps=[
                AMMSwap(
                    tx_hash='0xf54be824b4619777f1db0e3da91b0cd52f6dba730c95a75644e2b085e6ab9824',  # noqa: E501
                    log_index=300,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x10996eC4f3E7A1b314EbD966Fa8b1ad0fE0f8307'),  # noqa: E501
                    timestamp=Timestamp(1607009877),
                    location=Location.BALANCER,
                    token0=A_WCRES,
                    token1=A_WETH,
                    amount0_in=AssetAmount(FVal('47.87703800986513408')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('0.205709519074945018')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_API3,
            quote_asset=A_WETH,
            amount=AssetAmount(FVal('295.881648100500428692')),
            rate=Price(FVal('0.003346787723157288562491614498')),
            trade_index=0,
            swaps=[
                AMMSwap(
                    tx_hash='0xfed4e15051e3ce4dc0d2816f719701e5920e40bf41614b5feaa3c5a6a0186c03',  # noqa: E501
                    log_index=22,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x997c0fc9578a8194EFDdE2E0cD7aa6A69cFCD7c1'),  # noqa: E501
                    timestamp=Timestamp(1607010888),
                    location=Location.BALANCER,
                    token0=A_WETH,
                    token1=A_API3,
                    amount0_in=AssetAmount(FVal('0.990253067370299904')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('295.881648100500428692')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_WETH,
            quote_asset=A_MFT,
            amount=AssetAmount(FVal('0.686544199299304057')),
            rate=Price(FVal('243775.0324093115004367119900')),
            trade_index=0,
            swaps=[
                AMMSwap(
                    tx_hash='0xf0147c4b81098676c08ae20ae5bf8f8b60d0ad79eec484f3f93ac6ab49a3c51c',  # noqa: E501
                    log_index=97,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x2Eb6CfbFFC8785Cd0D9f2d233d0a617bF4269eeF'),  # noqa: E501
                    timestamp=Timestamp(1607015059),
                    location=Location.BALANCER,
                    token0=A_MFT,
                    token1=A_WETH,
                    amount0_in=AssetAmount(FVal('167362.334434612660404224')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('0.686544199299304057')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_WETH,
            quote_asset=A_AAVE,
            amount=AssetAmount(FVal('3.055412574642681758')),
            rate=Price(FVal('6.916116208273240607778771150')),
            trade_index=1,
            swaps=[
                AMMSwap(
                    tx_hash='0x67c0e9a0fdd002d0b9d1cca0c8e4ca4d30435bbf57bbf0091396275efaea414b',  # noqa: E501
                    log_index=37,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x0E552307659E70bF61f918f96AA880Cdec40d7E2'),  # noqa: E501
                    timestamp=Timestamp(1607015339),
                    location=Location.BALANCER,
                    token0=A_AAVE,
                    token1=A_WETH,
                    amount0_in=AssetAmount(FVal('21.131588430448123904')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('3.055412574642681758')),
                ),
            ],
        ),
        AMMTrade(
            trade_type=TradeType.BUY,
            base_asset=A_AAVE,
            quote_asset=A_WETH,
            amount=AssetAmount(FVal('21.131588567541018817')),
            rate=Price(FVal('0.1435213742524287826717337545')),
            trade_index=0,
            swaps=[
                AMMSwap(
                    tx_hash='0x67c0e9a0fdd002d0b9d1cca0c8e4ca4d30435bbf57bbf0091396275efaea414b',  # noqa: E501
                    log_index=31,
                    address=string_to_ethereum_address('0x029f388aC4D5C8BfF490550ce0853221030E822b'),  # noqa: E501
                    from_address=string_to_ethereum_address('0x0000000000007F150Bd6f54c40A34d7C3d5e9f56'),  # noqa: E501
                    to_address=string_to_ethereum_address('0x7c90a3cd7Ec80dd2F633ed562480AbbEEd3bE546'),  # noqa: E501
                    timestamp=Timestamp(1607015339),
                    location=Location.BALANCER,
                    token0=A_WETH,
                    token1=A_AAVE,
                    amount0_in=AssetAmount(FVal('3.0328346313504')),
                    amount1_in=AssetAmount(ZERO),
                    amount0_out=AssetAmount(ZERO),
                    amount1_out=AssetAmount(FVal('21.131588567541018817')),
                ),
            ],
        ),
    ]
예제 #21
0
                token1=A_WETH,
                amount0_in=AssetAmount(FVal('0.01562514')),
                amount1_in=AssetAmount(ZERO),
                amount0_out=AssetAmount(ZERO),
                amount1_out=AssetAmount(FVal('0.205421420618533148')),
            ),
        ],
    ).serialize()


BALANCER_TEST_ADDR3_EXPECTED_HISTORY_POOL1 = (
    BalancerPoolEventsBalance(
        address=BALANCER_TEST_ADDR3,
        pool_address_token=BALANCER_TEST_ADDR3_POOL1,
        profit_loss_amounts=[
            AssetAmount(FVal('-0.039312851799093402')),
            AssetAmount(FVal('0.744372160905819159')),
        ],
        usd_profit_loss=FVal('-0.76584117161052920880190053'),
        events=[
            BalancerEvent(
                tx_hash='0xb9dff9df4e3838c75d354d62c4596d94e5eb8904e07cee07a3b7ffa611c05544',
                log_index=331,
                address=BALANCER_TEST_ADDR3,
                timestamp=Timestamp(1597144247),
                event_type=BalancerBPTEventType.MINT,
                pool_address_token=BALANCER_TEST_ADDR3_POOL1,
                lp_balance=Balance(
                    amount=FVal('0.042569019597126949'),
                    usd_value=FVal('19.779488662371895'),
                ),
예제 #22
0
    def process_kraken_trades(
        self,
        raw_data: List[HistoryBaseEntry],
    ) -> Tuple[List[Trade], Timestamp]:
        """
        Given a list of history events we process them to create Trade objects. The valid
        History events type are
        - Trade
        - Receive
        - Spend
        - Adjustment

        A pair of receive and spend events can be a trade and kraken uses this kind of event
        for instant trades and trades made from the phone app. What we do in order to verify
        that it is a trade is to check if we can find a pair with the same event id.

        Also in some rare occasions Kraken may forcibly adjust something for you.
        Example would be delisting of DAO token and forcible exchange to ETH.

        Returns:
        - The list of trades processed
        - The biggest timestamp of all the trades processed

        May raise:
        - RemoteError if the pairs couldn't be correctly queried
        """
        trades = []
        max_ts = 0
        get_attr = operator.attrgetter('event_identifier')
        adjustments: List[HistoryBaseEntry] = []
        # Create a list of lists where each sublist has the events for the same event identifier
        grouped_events = [
            list(g) for k, g in itertools.groupby(
                sorted(raw_data, key=get_attr), get_attr)
        ]  # noqa: E501
        for trade_parts in grouped_events:
            trade = self.process_kraken_events_for_trade(
                trade_parts, adjustments)
            if trade is None:
                continue

            trades.append(trade)
            max_ts = max(max_ts, trade.timestamp)

        adjustments.sort(key=lambda x: x.timestamp)
        if len(adjustments) % 2 == 0:
            for a1, a2 in pairwise(adjustments):
                if a1.event_subtype is None or a2.event_subtype is None:
                    log.warning(
                        f'Found two kraken adjustment entries without a subtype: {a1} {a2}',
                    )
                    continue

                if a1.event_subtype == HistoryEventSubType.SPEND and a2.event_subtype == HistoryEventSubType.RECEIVE:  # noqa: E501
                    spend_event = a1
                    receive_event = a2
                elif a2.event_subtype == HistoryEventSubType.SPEND and a2.event_subtype == HistoryEventSubType.RECEIVE:  # noqa: E501
                    spend_event = a2
                    receive_event = a1
                else:
                    log.warning(
                        f'Found two kraken adjustment with unmatching subtype {a1} {a2}',
                    )
                    continue

                rate = Price(
                    abs(receive_event.balance.amount /
                        spend_event.balance.amount))
                trade = Trade(
                    timestamp=ts_ms_to_sec(a1.timestamp),
                    location=Location.KRAKEN,
                    base_asset=receive_event.asset,
                    quote_asset=spend_event.asset,
                    trade_type=TradeType.BUY,
                    amount=AssetAmount(receive_event.balance.amount),
                    rate=rate,
                    fee=None,
                    fee_currency=None,
                    link='adjustment' + a1.event_identifier +
                    a2.event_identifier,
                )
                trades.append(trade)

        else:
            log.warning(
                f'Got even number of kraken adjustment historic entries. '
                f'Skipping reading them. {adjustments}', )

        return trades, Timestamp(max_ts)
예제 #23
0
    def _get_events_graph(
        self,
        address: ChecksumEthAddress,
        start_ts: Timestamp,
        end_ts: Timestamp,
        event_type: EventType,
    ) -> List[LiquidityPoolEvent]:
        """Get the address' events (mints & burns) querying the AMM's subgraph
        Each event data is stored in a <LiquidityPoolEvent>.
        """
        address_events: List[LiquidityPoolEvent] = []
        if event_type == self.mint_event:
            query = MINTS_QUERY
            query_schema = 'mints'
        elif event_type == self.burn_event:
            query = BURNS_QUERY
            query_schema = 'burns'
        else:
            log.error(
                f'Unexpected {self.location} event_type: {event_type}. Skipping events query.',
            )
            return address_events

        query_id = '0'
        query_offset = 0
        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$address': 'Bytes!',
            '$start_ts': 'BigInt!',
            '$end_ts': 'BigInt!',
            '$id': 'ID!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': query_offset,
            'address': address.lower(),
            'start_ts': str(start_ts),
            'end_ts': str(end_ts),
            'id': query_id,
        }
        querystr = format_query_indentation(query.format())

        while True:
            try:
                result = self.graph.query(
                    querystr=querystr,
                    param_types=param_types,
                    param_values=param_values,
                )
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e),
                                                     location=self.location), )
                raise
            except AttributeError as e:
                raise ModuleInitializationFailure(
                    f'{self.location} subgraph remote error') from e

            result_data = result[query_schema]

            for event in result_data:
                token0_ = event['pair']['token0']
                token1_ = event['pair']['token1']

                try:
                    token0_deserialized = deserialize_ethereum_address(
                        token0_['id'])
                    token1_deserialized = deserialize_ethereum_address(
                        token1_['id'])
                    pool_deserialized = deserialize_ethereum_address(
                        event['pair']['id'])
                except DeserializationError as e:
                    msg = (
                        f'Failed to deserialize address involved in liquidity pool event for'
                        f' {self.location}. Token 0: {token0_["id"]}, token 1: {token0_["id"]},'
                        f' pair: {event["pair"]["id"]}.')
                    log.error(msg)
                    raise RemoteError(msg) from e

                token0 = get_or_create_ethereum_token(
                    userdb=self.database,
                    symbol=token0_['symbol'],
                    ethereum_address=token0_deserialized,
                    name=token0_['name'],
                    decimals=token0_['decimals'],
                )
                token1 = get_or_create_ethereum_token(
                    userdb=self.database,
                    symbol=token1_['symbol'],
                    ethereum_address=token1_deserialized,
                    name=token1_['name'],
                    decimals=int(token1_['decimals']),
                )
                lp_event = LiquidityPoolEvent(
                    tx_hash=event['transaction']['id'],
                    log_index=int(event['logIndex']),
                    address=address,
                    timestamp=Timestamp(int(event['timestamp'])),
                    event_type=event_type,
                    pool_address=pool_deserialized,
                    token0=token0,
                    token1=token1,
                    amount0=AssetAmount(FVal(event['amount0'])),
                    amount1=AssetAmount(FVal(event['amount1'])),
                    usd_price=Price(FVal(event['amountUSD'])),
                    lp_amount=AssetAmount(FVal(event['liquidity'])),
                )
                address_events.append(lp_event)
                query_id = event['id']

            # Check whether an extra request is needed
            if len(result_data) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            if query_offset == GRAPH_QUERY_SKIP_LIMIT:
                query_offset = 0
                new_query_id = query_id
            else:
                query_offset += GRAPH_QUERY_LIMIT
                new_query_id = '0'
            param_values = {
                **param_values,
                'id': new_query_id,
                'offset': query_offset,
            }

        return address_events
예제 #24
0
def test_query_asset_movements_sandbox(
        sandbox_kuckoin,
        inquirer,  # pylint: disable=unused-argument
):
    """Unfortunately the sandbox environment does not support deposits and
    withdrawals, therefore they must be mocked.

    Below a list of the movements and their timestamps in ascending mode:

    Deposits:
    - deposit 1 - deposit: 1612556651
    - deposit 2 - deposit: 1612556652
    - deposit 3 - deposit: 1612556653 -> skipped, inner deposit

    Withdrawals:
    - withdraw 1: 1612556651 -> skipped, inner withdraw
    - withdraw 2: 1612556652
    - withdraw 3: 1612556656 -> never requested

    By requesting trades from 1612556651 to 1612556654 and patching the time
    step as 2s (via MONTHS_IN_SECONDS) we should get back 3 movements.
    """
    deposits_response_1 = ("""
        {
            "code":"200000",
            "data":{
                "currentPage":1,
                "pageSize":2,
                "totalNum":2,
                "totalPage":1,
                "items":[
                    {
                        "address":"0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
                        "memo":"5c247c8a03aa677cea2a251d",
                        "amount":1,
                        "fee":0.0001,
                        "currency":"KCS",
                        "isInner":false,
                        "walletTxId":"5bbb57386d99522d9f954c5a",
                        "status":"SUCCESS",
                        "remark":"movement 2 - deposit",
                        "createdAt":1612556652000,
                        "updatedAt":1612556652000
                    },
                    {
                        "address":"0x5f047b29041bcfdbf0e4478cdfa753a336ba6989",
                        "memo":"5c247c8a03aa677cea2a251d",
                        "amount":1000,
                        "fee":0.01,
                        "currency":"LINK",
                        "isInner":false,
                        "walletTxId":"5bbb57386d99522d9f954c5b@test",
                        "status":"SUCCESS",
                        "remark":"movement 1 - deposit",
                        "createdAt":1612556651000,
                        "updatedAt":1612556651000
                    }
                ]
            }
        }
        """)
    deposits_response_2 = ("""
        {
            "code":"200000",
            "data":{
                "currentPage":1,
                "pageSize":1,
                "totalNum":1,
                "totalPage":1,
                "items":[
                    {
                        "address":"1DrT5xUaJ3CBZPDeFR2qdjppM6dzs4rsMt",
                        "memo":"",
                        "currency":"BCHSV",
                        "amount":1,
                        "fee":0.1,
                        "walletTxId":"b893c3ece1b8d7cacb49a39ddd759cf407817f6902f566c443ba16614874ada6",
                        "isInner":true,
                        "status":"SUCCESS",
                        "remark":"movement 4 - deposit",
                        "createdAt":1612556653000,
                        "updatedAt":1612556653000
                    }
                ]
            }
        }
        """)
    withdrawals_response_1 = ("""
        {
            "code":"200000",
            "data":{
                "currentPage":1,
                "pageSize":2,
                "totalNum":2,
                "totalPage":1,
                "items":[
                    {
                        "id":"5c2dc64e03aa675aa263f1a4",
                        "address":"1DrT5xUaJ3CBZPDeFR2qdjppM6dzs4rsMt",
                        "memo":"",
                        "currency":"BCHSV",
                        "amount":2.5,
                        "fee":0.25,
                        "walletTxId":"b893c3ece1b8d7cacb49a39ddd759cf407817f6902f566c443ba16614874ada4",
                        "isInner":false,
                        "status":"SUCCESS",
                        "remark":"movement 4 - withdraw",
                        "createdAt":1612556652000,
                        "updatedAt":1612556652000
                    },
                    {
                        "id":"5c2dc64e03aa675aa263f1a3",
                        "address":"0x5bedb060b8eb8d823e2414d82acce78d38be7fe9",
                        "memo":"",
                        "currency":"ETH",
                        "amount":1,
                        "fee":0.01,
                        "walletTxId":"3e2414d82acce78d38be7fe9",
                        "isInner":true,
                        "status":"SUCCESS",
                        "remark":"movement 3 - withdraw",
                        "createdAt":1612556651000,
                        "updatedAt":1612556651000
                    }
                ]
            }
        }
        """)
    withdrawals_response_2 = ("""
        {
            "code":"200000",
            "data":{
                "currentPage":0,
                "pageSize":0,
                "totalNum":0,
                "totalPage":0,
                "items":[]
            }
        }
        """)
    withdrawals_response_3 = ("""
        {
            "code":"200000",
            "data":{
                "currentPage":1,
                "pageSize":1,
                "totalNum":1,
                "totalPage":1,
                "items":[
                    {
                        "id":"5c2dc64e03aa675aa263f1a5",
                        "address":"0x5bedb060b8eb8d823e2414d82acce78d38be7f00",
                        "memo":"",
                        "currency":"KCS",
                        "amount":2.5,
                        "fee":0.25,
                        "walletTxId":"b893c3ece1b8d7cacb49a39ddd759cf407817f6902f566c443ba16614874ada5",
                        "isInner":false,
                        "status":"SUCCESS",
                        "remark":"movement 5 - withdraw",
                        "createdAt":1612556655000,
                        "updatedAt":1612556655000
                    }
                ]
            }
        }
        """)
    expected_asset_movements = [
        AssetMovement(
            location=Location.KUCOIN,
            category=AssetMovementCategory.DEPOSIT,
            timestamp=Timestamp(1612556652),
            address='0x5f047b29041bcfdbf0e4478cdfa753a336ba6989',
            transaction_id='5bbb57386d99522d9f954c5a',
            asset=A_KCS,
            amount=AssetAmount(FVal('1')),
            fee_asset=A_KCS,
            fee=Fee(FVal('0.0001')),
            link='',
        ),
        AssetMovement(
            location=Location.KUCOIN,
            category=AssetMovementCategory.DEPOSIT,
            timestamp=Timestamp(1612556651),
            address='0x5f047b29041bcfdbf0e4478cdfa753a336ba6989',
            transaction_id='5bbb57386d99522d9f954c5b',
            asset=A_LINK,
            amount=AssetAmount(FVal('1000')),
            fee_asset=A_LINK,
            fee=Fee(FVal('0.01')),
            link='',
        ),
        AssetMovement(
            location=Location.KUCOIN,
            category=AssetMovementCategory.WITHDRAWAL,
            timestamp=Timestamp(1612556652),
            address='1DrT5xUaJ3CBZPDeFR2qdjppM6dzs4rsMt',
            transaction_id=
            'b893c3ece1b8d7cacb49a39ddd759cf407817f6902f566c443ba16614874ada4',
            asset=A_BSV,
            amount=AssetAmount(FVal('2.5')),
            fee_asset=A_BSV,
            fee=Fee(FVal('0.25')),
            link='5c2dc64e03aa675aa263f1a4',
        ),
    ]

    def get_endpoints_response():
        results = [
            f'{deposits_response_1}',
            f'{deposits_response_2}',
            f'{withdrawals_response_1}',
            f'{withdrawals_response_2}',
            # if pagination works as expected and the requesting loop is broken,
            # the response below won't be processed
            f'{withdrawals_response_3}',
        ]
        for result_ in results:
            yield result_

    def mock_api_query_response(case, options):  # pylint: disable=unused-argument
        return MockResponse(HTTPStatus.OK, next(get_response))
예제 #25
0
def test_query_trades_sandbox(sandbox_kuckoin, inquirer):  # pylint: disable=unused-argument
    """The sandbox account has 6 trades. Below a list of the trades and their
    timestamps in ascending mode.
    - trade 1: 1612556651 -> skipped
    - trade 2: 1612556693
    - trade 3: 1612556765
    - trade 4: 1612556765
    - trade 5: 1612556765
    - trade 6: 1612556794 -> skipped

    By requesting trades from 1612556693 to 1612556765, the first and last trade
    should be skipped.
    """
    expected_trades = [
        Trade(
            timestamp=Timestamp(1612556765),
            location=Location.KUCOIN,
            base_asset=A_ETH,
            quote_asset=A_BTC,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal('0.02934995')),
            rate=Price(FVal('0.046058')),
            fee=Fee(FVal('9.4625999797E-7')),
            fee_currency=A_BTC,
            link='601da9ddf73c300006194ec6',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(1612556765),
            location=Location.KUCOIN,
            base_asset=A_ETH,
            quote_asset=A_BTC,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal('0.02')),
            rate=Price(FVal('0.04561')),
            fee=Fee(FVal('6.3854E-7')),
            fee_currency=A_BTC,
            link='601da9ddf73c300006194ec5',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(1612556765),
            location=Location.KUCOIN,
            base_asset=A_ETH,
            quote_asset=A_BTC,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal('0.06')),
            rate=Price(FVal('0.0456')),
            fee=Fee(FVal('0.0000019152')),
            fee_currency=A_BTC,
            link='601da9ddf73c300006194ec4',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(1612556693),
            location=Location.KUCOIN,
            base_asset=A_BTC,
            quote_asset=A_USDT,
            trade_type=TradeType.SELL,
            amount=AssetAmount(FVal('0.0013')),
            rate=Price(FVal('37624.4')),
            fee=Fee(FVal('0.034238204')),
            fee_currency=A_USDT,
            link='601da995e0ee8b00063a075c',
            notes='',
        ),
    ]
    trades, _ = sandbox_kuckoin.query_online_trade_history(
        start_ts=Timestamp(1612556693),
        end_ts=Timestamp(1612556765),
    )
    assert trades == expected_trades
예제 #26
0
    def _read_subgraph_trades(
        self,
        address: ChecksumEthAddress,
        start_ts: Timestamp,
        end_ts: Timestamp,
    ) -> List[AMMTrade]:
        """Get the address' trades data querying the AMM subgraph

        Each trade (swap) instantiates an <AMMTrade>.

        The trade pair (i.e. BASE_QUOTE) is determined by `reserve0_reserve1`.
        Translated to AMM lingo:

        Trade type BUY:
        - `asset1In` (QUOTE, reserve1) is gt 0.
        - `asset0Out` (BASE, reserve0) is gt 0.

        Trade type SELL:
        - `asset0In` (BASE, reserve0) is gt 0.
        - `asset1Out` (QUOTE, reserve1) is gt 0.

        May raise
        - RemoteError
        """
        trades: List[AMMTrade] = []
        query_id = '0'
        query_offset = 0
        param_types = {
            '$limit': 'Int!',
            '$offset': 'Int!',
            '$address': 'Bytes!',
            '$start_ts': 'BigInt!',
            '$end_ts': 'BigInt!',
            '$id': 'ID!',
        }
        param_values = {
            'limit': GRAPH_QUERY_LIMIT,
            'offset': 0,
            'address': address.lower(),
            'start_ts': str(start_ts),
            'end_ts': str(end_ts),
            'id': query_id,
        }
        querystr = format_query_indentation(self.swaps_query.format())

        while True:
            try:
                result = self.graph.query(
                    querystr=querystr,
                    param_types=param_types,
                    param_values=param_values,
                )
            except RemoteError as e:
                self.msg_aggregator.add_error(
                    SUBGRAPH_REMOTE_ERROR_MSG.format(error_msg=str(e),
                                                     location=self.location), )
                raise

            for entry in result['swaps']:
                swaps = []
                try:
                    for swap in entry['transaction']['swaps']:
                        timestamp = swap['timestamp']
                        swap_token0 = swap['pair']['token0']
                        swap_token1 = swap['pair']['token1']

                        try:
                            token0_deserialized = deserialize_ethereum_address(
                                swap_token0['id'])
                            token1_deserialized = deserialize_ethereum_address(
                                swap_token1['id'])
                            from_address_deserialized = deserialize_ethereum_address(
                                swap['sender'])  # noqa
                            to_address_deserialized = deserialize_ethereum_address(
                                swap['to'])
                        except DeserializationError:
                            msg = (
                                f'Failed to deserialize addresses in trade from {self.location} graph'  # noqa
                                f' with token 0: {swap_token0["id"]}, token 1: {swap_token1["id"]}, '  # noqa
                                f'swap sender: {swap["sender"]}, swap receiver {swap["to"]}'
                            )
                            log.error(msg)
                            continue

                        token0 = get_or_create_ethereum_token(
                            userdb=self.database,
                            symbol=swap_token0['symbol'],
                            ethereum_address=token0_deserialized,
                            name=swap_token0['name'],
                            decimals=swap_token0['decimals'],
                        )
                        token1 = get_or_create_ethereum_token(
                            userdb=self.database,
                            symbol=swap_token1['symbol'],
                            ethereum_address=token1_deserialized,
                            name=swap_token1['name'],
                            decimals=int(swap_token1['decimals']),
                        )

                        try:
                            amount0_in = FVal(swap['amount0In'])
                            amount1_in = FVal(swap['amount1In'])
                            amount0_out = FVal(swap['amount0Out'])
                            amount1_out = FVal(swap['amount1Out'])
                        except ValueError as e:
                            log.error(
                                f'Failed to read amounts in {self.location} swap {str(swap)}. '
                                f'{str(e)}.', )
                            continue

                        swaps.append(
                            AMMSwap(
                                tx_hash=swap['id'].split('-')[0],
                                log_index=int(swap['logIndex']),
                                address=address,
                                from_address=from_address_deserialized,
                                to_address=to_address_deserialized,
                                timestamp=Timestamp(int(timestamp)),
                                location=self.location,
                                token0=token0,
                                token1=token1,
                                amount0_in=AssetAmount(amount0_in),
                                amount1_in=AssetAmount(amount1_in),
                                amount0_out=AssetAmount(amount0_out),
                                amount1_out=AssetAmount(amount1_out),
                            ))
                    query_id = entry['id']
                except KeyError as e:
                    log.error(
                        f'Failed to read trade in {self.location} swap {str(entry)}. '
                        f'{str(e)}.', )
                    continue

                # with the new logic the list of swaps can be empty, in that case don't try
                # to make trades from the swaps
                if len(swaps) == 0:
                    continue

                # Now that we got all swaps for a transaction, create the trade object
                trades.extend(self._tx_swaps_to_trades(swaps))

            # Check whether an extra request is needed
            if len(result['swaps']) < GRAPH_QUERY_LIMIT:
                break

            # Update pagination step
            if query_offset == GRAPH_QUERY_SKIP_LIMIT:
                query_offset = 0
                new_query_id = query_id
            else:
                query_offset += GRAPH_QUERY_LIMIT
                new_query_id = '0'
            param_values = {
                **param_values,
                'id': new_query_id,
                'offset': query_offset,
            }

        return trades
예제 #27
0
    SushiswapPoolEventsBalance(
        address=string_to_ethereum_address(TEST_EVENTS_ADDRESS_1),
        pool_address=string_to_ethereum_address("0xC3f279090a47e80990Fe3a9c30d24Cb117EF91a8"),
        token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
        token1=EthereumToken('0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF'),
        events=[
            SushiswapPoolEvent(
                tx_hash='0xb226ddb8cbb286a7a998a35263ad258110eed5f923488f03a8d890572cd4608e',
                log_index=137,
                address=string_to_ethereum_address(TEST_EVENTS_ADDRESS_1),
                timestamp=Timestamp(1627401170),
                event_type=EventType.MINT_SUSHISWAP,
                pool_address=string_to_ethereum_address("0xC3f279090a47e80990Fe3a9c30d24Cb117EF91a8"),  # noqa: E501
                token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
                token1=EthereumToken('0xdBdb4d16EdA451D0503b854CF79D55697F90c8DF'),
                amount0=AssetAmount(FVal('0.192426688761441618')),
                amount1=AssetAmount(FVal('1.498665931466140813')),
                usd_price=Price(FVal('874.684787927721190125529172850727')),
                lp_amount=AssetAmount(FVal('0.023925092583833892')),
            ),
        ],
        profit_loss0=AssetAmount(FVal('-0.192426688761441618')),
        profit_loss1=AssetAmount(FVal('-1.498665931466140813')),
        usd_profit_loss=Price(FVal('-874.6847879277211901255291729')),
    ),
]


@pytest.mark.parametrize('ethereum_accounts', [[TEST_EVENTS_ADDRESS_1]])
@pytest.mark.parametrize('ethereum_modules', [['sushiswap']])
@pytest.mark.parametrize('start_with_valid_premium', [True])
예제 #28
0
def test_query_owned_assets(data_dir, username):
    """Test the get_owned_assets with also an unknown asset in the DB"""
    msg_aggregator = MessagesAggregator()
    data = DataHandler(data_dir, msg_aggregator)
    data.unlock(username, '123', create_new=True)

    balances = deepcopy(asset_balances)
    balances.extend([
        DBAssetBalance(
            category=BalanceType.ASSET,
            time=Timestamp(1488326400),
            asset=A_BTC,
            amount='1',
            usd_value='1222.66',
        ),
        DBAssetBalance(
            category=BalanceType.ASSET,
            time=Timestamp(1489326500),
            asset=A_XMR,
            amount='2',
            usd_value='33.8',
        ),
    ])
    data.db.add_multiple_balances(balances)
    data.db.conn.commit()

    # also make sure that assets from trades are included
    data.db.add_trades([
        Trade(
            timestamp=Timestamp(1),
            location=Location.EXTERNAL,
            base_asset=A_ETH,
            quote_asset=A_BTC,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal(1)),
            rate=Price(FVal(1)),
            fee=Fee(FVal('0.1')),
            fee_currency=A_BTC,
            link='',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(99),
            location=Location.EXTERNAL,
            base_asset=A_ETH,
            quote_asset=A_BTC,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal(2)),
            rate=Price(FVal(1)),
            fee=Fee(FVal('0.1')),
            fee_currency=A_BTC,
            link='',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(1),
            location=Location.EXTERNAL,
            base_asset=A_SDC,
            quote_asset=A_SDT2,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal(1)),
            rate=Price(FVal(1)),
            fee=Fee(FVal('0.1')),
            fee_currency=A_BTC,
            link='',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(1),
            location=Location.EXTERNAL,
            base_asset=A_SUSHI,
            quote_asset=A_1INCH,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal(1)),
            rate=Price(FVal(1)),
            fee=Fee(FVal('0.1')),
            fee_currency=A_BTC,
            link='',
            notes='',
        ),
        Trade(
            timestamp=Timestamp(3),
            location=Location.EXTERNAL,
            base_asset=A_SUSHI,
            quote_asset=A_1INCH,
            trade_type=TradeType.BUY,
            amount=AssetAmount(FVal(2)),
            rate=Price(FVal(1)),
            fee=Fee(FVal('0.1')),
            fee_currency=A_BTC,
            link='',
            notes='',
        ),
    ])

    assets_list = data.db.query_owned_assets()
    assert set(assets_list) == {
        A_USD, A_ETH, A_BTC, A_XMR, A_SDC, A_SDT2, A_SUSHI, A_1INCH
    }  # noqa: E501
    assert all(isinstance(x, Asset) for x in assets_list)
    warnings = data.db.msg_aggregator.consume_warnings()
    assert len(warnings) == 0
예제 #29
0
    def process_kraken_events_for_trade(
        self,
        trade_parts: List[HistoryBaseEntry],
        adjustments: List[HistoryBaseEntry],
    ) -> Optional[Trade]:
        """Processes events from trade parts to a trade. If it's an adjustment
        adds it to a separate list"""
        if trade_parts[0].event_type == HistoryEventType.ADJUSTMENT:
            adjustments.append(trade_parts[0])
            return None  # skip as they don't have same refid

        event_id = trade_parts[0].event_identifier
        is_spend_receive = False
        trade_assets = []
        spend_part, receive_part, fee_part, kfee_part = None, None, None, None

        for trade_part in trade_parts:
            if trade_part.event_type == HistoryEventType.RECEIVE:
                is_spend_receive = True
                receive_part = trade_part
            elif trade_part.event_type == HistoryEventType.SPEND:
                if trade_part.event_subtype == HistoryEventSubType.FEE:
                    fee_part = trade_part
                else:
                    is_spend_receive = True
                    spend_part = trade_part
            elif trade_part.event_type == HistoryEventType.TRADE:
                if trade_part.event_subtype == HistoryEventSubType.FEE:
                    fee_part = trade_part
                elif trade_part.asset == A_KFEE:
                    kfee_part = trade_part
                elif trade_part.balance.amount < ZERO:
                    spend_part = trade_part
                else:
                    receive_part = trade_part

            if (trade_part.balance.amount != ZERO
                    and trade_part.event_subtype != HistoryEventSubType.FEE):
                trade_assets.append(trade_part.asset)

        if is_spend_receive and len(trade_parts) < 2:
            log.warning(
                f'Found kraken spend/receive events {event_id} with '
                f'less than 2 parts. {trade_parts}', )
            self.msg_aggregator.add_warning(
                f'Found kraken spend/receive events {event_id} with '
                f'less than 2 parts. Skipping...', )
            return None

        timestamp = ts_ms_to_sec(trade_parts[0].timestamp)
        exchange_uuid = (str(event_id) + str(timestamp))
        if len(trade_assets) != 2:
            # This can happen some times (for lefteris 5 times since start of kraken usage)
            # when the other part of a trade is so small it's 0. So it's either a
            # receive event with no counterpart or a spend event with no counterpart.
            # This happens for really really small amounts. So we add rate 0 trades
            if spend_part is not None:
                base_asset = spend_part.asset
                trade_type = TradeType.SELL
                amount = spend_part.balance.amount * -1
            elif receive_part is not None:
                base_asset = receive_part.asset
                trade_type = TradeType.BUY
                amount = receive_part.balance.amount
            else:
                log.warning(
                    f'Found historic trade entries with no counterpart {trade_parts}'
                )
                return None

            trade = Trade(
                timestamp=timestamp,
                location=Location.KRAKEN,
                base_asset=base_asset,
                quote_asset=A_USD,  # whatever
                trade_type=trade_type,
                amount=AssetAmount(amount),
                rate=Price(ZERO),
                fee=None,
                fee_currency=None,
                link=exchange_uuid,
            )
            return trade

        if spend_part is None or receive_part is None:
            log.error(
                f'Failed to process {event_id}. Couldnt find spend/receive parts {trade_parts}',
            )
            self.msg_aggregator.add_error(
                f'Failed to read trades for event {event_id}. '
                f'More details are available at the logs', )
            return None

        spend_asset = spend_part.asset
        receive_asset = receive_part.asset
        if spend_asset.is_fiat() or trade_parts[0] == receive_part:
            trade_type = TradeType.BUY
            base_asset = receive_asset
            quote_asset = spend_asset
            amount = receive_part.balance.amount
            if amount == ZERO:
                self.msg_aggregator.add_warning(
                    f'Rate for kraken trade couldnt be calculated. Base amount is ZERO '
                    f'for event {event_id}. Skipping event', )
                return None

            rate = Price((spend_part.balance.amount / amount) * -1)
        else:
            trade_type = TradeType.SELL
            base_asset = spend_asset
            quote_asset = receive_asset
            amount = -1 * spend_part.balance.amount
            if amount == ZERO:
                self.msg_aggregator.add_warning(
                    f'Rate for kraken trade couldnt be calculated. Base amount is ZERO '
                    f'for event {event_id}. Skipping event', )
                return None

            rate = Price((receive_part.balance.amount / amount))

        # If kfee was found we use it as the fee for the trade
        if kfee_part is not None and fee_part is None:
            fee = Fee(kfee_part.balance.amount)
            fee_asset = A_KFEE
        elif (None, None) == (fee_part, kfee_part):
            fee = None
            fee_asset = None
        elif fee_part is not None:
            fee = Fee(fee_part.balance.amount)
            fee_asset = fee_part.asset

        trade = Trade(
            timestamp=timestamp,
            location=Location.KRAKEN,
            base_asset=base_asset,
            quote_asset=quote_asset,
            trade_type=trade_type,
            amount=AssetAmount(amount),
            rate=rate,
            fee=fee,
            fee_currency=fee_asset,
            link=exchange_uuid,
        )
        return trade
예제 #30
0
def get_expected_trades():
    """Function so no price (unknown) assets can be resolved only when existing in the DB"""
    address = string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121')
    return [AMMTrade(
        trade_type=TradeType.BUY,
        base_asset=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
        quote_asset=EthereumToken('0x4b4D2e899658FB59b1D518b68fe836B100ee8958'),
        amount=AssetAmount(FVal('796.857811')),
        rate=Price(FVal('0.0008323741932057006980885326353')),
        trade_index=0,
        swaps=[AMMSwap(
            tx_hash='0x962d904d75c751fbff316f7a2ed280bd93241d5088d747a4f26fe7437813512f',
            log_index=141,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'),
            timestamp=Timestamp(1609308616),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0x4b4D2e899658FB59b1D518b68fe836B100ee8958'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(FVal('0.663283877530785731')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('796.857811')),
        )],
    ), AMMTrade(
        trade_type=TradeType.BUY,
        base_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
        quote_asset=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
        amount=AssetAmount(FVal('2.223721994593087248')),
        rate=Price(FVal('1124.241252314216598775470692')),
        trade_index=0,
        swaps=[AMMSwap(
            tx_hash='0x90f68af0ebbbb8d4938a4fbd07a70862e806124abd907d1225f25a10afda0180',
            log_index=26,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x06da0fd433C1A5d7a4faa01111c044910A184553'),
            timestamp=Timestamp(1609303966),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
            token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            amount0_in=AssetAmount(FVal('2500')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('3.410623314913014194')),
        ), AMMSwap(
            tx_hash='0x90f68af0ebbbb8d4938a4fbd07a70862e806124abd907d1225f25a10afda0180',
            log_index=29,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'),
            timestamp=Timestamp(1609303966),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(FVal('3.410623314913014194')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('2474.601464')),
        ), AMMSwap(
            tx_hash='0x90f68af0ebbbb8d4938a4fbd07a70862e806124abd907d1225f25a10afda0180',
            log_index=32,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'),
            timestamp=Timestamp(1609303966),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(ZERO),
            amount1_in=AssetAmount(FVal('2474.601464')),
            amount0_out=AssetAmount(FVal('2.223721994593087248')),
            amount1_out=AssetAmount(ZERO),
        )],
    ), AMMTrade(
        trade_type=TradeType.BUY,
        base_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
        quote_asset=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
        amount=AssetAmount(FVal('1.925851508322134245')),
        rate=Price(FVal('1012.539124420295894184748806')),
        trade_index=0,
        swaps=[AMMSwap(
            tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2',
            log_index=205,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x06da0fd433C1A5d7a4faa01111c044910A184553'),
            timestamp=Timestamp(1609301469),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
            token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            amount0_in=AssetAmount(FVal('1950')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('2.6455727132446468')),
        ), AMMSwap(
            tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2',
            log_index=208,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'),
            timestamp=Timestamp(1609301469),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(FVal('2.6455727132446468')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('1936.810111')),
        ), AMMSwap(
            tx_hash='0xa54bf4c68d435e3c8f432fd7e62b7f8aca497a831a3d3fca305a954484ddd7b2',
            log_index=211,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'),
            timestamp=Timestamp(1609301469),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(ZERO),
            amount1_in=AssetAmount(FVal('1936.810111')),
            amount0_out=AssetAmount(FVal('1.925851508322134245')),
            amount1_out=AssetAmount(ZERO),
        )],
    ), AMMTrade(
        trade_type=TradeType.BUY,
        base_asset=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
        quote_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
        amount=AssetAmount(FVal('951.611472')),
        rate=Price(FVal('0.001050849038104071952592013309')),
        trade_index=0,
        swaps=[AMMSwap(
            tx_hash='0xb3cea8179e8bc349661f265937187403ae4914c108d118889d026bac1fbec4e9',
            log_index=9,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'),
            timestamp=Timestamp(1609296759),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(FVal('1')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('951.611472')),
        )],
    ), AMMTrade(
        trade_type=TradeType.BUY,
        base_asset=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
        quote_asset=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
        amount=AssetAmount(FVal('1')),
        rate=Price(FVal('511.20342')),
        trade_index=0,
        swaps=[AMMSwap(
            tx_hash='0x0ccec8fd15c8d3ab923ee4a2406778f22769e74da20b19a35e614bfead6bab8d',
            log_index=242,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x06da0fd433C1A5d7a4faa01111c044910A184553'),
            timestamp=Timestamp(1609294923),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48'),
            token1=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            amount0_in=AssetAmount(FVal('511.20342')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('0.690577933591789501')),
        ), AMMSwap(
            tx_hash='0x0ccec8fd15c8d3ab923ee4a2406778f22769e74da20b19a35e614bfead6bab8d',
            log_index=245,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0xC9cB53B48A2f3A9e75982685644c1870F1405CCb'),
            timestamp=Timestamp(1609294923),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(FVal('0.690577933591789501')),
            amount1_in=AssetAmount(ZERO),
            amount0_out=AssetAmount(ZERO),
            amount1_out=AssetAmount(FVal('506.399839')),
        ), AMMSwap(
            tx_hash='0x0ccec8fd15c8d3ab923ee4a2406778f22769e74da20b19a35e614bfead6bab8d',
            log_index=248,
            address=address,
            from_address=string_to_ethereum_address('0xd9e1cE17f2641f24aE83637ab66a2cca9C378B9F'),
            to_address=string_to_ethereum_address('0x63BC843b9640c4D79d6aE0105bc39F773172d121'),
            timestamp=Timestamp(1609294923),
            location=Location.SUSHISWAP,
            token0=EthereumToken('0x368B3a58B5f49392e5C9E4C998cb0bB966752E51'),
            token1=EthereumToken('0xdAC17F958D2ee523a2206206994597C13D831ec7'),
            amount0_in=AssetAmount(ZERO),
            amount1_in=AssetAmount(FVal('506.399839')),
            amount0_out=AssetAmount(FVal('1')),
            amount1_out=AssetAmount(ZERO),
        )],
    )]