ccxt / ccxt

A JavaScript / TypeScript / Python / C# / PHP cryptocurrency trading API with support for more than 100 bitcoin/altcoin exchanges
https://docs.ccxt.com
MIT License
33.19k stars 7.55k forks source link

EXMO: Method load_markets() doesn't work #4950

Closed DmitryVil closed 5 years ago

DmitryVil commented 5 years ago

ATTENTION!!!

MUST READ THIS BEFORE SUBMITTING ISSUES:

https://github.com/ccxt/ccxt/blob/master/CONTRIBUTING.md#how-to-submit-an-issue

Method load_markets() don't work for exchange exmo in version 1.18.437. In previous versions (for example in version 1.18.406) them work.

ccxt

kroitor commented 5 years ago

They have two website domains:

One of them accessible from one part of the world, the other one from the rest of the world. The problem is that they don't have an API endpoint for fees, and to fetch them we have to load a webpage from their website and parse the fees from there. However, when accessing it from different locations, one of the domains may be inaccessible, so, it is the connection timeout upon loading fees that is causing the trouble.

There's two ways to overcome this:

  1. Change the domain address for the webpage request:
import ccxt
exchange = ccxt.exmo ({
    'enableRateLimit': True,
    'api': {
        'web': 'https://exmo.com',
    },
})
  1. Don't fetch their fees from website and switch this off. I'd recommend this one until they make an endpoint for fees:
import ccxt
exchange = ccxt.exmo ({
    'enableRateLimit': True,
    'options': {
        'useWebapiForFetchingFees': False,
    },
})

Let us know if that doesn't answer the question.

DmitryVil commented 5 years ago

First advice doesn't work: ccxt

Second advice: in any case method load_markets() doesn't work

kroitor commented 5 years ago

@DmitryVil can you show the error with the second option here? Please, don't post screenshots, paste text and use markup for readability as described here:

kroitor commented 5 years ago

@DmitryVil also, i've just noticed, there's a typo in the first option (my bad), should be like so:

1.

import ccxt
exchange = ccxt.exmo ({
    'enableRateLimit': True,
    'urls': {
        'api': {
            'web': 'https://exmo.com',
        },
    },
})
import ccxt
exchange = ccxt.exmo ({
    'enableRateLimit': True,
    'options': {
        'useWebapiForFetchingFees': False,
    },
})

↑ Can you retry both and paste your results here in text?

DmitryVil commented 5 years ago

Kroitor, the main problem is load_markets() method unavailable.

Absentness of load_fees() method is not critical.

kroitor commented 5 years ago

@DmitryVil you're not understanding the callchain (loadMarkets calls loadFees in this scenario) – the loadMarkets will work, you'll get it very quickly, if you just follow precisely what i'm telling you )

This error:

First advice doesn't work:

↑ was due to a typo. I've posted a corrected version of it with my previous reply. Have you retried it?

Absentness of load_fees() method is not critical.

I know this, but this is not the point here )

Can you retry both versions from the corrected answer and paste your results here in text? Need to see the actual output to resolve this.

DmitryVil commented 5 years ago

You are right: first advice is workable. But, to be frank, is very unuseful. This problem raised today after updating ccxt library version

kroitor commented 5 years ago

You are right: first advice is workable. But, to be frank, is very unuseful.

Use the second one, basically, same thing.

This problem raised today after updating ccxt library version

Actually, the problem is raised by EXMO after blocking their website for different regions of the world. We've been looking into a solution, but they are making it hard to use their system intentionally. Before blocking the fees page, they could provide an API endpoint for it. We will keep looking for a better workaround.

Their websites will be inaccessible depending on where you launch the code, not which code you launch. So, it's beyond the scope of the lib, if the exchange is blocking itself from the outside world.

DmitryVil commented 5 years ago

Kroitor, sorry, what I just got from anynchronius part of library:

Mistake in main module: RequestTimeout('exmo GET https://exmo.com/en/docs/fees '), exmo exmo RequestTimeout('exmo GET https://exmo.com/en/docs/fees ')

DmitryVil commented 5 years ago

I launch my code with Great Britain IP address

kroitor commented 5 years ago

@DmitryVil have you tried the second option? What does it say?

DmitryVil commented 5 years ago

Finally, it launched but with warnings:

exmo requires to release all resources with an explicit call to the .close() coroutine. If you are creating the exchange instance from within your async coroutine, add exchange.close() to your code into a place when you're done with the exchange and don't need the exchange instance anymore (at the end of your async coroutine). Unclosed client session client_session: <aiohttp.client.ClientSession object at 0x0000026A3BC298D0>

kroitor commented 5 years ago

exmo requires to release all resources with an explicit call to the .close() coroutine. If you are creating the exchange instance from within your async coroutine, add exchange.close() to your code into a place when you're done with the exchange and don't need the exchange instance anymore (at the end of your async coroutine).

This is a completely different thing, related to async_support and to resolve this you just need to follow precisely what the message from the error is telling you (it says that you're not working with the async version as you should).

exmo requires to release all resources with an explicit call to the .close() coroutine.

If you are creating the exchange instance from within your async coroutine, add exchange.close() to your code into a place when you're done with the exchange

↑ I guess, from this it should be clear what to do. See the async examples on how to work with async sessions properly:

Also, use the search and find these 16 duplicate answers to the same question:

;))

Let us know if you still have issues with it after that.

P.S. please use proper markup in the future for readability, do not highlight everything in bold – read this very carefully on how to format the text on GitHub correctly:

DmitryVil commented 5 years ago

Kroitor, To create the exchange instance I use exactly what you suggest as an example in ccxt. Moreover, I create in the program one after another instances for some exchanges in the loop. But this message I got only for exmo and only after using code you adviced: exchange = ccxt.exmo ({ 'enableRateLimit': True, 'options': { 'useWebapiForFetchingFees': False, }, })

kroitor commented 5 years ago

@DmitryVil can you make a short but complete snippet of 10-20 lines to reproduce the issue? I mean, starting from the import clause and up to the point of breaking.

Because if I run this:

exchange = ccxt.exmo ({
    'enableRateLimit': True,
    'options': {
        'useWebapiForFetchingFees': False,
    },
})

↑ it works on my side, so, obviously, the cause is in the other code around (that code above is not enough to tell anything). Need to see the rest of it (please make it short, but complete), otherwise we will mostly be guessing...

kroitor commented 5 years ago

@DmitryVil here's an example of a proper short complete program that works with EXMO in async mode correctly and doesn't fail:

# -*- coding: utf-8 -*-

import asyncio
from pprint import pprint
import ccxt.async_support as ccxt

async def test():

    exchange = ccxt.exmo({
        'enableRateLimit': True,  # required according to the Manual
        'options': {
            'useWebapiForFetchingFees': False,
        }
    })

    try:
        orderbook = await exchange.fetch_order_book('BTC/USDT')
        await exchange.close()
        return orderbook
    except ccxt.BaseError as e:
        print(type(e).__name__, str(e), str(e.args))
        raise e

if __name__ == '__main__':
    pprint(asyncio.get_event_loop().run_until_complete(test()))

↑ Does it work for you if you save it to a py-file and launch it with your Python3? It should output an orderbook without any errors. If you don't see an error with this code, but you see an error with your code – the issue is with your code. You should follow the error messages as was described here: https://github.com/ccxt/ccxt/issues/4950#issuecomment-480627098

(I really hope that you post your marked up and readable code and text output as shown in the example in this comment).

DmitryVil commented 5 years ago

I initiate the instances with code like this: ##################################################################### def flrFetching(flr): ####### Request of exchange market ######## markets = asyncio.get_event_loop().run_until_complete(flr.loadMarkets(reload=True)) return markets

flrs = ['bitfinex', 'okex', 'exmo']

for flrID in flrs:
    flr = getattr(ccxt, flrID)()
    markets = flrFetching(flr)
    print('\n\n', flr.id, '\n', markets)

#################################################################### asyncronious part of the code I copies for ccxt library. what do you think may be a source of the problem?

kroitor commented 5 years ago

what do you think may be a source of the problem?

The reason is that you're not working with asyncio instances as described in the warning you're getting and in the examples above ) You don't close the session anywhere in your code. Missing await flr.close () in that example.

kroitor commented 5 years ago

@DmitryVil the proper code could be done in many different ways, depends on the flow of your application and the design of it, for example:

# -*- coding: utf-8 -*-

import asyncio
from pprint import pprint
import ccxt.async_support as ccxt

async def run_all_exchanges (flrs):
    results = {}
    for flrID in flrs:
        results[flrID] = await run_one_exchange (flrID)
    return results

async def run_one_exchange(exchange_id):

    exchange = getattr(ccxt, exchange_id) ({
        'enableRateLimit': True,  # required accoding to the Manual
        'options': {
            'useWebapiForFetchingFees': False,
        }
    })

    try:
        result = await exchange.load_markets()
        await exchange.close()  # ←---------------------- YOU'RE MISSING THIS
        return result
    except ccxt.BaseError as e:
        print(type(e).__name__, str(e), str(e.args))
        raise e

if __name__ == '__main__':
    exchange_ids = ['bitfinex', 'okex', 'exmo']
    markets = asyncio.get_event_loop().run_until_complete(run_all_exchanges(exchange_ids))
    pprint([ (market, list(markets[market].keys ())) for market in markets.keys()])

↑ I'm not saying that this is the way to do it, it is just one of the examples that shows loading markets... See other examples here: https://github.com/ccxt/ccxt/tree/master/examples/py

kroitor commented 5 years ago

P.S. To format your code here properly, you should surround it with triple backticks (```). Do not confuse backticks (```) with single quotes (\'\'\'). Add the language after the first three backtics for highlighting.

Example:

```Python import ccxt your code... ```

DmitryVil commented 5 years ago

Could you please explain: after await exchange.close(), may I return as a result of the method an exchange instance to the upper level? I mean return exchange

kroitor commented 5 years ago

after await exchange.close(), may I return as a result of the method an exchange instance to the upper level?

Yes, however, because it's closed, you can't call the methods on the closed instance from there. So, if you don't want to close it and want to keep it further, you can do something like this:

# -*- coding: utf-8 -*-

import asyncio
import ccxt.async_support as ccxt

async def run_all_exchanges(exchange_ids):
    results = {}

    for exchange_id in exchange_ids:

        exchange = getattr(ccxt, exchange_id)({
            'enableRateLimit': True,  # required accoding to the Manual
            'options': {
                'useWebapiForFetchingFees': False,
            }
        })

        symbol = 'ETH/BTC'
        print('Exchange:', exchange_id)

        print(exchange_id, 'symbols:')
        markets = await load_markets(exchange, symbol)  # ←----------- STEP 1
        print(list(markets.keys()))

        print(symbol, 'ticker:')
        ticker = await fetch_ticker(exchange, symbol)  # ←------------ STEP 2
        print(ticker)

        print(symbol, 'orderbook:')
        orderbook = await fetch_orderbook(exchange, symbol)  # ←------ STEP 3
        print(orderbook)

        await exchange.close()  # ←----------- LAST STEP GOES AFTER ALL CALLS

        results[exchange_id] = ticker

    return results

async def load_markets(exchange, symbol):
    try:
        result = await exchange.load_markets()
        return result
    except ccxt.BaseError as e:
        print(type(e).__name__, str(e), str(e.args))
        raise e

async def fetch_ticker(exchange, symbol):
    try:
        result = await exchange.fetch_ticker(symbol)
        return result
    except ccxt.BaseError as e:
        print(type(e).__name__, str(e), str(e.args))
        raise e

async def fetch_orderbook(exchange, symbol):
    try:
        result = await exchange.fetch_order_book(symbol)
        return result
    except ccxt.BaseError as e:
        print(type(e).__name__, str(e), str(e.args))
        raise e

if __name__ == '__main__':
    exchange_ids = ['bitfinex', 'okex', 'exmo']
    exchanges = []
    results = asyncio.get_event_loop().run_until_complete(run_all_exchanges(exchange_ids))
    print([(exchange_id, ticker) for exchange_id, ticker in results.items()])

Also, you can return the exchange instance (return exchange) from one async method to another to close() it later.

DmitryVil commented 5 years ago

What happens after using await exchange.close()? Is instance exchange disappearing?

kroitor commented 5 years ago

What happens after using await exchange.close()?

Here's the source code of that methods it's less than 5 lines ))

Is instance exchange disappearing?

No, the exchange class instance continues to exist, but the .close() method shuts down the internal asyncio session that is opened automatically upon creating the exchange instance. If you don't close it, Python will complain with a warning.

Having that asyncio session and the corresponding call to the .close() method is not the feature of this library, but is more like a requirement of the asyncio itself (this is how you do async in python, basically).

So, close() closes the session, and you can later re-open (and re-close) it with the same exchange class instance with the corresponding .open() method, which is here: