From 3b16bf7538da543127bdfe2201f4b74e9180a49a Mon Sep 17 00:00:00 2001 From: Frederic Fortier Date: Wed, 20 Dec 2017 16:04:01 -0500 Subject: [PATCH] BLD: completed unit tests for validating bundles against OHLCV data on each exchange --- catalyst/exchange/exchange.py | 2 + catalyst/exchange/exchange_utils.py | 24 ++++++++ tests/exchange/test_suite_bundle.py | 86 ++++++++++++++++++----------- 3 files changed, 79 insertions(+), 33 deletions(-) diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 41e361b5..13ad5ff7 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -19,6 +19,7 @@ from catalyst.exchange.exchange_errors import MismatchingBaseCurrencies, \ NoDataAvailableOnExchange, NoValueForField, LastCandleTooEarlyError from catalyst.exchange.exchange_utils import get_exchange_symbols, \ get_frequency, resample_history_df, has_bundle +from catalyst.utils.deprecate import deprecated log = Logger('Exchange', level=LOG_LEVEL) @@ -407,6 +408,7 @@ class Exchange: return value + @deprecated def get_series_from_candles(self, candles, start_dt, end_dt, data_frequency, field, previous_value=None): """ diff --git a/catalyst/exchange/exchange_utils.py b/catalyst/exchange/exchange_utils.py index fc87233a..a3f490f3 100644 --- a/catalyst/exchange/exchange_utils.py +++ b/catalyst/exchange/exchange_utils.py @@ -705,3 +705,27 @@ def save_asset_data(folder, df, decimals=8): header=print_headers, float_format='%.{}f'.format(decimals), ) + + +def get_candles_df(candles, field, freq, bar_count, end_dt, + previous_value=None): + all_series = dict() + for asset in candles: + periods = pd.date_range(end=end_dt, periods=bar_count, freq=freq) + + dates = [candle['last_traded'] for candle in candles[asset]] + values = [candle[field] for candle in candles[asset]] + series = pd.Series(values, index=dates) + + series = series.reindex( + periods, + method='ffill', + fill_value=previous_value, + ) + series.sort_index(inplace=True) + all_series[asset] = series + + df = pd.DataFrame(all_series) + df.dropna(inplace=True) + + return df diff --git a/tests/exchange/test_suite_bundle.py b/tests/exchange/test_suite_bundle.py index 0e649425..482bdb9c 100644 --- a/tests/exchange/test_suite_bundle.py +++ b/tests/exchange/test_suite_bundle.py @@ -1,4 +1,5 @@ import random +from datetime import timedelta from logbook import Logger from pandas.util.testing import assert_frame_equal @@ -8,11 +9,18 @@ import pandas as pd from catalyst import get_calendar from catalyst.exchange.asset_finder_exchange import AssetFinderExchange from catalyst.exchange.exchange_data_portal import DataPortalExchangeBacktest +from catalyst.exchange.exchange_utils import get_candles_df +from catalyst.exchange.factory import get_exchange from catalyst.exchange.test_utils import select_random_exchanges, output_df, \ select_random_assets log = Logger('TestSuiteExchange') +pd.set_option('display.expand_frame_repr', False) +pd.set_option('precision', 8) +pd.set_option('display.width', 1000) +pd.set_option('display.max_colwidth', 1000) + class TestSuiteBundle: @staticmethod @@ -29,7 +37,7 @@ class TestSuiteBundle: return data_portal def compare_bundle_with_exchange(self, exchange, assets, end_dt, bar_count, - freq, data_portal): + freq, data_frequency, data_portal): """ Creates DataFrames from the bundle and exchange for the specified data set. @@ -46,18 +54,19 @@ class TestSuiteBundle: ------- """ + data = dict() + log.info('creating data sample from bundle') - df1 = data_portal.get_history_window( + data['bundle'] = data_portal.get_history_window( assets=assets, end_dt=end_dt, bar_count=bar_count, frequency=freq, field='close', - data_frequency='minute' + data_frequency=data_frequency, ) - path = output_df(df1, assets, '{}_resampled'.format(freq)) - log.info('saved resampled bundle candles: {}\n{}'.format( - path, df1.tail(10)) + log.info('bundle data:\n{}'.format( + data['bundle'].tail(10)) ) log.info('creating data sample from exchange api') @@ -65,28 +74,29 @@ class TestSuiteBundle: end_dt=end_dt, freq=freq, assets=assets, - bar_count=bar_count + bar_count=bar_count, ) - - series = dict() - for asset in assets: - series[asset] = pd.Series( - data=[candle['close'] for candle in candles[asset]], - index=[candle['last_traded'] for candle in candles[asset]] - ) - - df2 = pd.DataFrame(series) - path = output_df(df2, assets, '{}_api'.format(freq)) - log.info('saved exchange api candles: {}\n{}'.format( - path, df2.tail(10)) + data['exchange'] = get_candles_df( + candles=candles, + field='close', + freq=freq, + bar_count=bar_count, + end_dt=end_dt, ) + log.info('exchange data:\n{}'.format( + data['exchange'].tail(10)) + ) + for source in data: + df = data[source] + path = output_df(df, assets, '{}_{}'.format(freq, source)) + log.info('saved {}:\n{}'.format(source, path)) - try: - assert_frame_equal(df1, df2) - return True - except: - log.warn('differences found in dataframes') - return False + assert_frame_equal( + right=data['bundle'], + left=data['exchange'], + check_less_precise=True + ) + pass def test_validate_bundles(self): exchange_population = 3 @@ -94,10 +104,11 @@ class TestSuiteBundle: data_frequency = random.choice(['minute', 'daily']) bundle = 'dailyBundle' if data_frequency == 'daily' else 'minuteBundle' - exchanges = select_random_exchanges( - population=exchange_population, - features=[bundle], - ) # Type: list[Exchange] + # exchanges = select_random_exchanges( + # population=exchange_population, + # features=[bundle], + # ) # Type: list[Exchange] + exchanges = [get_exchange('bitfinex', skip_init=True)] data_portal = TestSuiteBundle.get_data_portal( [exchange.name for exchange in exchanges] @@ -109,18 +120,27 @@ class TestSuiteBundle: freq = random.sample(frequencies, 1)[0] bar_count = random.randint(1, 10) - end_dt = pd.Timestamp.utcnow().floor('1T') - dt_range = pd.date_range( - end=end_dt, periods=bar_count, freq=freq - ) + assets = select_random_assets( exchange.assets, asset_population ) + end_dt = None + for asset in assets: + attribute = 'end_{}'.format(data_frequency) + asset_end_dt = getattr(asset, attribute) + + if end_dt is None or asset_end_dt < end_dt: + end_dt = asset_end_dt + + dt_range = pd.date_range( + end=end_dt, periods=bar_count, freq=freq + ) self.compare_bundle_with_exchange( exchange=exchange, assets=assets, end_dt=dt_range[-1], bar_count=bar_count, freq=freq, + data_frequency=data_frequency, data_portal=data_portal, )