Compare commits

...
9 Commits
12 changed files with 327 additions and 343 deletions
+2
View File
@@ -282,6 +282,7 @@ def run(ctx,
exchange=exchange_name, exchange=exchange_name,
algo_namespace=algo_namespace, algo_namespace=algo_namespace,
base_currency=base_currency, base_currency=base_currency,
analyze_live=None,
live_graph=False, live_graph=False,
simulate_orders=True, simulate_orders=True,
stats_output=None, stats_output=None,
@@ -470,6 +471,7 @@ def live(ctx,
algo_namespace=algo_namespace, algo_namespace=algo_namespace,
base_currency=base_currency, base_currency=base_currency,
live_graph=live_graph, live_graph=live_graph,
analyze_live=None,
simulate_orders=simulate_orders, simulate_orders=simulate_orders,
stats_output=None, stats_output=None,
) )
+2 -2
View File
@@ -37,7 +37,7 @@ def initialize(context):
context.base_price = None context.base_price = None
context.current_day = None context.current_day = None
context.RSI_OVERSOLD = 50 context.RSI_OVERSOLD = 55
context.RSI_OVERBOUGHT = 65 context.RSI_OVERBOUGHT = 65
context.CANDLE_SIZE = '5T' context.CANDLE_SIZE = '5T'
@@ -244,7 +244,7 @@ def analyze(context=None, perf=None):
if __name__ == '__main__': if __name__ == '__main__':
# The execution mode: backtest or live # The execution mode: backtest or live
live = False live = True
if live: if live:
run_algorithm( run_algorithm(
+1 -1
View File
@@ -680,7 +680,7 @@ class ExchangeTradingAlgorithmLive(ExchangeTradingAlgorithmBase):
if new_orders != self._last_orders: if new_orders != self._last_orders:
self.performance_needs_update = True self.performance_needs_update = True
self._last_orders = new_orders self._last_orders = copy.deepcopy(new_orders)
if self.performance_needs_update: if self.performance_needs_update:
self.perf_tracker.update_performance() self.perf_tracker.update_performance()
+159 -202
View File
@@ -70,7 +70,36 @@ class _RunAlgoError(click.ClickException, ValueError):
return self.pyfunc_msg return self.pyfunc_msg
def _build_namespace(algotext, local_namespace, defines): def _run(handle_data,
initialize,
before_trading_start,
analyze,
algofile,
algotext,
defines,
data_frequency,
capital_base,
data,
bundle,
bundle_timestamp,
start,
end,
output,
print_algo,
local_namespace,
environ,
live,
exchange,
algo_namespace,
base_currency,
live_graph,
analyze_live,
simulate_orders,
stats_output):
"""Run a backtest for the given algorithm.
This is shared between the cli and :func:`catalyst.run_algo`.
"""
if algotext is not None: if algotext is not None:
if local_namespace: if local_namespace:
ip = get_ipython() # noqa ip = get_ipython() # noqa
@@ -84,88 +113,93 @@ def _build_namespace(algotext, local_namespace, defines):
except ValueError: except ValueError:
raise ValueError( raise ValueError(
'invalid define %r, should be of the form name=value' % 'invalid define %r, should be of the form name=value' %
assign) assign,
)
try: try:
# evaluate in the same namespace so names may refer to # evaluate in the same namespace so names may refer to
# eachother # eachother
namespace[name] = eval(value, namespace) namespace[name] = eval(value, namespace)
except Exception as e: except Exception as e:
raise ValueError( raise ValueError(
'failed to execute definition for name %r: %s' % (name, e)) 'failed to execute definition for name %r: %s' % (name, e),
)
elif defines: elif defines:
raise _RunAlgoError( raise _RunAlgoError(
'cannot pass define without `algotext`', 'cannot pass define without `algotext`',
"cannot pass '-D' / '--define' without '-t' / '--algotext'") "cannot pass '-D' / '--define' without '-t' / '--algotext'",
)
else: else:
namespace = {} namespace = {}
if algofile is not None:
algotext = algofile.read()
return namespace if print_algo:
if PYGMENTS:
highlight(
def _mode(simulate_orders, live): algotext,
if not live: PythonLexer(),
return 'backtest' TerminalFormatter(),
elif simulate_orders: outfile=sys.stdout,
return 'paper-trading' )
else: else:
return 'live-trading' click.echo(algotext)
mode = 'paper-trading' if simulate_orders else 'live-trading' \
if live else 'backtest'
log.info('running algo in {mode} mode'.format(mode=mode))
def _build_exchanges_dict(exchange, live, simulate_orders, base_currency):
exchange_name = exchange exchange_name = exchange
if exchange_name is None: if exchange_name is None:
raise ValueError('Please specify at least one exchange.') raise ValueError('Please specify at least one exchange.')
exchange_list = [x.strip().lower() for x in exchange.split(',')] exchange_list = [x.strip().lower() for x in exchange.split(',')]
exchanges = {exchange_name: get_exchange( exchanges = dict()
for exchange_name in exchange_list:
exchanges[exchange_name] = get_exchange(
exchange_name=exchange_name, exchange_name=exchange_name,
base_currency=base_currency, base_currency=base_currency,
must_authenticate=(live and not simulate_orders)) must_authenticate=(live and not simulate_orders),
for exchange_name in exchange_list} skip_init=True,
)
return exchanges open_calendar = get_calendar('OPEN')
env = TradingEnvironment(
load=partial(
load_crypto_market_data,
environ=environ,
start_dt=start,
end_dt=end
),
environ=environ,
exchange_tz='UTC',
asset_db_path=None # We don't need an asset db, we have exchanges
)
env.asset_finder = ExchangeAssetFinder(exchanges=exchanges)
def _pretty_print_code(algotext): def choose_loader(column):
if PYGMENTS:
highlight(
algotext,
PythonLexer(),
TerminalFormatter(),
outfile=sys.stdout)
else:
click.echo(algotext)
def _choose_loader(data_frequency, column):
bound_cols = TradingPairPricing.columns bound_cols = TradingPairPricing.columns
if column in bound_cols: if column in bound_cols:
return ExchangePricingLoader(data_frequency) return ExchangePricingLoader(data_frequency)
raise ValueError( raise ValueError(
"No PipelineLoader registered for column %s." % column) "No PipelineLoader registered for column %s." % column
)
if live:
def _get_live_time_range():
start = pd.Timestamp.utcnow() start = pd.Timestamp.utcnow()
# TODO: fix the end data. # TODO: fix the end data.
end = start + timedelta(hours=8760) end = start + timedelta(hours=8760)
return start, end
def _data_for_live_trading(sim_params, exchanges, env, open_calendar):
data = DataPortalExchangeLive( data = DataPortalExchangeLive(
exchanges=exchanges, exchanges=exchanges,
asset_finder=env.asset_finder, asset_finder=env.asset_finder,
trading_calendar=open_calendar, trading_calendar=open_calendar,
first_trading_day=pd.to_datetime('today', utc=True)) first_trading_day=pd.to_datetime('today', utc=True)
)
return data def fetch_capital_base(exchange, attempt_index=0):
# TODO use proper retry here
def _fetch_capital_base(base_currency, exchange_name, exchange,
attempt_index=0):
""" """
Fetch the base currency amount required to bootstrap Fetch the base currency amount required to bootstrap
the algorithm against the exchange. the algorithm against the exchange.
@@ -185,44 +219,56 @@ def _fetch_capital_base(base_currency, exchange_name, exchange,
if attempt_index < 20: if attempt_index < 20:
log.warn( log.warn(
'could not retrieve balances on {}: {}'.format( 'could not retrieve balances on {}: {}'.format(
exchange.name, e)) exchange.name, e
)
)
sleep(5) sleep(5)
return _fetch_capital_base(base_currency, exchange_name, exchange, return fetch_capital_base(exchange, attempt_index + 1)
attempt_index + 1)
else: else:
raise ExchangeRequestErrorTooManyAttempts( raise ExchangeRequestErrorTooManyAttempts(
attempts=attempt_index, attempts=attempt_index,
error=e) error=e
)
if base_currency in balances: if base_currency in balances:
base_currency_available = balances[base_currency]['free'] base_currency_available = balances[base_currency]['free']
log.info( log.info(
'base currency available in the account: {} {}'.format( 'base currency available in the account: {} {}'.format(
base_currency_available, base_currency)) base_currency_available, base_currency
)
)
return base_currency_available return base_currency_available
else: else:
raise BaseCurrencyNotFoundError( raise BaseCurrencyNotFoundError(
base_currency=base_currency, base_currency=base_currency,
exchange=exchange_name) exchange=exchange_name
)
def _algorithm_class_for_live(algo_namespace, live_graph, stats_output,
analyze_live, base_currency, simulate_orders,
exchanges, capital_base):
if not simulate_orders: if not simulate_orders:
for exchange_name in exchanges: for exchange_name in exchanges:
exchange = exchanges[exchange_name] exchange = exchanges[exchange_name]
balance = _fetch_capital_base(base_currency, exchange_name, balance = fetch_capital_base(exchange)
exchange)
if balance < capital_base: if balance < capital_base:
raise NotEnoughCapitalError( raise NotEnoughCapitalError(
exchange=exchange_name, exchange=exchange_name,
base_currency=base_currency, base_currency=base_currency,
balance=balance, balance=balance,
capital_base=capital_base) capital_base=capital_base,
)
sim_params = create_simulation_parameters(
start=start,
end=end,
capital_base=capital_base,
emission_rate='minute',
data_frequency='minute'
)
# TODO: use the constructor instead
sim_params._arena = 'live'
algorithm_class = partial( algorithm_class = partial(
ExchangeTradingAlgorithmLive, ExchangeTradingAlgorithmLive,
@@ -231,50 +277,9 @@ def _algorithm_class_for_live(algo_namespace, live_graph, stats_output,
live_graph=live_graph, live_graph=live_graph,
simulate_orders=simulate_orders, simulate_orders=simulate_orders,
stats_output=stats_output, stats_output=stats_output,
analyze_live=analyze_live,) analyze_live=analyze_live,
)
return algorithm_class elif exchanges:
def _bundle_trading_environment(bundle_data, environ):
prefix, connstr = re.split(
r'sqlite:///',
str(bundle_data.asset_finder.engine.url),
maxsplit=1)
if prefix:
raise ValueError(
"invalid url %r, must begin with 'sqlite:///'" %
str(bundle_data.asset_finder.engine.url))
return TradingEnvironment(asset_db_path=connstr, environ=environ)
def _build_live_algo_and_data(sim_params, exchanges, env, open_calendar,
simulate_orders, algo_namespace, capital_base,
live_graph, stats_output, analyze_live,
base_currency, namespace, choose_loader,
algorithm_class_kwargs):
sim_params._arena = 'live' # TODO: use the constructor instead
data = _data_for_live_trading(sim_params, exchanges, env, open_calendar)
algorithm_class = _algorithm_class_for_live(
algo_namespace, live_graph, stats_output, analyze_live,
base_currency, simulate_orders, exchanges, capital_base)
return data, algorithm_class(
namespace=namespace,
env=env,
get_pipeline_loader=choose_loader,
sim_params=sim_params,
**algorithm_class_kwargs)
def _build_backtest_algo_and_data(
exchanges, bundle, env, environ, bundle_timestamp, open_calendar,
start, end, namespace, choose_loader, sim_params,
algorithm_class_kwargs):
if exchanges:
# Removed the existing Poloniex fork to keep things simple # Removed the existing Poloniex fork to keep things simple
# We can add back the complexity if required. # We can add back the complexity if required.
@@ -288,19 +293,41 @@ def _build_backtest_algo_and_data(
asset_finder=None, asset_finder=None,
trading_calendar=open_calendar, trading_calendar=open_calendar,
first_trading_day=start, first_trading_day=start,
last_available_session=end) last_available_session=end
)
sim_params = create_simulation_parameters(
start=start,
end=end,
capital_base=capital_base,
data_frequency=data_frequency,
emission_rate=data_frequency,
)
algorithm_class = partial( algorithm_class = partial(
ExchangeTradingAlgorithmBacktest, ExchangeTradingAlgorithmBacktest,
exchanges=exchanges) exchanges=exchanges
)
elif bundle is not None: elif bundle is not None:
# TODO This branch should probably be removed or fixed: it doesn't even bundle_data = load(
# build `algorithm_class`, so it will break when trying to instantiate bundle,
# it. environ,
bundle_data = load(bundle, environ, bundle_timestamp) bundle_timestamp,
)
env = _bundle_trading_environment(bundle_data, environ) prefix, connstr = re.split(
r'sqlite:///',
str(bundle_data.asset_finder.engine.url),
maxsplit=1,
)
if prefix:
raise ValueError(
"invalid url %r, must begin with 'sqlite:///'" %
str(bundle_data.asset_finder.engine.url),
)
env = TradingEnvironment(asset_db_path=connstr, environ=environ)
first_trading_day = \ first_trading_day = \
bundle_data.equity_minute_bar_reader.first_trading_day bundle_data.equity_minute_bar_reader.first_trading_day
@@ -309,103 +336,27 @@ def _build_backtest_algo_and_data(
first_trading_day=first_trading_day, first_trading_day=first_trading_day,
equity_minute_reader=bundle_data.equity_minute_bar_reader, equity_minute_reader=bundle_data.equity_minute_bar_reader,
equity_daily_reader=bundle_data.equity_daily_bar_reader, equity_daily_reader=bundle_data.equity_daily_bar_reader,
adjustment_reader=bundle_data.adjustment_reader) adjustment_reader=bundle_data.adjustment_reader,
)
return data, algorithm_class( perf = algorithm_class(
namespace=namespace, namespace=namespace,
env=env, env=env,
get_pipeline_loader=choose_loader, get_pipeline_loader=choose_loader,
sim_params=sim_params, sim_params=sim_params,
**algorithm_class_kwargs) **{
'initialize': initialize,
def _build_algo_and_data(handle_data, initialize, before_trading_start,
analyze, algofile, algotext, defines, data_frequency,
capital_base, data, bundle, bundle_timestamp, start,
end, output, print_algo, local_namespace, environ,
live, exchange, algo_namespace, base_currency,
live_graph, analyze_live, simulate_orders,
stats_output):
namespace = _build_namespace(algotext, local_namespace, defines)
if algotext is not None:
algotext = algofile.read()
if print_algo:
_pretty_print_code(algotext)
mode = _mode(simulate_orders, live)
log.info('running algo in {mode} mode'.format(mode=mode))
exchanges = _build_exchanges_dict(exchange, live, simulate_orders,
base_currency)
open_calendar = get_calendar('OPEN')
env = TradingEnvironment(
load=partial(load_crypto_market_data, environ=environ, start_dt=start,
end_dt=end),
environ=environ,
exchange_tz='UTC',
asset_db_path=None) # We don't need an asset db, we have exchanges
env.asset_finder = ExchangeAssetFinder(exchanges=exchanges)
choose_loader = partial(_choose_loader, data_frequency)
if live:
start, end = _get_live_time_range()
data_frequency = 'minute' # TODO double check if this is the desired behavior
sim_params = create_simulation_parameters(
start=start,
end=end,
capital_base=capital_base,
emission_rate=data_frequency,
data_frequency=data_frequency)
if algotext is None:
algorithm_class_kwargs = {'initialize': initialize,
'handle_data': handle_data, 'handle_data': handle_data,
'before_trading_start': before_trading_start, 'before_trading_start': before_trading_start,
'analyze': analyze} 'analyze': analyze,
else: } if algotext is None else {
algorithm_class_kwargs = {'algo_filename': getattr(algofile, 'name', 'algo_filename': getattr(algofile, 'name', '<algorithm>'),
'<algorithm>'), 'script': algotext,
'script': algotext} }
).run(
if live:
return _build_live_algo_and_data(
sim_params, exchanges, env, open_calendar, simulate_orders,
algo_namespace, capital_base, live_graph, stats_output,
analyze_live, base_currency, namespace, choose_loader,
algorithm_class_kwargs)
else:
return _build_backtest_algo_and_data(
exchanges, bundle, env, environ, bundle_timestamp, open_calendar,
start, end, namespace, choose_loader, sim_params,
algorithm_class_kwargs)
def _run(handle_data, initialize, before_trading_start, analyze, algofile,
algotext, defines, data_frequency, capital_base, data, bundle,
bundle_timestamp, start, end, output, print_algo, local_namespace,
environ, live, exchange, algo_namespace, base_currency, live_graph,
analyze_live, simulate_orders, stats_output):
"""Run an algorithm in backtest,
paper-trading or live-trading mode.
This is shared between the cli and :func:`catalyst.run_algo`.
"""
data, algorithm = _build_algo_and_data(
handle_data, initialize, before_trading_start, analyze, algofile,
algotext, defines, data_frequency, capital_base, data, bundle,
bundle_timestamp, start, end, output, print_algo, local_namespace,
environ, live, exchange, algo_namespace, base_currency, live_graph,
analyze_live, simulate_orders, stats_output)
perf = algorithm.run(
data, data,
overwrite_sim_params=False) overwrite_sim_params=False,
)
if output == '-': if output == '-':
click.echo(str(perf)) click.echo(str(perf))
@@ -462,7 +413,8 @@ def load_extensions(default, extensions, strict, environ, reload=False):
# without `strict` we should just log the failure # without `strict` we should just log the failure
warnings.warn( warnings.warn(
'Failed to load extension: %r\n%s' % (ext, e), 'Failed to load extension: %r\n%s' % (ext, e),
stacklevel=2) stacklevel=2
)
else: else:
_loaded_extensions.add(ext) _loaded_extensions.add(ext)
@@ -561,7 +513,8 @@ def run_algorithm(initialize,
catalyst.data.bundles.bundles : The available data bundles. catalyst.data.bundles.bundles : The available data bundles.
""" """
load_extensions( load_extensions(
default_extension, extensions, strict_extensions, environ) default_extension, extensions, strict_extensions, environ
)
if capital_base is None: if capital_base is None:
raise ValueError( raise ValueError(
@@ -569,7 +522,8 @@ def run_algorithm(initialize,
'amount of base currency available for trading. For example, ' 'amount of base currency available for trading. For example, '
'if the `capital_base` is 5ETH, the ' 'if the `capital_base` is 5ETH, the '
'`order_target_percent(asset, 1)` command will order 5ETH worth ' '`order_target_percent(asset, 1)` command will order 5ETH worth '
'of the specified asset.') 'of the specified asset.'
)
# I'm not sure that we need this since the modified DataPortal # I'm not sure that we need this since the modified DataPortal
# does not require extensions to be explicitly loaded. # does not require extensions to be explicitly loaded.
@@ -587,11 +541,13 @@ def run_algorithm(initialize,
elif len(non_none_data) != 1: elif len(non_none_data) != 1:
raise ValueError( raise ValueError(
'must specify one of `data`, `data_portal`, or `bundle`,' 'must specify one of `data`, `data_portal`, or `bundle`,'
' got: %r' % non_none_data) ' got: %r' % non_none_data,
)
elif 'bundle' not in non_none_data and bundle_timestamp is not None: elif 'bundle' not in non_none_data and bundle_timestamp is not None:
raise ValueError( raise ValueError(
'cannot specify `bundle_timestamp` without passing `bundle`') 'cannot specify `bundle_timestamp` without passing `bundle`',
)
return _run( return _run(
handle_data=handle_data, handle_data=handle_data,
initialize=initialize, initialize=initialize,
@@ -618,4 +574,5 @@ def run_algorithm(initialize,
live_graph=live_graph, live_graph=live_graph,
analyze_live=analyze_live, analyze_live=analyze_live,
simulate_orders=simulate_orders, simulate_orders=simulate_orders,
stats_output=stats_output) stats_output=stats_output
)
+10 -1
View File
@@ -2,6 +2,15 @@
Release Notes Release Notes
============= =============
Version 0.4.3
^^^^^^^^^^^^^
**Release Date**: 2017-01-05
Bug Fixes
~~~~~~~~~
- Fixed CLI issue (:issue:`137`)
- Upgraded CCXT
Version 0.4.2 Version 0.4.2
^^^^^^^^^^^^^ ^^^^^^^^^^^^^
**Release Date**: 2017-01-03 **Release Date**: 2017-01-03
@@ -39,7 +48,7 @@ Build
- Added market orders in live mode (:issue:`81`) - Added market orders in live mode (:issue:`81`)
Version 0.3.10 Version 0.3.10
^^^^^^^^^^^^^ ~~~~~~~~~~~~~~
**Release Date**: 2017-11-28 **Release Date**: 2017-11-28
Bug Fixes Bug Fixes
+1 -1
View File
@@ -20,7 +20,7 @@ dependencies:
- bcolz==0.12.1 - bcolz==0.12.1
- bottleneck==1.2.1 - bottleneck==1.2.1
- chardet==3.0.4 - chardet==3.0.4
- ccxt==1.10.283 - ccxt==1.10.565
- click==6.7 - click==6.7
- contextlib2==0.5.5 - contextlib2==0.5.5
- cycler==0.10.0 - cycler==0.10.0
+1 -1
View File
@@ -81,6 +81,6 @@ empyrical==0.2.1
tables==3.3.0 tables==3.3.0
#Catalyst dependencies #Catalyst dependencies
ccxt==1.10.283 ccxt==1.10.565
boto3==1.4.8 boto3==1.4.8
redo==1.6 redo==1.6
+3 -3
View File
@@ -19,16 +19,16 @@ class TestCCXT(BaseExchangeTestCase):
exchange_name=exchange_name, exchange_name=exchange_name,
key=auth['key'], key=auth['key'],
secret=auth['secret'], secret=auth['secret'],
base_currency='eth', base_currency='bnb',
) )
self.exchange.init() self.exchange.init()
def test_order(self): def test_order(self):
log.info('creating order') log.info('creating order')
asset = self.exchange.get_asset('neo_eth') asset = self.exchange.get_asset('neo_bnb')
order_id = self.exchange.order( order_id = self.exchange.order(
asset=asset, asset=asset,
style=ExchangeLimitOrder(limit_price=0.7), style=ExchangeLimitOrder(limit_price=10),
amount=1, amount=1,
) )
log.info('order created {}'.format(order_id)) log.info('order created {}'.format(order_id))
@@ -1,7 +1,7 @@
import random import random
import pandas as pd import pandas as pd
from logbook import Logger from logbook import TestHandler
from pandas.util.testing import assert_frame_equal from pandas.util.testing import assert_frame_equal
from catalyst import get_calendar from catalyst import get_calendar
@@ -11,8 +11,7 @@ from catalyst.exchange.utils.exchange_utils import get_candles_df
from catalyst.exchange.utils.factory import get_exchange from catalyst.exchange.utils.factory import get_exchange
from catalyst.exchange.utils.test_utils import output_df, \ from catalyst.exchange.utils.test_utils import output_df, \
select_random_assets select_random_assets
from catalyst.testing.fixtures import WithLogger, ZiplineTestCase
log = Logger('TestSuiteExchange')
pd.set_option('display.expand_frame_repr', False) pd.set_option('display.expand_frame_repr', False)
pd.set_option('precision', 8) pd.set_option('precision', 8)
@@ -20,7 +19,7 @@ pd.set_option('display.width', 1000)
pd.set_option('display.max_colwidth', 1000) pd.set_option('display.max_colwidth', 1000)
class TestSuiteBundle: class TestSuiteBundle(WithLogger, ZiplineTestCase):
@staticmethod @staticmethod
def get_data_portal(exchange_names): def get_data_portal(exchange_names):
open_calendar = get_calendar('OPEN') open_calendar = get_calendar('OPEN')
@@ -54,7 +53,8 @@ class TestSuiteBundle:
""" """
data = dict() data = dict()
log.info('creating data sample from bundle') log_catcher = TestHandler()
with log_catcher:
data['bundle'] = data_portal.get_history_window( data['bundle'] = data_portal.get_history_window(
assets=assets, assets=assets,
end_dt=end_dt, end_dt=end_dt,
@@ -63,11 +63,10 @@ class TestSuiteBundle:
field='close', field='close',
data_frequency=data_frequency, data_frequency=data_frequency,
) )
log.info('bundle data:\n{}'.format( print('bundle data:\n{}'.format(
data['bundle'].tail(10)) data['bundle'].tail(10))
) )
log.info('creating data sample from exchange api')
candles = exchange.get_candles( candles = exchange.get_candles(
end_dt=end_dt, end_dt=end_dt,
freq=freq, freq=freq,
@@ -81,13 +80,13 @@ class TestSuiteBundle:
bar_count=bar_count, bar_count=bar_count,
end_dt=end_dt, end_dt=end_dt,
) )
log.info('exchange data:\n{}'.format( print('exchange data:\n{}'.format(
data['exchange'].tail(10)) data['exchange'].tail(10))
) )
for source in data: for source in data:
df = data[source] df = data[source]
path = output_df(df, assets, '{}_{}'.format(freq, source)) path = output_df(df, assets, '{}_{}'.format(freq, source))
log.info('saved {}:\n{}'.format(source, path)) print('saved {}:\n{}'.format(source, path))
assert_frame_equal( assert_frame_equal(
right=data['bundle'], right=data['bundle'],
@@ -1,21 +1,25 @@
import json import json
import os import os
import random import random
from logging import Logger from logging import Logger, WARNING
from time import sleep from time import sleep
import pandas as pd import pandas as pd
from catalyst.assets._assets import TradingPair
from logbook import TestHandler
from catalyst.exchange.exchange_errors import ExchangeRequestError from catalyst.exchange.exchange_errors import ExchangeRequestError
from catalyst.exchange.exchange_execution import ExchangeLimitOrder from catalyst.exchange.exchange_execution import ExchangeLimitOrder
from catalyst.exchange.utils.exchange_utils import get_exchange_folder from catalyst.exchange.utils.exchange_utils import get_exchange_folder
from catalyst.exchange.utils.test_utils import select_random_exchanges, \ from catalyst.exchange.utils.test_utils import select_random_exchanges, \
handle_exchange_error, select_random_assets handle_exchange_error, select_random_assets
from catalyst.testing import ZiplineTestCase
from catalyst.testing.fixtures import WithLogger
log = Logger('TestSuiteExchange') log = Logger('TestSuiteExchange')
class TestSuiteExchange: class TestSuiteExchange(WithLogger, ZiplineTestCase):
def _test_markets_exchange(self, exchange, attempts=0): def _test_markets_exchange(self, exchange, attempts=0):
assets = None assets = None
try: try:
@@ -156,12 +160,14 @@ class TestSuiteExchange:
base_currency=quote_currency, base_currency=quote_currency,
) # Type: list[Exchange] ) # Type: list[Exchange]
log_catcher = TestHandler()
with log_catcher:
for exchange in exchanges: for exchange in exchanges:
exchange.init() exchange.init()
assets = exchange.get_assets(quote_currency=quote_currency) assets = exchange.get_assets(quote_currency=quote_currency)
asset = select_random_assets(assets, 1)[0] asset = select_random_assets(assets, 1)[0]
assert asset self.assertIsInstance(asset, TradingPair)
tickers = exchange.tickers([asset]) tickers = exchange.tickers([asset])
price = tickers[asset]['last_price'] price = tickers[asset]['last_price']
@@ -179,11 +185,22 @@ class TestSuiteExchange:
sleep(1) sleep(1)
open_order, _ = exchange.get_order(order.id, asset) open_order, _ = exchange.get_order(order.id, asset)
assert open_order.status == 0 self.assertEqual(0, open_order.status)
exchange.cancel_order(open_order, asset) exchange.cancel_order(open_order, asset)
sleep(1) sleep(1)
canceled_order, _ = exchange.get_order(open_order.id, asset) canceled_order, _ = exchange.get_order(open_order.id, asset)
assert canceled_order.status == 2 warnings = [record for record in log_catcher.records if
record.level == WARNING]
self.assertEqual(0, len(warnings))
self.assertEqual(2, canceled_order.status)
print(
'tested {exchange} / {symbol}, order: {order}'.format(
exchange=exchange.name,
symbol=asset.symbol,
order=order.id,
)
)
pass pass