diff --git a/catalyst/algorithm.py b/catalyst/algorithm.py index ed10fedd..93ea60bb 100644 --- a/catalyst/algorithm.py +++ b/catalyst/algorithm.py @@ -1135,7 +1135,7 @@ class TradingAlgorithm(object): 'date_rule. You should use keyword argument ' 'time_rule= when calling schedule_function without ' 'specifying a date_rule', stacklevel=3) - + freq = self.sim_params.data_frequency date_rule = date_rule or date_rules.every_day() diff --git a/catalyst/examples/buy_the_dip_live.py b/catalyst/examples/buy_the_dip_live.py index d567f738..369f5b6a 100644 --- a/catalyst/examples/buy_the_dip_live.py +++ b/catalyst/examples/buy_the_dip_live.py @@ -8,6 +8,7 @@ from catalyst.api import ( get_open_orders, ) from catalyst.exchange.exchange_errors import ExchangeRequestError +import matplotlib.pyplot as plt algo_namespace = 'buy_the_dip_live' log = Logger(algo_namespace) @@ -15,7 +16,9 @@ log = Logger(algo_namespace) def initialize(context): log.info('initializing algo') - context.asset = symbol('eos_usd') + context.ASSET_NAME = 'EOS_USD' + context.TICK_SIZE = 1000.0 + context.asset = symbol(context.ASSET_NAME) context.TARGET_POSITIONS = 100 context.BUY_INCREMENT = 1 @@ -37,18 +40,18 @@ def handle_data(context, data): # bar_count=20, # frequency='1d' # ) - # ohlc = data.history(context.asset, - # fields=['price', 'volume'], - # bar_count=120, - # frequency='1m' - # ) + ohlc = data.history(context.asset, + fields=['price', 'volume'], + bar_count=120, + frequency='1m' + ) # ohlc = data.history([context.asset, symbol('iot_usd')], # fields=['price', 'volume'], # bar_count=20, # frequency='1d' # ) - # hist_price = ohlc['price'] + hist_price = ohlc['price'] cash = context.portfolio.cash log.info('base currency available: {cash}'.format(cash=cash)) @@ -110,16 +113,20 @@ def handle_data(context, data): leverage=context.account.leverage, ) - try: - context.perf_tracker.update_performance() - log.info('the performance:\n{}'.format( - context.perf_tracker.to_dict('minute') - )) - except Exception as e: - log.warn('unable to calculate performance: {}'.format(e)) pass +def analyze(context, stats): + pnl, = plt.plot(stats.index, stats['pnl'], '-', + color='blue', + linewidth=1.0, + label='P&L', + ) + + plt.legend(handles=[pnl]) + plt.show() + pass + exchange_conn = dict( name='bitfinex', key='', @@ -129,6 +136,7 @@ exchange_conn = dict( run_algorithm( initialize=initialize, handle_data=handle_data, + analyze=analyze, capital_base=100000, exchange_conn=exchange_conn, live=True, diff --git a/catalyst/exchange/algorithm_exchange.py b/catalyst/exchange/algorithm_exchange.py index 4455c6f7..ee65e255 100644 --- a/catalyst/exchange/algorithm_exchange.py +++ b/catalyst/exchange/algorithm_exchange.py @@ -13,6 +13,9 @@ from datetime import time from time import sleep import logbook +import signal +import sys +import pandas as pd import catalyst.protocol as zp from catalyst.algorithm import TradingAlgorithm @@ -41,9 +44,12 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): def __init__(self, *args, **kwargs): self.exchange = kwargs.pop('exchange', None) self.orders = {} + self.minute_perfs = [] + self.is_running = True self.retry_check_open_orders = 5 self.retry_update_portfolio = 5 + self.retry_get_open_orders = 5 self.retry_order = 1 self.retry_delay = 5 @@ -51,6 +57,16 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): log.info('exchange trading algorithm successfully initialized') + def signal_handler(self, signal, frame): + self.is_running = False + + log.info('You pressed Ctrl+C!') + stats = pd.DataFrame(self.minute_perfs) + stats.set_index('period_close', drop=True, inplace=True) + self.analyze(stats) + + sys.exit(0) + def _create_clock(self): # This method is taken from TradingAlgorithm. # The clock has been replaced to use RealtimeClock @@ -86,6 +102,8 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): "US/Eastern" ) + signal.signal(signal.SIGINT, self.signal_handler) + return ExchangeClock( self.sim_params.sessions, execution_opens, @@ -109,6 +127,9 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): universe_func=self._calculate_universe ) + # self.perf_tracker.cumulative_performance.keep_transactions = True + # self.perf_tracker.cumulative_performance.keep_orders = True + return self.trading_client.transform() def updated_portfolio(self): @@ -145,6 +166,9 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): return self._check_open_orders(attempt_index + 1) def handle_data(self, data): + if not self.is_running: + return + self._update_portfolio() transactions = self._check_open_orders() @@ -159,6 +183,25 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): # every bar no matter if the algorithm places an order or not. self.validate_account_controls() + try: + # Since the clock runs 24/7, I trying to disable the daily + # Performance tracker and keep only minute and cumulative + self.perf_tracker.update_performance() + perf_dict = self.perf_tracker.to_dict('minute') + + # Weird messy part of zipline + # I derived the logic from: catalyst.algorithm.TradingAlgorithm#_create_daily_stats + minute_perf = perf_dict['minute_perf'] + minute_perf.update(perf_dict['cumulative_risk_metrics']) + + log.debug('the minute performance:\n{}'.format( + minute_perf + )) + self.minute_perfs.append(minute_perf) + + except Exception as e: + log.warn('unable to calculate performance: {}'.format(e)) + def _order(self, asset, amount, @@ -202,11 +245,22 @@ class ExchangeTradingAlgorithm(TradingAlgorithm): def batch_market_order(self, share_counts): raise NotImplementedError() + def _get_open_orders(self, asset=None, attempt_index=0): + try: + return self.exchange.get_open_orders(asset) + except ExchangeRequestError as e: + log.warn( + 'open orders attempt {}: {}'.format(attempt_index, e) + ) + if attempt_index < self.retry_get_open_orders: + sleep(self.retry_delay) + return self._get_open_orders(asset, attempt_index + 1) + @error_keywords(sid='Keyword argument `sid` is no longer supported for ' 'get_open_orders. Use `asset` instead.') @api_method def get_open_orders(self, asset=None): - return self.exchange.get_open_orders(asset) + return self._get_open_orders(asset) @api_method def get_order(self, order_id): diff --git a/catalyst/exchange/bitfinex.py b/catalyst/exchange/bitfinex.py index f2e72b8c..2b6b885f 100644 --- a/catalyst/exchange/bitfinex.py +++ b/catalyst/exchange/bitfinex.py @@ -54,7 +54,8 @@ class Bitfinex(Exchange): def _request(self, operation, data, version='v1'): payload_object = { 'request': '/{}/{}'.format(version, operation), - 'nonce': '{0:f}'.format(time.time() * 100000), # convert to string + 'nonce': '{0:f}'.format(time.time() * 1000000), + # convert to string 'options': {} } @@ -154,8 +155,10 @@ class Bitfinex(Exchange): # TODO: bitfinex does not specify comission. I could calculate it but not sure if it's worth it. commission = None + # TODO: zipline likes rounded dates to match statistics, is this ok? date = pd.Timestamp.utcfromtimestamp(float(order_status['timestamp'])) date = pytz.utc.localize(date) + date = date.floor('1 min') order = ExchangeOrder( dt=date, asset=self.assets[order_status['symbol']], @@ -451,6 +454,7 @@ class Bitfinex(Exchange): sell_price_oco=0 ) + date = pd.Timestamp.utcnow().floor('1 min') # Making zipline happy try: response = self._request('order/new', req) exchange_order = response.json() @@ -465,7 +469,7 @@ class Bitfinex(Exchange): order_id = exchange_order['id'] order = ExchangeOrder( - dt=pd.Timestamp.utcnow(), + dt=date, asset=asset, amount=amount, stop=style.get_stop_price(is_buy), diff --git a/catalyst/exchange/exchange.py b/catalyst/exchange/exchange.py index 5d9c532e..6c2f5ea2 100644 --- a/catalyst/exchange/exchange.py +++ b/catalyst/exchange/exchange.py @@ -101,7 +101,7 @@ class Exchange: transaction = Transaction( asset=order.asset, amount=order.amount, - dt=pd.Timestamp.utcnow(), + dt=pd.Timestamp.utcnow().floor('1 min'), price=order.executed_price, order_id=order.id, commission=order.commission @@ -334,7 +334,10 @@ class Exchange: if field not in ohlc: raise KeyError('Invalid column: %s' % field) - return ohlc[field] + value = ohlc[field] + log.debug('got spot value: {}'.format(value)) + + return value def get_history_window(self, assets, diff --git a/catalyst/exchange/exchange_clock.py b/catalyst/exchange/exchange_clock.py index 15a27e60..4698297c 100644 --- a/catalyst/exchange/exchange_clock.py +++ b/catalyst/exchange/exchange_clock.py @@ -15,8 +15,6 @@ from time import sleep from logbook import Logger import pandas as pd -import signal -import sys from catalyst.gens.sim_engine import ( BAR, @@ -59,13 +57,6 @@ class ExchangeClock(object): self._before_trading_start_bar_yielded = True def __iter__(self): - def signal_handler(signal, frame): - log.info('You pressed Ctrl+C!') - # yield pd.Timestamp.utcnow(), BAR - sys.exit(0) - - signal.signal(signal.SIGINT, signal_handler) - yield pd.Timestamp.utcnow(), SESSION_START while True: diff --git a/catalyst/utils/run_algo.py b/catalyst/utils/run_algo.py index 3e271c6e..c1e0d674 100644 --- a/catalyst/utils/run_algo.py +++ b/catalyst/utils/run_algo.py @@ -184,9 +184,6 @@ def _run(handle_data, first_trading_day = bundle_data.minute_bar_reader.first_trading_day - # DataPortalClass = (partial(DataPortalExchange, exchange) - # if exchange - # else DataPortal) data = DataPortal( env.asset_finder, open_calendar,