mirror of
https://github.com/wassname/catalyst.git
synced 2026-08-08 11:16:58 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
790ac22f8d | ||
|
|
4d8d1e33d0 | ||
|
|
595bd82234 | ||
|
|
9515d10cef | ||
|
|
56481bbbe0 | ||
|
|
79e4854973 | ||
|
|
b4e7629e8b | ||
|
|
a818d10d40 | ||
|
|
bd4b0d2756 |
@@ -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,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
@@ -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
|
||||||
|
)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
+8
-9
@@ -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'],
|
||||||
+22
-5
@@ -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
|
||||||
Reference in New Issue
Block a user