From 4740aebd7f903a1378f57019fd37e5597a8dbe1c Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Thu, 14 Dec 2017 15:42:39 -0500 Subject: [PATCH] BLD: testing markets for each exchange --- catalyst/examples/simple_loop.py | 55 ++++++--- catalyst/exchange/ccxt/ccxt_exchange.py | 11 +- docs/source/unit-tests.rst | 2 +- tests/exchange/test_suite_exchange.py | 141 ++++++++++++++++++++++++ 4 files changed, 189 insertions(+), 20 deletions(-) create mode 100644 tests/exchange/test_suite_exchange.py diff --git a/catalyst/examples/simple_loop.py b/catalyst/examples/simple_loop.py index 51ea435c..5ad9d53b 100644 --- a/catalyst/examples/simple_loop.py +++ b/catalyst/examples/simple_loop.py @@ -1,23 +1,26 @@ import talib import pandas as pd +from logbook import Logger, INFO from catalyst import run_algorithm from catalyst.api import symbol, record from catalyst.exchange.stats_utils import get_pretty_stats, \ extract_transactions +log = Logger('simple_loop', level=INFO) + def initialize(context): - print('initializing') + log.info('initializing') context.asset = symbol('eth_btc') context.base_price = None def handle_data(context, data): - print('handling bar: {}'.format(data.current_dt)) + log.info('handling bar: {}'.format(data.current_dt)) price = data.current(context.asset, 'close') - print('got price {price}'.format(price=price)) + log.info('got price {price}'.format(price=price)) prices = data.history( context.asset, @@ -26,10 +29,10 @@ def handle_data(context, data): frequency='30T' ) last_traded = prices.index[-1] - print('last candle date: {}'.format(last_traded)) + log.info('last candle date: {}'.format(last_traded)) rsi = talib.RSI(prices.values, timeperiod=14)[-1] - print('got rsi: {}'.format(rsi)) + log.info('got rsi: {}'.format(rsi)) # If base_price is not set, we use the current value. This is the # price at the first bar which we reference to calculate price_change. @@ -51,7 +54,7 @@ def handle_data(context, data): def analyze(context, perf): import matplotlib.pyplot as plt - print('the stats: {}'.format(get_pretty_stats(perf))) + log.info('the stats: {}'.format(get_pretty_stats(perf))) # The base currency of the algo exchange base_currency = context.exchanges.values()[0].base_currency.upper() @@ -111,15 +114,31 @@ def analyze(context, perf): if __name__ == '__main__': - run_algorithm( - capital_base=1, - initialize=initialize, - handle_data=handle_data, - analyze=None, - exchange_name='poloniex', - live=True, - algo_namespace='simple_loop', - base_currency='eth', - live_graph=False, - simulate_orders=True - ) + mode = 'backtest' + + if mode == 'backtest': + run_algorithm( + capital_base=1, + initialize=initialize, + handle_data=handle_data, + analyze=None, + exchange_name='poloniex', + algo_namespace='simple_loop', + base_currency='eth', + data_frequency='minute', + start=pd.to_datetime('2017-9-1', utc=True), + end=pd.to_datetime('2017-12-1', utc=True), + ) + else: + run_algorithm( + capital_base=1, + initialize=initialize, + handle_data=handle_data, + analyze=None, + exchange_name='binance', + live=True, + algo_namespace='simple_loop', + base_currency='eth', + live_graph=False, + simulate_orders=True + ) diff --git a/catalyst/exchange/ccxt/ccxt_exchange.py b/catalyst/exchange/ccxt/ccxt_exchange.py index 66944b88..498db034 100644 --- a/catalyst/exchange/ccxt/ccxt_exchange.py +++ b/catalyst/exchange/ccxt/ccxt_exchange.py @@ -71,8 +71,12 @@ class CCXT(Exchange): self.bundle = ExchangeBundle(self.name) self.markets = None + self._is_init = False def init(self): + if self._is_init: + return + exchange_folder = get_exchange_folder(self.name) filename = os.path.join(exchange_folder, 'cctx_markets.json') @@ -97,12 +101,13 @@ class CCXT(Exchange): self.markets = self.api.fetch_markets() with open(filename, 'w+') as f: - json.dump(self.markets, f) + json.dump(self.markets, f, indent=4) except ExchangeNotAvailable as e: raise ExchangeRequestError(error=e) self.load_assets() + self._is_init = True @staticmethod def find_exchanges(features=None): @@ -408,6 +413,10 @@ class CCXT(Exchange): self.assets = [] for market in self.markets: + if 'id' not in market: + log.warn('invalid market: {}'.format(market)) + continue + asset_defs = self.get_asset_defs(market) asset = None diff --git a/docs/source/unit-tests.rst b/docs/source/unit-tests.rst index 0e6aad76..1da822f1 100644 --- a/docs/source/unit-tests.rst +++ b/docs/source/unit-tests.rst @@ -32,7 +32,7 @@ Test: Fetch historical data for each market using the selected frequency Assert: - No error and not blank - - Date of each candle is consistent with the Catalyst desired pattern: + - Date of each candle is consistent with the Catalyst desired pattern, - All candle start at fix intervals - Last candle partial and forward looking from the end date diff --git a/tests/exchange/test_suite_exchange.py b/tests/exchange/test_suite_exchange.py new file mode 100644 index 00000000..87f8cb73 --- /dev/null +++ b/tests/exchange/test_suite_exchange.py @@ -0,0 +1,141 @@ +import json +import os +import random +import unittest +from logging import Logger +from time import sleep + +import pandas as pd +from ccxt import AuthenticationError + +from catalyst.exchange.exchange_errors import ExchangeRequestError +from catalyst.exchange.exchange_utils import get_exchange_folder +from catalyst.exchange.factory import find_exchanges +from catalyst.utils.paths import data_root + +log = Logger('TestSuiteExchange') + + +def handle_exchange_error(exchange, e): + is_blacklist = False + + if isinstance(e, AuthenticationError): + is_blacklist = True + + elif isinstance(e, ValueError) or isinstance(e, ExchangeRequestError): + is_blacklist = True + + else: + log.warn('unexpected error: {}'.format(e)) + is_blacklist = True + + if is_blacklist: + root = data_root() + filename = os.path.join(root, 'exchanges', 'blacklist.json') + + if os.path.isfile(filename): + with open(filename) as handle: + try: + bl_data = json.load(handle) + + except ValueError: + bl_data = dict() + + else: + bl_data = dict() + + if exchange.name not in bl_data: + bl_data[exchange.name] = '{}: {}'.format(e.__class__, e.message) + with open(filename, 'wt') as handle: + json.dump(bl_data, handle, indent=4) + + +def select_random_exchanges(population=3, features=None): + all_exchanges = find_exchanges(features) + + if population is not None: + exchanges = random.sample(all_exchanges, population) + + else: + exchanges = all_exchanges + + return exchanges + + +def select_random_assets(exchange, population=3): + all_assets = exchange.assets + assets = random.sample(all_assets, population) + return assets + + +# TODO: convert to Nosetest +class TestSuiteExchange(unittest.TestCase): + def _test_markets_exchange(self, exchange, attempts=0): + assets = None + try: + exchange.init() + + # Verify that the assets and markets are populated + if not exchange.markets: + raise ValueError( + 'no markets found' + ) + if not exchange.assets: + raise ValueError( + 'no assets derived from markets' + ) + assets = exchange.assets + + except ExchangeRequestError as e: + sleep(5) + + if attempts > 5: + handle_exchange_error(exchange, e) + + else: + self._test_markets_exchange(exchange, attempts + 1) + + except Exception as e: + handle_exchange_error(exchange, e) + + return assets + + def test_markets(self): + population = None + results = dict() + + exchanges = select_random_exchanges(population) # Type: list[Exchange] + for exchange in exchanges: + assets = self._test_markets_exchange(exchange) + if assets is not None: + results[exchange.name] = len(assets) + + folder = get_exchange_folder(exchange.name) + filename = os.path.join(folder, 'supported_assets.json') + + symbols = [asset.symbol for asset in assets] + with open(filename, 'wt') as handle: + json.dump(symbols, handle, indent=4) + + series = pd.Series(results) + print('the tested markets\n{}'.format(series)) + + if population is not None: + assert (len(results) == population) + + pass + + def test_ticker(self): + exchanges = select_random_exchanges(3) # Type: list[Exchange] + for exchange in exchanges: + exchange.init() + + assets = select_random_assets(exchange, 3) + exchange.tickers() + pass + + def test_candles(self): + pass + + def test_orders(self): + pass